From d20177ae3553e90caaf90678b38b45e760f1ece1 Mon Sep 17 00:00:00 2001 From: Anders Date: Wed, 10 Jul 2019 02:29:01 +0200 Subject: [PATCH 01/30] - Add new ESP-NOW mesh backend. - Add HelloEspnow.ino example to demonstrate the ESP-NOW mesh backend features. - Deprecate the ESP8266WiFiMesh class in favour of the new ESP-NOW and TCP/IP backends. - Update the TCP/IP mesh backend to use the new lwIP version preprocessor flag and remove obsolete preprocessor flags. --- libraries/ESP8266WiFiMesh/README.md | 16 +- .../examples/HelloEspnow/HelloEspnow.ino | 312 +++ libraries/ESP8266WiFiMesh/library.properties | 4 +- libraries/ESP8266WiFiMesh/src/Crypto.cpp | 1002 +++++++ libraries/ESP8266WiFiMesh/src/Crypto.h | 254 ++ .../ESP8266WiFiMesh/src/ESP8266WiFiMesh.cpp | 89 +- .../ESP8266WiFiMesh/src/ESP8266WiFiMesh.h | 32 +- .../src/EncryptedConnectionData.cpp | 181 ++ .../src/EncryptedConnectionData.h | 96 + .../src/EncryptedConnectionLog.cpp | 92 + .../src/EncryptedConnectionLog.h | 68 + .../ESP8266WiFiMesh/src/EspnowMeshBackend.cpp | 2295 +++++++++++++++++ .../ESP8266WiFiMesh/src/EspnowMeshBackend.h | 764 ++++++ .../src/EspnowProtocolInterpreter.cpp | 105 + .../src/EspnowProtocolInterpreter.h | 74 + .../src/ExpiringTimeTracker.cpp | 59 + .../ESP8266WiFiMesh/src/ExpiringTimeTracker.h | 48 + .../ESP8266WiFiMesh/src/JsonTranslator.cpp | 269 ++ .../ESP8266WiFiMesh/src/JsonTranslator.h | 109 + .../ESP8266WiFiMesh/src/MeshBackendBase.cpp | 276 ++ .../ESP8266WiFiMesh/src/MeshBackendBase.h | 302 +++ libraries/ESP8266WiFiMesh/src/MessageData.cpp | 69 + libraries/ESP8266WiFiMesh/src/MessageData.h | 54 + .../ESP8266WiFiMesh/src/MutexTracker.cpp | 72 + libraries/ESP8266WiFiMesh/src/MutexTracker.h | 71 + .../ESP8266WiFiMesh/src/PeerRequestLog.cpp | 50 + .../ESP8266WiFiMesh/src/PeerRequestLog.h | 60 + libraries/ESP8266WiFiMesh/src/RequestData.cpp | 32 + libraries/ESP8266WiFiMesh/src/RequestData.h | 47 + .../ESP8266WiFiMesh/src/ResponseData.cpp | 79 + libraries/ESP8266WiFiMesh/src/ResponseData.h | 59 + .../ESP8266WiFiMesh/src/TcpIpMeshBackend.cpp | 469 ++++ .../ESP8266WiFiMesh/src/TcpIpMeshBackend.h | 214 ++ libraries/ESP8266WiFiMesh/src/TimeTracker.cpp | 39 + libraries/ESP8266WiFiMesh/src/TimeTracker.h | 45 + .../src/TypeConversionFunctions.cpp | 87 + .../src/TypeConversionFunctions.h | 65 + .../ESP8266WiFiMesh/src/UtilityFunctions.cpp | 45 + .../ESP8266WiFiMesh/src/UtilityFunctions.h | 35 + .../ESP8266WiFiMesh/src/UtilityMethods.cpp | 74 +- 40 files changed, 8024 insertions(+), 89 deletions(-) create mode 100644 libraries/ESP8266WiFiMesh/examples/HelloEspnow/HelloEspnow.ino create mode 100644 libraries/ESP8266WiFiMesh/src/Crypto.cpp create mode 100644 libraries/ESP8266WiFiMesh/src/Crypto.h create mode 100644 libraries/ESP8266WiFiMesh/src/EncryptedConnectionData.cpp create mode 100644 libraries/ESP8266WiFiMesh/src/EncryptedConnectionData.h create mode 100644 libraries/ESP8266WiFiMesh/src/EncryptedConnectionLog.cpp create mode 100644 libraries/ESP8266WiFiMesh/src/EncryptedConnectionLog.h create mode 100644 libraries/ESP8266WiFiMesh/src/EspnowMeshBackend.cpp create mode 100644 libraries/ESP8266WiFiMesh/src/EspnowMeshBackend.h create mode 100644 libraries/ESP8266WiFiMesh/src/EspnowProtocolInterpreter.cpp create mode 100644 libraries/ESP8266WiFiMesh/src/EspnowProtocolInterpreter.h create mode 100644 libraries/ESP8266WiFiMesh/src/ExpiringTimeTracker.cpp create mode 100644 libraries/ESP8266WiFiMesh/src/ExpiringTimeTracker.h create mode 100644 libraries/ESP8266WiFiMesh/src/JsonTranslator.cpp create mode 100644 libraries/ESP8266WiFiMesh/src/JsonTranslator.h create mode 100644 libraries/ESP8266WiFiMesh/src/MeshBackendBase.cpp create mode 100644 libraries/ESP8266WiFiMesh/src/MeshBackendBase.h create mode 100644 libraries/ESP8266WiFiMesh/src/MessageData.cpp create mode 100644 libraries/ESP8266WiFiMesh/src/MessageData.h create mode 100644 libraries/ESP8266WiFiMesh/src/MutexTracker.cpp create mode 100644 libraries/ESP8266WiFiMesh/src/MutexTracker.h create mode 100644 libraries/ESP8266WiFiMesh/src/PeerRequestLog.cpp create mode 100644 libraries/ESP8266WiFiMesh/src/PeerRequestLog.h create mode 100644 libraries/ESP8266WiFiMesh/src/RequestData.cpp create mode 100644 libraries/ESP8266WiFiMesh/src/RequestData.h create mode 100644 libraries/ESP8266WiFiMesh/src/ResponseData.cpp create mode 100644 libraries/ESP8266WiFiMesh/src/ResponseData.h create mode 100644 libraries/ESP8266WiFiMesh/src/TcpIpMeshBackend.cpp create mode 100644 libraries/ESP8266WiFiMesh/src/TcpIpMeshBackend.h create mode 100644 libraries/ESP8266WiFiMesh/src/TimeTracker.cpp create mode 100644 libraries/ESP8266WiFiMesh/src/TimeTracker.h create mode 100644 libraries/ESP8266WiFiMesh/src/UtilityFunctions.cpp create mode 100644 libraries/ESP8266WiFiMesh/src/UtilityFunctions.h diff --git a/libraries/ESP8266WiFiMesh/README.md b/libraries/ESP8266WiFiMesh/README.md index 8d955220ca..dfd6f8fdda 100644 --- a/libraries/ESP8266WiFiMesh/README.md +++ b/libraries/ESP8266WiFiMesh/README.md @@ -3,9 +3,9 @@ ESP8266 WiFi Mesh A library for turning your ESP8266 into a mesh network node. -The library has been tested and works with Arduino core for ESP8266 version 2.3.0 (with default lwIP) and 2.4.2 or higher (with lwIP 1.4 and lwIP2). +The library has been tested and works with Arduino core for ESP8266 version 2.6.0 (with lwIP2). It may work with earlier and later core releases, but this has not been tested during development. -**Note:** This mesh library has been rewritten for core release 2.4.2. The old method signatures have been retained for compatibility purposes, but will be removed in core release 2.5.0. If you are still using these old method signatures please consider migrating to the new API shown in the `ESP8266WiFiMesh.h` source file. +**Note:** This mesh library has been rewritten for core release 2.6.0. The old method signatures have been retained for compatibility purposes, but will be removed in core release 3.0.0. If you are still using these old method signatures please consider migrating to the new API shown in the `EspnowMeshBackend.h` or `TcpIpMeshBackend.h` source files. Usage ----- @@ -48,13 +48,17 @@ ESP8266WiFiMesh(requestHandlerType requestHandler, responseHandlerType responseH ### Note -* This library can use static IP:s for the nodes to speed up connection times. To enable this, use the `setStaticIP` method after calling the `begin` method, as in the included example. Ensure that nodes connecting to the same AP have distinct static IP:s. Node IP:s need to be at the same subnet as the server gateway (192.168.4 for this library by default). It may also be worth noting that station gateway IP must match the IP for the server on the nodes, though this is the default setting for the library. +* This library can use static IP:s for the nodes to speed up connection times. To enable this, use the `setStaticIP` method after calling the `begin` method, as in the included example. When using static IP, the following is good to keep in mind: - At the moment static IP is a global setting, meaning that all ESP8266WiFiMesh instances on the same ESP8266 share the same static IP settings. + Ensure that nodes connecting to the same AP have distinct static IP:s. -* When Arduino core for ESP8266 version 2.4.2 or higher is used, there are optimizations available for WiFi scans and static IP use to reduce the time it takes for nodes to connect to each other. These optimizations are enabled by default. To take advantage of the static IP optimizations you also need to use lwIP2. The lwIP version can be changed in the Tools menu of Arduino IDE. + Node IP:s need to be at the same subnet as the server gateway (192.168.4 for this library by default). + + Station gateway IP must match the IP for the server on the nodes. This is the default setting for the library. - If you are using a core version prior to 2.4.2 it is possible to disable the WiFi scan and static IP optimizations by commenting out the `ENABLE_STATIC_IP_OPTIMIZATION` and `ENABLE_WIFI_SCAN_OPTIMIZATION` defines in ESP8266WiFiMesh.h. Press Ctrl+K in the Arduino IDE while an example from the mesh library is opened, to open the library folder (or click "Show Sketch Folder" in the Sketch menu). ESP8266WiFiMesh.h can then be found at ESP8266WiFiMesh/src. Edit the file with any text editor. + Static IP is a global setting (for now), meaning that all ESP8266WiFiMesh instances on the same ESP8266 share the same static IP settings. + +* When Arduino core for ESP8266 version 2.4.2 or higher is used, there are optimizations available for WiFi scans and static IP use to reduce the time it takes for nodes to connect to each other. These optimizations are enabled by default. To take advantage of the static IP optimizations you also need to use lwIP2. The lwIP version can be changed in the Tools menu of Arduino IDE. * The WiFi scan optimization mentioned above works by making WiFi scans only search through the same WiFi channel as the ESP8266WiFiMesh instance is using. If you would like to scan all WiFi channels instead, set the `scanAllWiFiChannels` argument of the `attemptTransmission` method to `true`. Note that scanning all WiFi channels will slow down scans considerably and make it more likely that existing WiFi connections will break during scans. Also note that if the ESP8266 has an active AP, that AP will switch WiFi channel to match that of any other AP the ESP8266 connects to (compare next bullet point). This can make it impossible for other nodes to detect the AP if they are scanning the wrong WiFi channel. To remedy this, force the AP back on the original channel by using the `restartAP` method of the current AP controller once the ESP8266 has disconnected from the other AP. This would typically be done like so: diff --git a/libraries/ESP8266WiFiMesh/examples/HelloEspnow/HelloEspnow.ino b/libraries/ESP8266WiFiMesh/examples/HelloEspnow/HelloEspnow.ino new file mode 100644 index 0000000000..bcbad36f20 --- /dev/null +++ b/libraries/ESP8266WiFiMesh/examples/HelloEspnow/HelloEspnow.ino @@ -0,0 +1,312 @@ +#include +#include +#include +#include + +/** + 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). + The reason is that this approach will place the strings in flash memory which will help save RAM during program execution. + Reading strings from flash will be slower than reading them from RAM, + but this will be a negligible difference when printing them to Serial. + + More on F(), FPSTR() and PROGMEM: + https://github.com/esp8266/Arduino/issues/1143 + https://arduino-esp8266.readthedocs.io/en/latest/PROGMEM.html +*/ +const char exampleMeshName[] PROGMEM = "MeshNode_"; // The name of the mesh network. Used as prefix for the node SSID and to find other network nodes in the example networkFilter function 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. + +// 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. +uint8_t espnowEncryptionKey[16] = {0x33, 0x44, 0x33, 0x44, 0x33, 0x44, 0x33, 0x44, // This is the key for encrypting transmissions. + 0x33, 0x44, 0x33, 0x44, 0x33, 0x44, 0x32, 0x11 + }; +uint8_t espnowEncryptionKok[16] = {0x22, 0x44, 0x33, 0x44, 0x33, 0x44, 0x33, 0x44, // This is the key for encrypting the encryption key. + 0x33, 0x44, 0x33, 0x44, 0x33, 0x44, 0x32, 0x33 + }; +uint8_t espnowHashKey[16] = {0xEF, 0x44, 0x33, 0x0C, 0x33, 0x44, 0xFE, 0x44, // This is the secret key used for HMAC during encrypted connection requests. + 0x33, 0x44, 0x33, 0xB0, 0x33, 0x44, 0x32, 0xAD + }; + +unsigned int requestNumber = 0; +unsigned int responseNumber = 0; + +String manageRequest(const String &request, MeshBackendBase &meshInstance); +transmission_status_t manageResponse(const String &response, MeshBackendBase &meshInstance); +void networkFilter(int numberOfNetworks, MeshBackendBase &meshInstance); + +/* Create the mesh node object */ +EspnowMeshBackend espnowNode = EspnowMeshBackend(manageRequest, manageResponse, networkFilter, FPSTR(exampleWiFiPassword), espnowEncryptionKey, espnowHashKey, FPSTR(exampleMeshName), uint64ToString(ESP.getChipId()), true); + +/** + Callback for when other nodes send you a request + + @param request The request string received from another node in the mesh + @param meshInstance The MeshBackendBase instance that called the function. + @returns The string to send back to the other node. For ESP-NOW, return an empy string ("") if no response should be sent. +*/ +String manageRequest(const String &request, MeshBackendBase &meshInstance) { + // We do not store strings in flash (via F()) in this function. + // The reason is that the other node will be waiting for our response, + // so keeping the strings in RAM will give a (small) improvement in response time. + // Of course, it is advised to adjust this approach based on RAM requirements. + + // To get the actual class of the polymorphic meshInstance, do as follows (meshBackendCast replaces dynamic_cast since RTTI is disabled) + if (EspnowMeshBackend *espnowInstance = meshBackendCast(&meshInstance)) { + String messageEncrypted = espnowInstance->receivedEncryptedMessage() ? ", Encrypted" : ", Unencrypted"; + Serial.print("ESP-NOW (" + espnowInstance->getSenderMac() + messageEncrypted + "): "); + } else if (TcpIpMeshBackend *tcpIpInstance = meshBackendCast(&meshInstance)) { + (void)tcpIpInstance; // This is useful to remove a "unused parameter" compiler warning. Does nothing else. + Serial.print("TCP/IP: "); + } else { + Serial.print("UNKNOWN!: "); + } + + /* Print out received message */ + // Only show first 100 characters because printing a large String takes a lot of time, which is a bad thing for a callback function. + // If you need to print the whole String it is better to store it and print it in the loop() later. + Serial.print("Request received: "); + Serial.println(request.substring(0, 100)); + + /* return a string to send back */ + return ("Hello world response #" + String(responseNumber++) + " from " + meshInstance.getMeshName() + meshInstance.getNodeID() + " with AP MAC " + WiFi.softAPmacAddress() + "."); +} + +/** + Callback for when you get a response from other nodes + + @param response The response string received from another node in the mesh + @param meshInstance The MeshBackendBase instance that called the function. + @returns The status code resulting from the response, as an int +*/ +transmission_status_t manageResponse(const String &response, MeshBackendBase &meshInstance) { + transmission_status_t statusCode = TS_TRANSMISSION_COMPLETE; + + // To get the actual class of the polymorphic meshInstance, do as follows (meshBackendCast replaces dynamic_cast since RTTI is disabled) + if (EspnowMeshBackend *espnowInstance = meshBackendCast(&meshInstance)) { + String messageEncrypted = espnowInstance->receivedEncryptedMessage() ? ", Encrypted" : ", Unencrypted"; + Serial.print("ESP-NOW (" + espnowInstance->getSenderMac() + messageEncrypted + "): "); + } else if (TcpIpMeshBackend *tcpIpInstance = meshBackendCast(&meshInstance)) { + Serial.print("TCP/IP: "); + + // Getting the sent message like this will work as long as ONLY(!) TCP/IP is used. + // With TCP/IP the response will follow immediately after the request, so the stored message will not have changed. + // With ESP-NOW there is no guarantee when or if a response will show up, it can happen before or after the stored message is changed. + // So for ESP-NOW, adding unique identifiers in the response and request is required to associate a response with a request. + Serial.print(F("Request sent: ")); + Serial.println(tcpIpInstance->getMessage().substring(0, 100)); + } else { + Serial.print("UNKNOWN!: "); + } + + /* Print out received message */ + // Only show first 100 characters because printing a large String takes a lot of time, which is a bad thing for a callback function. + // If you need to print the whole String it is better to store it and print it in the loop() later. + Serial.print(F("Response received: ")); + Serial.println(response.substring(0, 100)); + + return statusCode; +} + +/** + Callback used to decide which networks to connect to once a WiFi scan has been completed. + + @param numberOfNetworks The number of networks found in the WiFi scan. + @param meshInstance The MeshBackendBase instance that called the function. +*/ +void networkFilter(int numberOfNetworks, MeshBackendBase &meshInstance) { + // Note that the network index of a given node may change whenever a new scan is done. + for (int networkIndex = 0; networkIndex < numberOfNetworks; ++networkIndex) { + String currentSSID = WiFi.SSID(networkIndex); + int meshNameIndex = currentSSID.indexOf(meshInstance.getMeshName()); + + /* Connect to any _suitable_ APs which contain meshInstance.getMeshName() */ + if (meshNameIndex >= 0) { + uint64_t targetNodeID = stringToUint64(currentSSID.substring(meshNameIndex + meshInstance.getMeshName().length())); + + if (targetNodeID < stringToUint64(meshInstance.getNodeID())) { + MeshBackendBase::connectionQueue.push_back(NetworkInfo(networkIndex)); + } + } + } +} + +void setup() { + // Prevents the flash memory from being worn out, see: https://github.com/esp8266/Arduino/issues/1054 . + // This will however delay node WiFi start-up by about 700 ms. The delay is 900 ms if we otherwise would have stored the WiFi network we want to connect to. + WiFi.persistent(false); + + Serial.begin(115200); + delay(50); // Wait for Serial. + + //yield(); // Use this if you don't want to wait for Serial, but not with the ESP-NOW backend (yield() causes crashes with ESP-NOW). + + // The WiFi.disconnect() ensures that the WiFi is working correctly. If this is not done before receiving WiFi connections, + // those WiFi connections will take a long time to make or sometimes will not work at all. + WiFi.disconnect(); + + Serial.println(); + Serial.println(); + + Serial.println(F("Note that this library can use static IP:s for the nodes with the TCP/IP backend to speed up connection times.\n" + "Use the setStaticIP method to enable this.\n" + "Ensure that nodes connecting to the same AP have distinct static IP:s.\n" + "Also, remember to change the default mesh network password and ESP-NOW keys!\n\n")); + + Serial.println(F("Setting up mesh node...")); + + /* Initialise the mesh node */ + espnowNode.begin(); + + // Note: This changes the Kok for all EspnowMeshBackend instances on this ESP8266. + // Encrypted connections added before the Kok change will retain their old Kok. + // Both Kok and encryption key must match in an encrypted connection pair for encrypted communication to be possible. + // Otherwise the transmissions will never reach the recipient, even though acks are received by the sender. + EspnowMeshBackend::setEspnowEncryptionKok(espnowEncryptionKok); + espnowNode.setEspnowEncryptionKey(espnowEncryptionKey); + + // Makes it possible to find the node through scans, and also makes it possible to recover from an encrypted connection where only the other node is encrypted. + // Note that only one AP can be active at a time in total, and this will always be the one which was last activated. + // Thus the AP is shared by all backends. + espnowNode.activateAP(); + + // Storing our message in the EspnowMeshBackend instance is not required, but can be useful for organizing code, especially when using many EspnowMeshBackend instances. + // Note that calling espnowNode.attemptTransmission will replace the stored message with whatever message is transmitted. + espnowNode.setMessage(String(F("Hello world request #")) + String(requestNumber) + String(F(" from ")) + espnowNode.getMeshName() + espnowNode.getNodeID() + String(F("."))); +} + +int32_t timeOfLastScan = -10000; +void loop() { + // The performEspnowMaintainance() method performs all the background operations for the EspnowMeshBackend. + // It is recommended to place it in the beginning of the loop(), unless there is a need to put it elsewhere. + // Among other things, the method cleans up old Espnow log entries (freeing up RAM) and sends the responses you provide to Espnow requests. + // Note that depending on the amount of responses to send and their length, this method can take tens or even hundreds of milliseconds to complete. + // More intense transmission activity and less frequent calls to performEspnowMaintainance will likely cause the method to take longer to complete, so plan accordingly. + + //Should not be used inside responseHandler, requestHandler or networkFilter callbacks since performEspnowMaintainance() can alter the ESP-NOW state. + EspnowMeshBackend::performEspnowMaintainance(); + + if (millis() - timeOfLastScan > 10000) { // Give other nodes some time to connect between data transfers. + uint32_t startTime = millis(); + + Serial.println("\nPerforming unencrypted ESP-NOW transmissions."); + espnowNode.attemptTransmission(espnowNode.getMessage()); + Serial.println("Scan and " + String(MeshBackendBase::latestTransmissionOutcomes.size()) + " transmissions done in " + String(millis() - startTime) + " ms."); + + // Wait for response. espnowDelay continuously calls performEspnowMaintainance() so we will respond to ESP-NOW request while waiting. + // Should not be used inside responseHandler, requestHandler or networkFilter callbacks since performEspnowMaintainance() can alter the ESP-NOW state. + espnowDelay(100); + + timeOfLastScan = millis(); + + // One way to check how attemptTransmission worked out + if (MeshBackendBase::latestTransmissionSuccessful()) { + Serial.println(F("Transmission successful.")); + } + + // Another way to check how attemptTransmission worked out + if (MeshBackendBase::latestTransmissionOutcomes.empty()) { + Serial.println(F("No mesh AP found.")); + } else { + for (TransmissionResult &transmissionResult : MeshBackendBase::latestTransmissionOutcomes) { + if (transmissionResult.transmissionStatus == TS_TRANSMISSION_FAILED) { + Serial.println(String(F("Transmission failed to mesh AP ")) + transmissionResult.SSID); + } else if (transmissionResult.transmissionStatus == TS_CONNECTION_FAILED) { + Serial.println(String(F("Connection failed to mesh AP ")) + transmissionResult.SSID); + } else if (transmissionResult.transmissionStatus == TS_TRANSMISSION_COMPLETE) { + // No need to do anything, transmission was successful. + } else { + Serial.println(String(F("Invalid transmission status for ")) + transmissionResult.SSID + String(F("!"))); + assert(F("Invalid transmission status returned from responseHandler!") && false); + } + } + + Serial.println("\nPerforming encrypted ESP-NOW transmissions."); + + // We can create encrypted connections to individual nodes so that all ESP-NOW communication with the node will be encrypted. + if (espnowNode.requestEncryptedConnection(MeshBackendBase::connectionQueue[0].BSSID) == ECS_CONNECTION_ESTABLISHED) { + // The WiFi scan will detect the AP MAC, but this will automatically be converted to the encrypted STA MAC by the framework. + String peerMac = macToString(MeshBackendBase::connectionQueue[0].BSSID); + + Serial.println("Encrypted ESP-NOW connection with " + peerMac + " established!"); + + // Making a transmission now will cause messages to MeshBackendBase::connectionQueue[0].BSSID to be encrypted. + String espnowMessage = "This message is encrypted only when received by node " + peerMac; + Serial.println("\nTransmitting: " + espnowMessage); + espnowNode.attemptTransmission(espnowMessage, false); + espnowDelay(100); // Wait for response. + + // A connection can be serialized and stored for later use. + // Note that this saves the current state only, so if encrypted communication between the nodes happen after this, the stored state is invalid. + String serializedEncryptedConnection = EspnowMeshBackend::serializeEncryptedConnection(MeshBackendBase::connectionQueue[0].BSSID); + + Serial.println(); + // We can remove an encrypted connection like so. + espnowNode.removeEncryptedConnection(MeshBackendBase::connectionQueue[0].BSSID); + + // Note that the peer will still be encrypted, so although we can send unencrypted messages to the peer, we cannot read the encrypted responses it sends back. + espnowMessage = "This message is no longer encrypted when received by node " + peerMac; + Serial.println("\nTransmitting: " + espnowMessage); + espnowNode.attemptTransmission(espnowMessage, false); + espnowDelay(100); // Wait for response. + Serial.println("Cannot read the encrypted response..."); + + // Let's re-add our stored connection so we can communicate properly with MeshBackendBase::connectionQueue[0].BSSID again! + espnowNode.addEncryptedConnection(serializedEncryptedConnection); + + espnowMessage = "This message is once again encrypted when received by node " + peerMac; + Serial.println("\nTransmitting: " + espnowMessage); + espnowNode.attemptTransmission(espnowMessage, false); + espnowDelay(100); // Wait for response. + + Serial.println(); + // If we want to remove the encrypted connection on both nodes, we can do it like this. + encrypted_connection_removal_outcome_t removalOutcome = espnowNode.requestEncryptedConnectionRemoval(MeshBackendBase::connectionQueue[0].BSSID); + if (removalOutcome == ECRO_REMOVAL_SUCCEEDED) { + Serial.println(peerMac + " is no longer encrypted!\n"); + + // Of course, we can also just create a temporary encrypted connection that will remove itself once its duration has passed. + if (espnowNode.requestTemporaryEncryptedConnection(MeshBackendBase::connectionQueue[0].BSSID, 1000) == ECS_CONNECTION_ESTABLISHED) { + espnowDelay(42); + uint32_t remainingDuration = 0; + EspnowMeshBackend::getConnectionInfo(MeshBackendBase::connectionQueue[0].BSSID, &remainingDuration); + + espnowMessage = "Messages this node sends to " + peerMac + " will be encrypted for " + String(remainingDuration) + " ms more."; + Serial.println("\nTransmitting: " + espnowMessage); + espnowNode.attemptTransmission(espnowMessage, false); + + EspnowMeshBackend::getConnectionInfo(MeshBackendBase::connectionQueue[0].BSSID, &remainingDuration); + espnowDelay(remainingDuration + 100); + + espnowMessage = "Due to encrypted connection expiration, this message is no longer encrypted when received by node " + peerMac; + Serial.println("\nTransmitting: " + espnowMessage); + espnowNode.attemptTransmission(espnowMessage, false); + espnowDelay(100); // Wait for response. + } + + // Or if we prefer we can just let the library automatically create brief encrypted connections which are long enough to transmit an encrypted message. + // Note that encrypted responses will not be received, unless there already was an encrypted connection established with the peer before attemptAutoEncryptingTransmission was called. + espnowMessage = "This message is always encrypted, regardless of receiver."; + Serial.println("\nTransmitting: " + espnowMessage); + espnowNode.attemptAutoEncryptingTransmission(espnowMessage); + espnowDelay(100); // Wait for response. + } else { + Serial.println("Ooops! Encrypted connection removal failed. Status: " + String(removalOutcome)); + } + + // Finally, should you ever want to stop other parties from sending unencrypted messages to the node + // setAcceptsUnencryptedRequests(false); + // can be used for this. It applies to both encrypted connection requests and regular transmissions. + + Serial.println("\n##############################################################################################"); + } + + // Our last request was sent to all nodes found, so time to create a new request. + espnowNode.setMessage(String(F("Hello world request #")) + String(++requestNumber) + String(F(" from ")) + + espnowNode.getMeshName() + espnowNode.getNodeID() + String(F("."))); + } + + Serial.println(); + } +} diff --git a/libraries/ESP8266WiFiMesh/library.properties b/libraries/ESP8266WiFiMesh/library.properties index ddfea96a58..2ff47b0ead 100644 --- a/libraries/ESP8266WiFiMesh/library.properties +++ b/libraries/ESP8266WiFiMesh/library.properties @@ -1,6 +1,6 @@ name=ESP8266WiFiMesh -version=2.1 -author=Julian Fell +version=2.2 +author=Julian Fell, Anders Löfgren maintainer=Anders Löfgren sentence=Mesh network library paragraph=The library sets up a Mesh Node which acts as a router, creating a Mesh Network with other nodes. diff --git a/libraries/ESP8266WiFiMesh/src/Crypto.cpp b/libraries/ESP8266WiFiMesh/src/Crypto.cpp new file mode 100644 index 0000000000..bd8f51dea9 --- /dev/null +++ b/libraries/ESP8266WiFiMesh/src/Crypto.cpp @@ -0,0 +1,1002 @@ +/** + * An extremely minimal crypto library for Arduino devices. + * + * The SHA256 and AES implementations are derived from axTLS + * (http://axtls.sourceforge.net/), Copyright (c) 2008, Cameron Rich. + * + * Ported and refactored by Chris Ellis 2016. + * pkcs7 padding routines added by Mike Killewald Nov 26, 2017 (adopted from https://github.com/spaniakos/AES). + * + License + ======= + Balsa SCGI + Copyright (c) 2012, Chris Ellis + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "Crypto.h" + +/** + * Byte order helpers + */ + + +//#if BYTE_ORDER == BIG_ENDIAN +/* +inline static uint16_t crypto_htons(uint16_t x) +{ + return x; +} + +inline static uint16_t crypto_ntohs(uint16_t x) +{ + return x; +} + +inline static uint32_t crypto_htonl(uint32_t x) +{ + return x; +} + +inline static uint32_t crypto_ntohl(uint32_t x) +{ + return x; +} +*/ +//#else + +inline static uint16_t crypto_htons(uint16_t x) +{ + return ( + ((x & 0xff) << 8) | + ((x & 0xff00) >> 8) + ); +} + +inline static uint16_t crypto_ntohs(uint16_t x) +{ + return ( + ((x & 0xff) << 8) | + ((x & 0xff00) >> 8) + ); +} + +inline static uint32_t crypto_htonl(uint32_t x) +{ + return ( + ((x & 0xff) << 24) | + ((x & 0xff00) << 8) | + ((x & 0xff0000UL) >> 8) | + ((x & 0xff000000UL) >> 24) + ); +} + +inline static uint32_t crypto_ntohl(uint32_t x) +{ + return ( + ((x & 0xff) << 24) | + ((x & 0xff00) << 8) | + ((x & 0xff0000UL) >> 8) | + ((x & 0xff000000UL) >> 24) + ); +} + +//#endif + +#define GET_UINT32(n,b,i) \ +{ \ + (n) = ((uint32_t) (b)[(i) ] << 24) \ + | ((uint32_t) (b)[(i) + 1] << 16) \ + | ((uint32_t) (b)[(i) + 2] << 8) \ + | ((uint32_t) (b)[(i) + 3] ); \ +} + +#define PUT_UINT32(n,b,i) \ +{ \ + (b)[(i) ] = (byte) ((n) >> 24); \ + (b)[(i) + 1] = (byte) ((n) >> 16); \ + (b)[(i) + 2] = (byte) ((n) >> 8); \ + (b)[(i) + 3] = (byte) ((n) ); \ +} + +static const byte sha256_padding[64] = +{ + 0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 +}; + +/** + * Initialize the SHA256 hash + */ +SHA256::SHA256() +{ + total[0] = 0; + total[1] = 0; + state[0] = 0x6A09E667; + state[1] = 0xBB67AE85; + state[2] = 0x3C6EF372; + state[3] = 0xA54FF53A; + state[4] = 0x510E527F; + state[5] = 0x9B05688C; + state[6] = 0x1F83D9AB; + state[7] = 0x5BE0CD19; +} + +void SHA256::SHA256_Process(const byte digest[64]) +{ + uint32_t temp1, temp2, W[64]; + uint32_t A, B, C, D, E, F, G, H; + + GET_UINT32(W[0], digest, 0); + GET_UINT32(W[1], digest, 4); + GET_UINT32(W[2], digest, 8); + GET_UINT32(W[3], digest, 12); + GET_UINT32(W[4], digest, 16); + GET_UINT32(W[5], digest, 20); + GET_UINT32(W[6], digest, 24); + GET_UINT32(W[7], digest, 28); + GET_UINT32(W[8], digest, 32); + GET_UINT32(W[9], digest, 36); + GET_UINT32(W[10], digest, 40); + GET_UINT32(W[11], digest, 44); + GET_UINT32(W[12], digest, 48); + GET_UINT32(W[13], digest, 52); + GET_UINT32(W[14], digest, 56); + GET_UINT32(W[15], digest, 60); + +#define SHR(x,n) ((x & 0xFFFFFFFF) >> n) +#define ROTR(x,n) (SHR(x,n) | (x << (32 - n))) + +#define S0(x) (ROTR(x, 7) ^ ROTR(x,18) ^ SHR(x, 3)) +#define S1(x) (ROTR(x,17) ^ ROTR(x,19) ^ SHR(x,10)) + +#define S2(x) (ROTR(x, 2) ^ ROTR(x,13) ^ ROTR(x,22)) +#define S3(x) (ROTR(x, 6) ^ ROTR(x,11) ^ ROTR(x,25)) + +#define F0(x,y,z) ((x & y) | (z & (x | y))) +#define F1(x,y,z) (z ^ (x & (y ^ z))) + +#define R(t) \ +( \ + W[t] = S1(W[t - 2]) + W[t - 7] + \ + S0(W[t - 15]) + W[t - 16] \ +) + +#define P(a,b,c,d,e,f,g,h,x,K) \ +{ \ + temp1 = h + S3(e) + F1(e,f,g) + K + x; \ + temp2 = S2(a) + F0(a,b,c); \ + d += temp1; h = temp1 + temp2; \ +} + + A = state[0]; + B = state[1]; + C = state[2]; + D = state[3]; + E = state[4]; + F = state[5]; + G = state[6]; + H = state[7]; + + P(A, B, C, D, E, F, G, H, W[ 0], 0x428A2F98); + P(H, A, B, C, D, E, F, G, W[ 1], 0x71374491); + P(G, H, A, B, C, D, E, F, W[ 2], 0xB5C0FBCF); + P(F, G, H, A, B, C, D, E, W[ 3], 0xE9B5DBA5); + P(E, F, G, H, A, B, C, D, W[ 4], 0x3956C25B); + P(D, E, F, G, H, A, B, C, W[ 5], 0x59F111F1); + P(C, D, E, F, G, H, A, B, W[ 6], 0x923F82A4); + P(B, C, D, E, F, G, H, A, W[ 7], 0xAB1C5ED5); + P(A, B, C, D, E, F, G, H, W[ 8], 0xD807AA98); + P(H, A, B, C, D, E, F, G, W[ 9], 0x12835B01); + P(G, H, A, B, C, D, E, F, W[10], 0x243185BE); + P(F, G, H, A, B, C, D, E, W[11], 0x550C7DC3); + P(E, F, G, H, A, B, C, D, W[12], 0x72BE5D74); + P(D, E, F, G, H, A, B, C, W[13], 0x80DEB1FE); + P(C, D, E, F, G, H, A, B, W[14], 0x9BDC06A7); + P(B, C, D, E, F, G, H, A, W[15], 0xC19BF174); + P(A, B, C, D, E, F, G, H, R(16), 0xE49B69C1); + P(H, A, B, C, D, E, F, G, R(17), 0xEFBE4786); + P(G, H, A, B, C, D, E, F, R(18), 0x0FC19DC6); + P(F, G, H, A, B, C, D, E, R(19), 0x240CA1CC); + P(E, F, G, H, A, B, C, D, R(20), 0x2DE92C6F); + P(D, E, F, G, H, A, B, C, R(21), 0x4A7484AA); + P(C, D, E, F, G, H, A, B, R(22), 0x5CB0A9DC); + P(B, C, D, E, F, G, H, A, R(23), 0x76F988DA); + P(A, B, C, D, E, F, G, H, R(24), 0x983E5152); + P(H, A, B, C, D, E, F, G, R(25), 0xA831C66D); + P(G, H, A, B, C, D, E, F, R(26), 0xB00327C8); + P(F, G, H, A, B, C, D, E, R(27), 0xBF597FC7); + P(E, F, G, H, A, B, C, D, R(28), 0xC6E00BF3); + P(D, E, F, G, H, A, B, C, R(29), 0xD5A79147); + P(C, D, E, F, G, H, A, B, R(30), 0x06CA6351); + P(B, C, D, E, F, G, H, A, R(31), 0x14292967); + P(A, B, C, D, E, F, G, H, R(32), 0x27B70A85); + P(H, A, B, C, D, E, F, G, R(33), 0x2E1B2138); + P(G, H, A, B, C, D, E, F, R(34), 0x4D2C6DFC); + P(F, G, H, A, B, C, D, E, R(35), 0x53380D13); + P(E, F, G, H, A, B, C, D, R(36), 0x650A7354); + P(D, E, F, G, H, A, B, C, R(37), 0x766A0ABB); + P(C, D, E, F, G, H, A, B, R(38), 0x81C2C92E); + P(B, C, D, E, F, G, H, A, R(39), 0x92722C85); + P(A, B, C, D, E, F, G, H, R(40), 0xA2BFE8A1); + P(H, A, B, C, D, E, F, G, R(41), 0xA81A664B); + P(G, H, A, B, C, D, E, F, R(42), 0xC24B8B70); + P(F, G, H, A, B, C, D, E, R(43), 0xC76C51A3); + P(E, F, G, H, A, B, C, D, R(44), 0xD192E819); + P(D, E, F, G, H, A, B, C, R(45), 0xD6990624); + P(C, D, E, F, G, H, A, B, R(46), 0xF40E3585); + P(B, C, D, E, F, G, H, A, R(47), 0x106AA070); + P(A, B, C, D, E, F, G, H, R(48), 0x19A4C116); + P(H, A, B, C, D, E, F, G, R(49), 0x1E376C08); + P(G, H, A, B, C, D, E, F, R(50), 0x2748774C); + P(F, G, H, A, B, C, D, E, R(51), 0x34B0BCB5); + P(E, F, G, H, A, B, C, D, R(52), 0x391C0CB3); + P(D, E, F, G, H, A, B, C, R(53), 0x4ED8AA4A); + P(C, D, E, F, G, H, A, B, R(54), 0x5B9CCA4F); + P(B, C, D, E, F, G, H, A, R(55), 0x682E6FF3); + P(A, B, C, D, E, F, G, H, R(56), 0x748F82EE); + P(H, A, B, C, D, E, F, G, R(57), 0x78A5636F); + P(G, H, A, B, C, D, E, F, R(58), 0x84C87814); + P(F, G, H, A, B, C, D, E, R(59), 0x8CC70208); + P(E, F, G, H, A, B, C, D, R(60), 0x90BEFFFA); + P(D, E, F, G, H, A, B, C, R(61), 0xA4506CEB); + P(C, D, E, F, G, H, A, B, R(62), 0xBEF9A3F7); + P(B, C, D, E, F, G, H, A, R(63), 0xC67178F2); + + state[0] += A; + state[1] += B; + state[2] += C; + state[3] += D; + state[4] += E; + state[5] += F; + state[6] += G; + state[7] += H; +#if defined ESP8266 + ESP.wdtFeed(); +#endif +} + +/** + * Accepts an array of octets as the next portion of the message. + */ +void SHA256::doUpdate(const byte * msg, uint32_t len) +{ + uint32_t left = total[0] & 0x3F; + uint32_t fill = 64 - left; + + total[0] += len; + total[0] &= 0xFFFFFFFF; + + if (total[0] < len) + total[1]++; + + if (left && len >= fill) + { + memcpy((void *) (buffer + left), (void *) msg, fill); + SHA256::SHA256_Process(buffer); + len -= fill; + msg += fill; + left = 0; + } + + while (len >= 64) + { + SHA256::SHA256_Process(msg); + len -= 64; + msg += 64; + } + + if (len) + { + memcpy((void *) (buffer + left), (void *) msg, len); + } +} + +/** + * Return the 256-bit message digest into the user's array + */ +void SHA256::doFinal(byte *digest) +{ + uint32_t last, padn; + uint32_t high, low; + byte msglen[8]; + + high = (total[0] >> 29) + | (total[1] << 3); + low = (total[0] << 3); + + PUT_UINT32(high, msglen, 0); + PUT_UINT32(low, msglen, 4); + + last = total[0] & 0x3F; + padn = (last < 56) ? (56 - last) : (120 - last); + + SHA256::doUpdate(sha256_padding, padn); + SHA256::doUpdate(msglen, 8); + + PUT_UINT32(state[0], digest, 0); + PUT_UINT32(state[1], digest, 4); + PUT_UINT32(state[2], digest, 8); + PUT_UINT32(state[3], digest, 12); + PUT_UINT32(state[4], digest, 16); + PUT_UINT32(state[5], digest, 20); + PUT_UINT32(state[6], digest, 24); + PUT_UINT32(state[7], digest, 28); +#if defined ESP8266 + ESP.wdtFeed(); +#endif +} + +bool SHA256::matches(const byte *expected) +{ + byte theDigest[SHA256_SIZE]; + doFinal(theDigest); + for (byte i = 0; i < SHA256_SIZE; i++) + { + if (expected[i] != theDigest[i]) + return false; + } +#if defined ESP8266 + ESP.wdtFeed(); +#endif + return true; +} + +/******************************************************************************/ + +#define rot1(x) (((x) << 24) | ((x) >> 8)) +#define rot2(x) (((x) << 16) | ((x) >> 16)) +#define rot3(x) (((x) << 8) | ((x) >> 24)) + +/* + * This cute trick does 4 'mul by two' at once. Stolen from + * Dr B. R. Gladman but I'm sure the u-(u>>7) is + * a standard graphics trick + * The key to this is that we need to xor with 0x1b if the top bit is set. + * a 1xxx xxxx 0xxx 0xxx First we mask the 7bit, + * b 1000 0000 0000 0000 then we shift right by 7 putting the 7bit in 0bit, + * c 0000 0001 0000 0000 we then subtract (c) from (b) + * d 0111 1111 0000 0000 and now we and with our mask + * e 0001 1011 0000 0000 + */ +#define mt 0x80808080 +#define ml 0x7f7f7f7f +#define mh 0xfefefefe +#define mm 0x1b1b1b1b +#define mul2(x,t) ((t)=((x)&mt), \ + ((((x)+(x))&mh)^(((t)-((t)>>7))&mm))) + +#define inv_mix_col(x,f2,f4,f8,f9) (\ + (f2)=mul2(x,f2), \ + (f4)=mul2(f2,f4), \ + (f8)=mul2(f4,f8), \ + (f9)=(x)^(f8), \ + (f8)=((f2)^(f4)^(f8)), \ + (f2)^=(f9), \ + (f4)^=(f9), \ + (f8)^=rot3(f2), \ + (f8)^=rot2(f4), \ + (f8)^rot1(f9)) + +/* + * AES S-box + */ +static const uint8_t aes_sbox[256] = +{ + 0x63,0x7C,0x77,0x7B,0xF2,0x6B,0x6F,0xC5, + 0x30,0x01,0x67,0x2B,0xFE,0xD7,0xAB,0x76, + 0xCA,0x82,0xC9,0x7D,0xFA,0x59,0x47,0xF0, + 0xAD,0xD4,0xA2,0xAF,0x9C,0xA4,0x72,0xC0, + 0xB7,0xFD,0x93,0x26,0x36,0x3F,0xF7,0xCC, + 0x34,0xA5,0xE5,0xF1,0x71,0xD8,0x31,0x15, + 0x04,0xC7,0x23,0xC3,0x18,0x96,0x05,0x9A, + 0x07,0x12,0x80,0xE2,0xEB,0x27,0xB2,0x75, + 0x09,0x83,0x2C,0x1A,0x1B,0x6E,0x5A,0xA0, + 0x52,0x3B,0xD6,0xB3,0x29,0xE3,0x2F,0x84, + 0x53,0xD1,0x00,0xED,0x20,0xFC,0xB1,0x5B, + 0x6A,0xCB,0xBE,0x39,0x4A,0x4C,0x58,0xCF, + 0xD0,0xEF,0xAA,0xFB,0x43,0x4D,0x33,0x85, + 0x45,0xF9,0x02,0x7F,0x50,0x3C,0x9F,0xA8, + 0x51,0xA3,0x40,0x8F,0x92,0x9D,0x38,0xF5, + 0xBC,0xB6,0xDA,0x21,0x10,0xFF,0xF3,0xD2, + 0xCD,0x0C,0x13,0xEC,0x5F,0x97,0x44,0x17, + 0xC4,0xA7,0x7E,0x3D,0x64,0x5D,0x19,0x73, + 0x60,0x81,0x4F,0xDC,0x22,0x2A,0x90,0x88, + 0x46,0xEE,0xB8,0x14,0xDE,0x5E,0x0B,0xDB, + 0xE0,0x32,0x3A,0x0A,0x49,0x06,0x24,0x5C, + 0xC2,0xD3,0xAC,0x62,0x91,0x95,0xE4,0x79, + 0xE7,0xC8,0x37,0x6D,0x8D,0xD5,0x4E,0xA9, + 0x6C,0x56,0xF4,0xEA,0x65,0x7A,0xAE,0x08, + 0xBA,0x78,0x25,0x2E,0x1C,0xA6,0xB4,0xC6, + 0xE8,0xDD,0x74,0x1F,0x4B,0xBD,0x8B,0x8A, + 0x70,0x3E,0xB5,0x66,0x48,0x03,0xF6,0x0E, + 0x61,0x35,0x57,0xB9,0x86,0xC1,0x1D,0x9E, + 0xE1,0xF8,0x98,0x11,0x69,0xD9,0x8E,0x94, + 0x9B,0x1E,0x87,0xE9,0xCE,0x55,0x28,0xDF, + 0x8C,0xA1,0x89,0x0D,0xBF,0xE6,0x42,0x68, + 0x41,0x99,0x2D,0x0F,0xB0,0x54,0xBB,0x16, +}; + +/* + * AES is-box + */ +static const uint8_t aes_isbox[256] = +{ + 0x52,0x09,0x6a,0xd5,0x30,0x36,0xa5,0x38, + 0xbf,0x40,0xa3,0x9e,0x81,0xf3,0xd7,0xfb, + 0x7c,0xe3,0x39,0x82,0x9b,0x2f,0xff,0x87, + 0x34,0x8e,0x43,0x44,0xc4,0xde,0xe9,0xcb, + 0x54,0x7b,0x94,0x32,0xa6,0xc2,0x23,0x3d, + 0xee,0x4c,0x95,0x0b,0x42,0xfa,0xc3,0x4e, + 0x08,0x2e,0xa1,0x66,0x28,0xd9,0x24,0xb2, + 0x76,0x5b,0xa2,0x49,0x6d,0x8b,0xd1,0x25, + 0x72,0xf8,0xf6,0x64,0x86,0x68,0x98,0x16, + 0xd4,0xa4,0x5c,0xcc,0x5d,0x65,0xb6,0x92, + 0x6c,0x70,0x48,0x50,0xfd,0xed,0xb9,0xda, + 0x5e,0x15,0x46,0x57,0xa7,0x8d,0x9d,0x84, + 0x90,0xd8,0xab,0x00,0x8c,0xbc,0xd3,0x0a, + 0xf7,0xe4,0x58,0x05,0xb8,0xb3,0x45,0x06, + 0xd0,0x2c,0x1e,0x8f,0xca,0x3f,0x0f,0x02, + 0xc1,0xaf,0xbd,0x03,0x01,0x13,0x8a,0x6b, + 0x3a,0x91,0x11,0x41,0x4f,0x67,0xdc,0xea, + 0x97,0xf2,0xcf,0xce,0xf0,0xb4,0xe6,0x73, + 0x96,0xac,0x74,0x22,0xe7,0xad,0x35,0x85, + 0xe2,0xf9,0x37,0xe8,0x1c,0x75,0xdf,0x6e, + 0x47,0xf1,0x1a,0x71,0x1d,0x29,0xc5,0x89, + 0x6f,0xb7,0x62,0x0e,0xaa,0x18,0xbe,0x1b, + 0xfc,0x56,0x3e,0x4b,0xc6,0xd2,0x79,0x20, + 0x9a,0xdb,0xc0,0xfe,0x78,0xcd,0x5a,0xf4, + 0x1f,0xdd,0xa8,0x33,0x88,0x07,0xc7,0x31, + 0xb1,0x12,0x10,0x59,0x27,0x80,0xec,0x5f, + 0x60,0x51,0x7f,0xa9,0x19,0xb5,0x4a,0x0d, + 0x2d,0xe5,0x7a,0x9f,0x93,0xc9,0x9c,0xef, + 0xa0,0xe0,0x3b,0x4d,0xae,0x2a,0xf5,0xb0, + 0xc8,0xeb,0xbb,0x3c,0x83,0x53,0x99,0x61, + 0x17,0x2b,0x04,0x7e,0xba,0x77,0xd6,0x26, + 0xe1,0x69,0x14,0x63,0x55,0x21,0x0c,0x7d +}; + +static const unsigned char Rcon[30]= +{ + 0x01,0x02,0x04,0x08,0x10,0x20,0x40,0x80, + 0x1b,0x36,0x6c,0xd8,0xab,0x4d,0x9a,0x2f, + 0x5e,0xbc,0x63,0xc6,0x97,0x35,0x6a,0xd4, + 0xb3,0x7d,0xfa,0xef,0xc5,0x91, +}; + +/* Perform doubling in Galois Field GF(2^8) using the irreducible polynomial + x^8+x^4+x^3+x+1 */ +static unsigned char AES_xtime(uint32_t x) +{ + return (x&0x80) ? (x<<1)^0x1b : x<<1; +} + + +/** + * Encrypt a single block (16 bytes) of data + */ +void AES::encrypt(uint32_t *data) +{ + /* To make this code smaller, generate the sbox entries on the fly. + * This will have a really heavy effect upon performance. + */ + uint32_t tmp[4]; + uint32_t tmp1, old_a0, a0, a1, a2, a3, row; + int curr_rnd; + int rounds = _rounds; + const uint32_t *k = _ks; + + /* Pre-round key addition */ + for (row = 0; row < 4; row++) + data[row] ^= *(k++); + + /* Encrypt one block. */ + for (curr_rnd = 0; curr_rnd < rounds; curr_rnd++) + { + /* Perform ByteSub and ShiftRow operations together */ + for (row = 0; row < 4; row++) + { + a0 = (uint32_t)aes_sbox[(data[row%4]>>24)&0xFF]; + a1 = (uint32_t)aes_sbox[(data[(row+1)%4]>>16)&0xFF]; + a2 = (uint32_t)aes_sbox[(data[(row+2)%4]>>8)&0xFF]; + a3 = (uint32_t)aes_sbox[(data[(row+3)%4])&0xFF]; + + /* Perform MixColumn iff not last round */ + if (curr_rnd < (rounds - 1)) + { + tmp1 = a0 ^ a1 ^ a2 ^ a3; + old_a0 = a0; + a0 ^= tmp1 ^ AES_xtime(a0 ^ a1); + a1 ^= tmp1 ^ AES_xtime(a1 ^ a2); + a2 ^= tmp1 ^ AES_xtime(a2 ^ a3); + a3 ^= tmp1 ^ AES_xtime(a3 ^ old_a0); + } + + tmp[row] = ((a0 << 24) | (a1 << 16) | (a2 << 8) | a3); + } + + /* KeyAddition - note that it is vital that this loop is separate from + the MixColumn operation, which must be atomic...*/ + for (row = 0; row < 4; row++) + data[row] = tmp[row] ^ *(k++); + } +#if defined ESP8266 + ESP.wdtFeed(); +#endif +} + +/** + * Decrypt a single block (16 bytes) of data + */ +void AES::decrypt(uint32_t *data) +{ + uint32_t tmp[4]; + uint32_t xt0,xt1,xt2,xt3,xt4,xt5,xt6; + uint32_t a0, a1, a2, a3, row; + int curr_rnd; + int rounds = _rounds; + const uint32_t *k = _ks + ((rounds+1)*4); + + /* pre-round key addition */ + for (row=4; row > 0;row--) + data[row-1] ^= *(--k); + + /* Decrypt one block */ + for (curr_rnd = 0; curr_rnd < rounds; curr_rnd++) + { + /* Perform ByteSub and ShiftRow operations together */ + for (row = 4; row > 0; row--) + { + a0 = aes_isbox[(data[(row+3)%4]>>24)&0xFF]; + a1 = aes_isbox[(data[(row+2)%4]>>16)&0xFF]; + a2 = aes_isbox[(data[(row+1)%4]>>8)&0xFF]; + a3 = aes_isbox[(data[row%4])&0xFF]; + + /* Perform MixColumn iff not last round */ + if (curr_rnd<(rounds-1)) + { + /* The MDS cofefficients (0x09, 0x0B, 0x0D, 0x0E) + are quite large compared to encryption; this + operation slows decryption down noticeably. */ + xt0 = AES_xtime(a0^a1); + xt1 = AES_xtime(a1^a2); + xt2 = AES_xtime(a2^a3); + xt3 = AES_xtime(a3^a0); + xt4 = AES_xtime(xt0^xt1); + xt5 = AES_xtime(xt1^xt2); + xt6 = AES_xtime(xt4^xt5); + + xt0 ^= a1^a2^a3^xt4^xt6; + xt1 ^= a0^a2^a3^xt5^xt6; + xt2 ^= a0^a1^a3^xt4^xt6; + xt3 ^= a0^a1^a2^xt5^xt6; + tmp[row-1] = ((xt0<<24)|(xt1<<16)|(xt2<<8)|xt3); + } + else + tmp[row-1] = ((a0<<24)|(a1<<16)|(a2<<8)|a3); + } + + for (row = 4; row > 0; row--) + data[row-1] = tmp[row-1] ^ *(--k); + } +#if defined ESP8266 + ESP.wdtFeed(); +#endif +} + +AES::AES(const uint8_t *key, const uint8_t *iv, AES_MODE mode, CIPHER_MODE cipherMode) +{ + _cipherMode = cipherMode; + + int i, ii; + uint32_t *W, tmp, tmp2; + const unsigned char *ip; + int words; + + _arr_pad[0] = 0x01; + _arr_pad[1] = 0x02; + _arr_pad[2] = 0x03; + _arr_pad[3] = 0x04; + _arr_pad[4] = 0x05; + _arr_pad[5] = 0x06; + _arr_pad[6] = 0x07; + _arr_pad[7] = 0x08; + _arr_pad[8] = 0x09; + _arr_pad[9] = 0x0a; + _arr_pad[10] = 0x0b; + _arr_pad[11] = 0x0c; + _arr_pad[12] = 0x0d; + _arr_pad[13] = 0x0e; + _arr_pad[14] = 0x0f; + + switch (mode) + { + case AES_MODE_128: + i = 10; + words = 4; + break; + + case AES_MODE_256: + i = 14; + words = 8; + break; + + default: /* fail silently */ + return; + } + + _rounds = i; + _key_size = words; + W = _ks; + for (i = 0; i < words; i+=2) + { + W[i+0]= ((uint32_t)key[ 0]<<24)| + ((uint32_t)key[ 1]<<16)| + ((uint32_t)key[ 2]<< 8)| + ((uint32_t)key[ 3] ); + W[i+1]= ((uint32_t)key[ 4]<<24)| + ((uint32_t)key[ 5]<<16)| + ((uint32_t)key[ 6]<< 8)| + ((uint32_t)key[ 7] ); + key += 8; + } + + ip = Rcon; + ii = 4 * (_rounds+1); + for (i = words; i> 8)&0xff]<<16; + tmp2|=(uint32_t)aes_sbox[(tmp>>16)&0xff]<<24; + tmp2|=(uint32_t)aes_sbox[(tmp>>24) ]; + tmp=tmp2^(((unsigned int)*ip)<<24); + ip++; + } + + if ((words == 8) && ((i % words) == 4)) + { + tmp2 =(uint32_t)aes_sbox[(tmp )&0xff] ; + tmp2|=(uint32_t)aes_sbox[(tmp>> 8)&0xff]<< 8; + tmp2|=(uint32_t)aes_sbox[(tmp>>16)&0xff]<<16; + tmp2|=(uint32_t)aes_sbox[(tmp>>24) ]<<24; + tmp=tmp2; + } + + W[i]=W[i-words]^tmp; + } + + /* copy the iv across */ + memcpy(_iv, iv, 16); + + /* Do we need to convert the key */ + if (_cipherMode == CIPHER_DECRYPT) + { + convertKey(); + } +#if defined ESP8266 + ESP.wdtFeed(); +#endif +} + +int AES::getSize() +{ + return _size; +} + +void AES::setSize(int size) +{ + _size = size; +} + +int AES::calcSizeAndPad(int in_size) +{ + in_size++; // +1 for null terminater on input string + int buf = round(in_size / AES_BLOCKSIZE) * AES_BLOCKSIZE; + _size = (buf <= in_size) ? buf + AES_BLOCKSIZE : buf; + _pad_size = _size - in_size; + return _size; +} + +void AES::padPlaintext(const uint8_t* in, uint8_t* out) +{ + memcpy(out, in, _size); + for (int i = _size - _pad_size; i < _size; i++) + { + out[i] = _arr_pad[_pad_size - 1]; + } +} + +bool AES::checkPad(uint8_t* in, int lsize) +{ + if (in[lsize-1] <= 0x0f) + { + int lpad = (int)in[lsize-1]; + for (int i = lsize - 1; i >= lsize-lpad; i--) + { + if (_arr_pad[lpad - 1] != in[i]) + { + return false; + } + } + } + else + { + return true; + } + return true; +} + +void AES::processNoPad(const uint8_t *in, uint8_t *out, int length) +{ + if (_cipherMode == CIPHER_ENCRYPT) + { + encryptCBC(in, out, length); + } + else + { + decryptCBC(in, out, length); + } +} + +void AES::process(const uint8_t *in, uint8_t *out, int length) +{ + if (_cipherMode == CIPHER_ENCRYPT) + { + calcSizeAndPad(length); + uint8_t in_pad[getSize()]; + padPlaintext(in, in_pad); + encryptCBC(in_pad, out, getSize()); + } + else + { + decryptCBC(in, out, length); + } +} + +void AES::encryptCBC(const uint8_t *in, uint8_t *out, int length) +{ + int i; + uint32_t tin[4], tout[4], iv[4]; + + memcpy(iv, _iv, AES_IV_SIZE); + for (i = 0; i < 4; i++) + tout[i] = crypto_ntohl(iv[i]); + + for (length -= AES_BLOCKSIZE; length >= 0; length -= AES_BLOCKSIZE) + { + uint32_t msg_32[4]; + uint32_t out_32[4]; + memcpy(msg_32, in, AES_BLOCKSIZE); + in += AES_BLOCKSIZE; + + for (i = 0; i < 4; i++) + tin[i] = crypto_ntohl(msg_32[i])^tout[i]; + + AES::encrypt(tin); + + for (i = 0; i < 4; i++) + { + tout[i] = tin[i]; + out_32[i] = crypto_htonl(tout[i]); + } + + memcpy(out, out_32, AES_BLOCKSIZE); + out += AES_BLOCKSIZE; + } + + for (i = 0; i < 4; i++) + iv[i] = crypto_htonl(tout[i]); + memcpy(_iv, iv, AES_IV_SIZE); +#if defined ESP8266 + ESP.wdtFeed(); +#endif +} + +void AES::decryptCBC(const uint8_t *in, uint8_t *out, int length) +{ + int i; + uint32_t tin[4], bufxor[4], tout[4], data[4], iv[4]; + + memcpy(iv, _iv, AES_IV_SIZE); + for (i = 0; i < 4; i++) + bufxor[i] = crypto_ntohl(iv[i]); + + for (length -= 16; length >= 0; length -= 16) + { + uint32_t msg_32[4]; + uint32_t out_32[4]; + memcpy(msg_32, in, AES_BLOCKSIZE); + in += AES_BLOCKSIZE; + + for (i = 0; i < 4; i++) + { + tin[i] = crypto_ntohl(msg_32[i]); + data[i] = tin[i]; + } + + AES::decrypt(data); + + for (i = 0; i < 4; i++) + { + tout[i] = data[i] ^ bufxor[i]; + bufxor[i] = tin[i]; + out_32[i] = crypto_htonl(tout[i]); + } + + memcpy(out, out_32, AES_BLOCKSIZE); + out += AES_BLOCKSIZE; + } + + for (i = 0; i < 4; i++) + iv[i] = crypto_htonl(bufxor[i]); + memcpy(_iv, iv, AES_IV_SIZE); +#if defined ESP8266 + ESP.wdtFeed(); +#endif +} + +void AES::convertKey() +{ + int i; + uint32_t *k,w,t1,t2,t3,t4; + + k = _ks; + k += 4; + + for (i= _rounds*4; i > 4; i--) + { + w= *k; + w = inv_mix_col(w,t1,t2,t3,t4); + *k++ =w; + } +} + +#if defined ESP8266 || defined ESP32 +/** + * ESP8266 and ESP32 specific hardware true random number generator. + * + * Acording to the ESP32 documentation, you should not call the tRNG + * faster than 5MHz + * + */ + +void RNG::fill(uint8_t *dst, unsigned int length) +{ + // ESP8266 and ESP32 only + for (uint32_t i = 0; i < length; i++) + { + dst[i] = get(); + } +#if defined ESP8266 + ESP.wdtFeed(); +#endif +} + +byte RNG::get() +{ +#if defined ESP32 + // ESP32 only + uint32_t* randReg = (uint32_t*) 0x3FF75144; + return (byte) *randReg; +#elif defined ESP8266 + // ESP8266 only + uint32_t* randReg = (uint32_t*) 0x3FF20E44L; + return (byte) *randReg; +#else + // NOT SUPPORTED + return 0; +#endif +} + +uint32_t RNG::getLong() +{ +#if defined ESP32 + // ESP32 only + uint32_t* randReg = (uint32_t*) 0x3FF75144; + return (byte) *randReg; +#elif defined ESP8266 + // ESP8266 only + uint32_t* randReg = (uint32_t*) 0x3FF20E44L; + return *randReg; +#else + // NOT SUPPORTED + return 0; +#endif +} +#endif + + +/** + * SHA256 HMAC + */ + +SHA256HMAC::SHA256HMAC(const byte *key, unsigned int keyLen) +{ + // sort out the key + byte theKey[SHA256HMAC_BLOCKSIZE]; + memset(theKey, 0, SHA256HMAC_BLOCKSIZE); + if (keyLen > SHA256HMAC_BLOCKSIZE) + { + // take a hash of the key + SHA256 keyHahser; + keyHahser.doUpdate(key, keyLen); + keyHahser.doFinal(theKey); + } + else + { + // we already set the buffer to 0s, so just copy keyLen + // bytes from key + memcpy(theKey, key, keyLen); + } + // explicitly zero pads + memset(_innerKey, 0, SHA256HMAC_BLOCKSIZE); + memset(_outerKey, 0, SHA256HMAC_BLOCKSIZE); + // compute the keys + blockXor(theKey, _innerKey, HMAC_IPAD, SHA256HMAC_BLOCKSIZE); + blockXor(theKey, _outerKey, HMAC_OPAD, SHA256HMAC_BLOCKSIZE); + // start the intermediate hash + _hash.doUpdate(_innerKey, SHA256HMAC_BLOCKSIZE); +} + +void SHA256HMAC::doUpdate(const byte *msg, unsigned int len) +{ + _hash.doUpdate(msg, len); +} + +void SHA256HMAC::doFinal(byte *digest) +{ + // compute the intermediate hash + byte interHash[SHA256_SIZE]; + _hash.doFinal(interHash); + // compute the final hash + SHA256 finalHash; + finalHash.doUpdate(_outerKey, SHA256HMAC_BLOCKSIZE); + finalHash.doUpdate(interHash, SHA256_SIZE); + finalHash.doFinal(digest); +} + +bool SHA256HMAC::matches(const byte *expected) +{ + byte theDigest[SHA256_SIZE]; + doFinal(theDigest); + for (byte i = 0; i < SHA256_SIZE; i++) + { + if (expected[i] != theDigest[i]) + return false; + } + return true; +} + +void SHA256HMAC::blockXor(const byte *in, byte *out, byte val, byte len) +{ + for (byte i = 0; i < len; i++) + { + out[i] = in[i] ^ val; + } +} diff --git a/libraries/ESP8266WiFiMesh/src/Crypto.h b/libraries/ESP8266WiFiMesh/src/Crypto.h new file mode 100644 index 0000000000..9df82251bb --- /dev/null +++ b/libraries/ESP8266WiFiMesh/src/Crypto.h @@ -0,0 +1,254 @@ +/** + * An extremely minimal crypto library for Arduino devices. + * + * The SHA256 and AES implementations are derived from axTLS + * (http://axtls.sourceforge.net/), Copyright (c) 2008, Cameron Rich. + * + * Ported and refactored by Chris Ellis 2016. + * pkcs7 padding routines added by Mike Killewald Nov 26, 2017 (adopted from https://github.com/spaniakos/AES). + * + License + ======= + Balsa SCGI + Copyright (c) 2012, Chris Ellis + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef CRYPTO_h +#define CRYPTO_h + +#include + +#if defined ESP8266 +#include +#endif + +#define SHA256_SIZE 32 +#define SHA256HMAC_SIZE 32 +#define SHA256HMAC_BLOCKSIZE 64 +#define AES_MAXROUNDS 14 +#define AES_BLOCKSIZE 16 +#define AES_IV_SIZE 16 +#define AES_IV_LENGTH 16 +#define AES_128_KEY_LENGTH 16 +#define AES_256_KEY_LENGTH 16 + +/** + * Compute a SHA256 hash + */ +class SHA256 +{ + public: + SHA256(); + /** + * Update the hash with new data + */ + void doUpdate(const byte *msg, uint32_t len); + void doUpdate(const char *msg, unsigned int len) { doUpdate((byte*) msg, len); } + void doUpdate(const char *msg) { doUpdate((byte*) msg, strlen(msg)); } + /** + * Compute the final hash and store it in [digest], digest must be + * at least 32 bytes + */ + void doFinal(byte *digest); + /** + * Compute the final hash and check it matches this given expected hash + */ + bool matches(const byte *expected); + private: + void SHA256_Process(const byte digest[64]); + uint32_t total[2]; + uint32_t state[8]; + uint8_t buffer[64]; +}; + +#define HMAC_OPAD 0x5C +#define HMAC_IPAD 0x36 + +/** + * Compute a HMAC using SHA256 + */ +class SHA256HMAC +{ + public: + /** + * Compute a SHA256 HMAC with the given [key] key of [length] bytes + * for authenticity + */ + SHA256HMAC(const byte *key, unsigned int keyLen); + /** + * Update the hash with new data + */ + void doUpdate(const byte *msg, unsigned int len); + void doUpdate(const char *msg, unsigned int len) { doUpdate((byte*) msg, len); } + void doUpdate(const char *msg) { doUpdate((byte*) msg, strlen(msg)); } + /** + * Compute the final hash and store it in [digest], digest must be + * at least 32 bytes + */ + void doFinal(byte *digest); + /** + * Compute the final hash and check it matches this given expected hash + */ + bool matches(const byte *expected); + private: + void blockXor(const byte *in, byte *out, byte val, byte len); + SHA256 _hash; + byte _innerKey[SHA256HMAC_BLOCKSIZE]; + byte _outerKey[SHA256HMAC_BLOCKSIZE]; +}; + +/** + * AES 128 and 256, based on code from axTLS + */ +class AES +{ + public: + typedef enum + { + AES_MODE_128, + AES_MODE_256 + } AES_MODE; + typedef enum + { + CIPHER_ENCRYPT = 0x01, + CIPHER_DECRYPT = 0x02 + } CIPHER_MODE; + + /** + * Create this cipher instance in either encrypt or decrypt mode + * + * Use the given [key] which must be 16 bytes long for AES 128 and + * 32 bytes for AES 256 + * + * Use the given [iv] initialistion vection which must be 16 bytes long + * + * Use the either AES 128 or AES 256 as specified by [mode] + * + * Either encrypt or decrypt as specified by [cipherMode] + */ + AES(const uint8_t *key, const uint8_t *iv, AES_MODE mode, CIPHER_MODE cipherMode); + + /** + * Either encrypt or decrypt [in] and store into [out] for [length] bytes, applying no padding + * + * Note: the length must be a multiple of 16 bytes + */ + void processNoPad(const uint8_t *in, uint8_t *out, int length); + + /** + * Either encrypt or decrypt [in] and store into [out] for [length] bytes, applying padding as needed + * + * Note: the length must be a multiple of 16 bytes + */ + void process(const uint8_t *in, uint8_t *out, int length); + + /** Getter method for size + * + * This function returns the size + * @return an integer, that is the size of the of the padded plaintext, + * thus, the size of the ciphertext. + */ + int getSize(); + + /** Setter method for size + * + * This function sets the size of the plaintext+pad + * + */ + void setSize(int size); + + /** Calculates the size of the plaintext and the padding. + * + * Calculates the size of the plaintext with the size of the + * padding needed. Moreover it stores them in their class variables. + * + * @param in_size the size of the byte array ex sizeof(plaintext) + * @return an int the size of the plaintext plus the padding + */ + int calcSizeAndPad(int in_size); + + /** Pads the plaintext + * + * This function pads the plaintext and returns an char array with the + * plaintext and the padding in order for the plaintext to be compatible with + * 16bit size blocks required by AES + * + * @param in the string of the plaintext in a byte array + * @param out The string of the out array. + * @return no return, The padded plaintext is stored in the out pointer. + */ + void padPlaintext(const uint8_t* in, uint8_t* out); + + /** Check the if the padding is correct. + * + * This functions checks the padding of the plaintext. + * + * @param in the string of the plaintext in a byte array + * @param size the size of the string + * @return true if correct / false if not + */ + bool checkPad(uint8_t* in, int lsize); + + private: + void encryptCBC(const uint8_t *in, uint8_t *out, int length); + void decryptCBC(const uint8_t *in, uint8_t *out, int length); + void convertKey(); + void encrypt(uint32_t *data); + void decrypt(uint32_t *data); + uint16_t _rounds; + uint16_t _key_size; + uint32_t _ks[(AES_MAXROUNDS+1)*8]; + uint8_t _iv[AES_IV_SIZE]; + int _pad_size; // size of padding to add to plaintext + int _size; // size of plaintext plus padding to be ciphered + uint8_t _arr_pad[15]; + + CIPHER_MODE _cipherMode; +}; + +#if defined ESP8266 || defined ESP32 +/** + * ESP8266 and ESP32 specific true random number generator + */ +class RNG +{ + public: + /** + * Fill the [dst] array with [length] random bytes + */ + static void fill(uint8_t *dst, unsigned int length); + /** + * Get a random byte + */ + static byte get(); + /** + * Get a 32bit random number + */ + static uint32_t getLong(); + private: +}; +#endif + + +#endif diff --git a/libraries/ESP8266WiFiMesh/src/ESP8266WiFiMesh.cpp b/libraries/ESP8266WiFiMesh/src/ESP8266WiFiMesh.cpp index fead562e6a..12a41dd8eb 100644 --- a/libraries/ESP8266WiFiMesh/src/ESP8266WiFiMesh.cpp +++ b/libraries/ESP8266WiFiMesh/src/ESP8266WiFiMesh.cpp @@ -18,6 +18,28 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ + + + + + + +/******************************************************************************************** +* NOTE! +* +* This class is deprecated and will be removed in core version 3.0.0. +* If you are still using this class, please consider migrating to the new API shown in +* the EspnowMeshBackend.h or TcpIpMeshBackend.h source files. +* +* TODO: delete this file. +********************************************************************************************/ + + + + + + + #include #include #include @@ -29,7 +51,6 @@ #define SERVER_IP_ADDR "192.168.4.1" const IPAddress ESP8266WiFiMesh::emptyIP = IPAddress(); -const uint32_t ESP8266WiFiMesh::lwipVersion203Signature[3] {2,0,3}; String ESP8266WiFiMesh::lastSSID = ""; bool ESP8266WiFiMesh::staticIPActivated = false; @@ -51,10 +72,8 @@ ESP8266WiFiMesh::~ESP8266WiFiMesh() ESP8266WiFiMesh::ESP8266WiFiMesh(ESP8266WiFiMesh::requestHandlerType requestHandler, ESP8266WiFiMesh::responseHandlerType responseHandler, ESP8266WiFiMesh::networkFilterType networkFilter, const String &meshPassword, const String &meshName, const String &nodeID, bool verboseMode, uint8 meshWiFiChannel, uint16_t serverPort) - : _server(serverPort), _lwipVersion{0, 0, 0} -{ - storeLwipVersion(); - + : _server(serverPort) +{ updateNetworkNames(meshName, (nodeID != "" ? nodeID : uint64ToString(ESP.getChipId()))); _requestHandler = requestHandler; _responseHandler = responseHandler; @@ -99,15 +118,10 @@ void ESP8266WiFiMesh::begin() if(!ESP8266WiFiMesh::getAPController()) // If there is no active AP controller WiFi.mode(WIFI_STA); // WIFI_AP_STA mode automatically sets up an AP, so we can't use that as default. - #ifdef ENABLE_STATIC_IP_OPTIMIZATION - if(atLeastLwipVersion(lwipVersion203Signature)) - { - verboseModePrint(F("lwIP version is at least 2.0.3. Static ip optimizations enabled.\n")); - } - else - { - verboseModePrint(F("lwIP version is less than 2.0.3. Static ip optimizations DISABLED.\n")); - } + #if LWIP_VERSION_MAJOR >= 2 + verboseModePrint(F("lwIP version is at least 2. Static ip optimizations enabled.\n")); + #else + verboseModePrint(F("lwIP version is less than 2. Static ip optimizations DISABLED.\n")); #endif } } @@ -455,25 +469,18 @@ transmission_status_t ESP8266WiFiMesh::connectToNode(const String &targetSSID, i { if(staticIPActivated && lastSSID != "" && lastSSID != targetSSID) // So we only do this once per connection, in case there is a performance impact. { - #ifdef ENABLE_STATIC_IP_OPTIMIZATION - if(atLeastLwipVersion(lwipVersion203Signature)) - { - // 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. - WiFiMode_t storedWiFiMode = WiFi.getMode(); - WiFi.mode(WIFI_OFF); - WiFi.mode(storedWiFiMode); - yield(); - } - else - { - // Disable static IP so that we can connect to other servers via DHCP (DHCP is slower but required for connecting to more than one server, it seems (possible bug?)). - disableStaticIP(); - verboseModePrint(F("\nConnecting to a different network. Static IP deactivated to make this possible.")); - } + #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. + WiFiMode_t storedWiFiMode = WiFi.getMode(); + WiFi.mode(WIFI_OFF); + WiFi.mode(storedWiFiMode); + yield(); + #else // Disable static IP so that we can connect to other servers via DHCP (DHCP is slower but required for connecting to more than one server, it seems (possible bug?)). disableStaticIP(); verboseModePrint(F("\nConnecting to a different network. Static IP deactivated to make this possible.")); + #endif } lastSSID = targetSSID; @@ -537,10 +544,9 @@ void ESP8266WiFiMesh::attemptTransmission(const String &message, bool concluding /* Scan for APs */ connectionQueue.clear(); - // If scanAllWiFiChannels is true or Arduino core for ESP8266 version < 2.4.2 scanning will cause the WiFi radio to cycle through all WiFi channels. + // If scanAllWiFiChannels is true scanning will cause the WiFi radio to cycle through all WiFi channels. // This means existing WiFi connections are likely to break or work poorly if done frequently. int n = 0; - #ifdef ENABLE_WIFI_SCAN_OPTIMIZATION if(scanAllWiFiChannels) { n = WiFi.scanNetworks(false, _scanHidden); @@ -550,9 +556,6 @@ void ESP8266WiFiMesh::attemptTransmission(const String &message, bool concluding // Scan function argument overview: scanNetworks(bool async = false, bool show_hidden = false, uint8 channel = 0, uint8* ssid = NULL) n = WiFi.scanNetworks(false, _scanHidden, _meshWiFiChannel); } - #else - n = WiFi.scanNetworks(false, _scanHidden); - #endif _networkFilter(n, *this); // Update the connectionQueue. } @@ -650,18 +653,18 @@ void ESP8266WiFiMesh::acceptRequest() if (!waitForClientTransmission(_client, _apModeTimeoutMs) || !_client.available()) { continue; } - + /* Read in request and pass it to the supplied requestHandler */ String request = _client.readStringUntil('\r'); yield(); _client.flush(); - + String response = _requestHandler(request, *this); /* Send the response back to the client */ if (_client.connected()) { - verboseModePrint("Responding"); // Not storing strings in flash (via F()) to avoid performance impacts when using the string. + verboseModePrint("Responding"); // Not storing strings in flash (via F()) to avoid performance impacts when using the string. _client.print(response + "\r"); _client.flush(); yield(); @@ -669,3 +672,15 @@ void ESP8266WiFiMesh::acceptRequest() } } } + + +void ESP8266WiFiMesh::verboseModePrint(const String &stringToPrint, bool newline) +{ + if(_verboseMode) + { + if(newline) + Serial.println(stringToPrint); + else + Serial.print(stringToPrint); + } +} diff --git a/libraries/ESP8266WiFiMesh/src/ESP8266WiFiMesh.h b/libraries/ESP8266WiFiMesh/src/ESP8266WiFiMesh.h index ceca8f0ff4..7c5e908380 100644 --- a/libraries/ESP8266WiFiMesh/src/ESP8266WiFiMesh.h +++ b/libraries/ESP8266WiFiMesh/src/ESP8266WiFiMesh.h @@ -18,6 +18,28 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ + + + + + + +/******************************************************************************************** +* NOTE! +* +* This class is deprecated and will be removed in core version 3.0.0. +* If you are still using this class, please consider migrating to the new API shown in +* the EspnowMeshBackend.h or TcpIpMeshBackend.h source files. +* +* TODO: delete this file. +********************************************************************************************/ + + + + + + + #ifndef __WIFIMESH_H__ #define __WIFIMESH_H__ @@ -28,9 +50,6 @@ #include "NetworkInfo.h" #include "TransmissionResult.h" -#define ENABLE_STATIC_IP_OPTIMIZATION // Requires Arduino core for ESP8266 version 2.4.2 or higher and lwIP2 (lwIP can be changed in "Tools" menu of Arduino IDE). -#define ENABLE_WIFI_SCAN_OPTIMIZATION // Requires Arduino core for ESP8266 version 2.4.2 or higher. Scan time should go from about 2100 ms to around 60 ms if channel 1 (standard) is used. - const String WIFI_MESH_EMPTY_STRING = ""; class ESP8266WiFiMesh { @@ -44,8 +63,6 @@ class ESP8266WiFiMesh { uint8 _meshWiFiChannel; bool _verboseMode; WiFiServer _server; - uint32_t _lwipVersion[3]; - static const uint32_t lwipVersion203Signature[3]; String _message = WIFI_MESH_EMPTY_STRING; bool _scanHidden = false; bool _apHidden = false; @@ -56,6 +73,7 @@ class ESP8266WiFiMesh { static String lastSSID; static bool staticIPActivated; + bool useStaticIP; static IPAddress staticIP; static IPAddress gateway; static IPAddress subnetMask; @@ -78,8 +96,6 @@ class ESP8266WiFiMesh { bool waitForClientTransmission(WiFiClient &currClient, uint32_t maxWait); transmission_status_t attemptDataTransfer(); transmission_status_t attemptDataTransferKernel(); - void storeLwipVersion(); - bool atLeastLwipVersion(const uint32_t minLwipVersion[3]); @@ -133,7 +149,7 @@ class ESP8266WiFiMesh { //////////////////////////// TODO: REMOVE IN 2.5.0//////////////////////////// ~ESP8266WiFiMesh(); - + /** * WiFiMesh Constructor method. Creates a WiFi Mesh Node, ready to be initialised. * diff --git a/libraries/ESP8266WiFiMesh/src/EncryptedConnectionData.cpp b/libraries/ESP8266WiFiMesh/src/EncryptedConnectionData.cpp new file mode 100644 index 0000000000..ae30afac92 --- /dev/null +++ b/libraries/ESP8266WiFiMesh/src/EncryptedConnectionData.cpp @@ -0,0 +1,181 @@ +/* + * Copyright (C) 2019 Anders Löfgren + * + * License (MIT license): + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include "EncryptedConnectionData.h" +#include "UtilityFunctions.h" +#include "TypeConversionFunctions.h" +#include "JsonTranslator.h" + +using EspnowProtocolInterpreter::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) +{ + std::copy_n(peerStaMac, 6, _peerStaMac); + std::copy_n(peerApMac, 6, _peerApMac); + std::copy_n(hashKey, espnowHashKeyLength, _hashKey); +} + +EncryptedConnectionData::EncryptedConnectionData(const uint8_t peerStaMac[6], const uint8_t peerApMac[6], uint64_t peerSessionKey, uint64_t ownSessionKey, uint32_t duration, const uint8_t hashKey[espnowHashKeyLength]) + : EncryptedConnectionData(peerStaMac, peerApMac, peerSessionKey, ownSessionKey, hashKey) +{ + setRemainingDuration(duration); +} + +EncryptedConnectionData::EncryptedConnectionData(const EncryptedConnectionData &other) + : _peerSessionKey(other.getPeerSessionKey()), _ownSessionKey(other.getOwnSessionKey()), _desync(other.desync()), + _timeTracker(other.temporary() ? new ExpiringTimeTracker(*other.temporary()) : nullptr) +{ + other.getPeerStaMac(_peerStaMac); + other.getPeerApMac(_peerApMac); + other.getHashKey(_hashKey); +} + +EncryptedConnectionData & EncryptedConnectionData::operator=(const EncryptedConnectionData &other) +{ + if(this != &other) + { + other.getPeerStaMac(_peerStaMac); + other.getPeerApMac(_peerApMac); + _peerSessionKey = other.getPeerSessionKey(); + _ownSessionKey = other.getOwnSessionKey(); + other.getHashKey(_hashKey); + _desync = other.desync(); + _timeTracker = std::unique_ptr(other.temporary() ? new ExpiringTimeTracker(*other.temporary()) : nullptr); + } + return *this; +} + +uint8_t *EncryptedConnectionData::getEncryptedPeerMac(uint8_t *resultArray) const +{ + return getPeerStaMac(resultArray); +} + +uint8_t *EncryptedConnectionData::getUnencryptedPeerMac(uint8_t *resultArray) const +{ + return getPeerApMac(resultArray); +} + +uint8_t *EncryptedConnectionData::getPeerStaMac(uint8_t *resultArray) const +{ + std::copy_n(_peerStaMac, 6, resultArray); + return resultArray; +} + +uint8_t *EncryptedConnectionData::getPeerApMac(uint8_t *resultArray) const +{ + std::copy_n(_peerApMac, 6, resultArray); + return resultArray; +} + +bool EncryptedConnectionData::connectedTo(const uint8_t *peerMac) const +{ + if(macEqual(peerMac, _peerStaMac) || macEqual(peerMac, _peerApMac)) + { + return true; + } + else + { + return false; + } +} + +void EncryptedConnectionData::setHashKey(const uint8_t hashKey[espnowHashKeyLength]) +{ + assert(hashKey != nullptr); + + std::copy_n(hashKey, espnowHashKeyLength, _hashKey); +} + +uint8_t *EncryptedConnectionData::getHashKey(uint8_t *resultArray) const +{ + std::copy_n(_hashKey, espnowHashKeyLength, resultArray); + return resultArray; +} + +void EncryptedConnectionData::setPeerSessionKey(uint64_t sessionKey) { _peerSessionKey = sessionKey; } +uint64_t EncryptedConnectionData::getPeerSessionKey() const { return _peerSessionKey; } + +void EncryptedConnectionData::setOwnSessionKey(uint64_t sessionKey) { _ownSessionKey = sessionKey; } +uint64_t EncryptedConnectionData::getOwnSessionKey() const { return _ownSessionKey; } + +uint64_t EncryptedConnectionData::incrementSessionKey(uint64_t sessionKey, const uint8_t *hashKey, uint8_t hashKeyLength) +{ + String hmac = JsonTranslator::createHmac(uint64ToString(sessionKey), hashKey, hashKeyLength); + + /* HMAC truncation should be OK since hmac sha256 is a PRF and we are truncating to the leftmost (MSB) bits. + PRF: https://crypto.stackexchange.com/questions/26410/whats-the-gcm-sha-256-of-a-tls-protocol/26434#26434 + Truncate to leftmost bits: https://tools.ietf.org/html/rfc2104#section-5 */ + uint64_t newLeftmostBits = strtoul(hmac.substring(0, 8).c_str(), nullptr, HEX); // strtoul stops reading input when an invalid character is discovered. + if(newLeftmostBits == 0) + newLeftmostBits = RANDOM_REG32 | (1 << 31); // We never want newLeftmostBits == 0 since that would indicate an unencrypted transmission. + + uint64_t newRightmostBits = (uint32_t)(sessionKey + 1); + + return (newLeftmostBits << 32) | newRightmostBits; +} + +void EncryptedConnectionData::incrementOwnSessionKey() +{ + setOwnSessionKey(incrementSessionKey(getOwnSessionKey(), _hashKey, EspnowProtocolInterpreter::espnowHashKeyLength)); +} + +void EncryptedConnectionData::setDesync(bool desync) { _desync = desync; } +bool EncryptedConnectionData::desync() const { return _desync; } + +String EncryptedConnectionData::serialize() const +{ + // Returns: {"connectionState":{"duration":"123","password":"abc","ownSessionKey":"1A2","peerSessionKey":"3B4","peerStaMac":"F2","peerApMac":"E3"}} + + return + "{\"connectionState\":{" + + (temporary() ? JsonTranslator::jsonDuration + "\"" + String(temporary()->remainingDuration()) + "\"," : "") + + JsonTranslator::jsonDesync + "\"" + String(desync()) + "\"," + + JsonTranslator::jsonOwnSessionKey + "\"" + uint64ToString(getOwnSessionKey()) + "\"," + + JsonTranslator::jsonPeerSessionKey + "\"" + uint64ToString(getPeerSessionKey()) + "\"," + + JsonTranslator::jsonPeerStaMac + "\"" + macToString(_peerStaMac) + "\"," + + JsonTranslator::jsonPeerApMac + "\"" + macToString(_peerApMac) + "\"}}"; +} + +const ExpiringTimeTracker *EncryptedConnectionData::temporary() const +{ + return _timeTracker.get(); +} + +void EncryptedConnectionData::setRemainingDuration(uint32_t remainingDuration) +{ + if(!_timeTracker) + { + _timeTracker = std::unique_ptr(new ExpiringTimeTracker(remainingDuration)); // TODO: Change to std::make_unique(remainingDuration); once compiler fully supports C++14 + } + else + { + _timeTracker->setRemainingDuration(remainingDuration); + } +} + +void EncryptedConnectionData::removeDuration() +{ + _timeTracker = nullptr; +} diff --git a/libraries/ESP8266WiFiMesh/src/EncryptedConnectionData.h b/libraries/ESP8266WiFiMesh/src/EncryptedConnectionData.h new file mode 100644 index 0000000000..6b008b99a4 --- /dev/null +++ b/libraries/ESP8266WiFiMesh/src/EncryptedConnectionData.h @@ -0,0 +1,96 @@ +/* + * Copyright (C) 2019 Anders Löfgren + * + * License (MIT license): + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#ifndef __ESPNOWENCRYPTEDCONNECTIONDATA_H__ +#define __ESPNOWENCRYPTEDCONNECTIONDATA_H__ + +#include "ExpiringTimeTracker.h" +#include "EspnowProtocolInterpreter.h" +#include +#include + +class EncryptedConnectionData { + +public: + + virtual ~EncryptedConnectionData() = default; + + EncryptedConnectionData(const uint8_t peerStaMac[6], const uint8_t peerApMac[6], uint64_t peerSessionKey, uint64_t ownSessionKey, + const uint8_t hashKey[EspnowProtocolInterpreter::espnowHashKeyLength]); + EncryptedConnectionData(const uint8_t peerStaMac[6], const uint8_t peerApMac[6], uint64_t peerSessionKey, uint64_t ownSessionKey, + uint32_t duration, const uint8_t hashKey[EspnowProtocolInterpreter::espnowHashKeyLength]); + + EncryptedConnectionData(const EncryptedConnectionData &other); + + EncryptedConnectionData & operator=(const EncryptedConnectionData &other); + + /** + * @param resultArray An uint8_t array with at least size 6. + * + * @returns The interface MAC used for communicating with the peer. + */ + uint8_t *getEncryptedPeerMac(uint8_t *resultArray) const; + uint8_t *getUnencryptedPeerMac(uint8_t *resultArray) const; + + // @param resultArray At least size 6. + uint8_t *getPeerStaMac(uint8_t *resultArray) const; + uint8_t *getPeerApMac(uint8_t *resultArray) const; + + bool connectedTo(const uint8_t *peerMac) const; + + void setHashKey(const uint8_t hashKey[EspnowProtocolInterpreter::espnowHashKeyLength]); + // @param resultArray At least size espnowHashKeyLength. + uint8_t *getHashKey(uint8_t *resultArray) const; + + void setPeerSessionKey(uint64_t sessionKey); + uint64_t getPeerSessionKey() const; + void setOwnSessionKey(uint64_t sessionKey); + uint64_t getOwnSessionKey() const; + + static uint64_t incrementSessionKey(uint64_t sessionKey, const uint8_t *hashKey, uint8_t hashKeyLength); + void incrementOwnSessionKey(); + + void setDesync(bool desync); + bool desync() const; + + // Note that the espnowEncryptionKey, espnowEncryptionKok and espnowHashKey are not serialized. + // These will be set to the values of the EspnowMeshBackend instance that is adding the serialized encrypted connection. + String serialize() const; + + const ExpiringTimeTracker *temporary() const; + virtual void setRemainingDuration(uint32_t remainingDuration); + virtual void removeDuration(); + +private: + + uint8_t _peerStaMac[6] {0}; + uint8_t _peerApMac[6] {0}; + uint64_t _peerSessionKey; + uint64_t _ownSessionKey; + uint8_t _hashKey[EspnowProtocolInterpreter::espnowHashKeyLength] {0}; + bool _desync = false; + std::unique_ptr _timeTracker = nullptr; +}; + +#endif diff --git a/libraries/ESP8266WiFiMesh/src/EncryptedConnectionLog.cpp b/libraries/ESP8266WiFiMesh/src/EncryptedConnectionLog.cpp new file mode 100644 index 0000000000..45bcd1dd0b --- /dev/null +++ b/libraries/ESP8266WiFiMesh/src/EncryptedConnectionLog.cpp @@ -0,0 +1,92 @@ +/* + * Copyright (C) 2019 Anders Löfgren + * + * License (MIT license): + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include "EncryptedConnectionLog.h" + +using EspnowProtocolInterpreter::espnowHashKeyLength; + +EncryptedConnectionLog::EncryptedConnectionLog(const uint8_t peerStaMac[6], const uint8_t peerApMac[6], uint64_t peerSessionKey, uint64_t ownSessionKey, const uint8_t hashKey[espnowHashKeyLength]) + : EncryptedConnectionData(peerStaMac, peerApMac, peerSessionKey, ownSessionKey, hashKey) +{ } + +EncryptedConnectionLog::EncryptedConnectionLog(const uint8_t peerStaMac[6], const uint8_t peerApMac[6], uint64_t peerSessionKey, uint64_t ownSessionKey, uint32_t duration, const uint8_t hashKey[espnowHashKeyLength]) + : EncryptedConnectionData(peerStaMac, peerApMac, peerSessionKey, ownSessionKey, duration, hashKey) +{ } + +std::unique_ptr EncryptedConnectionLog::_soonestExpiringConnectionTracker = nullptr; + +bool EncryptedConnectionLog::_newRemovalsScheduled = false; + +void EncryptedConnectionLog::setRemainingDuration(uint32_t remainingDuration) +{ + EncryptedConnectionData::setRemainingDuration(remainingDuration); + + setScheduledForRemoval(false); + + updateSoonestExpiringConnectionTracker(remainingDuration); +} + +void EncryptedConnectionLog::removeDuration() +{ + EncryptedConnectionData::removeDuration(); + setScheduledForRemoval(false); +} + +void EncryptedConnectionLog::scheduleForRemoval() +{ + // When we give the connection 0 remaining duration it will be removed during the next performEspnowMaintainance() call. + // Duration must be changed before setting the scheduledForRemoval flag to true, since the flag is otherwise cleared. + setRemainingDuration(0); + setScheduledForRemoval(true); +} + +void EncryptedConnectionLog::setScheduledForRemoval(bool scheduledForRemoval) +{ + _scheduledForRemoval = scheduledForRemoval; + + if(scheduledForRemoval) + setNewRemovalsScheduled(true); +} +bool EncryptedConnectionLog::removalScheduled() const { return _scheduledForRemoval; } + +void EncryptedConnectionLog::setNewRemovalsScheduled(bool newRemovalsScheduled) { _newRemovalsScheduled = newRemovalsScheduled; } +bool EncryptedConnectionLog::newRemovalsScheduled( ){ return _newRemovalsScheduled; } + +const ExpiringTimeTracker *EncryptedConnectionLog::getSoonestExpiringConnectionTracker() +{ + return _soonestExpiringConnectionTracker.get(); +} + +void EncryptedConnectionLog::updateSoonestExpiringConnectionTracker(uint32_t remainingDuration) +{ + if(!getSoonestExpiringConnectionTracker() || remainingDuration < getSoonestExpiringConnectionTracker()->remainingDuration()) + { + _soonestExpiringConnectionTracker = std::unique_ptr(new ExpiringTimeTracker(remainingDuration)); // TODO: Change to std::make_unique(remainingDuration); once compiler fully supports C++14 + } +} + +void EncryptedConnectionLog::clearSoonestExpiringConnectionTracker() +{ + _soonestExpiringConnectionTracker = nullptr; +} diff --git a/libraries/ESP8266WiFiMesh/src/EncryptedConnectionLog.h b/libraries/ESP8266WiFiMesh/src/EncryptedConnectionLog.h new file mode 100644 index 0000000000..2c1a991549 --- /dev/null +++ b/libraries/ESP8266WiFiMesh/src/EncryptedConnectionLog.h @@ -0,0 +1,68 @@ +/* + * Copyright (C) 2019 Anders Löfgren + * + * License (MIT license): + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#ifndef __ESPNOWENCRYPTEDCONNECTIONLOG_H__ +#define __ESPNOWENCRYPTEDCONNECTIONLOG_H__ + +#include "EncryptedConnectionData.h" +#include "EspnowProtocolInterpreter.h" + +class EncryptedConnectionLog : public EncryptedConnectionData { + +public: + + EncryptedConnectionLog(const uint8_t peerStaMac[6], const uint8_t peerApMac[6], uint64_t peerSessionKey, uint64_t ownSessionKey, + const uint8_t hashKey[EspnowProtocolInterpreter::espnowHashKeyLength]); + EncryptedConnectionLog(const uint8_t peerStaMac[6], const uint8_t peerApMac[6], uint64_t peerSessionKey, uint64_t ownSessionKey, + uint32_t duration, const uint8_t hashKey[EspnowProtocolInterpreter::espnowHashKeyLength]); + + // Only guaranteed to expire at the latest when the soonestExpiringConnection does. Can expire before the soonestExpiringConnection since it is not updated on connection removal. + // Needs to be a copy to avoid invalidation during operations on temporaryEncryptedConnections. + static std::unique_ptr _soonestExpiringConnectionTracker; + + // Only indicates if at least one removal was scheduled since the flag was last cleared, not if the removal is still scheduled to happen. + // Canceling a removal will not update the flag. + static bool _newRemovalsScheduled; + + // Can be used to set a duration both for temporary and permanent encrypted connections (transforming the latter into a temporary connection in the process). + void setRemainingDuration(uint32_t remainingDuration) override; + void removeDuration() override; + + void scheduleForRemoval(); + bool removalScheduled() const; + + static void setNewRemovalsScheduled(bool newRemovalsScheduled); + static bool newRemovalsScheduled(); + + static const ExpiringTimeTracker *getSoonestExpiringConnectionTracker(); + static void updateSoonestExpiringConnectionTracker(uint32_t remainingDuration); + static void clearSoonestExpiringConnectionTracker(); + +private: + + bool _scheduledForRemoval = false; + void setScheduledForRemoval(bool scheduledForRemoval); +}; + +#endif diff --git a/libraries/ESP8266WiFiMesh/src/EspnowMeshBackend.cpp b/libraries/ESP8266WiFiMesh/src/EspnowMeshBackend.cpp new file mode 100644 index 0000000000..7bcca0d578 --- /dev/null +++ b/libraries/ESP8266WiFiMesh/src/EspnowMeshBackend.cpp @@ -0,0 +1,2295 @@ +/* + EspnowMeshBackend + + Copyright (C) 2019 Anders Löfgren + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include +extern "C" { + #include +} + +#include "EspnowMeshBackend.h" +#include "TypeConversionFunctions.h" +#include "UtilityFunctions.h" +#include "MutexTracker.h" +#include "JsonTranslator.h" +#include "Crypto.h" + +using EspnowProtocolInterpreter::espnowEncryptionKeyLength; +using EspnowProtocolInterpreter::espnowHashKeyLength; + +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; + +bool EspnowMeshBackend::_espnowTransmissionMutex = false; + +EspnowMeshBackend *EspnowMeshBackend::_espnowRequestManager = nullptr; + +std::map, MessageData> EspnowMeshBackend::receivedEspnowTransmissions = {}; +std::map, RequestData> EspnowMeshBackend::sentRequests = {}; +std::map, TimeTracker> EspnowMeshBackend::receivedRequests = {}; + +std::list EspnowMeshBackend::responsesToSend = {}; +std::list EspnowMeshBackend::peerRequestConfirmationsToSend = {}; + +std::vector EspnowMeshBackend::encryptedConnections = {}; + +uint32_t EspnowMeshBackend::_espnowTransmissionTimeoutMs = 40; +uint32_t EspnowMeshBackend::_espnowRetransmissionIntervalMs = 15; + +uint32_t EspnowMeshBackend::_encryptionRequestTimeoutMs = 500; + +bool EspnowMeshBackend::_espnowSendConfirmed = false; + +String EspnowMeshBackend::_ongoingPeerRequestNonce = ""; +EspnowMeshBackend *EspnowMeshBackend::_ongoingPeerRequester = nullptr; +encrypted_connection_status_t EspnowMeshBackend::_ongoingPeerRequestResult = ECS_MAX_CONNECTIONS_REACHED_SELF; +uint32_t EspnowMeshBackend::_ongoingPeerRequestEncryptionStart = 0; + +uint8_t EspnowMeshBackend::_espnowEncryptionKok[espnowEncryptionKeyLength] = { 0 }; +bool EspnowMeshBackend::_espnowEncryptionKokSet = false; + +uint32_t EspnowMeshBackend::_unencryptedMessageID = 0; + +// _logEntryLifetimeMs is based on someone storing 40 responses of 750 bytes each = 30 000 bytes (roughly full memory), +// which takes 2000 ms + some margin to send. Also, we want to avoid old entries taking up memory if they cannot be sent, +// so storage duration should not be too long. +uint32_t EspnowMeshBackend::_logEntryLifetimeMs = 2500; +uint32_t EspnowMeshBackend::_responseTimeoutMs = 5000; +uint32_t EspnowMeshBackend::_timeOfLastLogClear = 0; +uint32_t EspnowMeshBackend::_criticalHeapLevel = 6000; // In bytes +uint32_t EspnowMeshBackend::_criticalHeapLevelBuffer = 6000; // In bytes + +uint8_t EspnowMeshBackend::_maxTransmissionsPerMessage = 3; + +bool EspnowMeshBackend::_espnowSendToNodeMutex = false; +uint8_t EspnowMeshBackend::_transmissionTargetBSSID[6] = {0}; + +double EspnowMeshBackend::_transmissionsTotal = 0; +double EspnowMeshBackend::_transmissionsFailed = 0; + +bool EspnowMeshBackend::_staticVerboseMode = false; + +void espnowDelay(uint32_t durationMs) +{ + uint32_t startingTime = millis(); + + while(millis() - startingTime < durationMs) + { + delay(1); + EspnowMeshBackend::performEspnowMaintainance(); + } +} + +EspnowMeshBackend::EspnowMeshBackend(requestHandlerType requestHandler, responseHandlerType responseHandler, + networkFilterType networkFilter, const String &meshPassword, const uint8_t espnowEncryptionKey[espnowEncryptionKeyLength], + const uint8_t espnowHashKey[espnowHashKeyLength], const String &ssidPrefix, const String &ssidSuffix, bool verboseMode, + uint8 meshWiFiChannel) + : MeshBackendBase(requestHandler, responseHandler, networkFilter, MB_ESP_NOW) +{ + // Reserve the maximum possible usage early on to prevent heap fragmentation later. + encryptedConnections.reserve(maxEncryptedConnections); + + setSSID(ssidPrefix, "", ssidSuffix); + setMeshPassword(meshPassword); + setEspnowEncryptionKey(espnowEncryptionKey); + setEspnowHashKey(espnowHashKey); + setVerboseModeState(verboseMode); + setWiFiChannel(meshWiFiChannel); +} + +EspnowMeshBackend::~EspnowMeshBackend() +{ + if(isEspnowRequestManager()) + { + setEspnowRequestManager(nullptr); + } + + deleteSentRequestsByOwner(this); +} + +void EspnowMeshBackend::begin() +{ + if(!getAPController()) // If there is no active AP controller + WiFi.mode(WIFI_STA); // WIFI_AP_STA mode automatically sets up an AP, so we can't use that as default. + + activateEspnow(); +} + +void EspnowMeshBackend::performEspnowMaintainance() +{ + // Doing this during an ESP-NOW transmission could invalidate iterators + MutexTracker mutexTracker(_espnowTransmissionMutex, handlePostponedRemovals); + if(!mutexTracker.mutexCaptured()) + { + assert(false && "ERROR! Transmission in progress. Don't call performEspnowMaintainance from callbacks as this may corrupt program state! Aborting."); + return; + } + + if(millis() - _timeOfLastLogClear >= 500) // Clearing too frequently will cause a lot of unnecessary container iterations. + { + clearOldLogEntries(); + } + if(EncryptedConnectionLog::getSoonestExpiringConnectionTracker() && EncryptedConnectionLog::getSoonestExpiringConnectionTracker()->expired()) + { + updateTemporaryEncryptedConnections(); + } + + sendEspnowResponses(); +} + +void EspnowMeshBackend::updateTemporaryEncryptedConnections(bool scheduledRemovalOnly) +{ + EncryptedConnectionLog::clearSoonestExpiringConnectionTracker(); + + for(auto connectionIterator = encryptedConnections.begin(); connectionIterator != encryptedConnections.end(); ) + { + if(auto timeTrackerPointer = connectionIterator->temporary()) + { + if(timeTrackerPointer->expired() && (!scheduledRemovalOnly || connectionIterator->removalScheduled())) + { + uint8_t macArray[6] = { 0 }; + removeEncryptedConnectionUnprotected(connectionIterator->getEncryptedPeerMac(macArray), &connectionIterator); + continue; + } + else + { + EncryptedConnectionLog::updateSoonestExpiringConnectionTracker(timeTrackerPointer->remainingDuration()); + } + } + assert(!connectionIterator->removalScheduled()); // timeTracker should always exist and be expired if removal is scheduled. + + ++connectionIterator; + } + + EncryptedConnectionLog::setNewRemovalsScheduled(false); +} + +void EspnowMeshBackend::espnowReceiveCallbackWrapper(uint8_t *macaddr, uint8_t *dataArray, uint8_t len) +{ + using namespace EspnowProtocolInterpreter; + + if(len >= EspnowProtocolInterpreter::espnowProtocolBytesSize()) // If we do not receive at least the protocol bytes, the transmission is invalid. + { + //uint32_t callbackStart = millis(); + + // If there is a espnowRequestManager, get it + EspnowMeshBackend *currentEspnowRequestManager = getEspnowRequestManager(); + + char messageType = espnowGetMessageType(dataArray); + uint64_t receivedMessageID = espnowGetMessageID(dataArray); + + if(currentEspnowRequestManager && !currentEspnowRequestManager->acceptsUnencryptedRequests() + && !usesConstantSessionKey(messageType) && !verifyPeerSessionKey(receivedMessageID, macaddr, messageType)) + { + return; + } + + uint64_t uint64StationMac = macToUint64(macaddr); + bool transmissionEncrypted = usesEncryption(receivedMessageID); + + // Useful when debugging the protocol + //Serial.print("Received from Mac: " + macToString(macaddr) + " ID: " + uint64ToString(receivedMessageID)); + //Serial.println(transmissionEncrypted ? " Encrypted" : " Unencrypted"); + + if(messageType == 'Q') // Question (request) + { + if(ESP.getFreeHeap() <= criticalHeapLevel()) + { + warningPrint("WARNING! Free heap below critical level. Suspending ESP-NOW request processing until the situation improves."); + return; + } + + if(currentEspnowRequestManager) + { + if(!requestReceived(uint64StationMac, receivedMessageID)) // If the request has not already been received + { + if(transmissionEncrypted) + { + EncryptedConnectionLog *encryptedConnection = getEncryptedConnection(macaddr); + + if(!encryptedConnection || (!synchronizePeerSessionKey(receivedMessageID, *encryptedConnection) && + !verifyPeerSessionKey(receivedMessageID, *encryptedConnection, uint64StationMac, messageType))) + { + // We received an encrypted transmission + // and we have no encrypted connection to the transmitting node (in which case we want to avoid sending the secret session key back in an unencrypted response) + // or the transmission has the wrong session key + // and it doesn't have a session key that matches any multi-part transmission we are currently receiving (in which case the transmission is invalid). + return; + } + } + + //Serial.println("espnowReceiveCallbackWrapper before internal callback " + String(millis() - callbackStart)); + + currentEspnowRequestManager->espnowReceiveCallback(macaddr, dataArray, len); + } + } + } + else if(messageType == 'A') // Answer (response) + { + EspnowMeshBackend *requestSender = nullptr; + uint64_t requestMac = 0; + + if(transmissionEncrypted) + { + // An encrypted transmission can only be sent to the station interface, since it otherwise won't arrive (because of ESP_NOW_ROLE_CONTROLLER). + requestMac = uint64StationMac; + requestSender = getOwnerOfSentRequest(requestMac, receivedMessageID); + } + else + { + // An unencrypted transmission was probably sent to the AP interface as a result of a scan. + requestMac = espnowGetTransmissionMac(dataArray); + requestSender = getOwnerOfSentRequest(requestMac, receivedMessageID); + + // But if not, also check if it was sent to the station interface. + if(!requestSender) + { + requestMac = uint64StationMac; + requestSender = getOwnerOfSentRequest(requestMac, receivedMessageID); + } + } + + // If this node sent the request and it has not already been answered. + if(requestSender) + { + uint8_t macArray[6] = { 0 }; + + requestSender->espnowReceiveCallback(uint64ToMac(requestMac, macArray), dataArray, len); + } + } + else if(messageType == 'S') // Synchronization request + { + synchronizePeerSessionKey(receivedMessageID, macaddr); + } + else if(messageType == 'P') // Peer request + { + handlePeerRequest(macaddr, dataArray, len, uint64StationMac, receivedMessageID); + } + else if(messageType == 'C') // peer request Confirmation + { + handlePeerRequestConfirmation(macaddr, dataArray, len); + } + else + { + assert(messageType == 'Q' || messageType == 'A' || messageType == 'S' || messageType == 'P' || messageType == 'C'); + } + + //Serial.println("espnowReceiveCallbackWrapper duration " + String(millis() - callbackStart)); + } +} + +void EspnowMeshBackend::handlePeerRequest(uint8_t *macaddr, uint8_t *dataArray, uint8_t len, uint64_t uint64StationMac, uint64_t receivedMessageID) +{ + // Pairing process ends when encryptedConnectionVerificationHeader is received, maxConnectionsReachedHeader is sent or timeout is reached. + // Pairing process stages for request receiver: + // Receive: encryptionRequestHeader or temporaryEncryptionRequestHeader. + // Send: maxConnectionsReachedHeader / basicConnectionInfoHeader -> encryptedConnectionInfoHeader or maxConnectionsReachedHeader. + // Receive: encryptedConnectionVerificationHeader. + + using namespace EspnowProtocolInterpreter; + + if(!requestReceived(uint64StationMac, receivedMessageID)) + { + storeReceivedRequest(uint64StationMac, receivedMessageID, TimeTracker(millis())); + + bool encryptedCorrectly = synchronizePeerSessionKey(receivedMessageID, macaddr); + String message = espnowGetMessageContent(dataArray, len); + int32_t messageHeaderEndIndex = message.indexOf(':'); + String messageHeader = message.substring(0, messageHeaderEndIndex + 1); + + if(messageHeader == encryptedConnectionVerificationHeader) + { + if(encryptedCorrectly) + { + int32_t connectionRequestTypeEndIndex = message.indexOf(':', messageHeaderEndIndex + 1); + String connectionRequestType = message.substring(messageHeaderEndIndex + 1, connectionRequestTypeEndIndex + 1); + + if(connectionRequestType == encryptionRequestHeader) + { + temporaryEncryptedConnectionToPermanent(macaddr); + } + else if(connectionRequestType == temporaryEncryptionRequestHeader) + { + connectionLogIterator encryptedConnection = connectionLogEndIterator(); + if(!getEncryptedConnectionIterator(macaddr, encryptedConnection)) + assert(false && "We must have an encrypted connection if we received an encryptedConnectionVerificationHeader which was encryptedCorrectly."); + + if(encryptedConnection->temporary()) // Should not change duration for existing permanent connections. + { + uint32_t connectionDuration = 0; + if(JsonTranslator::getDuration(message, connectionDuration)) + { + encryptedConnection->setRemainingDuration(connectionDuration); + } + } + } + else + { + assert(false && "Unknown P-type verification message received!"); + } + } + } + else if(messageHeader == encryptionRequestHeader || messageHeader == temporaryEncryptionRequestHeader) + { + // If there is a espnowRequestManager, get it + if(EspnowMeshBackend *currentEspnowRequestManager = getEspnowRequestManager()) + { + String requestNonce = ""; + if(JsonTranslator::getNonce(message, requestNonce)) + { + uint8_t apMacArray[6] = { 0 }; + peerRequestConfirmationsToSend.emplace_back(receivedMessageID, encryptedCorrectly, currentEspnowRequestManager->getMeshPassword(), requestNonce, macaddr, espnowGetTransmissionMac(dataArray, apMacArray), currentEspnowRequestManager->getEspnowHashKey()); + } + } + } + else if(messageHeader == encryptedConnectionRemovalRequestHeader) + { + if(encryptedCorrectly) + removeEncryptedConnection(macaddr); + } + else + { + assert(false && "Unknown P-type message received!"); + } + } +} + +void EspnowMeshBackend::handlePeerRequestConfirmation(uint8_t *macaddr, uint8_t *dataArray, uint8_t len) +{ + // Pairing process ends when _ongoingPeerRequestNonce == "" or timeout is reached. + // Pairing process stages for request sender: + // Send: encryptionRequestHeader or temporaryEncryptionRequestHeader. + // Receive: maxConnectionsReachedHeader / basicConnectionInfoHeader -> encryptedConnectionInfoHeader or maxConnectionsReachedHeader. + // Send: encryptedConnectionVerificationHeader. + + using namespace EspnowProtocolInterpreter; + + if(_ongoingPeerRequestNonce != "") + { + String message = espnowGetMessageContent(dataArray, len); + String requestNonce = ""; + uint8_t macArray[6] = { 0 }; + if(JsonTranslator::getNonce(message, requestNonce) && requestNonce == _ongoingPeerRequestNonce) + { + int32_t messageHeaderEndIndex = message.indexOf(':'); + String messageHeader = message.substring(0, messageHeaderEndIndex + 1); + String messageBody = message.substring(messageHeaderEndIndex + 1); + + if(messageHeader == basicConnectionInfoHeader) + { + // _ongoingPeerRequestResult == ECS_CONNECTION_ESTABLISHED means we have already received a basicConnectionInfoHeader + if(_ongoingPeerRequestResult != ECS_CONNECTION_ESTABLISHED && + JsonTranslator::verifyHmac(message, _ongoingPeerRequester->getEspnowHashKey(), espnowHashKeyLength)) + { + _ongoingPeerRequestEncryptionStart = millis(); + + connectionLogIterator existingEncryptedConnection = connectionLogEndIterator(); + + if(!getEncryptedConnectionIterator(macaddr, existingEncryptedConnection)) + { + // Although the newly created session keys are normally never used (they are replaced with synchronized ones later), the session keys must still be randomized to prevent attacks until replaced. + _ongoingPeerRequestResult = _ongoingPeerRequester->addTemporaryEncryptedConnection(macaddr, espnowGetTransmissionMac(dataArray, macArray), + createSessionKey(), createSessionKey(), getEncryptionRequestTimeout()); + } + else + { + // Encrypted connection already exists + _ongoingPeerRequestResult = ECS_CONNECTION_ESTABLISHED; + + if(auto timeTrackerPointer = existingEncryptedConnection->temporary()) + { + if(timeTrackerPointer->remainingDuration() < getEncryptionRequestTimeout()) // Should only extend duration for existing connections. + { + existingEncryptedConnection->setRemainingDuration(getEncryptionRequestTimeout()); + } + } + } + + if(_ongoingPeerRequestResult != ECS_CONNECTION_ESTABLISHED) + { + // Adding connection failed, abort ongoing peer request. + _ongoingPeerRequestNonce = ""; + } + } + } + else + { + if(messageHeader == encryptedConnectionInfoHeader) + { + String messagePassword = ""; + + if(JsonTranslator::getPassword(messageBody, messagePassword) && messagePassword == _ongoingPeerRequester->getMeshPassword()) + { + // The mesh password is only shared via encrypted messages, so now we know this message is valid since it was encrypted and contained the correct nonce. + + EncryptedConnectionLog *encryptedConnection = getEncryptedConnection(macaddr); + uint64_t peerSessionKey = 0; + uint64_t ownSessionKey = 0; + if(encryptedConnection && JsonTranslator::getPeerSessionKey(messageBody, peerSessionKey) && JsonTranslator::getOwnSessionKey(messageBody, ownSessionKey)) + { + encryptedConnection->setPeerSessionKey(peerSessionKey); + encryptedConnection->setOwnSessionKey(ownSessionKey); + _ongoingPeerRequestResult = ECS_CONNECTION_ESTABLISHED; + } + else + { + _ongoingPeerRequestResult = ECS_REQUEST_TRANSMISSION_FAILED; + } + + _ongoingPeerRequestNonce = ""; + } + } + else if(messageHeader == maxConnectionsReachedHeader) + { + if(JsonTranslator::verifyHmac(message, _ongoingPeerRequester->getEspnowHashKey(), espnowHashKeyLength)) + { + _ongoingPeerRequestResult = ECS_MAX_CONNECTIONS_REACHED_PEER; + _ongoingPeerRequestNonce = ""; + } + } + else + { + assert(messageHeader == basicConnectionInfoHeader || messageHeader == encryptedConnectionInfoHeader || messageHeader == maxConnectionsReachedHeader); + } + } + } + } +} + +void EspnowMeshBackend::setEspnowRequestManager(EspnowMeshBackend *espnowMeshInstance) +{ + _espnowRequestManager = espnowMeshInstance; +} + +EspnowMeshBackend *EspnowMeshBackend::getEspnowRequestManager() {return _espnowRequestManager;} + +bool EspnowMeshBackend::isEspnowRequestManager() +{ + return (this == getEspnowRequestManager()); +} + +bool EspnowMeshBackend::activateEspnow() +{ + if (esp_now_init()==0) + { + if(_espnowEncryptionKokSet && esp_now_set_kok(_espnowEncryptionKok, espnowEncryptionKeyLength)) // esp_now_set_kok returns 0 on success. + warningPrint("Failed to set ESP-NOW KoK!"); + + if(getEspnowRequestManager() == nullptr) + { + setEspnowRequestManager(this); + } + + esp_now_register_recv_cb(espnowReceiveCallbackWrapper); + esp_now_register_send_cb([](uint8_t* mac, uint8_t sendStatus) { + (void)mac; // This is useful to remove a "unused parameter" compiler warning. Does nothing else. + if(!sendStatus) // sendStatus == 0 when send was OK. + _espnowSendConfirmed = true; // We do not want to reset this to false. That only happens before transmissions. Otherwise subsequent failed send attempts may obscure an initial successful one. + }); + + // Role must be set before adding peers. Cannot be changed while having peers. + // With ESP_NOW_ROLE_CONTROLLER, we always transmit from the station interface, which gives predictability. + if(esp_now_set_self_role(ESP_NOW_ROLE_CONTROLLER)) // esp_now_set_self_role returns 0 on success. + warningPrint("Failed to set ESP-NOW role! Maybe ESP-NOW peers are already added?"); + + verboseModePrint("ESP-NOW activated."); + verboseModePrint("My ESP-NOW STA MAC: " + WiFi.macAddress() + "\n"); // Get the station MAC address. The softAP MAC is different. + + return true; + } + else + { + warningPrint("ESP-NOW init failed!"); + return false; + } +} + +bool EspnowMeshBackend::deactivateEspnow() +{ + // esp_now_deinit() clears all ESP-NOW API settings, including receive callback, send callback, Kok and peers. + // The node will however continue to give acks to received ESP-NOW transmissions as long as the receiving interface (AP or STA) is active, even though the transmissions will not be processed. + if(esp_now_deinit() == 0) + { + responsesToSend.clear(); + peerRequestConfirmationsToSend.clear(); + receivedEspnowTransmissions.clear(); + sentRequests.clear(); + receivedRequests.clear(); + encryptedConnections.clear(); + EncryptedConnectionLog::setNewRemovalsScheduled(false); + + return true; + } + else + { + return false; + } +} + +uint32_t EspnowMeshBackend::logEntryLifetimeMs() +{ + return _logEntryLifetimeMs; +} + +uint32_t EspnowMeshBackend::responseTimeoutMs() +{ + return _responseTimeoutMs; +} + +void EspnowMeshBackend::setCriticalHeapLevelBuffer(uint32_t bufferInBytes) +{ + _criticalHeapLevelBuffer = bufferInBytes; +} + +uint32_t EspnowMeshBackend::criticalHeapLevelBuffer() +{ + return _criticalHeapLevelBuffer; +} + +template +void EspnowMeshBackend::deleteExpiredLogEntries(std::map, T> &logEntries, uint32_t maxEntryLifetimeMs) +{ + for(typename std::map, T>::iterator entryIterator = logEntries.begin(); + entryIterator != logEntries.end(); ) + { + if(entryIterator->second.timeSinceCreation() > maxEntryLifetimeMs) + { + entryIterator = logEntries.erase(entryIterator); + } + else + ++entryIterator; + } +} + +template +void EspnowMeshBackend::deleteExpiredLogEntries(std::list &logEntries, uint32_t maxEntryLifetimeMs) +{ + for(typename std::list::iterator entryIterator = logEntries.begin(); + entryIterator != logEntries.end(); ) + { + if(entryIterator->timeSinceCreation() > maxEntryLifetimeMs) + { + entryIterator = logEntries.erase(entryIterator); + } + else + ++entryIterator; + } +} + +template <> +void EspnowMeshBackend::deleteExpiredLogEntries(std::list &logEntries, uint32_t maxEntryLifetimeMs) +{ + for(typename std::list::iterator entryIterator = logEntries.begin(); + entryIterator != logEntries.end(); ) + { + auto timeTrackerPointer = entryIterator->temporary(); + if(timeTrackerPointer && timeTrackerPointer->timeSinceCreation() > maxEntryLifetimeMs) + { + entryIterator = logEntries.erase(entryIterator); + } + else + ++entryIterator; + } +} + +template <> +void EspnowMeshBackend::deleteExpiredLogEntries(std::list &logEntries, uint32_t maxEntryLifetimeMs) +{ + for(typename std::list::iterator entryIterator = logEntries.begin(); + entryIterator != logEntries.end(); ) + { + auto timeTrackerPointer = entryIterator->temporary(); + if(timeTrackerPointer && timeTrackerPointer->timeSinceCreation() > maxEntryLifetimeMs) + { + entryIterator = logEntries.erase(entryIterator); + } + else + ++entryIterator; + } +} + +void EspnowMeshBackend::clearOldLogEntries() +{ + // Clearing all old log entries at the same time should help minimize heap fragmentation. + + // uint32_t startTime = millis(); + + _timeOfLastLogClear = millis(); + + deleteExpiredLogEntries(receivedEspnowTransmissions, logEntryLifetimeMs()); + deleteExpiredLogEntries(receivedRequests, logEntryLifetimeMs()); // Just needs to be long enough to not accept repeated transmissions by mistake. + deleteExpiredLogEntries(sentRequests, logEntryLifetimeMs()); + deleteExpiredLogEntries(responsesToSend, logEntryLifetimeMs()); + deleteExpiredLogEntries(peerRequestConfirmationsToSend, getEncryptionRequestTimeout()); +} + +template +T *EspnowMeshBackend::getMapValue(std::map &mapIn, uint64_t keyIn) +{ + typename std::map::iterator mapIterator = mapIn.find(keyIn); + + if(mapIterator != mapIn.end()) + { + return &mapIterator->second; + } + + return nullptr; +} + +void EspnowMeshBackend::storeSentRequest(const uint64_t targetBSSID, const uint64_t messageID, const RequestData &requestData) +{ + sentRequests.insert(std::make_pair(std::make_pair(targetBSSID, messageID), requestData)); +} + +void EspnowMeshBackend::storeReceivedRequest(const uint64_t senderBSSID, const uint64_t messageID, const TimeTracker &timeTracker) +{ + receivedRequests.insert(std::make_pair(std::make_pair(senderBSSID, messageID), timeTracker)); +} + +EspnowMeshBackend *EspnowMeshBackend::getOwnerOfSentRequest(uint64_t requestMac, uint64_t requestID) +{ + std::map, RequestData>::iterator sentRequest = sentRequests.find(std::make_pair(requestMac, requestID)); + + if(sentRequest != sentRequests.end()) + { + return &sentRequest->second.getMeshInstance(); + } + + return nullptr; +} + +size_t EspnowMeshBackend::deleteSentRequest(uint64_t requestMac, uint64_t requestID) +{ + return sentRequests.erase(std::make_pair(requestMac, requestID)); +} + +size_t EspnowMeshBackend::deleteSentRequestsByOwner(EspnowMeshBackend *instancePointer) +{ + size_t numberDeleted = 0; + + for(std::map, RequestData>::iterator requestIterator = sentRequests.begin(); + requestIterator != sentRequests.end(); ) + { + if(&requestIterator->second.getMeshInstance() == instancePointer) // If instance at instancePointer made the request + { + requestIterator = sentRequests.erase(requestIterator); + numberDeleted++; + } + else + ++requestIterator; + } + + return numberDeleted; +} + +bool EspnowMeshBackend::requestReceived(uint64_t requestMac, uint64_t requestID) +{ + return receivedRequests.count(std::make_pair(requestMac, requestID)); +} + +uint32_t EspnowMeshBackend::criticalHeapLevel() +{ + return _criticalHeapLevel; +} + +uint64_t EspnowMeshBackend::generateMessageID(EncryptedConnectionLog *encryptedConnection) +{ + if(encryptedConnection) + { + return encryptedConnection->getOwnSessionKey(); + } + + return _unencryptedMessageID++; +} + +uint64_t EspnowMeshBackend::createSessionKey() +{ + uint64_t newSessionKey = randomUint64(); + return EspnowProtocolInterpreter::usesEncryption(newSessionKey) ? newSessionKey : (newSessionKey | ((uint64_t)RANDOM_REG32) << 32 | uint64MSB); +} + +void EspnowMeshBackend::setEspnowTransmissionTimeout(uint32_t timeoutMs) +{ + _espnowTransmissionTimeoutMs = timeoutMs; +} +uint32_t EspnowMeshBackend::getEspnowTransmissionTimeout() {return _espnowTransmissionTimeoutMs;} + +void EspnowMeshBackend::setEspnowRetransmissionInterval(uint32_t intervalMs) +{ + _espnowRetransmissionIntervalMs = intervalMs; +} +uint32_t EspnowMeshBackend::getEspnowRetransmissionInterval() {return _espnowRetransmissionIntervalMs;} + +void EspnowMeshBackend::setEncryptionRequestTimeout(uint32_t timeoutMs) +{ + _encryptionRequestTimeoutMs = timeoutMs; +} +uint32_t EspnowMeshBackend::getEncryptionRequestTimeout() {return _encryptionRequestTimeoutMs;} + +void EspnowMeshBackend::setAutoEncryptionDuration(uint32_t duration) +{ + _autoEncryptionDuration = duration; +} +uint32_t EspnowMeshBackend::getAutoEncryptionDuration() {return _autoEncryptionDuration;} + +bool EspnowMeshBackend::usesConstantSessionKey(char messageType) +{ + return messageType == 'A' || messageType == 'C'; +} + +transmission_status_t EspnowMeshBackend::espnowSendToNode(const String &message, const uint8_t *targetBSSID, char messageType, EspnowMeshBackend *espnowInstance) +{ + using EspnowProtocolInterpreter::synchronizationRequestHeader; + + EncryptedConnectionLog *encryptedConnection = getEncryptedConnection(targetBSSID); + + if(encryptedConnection) + { + uint8_t encryptedMac[6] {0}; + encryptedConnection->getEncryptedPeerMac(encryptedMac); + + if(encryptedConnection->desync()) + { + espnowSendToNodeUnsynchronized(synchronizationRequestHeader, encryptedMac, 'S', generateMessageID(encryptedConnection), espnowInstance); + + if(encryptedConnection->desync()) + { + return TS_TRANSMISSION_FAILED; + } + } + + return espnowSendToNodeUnsynchronized(message, encryptedMac, messageType, generateMessageID(encryptedConnection), espnowInstance); + } + else + { + return espnowSendToNodeUnsynchronized(message, targetBSSID, messageType, generateMessageID(encryptedConnection), espnowInstance); + } +} + +static const uint8_t broadcastMac[6] = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}; // Saved for future use. TODO +transmission_status_t EspnowMeshBackend::espnowSendToNodeUnsynchronized(const String message, const uint8_t *targetBSSID, char messageType, uint64_t messageID, EspnowMeshBackend *espnowInstance) +{ + using namespace EspnowProtocolInterpreter; + + MutexTracker mutexTracker(_espnowSendToNodeMutex); + if(!mutexTracker.mutexCaptured()) + { + assert(false && "ERROR! espnowSendToNode already in progress. Don't call espnowSendToNode from callbacks as this will make it impossible to know which transmissions succeed! Aborting."); + return TS_TRANSMISSION_FAILED; + } + + // We copy the message String and bssid array from the arguments in this method to make sure they are + // not modified by a callback during the delay(1) calls further down. + // This also makes it possible to get the current _transmissionTargetBSSID outside of the method. + std::copy_n(targetBSSID, 6, _transmissionTargetBSSID); + + EncryptedConnectionLog *encryptedConnection = getEncryptedConnection(_transmissionTargetBSSID); + + int32_t transmissionsRequired = ceil((double)message.length() / getMaxMessageBytesPerTransmission()); + int32_t transmissionsRemaining = transmissionsRequired > 1 ? transmissionsRequired - 1 : 0; + + _transmissionsTotal++; + + // Though it is possible to handle messages requiring more than 3 transmissions with the current design, transmission fail rates would increase dramatically. + // Messages composed of up to 128 transmissions can be handled without modification, but RAM limitations on the ESP8266 would make this hard in practice. + // We thus prefer to keep the code simple and performant instead. + // Very large messages can always be split by the user as required. + assert(transmissionsRequired <= getMaxTransmissionsPerMessage()); + assert(messageType == 'Q' || messageType == 'A' || messageType == 'S' || messageType == 'P' || messageType == 'C'); + if(messageType == 'P' || messageType == 'C') + { + assert(transmissionsRequired == 1); // These messages are assumed to be contained in one message by the receive callbacks. + } + + uint8_t transmissionSize = 0; + bool messageStart = true; + uint8_t sizeOfProtocolBytes = espnowProtocolBytesSize(); + + do + { + ////// Manage logs ////// + + if(transmissionsRemaining == 0 && messageType == 'Q') + { + assert(espnowInstance); // espnowInstance required when transmitting 'Q' type messages. + // If we are sending the last transmission of a request we should store the sent request in the log no matter if we receive an ack for the final transmission or not. + // That way we will always be ready to receive the response to the request when there is a chance the request message was transmitted successfully, + // even if the final ack for the request message was lost. + storeSentRequest(macToUint64(_transmissionTargetBSSID), messageID, RequestData(*espnowInstance)); + } + + ////// Create transmission array ////// + + if(transmissionsRemaining > 0) + transmissionSize = getMaxBytesPerTransmission(); + else if(message.length() == 0) + transmissionSize = sizeOfProtocolBytes; + else + transmissionSize = sizeOfProtocolBytes + (message.length() % getMaxMessageBytesPerTransmission() == 0 ? + getMaxMessageBytesPerTransmission() : message.length() % getMaxMessageBytesPerTransmission()); + + uint8_t transmission[transmissionSize]; + + ////// Fill protocol bytes ////// + + transmission[espnowMessageTypeIndex] = messageType; + + if(messageStart) + { + transmission[espnowTransmissionsRemainingIndex] = (char)(transmissionsRemaining | 0x80); + } + else + { + transmission[espnowTransmissionsRemainingIndex] = (char)transmissionsRemaining; + } + + // Fills indicies in range [espnowTransmissionMacIndex, espnowTransmissionMacIndex + 5] (6 bytes) with the MAC address of the WiFi AP interface. + // We always transmit from the station interface (due to using ESP_NOW_ROLE_CONTROLLER), so this makes it possible to always know both interface MAC addresses of a node that sends a transmission. + WiFi.softAPmacAddress(transmission + espnowTransmissionMacIndex); + + espnowSetMessageID(transmission, messageID); + + ////// Fill message bytes ////// + + int32_t transmissionStartIndex = (transmissionsRequired - transmissionsRemaining - 1) * getMaxMessageBytesPerTransmission(); + std::copy_n(message.substring(transmissionStartIndex, transmissionStartIndex + transmissionSize - sizeOfProtocolBytes).c_str(), + transmissionSize - sizeOfProtocolBytes, transmission + sizeOfProtocolBytes); + + ////// Transmit ////// + + _espnowSendConfirmed = false; + uint32_t transmissionStartTime = millis(); + + while(!_espnowSendConfirmed && millis() - transmissionStartTime < getEspnowTransmissionTimeout()) + { + if(esp_now_send(_transmissionTargetBSSID, transmission, transmissionSize) == 0) // == 0 => Success + { + uint32_t transmissionAttemptStart = millis(); + while(!_espnowSendConfirmed + && (millis() - transmissionAttemptStart < getEspnowRetransmissionInterval()) + && (millis() - transmissionStartTime < getEspnowTransmissionTimeout())) + { + delay(1); // Note that callbacks can be called during delay time, so it is possible to receive a transmission during this delay. + } + } + + if(_espnowSendConfirmed) + { + if(messageStart) + { + if(encryptedConnection && !usesConstantSessionKey(messageType) && encryptedConnection->getOwnSessionKey() == messageID) + { + encryptedConnection->setDesync(false); + encryptedConnection->incrementOwnSessionKey(); + } + + messageStart = false; + } + + break; + } + } + + if(!_espnowSendConfirmed) + { + _transmissionsFailed++; + + staticVerboseModePrint("espnowSendToNode failed!"); + staticVerboseModePrint("Transmission #: " + String(transmissionsRequired - transmissionsRemaining) + "/" + String(transmissionsRequired)); + staticVerboseModePrint("Transmission fail rate (up) " + String(getTransmissionFailRate())); + + if(messageStart && encryptedConnection && !usesConstantSessionKey(messageType) && encryptedConnection->getOwnSessionKey() == messageID) + encryptedConnection->setDesync(true); + + return TS_TRANSMISSION_FAILED; + } + + transmissionsRemaining--; // This is used when transfering multi-transmission messages. + + } while(transmissionsRemaining >= 0); + + // Useful when debugging the protocol + //staticVerboseModePrint("Sent to Mac: " + macToString(_transmissionTargetBSSID) + " ID: " + uint64ToString(messageID)); + + return TS_TRANSMISSION_COMPLETE; +} + +transmission_status_t EspnowMeshBackend::sendRequest(const String &message, const uint8_t *targetBSSID) +{ + transmission_status_t transmissionStatus = espnowSendToNode(message, targetBSSID, 'Q', this); + + return transmissionStatus; +} + +transmission_status_t EspnowMeshBackend::sendResponse(const String &message, uint64_t requestID, const uint8_t *targetBSSID) +{ + EncryptedConnectionLog *encryptedConnection = getEncryptedConnection(targetBSSID); + uint8_t encryptedMac[6] {0}; + + if(encryptedConnection) + { + encryptedConnection->getEncryptedPeerMac(encryptedMac); + } + + return espnowSendToNodeUnsynchronized(message, encryptedConnection ? encryptedMac : targetBSSID, 'A', requestID, this); +} + +bool EspnowMeshBackend::transmissionInProgress(){return _espnowTransmissionMutex;} + +EspnowMeshBackend::macAndType_td EspnowMeshBackend::createMacAndTypeValue(uint64_t uint64Mac, char messageType) +{ + return static_cast(uint64Mac << 8 | (uint64_t)messageType); +} + +uint64_t EspnowMeshBackend::macAndTypeToUint64Mac(const macAndType_td &macAndTypeValue) +{ + return static_cast(macAndTypeValue) >> 8; +} + +void EspnowMeshBackend::espnowReceiveCallback(uint8_t *macaddr, uint8_t *dataArray, uint8_t len) +{ + using namespace EspnowProtocolInterpreter; + + ////// ////// + /* + if(messageStart) + { + storeTransmission + } + else + { + if(messageFound) + storeTransmission or (erase and return) + else + return + } + + if(transmissionsRemaining != 0) + return + + processMessage + */ + ////// ////// + + char messageType = espnowGetMessageType(dataArray); + uint8_t transmissionsRemaining = espnowGetTransmissionsRemaining(dataArray); + uint64_t uint64Mac = macToUint64(macaddr); + + // The MAC is 6 bytes so two bytes of uint64Mac are free. We must include the messageType there since it is possible that we will + // receive both a request and a response that shares the same messageID from the same uint64Mac, being distinguished only by the messageType. + // This would otherwise potentially cause the request and response to be mixed into one message when they are multi-part transmissions sent roughly at the same time. + macAndType_td macAndType = createMacAndTypeValue(uint64Mac, messageType); + uint64_t messageID = espnowGetMessageID(dataArray); + + //uint32_t methodStart = millis(); + + if(espnowIsMessageStart(dataArray)) + { + // Does nothing if key already in receivedEspnowTransmissions + receivedEspnowTransmissions.insert(std::make_pair(std::make_pair(macAndType, messageID), MessageData(dataArray, len))); + } + else + { + std::map, MessageData>::iterator storedMessageIterator = receivedEspnowTransmissions.find(std::make_pair(macAndType, messageID)); + + if(storedMessageIterator != receivedEspnowTransmissions.end()) // If we have not stored the key already, we missed the first message part. + { + if(!storedMessageIterator->second.addToMessage(dataArray, len)) + { + // If we received the wrong message part, remove the whole message if we have missed a part. + // Otherwise just ignore the received part since it has already been stored. + + uint8_t transmissionsRemainingExpected = storedMessageIterator->second.getTransmissionsRemaining() - 1; + + if(transmissionsRemaining < transmissionsRemainingExpected) + { + receivedEspnowTransmissions.erase(storedMessageIterator); + return; + } + } + } + else + { + return; + } + } + + //Serial.println("methodStart storage done " + String(millis() - methodStart)); + + if(transmissionsRemaining != 0) + { + return; + } + + std::map, MessageData>::iterator storedMessageIterator = receivedEspnowTransmissions.find(std::make_pair(macAndType, messageID)); + assert(storedMessageIterator != receivedEspnowTransmissions.end()); + + // Copy totalMessage in case user callbacks (request/responseHandler) do something odd with receivedEspnowTransmissions list. + String totalMessage = storedMessageIterator->second.getTotalMessage(); // https://stackoverflow.com/questions/134731/returning-a-const-reference-to-an-object-instead-of-a-copy It is likely that most compilers will perform Named Value Return Value Optimisation in this case + + receivedEspnowTransmissions.erase(storedMessageIterator); // Erase the extra copy of the totalMessage, to save RAM. + + //Serial.println("methodStart erase done " + String(millis() - methodStart)); + + if(messageType == 'Q') // Question (request) + { + storeReceivedRequest(uint64Mac, messageID, TimeTracker(millis())); + //Serial.println("methodStart request stored " + String(millis() - methodStart)); + + setSenderMac(macaddr); + setReceivedEncryptedMessage(usesEncryption(messageID)); + String response = getRequestHandler()(totalMessage, *this); + //Serial.println("methodStart response acquired " + String(millis() - methodStart)); + + if(response.length() > 0) + { + responsesToSend.push_back(ResponseData(response, macaddr, messageID)); + + //Serial.println("methodStart Q done " + String(millis() - methodStart)); + } + } + else if(messageType == 'A') // Answer (response) + { + deleteSentRequest(uint64Mac, messageID); // Request has been answered, so stop accepting new answers about it. + + if(EncryptedConnectionLog *encryptedConnection = getEncryptedConnection(macaddr)) + { + if(encryptedConnection->getOwnSessionKey() == messageID) + { + encryptedConnection->setDesync(false); // We just received an answer to the latest request we sent to the node, so the node sending the answer must now be in sync. + encryptedConnection->incrementOwnSessionKey(); + } + } + + setSenderMac(macaddr); + setReceivedEncryptedMessage(usesEncryption(messageID)); + getResponseHandler()(totalMessage, *this); + } + else + { + assert(messageType == 'Q' || messageType == 'A'); + } + + ESP.wdtFeed(); // Prevents WDT reset in case we receive a lot of transmissions without break. + + //Serial.println("methodStart wdtFeed done " + String(millis() - methodStart)); +} + +void EspnowMeshBackend::setEspnowEncryptionKey(const uint8_t espnowEncryptionKey[espnowEncryptionKeyLength]) +{ + assert(espnowEncryptionKey != nullptr); + + for(int i = 0; i < espnowEncryptionKeyLength; i++) + { + _espnowEncryptionKey[i] = espnowEncryptionKey[i]; + } +} + +const uint8_t *EspnowMeshBackend::getEspnowEncryptionKey() +{ + return _espnowEncryptionKey; +} + +uint8_t *EspnowMeshBackend::getEspnowEncryptionKey(uint8_t resultArray[espnowEncryptionKeyLength]) +{ + std::copy_n(_espnowEncryptionKey, espnowEncryptionKeyLength, resultArray); + return resultArray; +} + +bool EspnowMeshBackend::setEspnowEncryptionKok(uint8_t espnowEncryptionKok[espnowEncryptionKeyLength]) +{ + if(espnowEncryptionKok == nullptr || esp_now_set_kok(espnowEncryptionKok, espnowEncryptionKeyLength)) // esp_now_set_kok failed if not == 0 + return false; + + for(int i = 0; i < espnowEncryptionKeyLength; i++) + { + _espnowEncryptionKok[i] = espnowEncryptionKok[i]; + } + + _espnowEncryptionKokSet = true; + + return true; +} + +const uint8_t *EspnowMeshBackend::getEspnowEncryptionKok() +{ + if(_espnowEncryptionKokSet) + return _espnowEncryptionKok; + else + return nullptr; +} + +void EspnowMeshBackend::setEspnowHashKey(const uint8_t espnowHashKey[espnowHashKeyLength]) +{ + assert(espnowHashKey != nullptr); + + for(int i = 0; i < espnowHashKeyLength; i++) + { + _espnowHashKey[i] = espnowHashKey[i]; + } +} + +const uint8_t *EspnowMeshBackend::getEspnowHashKey() +{ + return _espnowHashKey; +} + +bool EspnowMeshBackend::verifyPeerSessionKey(uint64_t sessionKey, const uint8_t *peerMac, char messageType) +{ + if(EncryptedConnectionLog *encryptedConnection = getEncryptedConnection(peerMac)) + { + return verifyPeerSessionKey(sessionKey, *encryptedConnection, macToUint64(peerMac), messageType); + } + + return false; +} + +bool EspnowMeshBackend::verifyPeerSessionKey(uint64_t sessionKey, EncryptedConnectionLog &encryptedConnection, uint64_t uint64PeerMac, char messageType) +{ + if(EspnowProtocolInterpreter::usesEncryption(sessionKey)) + { + if(sessionKey == encryptedConnection.getPeerSessionKey() + || receivedEspnowTransmissions.find(std::make_pair(createMacAndTypeValue(uint64PeerMac, messageType), sessionKey)) + != receivedEspnowTransmissions.end()) + { + // If sessionKey is correct or sessionKey is one part of a multi-part transmission. + return true; + } + } + + return false; +} + +bool EspnowMeshBackend::synchronizePeerSessionKey(uint64_t sessionKey, const uint8_t *peerMac) +{ + if(EncryptedConnectionLog *encryptedConnection = getEncryptedConnection(peerMac)) + { + return synchronizePeerSessionKey(sessionKey, *encryptedConnection); + } + + return false; +} + +bool EspnowMeshBackend::synchronizePeerSessionKey(uint64_t sessionKey, EncryptedConnectionLog &encryptedConnection) +{ + if(EspnowProtocolInterpreter::usesEncryption(sessionKey)) + { + if(sessionKey == encryptedConnection.getPeerSessionKey()) + { + uint8_t hashKey[espnowHashKeyLength] {0}; + encryptedConnection.setPeerSessionKey(EncryptedConnectionLog::incrementSessionKey(sessionKey, encryptedConnection.getHashKey(hashKey), espnowHashKeyLength)); + return true; + } + } + + return false; +} + +std::list::const_iterator EspnowMeshBackend::getScheduledResponse(uint32_t responseIndex) +{ + assert(responseIndex < numberOfScheduledResponses()); + + bool startFromBeginning = responseIndex < numberOfScheduledResponses()/2; + auto responseIterator = startFromBeginning ? responsesToSend.cbegin() : responsesToSend.cend(); + uint32_t stepsToTarget = startFromBeginning ? responseIndex : numberOfScheduledResponses() - responseIndex; // cend is one element beyond the last + + while(stepsToTarget > 0) + { + startFromBeginning ? ++responseIterator : --responseIterator; + stepsToTarget--; + } + + return responseIterator; +} + +String EspnowMeshBackend::getScheduledResponseMessage(uint32_t responseIndex) +{ + return getScheduledResponse(responseIndex)->getMessage(); +} + +const uint8_t *EspnowMeshBackend::getScheduledResponseRecipient(uint32_t responseIndex) +{ + return getScheduledResponse(responseIndex)->getRecipientMac(); +} + +void EspnowMeshBackend::clearAllScheduledResponses() +{ + responsesToSend.clear(); +} + +uint32_t EspnowMeshBackend::numberOfScheduledResponses() {return responsesToSend.size();} + + +void EspnowMeshBackend::deleteScheduledResponsesByRecipient(const uint8_t *recipientMac, bool encryptedOnly) +{ + for(auto responseIterator = responsesToSend.begin(); responseIterator != responsesToSend.end(); ) + { + if(macEqual(responseIterator->getRecipientMac(), recipientMac) && (!encryptedOnly || EspnowProtocolInterpreter::usesEncryption(responseIterator->getRequestID()))) + { + responseIterator = responsesToSend.erase(responseIterator); + } + else + ++responseIterator; + } +} + +void EspnowMeshBackend::setSenderMac(uint8_t *macArray) +{ + std::copy_n(macArray, 6, _senderMac); +} + +String EspnowMeshBackend::getSenderMac() {return macToString(_senderMac);} +uint8_t *EspnowMeshBackend::getSenderMac(uint8_t *macArray) +{ + std::copy_n(_senderMac, 6, macArray); + return macArray; +} + +void EspnowMeshBackend::setReceivedEncryptedMessage(bool receivedEncryptedMessage) { _receivedEncryptedMessage = receivedEncryptedMessage; } +bool EspnowMeshBackend::receivedEncryptedMessage() {return _receivedEncryptedMessage;} + +encrypted_connection_status_t EspnowMeshBackend::addEncryptedConnection(uint8_t *peerStaMac, uint8_t *peerApMac, uint64_t peerSessionKey, uint64_t ownSessionKey) +{ + assert(encryptedConnections.size() <= maxEncryptedConnections); // If this is not the case, ESP-NOW is no longer in sync with the library + + uint8_t encryptionKeyArray[espnowEncryptionKeyLength] = { 0 }; + + if(EncryptedConnectionLog *encryptedConnection = getEncryptedConnection(peerStaMac)) + { + // Encrypted connection with MAC already exists, so no need to replace it, just updating is enough. + temporaryEncryptedConnectionToPermanent(peerStaMac); + encryptedConnection->setPeerSessionKey(peerSessionKey); + encryptedConnection->setOwnSessionKey(ownSessionKey); + esp_now_set_peer_key(peerStaMac, getEspnowEncryptionKey(encryptionKeyArray), espnowEncryptionKeyLength); + encryptedConnection->setHashKey(getEspnowHashKey()); + + return ECS_CONNECTION_ESTABLISHED; + } + + if(encryptedConnections.size() == maxEncryptedConnections) + { + // No capacity for more encrypted connections. + return ECS_MAX_CONNECTIONS_REACHED_SELF; + } + // int esp_now_add_peer(u8 *mac_addr, u8 role, u8 channel, u8 *key, u8 key_len), returns 0 on success + // Only MAC, encryption key and key length (16) actually matter. The rest is not used by ESP-NOW. + else if(0 == esp_now_add_peer(peerStaMac, ESP_NOW_ROLE_CONTROLLER, getWiFiChannel(), getEspnowEncryptionKey(encryptionKeyArray), espnowEncryptionKeyLength)) + { + encryptedConnections.emplace_back(peerStaMac, peerApMac, peerSessionKey, ownSessionKey, getEspnowHashKey()); + return ECS_CONNECTION_ESTABLISHED; + } + else + { + return ECS_API_CALL_FAILED; + } +} + +encrypted_connection_status_t EspnowMeshBackend::addEncryptedConnection(const String &serializedConnectionState, bool ignoreDuration) +{ + uint32_t duration = 0; + bool desync = false; + uint64_t ownSessionKey = 0; + uint64_t peerSessionKey = 0; + uint8_t peerStaMac[6] = { 0 }; + uint8_t peerApMac[6] = { 0 }; + + if(JsonTranslator::getDesync(serializedConnectionState, desync) + && JsonTranslator::getOwnSessionKey(serializedConnectionState, ownSessionKey) && JsonTranslator::getPeerSessionKey(serializedConnectionState, peerSessionKey) + && JsonTranslator::getPeerStaMac(serializedConnectionState, peerStaMac) && JsonTranslator::getPeerApMac(serializedConnectionState, peerApMac)) + { + encrypted_connection_status_t result = ECS_API_CALL_FAILED; + + if(!ignoreDuration && JsonTranslator::getDuration(serializedConnectionState, duration)) + { + result = addTemporaryEncryptedConnection(peerStaMac, peerApMac, peerSessionKey, ownSessionKey, duration); + } + else + { + result = addEncryptedConnection(peerStaMac, peerApMac, peerSessionKey, ownSessionKey); + } + + if(result == ECS_CONNECTION_ESTABLISHED) + { + EncryptedConnectionLog *encryptedConnection = getEncryptedConnection(peerStaMac); + encryptedConnection->setDesync(desync); + } + + return result; + } + else + { + return ECS_REQUEST_TRANSMISSION_FAILED; + } +} + +encrypted_connection_status_t EspnowMeshBackend::addTemporaryEncryptedConnection(uint8_t *peerStaMac, uint8_t *peerApMac, uint64_t peerSessionKey, uint64_t ownSessionKey, uint32_t duration) +{ + assert(encryptedConnections.size() <= maxEncryptedConnections); // If this is not the case, ESP-NOW is no longer in sync with the library + + uint8_t encryptionKeyArray[espnowEncryptionKeyLength] = { 0 }; + + connectionLogIterator encryptedConnection = connectionLogEndIterator(); + + if(getEncryptedConnectionIterator(peerStaMac, encryptedConnection)) + { + // There is already an encrypted connection to this mac, so no need to replace it, just updating is enough. + encryptedConnection->setPeerSessionKey(peerSessionKey); + encryptedConnection->setOwnSessionKey(ownSessionKey); + esp_now_set_peer_key(peerStaMac, getEspnowEncryptionKey(encryptionKeyArray), espnowEncryptionKeyLength); + encryptedConnection->setHashKey(getEspnowHashKey()); + + if(encryptedConnection->temporary()) + { + encryptedConnection->setRemainingDuration(duration); + } + + return ECS_CONNECTION_ESTABLISHED; + } + + encrypted_connection_status_t result = addEncryptedConnection(peerStaMac, peerApMac, peerSessionKey, ownSessionKey); + + if(result == ECS_CONNECTION_ESTABLISHED) + { + if(!getEncryptedConnectionIterator(peerStaMac, encryptedConnection)) + assert(false && "No connection found despite being added in addTemporaryEncryptedConnection."); + + encryptedConnection->setRemainingDuration(duration); + } + + return result; +} + +encrypted_connection_status_t EspnowMeshBackend::addTemporaryEncryptedConnection(const String &serializedConnectionState, uint32_t duration) +{ + bool desync = false; + uint64_t ownSessionKey = 0; + uint64_t peerSessionKey = 0; + uint8_t peerStaMac[6] = { 0 }; + uint8_t peerApMac[6] = { 0 }; + + if(JsonTranslator::getDesync(serializedConnectionState, desync) + && JsonTranslator::getOwnSessionKey(serializedConnectionState, ownSessionKey) && JsonTranslator::getPeerSessionKey(serializedConnectionState, peerSessionKey) + && JsonTranslator::getPeerStaMac(serializedConnectionState, peerStaMac) && JsonTranslator::getPeerApMac(serializedConnectionState, peerApMac)) + { + encrypted_connection_status_t result = addTemporaryEncryptedConnection(peerStaMac, peerApMac, peerSessionKey, ownSessionKey, duration); + + if(result == ECS_CONNECTION_ESTABLISHED) + { + EncryptedConnectionLog *encryptedConnection = getEncryptedConnection(peerStaMac); + encryptedConnection->setDesync(desync); + } + + return result; + } + else + { + return ECS_REQUEST_TRANSMISSION_FAILED; + } +} + +void EspnowMeshBackend::handlePostponedRemovals() +{ + MutexTracker mutexTracker(_espnowTransmissionMutex); + if(!mutexTracker.mutexCaptured()) + { + assert(false && "ERROR! Transmission in progress. Don't call handlePostponedRemovals from callbacks as this may corrupt program state! Aborting."); + return; + } + + if(EncryptedConnectionLog::newRemovalsScheduled()) + { + updateTemporaryEncryptedConnections(true); + } +} + +encrypted_connection_status_t EspnowMeshBackend::requestEncryptedConnectionKernel(uint8_t *peerMac, const encryptionRequestBuilderType &encryptionRequestBuilder) +{ + using namespace EspnowProtocolInterpreter; + + assert(encryptedConnections.size() <= maxEncryptedConnections); // If this is not the case, ESP-NOW is no longer in sync with the library + + MutexTracker mutexTracker(_espnowTransmissionMutex, handlePostponedRemovals); + if(!mutexTracker.mutexCaptured()) + { + assert(false && "ERROR! Transmission in progress. Don't call requestEncryptedConnection from callbacks as this may corrupt program state! Aborting."); + return ECS_REQUEST_TRANSMISSION_FAILED; + } + + EncryptedConnectionLog *existingEncryptedConnection = getEncryptedConnection(peerMac); + ExpiringTimeTracker existingTimeTracker = existingEncryptedConnection && existingEncryptedConnection->temporary() ? + *existingEncryptedConnection->temporary() : ExpiringTimeTracker(0); + + if(!existingEncryptedConnection && encryptedConnections.size() >= maxEncryptedConnections) + { + assert(encryptedConnections.size() == maxEncryptedConnections); + + // No capacity for more encrypted connections. + return ECS_MAX_CONNECTIONS_REACHED_SELF; + } + + String requestNonce = macToString(peerMac) + uint64ToString(randomUint64()) + uint64ToString(randomUint64()); + _ongoingPeerRequestResult = ECS_REQUEST_TRANSMISSION_FAILED; + _ongoingPeerRequestNonce = requestNonce; + _ongoingPeerRequester = this; + String requestMessage = encryptionRequestBuilder(requestNonce, existingTimeTracker); + + verboseModePrint("Sending encrypted connection request to: " + macToString(peerMac)); + + if(espnowSendToNode(requestMessage, peerMac, 'P') == TS_TRANSMISSION_COMPLETE) + { + uint32_t startTime = millis(); + + // _ongoingPeerRequestNonce is set to "" when a peer confirmation response from the mac is received + while(millis() - startTime < getEncryptionRequestTimeout() && _ongoingPeerRequestNonce != "") + { + delay(1); + } + } + + if(_ongoingPeerRequestNonce != "") + { + // If nonce != "" we only received the basic connection info, so the pairing process is incomplete + _ongoingPeerRequestResult = ECS_REQUEST_TRANSMISSION_FAILED; + _ongoingPeerRequestNonce = ""; + } + else if(_ongoingPeerRequestResult == ECS_CONNECTION_ESTABLISHED) + { + requestMessage = encryptionRequestBuilder(requestNonce, existingTimeTracker); // Give the builder a chance to update the message + + int32_t messageHeaderEndIndex = requestMessage.indexOf(':'); + String messageHeader = requestMessage.substring(0, messageHeaderEndIndex + 1); + String messageBody = requestMessage.substring(messageHeaderEndIndex + 1); + + // If we do not get an ack within getEncryptionRequestTimeout() the peer has probably had the time to delete the temporary encrypted connection. + if(espnowSendToNode(encryptedConnectionVerificationHeader + requestMessage, peerMac, 'P') == TS_TRANSMISSION_COMPLETE + && millis() - _ongoingPeerRequestEncryptionStart < getEncryptionRequestTimeout()) + { + EncryptedConnectionLog *encryptedConnection = getEncryptedConnection(peerMac); + if(!encryptedConnection || encryptedConnection->removalScheduled()) + { + assert(encryptedConnection && "requestEncryptedConnectionKernel cannot find an encrypted connection!"); + // requestEncryptedConnectionRemoval received. + _ongoingPeerRequestResult = ECS_REQUEST_TRANSMISSION_FAILED; + } + else + { + // Finalize connection + if(messageHeader == encryptionRequestHeader) + { + temporaryEncryptedConnectionToPermanent(peerMac); + } + else if(messageHeader == temporaryEncryptionRequestHeader) + { + if(!existingEncryptedConnection || existingEncryptedConnection->temporary()) + { + // Should not change duration of existing permanent connections. + uint32_t connectionDuration = 0; + bool durationFound = JsonTranslator::getDuration(messageBody, connectionDuration); + assert(durationFound); + encryptedConnection->setRemainingDuration(connectionDuration); + } + } + else + { + assert(false && "Unknown messageHeader during encrypted connection finalization!"); + _ongoingPeerRequestResult = ECS_API_CALL_FAILED; + } + } + } + else + { + _ongoingPeerRequestResult = ECS_REQUEST_TRANSMISSION_FAILED; + } + } + + if(_ongoingPeerRequestResult != ECS_CONNECTION_ESTABLISHED) + { + if(!existingEncryptedConnection) + { + // Remove any connection that was added during the request attempt. + removeEncryptedConnectionUnprotected(peerMac); + } + } + + _ongoingPeerRequester = nullptr; + + return _ongoingPeerRequestResult; +} + +String EspnowMeshBackend::defaultEncryptionRequestBuilder(const String &requestHeader, const uint32_t durationMs, + const String &requestNonce, const ExpiringTimeTracker &existingTimeTracker) +{ + using namespace JsonTranslator; + using EspnowProtocolInterpreter::temporaryEncryptionRequestHeader; + + (void)existingTimeTracker; // This removes a "unused parameter" compiler warning. Does nothing else. + + String requestMessage = ""; + + if(requestHeader == temporaryEncryptionRequestHeader) + { + requestMessage += createEncryptionRequestIntro(requestHeader, durationMs); + } + else + { + requestMessage += createEncryptionRequestIntro(requestHeader); + } + + requestMessage += createEncryptionRequestEnding(requestNonce); + + return requestMessage; +} + +String EspnowMeshBackend::flexibleEncryptionRequestBuilder(const uint32_t minDurationMs, const String &requestNonce, const ExpiringTimeTracker &existingTimeTracker) +{ + using namespace JsonTranslator; + using EspnowProtocolInterpreter::temporaryEncryptionRequestHeader; + + uint32_t connectionDuration = minDurationMs >= existingTimeTracker.remainingDuration() ? + minDurationMs : existingTimeTracker.remainingDuration(); + + return createEncryptionRequestIntro(temporaryEncryptionRequestHeader, connectionDuration) + createEncryptionRequestEnding(requestNonce); +} + +encrypted_connection_status_t EspnowMeshBackend::requestEncryptedConnection(uint8_t *peerMac) +{ + using namespace std::placeholders; + return requestEncryptedConnectionKernel(peerMac, std::bind(defaultEncryptionRequestBuilder, EspnowProtocolInterpreter::encryptionRequestHeader, 0, _1, _2)); +} + +encrypted_connection_status_t EspnowMeshBackend::requestTemporaryEncryptedConnection(uint8_t *peerMac, uint32_t durationMs) +{ + using namespace std::placeholders; + return requestEncryptedConnectionKernel(peerMac, std::bind(defaultEncryptionRequestBuilder, EspnowProtocolInterpreter::temporaryEncryptionRequestHeader, durationMs, _1, _2)); +} + +encrypted_connection_status_t EspnowMeshBackend::requestFlexibleTemporaryEncryptedConnection(uint8_t *peerMac, uint32_t minDurationMs) +{ + using namespace std::placeholders; + return requestEncryptedConnectionKernel(peerMac, std::bind(flexibleEncryptionRequestBuilder, minDurationMs, _1, _2)); +} + +bool EspnowMeshBackend::temporaryEncryptedConnectionToPermanent(uint8_t *peerMac) +{ + if(EncryptedConnectionLog *temporaryConnection = getTemporaryEncryptedConnection(peerMac)) + { + temporaryConnection->removeDuration(); + return true; + } + else + { + return false; + } +} + +encrypted_connection_removal_outcome_t EspnowMeshBackend::removeEncryptedConnection(uint8_t *peerMac) +{ + auto connectionIterator = getEncryptedConnectionIterator(peerMac, encryptedConnections); + if(connectionIterator != encryptedConnections.end()) + { + MutexTracker mutexTracker(_espnowTransmissionMutex); + if(!mutexTracker.mutexCaptured()) + { + // We should not remove an encrypted connection while there is a transmission in progress, since that may cause encrypted data to be sent unencrypted. + // Thus when a transmission is in progress we just schedule the encrypted connection for removal, so it will be removed during the next updateTemporaryEncryptedConnections() call. + connectionIterator->scheduleForRemoval(); + return ECRO_REMOVAL_SCHEDULED; + } + else + { + return removeEncryptedConnectionUnprotected(peerMac); + } + } + else + { + // peerMac is already removed + return ECRO_REMOVAL_SUCCEEDED; + } +} + +encrypted_connection_removal_outcome_t EspnowMeshBackend::removeEncryptedConnectionUnprotected(uint8_t *peerMac, std::vector::iterator *resultingIterator) +{ + connectionLogIterator connectionIterator = getEncryptedConnectionIterator(peerMac, encryptedConnections); + return removeEncryptedConnectionUnprotected(connectionIterator, resultingIterator); +} + +encrypted_connection_removal_outcome_t EspnowMeshBackend::removeEncryptedConnectionUnprotected(connectionLogIterator &connectionIterator, std::vector::iterator *resultingIterator) +{ + assert(encryptedConnections.size() <= maxEncryptedConnections); // If this is not the case, ESP-NOW is no longer in sync with the library + + if(connectionIterator != connectionLogEndIterator()) + { + uint8_t encryptedMac[6] {0}; + connectionIterator->getEncryptedPeerMac(encryptedMac); + staticVerboseModePrint("Removing connection " + macToString(encryptedMac) + "... ", false); + bool removalSucceeded = esp_now_del_peer(encryptedMac) == 0; + + if(removalSucceeded) + { + if(resultingIterator != nullptr) + { + *resultingIterator = encryptedConnections.erase(connectionIterator); + } + else + { + encryptedConnections.erase(connectionIterator); + } + staticVerboseModePrint("Removal succeeded"); + + // Not deleting encrypted responses here would cause them to be sent unencrypted, + // exposing the peer session key which can be misused later if the encrypted connection is re-established. + deleteScheduledResponsesByRecipient(encryptedMac, true); + + // Not deleting these entries here may cause issues if the encrypted connection is quickly re-added + // and happens to get the same session keys as before (e.g. requestReceived() could then give false positives). + deleteEntriesByMac(receivedEspnowTransmissions, encryptedMac, true); + deleteEntriesByMac(sentRequests, encryptedMac, true); + deleteEntriesByMac(receivedRequests, encryptedMac, true); + + return ECRO_REMOVAL_SUCCEEDED; + } + else + { + staticVerboseModePrint("Removal failed"); + return ECRO_REMOVAL_FAILED; + } + } + else + { + // connection is already removed + return ECRO_REMOVAL_SUCCEEDED; + } +} + +template +void EspnowMeshBackend::deleteEntriesByMac(std::map, T> &logEntries, const uint8_t *peerMac, bool encryptedOnly) +{ + bool macFound = false; + + for(typename std::map, T>::iterator entryIterator = logEntries.begin(); + entryIterator != logEntries.end(); ) + { + if(macAndTypeToUint64Mac(entryIterator->first.first) == macToUint64(peerMac)) + { + macFound = true; + + if(!encryptedOnly || EspnowProtocolInterpreter::usesEncryption(entryIterator->first.second)) + { + entryIterator = logEntries.erase(entryIterator); + continue; + } + } + else if(macFound) + { + // Since the map is sorted by MAC, we know here that no more matching MAC will be found. + return; + } + + ++entryIterator; + } +} + +template +void EspnowMeshBackend::deleteEntriesByMac(std::map, T> &logEntries, const uint8_t *peerMac, bool encryptedOnly) +{ + bool macFound = false; + + for(typename std::map, T>::iterator entryIterator = logEntries.begin(); + entryIterator != logEntries.end(); ) + { + if(entryIterator->first.first == macToUint64(peerMac)) + { + macFound = true; + + if(!encryptedOnly || EspnowProtocolInterpreter::usesEncryption(entryIterator->first.second)) + { + entryIterator = logEntries.erase(entryIterator); + continue; + } + } + else if(macFound) + { + // Since the map is sorted by MAC, we know here that no more matching MAC will be found. + return; + } + + ++entryIterator; + } +} + +encrypted_connection_removal_outcome_t EspnowMeshBackend::requestEncryptedConnectionRemoval(uint8_t *peerMac) +{ + using EspnowProtocolInterpreter::encryptedConnectionRemovalRequestHeader; + + assert(encryptedConnections.size() <= maxEncryptedConnections); // If this is not the case, ESP-NOW is no longer in sync with the library + + MutexTracker mutexTracker(_espnowTransmissionMutex, handlePostponedRemovals); + if(!mutexTracker.mutexCaptured()) + { + assert(false && "ERROR! Transmission in progress. Don't call requestEncryptedConnectionRemoval from callbacks as this may corrupt program state! Aborting."); + return ECRO_REMOVAL_REQUEST_FAILED; + } + + if(EncryptedConnectionLog *encryptedConnection = getEncryptedConnection(peerMac)) + { + if(espnowSendToNode(encryptedConnectionRemovalRequestHeader, peerMac, 'P') == TS_TRANSMISSION_COMPLETE) + { + return removeEncryptedConnectionUnprotected(peerMac); + } + else + { + if(encryptedConnection->removalScheduled()) + return ECRO_REMOVAL_SUCCEEDED; // Removal will be completed by mutex destructorHook. + else + return ECRO_REMOVAL_REQUEST_FAILED; + } + } + else + { + // peerMac is already removed + return ECRO_REMOVAL_SUCCEEDED; + } +} + +void EspnowMeshBackend::setAcceptsUnencryptedRequests(bool acceptsUnencryptedRequests) { _acceptsUnencryptedRequests = acceptsUnencryptedRequests; } +bool EspnowMeshBackend::acceptsUnencryptedRequests() { return _acceptsUnencryptedRequests; } + +template +typename std::vector::iterator EspnowMeshBackend::getEncryptedConnectionIterator(const uint8_t *peerMac, typename std::vector &connectionVector) +{ + typename std::vector::iterator connectionIterator = connectionVector.begin(); + + while(connectionIterator != connectionVector.end()) + { + if(connectionIterator->connectedTo(peerMac)) + break; + else + ++connectionIterator; + } + + return connectionIterator; +} + +EspnowMeshBackend::connectionLogIterator EspnowMeshBackend::connectionLogEndIterator() +{ + return encryptedConnections.end(); +} + +bool EspnowMeshBackend::getEncryptedConnectionIterator(const uint8_t *peerMac, connectionLogIterator &iterator) +{ + connectionLogIterator result = getEncryptedConnectionIterator(peerMac, encryptedConnections); + + if(result != connectionLogEndIterator()) + { + iterator = result; + return true; + } + else + { + return false; + } +} + +bool EspnowMeshBackend::getTemporaryEncryptedConnectionIterator(const uint8_t *peerMac, connectionLogIterator &iterator) +{ + connectionLogIterator result = connectionLogEndIterator(); + + if(getEncryptedConnectionIterator(peerMac, result) && result->temporary()) + { + iterator = result; + return true; + } + else + { + return false; + } +} + +EncryptedConnectionLog *EspnowMeshBackend::getEncryptedConnection(const uint8_t *peerMac) +{ + auto connectionIterator = getEncryptedConnectionIterator(peerMac, encryptedConnections); + if(connectionIterator != encryptedConnections.end()) + { + return &(*connectionIterator); + } + else + { + return nullptr; + } +} + +EncryptedConnectionLog *EspnowMeshBackend::getTemporaryEncryptedConnection(const uint8_t *peerMac) +{ + connectionLogIterator connectionIterator = connectionLogEndIterator(); + if(getTemporaryEncryptedConnectionIterator(peerMac, connectionIterator)) + { + return &(*connectionIterator); + } + else + { + return nullptr; + } +} + + +uint8_t *EspnowMeshBackend::getEncryptedMac(const uint8_t *peerMac, uint8_t *resultArray) +{ + if(EncryptedConnectionLog *encryptedConnection = getEncryptedConnection(peerMac)) + { + return encryptedConnection->getEncryptedPeerMac(resultArray); + } + else + { + return nullptr; + } +} + +void EspnowMeshBackend::attemptTransmission(const String &message, bool scan, bool scanAllWiFiChannels) +{ + MutexTracker mutexTracker(_espnowTransmissionMutex, handlePostponedRemovals); + if(!mutexTracker.mutexCaptured()) + { + assert(false && "ERROR! Transmission in progress. Don't call attemptTransmission from callbacks as this may corrupt program state! Aborting."); + return; + } + + setMessage(message); + + latestTransmissionOutcomes.clear(); + + if(scan) + { + scanForNetworks(scanAllWiFiChannels); + } + + for(NetworkInfo ¤tNetwork : connectionQueue) + { + String currentSSID = ""; + int currentWiFiChannel = NETWORK_INFO_DEFAULT_INT; + uint8_t *currentBSSID = NULL; + + // If a BSSID has been assigned, it is prioritized over an assigned networkIndex since the networkIndex is more likely to change. + if(currentNetwork.BSSID != NULL) + { + currentSSID = currentNetwork.SSID; + currentWiFiChannel = currentNetwork.wifiChannel; + currentBSSID = currentNetwork.BSSID; + } + else // Use only networkIndex + { + currentSSID = WiFi.SSID(currentNetwork.networkIndex); + currentWiFiChannel = WiFi.channel(currentNetwork.networkIndex); + currentBSSID = WiFi.BSSID(currentNetwork.networkIndex); + } + + if(verboseMode()) // Avoid string generation if not required + { + printAPInfo(currentNetwork.networkIndex, currentSSID, currentWiFiChannel); + verboseModePrint(F("")); + } + + uint32_t transmissionStartTime = millis(); + transmission_status_t transmissionResult = sendRequest(getMessage(), currentBSSID); + + uint32_t transmissionDuration = millis() - transmissionStartTime; + + if(verboseMode() && transmissionResult == TS_TRANSMISSION_COMPLETE) // Avoid calculations if not required + { + totalDurationWhenSuccessful_AT += transmissionDuration; + successfulTransmissions_AT++; + if(transmissionDuration > maxTransmissionDuration_AT) + { + maxTransmissionDuration_AT = transmissionDuration; + } + } + + latestTransmissionOutcomes.push_back(TransmissionResult{.origin = currentNetwork, .transmissionStatus = transmissionResult}); + } + + if(verboseMode() && successfulTransmissions_AT > 0) // Avoid calculations if not required + { + verboseModePrint("Average duration of successful transmissions: " + String(totalDurationWhenSuccessful_AT/successfulTransmissions_AT) + " ms."); + verboseModePrint("Maximum duration of successful transmissions: " + String(maxTransmissionDuration_AT) + " ms."); + } + else + { + verboseModePrint("No successful transmission."); + } +} + +void EspnowMeshBackend::attemptAutoEncryptingTransmission(const String &message, bool scan, bool scanAllWiFiChannels) +{ + MutexTracker outerMutexTracker(_espnowTransmissionMutex, handlePostponedRemovals); + if(!outerMutexTracker.mutexCaptured()) + { + assert(false && "ERROR! Transmission in progress. Don't call attemptTransmission from callbacks as this may corrupt program state! Aborting."); + return; + } + + setMessage(message); + + latestTransmissionOutcomes.clear(); + + if(scan) + { + scanForNetworks(scanAllWiFiChannels); + } + + outerMutexTracker.releaseMutex(); + + for(NetworkInfo ¤tNetwork : connectionQueue) + { + MutexTracker innerMutexTracker = MutexTracker(_espnowTransmissionMutex); + if(!innerMutexTracker.mutexCaptured()) + { + assert(false && "ERROR! Unable to recapture Mutex in attemptAutoEncryptingTransmission. Aborting."); + return; + } + + String currentSSID = ""; + int currentWiFiChannel = NETWORK_INFO_DEFAULT_INT; + uint8_t *currentBSSID = NULL; + + // If a BSSID has been assigned, it is prioritized over an assigned networkIndex since the networkIndex is more likely to change. + if(currentNetwork.BSSID != NULL) + { + currentSSID = currentNetwork.SSID; + currentWiFiChannel = currentNetwork.wifiChannel; + currentBSSID = currentNetwork.BSSID; + } + else // Use only networkIndex + { + currentSSID = WiFi.SSID(currentNetwork.networkIndex); + currentWiFiChannel = WiFi.channel(currentNetwork.networkIndex); + currentBSSID = WiFi.BSSID(currentNetwork.networkIndex); + } + + if(verboseMode()) // Avoid string generation if not required + { + printAPInfo(currentNetwork.networkIndex, currentSSID, currentWiFiChannel); + verboseModePrint(F("")); + } + + EncryptedConnectionLog *encryptedConnection = getEncryptedConnection(currentBSSID); + encrypted_connection_status_t connectionStatus = ECS_MAX_CONNECTIONS_REACHED_SELF; + + innerMutexTracker.releaseMutex(); + + connectionStatus = requestFlexibleTemporaryEncryptedConnection(currentBSSID, getAutoEncryptionDuration()); + + innerMutexTracker = MutexTracker(_espnowTransmissionMutex); + if(!innerMutexTracker.mutexCaptured()) + { + assert(false && "ERROR! Unable to recapture Mutex in attemptAutoEncryptingTransmission. Aborting."); + return; + } + + if(connectionStatus == ECS_CONNECTION_ESTABLISHED) + { + uint32_t transmissionStartTime = millis(); + transmission_status_t transmissionResult = sendRequest(getMessage(), currentBSSID); + + uint32_t transmissionDuration = millis() - transmissionStartTime; + + if(verboseMode() && transmissionResult == TS_TRANSMISSION_COMPLETE) // Avoid calculations if not required + { + totalDurationWhenSuccessful_AT += transmissionDuration; + successfulTransmissions_AT++; + if(transmissionDuration > maxTransmissionDuration_AT) + { + maxTransmissionDuration_AT = transmissionDuration; + } + } + + latestTransmissionOutcomes.push_back(TransmissionResult{.origin = currentNetwork, .transmissionStatus = transmissionResult}); + } + else + { + latestTransmissionOutcomes.push_back(TransmissionResult{.origin = currentNetwork, .transmissionStatus = TS_CONNECTION_FAILED}); + } + + if(!encryptedConnection) + { + // Remove any connection that was added during the transmission attempt. + removeEncryptedConnectionUnprotected(currentBSSID); + } + } + + if(verboseMode() && successfulTransmissions_AT > 0) // Avoid calculations if not required + { + verboseModePrint("Average duration of successful transmissions: " + String(totalDurationWhenSuccessful_AT/successfulTransmissions_AT) + " ms."); + verboseModePrint("Maximum duration of successful transmissions: " + String(maxTransmissionDuration_AT) + " ms."); + } + else + { + verboseModePrint("No successful transmission."); + } +} + +void EspnowMeshBackend::sendEspnowResponses() +{ + //uint32_t startTime = millis(); + + uint32_t bufferedCriticalHeapLevel = criticalHeapLevel() + criticalHeapLevelBuffer(); // We preferably want to start clearing the logs a bit before things get critical. + + for(std::list::iterator confirmationsIterator = peerRequestConfirmationsToSend.begin(); confirmationsIterator != peerRequestConfirmationsToSend.end(); ) + { + using namespace EspnowProtocolInterpreter; + + auto timeTrackerPointer = confirmationsIterator->temporary(); + assert(timeTrackerPointer); // peerRequestConfirmations should always expire and so should always have a timeTracker + if(timeTrackerPointer->timeSinceCreation() > getEncryptionRequestTimeout()) + { + ++confirmationsIterator; + continue; + } + + uint8_t defaultBSSID[6] {0}; + confirmationsIterator->getEncryptedPeerMac(defaultBSSID); + uint8_t unencryptedBSSID[6] {0}; + confirmationsIterator->getUnencryptedPeerMac(unencryptedBSSID); + uint8_t hashKey[espnowHashKeyLength] {0}; + confirmationsIterator->getHashKey(hashKey); + + EncryptedConnectionLog *existingEncryptedConnection = getEncryptedConnection(defaultBSSID); + + // If we receive a non-encrypted request for encrypted connection from a node that already exists as an encrypted peer for us we cannot send a response to the encrypted MAC + // since that transmission will then be encrypted and impossible for the request sender to read. Of course, removing the existing encrypted connection would also work, + // but make it very simple for a third party to disrupt an encrypted connection by just sending random requests for encrypted connection. + bool sendToDefaultBSSID = confirmationsIterator->requestEncrypted() || !existingEncryptedConnection; + + // Note that callbacks can be called during delay time, so it is possible to receive a transmission during espnowSendToNode + // (which may add an element to the peerRequestConfirmationsToSend list). + + staticVerboseModePrint("Responding to encrypted connection request from MAC " + macToString(defaultBSSID)); + + if(!existingEncryptedConnection && encryptedConnections.size() >= maxEncryptedConnections) + { + espnowSendToNodeUnsynchronized(JsonTranslator::createEncryptionRequestHmacMessage(maxConnectionsReachedHeader, + confirmationsIterator->getPeerRequestNonce(), hashKey, espnowHashKeyLength), + defaultBSSID, 'C', generateMessageID(nullptr)); // Generates a new message ID to avoid sending encrypted sessionKeys over unencrypted connections. + + confirmationsIterator = peerRequestConfirmationsToSend.erase(confirmationsIterator); + } + else if(espnowSendToNodeUnsynchronized(JsonTranslator::createEncryptionRequestHmacMessage(basicConnectionInfoHeader, + confirmationsIterator->getPeerRequestNonce(), hashKey, espnowHashKeyLength), + sendToDefaultBSSID ? defaultBSSID : unencryptedBSSID, 'C', generateMessageID(nullptr)) // Generates a new message ID to avoid sending encrypted sessionKeys over unencrypted connections. + == TS_TRANSMISSION_COMPLETE) + { + // Try to add encrypted connection. If connection added send confirmation with encryptedConnection->getOwnSessionKey() as session key and C type message (won't increment key). Then proceed with next request (no need to wait for answer). + if(existingEncryptedConnection) + { + if(auto timeTrackerPointer = existingEncryptedConnection->temporary()) + { + if(getEncryptionRequestTimeout() > timeTrackerPointer->remainingDuration()) + { + existingEncryptedConnection->setRemainingDuration(getEncryptionRequestTimeout()); + } + } + } + else if(EspnowMeshBackend *currentEspnowRequestManager = getEspnowRequestManager()) + { + uint8_t staMacArray[6] = { 0 }; + uint8_t apMacArray[6] = { 0 }; + currentEspnowRequestManager->addTemporaryEncryptedConnection(confirmationsIterator->getPeerStaMac(staMacArray), confirmationsIterator->getPeerApMac(apMacArray), + createSessionKey(), createSessionKey(), getEncryptionRequestTimeout()); + existingEncryptedConnection = getEncryptedConnection(defaultBSSID); + } + else + { + warningPrint("WARNING! Ignoring received encrypted connection request since no EspnowRequestManager is assigned."); + } + + if(!existingEncryptedConnection) + { + // Send "node full" message + espnowSendToNodeUnsynchronized(JsonTranslator::createEncryptionRequestHmacMessage(maxConnectionsReachedHeader, + confirmationsIterator->getPeerRequestNonce(), hashKey, espnowHashKeyLength), + defaultBSSID, 'C', generateMessageID(nullptr)); // Generates a new message ID to avoid sending encrypted sessionKeys over unencrypted connections. + } + else + { + delay(1); // Give some time for the peer to add an encrypted connection + + // Send password and keys. + // Probably no need to know which connection type to use, that is stored in request node and will be sent over for finalization. + espnowSendToNodeUnsynchronized(JsonTranslator::createEncryptedConnectionInfo( + confirmationsIterator->getPeerRequestNonce(), confirmationsIterator->getAuthenticationPassword(), + existingEncryptedConnection->getOwnSessionKey(), existingEncryptedConnection->getPeerSessionKey()), + defaultBSSID, 'C', generateMessageID(nullptr)); // Generates a new message ID to avoid sending encrypted sessionKeys over unencrypted connections. + } + + confirmationsIterator = peerRequestConfirmationsToSend.erase(confirmationsIterator); + } + else + { + ++confirmationsIterator; + } + + if(ESP.getFreeHeap() <= bufferedCriticalHeapLevel) + { + // Heap is getting very low, which probably means we are receiving a lot of transmissions while trying to transmit responses. + // Clear all old data to try to avoid running out of memory. + warningPrint("WARNING! Free heap below chosen minimum. Performing emergency log clearing."); + clearOldLogEntries(); + return; // confirmationsIterator may be invalid now. Also, we should give the main loop a chance to respond to the situation. + } + } + + for(std::list::iterator responseIterator = responsesToSend.begin(); responseIterator != responsesToSend.end(); ) + { + if(responseIterator->timeSinceCreation() > logEntryLifetimeMs()) + { + // If the response is older than logEntryLifetimeMs(), the corresponding request log entry has been deleted at the request sender, + // so the request sender will not accept our response any more. + // This probably happens because we have a high transmission activity and more requests coming in than we can handle. + ++responseIterator; + continue; + } + + // Note that callbacks can be called during delay time, so it is possible to receive a transmission during espnowSendToNode + // (which may add an element to the responsesToSend list). + if(espnowSendToNodeUnsynchronized(responseIterator->getMessage(), responseIterator->getRecipientMac(), 'A', responseIterator->getRequestID()) + == TS_TRANSMISSION_COMPLETE) + { + responseIterator = responsesToSend.erase(responseIterator); + } + else + { + ++responseIterator; + } + + if(ESP.getFreeHeap() <= bufferedCriticalHeapLevel) + { + // Heap is getting very low, which probably means we are receiving a lot of transmissions while trying to transmit responses. + // Clear all old data to try to avoid running out of memory. + warningPrint("WARNING! Free heap below chosen minimum. Performing emergency log clearing."); + clearOldLogEntries(); + return; // responseIterator may be invalid now. Also, we should give the main loop a chance to respond to the situation. + } + } +} + +uint32_t EspnowMeshBackend::getMaxBytesPerTransmission() +{ + return _maxBytesPerTransmission; +} + +uint32_t EspnowMeshBackend::getMaxMessageBytesPerTransmission() +{ + return getMaxBytesPerTransmission() - EspnowProtocolInterpreter::espnowProtocolBytesSize(); +} + +void EspnowMeshBackend::setMaxTransmissionsPerMessage(uint8_t maxTransmissionsPerMessage) +{ + assert(1 <= maxTransmissionsPerMessage && maxTransmissionsPerMessage <= 128); + + _maxTransmissionsPerMessage = maxTransmissionsPerMessage; +} + +uint8_t EspnowMeshBackend::getMaxTransmissionsPerMessage() {return _maxTransmissionsPerMessage;} + +uint32_t EspnowMeshBackend::getMaxMessageLength() +{ + return getMaxTransmissionsPerMessage() * getMaxMessageBytesPerTransmission(); +} + +uint8_t EspnowMeshBackend::numberOfEncryptedConnections() +{ + return encryptedConnections.size(); +} + +espnow_connection_type_t EspnowMeshBackend::getConnectionInfoHelper(const EncryptedConnectionLog *encryptedConnection, uint32_t *remainingDuration, uint8_t *peerMac) +{ + if(!encryptedConnection) + { + return ECT_NO_CONNECTION; + } + else + { + if(peerMac) + encryptedConnection->getEncryptedPeerMac(peerMac); + + if(const ExpiringTimeTracker *timeTracker = encryptedConnection->temporary()) + { + if(remainingDuration) + *remainingDuration = timeTracker->remainingDuration(); + + return ECT_TEMPORARY_CONNECTION; + } + else + { + return ECT_PERMANENT_CONNECTION; + } + } +} + +espnow_connection_type_t EspnowMeshBackend::getConnectionInfo(uint8_t *peerMac, uint32_t *remainingDuration) +{ + EncryptedConnectionLog *encryptedConnection = nullptr; + + if(peerMac) + encryptedConnection = getEncryptedConnection(peerMac); + + return getConnectionInfoHelper(encryptedConnection, remainingDuration); +} + +espnow_connection_type_t EspnowMeshBackend::getConnectionInfo(uint32_t connectionIndex, uint32_t *remainingDuration, uint8_t *peerMac) +{ + EncryptedConnectionLog *encryptedConnection = nullptr; + + if(connectionIndex < numberOfEncryptedConnections()) + encryptedConnection = &encryptedConnections[connectionIndex]; + + return getConnectionInfoHelper(encryptedConnection, remainingDuration, peerMac); +} + +double EspnowMeshBackend::getTransmissionFailRate() +{ + if(_transmissionsTotal == 0) + return 0; + else + return _transmissionsFailed/_transmissionsTotal; +} + +void EspnowMeshBackend::resetTransmissionFailRate() +{ + _transmissionsFailed = 0; + _transmissionsTotal = 0; +} + +String EspnowMeshBackend::serializeEncryptedConnection(const uint8_t *peerMac) +{ + EncryptedConnectionLog *encryptedConnection = nullptr; + + if(peerMac) + encryptedConnection = getEncryptedConnection(peerMac); + + if(encryptedConnection) + return encryptedConnection->serialize(); + else + return ""; +} + +String EspnowMeshBackend::serializeEncryptedConnection(uint32_t connectionIndex) +{ + if(connectionIndex < numberOfEncryptedConnections()) + return encryptedConnections[connectionIndex].serialize(); + else + return ""; +} diff --git a/libraries/ESP8266WiFiMesh/src/EspnowMeshBackend.h b/libraries/ESP8266WiFiMesh/src/EspnowMeshBackend.h new file mode 100644 index 0000000000..a340029a2e --- /dev/null +++ b/libraries/ESP8266WiFiMesh/src/EspnowMeshBackend.h @@ -0,0 +1,764 @@ +/* + EspnowMeshBackend + + Copyright (C) 2019 Anders Löfgren + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +// ESP-NOW is faster for small data payloads (up to a few kB, split over multiple messages). Transfer of up to 234 bytes takes 4 ms. +// In general ESP-NOW transfer time can be approximated with the following function: transferTime = ceil(bytesToTransfer / 234.0)*3 ms. +// If you only transfer 234 bytes at a time, this adds up to around 56kB/s. Finally a chance to relive the glory of the olden days +// when people were restricted to V90 dial-up modems for internet access! +// TCP-IP takes longer to connect (around 1000 ms), and an AP has to disconnect all connected stations in order to transfer data to another AP, +// but this backend has a much higher data transfer speed than ESP-NOW once connected (100x faster or so). + +/** + * Encryption pairing process, schematic overview: + * + * Connection | Peer sends: | Peer requester sends: | Connection + * encrypted: | | | encrypted: + * | | Peer request + Nonce | + * | StaMac + Nonce + HMAC | | + * | | Ack | + * X | SessionKeys + Nonce + Password | | X + * X | | Ack | X + * X | | SessionKey | X + * X | Ack | | X + * | | | + * + * + * The ESP-NOW CCMP encryption should have replay attack protection built in, + * but since there is no official documentation from Espressif about this a 128 bit random nonce is included in encrypted connection requests. + */ + +#ifndef __ESPNOWMESHBACKEND_H__ +#define __ESPNOWMESHBACKEND_H__ + +#include "MeshBackendBase.h" +#include "EspnowProtocolInterpreter.h" +#include "EncryptedConnectionLog.h" +#include "PeerRequestLog.h" +#include "RequestData.h" +#include "ResponseData.h" +#include "MessageData.h" +#include +#include +#include "Crypto.h" + +typedef enum +{ + ECT_NO_CONNECTION = 0, + ECT_TEMPORARY_CONNECTION = 1, + ECT_PERMANENT_CONNECTION = 2 +} espnow_connection_type_t; + +typedef enum +{ + ECS_MAX_CONNECTIONS_REACHED_SELF = -3, + ECS_REQUEST_TRANSMISSION_FAILED = -2, + ECS_MAX_CONNECTIONS_REACHED_PEER = -1, + ECS_API_CALL_FAILED = 0, + ECS_CONNECTION_ESTABLISHED = 1 +} encrypted_connection_status_t; + +typedef enum +{ + ECRO_REMOVAL_REQUEST_FAILED = -1, + ECRO_REMOVAL_FAILED = 0, + ECRO_REMOVAL_SUCCEEDED = 1, + ECRO_REMOVAL_SCHEDULED = 2 +} encrypted_connection_removal_outcome_t; + + +/** + * An alternative to standard delay(). Will continuously call performEspnowMaintainance() during the waiting time, so that the ESP-NOW node remains responsive. + * Note that if there is a lot of ESP-NOW transmission activity to the node during the espnowDelay, the desired duration may be overshot by several ms. + * Thus, if precise timing is required, use standard delay() instead. + * + * Should not be used inside responseHandler, requestHandler or networkFilter callbacks since performEspnowMaintainance() can alter the ESP-NOW state. + * + * @param durationMs The shortest allowed delay duration, in milliseconds. + */ +void espnowDelay(uint32_t durationMs); + +class RequestData; + +class EspnowMeshBackend : public MeshBackendBase { + +public: + + /** + * WiFiMesh Constructor method. Creates a WiFi Mesh Node, ready to be initialised. + * + * @param requestHandler The callback handler for dealing with received requests. Takes a string as an argument which + * is the request string received from another node and returns the string to send back. + * @param responseHandler The callback handler for dealing with received responses. Takes a string as an argument which + * is the response string received from another node. Returns a transmission status code as a transmission_status_t. + * @param networkFilter The callback handler for deciding which WiFi networks to connect to. + * @param meshPassword The WiFi password for the mesh network. + * @param ssidPrefix The prefix (first part) of the node SSID. + * @param ssidSuffix The suffix (last part) of the node SSID. + * @param verboseMode Determines if we should print the events occurring in the library to Serial. Off by default. This setting is shared by all EspnowMeshBackend instances. + * @param meshWiFiChannel The WiFi channel used by the mesh network. Valid values are integers from 1 to 13. Defaults to 1. + * WARNING: The ESP8266 has only one WiFi channel, and the the station/client mode is always prioritized for channel selection. + * This can cause problems if several mesh instances exist on the same ESP8266 and use different WiFi channels. + * In such a case, whenever the station of one mesh instance connects to an AP, it will silently force the + * WiFi channel of any active AP on the ESP8266 to match that of the station. This will cause disconnects and possibly + * make it impossible for other stations to detect the APs whose WiFi channels have changed. + * + */ + EspnowMeshBackend(requestHandlerType requestHandler, responseHandlerType responseHandler, networkFilterType networkFilter, + const String &meshPassword, const uint8_t espnowEncryptionKey[EspnowProtocolInterpreter::espnowEncryptionKeyLength], + const uint8_t espnowHashKey[EspnowProtocolInterpreter::espnowHashKeyLength], const String &ssidPrefix, + const String &ssidSuffix, bool verboseMode = false, uint8 meshWiFiChannel = 1); + + ~EspnowMeshBackend() override; + + /** + * Initialises the node. + */ + void begin() override; + + /** + * This method performs all the background operations for the EspnowMeshBackend. + * It is recommended to place it in the beginning of the loop(), unless there is a need to put it elsewhere. + * Among other things, the method cleans up old Espnow log entries (freeing up RAM) and sends the responses you provide to Espnow requests. + * Note that depending on the amount of responses to send and their length, this method can take tens or even hundreds of milliseconds to complete. + * More intense transmission activity and less frequent calls to performEspnowMaintainance will likely cause the method to take longer to complete, so plan accordingly. + * + * Should not be used inside responseHandler, requestHandler or networkFilter callbacks since performEspnowMaintainance() can alter the ESP-NOW state. + */ + static void performEspnowMaintainance(); + + /** + * At critical heap level no more incoming requests are accepted. + */ + static uint32_t criticalHeapLevel(); + + /** + * At critical heap level no more incoming requests are accepted. + * This method sets the maximum number of bytes above the critical heap level that will trigger an early ESP-NOW log clearing in an attempt to increase available heap size. + * A too high value may cause very frequent early log clearings, which will slow things down. Especially if you are using a lot of heap in other parts of your program. + * A too low value may cause some incoming requests to be lost and/or an increase in heap fragmentation, + * especially if you quickly fill the heap by receiving a lot of large ESP-NOW messages or sending a lot of large ESP-NOW responses. + * The buffer is set to 6000 bytes by default, which should be enough to prevent lost incoming requests while giving plenty of heap to fill up before early clearing in most circumstances. + * + * The buffer can be set lower than the default if you are running low on heap, since it may otherwise be hard to get responses sent. + * However, lower values tend to result in more heap fragmentation during intense transmission activity. + * Depending on your situation (message size, transmission frequency), values below 2000-3000 bytes will also start to cause lost incoming requests due to heap shortage. + * + * If the buffer is set to 0 bytes a significant number of incoming requests are likely to be lost during intense transmission activity, + * and there is a greater risk of heap space completely running out before log clearing occurs (which may cause crashes or empty transmissions). + */ + static void setCriticalHeapLevelBuffer(uint32_t bufferInBytes); + static uint32_t criticalHeapLevelBuffer(); + + /** + * Deactivates Espnow for this node. Call begin() on a EspnowMeshBackend instance to reactivate Espnow. + * + * @returns True if deactivation was successful. False otherwise. + */ + static bool deactivateEspnow(); + + void attemptTransmission(const String &message, bool scan = true, bool scanAllWiFiChannels = false) override; + + // Will ensure that an encrypted connection exists to each target node before sending the message, + // establishing a temporary encrypted connection with duration getAutoEncryptionDuration() first if neccessary. + // If an encrypted connection cannot be established to a target node, no message will be sent to that node. + // Note that if an encrypted connection to a target node is not present before this method is called, the response from said node will likely not be received + // since it will be encrypted and the auto encrypted connection to the node is immediately removed after transmission. + // Also note that if a temporary encrypted connection already exists to a target node, this method will slightly extend the connection duration + // depending on the time it takes to verify the connection to the node. This can substantially increase the connection duration if many auto encrypting + // transmissions occurs. + void attemptAutoEncryptingTransmission(const String &message, bool scan = true, bool scanAllWiFiChannels = false); + + /** + * Set the EspnowMeshBackend instance responsible for handling incoming requests. The requestHandler of the instance will be called upon receiving ESP-NOW requests. + * + * Set to nullptr to stop processing the ESP-NOW requests received by this node (requests will be ignored, but still received (ack will be sent)). + * The node can still send ESP-NOW transmissions to other nodes, even when the espnowRequestManager is nullptr. + */ + static void setEspnowRequestManager(EspnowMeshBackend *espnowMeshInstance); + + static EspnowMeshBackend *getEspnowRequestManager(); + + /** + * Check if this EspnowMeshBackend instance is the espnowRequestManager. + * + * @returns True if this EspnowMeshBackend is the espnowRequestManager. False otherwise. + */ + bool isEspnowRequestManager(); + + /** + * 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 encrypted connections requested or added by this EspnowMeshBackend instance. + * + * NOTE: Encrypted connections added before the encryption key change will retain their old encryption key. + * Only changes the encryption key used by this EspnowMeshBackend instance, so each instance can use a separate key. + * Both Kok and encryption key must match in an encrypted connection pair for encrypted communication to be possible. + * Otherwise the transmissions will never reach the recipient, even though acks are received by the sender. + * + * @param espnowEncryptionKey An array containing the espnowEncryptionKeyLength bytes that will be used as the encryption key. + */ + void setEspnowEncryptionKey(const uint8_t espnowEncryptionKey[EspnowProtocolInterpreter::espnowEncryptionKeyLength]); + + /** + * Get the encryption key used by this EspnowMeshBackend instance for creating encrypted ESP-NOW connections. + * + * @return The current espnowEncryptionKey for this EspnowMeshBackend instance. + */ + const uint8_t *getEspnowEncryptionKey(); + uint8_t *getEspnowEncryptionKey(uint8_t resultArray[EspnowProtocolInterpreter::espnowEncryptionKeyLength]); + + /** + * Change the key used to encrypt/decrypt the encryption key when creating encrypted ESP-NOW connections. (Kok = key of keys, perhaps) If no Kok is provided by the user, a default Kok is used. + * Will apply to any new encrypted connections. + * Must be called after begin() to take effect. + * + * NOTE: Encrypted connections added before the Kok change will retain their old Kok. + * This changes the Kok for all EspnowMeshBackend instances on this ESP8266. + * Both Kok and encryption key must match in an encrypted connection pair for encrypted communication to be possible. + * Otherwise the transmissions will never reach the recipient, even though acks are received by the sender. + * + * @param espnowEncryptionKok An array containing the espnowEncryptionKeyLength bytes that will be used as the Kok. + * @return True if Kok was changed successfully. False if Kok was not changed. + */ + static bool setEspnowEncryptionKok(uint8_t espnowEncryptionKok[EspnowProtocolInterpreter::espnowEncryptionKeyLength]); + + /** + * Get the key used to encrypt the encryption keys when creating encrypted ESP-NOW connections. (Kok = key of keys, perhaps) Returns nullptr if no Kok has been provided by the user. + * + * @return nullptr if default Kok is used, or current espnowEncryptionKok if a custom Kok has been set via the setEspnowEncryptionKok method. + */ + static const uint8_t *getEspnowEncryptionKok(); + + /** + * Change the secret key used to generate HMACs for 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 encrypted connections requested or added by this EspnowMeshBackend instance. + * + * NOTE: Encrypted connections added before the key change will retain their old key. + * Only changes the secret hash key used by this EspnowMeshBackend instance, so each instance can use a separate secret key. + * + * @param espnowHashKey An array containing the espnowHashKeyLength bytes that will be used as the HMAC key. + */ + void setEspnowHashKey(const uint8_t espnowHashKey[EspnowProtocolInterpreter::espnowHashKeyLength]); + const uint8_t *getEspnowHashKey(); + + /** + * Hint: Use String.length() to get the ASCII length of a String. + * + * @returns The maximum number of bytes (or ASCII characters) a transmission can contain. Note that non-ASCII characters usually require the space of at least two ASCII characters each. + */ + static uint32_t getMaxMessageBytesPerTransmission(); + + /** + * Set the maximum acceptable message length, in terms of transmissions, when sending a message from this node. + * This has no effect when receiving messages, the limit for receiving is always 256 transmissions per message. + * Note that although values up to 128 are possible, this would in practice fill almost all the RAM available on the ESP8266 with just one message. + * Thus, if this value is set higher than the default, make sure there is enough heap available to store the messages + * and don't send messages more frequently than they can be processed. + * Also note that a higher value will make the node less responsive as it will be spending a long time transmitting. + * + * Typical symptoms of running out of heap are crashes and messages that become empty even though they shouldn't be. Keep this in mind if going beyond the default. + * + * @param maxTransmissionsPerMessage The maximum acceptable message length, in terms of transmissions, when sending a message from this node. Valid values are 1 to 128. Defaults to 3. + */ + static void setMaxTransmissionsPerMessage(uint8_t maxTransmissionsPerMessage); + static uint8_t getMaxTransmissionsPerMessage(); + + /** + * Hint: Use String.length() to get the ASCII length of a String. + * + * @returns The maximum length in bytes an ASCII message is allowed to be when transmitted by this node. Note that non-ASCII characters usually require at least two bytes each. + */ + static uint32_t getMaxMessageLength(); + + /** + * Set whether the normal events occurring in the library will be printed to Serial or not. Off by default. + * This setting is shared by all EspnowMeshBackend instances. + * + * @param enabled If true, library Serial prints are activated. + */ + void setVerboseModeState(bool enabled) override; + bool verboseMode() override; + + /** + * Only print stringToPrint if verboseMode() returns true. + * + * @param stringToPrint String to print. + * @param newline If true, will end the print with a newline. True by default. + */ + void verboseModePrint(const String &stringToPrint, bool newline = true) override; + + /** + * Same as verboseMode(), but used for printing from static functions. + * + * @returns True if the normal events occurring in the library will be printed to Serial. False otherwise. + */ + static bool staticVerboseMode(); + + /** + * Only print stringToPrint if staticVerboseMode() returns true. + * + * @param stringToPrint String to print. + * @param newline If true, will end the print with a newline. True by default. + */ + static void staticVerboseModePrint(const String &stringToPrint, bool newline = true); + + /** + * Get the message of the response at responseIndex among the responses that are scheduled for transmission from this node. + * + * @param responseIndex The index of the response. Must be lower than numberOfScheduledResponses(). + * @returns A String containing the message of the response at responseIndex. + */ + static String getScheduledResponseMessage(uint32_t responseIndex); + + /** + * Get the MAC address for the recipient of the response at responseIndex among the responses that are scheduled for transmission from this node. + * + * @param responseIndex The index of the response. Must be lower than numberOfScheduledResponses(). + * @returns An array with six bytes containing the MAC address for the recipient of the response at responseIndex. + */ + static const uint8_t *getScheduledResponseRecipient(uint32_t responseIndex); + + /** + * Get the number of ESP-NOW responses that are scheduled for transmission from this node. + * + * @returns The number of ESP-NOW responses scheduled for transmission. + */ + static uint32_t numberOfScheduledResponses(); + + /** + * Remove all responses that have been scheduled for transmission but are not yet transmitted. + * Note that cleared responses will not be received by their recipient. + */ + static void clearAllScheduledResponses(); + + /** + * Remove all responses targeting recipientMac that have been scheduled for transmission but are not yet transmitted. + * Optionally deletes only responses to encrypted requests. + * Note that deleted responses will not be received by their recipient. + * + * @param recipientMac The MAC address of the response recipient. + * @param encryptedOnly If true, only responses to encrypted requests will be deleted. + */ + static void deleteScheduledResponsesByRecipient(const uint8_t *recipientMac, bool encryptedOnly); + + /** + * Set the timeout to use for each ESP-NOW transmission when transmitting. + * Note that for multi-part transmissions (where message length is greater than getMaxMessageBytesPerTransmission()), the timeout is reset for each transmission part. + * The default timeouts should fit most use cases, but in case you do a lot of time consuming processing when the node receives a message, you may need to relax them a bit. + * + * @param timeoutMs The timeout that should be used for each ESP-NOW transmission, in milliseconds. Defaults to 40 ms. + */ + static void setEspnowTransmissionTimeout(uint32_t timeoutMs); + static uint32_t getEspnowTransmissionTimeout(); + + /** + * Set the time to wait for an ack after having made an ESP-NOW transmission. If no ack is received within said time, a new transmission attempt is made. + * Note that if a retransmission causes duplicate transmissions to reach the receiver, the duplicates will be detected and ignored automatically. + * The default timeouts should fit most use cases, but in case you do a lot of time consuming processing when the node receives a message, you may need to relax them a bit. + * + * @param intervalMs The time to wait for an ack after having made an ESP-NOW transmission, in milliseconds. Defaults to 15 ms. + */ + static void setEspnowRetransmissionInterval(uint32_t intervalMs); + static uint32_t getEspnowRetransmissionInterval(); + + // The maximum amount of time each of the two stages in an encrypted connection request may take. + static void setEncryptionRequestTimeout(uint32_t timeoutMs); + static uint32_t getEncryptionRequestTimeout(); + + void setAutoEncryptionDuration(uint32_t duration); + uint32_t getAutoEncryptionDuration(); + + /** + * Get the MAC address of the sender of the most recently received ESP-NOW request or response to this EspnowMeshBackend instance. + * Returns a String. + * By default the MAC will be that of the sender's station interface. The only exception is for unencrypted + * responses to requests sent to an AP interface, which will return the response sender's AP interface MAC. + * + * @returns A String filled with a hexadecimal representation of the MAC, without delimiters. + */ + String getSenderMac(); + + /** + * Get the MAC address of the sender of the most recently received ESP-NOW request or response to this EspnowMeshBackend instance. + * Returns a uint8_t array. + * By default the MAC will be that of the sender's station interface. The only exception is for unencrypted + * responses to requests sent to an AP interface, which will return the response sender's AP interface MAC. + * + * @param macArray The array that should store the MAC address. Must be at least 6 bytes. + * @returns macArray filled with the sender MAC. + */ + uint8_t *getSenderMac(uint8_t *macArray); + + /** + * Get whether the ESP-NOW request or response which was most recently received by this EspnowMeshBackend instance was encrypted or not. + * + * @returns If true, the request or response was encrypted. If false, it was unencrypted. + */ + bool receivedEncryptedMessage(); + + // Updates connection with current stored encryption key. + // At least one of the leftmost 32 bits in each of the session keys should be 1, since the key otherwise indicates the connection is unencrypted. + encrypted_connection_status_t addEncryptedConnection(uint8_t *peerStaMac, uint8_t *peerApMac, uint64_t peerSessionKey, uint64_t ownSessionKey); + // Note that the espnowEncryptionKey, espnowEncryptionKok and espnowHashKey are not serialized. + // These will be set to the values of the EspnowMeshBackend instance that is adding the serialized encrypted connection. + // @param ignoreDuration Ignores any stored duration serializedConnectionState, guaranteeing that the created connection will be permanent. Returns: ECS_REQUEST_TRANSMISSION_FAILED indicates malformed serializedConnectionState. + encrypted_connection_status_t addEncryptedConnection(const String &serializedConnectionState, bool ignoreDuration = false); + + // Adds a new temporary encrypted connection, or changes the duration of an existing temporary connection (only updates keys, not duration, for existing permanent connections). + // As with all these methods, changes will only take effect once the requester proves it has the ability to decrypt the session key. + // At least one of the leftmost 32 bits in each of the session keys should be 1, since the key otherwise indicates the connection is unencrypted. + encrypted_connection_status_t addTemporaryEncryptedConnection(uint8_t *peerStaMac, uint8_t *peerApMac, uint64_t peerSessionKey, uint64_t ownSessionKey, uint32_t duration); + // Note that the espnowEncryptionKey, espnowEncryptionKok and espnowHashKey are not serialized. + // These will be set to the values of the EspnowMeshBackend instance that is adding the serialized encrypted connection. + // Uses duration argument instead of any stored duration in serializedConnectionState. Returns: ECS_REQUEST_TRANSMISSION_FAILED indicates malformed serializedConnectionState. + encrypted_connection_status_t addTemporaryEncryptedConnection(const String &serializedConnectionState, uint32_t duration); + + // If an encrypted connection to peerMac already exists, only connection duration is updated. All other settings are kept as is. Use removeEncryptedConnection/requestEncryptedConnectionRemoval first if encryption keys should be updated. + // Makes sure both nodes have an encrypted connection to each other that's permanent. + encrypted_connection_status_t requestEncryptedConnection(uint8_t *peerMac); + // Makes sure both nodes have an encrypted connection to each other that's either permanent or has the duration specified. + encrypted_connection_status_t requestTemporaryEncryptedConnection(uint8_t *peerMac, uint32_t durationMs); + // Makes sure both nodes have an encrypted connection to each other that's either permanent or has at least the duration specified. + // Note that if a temporary encrypted connection already exists to a target node, this method will slightly extend the connection duration + // depending on the time it takes to verify the connection to the node. + encrypted_connection_status_t requestFlexibleTemporaryEncryptedConnection(uint8_t *peerMac, uint32_t minDurationMs); + static encrypted_connection_removal_outcome_t removeEncryptedConnection(uint8_t *peerMac); + encrypted_connection_removal_outcome_t requestEncryptedConnectionRemoval(uint8_t *peerMac); + + /** + * Set whether this EspnowMeshBackend instance will accept unencrypted ESP-NOW requests or not, when acting as EspnowRequestManager. + * When set to false and combined with already existing encrypted connections, this can be used to ensure only encrypted transmissions are processed. + * When set to false it will also make it impossible to send unencrypted requests for encrypted connection to the node, + * which can be useful if too many such requests could otherwise be expected. + * + * @param acceptsUnencryptedRequests If and only if true, unencrypted requests will be processed when this EspnowMeshBackend instance is acting as EspnowRequestManager. True by default. + */ + void setAcceptsUnencryptedRequests(bool acceptsUnencryptedRequests); + bool acceptsUnencryptedRequests(); + + /** + * @ returns The current number of encrypted ESP-NOW connections. + */ + static uint8_t numberOfEncryptedConnections(); + + // @returns resultArray filled with the MAC to the encrypted interface of the node, if an encrypted connection exists. nulltpr otherwise. + static uint8_t *getEncryptedMac(const uint8_t *peerMac, uint8_t *resultArray); + + // Create a string containing the current state of the encrypted connection for this node. The result can be used as input to addEncryptedConnection. + // Note that transferring the serialized state over an unencrypted connection will compromise the security of the stored connection. + // @ returns A String containing the serialized encrypted connection, or an empty String if there is no matching encrypted connection. + static String serializeEncryptedConnection(const uint8_t *peerMac); + static String serializeEncryptedConnection(uint32_t connectionIndex); + + /** + * Get information about any current ESP-NOW connection with another node. + * + * @param peerMac The node MAC for which to get information. Both MAC for AP interface and MAC for STA interface can be used (and will yield the same result). + * Use the getEncryptedMac method or the indexed based getConnectionInfo if there is a need to find the actual encrypted interface. + * @param remainingDuration An optional pointer to a uint32_t variable. + * If supplied and the connection type is ECT_TEMPORARY_CONNECTION the variable will be set to the remaining duration of the connection. + * Otherwise the variable value is not modified. + * @ returns The espnow_connection_type_t of the connection with peerMac. + */ + static espnow_connection_type_t getConnectionInfo(uint8_t *peerMac, uint32_t *remainingDuration = nullptr); + + /** + * Get information about any current ESP-NOW connection with another node. + * + * @param connectionIndex The connection index of the node for which to get information. Valid values are limited by numberOfEncryptedConnections(). + * @param remainingDuration An optional pointer to a uint32_t variable. + * If supplied and the connection type is ECT_TEMPORARY_CONNECTION the variable will be set to the remaining duration of the connection. + * Otherwise the variable value is not modified. + * @param peerMac An optional pointer to an uint8_t array with at least size 6. It will be filled with the MAC of the encrypted peer interface if an encrypted connection exists. + * Otherwise the array is not modified. + * @ returns The espnow_connection_type_t of the connection given by connectionIndex. + */ + static espnow_connection_type_t getConnectionInfo(uint32_t connectionIndex, uint32_t *remainingDuration = nullptr, uint8_t *peerMac = nullptr); + + /** + * @returns The proportion of ESP-NOW requests made by this node that have failed, since power on or latest reset. + */ + static double getTransmissionFailRate(); + + /** + * Reset TransmissionFailRate back to 0. + */ + static void resetTransmissionFailRate(); + +protected: + + typedef std::vector::iterator connectionLogIterator; + static connectionLogIterator connectionLogEndIterator(); + + bool activateEspnow(); + + /* + * Note that ESP-NOW is not perfect and in rare cases messages may be dropped. + * This needs to be compensated for in the application via extra verification + * (e.g. by always sending a response such as a message hash), if message delivery must be guaranteed. + * + * Note that although responses will generally be sent in the order they were created, this is not guaranteed to be the case. + * For example, response order will be mixed up if some responses fail to transmit while others transmit successfully. + */ + static void sendEspnowResponses(); + static void clearOldLogEntries(); + + static uint32_t getMaxBytesPerTransmission(); + + static std::list::const_iterator getScheduledResponse(uint32_t responseIndex); + + // Note that removing an encrypted connection while there are encrypted responses scheduled for transmission to the encrypted peer will cause these encrypted responses to be removed without being sent. + // Also note that removing an encrypted connection while there is encrypted data to be received will make the node unable to decrypt that data (although an ack will still be sent to confirm data reception). + // In other words, it is good to use these methods with care and to make sure that both nodes in an encrypted pair are in a state where it is safe for the encrypted connection to be removed before using them. + // Consider using getScheduledResponseRecipient and similar methods for this preparation. + // Should only be used when there is no transmissions in progress. In practice when _espnowTransmissionMutex is free. + // @param resultingIterator Will be set to the iterator position after the removed element, if an element to remove was found. Otherwise no change will occur. + static encrypted_connection_removal_outcome_t removeEncryptedConnectionUnprotected(uint8_t *peerMac, std::vector::iterator *resultingIterator = nullptr); + static encrypted_connection_removal_outcome_t removeEncryptedConnectionUnprotected(connectionLogIterator &connectionIterator, std::vector::iterator *resultingIterator); + + /** + * Set the MAC address considered to be the sender of the most recently received ESP-NOW request or response. + * + * @param macArray An uint8_t array which contains the MAC address to store. The method will store the first 6 bytes of the array. + */ + void setSenderMac(uint8_t *macArray); + + /** + * Set whether the most recently received ESP-NOW request or response is presented as having been encrypted or not. + * + * @param receivedEncryptedMessage If true, the request or response is presented as having been encrypted. + */ + void setReceivedEncryptedMessage(bool receivedEncryptedMessage); + + static bool temporaryEncryptedConnectionToPermanent(uint8_t *peerMac); + + /** + * Will be true if a transmission initiated by a public method is in progress. + */ + static bool _espnowTransmissionMutex; + + /** + * Check if there is an ongoing ESP-NOW transmission in the library. Used to avoid interrupting transmissions. + * + * @returns True if a transmission initiated by a public method is in progress. + */ + static bool transmissionInProgress(); + + enum class macAndType_td : uint64_t {}; + typedef uint64_t messageID_td; + typedef uint64_t peerMac_td; + + static macAndType_td createMacAndTypeValue(uint64_t uint64Mac, char messageType); + static uint64_t macAndTypeToUint64Mac(const macAndType_td &macAndTypeValue); + + /** + * Remove all entries which target peerMac in the logEntries map. + * Optionally deletes only entries sent/received by encrypted transmissions. + * + * @param logEntries The map to process. + * @param peerMac The MAC address of the peer node. + * @param encryptedOnly If true, only entries sent/received by encrypted transmissions will be deleted. + */ + template + static void deleteEntriesByMac(std::map, T> &logEntries, const uint8_t *peerMac, bool encryptedOnly); + + template + static void deleteEntriesByMac(std::map, T> &logEntries, const uint8_t *peerMac, bool encryptedOnly); + + static bool requestReceived(uint64_t requestMac, uint64_t requestID); + + /** + * Send an ESP-NOW message to the ESP8266 that has the MAC address specified in targetBSSID. + * + * @param messageType The identifier character for the type of message to send. Choices are 'Q' for question (request), + * 'A' for answer (response), 'S' for synchronization request, 'P' for peer request and 'C' for peer request confirmation. + * @returns The transmission status for the transmission. + */ + // Send a message to the node having targetBSSID as mac, changing targetBSSID to the mac of the encrypted connection if it exists and ensuring such an encrypted connection is synchronized. + static transmission_status_t espnowSendToNode(const String &message, const uint8_t *targetBSSID, char messageType, EspnowMeshBackend *espnowInstance = nullptr); + // Send a message using exactly the arguments given, without consideration for any encrypted connections. + static transmission_status_t espnowSendToNodeUnsynchronized(const String message, const uint8_t *targetBSSID, char messageType, uint64_t messageID, EspnowMeshBackend *espnowInstance = nullptr); + + transmission_status_t sendRequest(const String &message, const uint8_t *targetBSSID); + transmission_status_t sendResponse(const String &message, uint64_t requestID, const uint8_t *targetBSSID); + +private: + + typedef std::function encryptionRequestBuilderType; + static String defaultEncryptionRequestBuilder(const String &requestHeader, const uint32_t durationMs, const String &requestNonce, const ExpiringTimeTracker &existingTimeTracker); + static String flexibleEncryptionRequestBuilder(const uint32_t minDurationMs, const String &requestNonce, const ExpiringTimeTracker &existingTimeTracker); + + /** + * We can't feed esp_now_register_recv_cb our EspnowMeshBackend instance's espnowReceiveCallback method directly, so this callback wrapper is a workaround. + * + * This method is very time critical so avoid Serial printing in it and in methods called from it (such as espnowReceiveCallback) as much as possible. + * Otherwise transmission fail rate is likely to skyrocket. + */ + static void espnowReceiveCallbackWrapper(uint8_t *macaddr, uint8_t *dataArray, uint8_t len); + void espnowReceiveCallback(uint8_t *macaddr, uint8_t *data, uint8_t len); + + static void handlePeerRequest(uint8_t *macaddr, uint8_t *dataArray, uint8_t len, uint64_t uint64StationMac, uint64_t receivedMessageID); + static void handlePeerRequestConfirmation(uint8_t *macaddr, uint8_t *dataArray, uint8_t len); + + static void handlePostponedRemovals(); + + static bool verifyPeerSessionKey(uint64_t sessionKey, const uint8_t *peerMac, char messageType); + static bool verifyPeerSessionKey(uint64_t sessionKey, EncryptedConnectionLog &encryptedConnection, uint64_t uint64PeerMac, char messageType); + + static bool synchronizePeerSessionKey(uint64_t sessionKey, const uint8_t *peerMac); + static bool synchronizePeerSessionKey(uint64_t sessionKey, EncryptedConnectionLog &encryptedConnection); + + static const uint32_t _maxBytesPerTransmission = 250; + static uint8_t _maxTransmissionsPerMessage; + + static uint32_t _espnowTransmissionTimeoutMs; + static uint32_t _espnowRetransmissionIntervalMs; + + uint32_t _autoEncryptionDuration = 50; + + static bool _staticVerboseMode; + + static EspnowMeshBackend *_espnowRequestManager; + + static std::map, MessageData> receivedEspnowTransmissions; + static std::map, RequestData> sentRequests; + static std::map, TimeTracker> receivedRequests; + + static std::list responsesToSend; + static std::list peerRequestConfirmationsToSend; + + static std::vector encryptedConnections; + + static EncryptedConnectionLog *getEncryptedConnection(const uint8_t *peerMac); + static EncryptedConnectionLog *getTemporaryEncryptedConnection(const uint8_t *peerMac); + + //@returns iterator to connection in connectionVector, or connectionVector.end() if element not found + template + static typename std::vector::iterator getEncryptedConnectionIterator(const uint8_t *peerMac, typename std::vector &connectionVector); + static bool getEncryptedConnectionIterator(const uint8_t *peerMac, connectionLogIterator &iterator); + // @returns true if an encrypted connection to peerMac is found and the found connection is temporary. Only changes iterator if true is returned. + static bool getTemporaryEncryptedConnectionIterator(const uint8_t *peerMac, connectionLogIterator &iterator); + + static espnow_connection_type_t getConnectionInfoHelper(const EncryptedConnectionLog *encryptedConnection, uint32_t *remainingDuration, uint8_t *peerMac = nullptr); + + // Should only be used when there is no transmissions in progress, so it is safe to remove encrypted connections. In practice when _espnowTransmissionMutex is free. + // @param scheduledRemovalOnly If true, only deletes encrypted connections where removalScheduled() is true. This means only connections which have been requested for removal will be deleted, + // not other connections which have expired. + static void updateTemporaryEncryptedConnections(bool scheduledRemovalOnly = false); + + template + static void deleteExpiredLogEntries(std::map, T> &logEntries, uint32_t maxEntryLifetimeMs); + + template + static void deleteExpiredLogEntries(std::list &logEntries, uint32_t maxEntryLifetimeMs); + + static uint32_t _logEntryLifetimeMs; + static uint32_t logEntryLifetimeMs(); + static uint32_t _responseTimeoutMs; + static uint32_t responseTimeoutMs(); + + static uint32_t _encryptionRequestTimeoutMs; + + static uint32_t _timeOfLastLogClear; + static uint32_t _criticalHeapLevel; + static uint32_t _criticalHeapLevelBuffer; + + static bool _espnowSendConfirmed; + + static String _ongoingPeerRequestNonce; + static EspnowMeshBackend *_ongoingPeerRequester; + static encrypted_connection_status_t _ongoingPeerRequestResult; + static uint32_t _ongoingPeerRequestEncryptionStart; + + template + static T *getMapValue(std::map &mapIn, uint64_t keyIn); + + static bool usesConstantSessionKey(char messageType); + + bool _acceptsUnencryptedRequests = true; + + uint8_t _espnowEncryptionKey[EspnowProtocolInterpreter::espnowEncryptionKeyLength] {0}; + uint8_t _espnowHashKey[EspnowProtocolInterpreter::espnowHashKeyLength] {0}; + static uint8_t _espnowEncryptionKok[EspnowProtocolInterpreter::espnowEncryptionKeyLength]; + static bool _espnowEncryptionKokSet; + static uint32_t _unencryptedMessageID; + + uint8_t _senderMac[6] = {0}; + bool _receivedEncryptedMessage = false; + + static bool _espnowSendToNodeMutex; + static uint8_t _transmissionTargetBSSID[6]; + + static void storeSentRequest(const uint64_t targetBSSID, const uint64_t messageID, const RequestData &requestData); + static void storeReceivedRequest(const uint64_t senderBSSID, const uint64_t messageID, const TimeTracker &timeTracker); + + /** + * Get a pointer to the EspnowMeshBackend instance that sent a request with the given requestID to the specified mac address. + * + * @returns A valid EspnowMeshBackend pointer if a matching entry is found in the EspnowMeshBackend sentRequests container. nullptr otherwise. + */ + static EspnowMeshBackend *getOwnerOfSentRequest(uint64_t requestMac, uint64_t requestID); + + /** + * Delete all entries in the sentRequests container where requestMac is noted as having received requestID. + * + * @returns The number of entries deleted. + */ + static size_t deleteSentRequest(uint64_t requestMac, uint64_t requestID); + + static size_t deleteSentRequestsByOwner(EspnowMeshBackend *instancePointer); + + /** + * Contains the core logic used for requesting an encrypted connection to a peerMac. + * + * @param peerMac The MAC of the node with which an encrypted connection should be established. + * @param encryptionRequestBuilder A function which is responsible for constructing the request message to send. + * Called twice when the request is successful. First to build the initial request message and then to build the connection verification message. + * The request message should typically be of the form: JsonTranslator::createEncryptionRequestIntro() + JsonTranslator::createEncryptionRequestEnding(). + * @returns The ultimate status of the requested encrypted connection, as encrypted_connection_status_t. + */ + encrypted_connection_status_t requestEncryptedConnectionKernel(uint8_t *peerMac, const encryptionRequestBuilderType &encryptionRequestBuilder); + + /** + * Generate a new message ID to be used when making a data transmission. The generated ID will be different depending on whether an encrypted connection exists or not. + * + * @param encryptedConnection A pointer to the EncryptedConnectionLog of the encrypted connection. Can be set to nullptr if the connection is unecrypted. + * @returns The generated message ID. + */ + static uint64_t generateMessageID(EncryptedConnectionLog *encryptedConnection); + + /** + * Create a new session key for an encrypted connection using the built in RANDOM_REG32 of the ESP8266. + * Should only be used when initializing a new connection. + * Use generateMessageID instead when the encrypted connection is already initialized to keep the connection synchronized. + * + * @returns A uint64_t containing a new session key for an encrypted connection. + */ + static uint64_t createSessionKey(); + + // Used for verboseMode printing in attemptTransmission, _AT suffix used to reduce namespace clutter + uint32_t totalDurationWhenSuccessful_AT = 0; + uint32_t successfulTransmissions_AT = 0; + uint32_t maxTransmissionDuration_AT = 0; + + static double _transmissionsTotal; + static double _transmissionsFailed; +}; + +#endif diff --git a/libraries/ESP8266WiFiMesh/src/EspnowProtocolInterpreter.cpp b/libraries/ESP8266WiFiMesh/src/EspnowProtocolInterpreter.cpp new file mode 100644 index 0000000000..b5b284fa9c --- /dev/null +++ b/libraries/ESP8266WiFiMesh/src/EspnowProtocolInterpreter.cpp @@ -0,0 +1,105 @@ +/* + * Copyright (C) 2019 Anders Löfgren + * + * License (MIT license): + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include "EspnowProtocolInterpreter.h" +#include "TypeConversionFunctions.h" +#include + +namespace EspnowProtocolInterpreter +{ + const uint64_t uint64LeftmostBits = 0xFFFFFFFF00000000; + + uint8_t espnowProtocolBytesSize() + { + return 16; + } + + String espnowGetMessageContent(uint8_t *transmission, uint8_t transmissionLength) + { + if(transmissionLength < espnowProtocolBytesSize()) + { + return ""; + } + else + { + // Ensure we have a NULL terminated character array so the String() constructor knows where to stop. + uint8_t bufferedTransmission[transmissionLength + 1]; + std::copy_n(transmission, transmissionLength, bufferedTransmission); + bufferedTransmission[transmissionLength] = 0; + return String((char *)(bufferedTransmission + espnowProtocolBytesSize())); + } + } + + char espnowGetMessageType(const uint8_t *transmissionDataArray) + { + return char(transmissionDataArray[espnowMessageTypeIndex]); + } + + uint8_t espnowGetTransmissionsRemaining(const uint8_t *transmissionDataArray) + { + return (transmissionDataArray[espnowTransmissionsRemainingIndex] & 0x7F); + } + + bool espnowIsMessageStart(const uint8_t *transmissionDataArray) + { + return (transmissionDataArray[espnowTransmissionsRemainingIndex] & 0x80); // If MSB is one we have messageStart + } + + uint64_t espnowGetTransmissionMac(const uint8_t *transmissionDataArray) + { + return macToUint64(transmissionDataArray + espnowTransmissionMacIndex); + } + + uint8_t *espnowGetTransmissionMac(const uint8_t *transmissionDataArray, uint8_t *resultArray) + { + std::copy_n((transmissionDataArray + espnowTransmissionMacIndex), 6, resultArray); + return resultArray; + } + + uint64_t espnowGetMessageID(const uint8_t *transmissionDataArray) + { + uint64_t outcome = 0; + for(int shiftingFortune = 56; shiftingFortune >= 0; shiftingFortune -= 8) + { + outcome |= ((uint64_t)transmissionDataArray[espnowMessageIDIndex + 7 - shiftingFortune/8] << shiftingFortune); + } + + return outcome; + } + + uint8_t *espnowSetMessageID(uint8_t *transmissionDataArray, uint64_t messageID) + { + for(int shiftingFortune = 56; shiftingFortune >= 0; shiftingFortune -= 8) + { + transmissionDataArray[espnowMessageIDIndex + 7 - shiftingFortune/8] = messageID >> shiftingFortune & 0xFF; + } + return transmissionDataArray; + } + + bool usesEncryption(uint64_t messageID) + { + // At least one of the leftmost half of bits in messageID is 1 if the transmission is encrypted. + return messageID & uint64LeftmostBits; + } +} diff --git a/libraries/ESP8266WiFiMesh/src/EspnowProtocolInterpreter.h b/libraries/ESP8266WiFiMesh/src/EspnowProtocolInterpreter.h new file mode 100644 index 0000000000..45fa46b557 --- /dev/null +++ b/libraries/ESP8266WiFiMesh/src/EspnowProtocolInterpreter.h @@ -0,0 +1,74 @@ +/* + * Copyright (C) 2019 Anders Löfgren + * + * License (MIT license): + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#ifndef __ESPNOWPROTOCOLINTERPRETER_H__ +#define __ESPNOWPROTOCOLINTERPRETER_H__ + +#include + +// The following protocol is used on top of ESP-NOW (for the bits and bytes in each transmission): +// Bit 0-7: Message type. The type for requests must be different from the type for responses if they may require more than one transmission. Otherwise multi-part requests and responses with the same ID may be mixed together. +// Bit 8: Flag for message start. +// Bit 9-15: Transmissions remaining for the message. +// Byte 2-7: Transmission sender MAC address for AP interface. Since we always transmit from the station interface, this ensures both sender MAC addresses are available to the receiver. +// Byte 8-15: Message ID. 32 rightmost bits used for unencrypted messages (the rest is 0). 64 bits used for encrypted messages (with at least one of the leftmost 32 bits set to 1). +// This distinction based on encryption is required since the ESP-NOW API does not provide information about whether a received transmission is encrypted or not. +// Byte 16-249: The message. +// Each message can be split in up to EspnowMeshBackend::getMaxTransmissionsPerMessage() transmissions, based on message size. (max three transmissions per message is the default) + +namespace EspnowProtocolInterpreter +{ + const String synchronizationRequestHeader = "Synchronization request."; + const String encryptionRequestHeader = "AddEC:"; // Add encrypted connection + const String temporaryEncryptionRequestHeader = "AddTEC:"; // Add temporary encrypted connection + const String basicConnectionInfoHeader = "BasicCI:"; // Basic connection info + const String encryptedConnectionInfoHeader = "EncryptedCI:"; // Encrypted connection info + const String maxConnectionsReachedHeader = "ECS_MAX_CONNECTIONS_REACHED_PEER:"; + const String encryptedConnectionVerificationHeader = "ECVerified:"; // Encrypted connection verified + const String encryptedConnectionRemovalRequestHeader = "RemoveEC:"; // Remove encrypted connection + + const uint8_t espnowMessageTypeIndex = 0; + const uint8_t espnowTransmissionsRemainingIndex = 1; + const uint8_t espnowTransmissionMacIndex = 2; + const uint8_t espnowMessageIDIndex = 8; + + uint8_t espnowProtocolBytesSize(); + + const uint8_t espnowEncryptionKeyLength = 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. + + String espnowGetMessageContent(uint8_t *transmission, uint8_t transmissionLength); + char espnowGetMessageType(const uint8_t *transmissionDataArray); + uint8_t espnowGetTransmissionsRemaining(const uint8_t *transmissionDataArray); + bool espnowIsMessageStart(const uint8_t *transmissionDataArray); + uint64_t espnowGetTransmissionMac(const uint8_t *transmissionDataArray); + uint8_t *espnowGetTransmissionMac(const uint8_t *transmissionDataArray, uint8_t *resultArray); + uint64_t espnowGetMessageID(const uint8_t *transmissionDataArray); + // @return a pointer to transmissionDataArray + uint8_t *espnowSetMessageID(uint8_t *transmissionDataArray, uint64_t messageID); + + bool usesEncryption(uint64_t messageID); +} + +#endif diff --git a/libraries/ESP8266WiFiMesh/src/ExpiringTimeTracker.cpp b/libraries/ESP8266WiFiMesh/src/ExpiringTimeTracker.cpp new file mode 100644 index 0000000000..6c17d2dc21 --- /dev/null +++ b/libraries/ESP8266WiFiMesh/src/ExpiringTimeTracker.cpp @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2019 Anders Löfgren + * + * License (MIT license): + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include "ExpiringTimeTracker.h" + +ExpiringTimeTracker::ExpiringTimeTracker(uint32_t duration, uint32_t creationTimeMs) : + TimeTracker(creationTimeMs), _duration(duration) +{ } + +uint32_t ExpiringTimeTracker::duration() const +{ + return _duration; +} + +void ExpiringTimeTracker::setRemainingDuration(uint32_t remainingDuration) +{ + _duration = timeSinceCreation() + remainingDuration; +} + +uint32_t ExpiringTimeTracker::remainingDuration() const +{ + uint32_t remainingDuration = duration() - timeSinceCreation(); + + if(expired()) + { + // Overflow probably occured for remainingDuration calculation. + return 0; + } + else + { + return remainingDuration; + } +} + +bool ExpiringTimeTracker::expired() const +{ + return timeSinceCreation() > duration(); +} \ No newline at end of file diff --git a/libraries/ESP8266WiFiMesh/src/ExpiringTimeTracker.h b/libraries/ESP8266WiFiMesh/src/ExpiringTimeTracker.h new file mode 100644 index 0000000000..32fdd94407 --- /dev/null +++ b/libraries/ESP8266WiFiMesh/src/ExpiringTimeTracker.h @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2019 Anders Löfgren + * + * License (MIT license): + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#ifndef __EXPIRINGTIMETRACKER_H__ +#define __EXPIRINGTIMETRACKER_H__ + +#include "TimeTracker.h" +#include + +class ExpiringTimeTracker : public TimeTracker { + +public: + + ~ExpiringTimeTracker() override = default; + + ExpiringTimeTracker(uint32_t duration, uint32_t creationTimeMs = millis()); + uint32_t duration() const; + void setRemainingDuration(uint32_t remainingDuration); + uint32_t remainingDuration() const; + bool expired() const; + +private: + + uint32_t _duration; +}; + +#endif diff --git a/libraries/ESP8266WiFiMesh/src/JsonTranslator.cpp b/libraries/ESP8266WiFiMesh/src/JsonTranslator.cpp new file mode 100644 index 0000000000..c92f5f1577 --- /dev/null +++ b/libraries/ESP8266WiFiMesh/src/JsonTranslator.cpp @@ -0,0 +1,269 @@ +/* + * Copyright (C) 2019 Anders Löfgren + * + * License (MIT license): + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include "JsonTranslator.h" +#include "Crypto.h" +#include "EspnowProtocolInterpreter.h" +#include "TypeConversionFunctions.h" + +namespace JsonTranslator +{ + String createJsonPair(const String &valueIdentifier, const String &value) + { + return valueIdentifier + "\"" + value + "\","; + } + + String createJsonEndPair(const String &valueIdentifier, const String &value) + { + return valueIdentifier + "\"" + value + "\"}}"; + } + + uint8_t *createHmac(const String &message, const uint8_t *hashKey, uint8_t hashKeyLength, uint8_t resultArray[SHA256HMAC_SIZE]) + { + // Create the HMAC instance with our key + SHA256HMAC hmac(hashKey, hashKeyLength); + + // Update the HMAC with our message + hmac.doUpdate(message.c_str()); + + // Finish the HMAC calculation and return the authentication code + hmac.doFinal(resultArray); + + // resultArray now contains our SHA256HMAC_SIZE byte authentication code + return resultArray; + } + + String createHmac(const String &message, const uint8_t *hashKey, uint8_t hashKeyLength) + { + byte hmac[SHA256HMAC_SIZE]; + createHmac(message, hashKey, hashKeyLength, hmac); + return uint8ArrayToHexString(hmac, SHA256HMAC_SIZE); + } + + bool verifyHmac(const String &message, const String &messageHmac, const uint8_t *hashKey, uint8_t hashKeyLength) + { + if(messageHmac.length() != 2*SHA256HMAC_SIZE) // We know that each HMAC byte should become 2 String characters due to uint8ArrayToHexString. + return false; + + String generatedHmac = createHmac(message, hashKey, hashKeyLength); + if(generatedHmac == messageHmac) + return true; + else + return false; + } + + bool verifyHmac(const String &encryptionRequestHmacMessage, const uint8_t *hashKey, uint8_t hashKeyLength) + { + String hmac = ""; + if(getHmac(encryptionRequestHmacMessage, hmac)) + { + int32_t hmacStartIndex = encryptionRequestHmacMessage.indexOf(jsonHmac); + if(hmacStartIndex < 0) + return false; + + if(verifyHmac(encryptionRequestHmacMessage.substring(0, hmacStartIndex), hmac, hashKey, hashKeyLength)) + { + return true; + } + } + + return false; + } + + String createEncryptedConnectionInfo(const String &requestNonce, const String &authenticationPassword, uint64_t ownSessionKey, uint64_t peerSessionKey) + { + // Returns: Encrypted connection info:{"arguments":{"nonce":"1F2","password":"abc","ownSessionKey":"3B4","peerSessionKey":"1A2"}} + + + return + EspnowProtocolInterpreter::encryptedConnectionInfoHeader + "{\"arguments\":{" + + createJsonPair(jsonNonce, requestNonce) + + createJsonPair(jsonPassword, authenticationPassword) + + createJsonPair(jsonOwnSessionKey, uint64ToString(peerSessionKey)) // Exchanges session keys since it should be valid for the receiver. + + createJsonEndPair(jsonPeerSessionKey, uint64ToString(ownSessionKey)); + } + + String createEncryptionRequestIntro(const String &requestHeader, uint32_t duration) + { + return + requestHeader + "{\"arguments\":{" + + (requestHeader == EspnowProtocolInterpreter::temporaryEncryptionRequestHeader ? createJsonPair(jsonDuration, String(duration)) : ""); + } + + String createEncryptionRequestEnding(const String &requestNonce) + { + return createJsonEndPair(jsonNonce, requestNonce); + } + + String createEncryptionRequestMessage(const String &requestHeader, const String &requestNonce, uint32_t duration) + { + return createEncryptionRequestIntro(requestHeader, duration) + createEncryptionRequestEnding(requestNonce); + } + + String createEncryptionRequestHmacMessage(const String &requestHeader, const String &requestNonce, const uint8_t *hashKey, uint8_t hashKeyLength, uint32_t duration) + { + String mainMessage = createEncryptionRequestIntro(requestHeader, duration) + createJsonPair(jsonNonce, requestNonce); + String hmac = createHmac(mainMessage, hashKey, hashKeyLength); + return mainMessage + createJsonEndPair(jsonHmac, hmac); + } + + int32_t getStartIndex(const String &jsonString, const String &valueIdentifier, int32_t searchStartIndex) + { + int32_t startIndex = jsonString.indexOf(valueIdentifier, searchStartIndex); + if(startIndex < 0) + return startIndex; + + startIndex += valueIdentifier.length() + 1; // Do not include valueIdentifier and initial quotation mark + return startIndex; + } + + int32_t getEndIndex(const String &jsonString, int32_t searchStartIndex) + { + int32_t endIndex = jsonString.indexOf(',', searchStartIndex); + if(endIndex < 0) + endIndex = jsonString.indexOf('}', searchStartIndex); + + endIndex -= 1; // End index will be at the character after the closing quotation mark, so need to subtract 1. + + return endIndex; + } + + bool getPassword(const String &jsonString, String &result) + { + int32_t startIndex = getStartIndex(jsonString, jsonPassword); + if(startIndex < 0) + return false; + + int32_t endIndex = getEndIndex(jsonString, startIndex); + if(endIndex < 0) + return false; + + result = jsonString.substring(startIndex, endIndex); + return true; + } + + bool getOwnSessionKey(const String &jsonString, uint64_t &result) + { + int32_t startIndex = getStartIndex(jsonString, jsonOwnSessionKey); + if(startIndex < 0) + return false; + + int32_t endIndex = getEndIndex(jsonString, startIndex); + if(endIndex < 0) + return false; + + result = stringToUint64(jsonString.substring(startIndex, endIndex)); + return true; + } + + bool getPeerSessionKey(const String &jsonString, uint64_t &result) + { + int32_t startIndex = getStartIndex(jsonString, jsonPeerSessionKey); + if(startIndex < 0) + return false; + + int32_t endIndex = getEndIndex(jsonString, startIndex); + if(endIndex < 0) + return false; + + result = stringToUint64(jsonString.substring(startIndex, endIndex)); + return true; + } + + bool getPeerStaMac(const String &jsonString, uint8_t *resultArray) + { + int32_t startIndex = getStartIndex(jsonString, jsonPeerStaMac); + if(startIndex < 0) + return false; + + int32_t endIndex = getEndIndex(jsonString, startIndex); + if(endIndex < 0 || endIndex - startIndex != 12) // Mac String is always 12 characters long + return false; + + stringToMac(jsonString.substring(startIndex, endIndex), resultArray); + return true; + } + + bool getPeerApMac(const String &jsonString, uint8_t *resultArray) + { + int32_t startIndex = getStartIndex(jsonString, jsonPeerApMac); + if(startIndex < 0) + return false; + + int32_t endIndex = getEndIndex(jsonString, startIndex); + if(endIndex < 0 || endIndex - startIndex != 12) // Mac String is always 12 characters long + return false; + + stringToMac(jsonString.substring(startIndex, endIndex), resultArray); + return true; + } + + bool getDuration(const String &jsonString, uint32_t &result) + { + int32_t startIndex = getStartIndex(jsonString, jsonDuration); + if(startIndex < 0) + return false; + + result = strtoul(jsonString.substring(startIndex).c_str(), nullptr, 0); // strtoul stops reading input when an invalid character is discovered. + return true; + } + + bool getNonce(const String &jsonString, String &result) + { + int32_t startIndex = getStartIndex(jsonString, jsonNonce); + if(startIndex < 0) + return false; + + int32_t endIndex = getEndIndex(jsonString, startIndex); + if(endIndex < 0) + return false; + + result = jsonString.substring(startIndex, endIndex); + return true; + } + + bool getHmac(const String &jsonString, String &result) + { + int32_t startIndex = getStartIndex(jsonString, jsonHmac); + if(startIndex < 0) + return false; + + int32_t endIndex = getEndIndex(jsonString, startIndex); + if(endIndex < 0) + return false; + + result = jsonString.substring(startIndex, endIndex); + return true; + } + + bool getDesync(const String &jsonString, bool &result) + { + int32_t startIndex = getStartIndex(jsonString, jsonDesync); + if(startIndex < 0) + return false; + + result = bool(strtoul(jsonString.substring(startIndex).c_str(), nullptr, 0)); // strtoul stops reading input when an invalid character is discovered. + return true; + } +} diff --git a/libraries/ESP8266WiFiMesh/src/JsonTranslator.h b/libraries/ESP8266WiFiMesh/src/JsonTranslator.h new file mode 100644 index 0000000000..e03b468b03 --- /dev/null +++ b/libraries/ESP8266WiFiMesh/src/JsonTranslator.h @@ -0,0 +1,109 @@ +/* + * Copyright (C) 2019 Anders Löfgren + * + * License (MIT license): + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#ifndef __ESPNOWJSONTRANSLATOR_H__ +#define __ESPNOWJSONTRANSLATOR_H__ + +#include +#include "Crypto.h" + +namespace JsonTranslator +{ + const String jsonPassword = "\"password\":"; + const String jsonOwnSessionKey = "\"ownSK\":"; + const String jsonPeerSessionKey = "\"peerSK\":"; + const String jsonPeerStaMac = "\"peerStaMac\":"; + const String jsonPeerApMac = "\"peerApMac\":"; + const String jsonDuration = "\"duration\":"; + const String jsonNonce = "\"nonce\":"; + const String jsonHmac = "\"hmac\":"; + const String jsonDesync = "\"desync\":"; + + String createJsonPair(const String &valueIdentifier, const String &value); + String createJsonEndPair(const String &valueIdentifier, const String &value); + + uint8_t *createHmac(const String &message, const uint8_t *hashKey, uint8_t hashKeyLength, uint8_t resultArray[SHA256HMAC_SIZE]); + String createHmac(const String &message, const uint8_t *hashKey, uint8_t hashKeyLength); + + bool verifyHmac(const String &message, const String &messageHmac, const uint8_t *hashKey, uint8_t hashKeyLength); + bool verifyHmac(const String &encryptionRequestHmacMessage, const uint8_t *hashKey, uint8_t hashKeyLength); + + String createEncryptedConnectionInfo(const String &requestNonce, const String &authenticationPassword, uint64_t ownSessionKey, uint64_t peerSessionKey); + String createEncryptionRequestIntro(const String &requestHeader, uint32_t duration = 0); + String createEncryptionRequestEnding(const String &requestNonce); + String createEncryptionRequestMessage(const String &requestHeader, const String &requestNonce, uint32_t duration = 0); + String createEncryptionRequestHmacMessage(const String &requestHeader, const String &requestNonce, const uint8_t *hashKey, uint8_t hashKeyLength, uint32_t duration = 0); + + /** + * Provides the index within jsonString where the value of valueIdentifier starts. + * + * @param jsonString The String to search within. + * @param valueIdentifier The identifier to search for. + * @param searchStartIndex Optional argument that makes it possible to decide at which index of jsonString the search starts. Search will begin at index 0 if not provided. + * + * @returns An int32_t containing the index within jsonString where the value of valueIdentifier starts, or a negative value if valueIdentifier was not found. + */ + int32_t getStartIndex(const String &jsonString, const String &valueIdentifier, int32_t searchStartIndex = 0); + + /** + * Provides the index within jsonString where the next JSON termination character (',' or '}') is found, starting from searchStartIndex. + * + * @param jsonString The String to search within. + * @param searchStartIndex The index of jsonString where the search will start. + * + * @returns An int32_t containing the index within jsonString where the next JSON termination character is found, or a negative value if no such character was found. + */ + int32_t getEndIndex(const String &jsonString, int32_t searchStartIndex); + + /** + * Stores the value of the password field within jsonString into the result variable. + * No changes to the result variable are made if jsonString does not contain a password. + * + * @param jsonString The String to search within. + * @param result The String where the value should be stored. + * + * @returns True if a value was found. False otherwise. + */ + bool getPassword(const String &jsonString, String &result); + bool getOwnSessionKey(const String &jsonString, uint64_t &result); + bool getPeerSessionKey(const String &jsonString, uint64_t &result); + + /** + * Stores the value of the peerStaMac field within jsonString into the resultArray. + * No changes to the resultArray are made if jsonString does not contain a peerStaMac. + * + * @param jsonString The String to search within. + * @param resultArray The uint8_t array where the value should be stored. Must be at least 6 bytes. + * + * @returns True if a value was found. False otherwise. + */ + bool getPeerStaMac(const String &jsonString, uint8_t *resultArray); + bool getPeerApMac(const String &jsonString, uint8_t *resultArray); + bool getDuration(const String &jsonString, uint32_t &result); + bool getNonce(const String &jsonString, String &result); + bool getHmac(const String &jsonString, String &result); + bool getDesync(const String &jsonString, bool &result); +} + +#endif diff --git a/libraries/ESP8266WiFiMesh/src/MeshBackendBase.cpp b/libraries/ESP8266WiFiMesh/src/MeshBackendBase.cpp new file mode 100644 index 0000000000..f9454ddbd8 --- /dev/null +++ b/libraries/ESP8266WiFiMesh/src/MeshBackendBase.cpp @@ -0,0 +1,276 @@ +/* + MeshBackendBase + + Copyright (c) 2015 Julian Fell and 2019 Anders Löfgren. All rights reserved. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include "MeshBackendBase.h" + +#include + +MeshBackendBase *MeshBackendBase::apController = nullptr; + +std::vector MeshBackendBase::connectionQueue = {}; +std::vector MeshBackendBase::latestTransmissionOutcomes = {}; + +bool MeshBackendBase::_printWarnings = true; + +MeshBackendBase::MeshBackendBase(requestHandlerType requestHandler, responseHandlerType responseHandler, networkFilterType networkFilter, mesh_backend_t classType) +{ + setRequestHandler(requestHandler); + setResponseHandler(responseHandler); + setNetworkFilter(networkFilter); + setClassType(classType); +} + +MeshBackendBase::~MeshBackendBase() +{ + deactivateAP(); +} + +void MeshBackendBase::setClassType(mesh_backend_t classType) +{ + _classType = classType; +} + +mesh_backend_t MeshBackendBase::getClassType() {return _classType;} + +void MeshBackendBase::activateAP() +{ + // Deactivate active AP to avoid two servers using the same port, which can lead to crashes. + if(MeshBackendBase *currentAPController = MeshBackendBase::getAPController()) + currentAPController->deactivateAP(); + + activateAPHook(); + + WiFi.mode(WIFI_AP_STA); + + apController = this; +} + +void MeshBackendBase::activateAPHook() +{ + WiFi.softAP( getSSID().c_str(), getMeshPassword().c_str(), getWiFiChannel(), getAPHidden() ); // Note that a maximum of 8 TCP/IP stations can be connected at a time to each AP, max 4 by default. +} + +void MeshBackendBase::deactivateAP() +{ + if(isAPController()) + { + deactivateAPHook(); + + WiFi.softAPdisconnect(); + WiFi.mode(WIFI_STA); + + // Since there is no active AP controller now, make the apController variable point to nothing. + apController = nullptr; + } +} + +void MeshBackendBase::deactivateAPHook() +{ +} + +void MeshBackendBase::restartAP() +{ + deactivateAP(); + yield(); + activateAP(); + yield(); +} + +MeshBackendBase *MeshBackendBase::getAPController() +{ + return apController; +} + +bool MeshBackendBase::isAPController() +{ + return (this == getAPController()); +} + +void MeshBackendBase::setWiFiChannel(uint8 newWiFiChannel) +{ + assert(1 <= newWiFiChannel && newWiFiChannel <= 13); + + _meshWiFiChannel = newWiFiChannel; + + // WiFi.channel() will change if this node connects to an AP with another channel, + // so there is no guarantee we are using _meshWiFiChannel. + // Also, we cannot change the WiFi channel while we are still connected to the other AP. + if(WiFi.channel() != getWiFiChannel() && WiFi.status() != WL_CONNECTED) + { + // Apply changes to active AP. + if(isAPController()) + restartAP(); + } +} + +uint8 MeshBackendBase::getWiFiChannel() +{ + return _meshWiFiChannel; +} + +void MeshBackendBase::setSSID(const String &newSSIDPrefix, const String &newSSIDRoot, const String &newSSIDSuffix) +{ + if(newSSIDPrefix != "") + _SSIDPrefix = newSSIDPrefix; + if(newSSIDRoot != "") + _SSIDRoot = newSSIDRoot; + if(newSSIDSuffix != "") + _SSIDSuffix = newSSIDSuffix; + + String newSSID = _SSIDPrefix + _SSIDRoot + _SSIDSuffix; + + if(getSSID() != newSSID) + { + _SSID = newSSID; + + // Apply SSID changes to active AP. + if(isAPController()) + restartAP(); + } +} + +String MeshBackendBase::getSSID() {return _SSID;} + +void MeshBackendBase::setSSIDPrefix(const String &newSSIDPrefix) +{ + setSSID(newSSIDPrefix); +} + +String MeshBackendBase::getSSIDPrefix() {return _SSIDPrefix;} + +void MeshBackendBase::setSSIDRoot(const String &newSSIDRoot) +{ + setSSID("", newSSIDRoot); +} + +String MeshBackendBase::getSSIDRoot() {return _SSIDRoot;} + +void MeshBackendBase::setSSIDSuffix(const String &newSSIDSuffix) +{ + setSSID("", "", newSSIDSuffix); +} + +String MeshBackendBase::getSSIDSuffix() {return _SSIDSuffix;} + +void MeshBackendBase::setMeshName(const String &newMeshName) +{ + setSSIDPrefix(newMeshName); +} + +String MeshBackendBase::getMeshName() {return getSSIDPrefix();} + +void MeshBackendBase::setNodeID(const String &newNodeID) +{ + setSSIDSuffix(newNodeID); +} + +String MeshBackendBase::getNodeID() {return getSSIDSuffix();} + +void MeshBackendBase::setMeshPassword(const String &newMeshPassword) +{ + assert(8 <= newMeshPassword.length() && newMeshPassword.length() <= 64); // Limited by the ESP8266 API. + + _meshPassword = newMeshPassword; + + // Apply changes to active AP. + if(isAPController()) + restartAP(); +} + +String MeshBackendBase::getMeshPassword() {return _meshPassword;} + +void MeshBackendBase::setMessage(const String &newMessage) {_message = newMessage;} +String MeshBackendBase::getMessage() {return _message;} + +void MeshBackendBase::setRequestHandler(MeshBackendBase::requestHandlerType requestHandler) {_requestHandler = requestHandler;} +MeshBackendBase::requestHandlerType MeshBackendBase::getRequestHandler() {return _requestHandler;} + +void MeshBackendBase::setResponseHandler(MeshBackendBase::responseHandlerType responseHandler) {_responseHandler = responseHandler;} +MeshBackendBase::responseHandlerType MeshBackendBase::getResponseHandler() {return _responseHandler;} + +void MeshBackendBase::setNetworkFilter(MeshBackendBase::networkFilterType networkFilter) {_networkFilter = networkFilter;} +MeshBackendBase::networkFilterType MeshBackendBase::getNetworkFilter() {return _networkFilter;} + +void MeshBackendBase::setScanHidden(bool scanHidden) +{ + _scanHidden = scanHidden; +} + +bool MeshBackendBase::getScanHidden() {return _scanHidden;} + +void MeshBackendBase::setAPHidden(bool apHidden) +{ + if(getAPHidden() != apHidden) + { + _apHidden = apHidden; + + // Apply changes to active AP. + if(isAPController()) + restartAP(); + } +} + +bool MeshBackendBase::getAPHidden() {return _apHidden;} + +bool MeshBackendBase::latestTransmissionSuccessful() +{ + if(MeshBackendBase::latestTransmissionOutcomes.empty()) + return false; + else + for(TransmissionResult &transmissionResult : MeshBackendBase::latestTransmissionOutcomes) + if(transmissionResult.transmissionStatus != TS_TRANSMISSION_COMPLETE) + return false; + + return true; +} + +void MeshBackendBase::scanForNetworks(bool scanAllWiFiChannels) +{ + verboseModePrint(F("Scanning... "), false); + + /* Scan for APs */ + connectionQueue.clear(); + + // If scanAllWiFiChannels is true, scanning will cause the WiFi radio to cycle through all WiFi channels. + // This means existing WiFi connections are likely to break or work poorly if done frequently. + int n = 0; + if(scanAllWiFiChannels) + { + n = WiFi.scanNetworks(false, getScanHidden()); + } + else + { + // Scan function argument overview: scanNetworks(bool async = false, bool show_hidden = false, uint8 channel = 0, uint8* ssid = NULL) + n = WiFi.scanNetworks(false, getScanHidden(), getWiFiChannel()); + } + + getNetworkFilter()(n, *this); // Update the connectionQueue. +} + +void MeshBackendBase::printAPInfo(const int apNetworkIndex, const String &apSSID, const int apWiFiChannel) +{ + verboseModePrint(String(F("AP acquired: ")) + apSSID + String(F(", Ch:")) + String(apWiFiChannel) + " ", false); + + if(apNetworkIndex != NETWORK_INFO_DEFAULT_INT) + { + verboseModePrint("(" + String(WiFi.RSSI(apNetworkIndex)) + String(F("dBm) ")) + + (WiFi.encryptionType(apNetworkIndex) == ENC_TYPE_NONE ? String(F("open")) : ""), false); + } + + verboseModePrint(F("... "), false); +} diff --git a/libraries/ESP8266WiFiMesh/src/MeshBackendBase.h b/libraries/ESP8266WiFiMesh/src/MeshBackendBase.h new file mode 100644 index 0000000000..cfaae01e80 --- /dev/null +++ b/libraries/ESP8266WiFiMesh/src/MeshBackendBase.h @@ -0,0 +1,302 @@ +/* + MeshBackendBase + + Copyright (c) 2015 Julian Fell and 2019 Anders Löfgren. All rights reserved. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#ifndef __MESHBACKENDBASE_H__ +#define __MESHBACKENDBASE_H__ + +#include +#include "TransmissionResult.h" + +const String ESP8266_MESH_EMPTY_STRING = ""; + +typedef enum +{ + MB_TCP_IP = 0, + MB_ESP_NOW = 1 +} mesh_backend_t; + +class MeshBackendBase { + +protected: + + typedef std::function requestHandlerType; + typedef std::function responseHandlerType; + typedef std::function networkFilterType; + +public: + + MeshBackendBase(requestHandlerType requestHandler, responseHandlerType responseHandler, networkFilterType networkFilter, mesh_backend_t classType); + + virtual ~MeshBackendBase(); + + /** + * A vector that contains the NetworkInfo for each WiFi network to connect to. + * The connectionQueue vector is cleared before each new scan and filled via the networkFilter callback function once the scan completes. + * WiFi connections will start with connectionQueue[0] and then incrementally proceed to higher vector positions. + * Note that old network indicies often are invalidated whenever a new WiFi network scan occurs. + */ + static std::vector connectionQueue; + + /** + * A vector with the TransmissionResult for each AP to which a transmission was attempted during the latest attemptTransmission call. + * The latestTransmissionOutcomes vector is cleared before each new transmission attempt. + * Connection attempts are indexed in the same order they were attempted. + * Note that old network indicies often are invalidated whenever a new WiFi network scan occurs. + */ + static std::vector latestTransmissionOutcomes; + + /** + * @returns True if latest transmission was successful (i.e. latestTransmissionOutcomes is not empty and all entries have transmissionStatus TS_TRANSMISSION_COMPLETE). False otherwise. + */ + static bool latestTransmissionSuccessful(); + + /** + * Initialises the node. + */ + 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. + * This is managed automatically by the activateAP method. + */ + void activateAP(); + void deactivateAP(); + void restartAP(); + + /** + * Get the MeshBackendBase instance currently in control of the ESP8266 AP. + * Note that the result will be nullptr when there is no active AP controller. + * If another instance takes control over the AP after the pointer is created, + * the created pointer will still point to the old AP instance. + * + * @returns A pointer to the MeshBackendBase instance currently in control of the ESP8266 AP, + * or nullptr if there is no active AP controller. + */ + static MeshBackendBase *getAPController(); + + /** + * Check if this MeshBackendBase instance is in control of the ESP8266 AP. + * + * @returns True if this MeshBackendBase instance is in control of the ESP8266 AP. False otherwise. + */ + bool isAPController(); + + /** + * Change the WiFi channel used by this MeshBackendBase instance. + * Will also change the WiFi channel for the active AP if this MeshBackendBase instance is the current AP controller and it is possible to change channel. + * + * WARNING: The ESP8266 has only one WiFi channel, and the the station/client mode is always prioritized for channel selection. + * This can cause problems if several MeshBackendBase instances exist on the same ESP8266 and use different WiFi channels. + * In such a case, whenever the station of one MeshBackendBase instance connects to an AP, it will silently force the + * WiFi channel of any active AP on the ESP8266 to match that of the station. This will cause disconnects and possibly + * make it impossible for other stations to detect the APs whose WiFi channels have changed. + * + * @param newWiFiChannel The WiFi channel to change to. Valid values are integers from 1 to 13. + * + */ + void setWiFiChannel(uint8 newWiFiChannel); + uint8 getWiFiChannel(); + + /** + * Change the SSID used by this MeshBackendBase instance. + * Will also change the SSID for the active AP if this MeshBackendBase instance is the current AP controller. + * + * @param newSSIDPrefix The first part of the new SSID. + * @param newSSIDRoot The middle part of the new SSID. + * @param newSSIDSuffix The last part of the new SSID. + */ + void setSSID(const String &newSSIDPrefix = ESP8266_MESH_EMPTY_STRING, const String &newSSIDRoot = ESP8266_MESH_EMPTY_STRING, + const String &newSSIDSuffix = ESP8266_MESH_EMPTY_STRING); + String getSSID(); + + /** + * Change the first part of the SSID used by this MeshBackendBase instance. + * Will also change the first part of the SSID for the active AP if this MeshBackendBase instance is the current AP controller. + * + * @param newSSIDPrefix The new first part of the SSID. + */ + void setSSIDPrefix(const String &newSSIDPrefix); + String getSSIDPrefix(); + + /** + * Change the middle part of the SSID used by this MeshBackendBase instance. + * Will also change the middle part of the SSID for the active AP if this MeshBackendBase instance is the current AP controller. + * + * @param newSSIDPrefix The new middle part of the SSID. + */ + void setSSIDRoot(const String &newSSIDRoot); + String getSSIDRoot(); + + /** + * Change the last part of the SSID used by this MeshBackendBase instance. + * Will also change the last part of the SSID for the active AP if this MeshBackendBase instance is the current AP controller. + * + * @param newSSIDSuffix The new last part of the SSID. + */ + void setSSIDSuffix(const String &newSSIDSuffix); + String getSSIDSuffix(); + + /** + * Change the mesh name used by this MeshBackendBase instance. + * Will also change the mesh name for the active AP if this MeshBackendBase instance is the current AP controller. + * Used as alias for setSSIDPrefix by default. Feel free to override this method in a subclass if your mesh name is not equal to SSIDPrefix. + * + * @param newMeshName The mesh name to change to. + */ + virtual void setMeshName(const String &newMeshName); + virtual String getMeshName(); + + /** + * Change the node id used by this MeshBackendBase instance. + * Will also change the node id for the active AP if this MeshBackendBase instance is the current AP controller. + * Used as alias for setSSIDSuffix by default. Feel free to override this method in a subclass if your node id is not equal to SSIDSuffix. + * + * @param newNodeID The node id to change to. + */ + virtual void setNodeID(const String &newNodeID); + virtual String getNodeID(); + + /** + * Set the password used when connecting to other AP:s and when other nodes connect to the AP of this node. + * Will also change the setting for the active AP if this MeshBackendBase instance is the current AP controller. + * + * @param newMeshPassword The password to use. + */ + void setMeshPassword(const String &newMeshPassword); + String getMeshPassword(); + + /** + * Set the message that will be sent to other nodes when calling attemptTransmission. + * + * @param newMessage The message to send. + */ + void setMessage(const String &newMessage); + String getMessage(); + + virtual void attemptTransmission(const String &message, bool scan = true, bool scanAllWiFiChannels = false) = 0; + + void setRequestHandler(requestHandlerType requestHandler); + requestHandlerType getRequestHandler(); + + void setResponseHandler(responseHandlerType responseHandler); + responseHandlerType getResponseHandler(); + + void setNetworkFilter(networkFilterType networkFilter); + networkFilterType getNetworkFilter(); + + /** + * Set whether scan results from this MeshBackendBase instance will include WiFi networks with hidden SSIDs. + * This is false by default. + * The SSID field of a found hidden network will be blank in the scan results. + * WiFi.isHidden(networkIndex) can be used to verify that a found network is hidden. + * + * @param scanHidden If true, WiFi networks with hidden SSIDs will be included in scan results. + */ + void setScanHidden(bool scanHidden); + bool getScanHidden(); + + /** + * Set whether the AP controlled by this MeshBackendBase instance will have a WiFi network with hidden SSID. + * This is false by default. + * Will also change the setting for the active AP if this MeshBackendBase instance is the current AP controller. + * + * @param apHidden If true, the WiFi network created will have a hidden SSID. + */ + void setAPHidden(bool apHidden); + bool getAPHidden(); + + /** + * Set whether the normal events occurring in the library will be printed to Serial or not. Off by default. + * This setting is separate for each mesh instance. + * + * @param enabled If true, library Serial prints are activated. + */ + virtual void setVerboseModeState(bool enabled); + virtual bool verboseMode(); + + /** + * Only print stringToPrint if verboseMode() returns true. + * + * @param stringToPrint String to print. + * @param newline If true, will end the print with a newline. True by default. + */ + virtual void verboseModePrint(const String &stringToPrint, bool newline = true); + + /** + * Set whether the warnings occurring in the library will be printed to Serial or not. On by default. + * This setting will affect all mesh instances. + * + * @param printEnabled If true, warning Serial prints from the library are activated. + */ + static void setPrintWarnings(bool printEnabled); + static bool printWarnings(); + + /** + * Only print stringToPrint if printWarnings() returns true. + * + * @param stringToPrint String to print. + * @param newline If true, will end the print with a newline. True by default. + */ + static void warningPrint(const String &stringToPrint, bool newline = true); + + mesh_backend_t getClassType(); + +protected: + + virtual void scanForNetworks(bool scanAllWiFiChannels); + virtual void printAPInfo(const int apNetworkIndex, const String &apSSID, const int apWiFiChannel); + + /** + * Called just before we activate the AP. + * Put _server.stop() in deactivateAPHook() in case you use _server.begin() here. + */ + virtual void activateAPHook(); + + /** + * Called just before we deactivate the AP. + * Put _server.stop() here in case you use _server.begin() in activateAPHook(). + */ + virtual void deactivateAPHook(); + + void setClassType(mesh_backend_t classType); + +private: + + mesh_backend_t _classType; + + static MeshBackendBase *apController; + + String _SSID; + String _SSIDPrefix; + String _SSIDRoot; + String _SSIDSuffix; + String _meshPassword; + uint8 _meshWiFiChannel; + bool _verboseMode; + String _message = ESP8266_MESH_EMPTY_STRING; + bool _scanHidden = false; + bool _apHidden = false; + + requestHandlerType _requestHandler; + responseHandlerType _responseHandler; + networkFilterType _networkFilter; + + static bool _printWarnings; +}; + +#endif diff --git a/libraries/ESP8266WiFiMesh/src/MessageData.cpp b/libraries/ESP8266WiFiMesh/src/MessageData.cpp new file mode 100644 index 0000000000..f0070c2f5f --- /dev/null +++ b/libraries/ESP8266WiFiMesh/src/MessageData.cpp @@ -0,0 +1,69 @@ +/* + * Copyright (C) 2019 Anders Löfgren + * + * License (MIT license): + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include "MessageData.h" +#include "EspnowProtocolInterpreter.h" +#include "EspnowMeshBackend.h" +#include + +MessageData::MessageData(uint8_t *initialTransmission, uint8_t transmissionLength,uint32_t creationTimeMs) : + TimeTracker(creationTimeMs) +{ + _transmissionsExpected = EspnowProtocolInterpreter::espnowGetTransmissionsRemaining(initialTransmission) + 1; + addToMessage(initialTransmission, transmissionLength); +} + +bool MessageData::addToMessage(uint8_t *transmission, uint8_t transmissionLength) +{ + if(EspnowProtocolInterpreter::espnowGetTransmissionsRemaining(transmission) == getTransmissionsRemaining() - 1) + { + String message = EspnowProtocolInterpreter::espnowGetMessageContent(transmission, transmissionLength); + assert(message.length() <= EspnowMeshBackend::getMaxMessageBytesPerTransmission()); // Should catch some cases where transmission is not null terminated. + _totalMessage += message; + _transmissionsReceived++; + return true; + } + + return false; +} + +uint8_t MessageData::getTransmissionsReceived() +{ + return _transmissionsReceived; +} + +uint8_t MessageData::getTransmissionsExpected() +{ + return _transmissionsExpected; +} + +uint8_t MessageData::getTransmissionsRemaining() +{ + return getTransmissionsExpected() - getTransmissionsReceived(); +} + +String MessageData::getTotalMessage() +{ + return _totalMessage; +} diff --git a/libraries/ESP8266WiFiMesh/src/MessageData.h b/libraries/ESP8266WiFiMesh/src/MessageData.h new file mode 100644 index 0000000000..2d9ad9da78 --- /dev/null +++ b/libraries/ESP8266WiFiMesh/src/MessageData.h @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2019 Anders Löfgren + * + * License (MIT license): + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#ifndef __ESPNOWMESSAGEDATA_H__ +#define __ESPNOWMESSAGEDATA_H__ + +#include "TimeTracker.h" +#include + +class MessageData : public TimeTracker { + +public: + + MessageData(uint8_t *initialTransmission, uint8_t transmissionLength, uint32_t creationTimeMs = millis()); + /** + * @transmission A string of characters, including initial protocol bytes. + * @transmissionLength Length of transmission. + */ + bool addToMessage(uint8_t *transmission, uint8_t transmissionLength); + uint8_t getTransmissionsReceived(); + uint8_t getTransmissionsExpected(); + uint8_t getTransmissionsRemaining(); + String getTotalMessage(); + +private: + + uint8_t _transmissionsReceived = 0; + uint8_t _transmissionsExpected; + String _totalMessage = ""; + +}; + +#endif diff --git a/libraries/ESP8266WiFiMesh/src/MutexTracker.cpp b/libraries/ESP8266WiFiMesh/src/MutexTracker.cpp new file mode 100644 index 0000000000..97e84a75eb --- /dev/null +++ b/libraries/ESP8266WiFiMesh/src/MutexTracker.cpp @@ -0,0 +1,72 @@ +/* + * Copyright (C) 2019 Anders Löfgren + * + * License (MIT license): + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include "MutexTracker.h" + +MutexTracker::MutexTracker(bool &mutexToCapture) +{ + attemptMutexCapture(mutexToCapture); +} + +MutexTracker::MutexTracker(bool &mutexToCapture, std::function destructorHook) : MutexTracker(mutexToCapture) +{ + _destructorHook = destructorHook; +} + +MutexTracker::~MutexTracker() +{ + releaseMutex(); + _destructorHook(); +} + +bool MutexTracker::mutexCaptured() +{ + if(_capturedMutex) + return true; + else + return false; +} + +void MutexTracker::releaseMutex() +{ + if(mutexCaptured()) + { + *_capturedMutex = false; + _capturedMutex = nullptr; + } +} + +bool MutexTracker::attemptMutexCapture(bool &mutexToCapture) +{ + if(!mutexToCapture) + { + _capturedMutex = &mutexToCapture; + *_capturedMutex = true; + return true; + } + else + { + return false; + } +} diff --git a/libraries/ESP8266WiFiMesh/src/MutexTracker.h b/libraries/ESP8266WiFiMesh/src/MutexTracker.h new file mode 100644 index 0000000000..b7144cb27c --- /dev/null +++ b/libraries/ESP8266WiFiMesh/src/MutexTracker.h @@ -0,0 +1,71 @@ +/* + * Copyright (C) 2019 Anders Löfgren + * + * License (MIT license): + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#ifndef __MUTEXTRACKER_H__ +#define __MUTEXTRACKER_H__ + +#include + +/** + * A SLIM (Scope LImited Manager)/Scope-Bound Resource Management/RAII class to manage the state of a mutex. + */ +class MutexTracker +{ + public: + + /** + * Attempts to capture the mutex. Use the mutexCaptured() method to check success. + */ + MutexTracker(bool &mutexToCapture); + + /** + * Attempts to capture the mutex. Use the mutexCaptured() method to check success. + * + * @param destructorHook A function to hook into the MutexTracker destructor. Will be called when the MutexTracker instance is being destroyed, after the mutex has been released. + */ + MutexTracker(bool &mutexToCapture, std::function destructorHook); + + ~MutexTracker(); + + bool mutexCaptured(); + + /** + * Set the mutex free to roam the binary plains, giving new MutexTrackers a chance to capture it. + */ + void releaseMutex(); + + private: + + bool *_capturedMutex = nullptr; + std::function _destructorHook = [](){ }; + + /** + * Attempt to capture the mutex. + * + * @returns True if mutex was caught (meaning no other instance is holding the mutex). False otherwise. + */ + bool attemptMutexCapture(bool &mutexToCapture); +}; + +#endif diff --git a/libraries/ESP8266WiFiMesh/src/PeerRequestLog.cpp b/libraries/ESP8266WiFiMesh/src/PeerRequestLog.cpp new file mode 100644 index 0000000000..dd9d33dbd1 --- /dev/null +++ b/libraries/ESP8266WiFiMesh/src/PeerRequestLog.cpp @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2019 Anders Löfgren + * + * License (MIT license): + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include "PeerRequestLog.h" +#include "EspnowMeshBackend.h" + +using EspnowProtocolInterpreter::espnowHashKeyLength; + +PeerRequestLog::PeerRequestLog(uint64_t requestID, bool requestEncrypted, const String &authenticationPassword, const String &peerRequestNonce, const uint8_t peerStaMac[6], const uint8_t peerApMac[6], const uint8_t hashKey[espnowHashKeyLength]) + : EncryptedConnectionData(peerStaMac, peerApMac, 0, 0, EspnowMeshBackend::getEncryptionRequestTimeout(), hashKey), + _requestID(requestID), _requestEncrypted(requestEncrypted), _authenticationPassword(authenticationPassword), _peerRequestNonce(peerRequestNonce) +{ } + +PeerRequestLog::PeerRequestLog(uint64_t requestID, bool requestEncrypted, const String &authenticationPassword, const String &peerRequestNonce, const uint8_t peerStaMac[6], const uint8_t peerApMac[6], uint64_t peerSessionKey, uint64_t ownSessionKey, const uint8_t hashKey[espnowHashKeyLength]) + : EncryptedConnectionData(peerStaMac, peerApMac, peerSessionKey, ownSessionKey, EspnowMeshBackend::getEncryptionRequestTimeout(), hashKey), + _requestID(requestID), _requestEncrypted(requestEncrypted), _authenticationPassword(authenticationPassword), _peerRequestNonce(peerRequestNonce) +{ } + +void PeerRequestLog::setRequestID(uint64_t requestID) { _requestID = requestID; } +uint64_t PeerRequestLog::getRequestID() { return _requestID; } + +void PeerRequestLog::setRequestEncrypted(bool requestEncrypted) { _requestEncrypted = requestEncrypted; } +bool PeerRequestLog::requestEncrypted() { return _requestEncrypted; } + +void PeerRequestLog::setAuthenticationPassword(const String &password) { _authenticationPassword = password; } +String PeerRequestLog::getAuthenticationPassword() { return _authenticationPassword; } + +void PeerRequestLog::setPeerRequestNonce(const String &nonce) { _peerRequestNonce = nonce; } +String PeerRequestLog::getPeerRequestNonce() { return _peerRequestNonce; } diff --git a/libraries/ESP8266WiFiMesh/src/PeerRequestLog.h b/libraries/ESP8266WiFiMesh/src/PeerRequestLog.h new file mode 100644 index 0000000000..7f058f1719 --- /dev/null +++ b/libraries/ESP8266WiFiMesh/src/PeerRequestLog.h @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2019 Anders Löfgren + * + * License (MIT license): + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#ifndef __ESPNOWPEERREQUESTLOG_H__ +#define __ESPNOWPEERREQUESTLOG_H__ + +#include "EncryptedConnectionData.h" +#include "EspnowProtocolInterpreter.h" + +class PeerRequestLog : public EncryptedConnectionData { + +public: + + PeerRequestLog(uint64_t requestID, bool requestEncrypted, const String &authenticationPassword, const String &peerRequestNonce, const uint8_t peerStaMac[6], + const uint8_t peerApMac[6], const uint8_t hashKey[EspnowProtocolInterpreter::espnowHashKeyLength]); + PeerRequestLog(uint64_t requestID, bool requestEncrypted, const String &authenticationPassword, const String &peerRequestNonce, const uint8_t peerStaMac[6], + const uint8_t peerApMac[6], uint64_t peerSessionKey, uint64_t ownSessionKey, const uint8_t hashKey[EspnowProtocolInterpreter::espnowHashKeyLength]); + + void setRequestID(uint64_t requestID); + uint64_t getRequestID(); + + void setRequestEncrypted(bool requestEncrypted); + bool requestEncrypted(); + + void setAuthenticationPassword(const String &password); + String getAuthenticationPassword(); + + void setPeerRequestNonce(const String &nonce); + String getPeerRequestNonce(); + +private: + + uint64_t _requestID; + bool _requestEncrypted; + String _authenticationPassword = ""; + String _peerRequestNonce = ""; +}; + +#endif diff --git a/libraries/ESP8266WiFiMesh/src/RequestData.cpp b/libraries/ESP8266WiFiMesh/src/RequestData.cpp new file mode 100644 index 0000000000..a45b460514 --- /dev/null +++ b/libraries/ESP8266WiFiMesh/src/RequestData.cpp @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2019 Anders Löfgren + * + * License (MIT license): + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include "RequestData.h" + +RequestData::RequestData(EspnowMeshBackend &meshInstance, uint32_t creationTimeMs) : + TimeTracker(creationTimeMs), _meshInstance(meshInstance) +{ } + +void RequestData::setMeshInstance(EspnowMeshBackend &meshInstance) { _meshInstance = meshInstance; } +EspnowMeshBackend &RequestData::getMeshInstance() { return _meshInstance; } diff --git a/libraries/ESP8266WiFiMesh/src/RequestData.h b/libraries/ESP8266WiFiMesh/src/RequestData.h new file mode 100644 index 0000000000..77f373245d --- /dev/null +++ b/libraries/ESP8266WiFiMesh/src/RequestData.h @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2019 Anders Löfgren + * + * License (MIT license): + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#ifndef __ESPNOWREQUESTDATA_H__ +#define __ESPNOWREQUESTDATA_H__ + +#include "TimeTracker.h" +#include "EspnowMeshBackend.h" + +class EspnowMeshBackend; + +class RequestData : public TimeTracker { + +public: + + RequestData(EspnowMeshBackend &meshInstance, uint32_t creationTimeMs = millis()); + + void setMeshInstance(EspnowMeshBackend &meshInstance); + EspnowMeshBackend &getMeshInstance(); + +private: + + EspnowMeshBackend &_meshInstance; +}; + +#endif diff --git a/libraries/ESP8266WiFiMesh/src/ResponseData.cpp b/libraries/ESP8266WiFiMesh/src/ResponseData.cpp new file mode 100644 index 0000000000..b3678d2466 --- /dev/null +++ b/libraries/ESP8266WiFiMesh/src/ResponseData.cpp @@ -0,0 +1,79 @@ +/* + * Copyright (C) 2019 Anders Löfgren + * + * License (MIT license): + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include "ResponseData.h" + +ResponseData::ResponseData(const String &message, const uint8_t recipientMac[6], uint64_t requestID, uint32_t creationTimeMs) : + TimeTracker(creationTimeMs), _message(message), _requestID(requestID) +{ + storeRecipientMac(recipientMac); +} + +ResponseData::ResponseData(const ResponseData &other) + : TimeTracker(other), _message(other.getMessage()), _requestID(other.getRequestID()) +{ + storeRecipientMac(other.getRecipientMac()); +} + +ResponseData & ResponseData::operator=(const ResponseData &other) +{ + if(this != &other) + { + TimeTracker::operator=(other); + _message = other.getMessage(); + _requestID = other.getRequestID(); + storeRecipientMac(other.getRecipientMac()); + } + + return *this; +} + +void ResponseData::storeRecipientMac(const uint8_t newRecipientMac[6]) +{ + if(newRecipientMac != nullptr) + { + if(_recipientMac == nullptr) + { + _recipientMac = _recipientMacArray; + } + + for(int i = 0; i < 6; i++) + { + _recipientMac[i] = newRecipientMac[i]; + } + } + else + { + _recipientMac = nullptr; + } +} + +void ResponseData::setRecipientMac(const uint8_t recipientMac[6]) { storeRecipientMac(recipientMac); } +const uint8_t *ResponseData::getRecipientMac() const { return _recipientMac; } + +void ResponseData::setMessage(String &message) { _message = message; } +String ResponseData::getMessage() const { return _message; } + +void ResponseData::setRequestID(uint64_t requestID) { _requestID = requestID; } +uint64_t ResponseData::getRequestID() const { return _requestID; } diff --git a/libraries/ESP8266WiFiMesh/src/ResponseData.h b/libraries/ESP8266WiFiMesh/src/ResponseData.h new file mode 100644 index 0000000000..7bd527d6e3 --- /dev/null +++ b/libraries/ESP8266WiFiMesh/src/ResponseData.h @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2019 Anders Löfgren + * + * License (MIT license): + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#ifndef __ESPNOWRESPONSEDATA_H__ +#define __ESPNOWRESPONSEDATA_H__ + +#include "TimeTracker.h" +#include + +class ResponseData : public TimeTracker { + +public: + + ResponseData(const String &message, const uint8_t recipientMac[6], uint64_t requestID, uint32_t creationTimeMs = millis()); + ResponseData(const ResponseData &other); + ResponseData & operator=(const ResponseData &other); + // No need for explicit destructor with current class design + + void setRecipientMac(const uint8_t recipientMac[6]); + const uint8_t *getRecipientMac() const; + + void setMessage(String &message); + String getMessage() const; + + void setRequestID(uint64_t requestID); + uint64_t getRequestID() const; + +private: + + void storeRecipientMac(const uint8_t newRecipientMac[6]); + + uint8_t _recipientMacArray[6] {0}; + uint8_t *_recipientMac = nullptr; + String _message = ""; + uint64_t _requestID = 0; +}; + +#endif diff --git a/libraries/ESP8266WiFiMesh/src/TcpIpMeshBackend.cpp b/libraries/ESP8266WiFiMesh/src/TcpIpMeshBackend.cpp new file mode 100644 index 0000000000..b00f8744a4 --- /dev/null +++ b/libraries/ESP8266WiFiMesh/src/TcpIpMeshBackend.cpp @@ -0,0 +1,469 @@ +/* + TcpIpMeshBackend + + Copyright (c) 2015 Julian Fell and 2019 Anders Löfgren. All rights reserved. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include +#include +#include +#include + +#include "TcpIpMeshBackend.h" +#include "TypeConversionFunctions.h" +#include "MutexTracker.h" + +#define SERVER_IP_ADDR "192.168.4.1" + +const IPAddress TcpIpMeshBackend::emptyIP = IPAddress(); + +bool TcpIpMeshBackend::_tcpIpTransmissionMutex = false; + +String TcpIpMeshBackend::lastSSID = ""; +bool TcpIpMeshBackend::staticIPActivated = false; + +// IP needs to be at the same subnet as server gateway (192.168.4 in this case). Station gateway ip must match ip for server. +IPAddress TcpIpMeshBackend::staticIP = emptyIP; +IPAddress TcpIpMeshBackend::gateway = IPAddress(192,168,4,1); +IPAddress TcpIpMeshBackend::subnetMask = IPAddress(255,255,255,0); + +TcpIpMeshBackend::TcpIpMeshBackend(requestHandlerType requestHandler, responseHandlerType responseHandler, + networkFilterType networkFilter, const String &meshPassword, const String &ssidPrefix, + const String &ssidSuffix, bool verboseMode, uint8 meshWiFiChannel, uint16_t serverPort) + : MeshBackendBase(requestHandler, responseHandler, networkFilter, MB_TCP_IP), _server(serverPort) +{ + setSSID(ssidPrefix, "", ssidSuffix); + setMeshPassword(meshPassword); + setVerboseModeState(verboseMode); + setWiFiChannel(meshWiFiChannel); + setServerPort(serverPort); +} + +void TcpIpMeshBackend::begin() +{ + if(!TcpIpMeshBackend::getAPController()) // If there is no active AP controller + WiFi.mode(WIFI_STA); // WIFI_AP_STA mode automatically sets up an AP, so we can't use that as default. + + #if LWIP_VERSION_MAJOR >= 2 + verboseModePrint(F("lwIP version is at least 2. Static ip optimizations enabled.\n")); + #else + verboseModePrint(F("lwIP version is less than 2. Static ip optimizations DISABLED.\n")); + #endif +} + +void TcpIpMeshBackend::activateAPHook() +{ + WiFi.softAP( getSSID().c_str(), getMeshPassword().c_str(), getWiFiChannel(), getAPHidden(), _maxAPStations ); // Note that a maximum of 8 TCP/IP stations can be connected at a time to each AP, max 4 by default. + + _server = WiFiServer(getServerPort()); // Fixes an occasional crash bug that occurs when using the copy constructor to duplicate the AP controller. + _server.begin(); // Actually calls _server.stop()/_server.close() first. +} + +void TcpIpMeshBackend::deactivateAPHook() +{ + _server.stop(); +} + +bool TcpIpMeshBackend::transmissionInProgress(){return _tcpIpTransmissionMutex;} + +void TcpIpMeshBackend::setStaticIP(const IPAddress &newIP) +{ + // Comment out the line below to remove static IP and use DHCP instead. + // DHCP makes WiFi connection happen slower, but there is no need to care about manually giving different IPs to the nodes and less need to worry about used IPs giving "Server unavailable" issues. + // Static IP has faster connection times (50 % of DHCP) and will make sending of data to a node that is already transmitting data happen more reliably. + // Note that after WiFi.config(staticIP, gateway, subnetMask) is used, static IP will always be active, even for new connections, unless WiFi.config(0u,0u,0u); is called. + WiFi.config(newIP, gateway, subnetMask); + staticIPActivated = true; + staticIP = newIP; +} + +IPAddress TcpIpMeshBackend::getStaticIP() +{ + if(staticIPActivated) + return staticIP; + + return emptyIP; +} + +void TcpIpMeshBackend::disableStaticIP() +{ + WiFi.config(0u,0u,0u); + yield(); + staticIPActivated = false; +} + +void TcpIpMeshBackend::setServerPort(uint16_t serverPort) +{ + _serverPort = serverPort; + + // Apply changes to active AP. + if(isAPController()) + restartAP(); +} + +uint16_t TcpIpMeshBackend::getServerPort() {return _serverPort;} + +void TcpIpMeshBackend::setMaxAPStations(uint8_t maxAPStations) +{ + assert(maxAPStations <= 8); // Valid values are 0 to 8, but uint8_t is always at least 0. + + if(_maxAPStations != maxAPStations) + { + _maxAPStations = maxAPStations; + + // Apply changes to active AP. + if(isAPController()) + restartAP(); + } +} + +bool TcpIpMeshBackend::getMaxAPStations() {return _maxAPStations;} + +void TcpIpMeshBackend::setConnectionAttemptTimeout(int32_t connectionAttemptTimeoutMs) +{ + _connectionAttemptTimeoutMs = connectionAttemptTimeoutMs; +} + +int32_t TcpIpMeshBackend::getConnectionAttemptTimeout() {return _connectionAttemptTimeoutMs;} + +void TcpIpMeshBackend::setStationModeTimeout(int stationModeTimeoutMs) +{ + _stationModeTimeoutMs = stationModeTimeoutMs; +} + +int TcpIpMeshBackend::getStationModeTimeout() {return _stationModeTimeoutMs;} + +void TcpIpMeshBackend::setAPModeTimeout(uint32_t apModeTimeoutMs) +{ + _apModeTimeoutMs = apModeTimeoutMs; +} + +uint32_t TcpIpMeshBackend::getAPModeTimeout() {return _apModeTimeoutMs;} + +/** + * Disconnect completely from a network. + */ +void TcpIpMeshBackend::fullStop(WiFiClient &currClient) +{ + currClient.stop(); + yield(); + WiFi.disconnect(); + yield(); +} + +/** + * Wait for a WiFiClient to transmit + * + * @returns: True if the client is ready, false otherwise. + * + */ +bool TcpIpMeshBackend::waitForClientTransmission(WiFiClient &currClient, uint32_t maxWait) +{ + uint32_t connectionStartTime = millis(); + uint32_t waitingTime = millis() - connectionStartTime; + while(currClient.connected() && !currClient.available() && waitingTime < maxWait) + { + delay(1); + waitingTime = millis() - connectionStartTime; + } + + /* Return false if the client isn't ready to communicate */ + if (WiFi.status() == WL_DISCONNECTED && !currClient.available()) + { + verboseModePrint(F("Disconnected!")); + return false; + } + + return true; +} + +/** + * Send the mesh instance's current message then read back the other node's response + * and pass that to the user-supplied responseHandler. + * + * @param currClient The client to which the message should be transmitted. + * @returns: A status code based on the outcome of the exchange. + * + */ +transmission_status_t TcpIpMeshBackend::exchangeInfo(WiFiClient &currClient) +{ + verboseModePrint("Transmitting"); // Not storing strings in flash (via F()) to avoid performance impacts when using the string. + + currClient.print(getMessage() + "\r"); + yield(); + + if (!waitForClientTransmission(currClient, _stationModeTimeoutMs)) + { + fullStop(currClient); + return TS_CONNECTION_FAILED; + } + + if (!currClient.available()) + { + verboseModePrint(F("No response!")); + return TS_TRANSMISSION_FAILED; // WiFi.status() != WL_DISCONNECTED so we do not want to use fullStop(currClient) here since that would force the node to scan for WiFi networks. + } + + String response = currClient.readStringUntil('\r'); + yield(); + currClient.flush(); + + /* Pass data to user callback */ + return getResponseHandler()(response, *this); +} + +/** + * Handle data transfer process with a connected AP. + * + * @returns: A status code based on the outcome of the data transfer attempt. + */ +transmission_status_t TcpIpMeshBackend::attemptDataTransfer() +{ + // Unlike WiFi.mode(WIFI_AP);, WiFi.mode(WIFI_AP_STA); allows us to stay connected to the AP we connected to in STA mode, at the same time as we can receive connections from other stations. + // We cannot send data to the AP in STA_AP mode though, that requires STA mode. + // Switching to STA mode will disconnect all stations connected to the node AP (though they can request a reconnect even while we are in STA mode). + WiFiMode_t storedWiFiMode = WiFi.getMode(); + WiFi.mode(WIFI_STA); + delay(1); + transmission_status_t transmissionOutcome = attemptDataTransferKernel(); + WiFi.mode(storedWiFiMode); + delay(1); + + return transmissionOutcome; +} + +/** + * Helper function that contains the core functionality for the data transfer process with a connected AP. + * + * @returns: A status code based on the outcome of the data transfer attempt. + */ +transmission_status_t TcpIpMeshBackend::attemptDataTransferKernel() +{ + WiFiClient currClient; + currClient.setTimeout(_stationModeTimeoutMs); + + /* Connect to the node's server */ + if (!currClient.connect(SERVER_IP_ADDR, getServerPort())) + { + fullStop(currClient); + verboseModePrint(F("Server unavailable")); + return TS_CONNECTION_FAILED; + } + + transmission_status_t transmissionOutcome = exchangeInfo(currClient); + if (transmissionOutcome <= 0) + { + verboseModePrint(F("Transmission failed during exchangeInfo.")); + return transmissionOutcome; + } + + currClient.stop(); + yield(); + + return transmissionOutcome; +} + +void TcpIpMeshBackend::initiateConnectionToAP(const String &targetSSID, int targetChannel, uint8_t *targetBSSID) +{ + if(targetChannel == NETWORK_INFO_DEFAULT_INT) + WiFi.begin( targetSSID.c_str(), getMeshPassword().c_str() ); // Without giving channel and BSSID, connection time is longer. + else if(targetBSSID == NULL) + WiFi.begin( targetSSID.c_str(), getMeshPassword().c_str(), targetChannel ); // Without giving channel and BSSID, connection time is longer. + else + WiFi.begin( targetSSID.c_str(), getMeshPassword().c_str(), targetChannel, targetBSSID ); +} + +/** + * Connect to the AP at SSID and transmit the mesh instance's current message. + * + * @param targetSSID The name of the AP the other node has set up. + * @param targetChannel The WiFI channel of the AP the other node has set up. + * @param targetBSSID The MAC address of the AP the other node has set up. + * @returns: A status code based on the outcome of the connection and data transfer process. + * + */ +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 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. + WiFiMode_t storedWiFiMode = WiFi.getMode(); + WiFi.mode(WIFI_OFF); + WiFi.mode(storedWiFiMode); + yield(); + + #else + // Disable static IP so that we can connect to other servers via DHCP (DHCP is slower but required for connecting to more than one server, it seems (possible bug?)). + disableStaticIP(); + verboseModePrint(F("\nConnecting to a different network. Static IP deactivated to make this possible.")); + + #endif + } + lastSSID = targetSSID; + + verboseModePrint(F("Connecting... "), false); + initiateConnectionToAP(targetSSID, targetChannel, targetBSSID); + + int connectionStartTime = millis(); + int attemptNumber = 1; + + int waitingTime = millis() - connectionStartTime; + while((WiFi.status() == WL_DISCONNECTED) && waitingTime <= _connectionAttemptTimeoutMs) + { + if(waitingTime > attemptNumber * _connectionAttemptTimeoutMs) // _connectionAttemptTimeoutMs can be replaced (lowered) if you want to limit the time allowed for each connection attempt. + { + verboseModePrint(F("... "), false); + WiFi.disconnect(); + yield(); + initiateConnectionToAP(targetSSID, targetChannel, targetBSSID); + attemptNumber++; + } + delay(1); + waitingTime = millis() - connectionStartTime; + } + + verboseModePrint(String(waitingTime)); + + /* If the connection timed out */ + if (WiFi.status() != WL_CONNECTED) + { + verboseModePrint(F("Timeout")); + return TS_CONNECTION_FAILED; + } + + return attemptDataTransfer(); +} + +void TcpIpMeshBackend::attemptTransmission(const String &message, bool scan, bool scanAllWiFiChannels, bool concludingDisconnect, bool initialDisconnect ) +{ + MutexTracker mutexTracker(_tcpIpTransmissionMutex); + if(!mutexTracker.mutexCaptured()) + { + assert(false && "ERROR! TCP/IP transmission in progress. Don't call attemptTransmission from callbacks as this may corrupt program state! Aborting."); + return; + } + + if(initialDisconnect) + { + WiFi.disconnect(); + yield(); + } + + setMessage(message); + + latestTransmissionOutcomes.clear(); + + if(WiFi.status() == WL_CONNECTED) + { + transmission_status_t transmissionResult = attemptDataTransfer(); + latestTransmissionOutcomes.push_back(TransmissionResult(connectionQueue.back(), transmissionResult)); + } + else + { + if(scan) + { + scanForNetworks(scanAllWiFiChannels); + } + + for(NetworkInfo ¤tNetwork : connectionQueue) + { + WiFi.disconnect(); + yield(); + + String currentSSID = ""; + int currentWiFiChannel = NETWORK_INFO_DEFAULT_INT; + uint8_t *currentBSSID = NULL; + + // If an SSID has been assigned, it is prioritized over an assigned networkIndex since the networkIndex is more likely to change. + if(currentNetwork.SSID != "") + { + currentSSID = currentNetwork.SSID; + currentWiFiChannel = currentNetwork.wifiChannel; + currentBSSID = currentNetwork.BSSID; + } + else // Use only networkIndex + { + currentSSID = WiFi.SSID(currentNetwork.networkIndex); + currentWiFiChannel = WiFi.channel(currentNetwork.networkIndex); + currentBSSID = WiFi.BSSID(currentNetwork.networkIndex); + } + + if(verboseMode()) // Avoid string generation if not required + { + printAPInfo(currentNetwork.networkIndex, currentSSID, currentWiFiChannel); + } + + transmission_status_t transmissionResult = connectToNode(currentSSID, currentWiFiChannel, currentBSSID); + + latestTransmissionOutcomes.push_back(TransmissionResult{.origin = currentNetwork, .transmissionStatus = transmissionResult}); + } + } + + if(WiFi.status() == WL_CONNECTED && staticIP != emptyIP && !staticIPActivated) + { + verboseModePrint(F("Reactivating static IP to allow for faster re-connects.")); + setStaticIP(staticIP); + } + + // If we do not want to be connected at end of transmission, disconnect here so we can re-enable static IP first (above). + if(concludingDisconnect) + { + WiFi.disconnect(); + yield(); + } +} + +void TcpIpMeshBackend::attemptTransmission(const String &message, bool scan, bool scanAllWiFiChannels) +{ + attemptTransmission(message, scan, scanAllWiFiChannels, true, false); +} + +void TcpIpMeshBackend::acceptRequest() +{ + MutexTracker mutexTracker(_tcpIpTransmissionMutex); + if(!mutexTracker.mutexCaptured()) + { + assert(false && "ERROR! TCP/IP transmission in progress. Don't call acceptRequest from TCP/IP callbacks as this may corrupt program state! Aborting."); + return; + } + + while (true) { + WiFiClient _client = _server.available(); + + if (!_client) + break; + + if (!waitForClientTransmission(_client, _apModeTimeoutMs) || !_client.available()) { + continue; + } + + /* Read in request and pass it to the supplied requestHandler */ + String request = _client.readStringUntil('\r'); + yield(); + _client.flush(); + + String response = getRequestHandler()(request, *this); + + /* Send the response back to the client */ + if (_client.connected()) + { + verboseModePrint("Responding"); // Not storing strings in flash (via F()) to avoid performance impacts when using the string. + _client.print(response + "\r"); + _client.flush(); + yield(); + } + } +} diff --git a/libraries/ESP8266WiFiMesh/src/TcpIpMeshBackend.h b/libraries/ESP8266WiFiMesh/src/TcpIpMeshBackend.h new file mode 100644 index 0000000000..86dc7ce03d --- /dev/null +++ b/libraries/ESP8266WiFiMesh/src/TcpIpMeshBackend.h @@ -0,0 +1,214 @@ +/* + TcpIpMeshBackend + + Copyright (c) 2015 Julian Fell and 2019 Anders Löfgren. All rights reserved. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +// ESP-NOW is faster for small data payloads (up to a few kB, split over multiple messages). Transfer of up to 234 bytes takes 4 ms. +// In general ESP-NOW transfer time can be approximated with the following function: transferTime = ceil(bytesToTransfer / 234.0)*3 ms. +// If you only transfer 234 bytes at a time, this adds up to around 56kB/s. Finally a chance to relive the glory of the olden days +// when people were restricted to V90 dial-up modems for internet access! +// TCP-IP takes longer to connect (around 1000 ms), and an AP has to disconnect all connected stations in order to transfer data to another AP, +// but this backend has a much higher data transfer speed than ESP-NOW once connected (100x faster or so). + +#ifndef __TCPIPMESHBACKEND_H__ +#define __TCPIPMESHBACKEND_H__ + +#include +#include +#include +#include +#include "MeshBackendBase.h" +#include "NetworkInfo.h" + +class TcpIpMeshBackend : public MeshBackendBase { + +public: + + /** + * WiFiMesh Constructor method. Creates a WiFi Mesh Node, ready to be initialised. + * + * @param requestHandler The callback handler for dealing with received requests. Takes a string as an argument which + * is the request string received from another node and returns the string to send back. + * @param responseHandler The callback handler for dealing with received responses. Takes a string as an argument which + * is the response string received from another node. Returns a transmission status code as a transmission_status_t. + * @param networkFilter The callback handler for deciding which WiFi networks to connect to. + * @param meshPassword The WiFi password for the mesh network. + * @param ssidPrefix The prefix (first part) of the node SSID. + * @param ssidSuffix The suffix (last part) of the node SSID. + * @param verboseMode Determines if we should print the events occurring in the library to Serial. Off by default. This setting is separate for each TcpIpMeshBackend instance. + * @param meshWiFiChannel The WiFi channel used by the mesh network. Valid values are integers from 1 to 13. Defaults to 1. + * WARNING: The ESP8266 has only one WiFi channel, and the the station/client mode is always prioritized for channel selection. + * This can cause problems if several mesh instances exist on the same ESP8266 and use different WiFi channels. + * In such a case, whenever the station of one mesh instance connects to an AP, it will silently force the + * WiFi channel of any active AP on the ESP8266 to match that of the station. This will cause disconnects and possibly + * make it impossible for other stations to detect the APs whose WiFi channels have changed. + * @param serverPort The server port used both by the AP of the TcpIpMeshBackend instance and when the instance connects to other APs. + * If multiple APs exist on a single ESP8266, each requires a separate server port. + * If two AP:s on the same ESP8266 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. + * + */ + TcpIpMeshBackend(requestHandlerType requestHandler, responseHandlerType responseHandler, networkFilterType networkFilter, + const String &meshPassword, const String &ssidPrefix, const String &ssidSuffix, bool verboseMode = false, + uint8 meshWiFiChannel = 1, uint16_t serverPort = 4011); + + /** + * Initialises the node. + */ + void begin() override; + + /** + * If AP connection already exists, and the initialDisconnect argument is set to false, send message only to the already connected AP. + * Otherwise, scan for other networks, send the scan result to networkFilter and then transmit the message to the networks found in connectionQueue. + * + * @param message The message to send to other nodes. It will be stored in the class instance until replaced via attemptTransmission or setMessage. + * @param concludingDisconnect Disconnect from AP once transmission is complete. Defaults to true. + * @param initialDisconnect Disconnect from any currently connected AP before attempting transmission. Defaults to false. + * @param scan Scan for new networks and call the networkFilter function with the scan results. When set to false, only the data already in connectionQueue will be used for the transmission. + * @param scanAllWiFiChannels Scan all WiFi channels during a WiFi scan, instead of just the channel the MeshBackendBase instance is using. + * Scanning all WiFi channels takes about 2100 ms, compared to just 60 ms if only channel 1 (standard) is scanned. + * Note that if the ESP8266 has an active AP, that AP will switch WiFi channel to match that of any other AP the ESP8266 connects to. + * This can make it impossible for other nodes to detect the AP if they are scanning the wrong WiFi channel. + */ + void attemptTransmission(const String &message, bool scan, bool scanAllWiFiChannels, bool concludingDisconnect, bool initialDisconnect = false); + + void attemptTransmission(const String &message, bool scan = true, bool scanAllWiFiChannels = false) override; + + /** + * If any clients are connected, accept their requests and call the requestHandler function for each one. + */ + void acceptRequest(); + + /** + * Set a static IP address for the ESP8266 and activate use of static IP. + * The static IP needs to be at the same subnet as the server's gateway. + */ + void setStaticIP(const IPAddress &newIP); + IPAddress getStaticIP(); + void disableStaticIP(); + + /** + * An empty IPAddress. Used as default when no IP is set. + */ + static const IPAddress emptyIP; + + /** + * Set the server port used both by the AP of the TcpIpMeshBackend instance and when the instance connects to other APs. + * If multiple APs exist on a single ESP8266, each requires a separate server port. + * If two AP:s on the same ESP8266 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. + * Will also change the setting for the active AP if this TcpIpMeshBackend instance is the current AP controller. + * + * @param serverPort The server port to use. + * + */ + void setServerPort(uint16_t serverPort); + uint16_t getServerPort(); + + /** + * Set the maximum number of stations that can simultaneously be connected to the AP controlled by this TcpIpMeshBackend instance. + * This number is 4 by default. + * Once the max number has been reached, any other station that wants to connect will be forced to wait until an already connected station disconnects. + * The more stations that are connected, the more memory is required. + * Will also change the setting for the active AP if this TcpIpMeshBackend instance is the current AP controller. + * + * @param maxAPStations The maximum number of simultaneous station connections allowed. Valid values are 0 to 8. + */ + void setMaxAPStations(uint8_t maxAPStations); + bool getMaxAPStations(); + + /** + * Set the timeout for each attempt to connect to another AP that occurs through the attemptTransmission method by this TcpIpMeshBackend instance. + * The timeout is 10 000 ms by default. + * + * @param connectionAttemptTimeoutMs The timeout for each connection attempt, in milliseconds. + */ + void setConnectionAttemptTimeout(int32_t connectionAttemptTimeoutMs); + int32_t getConnectionAttemptTimeout(); + + /** + * Set the timeout to use for transmissions when this TcpIpMeshBackend instance acts as a station (i.e. when connected to another AP). + * This will affect the timeout of the attemptTransmission method once a connection to an AP has been established. + * The timeout is 5 000 ms by default. + * + * @param stationModeTimeoutMs The timeout to use, in milliseconds. + */ + void setStationModeTimeout(int stationModeTimeoutMs); + int getStationModeTimeout(); + + /** + * Set the timeout to use for transmissions when this TcpIpMeshBackend instance acts as an AP (i.e. when receiving connections from other stations). + * This will affect the timeout of the acceptRequest method. + * The timeout is 4 500 ms by default. + * Will also change the setting for the active AP if this TcpIpMeshBackend instance is the current AP controller. + * + * @param apModeTimeoutMs The timeout to use, in milliseconds. + */ + void setAPModeTimeout(uint32_t apModeTimeoutMs); + uint32_t getAPModeTimeout(); + +protected: + + /** + * Called just before we activate the AP. + * Put _server.stop() in deactivateAPHook() in case you use _server.begin() here. + */ + void activateAPHook() override; + + /** + * Called just before we deactivate the AP. + * Put _server.stop() here in case you use _server.begin() in activateAPHook(). + */ + void deactivateAPHook() override; + + /** + * Will be true if a transmission initiated by a public method is in progress. + */ + static bool _tcpIpTransmissionMutex; + + /** + * Check if there is an ongoing TCP/IP transmission in the library. Used to avoid interrupting transmissions. + * + * @returns True if a transmission initiated by a public method is in progress. + */ + static bool transmissionInProgress(); + +private: + + uint16_t _serverPort; + WiFiServer _server; + uint8_t _maxAPStations = 4; // Only affects TCP/IP connections, not ESP-NOW connections + int32_t _connectionAttemptTimeoutMs = 10000; + int _stationModeTimeoutMs = 5000; // int is the type used in the Arduino core for this particular API, not uint32_t, which is why we use int here. + uint32_t _apModeTimeoutMs = 4500; + + static String lastSSID; + static bool staticIPActivated; + bool useStaticIP; + static IPAddress staticIP; + static IPAddress gateway; + static IPAddress subnetMask; + + void fullStop(WiFiClient &currClient); + void initiateConnectionToAP(const String &targetSSID, int targetChannel = NETWORK_INFO_DEFAULT_INT, uint8_t *targetBSSID = NULL); + transmission_status_t connectToNode(const String &targetSSID, int targetChannel = NETWORK_INFO_DEFAULT_INT, uint8_t *targetBSSID = NULL); + transmission_status_t exchangeInfo(WiFiClient &currClient); + bool waitForClientTransmission(WiFiClient &currClient, uint32_t maxWait); + transmission_status_t attemptDataTransfer(); + transmission_status_t attemptDataTransferKernel(); +}; + +#endif diff --git a/libraries/ESP8266WiFiMesh/src/TimeTracker.cpp b/libraries/ESP8266WiFiMesh/src/TimeTracker.cpp new file mode 100644 index 0000000000..1656e185e3 --- /dev/null +++ b/libraries/ESP8266WiFiMesh/src/TimeTracker.cpp @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2019 Anders Löfgren + * + * License (MIT license): + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include "TimeTracker.h" +#include + +TimeTracker::TimeTracker(uint32_t creationTimeMs) : _creationTimeMs(creationTimeMs) +{ } + +uint32_t TimeTracker::timeSinceCreation() const +{ + return millis() - creationTimeMs(); // Will work even when millis() overflow: http://forum.arduino.cc/index.php/topic,42997.0.html +} + +uint32_t TimeTracker::creationTimeMs() const +{ + return _creationTimeMs; +} diff --git a/libraries/ESP8266WiFiMesh/src/TimeTracker.h b/libraries/ESP8266WiFiMesh/src/TimeTracker.h new file mode 100644 index 0000000000..76e5eafe4f --- /dev/null +++ b/libraries/ESP8266WiFiMesh/src/TimeTracker.h @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2019 Anders Löfgren + * + * License (MIT license): + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#ifndef __TIMETRACKER_H__ +#define __TIMETRACKER_H__ + +#include + +class TimeTracker { + +public: + + virtual ~TimeTracker() = default; + + TimeTracker(uint32_t creationTimeMs); + uint32_t timeSinceCreation() const; + uint32_t creationTimeMs() const; + +private: + + uint32_t _creationTimeMs; +}; + +#endif diff --git a/libraries/ESP8266WiFiMesh/src/TypeConversionFunctions.cpp b/libraries/ESP8266WiFiMesh/src/TypeConversionFunctions.cpp index be908b556e..0458cc54c8 100644 --- a/libraries/ESP8266WiFiMesh/src/TypeConversionFunctions.cpp +++ b/libraries/ESP8266WiFiMesh/src/TypeConversionFunctions.cpp @@ -24,6 +24,7 @@ */ #include "TypeConversionFunctions.h" +#include "Crypto.h" String uint64ToString(uint64_t number, byte base) { @@ -56,3 +57,89 @@ uint64_t stringToUint64(const String &string, byte base) return result; } + +String uint8ArrayToHexString(const uint8_t *uint8Array, uint32_t arrayLength) +{ + char hexString[2*arrayLength + 1]; // Each uint8_t will become two characters (00 to FF) and we want a null terminated char array. + hexString[arrayLength + 1] = { 0 }; + for(uint32_t i = 0; i < arrayLength; i++) + { + sprintf(hexString + 2*i, "%02X", uint8Array[i]); + } + + return String(hexString); +} + +uint8_t *hexStringToUint8Array(const String &hexString, uint8_t *uint8Array, uint32_t arrayLength) +{ + assert(hexString.length() >= arrayLength*2); // Each array element can hold two hexString characters + + for(uint32_t i = 0; i < arrayLength; i++) + { + uint8Array[i] = strtoul(hexString.substring(i*2, (i+1)*2).c_str(), nullptr, 16); + } + + return uint8Array; +} + +String macToString(const uint8_t *mac) +{ + char macString[13] = { 0 }; + sprintf(macString, "%02X%02X%02X%02X%02X%02X", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]); + return String(macString); +} + +uint8_t *stringToMac(const String &macString, uint8_t *macArray) +{ + return hexStringToUint8Array(macString, macArray, 6); +} + +uint64_t macToUint64(const uint8_t *macArray) +{ + uint64_t outcome = 0; + for(int shiftingFortune = 40; shiftingFortune >= 0; shiftingFortune -= 8) + { + outcome |= ((uint64_t)macArray[5 - shiftingFortune/8] << shiftingFortune); + } + + return outcome; +} + +uint8_t *uint64ToMac(uint64_t macValue, uint8_t *macArray) +{ + assert(macValue <= 0xFFFFFFFFFFFF); // Overflow will occur if value can't fit within 6 bytes + + for(int shiftingFortune = 40; shiftingFortune >= 0; shiftingFortune -= 8) + { + macArray[5 - shiftingFortune/8] = macValue >> shiftingFortune & 0xFF; + } + return macArray; +} + +/** + * Helper function for meshBackendCast. + */ +template +T attemptPointerCast(MeshBackendBase *meshBackendBaseInstance, mesh_backend_t resultClassType) +{ + if(meshBackendBaseInstance && meshBackendBaseInstance->getClassType() == resultClassType) + { + return static_cast(meshBackendBaseInstance); + } + else + { + return nullptr; + } +} + +template <> +EspnowMeshBackend *meshBackendCast(MeshBackendBase *meshBackendBaseInstance) +{ + return attemptPointerCast(meshBackendBaseInstance, MB_ESP_NOW); +} + +template <> +TcpIpMeshBackend *meshBackendCast(MeshBackendBase *meshBackendBaseInstance) +{ + return attemptPointerCast(meshBackendBaseInstance, MB_TCP_IP); +} diff --git a/libraries/ESP8266WiFiMesh/src/TypeConversionFunctions.h b/libraries/ESP8266WiFiMesh/src/TypeConversionFunctions.h index 5d42e414cc..a6fa114653 100644 --- a/libraries/ESP8266WiFiMesh/src/TypeConversionFunctions.h +++ b/libraries/ESP8266WiFiMesh/src/TypeConversionFunctions.h @@ -28,6 +28,9 @@ #include #include +#include "MeshBackendBase.h" +#include "TcpIpMeshBackend.h" +#include "EspnowMeshBackend.h" /** * Note that using a base higher than 16 increases likelihood of randomly generating SSID strings containing controversial words. @@ -47,4 +50,66 @@ String uint64ToString(uint64_t number, byte base = 16); */ uint64_t stringToUint64(const String &string, byte base = 16); +// All array elements will be padded with zeroes to ensure they are converted to 2 string characters each. +String uint8ArrayToHexString(const uint8_t *uint8Array, uint32_t arrayLength); + +// There must be 2 string characters for each array element. Use padding with zeroes where required. +uint8_t *hexStringToUint8Array(const String &hexString, uint8_t *uint8Array, uint32_t arrayLength); + +/** + * Takes a uint8_t array and converts the first 6 bytes to a hexadecimal string. + * + * @param mac A uint8_t array with the mac address to convert to a string. Should be 6 bytes in total. + * @returns A hexadecimal string representation of the mac. + */ +String macToString(const uint8_t *mac); + +/** + * Takes a String and converts the first 12 characters to uint8_t numbers which are stored in the macArray from low to high index. Assumes hexadecimal number encoding. + * + * @param macString A String which begins with the mac address to store in the array as a hexadecimal number. + * @param macArray A uint8_t array that will hold the mac address once the function returns. Should have a size of at least 6 bytes. + * @returns The macArray. + */ +uint8_t *stringToMac(const String &macString, uint8_t *macArray); + +/** + * Takes a uint8_t array and converts the first 6 bytes to a uint64_t. Assumes index 0 of the array contains MSB. + * + * @param macArray A uint8_t array with the mac address to convert to a uint64_t. Should be 6 bytes in total. + * @returns A uint64_t representation of the mac. + */ +uint64_t macToUint64(const uint8_t *macArray); + +/** + * Takes a uint64_t value and stores the bits of the first 6 bytes in a uint8_t array. Assumes index 0 of the array should contain MSB. + * + * @param macValue The uint64_t value to convert to a mac array. Value must fit within 6 bytes. + * @param macArray A uint8_t array that will hold the mac address once the function returns. Should have a size of at least 6 bytes. + * @returns The macArray. + */ +uint8_t *uint64ToMac(uint64_t macValue, uint8_t *macArray); + +/** + * Conversion function that can be used on MeshBackend classes instead of dynamic_cast since RTTI is disabled. + * + * @param T The MeshBackend class pointer type to cast the meshBackendBaseInstance pointer into. + * @param meshBackendBaseInstance The instance pointer to cast. + * @returns A pointer of type T to meshBackendBaseInstance if meshBackendBaseInstance is of type T. nullptr otherwise. + */ +template +T meshBackendCast(MeshBackendBase *meshBackendBaseInstance) +{ + // The only valid template arguments are handled by the template specializations below, so ending up here is an error. + static_assert(std::is_same::value || std::is_same::value, + "Error: Invalid MeshBackend class type. Make sure the template argument to meshBackendCast is supported!"); +} + +// These template specializations allow us to put the main template functionality in the .cpp file (which gives better encapsulation). +template <> +EspnowMeshBackend *meshBackendCast(MeshBackendBase *meshBackendBaseInstance); + +template <> +TcpIpMeshBackend *meshBackendCast(MeshBackendBase *meshBackendBaseInstance); + #endif diff --git a/libraries/ESP8266WiFiMesh/src/UtilityFunctions.cpp b/libraries/ESP8266WiFiMesh/src/UtilityFunctions.cpp new file mode 100644 index 0000000000..6b4af4c018 --- /dev/null +++ b/libraries/ESP8266WiFiMesh/src/UtilityFunctions.cpp @@ -0,0 +1,45 @@ +/* + * UtilityFunctions + * Copyright (C) 2019 Anders Löfgren + * + * License (MIT license): + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include "UtilityFunctions.h" +#include + +bool macEqual(const uint8_t *macOne, const uint8_t *macTwo) +{ + for(int i = 0; i <= 5; i++) + { + if(macOne[i] != macTwo[i]) + { + return false; + } + } + + return true; +} + +uint64_t randomUint64() +{ + return (((uint64_t)RANDOM_REG32 << 32) | (uint64_t)RANDOM_REG32); +} diff --git a/libraries/ESP8266WiFiMesh/src/UtilityFunctions.h b/libraries/ESP8266WiFiMesh/src/UtilityFunctions.h new file mode 100644 index 0000000000..6fa0afb617 --- /dev/null +++ b/libraries/ESP8266WiFiMesh/src/UtilityFunctions.h @@ -0,0 +1,35 @@ +/* + * UtilityFunctions + * Copyright (C) 2019 Anders Löfgren + * + * License (MIT license): + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#ifndef __UTILITYFUNCTIONS_H__ +#define __UTILITYFUNCTIONS_H__ + +#include + +bool macEqual(const uint8_t *macOne, const uint8_t *macTwo); + +uint64_t randomUint64(); + +#endif diff --git a/libraries/ESP8266WiFiMesh/src/UtilityMethods.cpp b/libraries/ESP8266WiFiMesh/src/UtilityMethods.cpp index 795eacb638..9425d87ed2 100644 --- a/libraries/ESP8266WiFiMesh/src/UtilityMethods.cpp +++ b/libraries/ESP8266WiFiMesh/src/UtilityMethods.cpp @@ -1,5 +1,5 @@ /* - * TransmissionResult + * UtilityMethods * Copyright (C) 2018 Anders Löfgren * * License (MIT license): @@ -24,11 +24,15 @@ */ #include "TypeConversionFunctions.h" -#include "ESP8266WiFiMesh.h" +#include "MeshBackendBase.h" +#include "EspnowMeshBackend.h" -void ESP8266WiFiMesh::verboseModePrint(const String &stringToPrint, bool newline) +void MeshBackendBase::setVerboseModeState(bool enabled) {_verboseMode = enabled;} +bool MeshBackendBase::verboseMode() {return _verboseMode;} + +void MeshBackendBase::verboseModePrint(const String &stringToPrint, bool newline) { - if(_verboseMode) + if(verboseMode()) { if(newline) Serial.println(stringToPrint); @@ -37,45 +41,43 @@ void ESP8266WiFiMesh::verboseModePrint(const String &stringToPrint, bool newline } } -/** - * Calculate the current lwIP version number and store the numbers in the _lwipVersion array. - * lwIP version can be changed in the "Tools" menu of Arduino IDE. - */ -void ESP8266WiFiMesh::storeLwipVersion() +void EspnowMeshBackend::setVerboseModeState(bool enabled) {MeshBackendBase::setVerboseModeState(enabled); _staticVerboseMode = enabled;} +bool EspnowMeshBackend::verboseMode() {return staticVerboseMode();} + +void EspnowMeshBackend::verboseModePrint(const String &stringToPrint, bool newline) { - // ESP.getFullVersion() looks something like: - // SDK:2.2.1(cfd48f3)/Core:win-2.5.0-dev/lwIP:2.0.3(STABLE-2_0_3_RELEASE/glue:arduino-2.4.1-10-g0c0d8c2)/BearSSL:94e9704 - String fullVersion = ESP.getFullVersion(); + if(verboseMode()) + { + if(newline) + Serial.println(stringToPrint); + else + Serial.print(stringToPrint); + } +} - int i = fullVersion.indexOf("lwIP:") + 5; - char currentChar = fullVersion.charAt(i); +bool EspnowMeshBackend::staticVerboseMode() {return _staticVerboseMode;} - for(int versionPart = 0; versionPart < 3; versionPart++) +void EspnowMeshBackend::staticVerboseModePrint(const String &stringToPrint, bool newline) +{ + if(staticVerboseMode()) { - while(!isdigit(currentChar)) - { - currentChar = fullVersion.charAt(++i); - } - while(isdigit(currentChar)) - { - _lwipVersion[versionPart] = 10 * _lwipVersion[versionPart] + (currentChar - '0'); // Left shift and add digit value, in base 10. - currentChar = fullVersion.charAt(++i); - } + if(newline) + Serial.println(stringToPrint); + else + Serial.print(stringToPrint); } } -/** - * Check if the code is running on a version of lwIP that is at least minLwipVersion. - */ -bool ESP8266WiFiMesh::atLeastLwipVersion(const uint32_t minLwipVersion[3]) -{ - for(int versionPart = 0; versionPart < 3; versionPart++) +void MeshBackendBase::setPrintWarnings(bool printEnabled) {_printWarnings = printEnabled;} +bool MeshBackendBase::printWarnings() {return _printWarnings;} + +void MeshBackendBase::warningPrint(const String &stringToPrint, bool newline) +{ + if(printWarnings()) { - if(_lwipVersion[versionPart] > minLwipVersion[versionPart]) - return true; - else if(_lwipVersion[versionPart] < minLwipVersion[versionPart]) - return false; + if(newline) + Serial.println(stringToPrint); + else + Serial.print(stringToPrint); } - - return true; } From 5834c547173d273def4a5337fac814a6856785c4 Mon Sep 17 00:00:00 2001 From: Anders Date: Sat, 10 Aug 2019 20:35:48 +0200 Subject: [PATCH 02/30] - Add broadcast functionality. - Add createPermanentConnections argument to attemptAutoEncryptingTransmission method. - Reduce risk of misinterpreting acks by adding check for ack sender MAC. - Reduce _encryptionRequestTimeoutMs from 500 ms to 300 ms since this should give enough (100 %) margin to the level where problems start appearing (150 ms timeout) and also save a lot of time in case of request failure. - Improve comments. --- .../examples/HelloEspnow/HelloEspnow.ino | 69 ++++++++-- .../src/CompatibilityLayer.cpp | 4 +- .../ESP8266WiFiMesh/src/ESP8266WiFiMesh.cpp | 10 +- .../ESP8266WiFiMesh/src/ESP8266WiFiMesh.h | 6 +- .../src/EncryptedConnectionData.h | 2 +- .../ESP8266WiFiMesh/src/EspnowMeshBackend.cpp | 116 +++++++++++++---- .../ESP8266WiFiMesh/src/EspnowMeshBackend.h | 123 ++++++++++++------ .../src/EspnowProtocolInterpreter.h | 2 +- .../ESP8266WiFiMesh/src/JsonTranslator.h | 8 +- .../ESP8266WiFiMesh/src/MeshBackendBase.cpp | 2 + .../ESP8266WiFiMesh/src/MeshBackendBase.h | 36 +++-- libraries/ESP8266WiFiMesh/src/MessageData.cpp | 11 +- libraries/ESP8266WiFiMesh/src/MessageData.h | 1 + libraries/ESP8266WiFiMesh/src/MutexTracker.h | 2 +- .../ESP8266WiFiMesh/src/TcpIpMeshBackend.cpp | 10 +- .../ESP8266WiFiMesh/src/TcpIpMeshBackend.h | 11 +- .../src/TypeConversionFunctions.h | 14 +- 17 files changed, 305 insertions(+), 122 deletions(-) diff --git a/libraries/ESP8266WiFiMesh/examples/HelloEspnow/HelloEspnow.ino b/libraries/ESP8266WiFiMesh/examples/HelloEspnow/HelloEspnow.ino index bcbad36f20..d698f6a9e8 100644 --- a/libraries/ESP8266WiFiMesh/examples/HelloEspnow/HelloEspnow.ino +++ b/libraries/ESP8266WiFiMesh/examples/HelloEspnow/HelloEspnow.ino @@ -14,7 +14,7 @@ https://github.com/esp8266/Arduino/issues/1143 https://arduino-esp8266.readthedocs.io/en/latest/PROGMEM.html */ -const char exampleMeshName[] PROGMEM = "MeshNode_"; // The name of the mesh network. Used as prefix for the node SSID and to find other network nodes in the example networkFilter function below. +const char exampleMeshName[] PROGMEM = "MeshNode_"; // The name of the mesh network. Used as prefix for the node SSID and to find other network nodes during ESP-NOW broadcasts and in the example networkFilter function 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. // 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. @@ -35,16 +35,17 @@ unsigned int responseNumber = 0; String manageRequest(const String &request, MeshBackendBase &meshInstance); transmission_status_t manageResponse(const String &response, MeshBackendBase &meshInstance); void networkFilter(int numberOfNetworks, MeshBackendBase &meshInstance); +bool broadcastFilter(String &firstTransmission, EspnowMeshBackend &meshInstance); /* Create the mesh node object */ -EspnowMeshBackend espnowNode = EspnowMeshBackend(manageRequest, manageResponse, networkFilter, FPSTR(exampleWiFiPassword), espnowEncryptionKey, espnowHashKey, FPSTR(exampleMeshName), uint64ToString(ESP.getChipId()), true); +EspnowMeshBackend espnowNode = EspnowMeshBackend(manageRequest, manageResponse, networkFilter, broadcastFilter, FPSTR(exampleWiFiPassword), espnowEncryptionKey, espnowHashKey, FPSTR(exampleMeshName), uint64ToString(ESP.getChipId()), true); /** Callback for when other nodes send you a request @param request The request string received from another node in the mesh @param meshInstance The MeshBackendBase instance that called the function. - @returns 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) { // We do not store strings in flash (via F()) in this function. @@ -78,7 +79,7 @@ String manageRequest(const String &request, MeshBackendBase &meshInstance) { @param response The response string received from another node in the mesh @param meshInstance The MeshBackendBase instance that called the function. - @returns The status code resulting from the response, as an int + @return The status code resulting from the response, as an int */ transmission_status_t manageResponse(const String &response, MeshBackendBase &meshInstance) { transmission_status_t statusCode = TS_TRANSMISSION_COMPLETE; @@ -132,6 +133,42 @@ void networkFilter(int numberOfNetworks, MeshBackendBase &meshInstance) { } } +const char broadcastMetadataDelimiter = 23; // 23 = End-of-Transmission-Block (ETB) control character in ASCII + +/** + Callback used to decide which broadcast messages to accept. Only called for the first transmission in each broadcast. + If true is returned from this callback, the first broadcast transmission is saved until the entire broadcast message has been received. + The complete broadcast message will then be sent to the requestHandler (manageRequest in this example). + If false is returned from this callback, the broadcast message is discarded. + + @param firstTransmission The first transmission of the broadcast. + @param meshInstance The EspnowMeshBackend instance that called the function. + + @return True if the broadcast should be accepted. False otherwise. +*/ +bool broadcastFilter(String &firstTransmission, EspnowMeshBackend &meshInstance) { + /** + This example broadcastFilter will accept a transmission if it contains the broadcastMetadataDelimiter + and as metaData either no targetMeshName or a targetMeshName that matches the MeshName of meshInstance. + */ + + int32_t metadataEndIndex = firstTransmission.indexOf(broadcastMetadataDelimiter); + + if (metadataEndIndex == -1) { + return false; // broadcastMetadataDelimiter not found + } + + String targetMeshName = firstTransmission.substring(0, metadataEndIndex); + + if (targetMeshName != "" && meshInstance.getMeshName() != targetMeshName) { + return false; // Broadcast is for another mesh network + } else { + // Remove metadata from message and mark as accepted broadcast. + firstTransmission = firstTransmission.substring(metadataEndIndex + 1); + return true; + } +} + void setup() { // Prevents the flash memory from being worn out, see: https://github.com/esp8266/Arduino/issues/1054 . // This will however delay node WiFi start-up by about 700 ms. The delay is 900 ms if we otherwise would have stored the WiFi network we want to connect to. @@ -173,6 +210,7 @@ void setup() { // Storing our message in the EspnowMeshBackend instance is not required, but can be useful for organizing code, especially when using many EspnowMeshBackend instances. // Note that calling espnowNode.attemptTransmission will replace the stored message with whatever message is transmitted. + // Also note that the maximum allowed number of ASCII characters in a ESP-NOW message is given by EspnowMeshBackend::getMaxMessageLength(). espnowNode.setMessage(String(F("Hello world request #")) + String(requestNumber) + String(F(" from ")) + espnowNode.getMeshName() + espnowNode.getNodeID() + String(F("."))); } @@ -188,18 +226,18 @@ void loop() { EspnowMeshBackend::performEspnowMaintainance(); if (millis() - timeOfLastScan > 10000) { // Give other nodes some time to connect between data transfers. - uint32_t startTime = millis(); - Serial.println("\nPerforming unencrypted ESP-NOW transmissions."); + + uint32_t startTime = millis(); espnowNode.attemptTransmission(espnowNode.getMessage()); Serial.println("Scan and " + String(MeshBackendBase::latestTransmissionOutcomes.size()) + " transmissions done in " + String(millis() - startTime) + " ms."); + timeOfLastScan = millis(); + // Wait for response. espnowDelay continuously calls performEspnowMaintainance() so we will respond to ESP-NOW request while waiting. // Should not be used inside responseHandler, requestHandler or networkFilter callbacks since performEspnowMaintainance() can alter the ESP-NOW state. espnowDelay(100); - timeOfLastScan = millis(); - // One way to check how attemptTransmission worked out if (MeshBackendBase::latestTransmissionSuccessful()) { Serial.println(F("Transmission successful.")); @@ -222,6 +260,20 @@ void loop() { } } + Serial.println("\nPerforming ESP-NOW broadcast."); + + startTime = millis(); + + // Remove espnowNode.getMeshName() from the broadcastMetadata below to broadcast to all ESP-NOW nodes regardless of MeshName. + // Note that data that comes before broadcastMetadataDelimiter should not contain any broadcastMetadataDelimiter characters, + // otherwise the broadcastFilter function used in this example file will not work. + String broadcastMetadata = espnowNode.getMeshName() + String(broadcastMetadataDelimiter); + String broadcastMessage = String(F("Broadcast #")) + String(requestNumber) + String(F(" from ")) + espnowNode.getMeshName() + espnowNode.getNodeID() + String(F(".")); + espnowNode.broadcast(broadcastMetadata + broadcastMessage); + Serial.println("Broadcast to all mesh nodes done in " + String(millis() - startTime) + " ms."); + + espnowDelay(100); // Wait for responses (broadcasts can receive an unlimited number of responses, other transmissions can only receive one response). + Serial.println("\nPerforming encrypted ESP-NOW transmissions."); // We can create encrypted connections to individual nodes so that all ESP-NOW communication with the node will be encrypted. @@ -287,6 +339,7 @@ void loop() { // Or if we prefer we can just let the library automatically create brief encrypted connections which are long enough to transmit an encrypted message. // Note that encrypted responses will not be received, unless there already was an encrypted connection established with the peer before attemptAutoEncryptingTransmission was called. + // This can be remedied via the createPermanentConnections argument, though it must be noted that the maximum number of encrypted connections supported at a time is 6. espnowMessage = "This message is always encrypted, regardless of receiver."; Serial.println("\nTransmitting: " + espnowMessage); espnowNode.attemptAutoEncryptingTransmission(espnowMessage); diff --git a/libraries/ESP8266WiFiMesh/src/CompatibilityLayer.cpp b/libraries/ESP8266WiFiMesh/src/CompatibilityLayer.cpp index fd926a9351..0bb480acd0 100644 --- a/libraries/ESP8266WiFiMesh/src/CompatibilityLayer.cpp +++ b/libraries/ESP8266WiFiMesh/src/CompatibilityLayer.cpp @@ -27,9 +27,9 @@ /******************************************************************************************** * NOTE! * -* All method signatures in this file are deprecated and will be removed in core version 2.5.0. +* All method signatures in this file are deprecated and will be removed in core version 3.0.0. * If you are still using these methods, please consider migrating to the new API shown in -* the ESP8266WiFiMesh.h source file. +* the EspnowMeshBackend.h or TcpIpMeshBackend.h source files. * * TODO: delete this file. ********************************************************************************************/ diff --git a/libraries/ESP8266WiFiMesh/src/ESP8266WiFiMesh.cpp b/libraries/ESP8266WiFiMesh/src/ESP8266WiFiMesh.cpp index 12a41dd8eb..f4d198a5cb 100644 --- a/libraries/ESP8266WiFiMesh/src/ESP8266WiFiMesh.cpp +++ b/libraries/ESP8266WiFiMesh/src/ESP8266WiFiMesh.cpp @@ -337,7 +337,7 @@ void ESP8266WiFiMesh::fullStop(WiFiClient &currClient) /** * Wait for a WiFiClient to transmit * - * @returns: True if the client is ready, false otherwise. + * @return: True if the client is ready, false otherwise. * */ bool ESP8266WiFiMesh::waitForClientTransmission(WiFiClient &currClient, uint32_t maxWait) @@ -365,7 +365,7 @@ bool ESP8266WiFiMesh::waitForClientTransmission(WiFiClient &currClient, uint32_t * and pass that to the user-supplied responseHandler. * * @param currClient The client to which the message should be transmitted. - * @returns: A status code based on the outcome of the exchange. + * @return: A status code based on the outcome of the exchange. * */ transmission_status_t ESP8266WiFiMesh::exchangeInfo(WiFiClient &currClient) @@ -398,7 +398,7 @@ transmission_status_t ESP8266WiFiMesh::exchangeInfo(WiFiClient &currClient) /** * Handle data transfer process with a connected AP. * - * @returns: A status code based on the outcome of the data transfer attempt. + * @return: A status code based on the outcome of the data transfer attempt. */ transmission_status_t ESP8266WiFiMesh::attemptDataTransfer() { @@ -418,7 +418,7 @@ transmission_status_t ESP8266WiFiMesh::attemptDataTransfer() /** * Helper function that contains the core functionality for the data transfer process with a connected AP. * - * @returns: A status code based on the outcome of the data transfer attempt. + * @return: A status code based on the outcome of the data transfer attempt. */ transmission_status_t ESP8266WiFiMesh::attemptDataTransferKernel() { @@ -462,7 +462,7 @@ void ESP8266WiFiMesh::initiateConnectionToAP(const String &targetSSID, int targe * @param targetSSID The name of the AP the other node has set up. * @param targetChannel The WiFI channel of the AP the other node has set up. * @param targetBSSID The mac address of the AP the other node has set up. - * @returns: A status code based on the outcome of the connection and data transfer process. + * @return: A status code based on the outcome of the connection and data transfer process. * */ transmission_status_t ESP8266WiFiMesh::connectToNode(const String &targetSSID, int targetChannel, uint8_t *targetBSSID) diff --git a/libraries/ESP8266WiFiMesh/src/ESP8266WiFiMesh.h b/libraries/ESP8266WiFiMesh/src/ESP8266WiFiMesh.h index 7c5e908380..139b2e980f 100644 --- a/libraries/ESP8266WiFiMesh/src/ESP8266WiFiMesh.h +++ b/libraries/ESP8266WiFiMesh/src/ESP8266WiFiMesh.h @@ -194,7 +194,7 @@ class ESP8266WiFiMesh { static std::vector latestTransmissionOutcomes; /** - * @returns True if latest transmission was successful (i.e. latestTransmissionOutcomes is not empty and all entries have transmissionStatus TS_TRANSMISSION_COMPLETE). False otherwise. + * @return True if latest transmission was successful (i.e. latestTransmissionOutcomes is not empty and all entries have transmissionStatus TS_TRANSMISSION_COMPLETE). False otherwise. */ static bool latestTransmissionSuccessful(); @@ -217,7 +217,7 @@ class ESP8266WiFiMesh { * If another instance takes control over the AP after the pointer is created, * the created pointer will still point to the old AP instance. * - * @returns A pointer to the ESP8266WiFiMesh instance currently in control of the ESP8266 AP, + * @return A pointer to the ESP8266WiFiMesh instance currently in control of the ESP8266 AP, * or nullptr if there is no active AP controller. */ static ESP8266WiFiMesh * getAPController(); @@ -225,7 +225,7 @@ class ESP8266WiFiMesh { /** * Check if this ESP8266WiFiMesh instance is in control of the ESP8266 AP. * - * @returns True if this ESP8266WiFiMesh instance is in control of the ESP8266 AP. False otherwise. + * @return True if this ESP8266WiFiMesh instance is in control of the ESP8266 AP. False otherwise. */ bool isAPController(); diff --git a/libraries/ESP8266WiFiMesh/src/EncryptedConnectionData.h b/libraries/ESP8266WiFiMesh/src/EncryptedConnectionData.h index 6b008b99a4..312b5139af 100644 --- a/libraries/ESP8266WiFiMesh/src/EncryptedConnectionData.h +++ b/libraries/ESP8266WiFiMesh/src/EncryptedConnectionData.h @@ -48,7 +48,7 @@ class EncryptedConnectionData { /** * @param resultArray An uint8_t array with at least size 6. * - * @returns The interface MAC used for communicating with the peer. + * @return The interface MAC used for communicating with the peer. */ uint8_t *getEncryptedPeerMac(uint8_t *resultArray) const; uint8_t *getUnencryptedPeerMac(uint8_t *resultArray) const; diff --git a/libraries/ESP8266WiFiMesh/src/EspnowMeshBackend.cpp b/libraries/ESP8266WiFiMesh/src/EspnowMeshBackend.cpp index 7bcca0d578..6c249452d8 100644 --- a/libraries/ESP8266WiFiMesh/src/EspnowMeshBackend.cpp +++ b/libraries/ESP8266WiFiMesh/src/EspnowMeshBackend.cpp @@ -35,6 +35,8 @@ static const uint8_t maxEncryptedConnections = 6; // This is limited by the ESP- static const uint64_t uint64MSB = 0x8000000000000000; +const uint8_t EspnowMeshBackend::broadcastMac[6] = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}; + bool EspnowMeshBackend::_espnowTransmissionMutex = false; EspnowMeshBackend *EspnowMeshBackend::_espnowRequestManager = nullptr; @@ -51,7 +53,7 @@ std::vector EspnowMeshBackend::encryptedConnections = {} uint32_t EspnowMeshBackend::_espnowTransmissionTimeoutMs = 40; uint32_t EspnowMeshBackend::_espnowRetransmissionIntervalMs = 15; -uint32_t EspnowMeshBackend::_encryptionRequestTimeoutMs = 500; +uint32_t EspnowMeshBackend::_encryptionRequestTimeoutMs = 300; bool EspnowMeshBackend::_espnowSendConfirmed = false; @@ -69,7 +71,7 @@ uint32_t EspnowMeshBackend::_unencryptedMessageID = 0; // which takes 2000 ms + some margin to send. Also, we want to avoid old entries taking up memory if they cannot be sent, // so storage duration should not be too long. uint32_t EspnowMeshBackend::_logEntryLifetimeMs = 2500; -uint32_t EspnowMeshBackend::_responseTimeoutMs = 5000; +uint32_t EspnowMeshBackend::_broadcastResponseTimeoutMs = 1000; // This is shorter than _logEntryLifetimeMs to preserve RAM since broadcasts are not deleted from sentRequests until they expire. uint32_t EspnowMeshBackend::_timeOfLastLogClear = 0; uint32_t EspnowMeshBackend::_criticalHeapLevel = 6000; // In bytes uint32_t EspnowMeshBackend::_criticalHeapLevelBuffer = 6000; // In bytes @@ -95,15 +97,16 @@ void espnowDelay(uint32_t durationMs) } } -EspnowMeshBackend::EspnowMeshBackend(requestHandlerType requestHandler, responseHandlerType responseHandler, - networkFilterType networkFilter, const String &meshPassword, const uint8_t espnowEncryptionKey[espnowEncryptionKeyLength], +EspnowMeshBackend::EspnowMeshBackend(requestHandlerType requestHandler, responseHandlerType responseHandler, networkFilterType networkFilter, + broadcastFilterType broadcastFilter, const String &meshPassword, const uint8_t espnowEncryptionKey[espnowEncryptionKeyLength], const uint8_t espnowHashKey[espnowHashKeyLength], const String &ssidPrefix, const String &ssidSuffix, bool verboseMode, uint8 meshWiFiChannel) : MeshBackendBase(requestHandler, responseHandler, networkFilter, MB_ESP_NOW) { // Reserve the maximum possible usage early on to prevent heap fragmentation later. encryptedConnections.reserve(maxEncryptedConnections); - + + setBroadcastFilter(broadcastFilter); setSSID(ssidPrefix, "", ssidSuffix); setMeshPassword(meshPassword); setEspnowEncryptionKey(espnowEncryptionKey); @@ -206,7 +209,7 @@ void EspnowMeshBackend::espnowReceiveCallbackWrapper(uint8_t *macaddr, uint8_t * //Serial.print("Received from Mac: " + macToString(macaddr) + " ID: " + uint64ToString(receivedMessageID)); //Serial.println(transmissionEncrypted ? " Encrypted" : " Unencrypted"); - if(messageType == 'Q') // Question (request) + if(messageType == 'Q' || messageType == 'B') // Question (request) or Broadcast { if(ESP.getFreeHeap() <= criticalHeapLevel()) { @@ -215,7 +218,7 @@ void EspnowMeshBackend::espnowReceiveCallbackWrapper(uint8_t *macaddr, uint8_t * } if(currentEspnowRequestManager) - { + { if(!requestReceived(uint64StationMac, receivedMessageID)) // If the request has not already been received { if(transmissionEncrypted) @@ -262,6 +265,12 @@ void EspnowMeshBackend::espnowReceiveCallbackWrapper(uint8_t *macaddr, uint8_t * requestMac = uint64StationMac; requestSender = getOwnerOfSentRequest(requestMac, receivedMessageID); } + + // Or if it was sent as a broadcast. (A broadcast can never be encrypted) + if(!requestSender) + { + requestSender = getOwnerOfSentRequest(uint64BroadcastMac, receivedMessageID); + } } // If this node sent the request and it has not already been answered. @@ -286,7 +295,7 @@ void EspnowMeshBackend::espnowReceiveCallbackWrapper(uint8_t *macaddr, uint8_t * } else { - assert(messageType == 'Q' || messageType == 'A' || messageType == 'S' || messageType == 'P' || messageType == 'C'); + assert(messageType == 'Q' || messageType == 'A' || messageType == 'B' || messageType == 'S' || messageType == 'P' || messageType == 'C'); } //Serial.println("espnowReceiveCallbackWrapper duration " + String(millis() - callbackStart)); @@ -497,8 +506,9 @@ bool EspnowMeshBackend::activateEspnow() esp_now_register_recv_cb(espnowReceiveCallbackWrapper); esp_now_register_send_cb([](uint8_t* mac, uint8_t sendStatus) { - (void)mac; // This is useful to remove a "unused parameter" compiler warning. Does nothing else. - if(!sendStatus) // sendStatus == 0 when send was OK. + if(_espnowSendConfirmed) + return; + else if(!sendStatus && 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. }); @@ -546,9 +556,9 @@ uint32_t EspnowMeshBackend::logEntryLifetimeMs() return _logEntryLifetimeMs; } -uint32_t EspnowMeshBackend::responseTimeoutMs() +uint32_t EspnowMeshBackend::broadcastResponseTimeoutMs() { - return _responseTimeoutMs; + return _broadcastResponseTimeoutMs; } void EspnowMeshBackend::setCriticalHeapLevelBuffer(uint32_t bufferInBytes) @@ -576,6 +586,24 @@ void EspnowMeshBackend::deleteExpiredLogEntries(std::map, } } +void EspnowMeshBackend::deleteExpiredLogEntries(std::map, RequestData> &logEntries, uint32_t requestLifetimeMs, uint32_t broadcastLifetimeMs) +{ + for(typename std::map, RequestData>::iterator entryIterator = logEntries.begin(); + entryIterator != logEntries.end(); ) + { + bool broadcast = entryIterator->first.first == uint64BroadcastMac; + uint32_t timeSinceCreation = entryIterator->second.timeSinceCreation(); + + if((!broadcast && timeSinceCreation > requestLifetimeMs) + || (broadcast && timeSinceCreation > broadcastLifetimeMs)) + { + entryIterator = logEntries.erase(entryIterator); + } + else + ++entryIterator; + } +} + template void EspnowMeshBackend::deleteExpiredLogEntries(std::list &logEntries, uint32_t maxEntryLifetimeMs) { @@ -633,7 +661,7 @@ void EspnowMeshBackend::clearOldLogEntries() deleteExpiredLogEntries(receivedEspnowTransmissions, logEntryLifetimeMs()); deleteExpiredLogEntries(receivedRequests, logEntryLifetimeMs()); // Just needs to be long enough to not accept repeated transmissions by mistake. - deleteExpiredLogEntries(sentRequests, logEntryLifetimeMs()); + deleteExpiredLogEntries(sentRequests, logEntryLifetimeMs(), broadcastResponseTimeoutMs()); deleteExpiredLogEntries(responsesToSend, logEntryLifetimeMs()); deleteExpiredLogEntries(peerRequestConfirmationsToSend, getEncryptionRequestTimeout()); } @@ -747,6 +775,9 @@ void EspnowMeshBackend::setAutoEncryptionDuration(uint32_t duration) } uint32_t EspnowMeshBackend::getAutoEncryptionDuration() {return _autoEncryptionDuration;} +void EspnowMeshBackend::setBroadcastFilter(broadcastFilterType broadcastFilter) {_broadcastFilter = broadcastFilter;} +EspnowMeshBackend::broadcastFilterType EspnowMeshBackend::getBroadcastFilter() {return _broadcastFilter;} + bool EspnowMeshBackend::usesConstantSessionKey(char messageType) { return messageType == 'A' || messageType == 'C'; @@ -781,7 +812,6 @@ transmission_status_t EspnowMeshBackend::espnowSendToNode(const String &message, } } -static const uint8_t broadcastMac[6] = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}; // Saved for future use. TODO transmission_status_t EspnowMeshBackend::espnowSendToNodeUnsynchronized(const String message, const uint8_t *targetBSSID, char messageType, uint64_t messageID, EspnowMeshBackend *espnowInstance) { using namespace EspnowProtocolInterpreter; @@ -810,7 +840,7 @@ transmission_status_t EspnowMeshBackend::espnowSendToNodeUnsynchronized(const St // We thus prefer to keep the code simple and performant instead. // Very large messages can always be split by the user as required. assert(transmissionsRequired <= getMaxTransmissionsPerMessage()); - assert(messageType == 'Q' || messageType == 'A' || messageType == 'S' || messageType == 'P' || messageType == 'C'); + assert(messageType == 'Q' || messageType == 'A' || messageType == 'B' || messageType == 'S' || messageType == 'P' || messageType == 'C'); if(messageType == 'P' || messageType == 'C') { assert(transmissionsRequired == 1); // These messages are assumed to be contained in one message by the receive callbacks. @@ -824,9 +854,9 @@ transmission_status_t EspnowMeshBackend::espnowSendToNodeUnsynchronized(const St { ////// Manage logs ////// - if(transmissionsRemaining == 0 && messageType == 'Q') + if(transmissionsRemaining == 0 && (messageType == 'Q' || messageType == 'B')) { - assert(espnowInstance); // espnowInstance required when transmitting 'Q' type messages. + assert(espnowInstance); // espnowInstance required when transmitting 'Q' and 'B' type messages. // If we are sending the last transmission of a request we should store the sent request in the log no matter if we receive an ack for the final transmission or not. // That way we will always be ready to receive the response to the request when there is a chance the request message was transmitted successfully, // even if the final ack for the request message was lost. @@ -1000,8 +1030,27 @@ void EspnowMeshBackend::espnowReceiveCallback(uint8_t *macaddr, uint8_t *dataArr if(espnowIsMessageStart(dataArray)) { - // Does nothing if key already in receivedEspnowTransmissions - receivedEspnowTransmissions.insert(std::make_pair(std::make_pair(macAndType, messageID), MessageData(dataArray, len))); + if(messageType == 'B') + { + String message = espnowGetMessageContent(dataArray, len); + setSenderMac(macaddr); + setReceivedEncryptedMessage(usesEncryption(messageID)); + bool acceptBroadcast = getBroadcastFilter()(message, *this); + if(acceptBroadcast) + { + // Does nothing if key already in receivedEspnowTransmissions + receivedEspnowTransmissions.insert(std::make_pair(std::make_pair(macAndType, messageID), MessageData(message, espnowGetTransmissionsRemaining(dataArray)))); + } + else + { + return; + } + } + else + { + // Does nothing if key already in receivedEspnowTransmissions + receivedEspnowTransmissions.insert(std::make_pair(std::make_pair(macAndType, messageID), MessageData(dataArray, len))); + } } else { @@ -1038,7 +1087,7 @@ void EspnowMeshBackend::espnowReceiveCallback(uint8_t *macaddr, uint8_t *dataArr std::map, MessageData>::iterator storedMessageIterator = receivedEspnowTransmissions.find(std::make_pair(macAndType, messageID)); assert(storedMessageIterator != receivedEspnowTransmissions.end()); - + // Copy totalMessage in case user callbacks (request/responseHandler) do something odd with receivedEspnowTransmissions list. String totalMessage = storedMessageIterator->second.getTotalMessage(); // https://stackoverflow.com/questions/134731/returning-a-const-reference-to-an-object-instead-of-a-copy It is likely that most compilers will perform Named Value Return Value Optimisation in this case @@ -1046,7 +1095,7 @@ void EspnowMeshBackend::espnowReceiveCallback(uint8_t *macaddr, uint8_t *dataArr //Serial.println("methodStart erase done " + String(millis() - methodStart)); - if(messageType == 'Q') // Question (request) + if(messageType == 'Q' || messageType == 'B') // Question (request) or Broadcast { storeReceivedRequest(uint64Mac, messageID, TimeTracker(millis())); //Serial.println("methodStart request stored " + String(millis() - methodStart)); @@ -1082,7 +1131,7 @@ void EspnowMeshBackend::espnowReceiveCallback(uint8_t *macaddr, uint8_t *dataArr } else { - assert(messageType == 'Q' || messageType == 'A'); + assert(messageType == 'Q' || messageType == 'A' || messageType == 'B'); } ESP.wdtFeed(); // Prevents WDT reset in case we receive a lot of transmissions without break. @@ -1286,7 +1335,7 @@ encrypted_connection_status_t EspnowMeshBackend::addEncryptedConnection(uint8_t // No capacity for more encrypted connections. return ECS_MAX_CONNECTIONS_REACHED_SELF; } - // int esp_now_add_peer(u8 *mac_addr, u8 role, u8 channel, u8 *key, u8 key_len), returns 0 on success + // returns 0 on success: int esp_now_add_peer(u8 *mac_addr, u8 role, u8 channel, u8 *key, u8 key_len) // Only MAC, encryption key and key length (16) actually matter. The rest is not used by ESP-NOW. else if(0 == esp_now_add_peer(peerStaMac, ESP_NOW_ROLE_CONTROLLER, getWiFiChannel(), getEspnowEncryptionKey(encryptionKeyArray), espnowEncryptionKeyLength)) { @@ -1932,7 +1981,7 @@ void EspnowMeshBackend::attemptTransmission(const String &message, bool scan, bo } } -void EspnowMeshBackend::attemptAutoEncryptingTransmission(const String &message, bool scan, bool scanAllWiFiChannels) +void EspnowMeshBackend::attemptAutoEncryptingTransmission(const String &message, bool scan, bool scanAllWiFiChannels, bool createPermanentConnections) { MutexTracker outerMutexTracker(_espnowTransmissionMutex, handlePostponedRemovals); if(!outerMutexTracker.mutexCaptured()) @@ -1990,7 +2039,10 @@ void EspnowMeshBackend::attemptAutoEncryptingTransmission(const String &message, innerMutexTracker.releaseMutex(); - connectionStatus = requestFlexibleTemporaryEncryptedConnection(currentBSSID, getAutoEncryptionDuration()); + if(createPermanentConnections) + connectionStatus = requestEncryptedConnection(currentBSSID); + else + connectionStatus = requestFlexibleTemporaryEncryptedConnection(currentBSSID, getAutoEncryptionDuration()); innerMutexTracker = MutexTracker(_espnowTransmissionMutex); if(!innerMutexTracker.mutexCaptured()) @@ -2023,7 +2075,7 @@ void EspnowMeshBackend::attemptAutoEncryptingTransmission(const String &message, latestTransmissionOutcomes.push_back(TransmissionResult{.origin = currentNetwork, .transmissionStatus = TS_CONNECTION_FAILED}); } - if(!encryptedConnection) + if(!encryptedConnection && !createPermanentConnections) { // Remove any connection that was added during the transmission attempt. removeEncryptedConnectionUnprotected(currentBSSID); @@ -2041,6 +2093,18 @@ void EspnowMeshBackend::attemptAutoEncryptingTransmission(const String &message, } } +void EspnowMeshBackend::broadcast(const String &message) +{ + MutexTracker mutexTracker(_espnowTransmissionMutex, handlePostponedRemovals); + if(!mutexTracker.mutexCaptured()) + { + assert(false && "ERROR! Transmission in progress. Don't call broadcast from callbacks as this may corrupt program state! Aborting."); + return; + } + + espnowSendToNode(message, broadcastMac, 'B', this); +} + void EspnowMeshBackend::sendEspnowResponses() { //uint32_t startTime = millis(); diff --git a/libraries/ESP8266WiFiMesh/src/EspnowMeshBackend.h b/libraries/ESP8266WiFiMesh/src/EspnowMeshBackend.h index a340029a2e..6292a5eb35 100644 --- a/libraries/ESP8266WiFiMesh/src/EspnowMeshBackend.h +++ b/libraries/ESP8266WiFiMesh/src/EspnowMeshBackend.h @@ -96,6 +96,10 @@ class RequestData; class EspnowMeshBackend : public MeshBackendBase { +protected: + + typedef std::function broadcastFilterType; + public: /** @@ -106,6 +110,7 @@ class EspnowMeshBackend : public MeshBackendBase { * @param responseHandler The callback handler for dealing with received responses. Takes a string as an argument which * is the response string received from another node. Returns a transmission status code as a transmission_status_t. * @param networkFilter The callback handler for deciding which WiFi networks to connect to. + * @param broadcastFilter The callback handler for deciding which ESP-NOW broadcasts to accept. * @param meshPassword The WiFi password for the mesh network. * @param ssidPrefix The prefix (first part) of the node SSID. * @param ssidSuffix The suffix (last part) of the node SSID. @@ -118,7 +123,7 @@ class EspnowMeshBackend : public MeshBackendBase { * make it impossible for other stations to detect the APs whose WiFi channels have changed. * */ - EspnowMeshBackend(requestHandlerType requestHandler, responseHandlerType responseHandler, networkFilterType networkFilter, + EspnowMeshBackend(requestHandlerType requestHandler, responseHandlerType responseHandler, networkFilterType networkFilter, broadcastFilterType broadcastFilter, const String &meshPassword, const uint8_t espnowEncryptionKey[EspnowProtocolInterpreter::espnowEncryptionKeyLength], const uint8_t espnowHashKey[EspnowProtocolInterpreter::espnowHashKeyLength], const String &ssidPrefix, const String &ssidSuffix, bool verboseMode = false, uint8 meshWiFiChannel = 1); @@ -167,21 +172,45 @@ class EspnowMeshBackend : public MeshBackendBase { /** * Deactivates Espnow for this node. Call begin() on a EspnowMeshBackend instance to reactivate Espnow. * - * @returns True if deactivation was successful. False otherwise. + * @return True if deactivation was successful. False otherwise. */ static bool deactivateEspnow(); void attemptTransmission(const String &message, bool scan = true, bool scanAllWiFiChannels = false) override; - // Will ensure that an encrypted connection exists to each target node before sending the message, - // establishing a temporary encrypted connection with duration getAutoEncryptionDuration() first if neccessary. - // If an encrypted connection cannot be established to a target node, no message will be sent to that node. - // Note that if an encrypted connection to a target node is not present before this method is called, the response from said node will likely not be received - // since it will be encrypted and the auto encrypted connection to the node is immediately removed after transmission. - // Also note that if a temporary encrypted connection already exists to a target node, this method will slightly extend the connection duration - // depending on the time it takes to verify the connection to the node. This can substantially increase the connection duration if many auto encrypting - // transmissions occurs. - void attemptAutoEncryptingTransmission(const String &message, bool scan = true, bool scanAllWiFiChannels = false); + /* + * Will ensure that an encrypted connection exists to each target node before sending the message, + * establishing a temporary encrypted connection with duration getAutoEncryptionDuration() first if neccessary. + * If an encrypted connection cannot be established to a target node, no message will be sent to that node. + * Note that if an encrypted connection to a target node is not present before this method is called, the response from said node will likely not be received + * since it will be encrypted and the auto encrypted connection to the node is immediately removed after transmission (unless the createPermanentConnections argument is set to true). + * Also note that if a temporary encrypted connection already exists to a target node, this method will slightly extend the connection duration + * depending on the time it takes to verify the connection to the node. This can substantially increase the connection duration if many auto encrypting + * transmissions occurs. + * + * @param message The message to send to other nodes. It will be stored in the class instance until replaced via attemptTransmission or setMessage. + * @param scan Scan for new networks and call the networkFilter function with the scan results. When set to false, only the data already in connectionQueue will be used for the transmission. + * @param scanAllWiFiChannels Scan all WiFi channels during a WiFi scan, instead of just the channel the MeshBackendBase instance is using. + * Scanning all WiFi channels takes about 2100 ms, compared to just 60 ms if only channel 1 (standard) is scanned. + * Note that if the ESP8266 has an active AP, that AP will switch WiFi channel to match that of any other AP the ESP8266 connects to. + * This can make it impossible for other nodes to detect the AP if they are scanning the wrong WiFi channel. + * @param createPermanentConnections Ensures encrypted connections used for this transmission are permanent and not removed once the transmission is complete. + * This guarantees that encrypted responses to the transmission is received, as long as the encrypted connection is not removed by other means. + * Note that a maximum of 6 encrypted ESP-NOW connections can be maintained at the same time by the node. + * Defaults to false. + */ + void attemptAutoEncryptingTransmission(const String &message, bool scan = true, bool scanAllWiFiChannels = false, bool createPermanentConnections = false); + + /** + * Send a message simultaneously to all nearby nodes which have ESP-NOW activated. + * A broadcast is always treated as a request by the receiving node. + * There is no limit to the number of responses a node can get when sending a broadcast, it will always accept new responses until the broadcastResponseTimeout is reached. + * This also means that the broadcaster can receive duplicate responses from the same node if transmission conditions are poor and an ack is lost. + * A broadcast can never be sent encrypted. + * + * @param message The message to send to the other nodes. Unlike the attemptTransmission method, the message will not be stored in the class instance, since there is no certain way to change the message during an ongoing broadcast. + */ + void broadcast(const String &message); /** * Set the EspnowMeshBackend instance responsible for handling incoming requests. The requestHandler of the instance will be called upon receiving ESP-NOW requests. @@ -196,7 +225,7 @@ class EspnowMeshBackend : public MeshBackendBase { /** * Check if this EspnowMeshBackend instance is the espnowRequestManager. * - * @returns True if this EspnowMeshBackend is the espnowRequestManager. False otherwise. + * @return True if this EspnowMeshBackend is the espnowRequestManager. False otherwise. */ bool isEspnowRequestManager(); @@ -260,7 +289,7 @@ class EspnowMeshBackend : public MeshBackendBase { /** * Hint: Use String.length() to get the ASCII length of a String. * - * @returns The maximum number of bytes (or ASCII characters) a transmission can contain. Note that non-ASCII characters usually require the space of at least two ASCII characters each. + * @return The maximum number of bytes (or ASCII characters) a transmission can contain. Note that non-ASCII characters usually require the space of at least two ASCII characters each. */ static uint32_t getMaxMessageBytesPerTransmission(); @@ -282,11 +311,11 @@ class EspnowMeshBackend : public MeshBackendBase { /** * Hint: Use String.length() to get the ASCII length of a String. * - * @returns The maximum length in bytes an ASCII message is allowed to be when transmitted by this node. Note that non-ASCII characters usually require at least two bytes each. + * @return The maximum length in bytes an ASCII message is allowed to be when transmitted/broadcasted by this node. Note that non-ASCII characters usually require at least two bytes each. */ static uint32_t getMaxMessageLength(); - /** + /** * Set whether the normal events occurring in the library will be printed to Serial or not. Off by default. * This setting is shared by all EspnowMeshBackend instances. * @@ -306,7 +335,7 @@ class EspnowMeshBackend : public MeshBackendBase { /** * Same as verboseMode(), but used for printing from static functions. * - * @returns True if the normal events occurring in the library will be printed to Serial. False otherwise. + * @return True if the normal events occurring in the library will be printed to Serial. False otherwise. */ static bool staticVerboseMode(); @@ -322,7 +351,7 @@ class EspnowMeshBackend : public MeshBackendBase { * Get the message of the response at responseIndex among the responses that are scheduled for transmission from this node. * * @param responseIndex The index of the response. Must be lower than numberOfScheduledResponses(). - * @returns A String containing the message of the response at responseIndex. + * @return A String containing the message of the response at responseIndex. */ static String getScheduledResponseMessage(uint32_t responseIndex); @@ -330,14 +359,14 @@ class EspnowMeshBackend : public MeshBackendBase { * Get the MAC address for the recipient of the response at responseIndex among the responses that are scheduled for transmission from this node. * * @param responseIndex The index of the response. Must be lower than numberOfScheduledResponses(). - * @returns An array with six bytes containing the MAC address for the recipient of the response at responseIndex. + * @return An array with six bytes containing the MAC address for the recipient of the response at responseIndex. */ static const uint8_t *getScheduledResponseRecipient(uint32_t responseIndex); /** * Get the number of ESP-NOW responses that are scheduled for transmission from this node. * - * @returns The number of ESP-NOW responses scheduled for transmission. + * @return The number of ESP-NOW responses scheduled for transmission. */ static uint32_t numberOfScheduledResponses(); @@ -383,32 +412,35 @@ class EspnowMeshBackend : public MeshBackendBase { void setAutoEncryptionDuration(uint32_t duration); uint32_t getAutoEncryptionDuration(); + + void setBroadcastFilter(broadcastFilterType broadcastFilter); + broadcastFilterType getBroadcastFilter(); /** - * Get the MAC address of the sender of the most recently received ESP-NOW request or response to this EspnowMeshBackend instance. + * Get the MAC address of the sender of the most recently received ESP-NOW request, response or broadcast to this EspnowMeshBackend instance. * Returns a String. * By default the MAC will be that of the sender's station interface. The only exception is for unencrypted * responses to requests sent to an AP interface, which will return the response sender's AP interface MAC. * - * @returns A String filled with a hexadecimal representation of the MAC, without delimiters. + * @return A String filled with a hexadecimal representation of the MAC, without delimiters. */ String getSenderMac(); /** - * Get the MAC address of the sender of the most recently received ESP-NOW request or response to this EspnowMeshBackend instance. + * Get the MAC address of the sender of the most recently received ESP-NOW request, response or broadcast to this EspnowMeshBackend instance. * Returns a uint8_t array. * By default the MAC will be that of the sender's station interface. The only exception is for unencrypted * responses to requests sent to an AP interface, which will return the response sender's AP interface MAC. * * @param macArray The array that should store the MAC address. Must be at least 6 bytes. - * @returns macArray filled with the sender MAC. + * @return macArray filled with the sender MAC. */ uint8_t *getSenderMac(uint8_t *macArray); /** - * Get whether the ESP-NOW request or response which was most recently received by this EspnowMeshBackend instance was encrypted or not. + * Get whether the ESP-NOW request, response or broadcast which was most recently received by this EspnowMeshBackend instance was encrypted or not. * - * @returns If true, the request or response was encrypted. If false, it was unencrypted. + * @return If true, the request, response or broadcast was encrypted. If false, it was unencrypted. */ bool receivedEncryptedMessage(); @@ -457,7 +489,7 @@ class EspnowMeshBackend : public MeshBackendBase { */ static uint8_t numberOfEncryptedConnections(); - // @returns resultArray filled with the MAC to the encrypted interface of the node, if an encrypted connection exists. nulltpr otherwise. + // @return resultArray filled with the MAC to the encrypted interface of the node, if an encrypted connection exists. nulltpr otherwise. static uint8_t *getEncryptedMac(const uint8_t *peerMac, uint8_t *resultArray); // Create a string containing the current state of the encrypted connection for this node. The result can be used as input to addEncryptedConnection. @@ -492,7 +524,7 @@ class EspnowMeshBackend : public MeshBackendBase { static espnow_connection_type_t getConnectionInfo(uint32_t connectionIndex, uint32_t *remainingDuration = nullptr, uint8_t *peerMac = nullptr); /** - * @returns The proportion of ESP-NOW requests made by this node that have failed, since power on or latest reset. + * @return The proportion of ESP-NOW requests made by this node that have failed, since power on or latest reset. */ static double getTransmissionFailRate(); @@ -506,6 +538,9 @@ class EspnowMeshBackend : public MeshBackendBase { typedef std::vector::iterator connectionLogIterator; static connectionLogIterator connectionLogEndIterator(); + static const uint8_t broadcastMac[6]; + static const uint64_t uint64BroadcastMac = 0xFFFFFFFFFFFF; + bool activateEspnow(); /* @@ -533,16 +568,16 @@ class EspnowMeshBackend : public MeshBackendBase { static encrypted_connection_removal_outcome_t removeEncryptedConnectionUnprotected(connectionLogIterator &connectionIterator, std::vector::iterator *resultingIterator); /** - * Set the MAC address considered to be the sender of the most recently received ESP-NOW request or response. + * Set the MAC address considered to be the sender of the most recently received ESP-NOW request, response or broadcast. * * @param macArray An uint8_t array which contains the MAC address to store. The method will store the first 6 bytes of the array. */ void setSenderMac(uint8_t *macArray); /** - * Set whether the most recently received ESP-NOW request or response is presented as having been encrypted or not. + * Set whether the most recently received ESP-NOW request, response or broadcast is presented as having been encrypted or not. * - * @param receivedEncryptedMessage If true, the request or response is presented as having been encrypted. + * @param receivedEncryptedMessage If true, the request, response or broadcast is presented as having been encrypted. */ void setReceivedEncryptedMessage(bool receivedEncryptedMessage); @@ -556,7 +591,7 @@ class EspnowMeshBackend : public MeshBackendBase { /** * Check if there is an ongoing ESP-NOW transmission in the library. Used to avoid interrupting transmissions. * - * @returns True if a transmission initiated by a public method is in progress. + * @return True if a transmission initiated by a public method is in progress. */ static bool transmissionInProgress(); @@ -587,8 +622,8 @@ class EspnowMeshBackend : public MeshBackendBase { * Send an ESP-NOW message to the ESP8266 that has the MAC address specified in targetBSSID. * * @param messageType The identifier character for the type of message to send. Choices are 'Q' for question (request), - * 'A' for answer (response), 'S' for synchronization request, 'P' for peer request and 'C' for peer request confirmation. - * @returns The transmission status for the transmission. + * 'A' for answer (response), 'B' for broadcast, 'S' for synchronization request, 'P' for peer request and 'C' for peer request confirmation. + * @return The transmission status for the transmission. */ // Send a message to the node having targetBSSID as mac, changing targetBSSID to the mac of the encrypted connection if it exists and ensuring such an encrypted connection is synchronized. static transmission_status_t espnowSendToNode(const String &message, const uint8_t *targetBSSID, char messageType, EspnowMeshBackend *espnowInstance = nullptr); @@ -648,11 +683,11 @@ class EspnowMeshBackend : public MeshBackendBase { static EncryptedConnectionLog *getEncryptedConnection(const uint8_t *peerMac); static EncryptedConnectionLog *getTemporaryEncryptedConnection(const uint8_t *peerMac); - //@returns iterator to connection in connectionVector, or connectionVector.end() if element not found + //@return iterator to connection in connectionVector, or connectionVector.end() if element not found template static typename std::vector::iterator getEncryptedConnectionIterator(const uint8_t *peerMac, typename std::vector &connectionVector); static bool getEncryptedConnectionIterator(const uint8_t *peerMac, connectionLogIterator &iterator); - // @returns true if an encrypted connection to peerMac is found and the found connection is temporary. Only changes iterator if true is returned. + // @return true if an encrypted connection to peerMac is found and the found connection is temporary. Only changes iterator if true is returned. static bool getTemporaryEncryptedConnectionIterator(const uint8_t *peerMac, connectionLogIterator &iterator); static espnow_connection_type_t getConnectionInfoHelper(const EncryptedConnectionLog *encryptedConnection, uint32_t *remainingDuration, uint8_t *peerMac = nullptr); @@ -665,13 +700,15 @@ class EspnowMeshBackend : public MeshBackendBase { template static void deleteExpiredLogEntries(std::map, T> &logEntries, uint32_t maxEntryLifetimeMs); + static void deleteExpiredLogEntries(std::map, RequestData> &logEntries, uint32_t requestLifetimeMs, uint32_t broadcastLifetimeMs); + template static void deleteExpiredLogEntries(std::list &logEntries, uint32_t maxEntryLifetimeMs); static uint32_t _logEntryLifetimeMs; static uint32_t logEntryLifetimeMs(); - static uint32_t _responseTimeoutMs; - static uint32_t responseTimeoutMs(); + static uint32_t _broadcastResponseTimeoutMs; + static uint32_t broadcastResponseTimeoutMs(); static uint32_t _encryptionRequestTimeoutMs; @@ -681,6 +718,8 @@ class EspnowMeshBackend : public MeshBackendBase { static bool _espnowSendConfirmed; + broadcastFilterType _broadcastFilter; + static String _ongoingPeerRequestNonce; static EspnowMeshBackend *_ongoingPeerRequester; static encrypted_connection_status_t _ongoingPeerRequestResult; @@ -711,14 +750,14 @@ class EspnowMeshBackend : public MeshBackendBase { /** * Get a pointer to the EspnowMeshBackend instance that sent a request with the given requestID to the specified mac address. * - * @returns A valid EspnowMeshBackend pointer if a matching entry is found in the EspnowMeshBackend sentRequests container. nullptr otherwise. + * @return A valid EspnowMeshBackend pointer if a matching entry is found in the EspnowMeshBackend sentRequests container. nullptr otherwise. */ static EspnowMeshBackend *getOwnerOfSentRequest(uint64_t requestMac, uint64_t requestID); /** * Delete all entries in the sentRequests container where requestMac is noted as having received requestID. * - * @returns The number of entries deleted. + * @return The number of entries deleted. */ static size_t deleteSentRequest(uint64_t requestMac, uint64_t requestID); @@ -731,7 +770,7 @@ class EspnowMeshBackend : public MeshBackendBase { * @param encryptionRequestBuilder A function which is responsible for constructing the request message to send. * Called twice when the request is successful. First to build the initial request message and then to build the connection verification message. * The request message should typically be of the form: JsonTranslator::createEncryptionRequestIntro() + JsonTranslator::createEncryptionRequestEnding(). - * @returns The ultimate status of the requested encrypted connection, as encrypted_connection_status_t. + * @return The ultimate status of the requested encrypted connection, as encrypted_connection_status_t. */ encrypted_connection_status_t requestEncryptedConnectionKernel(uint8_t *peerMac, const encryptionRequestBuilderType &encryptionRequestBuilder); @@ -739,7 +778,7 @@ class EspnowMeshBackend : public MeshBackendBase { * Generate a new message ID to be used when making a data transmission. The generated ID will be different depending on whether an encrypted connection exists or not. * * @param encryptedConnection A pointer to the EncryptedConnectionLog of the encrypted connection. Can be set to nullptr if the connection is unecrypted. - * @returns The generated message ID. + * @return The generated message ID. */ static uint64_t generateMessageID(EncryptedConnectionLog *encryptedConnection); @@ -748,7 +787,7 @@ class EspnowMeshBackend : public MeshBackendBase { * Should only be used when initializing a new connection. * Use generateMessageID instead when the encrypted connection is already initialized to keep the connection synchronized. * - * @returns A uint64_t containing a new session key for an encrypted connection. + * @return A uint64_t containing a new session key for an encrypted connection. */ static uint64_t createSessionKey(); diff --git a/libraries/ESP8266WiFiMesh/src/EspnowProtocolInterpreter.h b/libraries/ESP8266WiFiMesh/src/EspnowProtocolInterpreter.h index 45fa46b557..5ed6acdead 100644 --- a/libraries/ESP8266WiFiMesh/src/EspnowProtocolInterpreter.h +++ b/libraries/ESP8266WiFiMesh/src/EspnowProtocolInterpreter.h @@ -52,7 +52,7 @@ namespace EspnowProtocolInterpreter const uint8_t espnowTransmissionsRemainingIndex = 1; const uint8_t espnowTransmissionMacIndex = 2; const uint8_t espnowMessageIDIndex = 8; - + uint8_t espnowProtocolBytesSize(); const uint8_t espnowEncryptionKeyLength = 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. diff --git a/libraries/ESP8266WiFiMesh/src/JsonTranslator.h b/libraries/ESP8266WiFiMesh/src/JsonTranslator.h index e03b468b03..7e75ab9f1c 100644 --- a/libraries/ESP8266WiFiMesh/src/JsonTranslator.h +++ b/libraries/ESP8266WiFiMesh/src/JsonTranslator.h @@ -62,7 +62,7 @@ namespace JsonTranslator * @param valueIdentifier The identifier to search for. * @param searchStartIndex Optional argument that makes it possible to decide at which index of jsonString the search starts. Search will begin at index 0 if not provided. * - * @returns An int32_t containing the index within jsonString where the value of valueIdentifier starts, or a negative value if valueIdentifier was not found. + * @return An int32_t containing the index within jsonString where the value of valueIdentifier starts, or a negative value if valueIdentifier was not found. */ int32_t getStartIndex(const String &jsonString, const String &valueIdentifier, int32_t searchStartIndex = 0); @@ -72,7 +72,7 @@ namespace JsonTranslator * @param jsonString The String to search within. * @param searchStartIndex The index of jsonString where the search will start. * - * @returns An int32_t containing the index within jsonString where the next JSON termination character is found, or a negative value if no such character was found. + * @return An int32_t containing the index within jsonString where the next JSON termination character is found, or a negative value if no such character was found. */ int32_t getEndIndex(const String &jsonString, int32_t searchStartIndex); @@ -83,7 +83,7 @@ namespace JsonTranslator * @param jsonString The String to search within. * @param result The String where the value should be stored. * - * @returns True if a value was found. False otherwise. + * @return True if a value was found. False otherwise. */ bool getPassword(const String &jsonString, String &result); bool getOwnSessionKey(const String &jsonString, uint64_t &result); @@ -96,7 +96,7 @@ namespace JsonTranslator * @param jsonString The String to search within. * @param resultArray The uint8_t array where the value should be stored. Must be at least 6 bytes. * - * @returns True if a value was found. False otherwise. + * @return True if a value was found. False otherwise. */ bool getPeerStaMac(const String &jsonString, uint8_t *resultArray); bool getPeerApMac(const String &jsonString, uint8_t *resultArray); diff --git a/libraries/ESP8266WiFiMesh/src/MeshBackendBase.cpp b/libraries/ESP8266WiFiMesh/src/MeshBackendBase.cpp index f9454ddbd8..d4212743a8 100644 --- a/libraries/ESP8266WiFiMesh/src/MeshBackendBase.cpp +++ b/libraries/ESP8266WiFiMesh/src/MeshBackendBase.cpp @@ -134,6 +134,8 @@ void MeshBackendBase::setSSID(const String &newSSIDPrefix, const String &newSSID String newSSID = _SSIDPrefix + _SSIDRoot + _SSIDSuffix; + assert(newSSID.length() <= 31); + if(getSSID() != newSSID) { _SSID = newSSID; diff --git a/libraries/ESP8266WiFiMesh/src/MeshBackendBase.h b/libraries/ESP8266WiFiMesh/src/MeshBackendBase.h index cfaae01e80..3933098223 100644 --- a/libraries/ESP8266WiFiMesh/src/MeshBackendBase.h +++ b/libraries/ESP8266WiFiMesh/src/MeshBackendBase.h @@ -61,7 +61,7 @@ class MeshBackendBase { static std::vector latestTransmissionOutcomes; /** - * @returns True if latest transmission was successful (i.e. latestTransmissionOutcomes is not empty and all entries have transmissionStatus TS_TRANSMISSION_COMPLETE). False otherwise. + * @return True if latest transmission was successful (i.e. latestTransmissionOutcomes is not empty and all entries have transmissionStatus TS_TRANSMISSION_COMPLETE). False otherwise. */ static bool latestTransmissionSuccessful(); @@ -84,7 +84,7 @@ class MeshBackendBase { * If another instance takes control over the AP after the pointer is created, * the created pointer will still point to the old AP instance. * - * @returns A pointer to the MeshBackendBase instance currently in control of the ESP8266 AP, + * @return A pointer to the MeshBackendBase instance currently in control of the ESP8266 AP, * or nullptr if there is no active AP controller. */ static MeshBackendBase *getAPController(); @@ -92,13 +92,14 @@ class MeshBackendBase { /** * Check if this MeshBackendBase instance is in control of the ESP8266 AP. * - * @returns True if this MeshBackendBase instance is in control of the ESP8266 AP. False otherwise. + * @return True if this MeshBackendBase instance is in control of the ESP8266 AP. False otherwise. */ bool isAPController(); /** * Change the WiFi channel used by this MeshBackendBase instance. - * Will also change the WiFi channel for the active AP if this MeshBackendBase instance is the current AP controller and it is possible to change channel. + * Will also change the WiFi channel for the active AP (via an AP restart) + * if this MeshBackendBase instance is the current AP controller and it is possible to change channel. * * WARNING: The ESP8266 has only one WiFi channel, and the the station/client mode is always prioritized for channel selection. * This can cause problems if several MeshBackendBase instances exist on the same ESP8266 and use different WiFi channels. @@ -114,7 +115,9 @@ class MeshBackendBase { /** * Change the SSID used by this MeshBackendBase instance. - * Will also change the SSID for the active AP if this MeshBackendBase instance is the current AP controller. + * The SSID can be at most 31 characters long. + * Will also change the SSID for the active AP (via an AP restart) + * if this MeshBackendBase instance is the current AP controller. * * @param newSSIDPrefix The first part of the new SSID. * @param newSSIDRoot The middle part of the new SSID. @@ -126,7 +129,8 @@ class MeshBackendBase { /** * Change the first part of the SSID used by this MeshBackendBase instance. - * Will also change the first part of the SSID for the active AP if this MeshBackendBase instance is the current AP controller. + * Will also change the first part of the SSID for the active AP (via an AP restart) + * if this MeshBackendBase instance is the current AP controller. * * @param newSSIDPrefix The new first part of the SSID. */ @@ -135,7 +139,8 @@ class MeshBackendBase { /** * Change the middle part of the SSID used by this MeshBackendBase instance. - * Will also change the middle part of the SSID for the active AP if this MeshBackendBase instance is the current AP controller. + * Will also change the middle part of the SSID for the active AP (via an AP restart) + * if this MeshBackendBase instance is the current AP controller. * * @param newSSIDPrefix The new middle part of the SSID. */ @@ -144,7 +149,8 @@ class MeshBackendBase { /** * Change the last part of the SSID used by this MeshBackendBase instance. - * Will also change the last part of the SSID for the active AP if this MeshBackendBase instance is the current AP controller. + * Will also change the last part of the SSID for the active AP (via an AP restart) + * if this MeshBackendBase instance is the current AP controller. * * @param newSSIDSuffix The new last part of the SSID. */ @@ -153,7 +159,9 @@ class MeshBackendBase { /** * Change the mesh name used by this MeshBackendBase instance. - * Will also change the mesh name for the active AP if this MeshBackendBase instance is the current AP controller. + * Will also change the mesh name for the active AP (via an AP restart) + * if this MeshBackendBase instance is the current AP controller. + * * Used as alias for setSSIDPrefix by default. Feel free to override this method in a subclass if your mesh name is not equal to SSIDPrefix. * * @param newMeshName The mesh name to change to. @@ -163,7 +171,9 @@ class MeshBackendBase { /** * Change the node id used by this MeshBackendBase instance. - * Will also change the node id for the active AP if this MeshBackendBase instance is the current AP controller. + * Will also change the node id for the active AP (via an AP restart) + * if this MeshBackendBase instance is the current AP controller. + * * Used as alias for setSSIDSuffix by default. Feel free to override this method in a subclass if your node id is not equal to SSIDSuffix. * * @param newNodeID The node id to change to. @@ -173,7 +183,8 @@ class MeshBackendBase { /** * Set the password used when connecting to other AP:s and when other nodes connect to the AP of this node. - * Will also change the setting for the active AP if this MeshBackendBase instance is the current AP controller. + * Will also change the setting for the active AP (via an AP restart) + * if this MeshBackendBase instance is the current AP controller. * * @param newMeshPassword The password to use. */ @@ -213,7 +224,8 @@ class MeshBackendBase { /** * Set whether the AP controlled by this MeshBackendBase instance will have a WiFi network with hidden SSID. * This is false by default. - * Will also change the setting for the active AP if this MeshBackendBase instance is the current AP controller. + * Will also change the setting for the active AP (via an AP restart) + * if this MeshBackendBase instance is the current AP controller. * * @param apHidden If true, the WiFi network created will have a hidden SSID. */ diff --git a/libraries/ESP8266WiFiMesh/src/MessageData.cpp b/libraries/ESP8266WiFiMesh/src/MessageData.cpp index f0070c2f5f..89508bd43d 100644 --- a/libraries/ESP8266WiFiMesh/src/MessageData.cpp +++ b/libraries/ESP8266WiFiMesh/src/MessageData.cpp @@ -27,7 +27,16 @@ #include "EspnowMeshBackend.h" #include -MessageData::MessageData(uint8_t *initialTransmission, uint8_t transmissionLength,uint32_t creationTimeMs) : +MessageData::MessageData(String &message, uint8_t transmissionsRemaining, uint32_t creationTimeMs) : + TimeTracker(creationTimeMs) +{ + _transmissionsExpected = transmissionsRemaining + 1; + assert(message.length() <= EspnowMeshBackend::getMaxMessageBytesPerTransmission()); // Should catch some cases where transmission is not null terminated. + _totalMessage += message; + _transmissionsReceived++; +} + +MessageData::MessageData(uint8_t *initialTransmission, uint8_t transmissionLength, uint32_t creationTimeMs) : TimeTracker(creationTimeMs) { _transmissionsExpected = EspnowProtocolInterpreter::espnowGetTransmissionsRemaining(initialTransmission) + 1; diff --git a/libraries/ESP8266WiFiMesh/src/MessageData.h b/libraries/ESP8266WiFiMesh/src/MessageData.h index 2d9ad9da78..71e291fb1f 100644 --- a/libraries/ESP8266WiFiMesh/src/MessageData.h +++ b/libraries/ESP8266WiFiMesh/src/MessageData.h @@ -32,6 +32,7 @@ class MessageData : public TimeTracker { public: + MessageData(String &message, uint8_t transmissionsRemaining, uint32_t creationTimeMs = millis()); MessageData(uint8_t *initialTransmission, uint8_t transmissionLength, uint32_t creationTimeMs = millis()); /** * @transmission A string of characters, including initial protocol bytes. diff --git a/libraries/ESP8266WiFiMesh/src/MutexTracker.h b/libraries/ESP8266WiFiMesh/src/MutexTracker.h index b7144cb27c..a3922dc63b 100644 --- a/libraries/ESP8266WiFiMesh/src/MutexTracker.h +++ b/libraries/ESP8266WiFiMesh/src/MutexTracker.h @@ -63,7 +63,7 @@ class MutexTracker /** * Attempt to capture the mutex. * - * @returns True if mutex was caught (meaning no other instance is holding the mutex). False otherwise. + * @return True if mutex was caught (meaning no other instance is holding the mutex). False otherwise. */ bool attemptMutexCapture(bool &mutexToCapture); }; diff --git a/libraries/ESP8266WiFiMesh/src/TcpIpMeshBackend.cpp b/libraries/ESP8266WiFiMesh/src/TcpIpMeshBackend.cpp index b00f8744a4..4f73877e74 100644 --- a/libraries/ESP8266WiFiMesh/src/TcpIpMeshBackend.cpp +++ b/libraries/ESP8266WiFiMesh/src/TcpIpMeshBackend.cpp @@ -166,7 +166,7 @@ void TcpIpMeshBackend::fullStop(WiFiClient &currClient) /** * Wait for a WiFiClient to transmit * - * @returns: True if the client is ready, false otherwise. + * @return True if the client is ready, false otherwise. * */ bool TcpIpMeshBackend::waitForClientTransmission(WiFiClient &currClient, uint32_t maxWait) @@ -194,7 +194,7 @@ bool TcpIpMeshBackend::waitForClientTransmission(WiFiClient &currClient, uint32_ * and pass that to the user-supplied responseHandler. * * @param currClient The client to which the message should be transmitted. - * @returns: A status code based on the outcome of the exchange. + * @return A status code based on the outcome of the exchange. * */ transmission_status_t TcpIpMeshBackend::exchangeInfo(WiFiClient &currClient) @@ -227,7 +227,7 @@ transmission_status_t TcpIpMeshBackend::exchangeInfo(WiFiClient &currClient) /** * Handle data transfer process with a connected AP. * - * @returns: A status code based on the outcome of the data transfer attempt. + * @return A status code based on the outcome of the data transfer attempt. */ transmission_status_t TcpIpMeshBackend::attemptDataTransfer() { @@ -247,7 +247,7 @@ transmission_status_t TcpIpMeshBackend::attemptDataTransfer() /** * Helper function that contains the core functionality for the data transfer process with a connected AP. * - * @returns: A status code based on the outcome of the data transfer attempt. + * @return A status code based on the outcome of the data transfer attempt. */ transmission_status_t TcpIpMeshBackend::attemptDataTransferKernel() { @@ -291,7 +291,7 @@ void TcpIpMeshBackend::initiateConnectionToAP(const String &targetSSID, int targ * @param targetSSID The name of the AP the other node has set up. * @param targetChannel The WiFI channel of the AP the other node has set up. * @param targetBSSID The MAC address of the AP the other node has set up. - * @returns: A status code based on the outcome of the connection and data transfer process. + * @return A status code based on the outcome of the connection and data transfer process. * */ transmission_status_t TcpIpMeshBackend::connectToNode(const String &targetSSID, int targetChannel, uint8_t *targetBSSID) diff --git a/libraries/ESP8266WiFiMesh/src/TcpIpMeshBackend.h b/libraries/ESP8266WiFiMesh/src/TcpIpMeshBackend.h index 86dc7ce03d..0c9f809666 100644 --- a/libraries/ESP8266WiFiMesh/src/TcpIpMeshBackend.h +++ b/libraries/ESP8266WiFiMesh/src/TcpIpMeshBackend.h @@ -110,7 +110,8 @@ class TcpIpMeshBackend : public MeshBackendBase { * If multiple APs exist on a single ESP8266, each requires a separate server port. * If two AP:s on the same ESP8266 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. - * Will also change the setting for the active AP if this TcpIpMeshBackend instance is the current AP controller. + * Will also change the setting for the active AP (via an AP restart) + * if this TcpIpMeshBackend instance is the current AP controller. * * @param serverPort The server port to use. * @@ -123,7 +124,8 @@ class TcpIpMeshBackend : public MeshBackendBase { * This number is 4 by default. * Once the max number has been reached, any other station that wants to connect will be forced to wait until an already connected station disconnects. * The more stations that are connected, the more memory is required. - * Will also change the setting for the active AP if this TcpIpMeshBackend instance is the current AP controller. + * Will also change the setting for the active AP (via an AP restart) + * if this TcpIpMeshBackend instance is the current AP controller. * * @param maxAPStations The maximum number of simultaneous station connections allowed. Valid values are 0 to 8. */ @@ -153,7 +155,8 @@ class TcpIpMeshBackend : public MeshBackendBase { * Set the timeout to use for transmissions when this TcpIpMeshBackend instance acts as an AP (i.e. when receiving connections from other stations). * This will affect the timeout of the acceptRequest method. * The timeout is 4 500 ms by default. - * Will also change the setting for the active AP if this TcpIpMeshBackend instance is the current AP controller. + * Will also change the setting for the active AP (without an AP restart) + * if this TcpIpMeshBackend instance is the current AP controller. * * @param apModeTimeoutMs The timeout to use, in milliseconds. */ @@ -182,7 +185,7 @@ class TcpIpMeshBackend : public MeshBackendBase { /** * Check if there is an ongoing TCP/IP transmission in the library. Used to avoid interrupting transmissions. * - * @returns True if a transmission initiated by a public method is in progress. + * @return True if a transmission initiated by a public method is in progress. */ static bool transmissionInProgress(); diff --git a/libraries/ESP8266WiFiMesh/src/TypeConversionFunctions.h b/libraries/ESP8266WiFiMesh/src/TypeConversionFunctions.h index a6fa114653..1e59dcc770 100644 --- a/libraries/ESP8266WiFiMesh/src/TypeConversionFunctions.h +++ b/libraries/ESP8266WiFiMesh/src/TypeConversionFunctions.h @@ -37,7 +37,7 @@ * * @param number The number to convert to a string with radix "base". * @param base The radix to convert "number" into. Must be between 2 and 36. - * @returns 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); @@ -46,7 +46,7 @@ String uint64ToString(uint64_t number, byte base = 16); * * @param string The string to convert to uint64_t. String must use radix "base". * @param base The radix of "string". Must be between 2 and 36. - * @returns 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); @@ -60,7 +60,7 @@ uint8_t *hexStringToUint8Array(const String &hexString, uint8_t *uint8Array, uin * 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. - * @returns A hexadecimal string representation of the mac. + * @return A hexadecimal string representation of the mac. */ String macToString(const uint8_t *mac); @@ -69,7 +69,7 @@ String macToString(const uint8_t *mac); * * @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. - * @returns The macArray. + * @return The macArray. */ uint8_t *stringToMac(const String &macString, uint8_t *macArray); @@ -77,7 +77,7 @@ uint8_t *stringToMac(const String &macString, uint8_t *macArray); * Takes a uint8_t array and converts the first 6 bytes to a uint64_t. Assumes index 0 of the array contains MSB. * * @param macArray A uint8_t array with the mac address to convert to a uint64_t. Should be 6 bytes in total. - * @returns A uint64_t representation of the mac. + * @return A uint64_t representation of the mac. */ uint64_t macToUint64(const uint8_t *macArray); @@ -86,7 +86,7 @@ uint64_t macToUint64(const uint8_t *macArray); * * @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. - * @returns The macArray. + * @return The macArray. */ uint8_t *uint64ToMac(uint64_t macValue, uint8_t *macArray); @@ -95,7 +95,7 @@ uint8_t *uint64ToMac(uint64_t macValue, uint8_t *macArray); * * @param T The MeshBackend class pointer type to cast the meshBackendBaseInstance pointer into. * @param meshBackendBaseInstance The instance pointer to cast. - * @returns 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 T meshBackendCast(MeshBackendBase *meshBackendBaseInstance) From 86025c788438427f9195b56f51ce40a8304de6d2 Mon Sep 17 00:00:00 2001 From: Anders Date: Wed, 18 Sep 2019 22:26:37 +0200 Subject: [PATCH 03/30] - Make each mesh backend use a unique NetworkInfo class and separate connectionQueue and latestTransmissionOutcomes vectors. - Deprecate NetworkInfo and TransmissionResult classes. - Add single recipient transmission methods. - Add a getCurrentMessage method to TcpIpMeshBackend to maintain feature parity when using single recipient transmission methods. - Increase code abstraction level in transmission methods. - Remove use of networkIndex except for in constructors, since it can change after each scan. - Make Espnow backend require at least BSSID to connect, and the TcpIp backend require at least SSID. - Make printAPInfo method take NetworkInfo as argument. - Add new TransmissionOutcome class to replace obsolete TransmissionResult. - Add _scanMutex. - Improve code abstraction in HelloEspnow.ino. - Update HelloEspnow.ino example to demonstrate the new features. - Update and improve comments. --- .../examples/HelloEspnow/HelloEspnow.ino | 73 +++-- .../ESP8266WiFiMesh/src/EspnowMeshBackend.cpp | 276 ++++++++++-------- .../ESP8266WiFiMesh/src/EspnowMeshBackend.h | 45 ++- .../ESP8266WiFiMesh/src/EspnowNetworkInfo.cpp | 32 ++ .../ESP8266WiFiMesh/src/EspnowNetworkInfo.h | 43 +++ .../ESP8266WiFiMesh/src/MeshBackendBase.cpp | 37 ++- .../ESP8266WiFiMesh/src/MeshBackendBase.h | 22 +- libraries/ESP8266WiFiMesh/src/NetworkInfo.cpp | 24 +- libraries/ESP8266WiFiMesh/src/NetworkInfo.h | 26 +- .../ESP8266WiFiMesh/src/NetworkInfoBase.cpp | 119 ++++++++ .../ESP8266WiFiMesh/src/NetworkInfoBase.h | 101 +++++++ .../ESP8266WiFiMesh/src/TcpIpMeshBackend.cpp | 145 ++++++--- .../ESP8266WiFiMesh/src/TcpIpMeshBackend.h | 50 +++- .../ESP8266WiFiMesh/src/TcpIpNetworkInfo.cpp | 31 ++ .../ESP8266WiFiMesh/src/TcpIpNetworkInfo.h | 46 +++ .../src/TransmissionOutcome.cpp | 36 +++ .../ESP8266WiFiMesh/src/TransmissionOutcome.h | 54 ++++ .../src/TransmissionResult.cpp | 23 ++ .../ESP8266WiFiMesh/src/TransmissionResult.h | 32 +- 19 files changed, 979 insertions(+), 236 deletions(-) create mode 100644 libraries/ESP8266WiFiMesh/src/EspnowNetworkInfo.cpp create mode 100644 libraries/ESP8266WiFiMesh/src/EspnowNetworkInfo.h create mode 100644 libraries/ESP8266WiFiMesh/src/NetworkInfoBase.cpp create mode 100644 libraries/ESP8266WiFiMesh/src/NetworkInfoBase.h create mode 100644 libraries/ESP8266WiFiMesh/src/TcpIpNetworkInfo.cpp create mode 100644 libraries/ESP8266WiFiMesh/src/TcpIpNetworkInfo.h create mode 100644 libraries/ESP8266WiFiMesh/src/TransmissionOutcome.cpp create mode 100644 libraries/ESP8266WiFiMesh/src/TransmissionOutcome.h diff --git a/libraries/ESP8266WiFiMesh/examples/HelloEspnow/HelloEspnow.ino b/libraries/ESP8266WiFiMesh/examples/HelloEspnow/HelloEspnow.ino index d698f6a9e8..d28e2c7079 100644 --- a/libraries/ESP8266WiFiMesh/examples/HelloEspnow/HelloEspnow.ino +++ b/libraries/ESP8266WiFiMesh/examples/HelloEspnow/HelloEspnow.ino @@ -14,7 +14,7 @@ https://github.com/esp8266/Arduino/issues/1143 https://arduino-esp8266.readthedocs.io/en/latest/PROGMEM.html */ -const char exampleMeshName[] PROGMEM = "MeshNode_"; // The name of the mesh network. Used as prefix for the node SSID and to find other network nodes during ESP-NOW broadcasts and in the example networkFilter function below. +const char exampleMeshName[] PROGMEM = "MeshNode_"; // The name of the mesh network. Used as prefix for the node SSID and to find other network nodes in the example networkFilter and broadcastFilter functions below. const char exampleWiFiPassword[] PROGMEM = "ChangeThisWiFiPassword_TODO"; // The password has to be min 8 and max 64 characters long, otherwise an AP which uses it will not be found during scans. // 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. @@ -32,6 +32,8 @@ uint8_t espnowHashKey[16] = {0xEF, 0x44, 0x33, 0x0C, 0x33, 0x44, 0xFE, 0x44, // unsigned int requestNumber = 0; unsigned int responseNumber = 0; +const char broadcastMetadataDelimiter = 23; // 23 = End-of-Transmission-Block (ETB) control character in ASCII + String manageRequest(const String &request, MeshBackendBase &meshInstance); transmission_status_t manageResponse(const String &response, MeshBackendBase &meshInstance); void networkFilter(int numberOfNetworks, MeshBackendBase &meshInstance); @@ -96,7 +98,7 @@ transmission_status_t manageResponse(const String &response, MeshBackendBase &me // With ESP-NOW there is no guarantee when or if a response will show up, it can happen before or after the stored message is changed. // So for ESP-NOW, adding unique identifiers in the response and request is required to associate a response with a request. Serial.print(F("Request sent: ")); - Serial.println(tcpIpInstance->getMessage().substring(0, 100)); + Serial.println(tcpIpInstance->getCurrentMessage().substring(0, 100)); } else { Serial.print("UNKNOWN!: "); } @@ -127,14 +129,18 @@ void networkFilter(int numberOfNetworks, MeshBackendBase &meshInstance) { uint64_t targetNodeID = stringToUint64(currentSSID.substring(meshNameIndex + meshInstance.getMeshName().length())); if (targetNodeID < stringToUint64(meshInstance.getNodeID())) { - MeshBackendBase::connectionQueue.push_back(NetworkInfo(networkIndex)); + if (EspnowMeshBackend *espnowInstance = meshBackendCast(&meshInstance)) { + espnowInstance->connectionQueue().push_back(networkIndex); + } else if (TcpIpMeshBackend *tcpIpInstance = meshBackendCast(&meshInstance)) { + tcpIpInstance->connectionQueue().push_back(networkIndex); + } else { + Serial.println(String(F("Invalid mesh backend!"))); + } } } } } -const char broadcastMetadataDelimiter = 23; // 23 = End-of-Transmission-Block (ETB) control character in ASCII - /** Callback used to decide which broadcast messages to accept. Only called for the first transmission in each broadcast. If true is returned from this callback, the first broadcast transmission is saved until the entire broadcast message has been received. @@ -147,10 +153,8 @@ const char broadcastMetadataDelimiter = 23; // 23 = End-of-Transmission-Block (E @return True if the broadcast should be accepted. False otherwise. */ bool broadcastFilter(String &firstTransmission, EspnowMeshBackend &meshInstance) { - /** - This example broadcastFilter will accept a transmission if it contains the broadcastMetadataDelimiter - and as metaData either no targetMeshName or a targetMeshName that matches the MeshName of meshInstance. - */ + // This example broadcastFilter will accept a transmission if it contains the broadcastMetadataDelimiter + // and as metaData either no targetMeshName or a targetMeshName that matches the MeshName of meshInstance. int32_t metadataEndIndex = firstTransmission.indexOf(broadcastMetadataDelimiter); @@ -230,7 +234,7 @@ void loop() { uint32_t startTime = millis(); espnowNode.attemptTransmission(espnowNode.getMessage()); - Serial.println("Scan and " + String(MeshBackendBase::latestTransmissionOutcomes.size()) + " transmissions done in " + String(millis() - startTime) + " ms."); + Serial.println("Scan and " + String(espnowNode.latestTransmissionOutcomes().size()) + " transmissions done in " + String(millis() - startTime) + " ms."); timeOfLastScan = millis(); @@ -239,23 +243,23 @@ void loop() { espnowDelay(100); // One way to check how attemptTransmission worked out - if (MeshBackendBase::latestTransmissionSuccessful()) { + if (espnowNode.latestTransmissionSuccessful()) { Serial.println(F("Transmission successful.")); } // Another way to check how attemptTransmission worked out - if (MeshBackendBase::latestTransmissionOutcomes.empty()) { + if (espnowNode.latestTransmissionOutcomes().empty()) { Serial.println(F("No mesh AP found.")); } else { - for (TransmissionResult &transmissionResult : MeshBackendBase::latestTransmissionOutcomes) { - if (transmissionResult.transmissionStatus == TS_TRANSMISSION_FAILED) { - Serial.println(String(F("Transmission failed to mesh AP ")) + transmissionResult.SSID); - } else if (transmissionResult.transmissionStatus == TS_CONNECTION_FAILED) { - Serial.println(String(F("Connection failed to mesh AP ")) + transmissionResult.SSID); - } else if (transmissionResult.transmissionStatus == TS_TRANSMISSION_COMPLETE) { + for (TransmissionOutcome &transmissionOutcome : espnowNode.latestTransmissionOutcomes()) { + if (transmissionOutcome.transmissionStatus() == TS_TRANSMISSION_FAILED) { + Serial.println(String(F("Transmission failed to mesh AP ")) + transmissionOutcome.SSID()); + } else if (transmissionOutcome.transmissionStatus() == TS_CONNECTION_FAILED) { + Serial.println(String(F("Connection failed to mesh AP ")) + transmissionOutcome.SSID()); + } else if (transmissionOutcome.transmissionStatus() == TS_TRANSMISSION_COMPLETE) { // No need to do anything, transmission was successful. } else { - Serial.println(String(F("Invalid transmission status for ")) + transmissionResult.SSID + String(F("!"))); + Serial.println(String(F("Invalid transmission status for ")) + transmissionOutcome.SSID() + String(F("!"))); assert(F("Invalid transmission status returned from responseHandler!") && false); } } @@ -276,14 +280,16 @@ void loop() { Serial.println("\nPerforming encrypted ESP-NOW transmissions."); + uint8_t targetBSSID[6] {0}; + // We can create encrypted connections to individual nodes so that all ESP-NOW communication with the node will be encrypted. - if (espnowNode.requestEncryptedConnection(MeshBackendBase::connectionQueue[0].BSSID) == ECS_CONNECTION_ESTABLISHED) { + if (espnowNode.connectionQueue()[0].getBSSID(targetBSSID) && espnowNode.requestEncryptedConnection(targetBSSID) == ECS_CONNECTION_ESTABLISHED) { // The WiFi scan will detect the AP MAC, but this will automatically be converted to the encrypted STA MAC by the framework. - String peerMac = macToString(MeshBackendBase::connectionQueue[0].BSSID); + String peerMac = macToString(targetBSSID); Serial.println("Encrypted ESP-NOW connection with " + peerMac + " established!"); - // Making a transmission now will cause messages to MeshBackendBase::connectionQueue[0].BSSID to be encrypted. + // Making a transmission now will cause messages to targetBSSID to be encrypted. String espnowMessage = "This message is encrypted only when received by node " + peerMac; Serial.println("\nTransmitting: " + espnowMessage); espnowNode.attemptTransmission(espnowMessage, false); @@ -291,11 +297,11 @@ void loop() { // A connection can be serialized and stored for later use. // Note that this saves the current state only, so if encrypted communication between the nodes happen after this, the stored state is invalid. - String serializedEncryptedConnection = EspnowMeshBackend::serializeEncryptedConnection(MeshBackendBase::connectionQueue[0].BSSID); + String serializedEncryptedConnection = EspnowMeshBackend::serializeEncryptedConnection(targetBSSID); Serial.println(); // We can remove an encrypted connection like so. - espnowNode.removeEncryptedConnection(MeshBackendBase::connectionQueue[0].BSSID); + espnowNode.removeEncryptedConnection(targetBSSID); // Note that the peer will still be encrypted, so although we can send unencrypted messages to the peer, we cannot read the encrypted responses it sends back. espnowMessage = "This message is no longer encrypted when received by node " + peerMac; @@ -304,7 +310,7 @@ void loop() { espnowDelay(100); // Wait for response. Serial.println("Cannot read the encrypted response..."); - // Let's re-add our stored connection so we can communicate properly with MeshBackendBase::connectionQueue[0].BSSID again! + // Let's re-add our stored connection so we can communicate properly with targetBSSID again! espnowNode.addEncryptedConnection(serializedEncryptedConnection); espnowMessage = "This message is once again encrypted when received by node " + peerMac; @@ -314,21 +320,28 @@ void loop() { Serial.println(); // If we want to remove the encrypted connection on both nodes, we can do it like this. - encrypted_connection_removal_outcome_t removalOutcome = espnowNode.requestEncryptedConnectionRemoval(MeshBackendBase::connectionQueue[0].BSSID); + encrypted_connection_removal_outcome_t removalOutcome = espnowNode.requestEncryptedConnectionRemoval(targetBSSID); if (removalOutcome == ECRO_REMOVAL_SUCCEEDED) { - Serial.println(peerMac + " is no longer encrypted!\n"); + Serial.println(peerMac + " is no longer encrypted!"); + + espnowMessage = "This message is only received by node " + peerMac + ". Transmitting in this way will not change the transmission state of the sender."; + Serial.println("Transmitting: " + espnowMessage); + espnowNode.attemptTransmission(espnowMessage, EspnowNetworkInfo(targetBSSID)); + espnowDelay(100); // Wait for response. + + Serial.println(); // Of course, we can also just create a temporary encrypted connection that will remove itself once its duration has passed. - if (espnowNode.requestTemporaryEncryptedConnection(MeshBackendBase::connectionQueue[0].BSSID, 1000) == ECS_CONNECTION_ESTABLISHED) { + if (espnowNode.requestTemporaryEncryptedConnection(targetBSSID, 1000) == ECS_CONNECTION_ESTABLISHED) { espnowDelay(42); uint32_t remainingDuration = 0; - EspnowMeshBackend::getConnectionInfo(MeshBackendBase::connectionQueue[0].BSSID, &remainingDuration); + EspnowMeshBackend::getConnectionInfo(targetBSSID, &remainingDuration); espnowMessage = "Messages this node sends to " + peerMac + " will be encrypted for " + String(remainingDuration) + " ms more."; Serial.println("\nTransmitting: " + espnowMessage); espnowNode.attemptTransmission(espnowMessage, false); - EspnowMeshBackend::getConnectionInfo(MeshBackendBase::connectionQueue[0].BSSID, &remainingDuration); + EspnowMeshBackend::getConnectionInfo(targetBSSID, &remainingDuration); espnowDelay(remainingDuration + 100); espnowMessage = "Due to encrypted connection expiration, this message is no longer encrypted when received by node " + peerMac; diff --git a/libraries/ESP8266WiFiMesh/src/EspnowMeshBackend.cpp b/libraries/ESP8266WiFiMesh/src/EspnowMeshBackend.cpp index 6c249452d8..072bf7fb7a 100644 --- a/libraries/ESP8266WiFiMesh/src/EspnowMeshBackend.cpp +++ b/libraries/ESP8266WiFiMesh/src/EspnowMeshBackend.cpp @@ -50,6 +50,9 @@ std::list EspnowMeshBackend::peerRequestConfirmationsToSend = {} std::vector EspnowMeshBackend::encryptedConnections = {}; +std::vector EspnowMeshBackend::_connectionQueue = {}; +std::vector EspnowMeshBackend::_latestTransmissionOutcomes = {}; + uint32_t EspnowMeshBackend::_espnowTransmissionTimeoutMs = 40; uint32_t EspnowMeshBackend::_espnowRetransmissionIntervalMs = 15; @@ -133,6 +136,16 @@ void EspnowMeshBackend::begin() activateEspnow(); } +std::vector & EspnowMeshBackend::connectionQueue() +{ + return _connectionQueue; +} + +std::vector & EspnowMeshBackend::latestTransmissionOutcomes() +{ + return _latestTransmissionOutcomes; +} + void EspnowMeshBackend::performEspnowMaintainance() { // Doing this during an ESP-NOW transmission could invalidate iterators @@ -1671,7 +1684,7 @@ encrypted_connection_removal_outcome_t EspnowMeshBackend::removeEncryptedConnect } } -encrypted_connection_removal_outcome_t EspnowMeshBackend::removeEncryptedConnectionUnprotected(uint8_t *peerMac, std::vector::iterator *resultingIterator) +encrypted_connection_removal_outcome_t EspnowMeshBackend::removeEncryptedConnectionUnprotected(const uint8_t *peerMac, std::vector::iterator *resultingIterator) { connectionLogIterator connectionIterator = getEncryptedConnectionIterator(peerMac, encryptedConnections); return removeEncryptedConnectionUnprotected(connectionIterator, resultingIterator); @@ -1908,68 +1921,57 @@ uint8_t *EspnowMeshBackend::getEncryptedMac(const uint8_t *peerMac, uint8_t *res } } -void EspnowMeshBackend::attemptTransmission(const String &message, bool scan, bool scanAllWiFiChannels) +void EspnowMeshBackend::prepareForTransmission(const String &message, bool scan, bool scanAllWiFiChannels) { - MutexTracker mutexTracker(_espnowTransmissionMutex, handlePostponedRemovals); - if(!mutexTracker.mutexCaptured()) - { - assert(false && "ERROR! Transmission in progress. Don't call attemptTransmission from callbacks as this may corrupt program state! Aborting."); - return; - } - setMessage(message); - latestTransmissionOutcomes.clear(); + latestTransmissionOutcomes().clear(); if(scan) { + connectionQueue().clear(); scanForNetworks(scanAllWiFiChannels); } - - for(NetworkInfo ¤tNetwork : connectionQueue) +} + +transmission_status_t EspnowMeshBackend::initiateTransmission(const String &message, const EspnowNetworkInfo &recipientInfo) +{ + uint8_t targetBSSID[6] {0}; + + assert(recipientInfo.BSSID() != nullptr); // We need at least the BSSID to connect + recipientInfo.getBSSID(targetBSSID); + + if(verboseMode()) // Avoid string generation if not required { - String currentSSID = ""; - int currentWiFiChannel = NETWORK_INFO_DEFAULT_INT; - uint8_t *currentBSSID = NULL; + printAPInfo(recipientInfo); + verboseModePrint(F("")); + } - // If a BSSID has been assigned, it is prioritized over an assigned networkIndex since the networkIndex is more likely to change. - if(currentNetwork.BSSID != NULL) - { - currentSSID = currentNetwork.SSID; - currentWiFiChannel = currentNetwork.wifiChannel; - currentBSSID = currentNetwork.BSSID; - } - else // Use only networkIndex - { - currentSSID = WiFi.SSID(currentNetwork.networkIndex); - currentWiFiChannel = WiFi.channel(currentNetwork.networkIndex); - currentBSSID = WiFi.BSSID(currentNetwork.networkIndex); - } + return initiateTransmissionKernel(message, targetBSSID); +} - if(verboseMode()) // Avoid string generation if not required - { - printAPInfo(currentNetwork.networkIndex, currentSSID, currentWiFiChannel); - verboseModePrint(F("")); - } - - uint32_t transmissionStartTime = millis(); - transmission_status_t transmissionResult = sendRequest(getMessage(), currentBSSID); +transmission_status_t EspnowMeshBackend::initiateTransmissionKernel(const String &message, const uint8_t *targetBSSID) +{ + uint32_t transmissionStartTime = millis(); + transmission_status_t transmissionResult = sendRequest(message, targetBSSID); - uint32_t transmissionDuration = millis() - transmissionStartTime; - - if(verboseMode() && transmissionResult == TS_TRANSMISSION_COMPLETE) // Avoid calculations if not required + uint32_t transmissionDuration = millis() - transmissionStartTime; + + if(verboseMode() && transmissionResult == TS_TRANSMISSION_COMPLETE) // Avoid calculations if not required + { + totalDurationWhenSuccessful_AT += transmissionDuration; + successfulTransmissions_AT++; + if(transmissionDuration > maxTransmissionDuration_AT) { - totalDurationWhenSuccessful_AT += transmissionDuration; - successfulTransmissions_AT++; - if(transmissionDuration > maxTransmissionDuration_AT) - { - maxTransmissionDuration_AT = transmissionDuration; - } + maxTransmissionDuration_AT = transmissionDuration; } - - latestTransmissionOutcomes.push_back(TransmissionResult{.origin = currentNetwork, .transmissionStatus = transmissionResult}); } + return transmissionResult; +} + +void EspnowMeshBackend::printTransmissionStatistics() +{ if(verboseMode() && successfulTransmissions_AT > 0) // Avoid calculations if not required { verboseModePrint("Average duration of successful transmissions: " + String(totalDurationWhenSuccessful_AT/successfulTransmissions_AT) + " ms."); @@ -1981,116 +1983,136 @@ void EspnowMeshBackend::attemptTransmission(const String &message, bool scan, bo } } -void EspnowMeshBackend::attemptAutoEncryptingTransmission(const String &message, bool scan, bool scanAllWiFiChannels, bool createPermanentConnections) +void EspnowMeshBackend::attemptTransmission(const String &message, bool scan, bool scanAllWiFiChannels) { - MutexTracker outerMutexTracker(_espnowTransmissionMutex, handlePostponedRemovals); - if(!outerMutexTracker.mutexCaptured()) + MutexTracker mutexTracker(_espnowTransmissionMutex, handlePostponedRemovals); + if(!mutexTracker.mutexCaptured()) { assert(false && "ERROR! Transmission in progress. Don't call attemptTransmission from callbacks as this may corrupt program state! Aborting."); return; } - setMessage(message); + prepareForTransmission(message, scan, scanAllWiFiChannels); + + for(EspnowNetworkInfo ¤tNetwork : connectionQueue()) + { + transmission_status_t transmissionResult = initiateTransmission(getMessage(), currentNetwork); - latestTransmissionOutcomes.clear(); + latestTransmissionOutcomes().push_back(TransmissionOutcome{.origin = currentNetwork, .transmissionStatus = transmissionResult}); + } - if(scan) + printTransmissionStatistics(); +} + +transmission_status_t EspnowMeshBackend::attemptTransmission(const String &message, const EspnowNetworkInfo &recipientInfo) +{ + MutexTracker mutexTracker(_espnowTransmissionMutex, handlePostponedRemovals); + if(!mutexTracker.mutexCaptured()) { - scanForNetworks(scanAllWiFiChannels); + assert(false && "ERROR! Transmission in progress. Don't call attemptTransmission from callbacks as this may corrupt program state! Aborting."); + return TS_CONNECTION_FAILED; } - outerMutexTracker.releaseMutex(); - - for(NetworkInfo ¤tNetwork : connectionQueue) + return initiateTransmission(message, recipientInfo); +} + +encrypted_connection_status_t EspnowMeshBackend::initiateAutoEncryptingConnection(const EspnowNetworkInfo &recipientInfo, bool createPermanentConnection, uint8_t *targetBSSID, EncryptedConnectionLog **encryptedConnection) +{ + assert(recipientInfo.BSSID() != nullptr); // We need at least the BSSID to connect + recipientInfo.getBSSID(targetBSSID); + + if(verboseMode()) // Avoid string generation if not required { - MutexTracker innerMutexTracker = MutexTracker(_espnowTransmissionMutex); - if(!innerMutexTracker.mutexCaptured()) - { - assert(false && "ERROR! Unable to recapture Mutex in attemptAutoEncryptingTransmission. Aborting."); - return; - } - - String currentSSID = ""; - int currentWiFiChannel = NETWORK_INFO_DEFAULT_INT; - uint8_t *currentBSSID = NULL; + printAPInfo(recipientInfo); + verboseModePrint(F("")); + } - // If a BSSID has been assigned, it is prioritized over an assigned networkIndex since the networkIndex is more likely to change. - if(currentNetwork.BSSID != NULL) - { - currentSSID = currentNetwork.SSID; - currentWiFiChannel = currentNetwork.wifiChannel; - currentBSSID = currentNetwork.BSSID; - } - else // Use only networkIndex - { - currentSSID = WiFi.SSID(currentNetwork.networkIndex); - currentWiFiChannel = WiFi.channel(currentNetwork.networkIndex); - currentBSSID = WiFi.BSSID(currentNetwork.networkIndex); - } + *encryptedConnection = getEncryptedConnection(targetBSSID); + encrypted_connection_status_t connectionStatus = ECS_MAX_CONNECTIONS_REACHED_SELF; - if(verboseMode()) // Avoid string generation if not required - { - printAPInfo(currentNetwork.networkIndex, currentSSID, currentWiFiChannel); - verboseModePrint(F("")); - } + if(createPermanentConnection) + connectionStatus = requestEncryptedConnection(targetBSSID); + else + connectionStatus = requestFlexibleTemporaryEncryptedConnection(targetBSSID, getAutoEncryptionDuration()); - EncryptedConnectionLog *encryptedConnection = getEncryptedConnection(currentBSSID); - encrypted_connection_status_t connectionStatus = ECS_MAX_CONNECTIONS_REACHED_SELF; + return connectionStatus; +} - innerMutexTracker.releaseMutex(); +transmission_status_t EspnowMeshBackend::initiateAutoEncryptingTransmission(const String &message, const uint8_t *targetBSSID, encrypted_connection_status_t connectionStatus) +{ + transmission_status_t transmissionResult = TS_CONNECTION_FAILED; + + if(connectionStatus == ECS_CONNECTION_ESTABLISHED) + { + transmissionResult = initiateTransmissionKernel(message, targetBSSID); + } + + return transmissionResult; +} - if(createPermanentConnections) - connectionStatus = requestEncryptedConnection(currentBSSID); - else - connectionStatus = requestFlexibleTemporaryEncryptedConnection(currentBSSID, getAutoEncryptionDuration()); +void EspnowMeshBackend::finalizeAutoEncryptingConnection(const uint8_t *targetBSSID, const EncryptedConnectionLog *encryptedConnection, bool createPermanentConnection) +{ + if(!encryptedConnection && !createPermanentConnection) + { + // Remove any connection that was added during the transmission attempt. + removeEncryptedConnectionUnprotected(targetBSSID); + } +} - innerMutexTracker = MutexTracker(_espnowTransmissionMutex); +void EspnowMeshBackend::attemptAutoEncryptingTransmission(const String &message, bool scan, bool scanAllWiFiChannels, bool createPermanentConnections) +{ + MutexTracker outerMutexTracker(_espnowTransmissionMutex, handlePostponedRemovals); + if(!outerMutexTracker.mutexCaptured()) + { + assert(false && "ERROR! Transmission in progress. Don't call attemptTransmission from callbacks as this may corrupt program state! Aborting."); + return; + } + + prepareForTransmission(message, scan, scanAllWiFiChannels); + + outerMutexTracker.releaseMutex(); + + for(EspnowNetworkInfo ¤tNetwork : connectionQueue()) + { + uint8_t currentBSSID[6] {0}; + EncryptedConnectionLog *encryptedConnection = nullptr; + encrypted_connection_status_t connectionStatus = initiateAutoEncryptingConnection(currentNetwork, createPermanentConnections, currentBSSID, &encryptedConnection); + + MutexTracker innerMutexTracker = MutexTracker(_espnowTransmissionMutex); if(!innerMutexTracker.mutexCaptured()) { assert(false && "ERROR! Unable to recapture Mutex in attemptAutoEncryptingTransmission. Aborting."); return; } - if(connectionStatus == ECS_CONNECTION_ESTABLISHED) - { - uint32_t transmissionStartTime = millis(); - transmission_status_t transmissionResult = sendRequest(getMessage(), currentBSSID); - - uint32_t transmissionDuration = millis() - transmissionStartTime; + transmission_status_t transmissionResult = initiateAutoEncryptingTransmission(getMessage(), currentBSSID, connectionStatus); - if(verboseMode() && transmissionResult == TS_TRANSMISSION_COMPLETE) // Avoid calculations if not required - { - totalDurationWhenSuccessful_AT += transmissionDuration; - successfulTransmissions_AT++; - if(transmissionDuration > maxTransmissionDuration_AT) - { - maxTransmissionDuration_AT = transmissionDuration; - } - } - - latestTransmissionOutcomes.push_back(TransmissionResult{.origin = currentNetwork, .transmissionStatus = transmissionResult}); - } - else - { - latestTransmissionOutcomes.push_back(TransmissionResult{.origin = currentNetwork, .transmissionStatus = TS_CONNECTION_FAILED}); - } + latestTransmissionOutcomes().push_back(TransmissionOutcome{.origin = currentNetwork, .transmissionStatus = transmissionResult}); - if(!encryptedConnection && !createPermanentConnections) - { - // Remove any connection that was added during the transmission attempt. - removeEncryptedConnectionUnprotected(currentBSSID); - } + finalizeAutoEncryptingConnection(currentBSSID, encryptedConnection, createPermanentConnections); } - if(verboseMode() && successfulTransmissions_AT > 0) // Avoid calculations if not required - { - verboseModePrint("Average duration of successful transmissions: " + String(totalDurationWhenSuccessful_AT/successfulTransmissions_AT) + " ms."); - verboseModePrint("Maximum duration of successful transmissions: " + String(maxTransmissionDuration_AT) + " ms."); - } - else + printTransmissionStatistics(); +} + +transmission_status_t EspnowMeshBackend::attemptAutoEncryptingTransmission(const String &message, const EspnowNetworkInfo &recipientInfo, bool createPermanentConnection) +{ + uint8_t targetBSSID[6] {0}; + EncryptedConnectionLog *encryptedConnection = nullptr; + encrypted_connection_status_t connectionStatus = initiateAutoEncryptingConnection(recipientInfo, createPermanentConnection, targetBSSID, &encryptedConnection); + + MutexTracker mutexTracker(_espnowTransmissionMutex, handlePostponedRemovals); + if(!mutexTracker.mutexCaptured()) { - verboseModePrint("No successful transmission."); + assert(false && "ERROR! Transmission in progress. Don't call attemptTransmission from callbacks as this may corrupt program state! Aborting."); + return TS_CONNECTION_FAILED; } + + transmission_status_t transmissionResult = initiateAutoEncryptingTransmission(message, targetBSSID, connectionStatus); + + finalizeAutoEncryptingConnection(targetBSSID, encryptedConnection, createPermanentConnection); + + return transmissionResult; } void EspnowMeshBackend::broadcast(const String &message) diff --git a/libraries/ESP8266WiFiMesh/src/EspnowMeshBackend.h b/libraries/ESP8266WiFiMesh/src/EspnowMeshBackend.h index 6292a5eb35..1bfcc2524c 100644 --- a/libraries/ESP8266WiFiMesh/src/EspnowMeshBackend.h +++ b/libraries/ESP8266WiFiMesh/src/EspnowMeshBackend.h @@ -55,6 +55,7 @@ #include #include #include "Crypto.h" +#include "EspnowNetworkInfo.h" typedef enum { @@ -130,6 +131,24 @@ class EspnowMeshBackend : public MeshBackendBase { ~EspnowMeshBackend() override; + /** + * Returns a vector that contains the NetworkInfo for each WiFi network to connect to. + * This vector is unique for each mesh backend. + * The connectionQueue vector is cleared before each new scan and filled via the networkFilter callback function once the scan completes. + * WiFi connections will start with connectionQueue[0] and then incrementally proceed to higher vector positions. + * Note that old network indicies often are invalidated whenever a new WiFi network scan occurs. + */ + std::vector & connectionQueue(); + + /** + * Returns a vector with the TransmissionOutcome for each AP to which a transmission was attempted during the latest attemptTransmission call. + * This vector is unique for each mesh backend. + * The latestTransmissionOutcomes vector is cleared before each new transmission attempt. + * Connection attempts are indexed in the same order they were attempted. + * Note that old network indicies often are invalidated whenever a new WiFi network scan occurs. + */ + std::vector & latestTransmissionOutcomes() override; + /** * Initialises the node. */ @@ -177,6 +196,12 @@ class EspnowMeshBackend : public MeshBackendBase { static bool deactivateEspnow(); void attemptTransmission(const String &message, bool scan = true, bool scanAllWiFiChannels = false) override; + + /** + * Transmit message to a single recipient without changing the local transmission state. + * Will not change connectionQueue, latestTransmissionOutcomes or stored message. + */ + transmission_status_t attemptTransmission(const String &message, const EspnowNetworkInfo &recipientInfo); /* * Will ensure that an encrypted connection exists to each target node before sending the message, @@ -201,6 +226,12 @@ class EspnowMeshBackend : public MeshBackendBase { */ void attemptAutoEncryptingTransmission(const String &message, bool scan = true, bool scanAllWiFiChannels = false, bool createPermanentConnections = false); + /** + * Transmit message to a single recipient without changing the local transmission state (apart from encrypted connections). + * Will not change connectionQueue, latestTransmissionOutcomes or stored message. + */ + transmission_status_t attemptAutoEncryptingTransmission(const String &message, const EspnowNetworkInfo &recipientInfo, bool createPermanentConnection = false); + /** * Send a message simultaneously to all nearby nodes which have ESP-NOW activated. * A broadcast is always treated as a request by the receiving node. @@ -535,6 +566,9 @@ class EspnowMeshBackend : public MeshBackendBase { protected: + static std::vector _connectionQueue; + static std::vector _latestTransmissionOutcomes; + typedef std::vector::iterator connectionLogIterator; static connectionLogIterator connectionLogEndIterator(); @@ -564,7 +598,7 @@ class EspnowMeshBackend : public MeshBackendBase { // Consider using getScheduledResponseRecipient and similar methods for this preparation. // Should only be used when there is no transmissions in progress. In practice when _espnowTransmissionMutex is free. // @param resultingIterator Will be set to the iterator position after the removed element, if an element to remove was found. Otherwise no change will occur. - static encrypted_connection_removal_outcome_t removeEncryptedConnectionUnprotected(uint8_t *peerMac, std::vector::iterator *resultingIterator = nullptr); + static encrypted_connection_removal_outcome_t removeEncryptedConnectionUnprotected(const uint8_t *peerMac, std::vector::iterator *resultingIterator = nullptr); static encrypted_connection_removal_outcome_t removeEncryptedConnectionUnprotected(connectionLogIterator &connectionIterator, std::vector::iterator *resultingIterator); /** @@ -791,6 +825,15 @@ class EspnowMeshBackend : public MeshBackendBase { */ static uint64_t createSessionKey(); + void prepareForTransmission(const String &message, bool scan, bool scanAllWiFiChannels); + transmission_status_t initiateTransmission(const String &message, const EspnowNetworkInfo &recipientInfo); + transmission_status_t initiateTransmissionKernel(const String &message, const uint8_t *targetBSSID); + void printTransmissionStatistics(); + + encrypted_connection_status_t initiateAutoEncryptingConnection(const EspnowNetworkInfo &recipientInfo, bool createPermanentConnection, uint8_t *targetBSSID, EncryptedConnectionLog **encryptedConnection); + transmission_status_t initiateAutoEncryptingTransmission(const String &message, const uint8_t *targetBSSID, encrypted_connection_status_t connectionStatus); + void finalizeAutoEncryptingConnection(const uint8_t *targetBSSID, const EncryptedConnectionLog *encryptedConnection, bool createPermanentConnection); + // Used for verboseMode printing in attemptTransmission, _AT suffix used to reduce namespace clutter uint32_t totalDurationWhenSuccessful_AT = 0; uint32_t successfulTransmissions_AT = 0; diff --git a/libraries/ESP8266WiFiMesh/src/EspnowNetworkInfo.cpp b/libraries/ESP8266WiFiMesh/src/EspnowNetworkInfo.cpp new file mode 100644 index 0000000000..a8940a5d20 --- /dev/null +++ b/libraries/ESP8266WiFiMesh/src/EspnowNetworkInfo.cpp @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2019 Anders Löfgren + * + * License (MIT license): + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include "EspnowNetworkInfo.h" + +EspnowNetworkInfo::EspnowNetworkInfo(int networkIndex) : NetworkInfoBase(networkIndex) { }; + +EspnowNetworkInfo::EspnowNetworkInfo(const uint8_t BSSID[6], const String &SSID, int32_t wifiChannel, uint8_t encryptionType, int32_t RSSI , bool isHidden) + : NetworkInfoBase(SSID, wifiChannel, BSSID, encryptionType, RSSI, isHidden) +{ } + diff --git a/libraries/ESP8266WiFiMesh/src/EspnowNetworkInfo.h b/libraries/ESP8266WiFiMesh/src/EspnowNetworkInfo.h new file mode 100644 index 0000000000..947d1028e5 --- /dev/null +++ b/libraries/ESP8266WiFiMesh/src/EspnowNetworkInfo.h @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2019 Anders Löfgren + * + * License (MIT license): + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#ifndef __ESPNOWNETWORKINFO_H__ +#define __ESPNOWNETWORKINFO_H__ + +#include "NetworkInfoBase.h" + +class EspnowNetworkInfo : public NetworkInfoBase { + +public: + + /** + * Automatically fill in the rest of the network info using networkIndex and the WiFi scan results. + */ + EspnowNetworkInfo(int networkIndex); + + EspnowNetworkInfo(const uint8_t BSSID[6], const String &SSID = defaultSSID, int32_t wifiChannel = defaultWifiChannel, uint8_t encryptionType = defaultEncryptionType, + int32_t RSSI = defaultRSSI, bool isHidden = defaultIsHidden); +}; + +#endif diff --git a/libraries/ESP8266WiFiMesh/src/MeshBackendBase.cpp b/libraries/ESP8266WiFiMesh/src/MeshBackendBase.cpp index d4212743a8..69232e5066 100644 --- a/libraries/ESP8266WiFiMesh/src/MeshBackendBase.cpp +++ b/libraries/ESP8266WiFiMesh/src/MeshBackendBase.cpp @@ -17,13 +17,14 @@ */ #include "MeshBackendBase.h" +#include "TypeConversionFunctions.h" +#include "MutexTracker.h" #include MeshBackendBase *MeshBackendBase::apController = nullptr; -std::vector MeshBackendBase::connectionQueue = {}; -std::vector MeshBackendBase::latestTransmissionOutcomes = {}; +bool MeshBackendBase::_scanMutex = false; bool MeshBackendBase::_printWarnings = true; @@ -231,22 +232,28 @@ bool MeshBackendBase::getAPHidden() {return _apHidden;} bool MeshBackendBase::latestTransmissionSuccessful() { - if(MeshBackendBase::latestTransmissionOutcomes.empty()) + if(latestTransmissionOutcomes().empty()) return false; else - for(TransmissionResult &transmissionResult : MeshBackendBase::latestTransmissionOutcomes) - if(transmissionResult.transmissionStatus != TS_TRANSMISSION_COMPLETE) + for(TransmissionOutcome &transmissionOutcome : latestTransmissionOutcomes()) + if(transmissionOutcome.transmissionStatus() != TS_TRANSMISSION_COMPLETE) return false; return true; } void MeshBackendBase::scanForNetworks(bool scanAllWiFiChannels) -{ +{ + MutexTracker mutexTracker(_scanMutex); + if(!mutexTracker.mutexCaptured()) + { + assert(false && "ERROR! Scan already in progress. Don't call scanForNetworks from callbacks as this may corrupt program state! Aborting."); + return; + } + verboseModePrint(F("Scanning... "), false); /* Scan for APs */ - connectionQueue.clear(); // If scanAllWiFiChannels is true, scanning will cause the WiFi radio to cycle through all WiFi channels. // This means existing WiFi connections are likely to break or work poorly if done frequently. @@ -264,14 +271,20 @@ void MeshBackendBase::scanForNetworks(bool scanAllWiFiChannels) getNetworkFilter()(n, *this); // Update the connectionQueue. } -void MeshBackendBase::printAPInfo(const int apNetworkIndex, const String &apSSID, const int apWiFiChannel) +void MeshBackendBase::printAPInfo(const NetworkInfoBase &apNetworkInfo) { - verboseModePrint(String(F("AP acquired: ")) + apSSID + String(F(", Ch:")) + String(apWiFiChannel) + " ", false); + String mainNetworkIdentifier = apNetworkInfo.SSID(); + if(mainNetworkIdentifier == NetworkInfoBase::defaultSSID) // If SSID not provided, use BSSID instead + { + mainNetworkIdentifier = macToString(apNetworkInfo.BSSID()); + } + + verboseModePrint(String(F("AP acquired: ")) + mainNetworkIdentifier + String(F(", Ch:")) + String(apNetworkInfo.wifiChannel()) + " ", false); - if(apNetworkIndex != NETWORK_INFO_DEFAULT_INT) + if(apNetworkInfo.RSSI() != NetworkInfoBase::defaultRSSI) { - verboseModePrint("(" + String(WiFi.RSSI(apNetworkIndex)) + String(F("dBm) ")) + - (WiFi.encryptionType(apNetworkIndex) == ENC_TYPE_NONE ? String(F("open")) : ""), false); + verboseModePrint("(" + String(apNetworkInfo.RSSI()) + String(F("dBm) ")) + + (apNetworkInfo.encryptionType() == ENC_TYPE_NONE ? String(F("open")) : ""), false); } verboseModePrint(F("... "), false); diff --git a/libraries/ESP8266WiFiMesh/src/MeshBackendBase.h b/libraries/ESP8266WiFiMesh/src/MeshBackendBase.h index 3933098223..778dbabddc 100644 --- a/libraries/ESP8266WiFiMesh/src/MeshBackendBase.h +++ b/libraries/ESP8266WiFiMesh/src/MeshBackendBase.h @@ -20,7 +20,8 @@ #define __MESHBACKENDBASE_H__ #include -#include "TransmissionResult.h" +#include "TransmissionOutcome.h" +#include "NetworkInfoBase.h" const String ESP8266_MESH_EMPTY_STRING = ""; @@ -45,25 +46,18 @@ class MeshBackendBase { virtual ~MeshBackendBase(); /** - * A vector that contains the NetworkInfo for each WiFi network to connect to. - * The connectionQueue vector is cleared before each new scan and filled via the networkFilter callback function once the scan completes. - * WiFi connections will start with connectionQueue[0] and then incrementally proceed to higher vector positions. - * Note that old network indicies often are invalidated whenever a new WiFi network scan occurs. - */ - static std::vector connectionQueue; - - /** - * A vector with the TransmissionResult for each AP to which a transmission was attempted during the latest attemptTransmission call. + * Returns a vector with the TransmissionOutcome for each AP to which a transmission was attempted during the latest attemptTransmission call. + * This vector is unique for each mesh backend. * The latestTransmissionOutcomes vector is cleared before each new transmission attempt. * Connection attempts are indexed in the same order they were attempted. * Note that old network indicies often are invalidated whenever a new WiFi network scan occurs. */ - static std::vector latestTransmissionOutcomes; + virtual std::vector & latestTransmissionOutcomes() = 0; /** * @return True if latest transmission was successful (i.e. latestTransmissionOutcomes is not empty and all entries have transmissionStatus TS_TRANSMISSION_COMPLETE). False otherwise. */ - static bool latestTransmissionSuccessful(); + bool latestTransmissionSuccessful(); /** * Initialises the node. @@ -271,7 +265,7 @@ class MeshBackendBase { protected: virtual void scanForNetworks(bool scanAllWiFiChannels); - virtual void printAPInfo(const int apNetworkIndex, const String &apSSID, const int apWiFiChannel); + virtual void printAPInfo(const NetworkInfoBase &apNetworkInfo); /** * Called just before we activate the AP. @@ -287,6 +281,8 @@ class MeshBackendBase { void setClassType(mesh_backend_t classType); + static bool _scanMutex; + private: mesh_backend_t _classType; diff --git a/libraries/ESP8266WiFiMesh/src/NetworkInfo.cpp b/libraries/ESP8266WiFiMesh/src/NetworkInfo.cpp index 10300dd7bf..7e983ce867 100644 --- a/libraries/ESP8266WiFiMesh/src/NetworkInfo.cpp +++ b/libraries/ESP8266WiFiMesh/src/NetworkInfo.cpp @@ -22,6 +22,29 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ + + + + + + + +/******************************************************************************************** +* NOTE! +* +* This class is deprecated and will be removed in core version 3.0.0. +* If you are still using this class, please consider migrating to the new API shown in +* the EspnowNetworkInfo.h or TcpIpNetworkInfo.h source files. +* +* TODO: delete this file. +********************************************************************************************/ + + + + + + + #include "NetworkInfo.h" @@ -74,4 +97,3 @@ NetworkInfo & NetworkInfo::operator=(const NetworkInfo &other) networkIndex = other.networkIndex; return *this; } - diff --git a/libraries/ESP8266WiFiMesh/src/NetworkInfo.h b/libraries/ESP8266WiFiMesh/src/NetworkInfo.h index 82fcd90c04..9416cf7658 100644 --- a/libraries/ESP8266WiFiMesh/src/NetworkInfo.h +++ b/libraries/ESP8266WiFiMesh/src/NetworkInfo.h @@ -23,12 +23,34 @@ * THE SOFTWARE. */ + + + + + + +/******************************************************************************************** +* NOTE! +* +* This class is deprecated and will be removed in core version 3.0.0. +* If you are still using this class, please consider migrating to the new API shown in +* the EspnowNetworkInfo.h or TcpIpNetworkInfo.h source files. +* +* TODO: delete this file. +********************************************************************************************/ + + + + + + + + #ifndef __NETWORKINFO_H__ #define __NETWORKINFO_H__ #include - -const int NETWORK_INFO_DEFAULT_INT = -1; +#include "NetworkInfoBase.h" class NetworkInfo { diff --git a/libraries/ESP8266WiFiMesh/src/NetworkInfoBase.cpp b/libraries/ESP8266WiFiMesh/src/NetworkInfoBase.cpp new file mode 100644 index 0000000000..9d3667d330 --- /dev/null +++ b/libraries/ESP8266WiFiMesh/src/NetworkInfoBase.cpp @@ -0,0 +1,119 @@ +/* + * Copyright (C) 2019 Anders Löfgren + * + * License (MIT license): + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include "NetworkInfoBase.h" + +const String NetworkInfoBase::defaultSSID = ""; +const int32_t NetworkInfoBase::defaultWifiChannel = NETWORK_INFO_DEFAULT_INT; +const uint8_t NetworkInfoBase::defaultEncryptionType = 0; +const int32_t NetworkInfoBase::defaultRSSI = ~0; +const bool NetworkInfoBase::defaultIsHidden = false; + +void NetworkInfoBase::storeBSSID(const uint8_t newBSSID[6]) +{ + if(newBSSID != nullptr) + { + if(_BSSID == nullptr) + { + _BSSID = _bssidArray; + } + + for(int i = 0; i < 6; i++) + { + _BSSID[i] = newBSSID[i]; + } + } + else + { + _BSSID = nullptr; + } +} + +NetworkInfoBase::NetworkInfoBase() {}; + +NetworkInfoBase::NetworkInfoBase(uint8_t networkIndex) +{ + uint8_t *bssidPtr = nullptr; + WiFi.getNetworkInfo(networkIndex, _SSID, _encryptionType, _RSSI, bssidPtr, _wifiChannel, _isHidden); + storeBSSID(bssidPtr); +} + +NetworkInfoBase::NetworkInfoBase(const String &SSID, int32_t wifiChannel, const uint8_t BSSID[6], uint8_t encryptionType, int32_t RSSI, bool isHidden) : + _SSID(SSID), _wifiChannel(wifiChannel), _encryptionType(encryptionType), _RSSI(RSSI), _isHidden(isHidden) +{ + storeBSSID(BSSID); +} + +NetworkInfoBase::NetworkInfoBase(const NetworkInfoBase &other) : _SSID(other.SSID()), _wifiChannel(other.wifiChannel()), _encryptionType(other.encryptionType()), + _RSSI(other.RSSI()), _isHidden(other.isHidden()) +{ + storeBSSID(other.BSSID()); +} + +NetworkInfoBase & NetworkInfoBase::operator=(const NetworkInfoBase &other) +{ + if(this != &other) + { + storeBSSID(other.BSSID()); + _SSID = other.SSID(); + _wifiChannel = other.wifiChannel(); + _encryptionType = other.encryptionType(); + _RSSI = other.RSSI(); + _isHidden = other.isHidden(); + } + + return *this; +} + +NetworkInfoBase::~NetworkInfoBase() { }; + +void NetworkInfoBase::setBSSID(const uint8_t BSSID[6]) { storeBSSID(BSSID); } +const uint8_t *NetworkInfoBase::BSSID() const { return _BSSID; } +uint8_t *NetworkInfoBase::getBSSID(uint8_t resultArray[6]) const +{ + if(BSSID()) + { + std::copy_n(_bssidArray, 6, resultArray); + return resultArray; + } + else + { + return nullptr; + } +} + +void NetworkInfoBase::setSSID(String &SSID) { _SSID = SSID; } +String NetworkInfoBase::SSID() const { return _SSID; } + +void NetworkInfoBase::setWifiChannel(int32_t wifiChannel) { _wifiChannel = wifiChannel; } +int32_t NetworkInfoBase::wifiChannel() const { return _wifiChannel; } + +void NetworkInfoBase::setEncryptionType(uint8_t encryptionType) { _encryptionType = encryptionType; } +uint8_t NetworkInfoBase::encryptionType() const { return _encryptionType; } + +void NetworkInfoBase::setRSSI(int32_t RSSI) { _RSSI = RSSI; } +int32_t NetworkInfoBase::RSSI() const { return _RSSI; } + +void NetworkInfoBase::setIsHidden(bool isHidden) { _isHidden = isHidden; } +bool NetworkInfoBase::isHidden() const { return _isHidden; } diff --git a/libraries/ESP8266WiFiMesh/src/NetworkInfoBase.h b/libraries/ESP8266WiFiMesh/src/NetworkInfoBase.h new file mode 100644 index 0000000000..8650322ee8 --- /dev/null +++ b/libraries/ESP8266WiFiMesh/src/NetworkInfoBase.h @@ -0,0 +1,101 @@ +/* + * Copyright (C) 2019 Anders Löfgren + * + * License (MIT license): + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#ifndef __NETWORKINFOBASE_H__ +#define __NETWORKINFOBASE_H__ + +#include + +const int NETWORK_INFO_DEFAULT_INT = -1; + +class NetworkInfoBase { + +public: + + /** + * Automatically fill in the rest of the network info using networkIndex and the WiFi scan results. + */ + NetworkInfoBase(uint8_t networkIndex); + + /** + * Without giving channel and BSSID, connection time is longer. + */ + NetworkInfoBase(const String &SSID, int32_t wifiChannel, const uint8_t BSSID[6], uint8_t encryptionType, int32_t RSSI, bool isHidden); + + NetworkInfoBase(const NetworkInfoBase &other); + + NetworkInfoBase & operator=(const NetworkInfoBase &other); + + void setBSSID(const uint8_t BSSID[6]); + const uint8_t *BSSID() const; + /** + * @return If BSSID is set, a pointer to resultArray which will contain a copy of BSSID. nullptr otherwise. + */ + uint8_t *getBSSID(uint8_t resultArray[6]) const; + + void setSSID(String &SSID); + String SSID() const; + + void setWifiChannel(int32_t wifiChannel); + int32_t wifiChannel() const; + + void setEncryptionType(uint8_t encryptionType); + uint8_t encryptionType() const; + + void setRSSI(int32_t RSSI); + int32_t RSSI() const; + + void setIsHidden(bool isHidden); + bool isHidden() const; + + static const String defaultSSID; + static const int32_t defaultWifiChannel; + static const uint8_t defaultEncryptionType; + static const int32_t defaultRSSI; + static const bool defaultIsHidden; + +protected: + + ~NetworkInfoBase(); + + NetworkInfoBase(); + + /** + * Copy newBSSID into _BSSID. + * Prefer this method for changing NetworkInfo BSSID, unless you actually want to change the _BSSID pointer. + */ + void storeBSSID(const uint8_t newBSSID[6]); + +private: + + uint8_t _bssidArray[6] {0}; + uint8_t *_BSSID = nullptr; + String _SSID = defaultSSID; + int32_t _wifiChannel = defaultWifiChannel; + uint8_t _encryptionType = defaultEncryptionType; // see enum wl_enc_type for values + int32_t _RSSI = defaultRSSI; + bool _isHidden = defaultIsHidden; +}; + +#endif diff --git a/libraries/ESP8266WiFiMesh/src/TcpIpMeshBackend.cpp b/libraries/ESP8266WiFiMesh/src/TcpIpMeshBackend.cpp index 4f73877e74..8dcab87ab4 100644 --- a/libraries/ESP8266WiFiMesh/src/TcpIpMeshBackend.cpp +++ b/libraries/ESP8266WiFiMesh/src/TcpIpMeshBackend.cpp @@ -34,11 +34,16 @@ bool TcpIpMeshBackend::_tcpIpTransmissionMutex = false; String TcpIpMeshBackend::lastSSID = ""; bool TcpIpMeshBackend::staticIPActivated = false; +String TcpIpMeshBackend::_temporaryMessage = ""; + // IP needs to be at the same subnet as server gateway (192.168.4 in this case). Station gateway ip must match ip for server. IPAddress TcpIpMeshBackend::staticIP = emptyIP; IPAddress TcpIpMeshBackend::gateway = IPAddress(192,168,4,1); IPAddress TcpIpMeshBackend::subnetMask = IPAddress(255,255,255,0); +std::vector TcpIpMeshBackend::_connectionQueue = {}; +std::vector TcpIpMeshBackend::_latestTransmissionOutcomes = {}; + TcpIpMeshBackend::TcpIpMeshBackend(requestHandlerType requestHandler, responseHandlerType responseHandler, networkFilterType networkFilter, const String &meshPassword, const String &ssidPrefix, const String &ssidSuffix, bool verboseMode, uint8 meshWiFiChannel, uint16_t serverPort) @@ -51,6 +56,16 @@ TcpIpMeshBackend::TcpIpMeshBackend(requestHandlerType requestHandler, responseHa setServerPort(serverPort); } +std::vector & TcpIpMeshBackend::connectionQueue() +{ + return _connectionQueue; +} + +std::vector & TcpIpMeshBackend::latestTransmissionOutcomes() +{ + return _latestTransmissionOutcomes; +} + void TcpIpMeshBackend::begin() { if(!TcpIpMeshBackend::getAPController()) // If there is no active AP controller @@ -78,6 +93,20 @@ void TcpIpMeshBackend::deactivateAPHook() bool TcpIpMeshBackend::transmissionInProgress(){return _tcpIpTransmissionMutex;} +void TcpIpMeshBackend::setTemporaryMessage(const String &newTemporaryMessage) {_temporaryMessage = newTemporaryMessage;} +String TcpIpMeshBackend::getTemporaryMessage() {return _temporaryMessage;} +void TcpIpMeshBackend::clearTemporaryMessage() {_temporaryMessage = "";} + +String TcpIpMeshBackend::getCurrentMessage() +{ + String message = getTemporaryMessage(); + + if(message == "") // If no temporary message stored + message = getMessage(); + + return message; +} + void TcpIpMeshBackend::setStaticIP(const IPAddress &newIP) { // Comment out the line below to remove static IP and use DHCP instead. @@ -201,7 +230,7 @@ transmission_status_t TcpIpMeshBackend::exchangeInfo(WiFiClient &currClient) { verboseModePrint("Transmitting"); // Not storing strings in flash (via F()) to avoid performance impacts when using the string. - currClient.print(getMessage() + "\r"); + currClient.print(getCurrentMessage() + "\r"); yield(); if (!waitForClientTransmission(currClient, _stationModeTimeoutMs)) @@ -347,7 +376,42 @@ transmission_status_t TcpIpMeshBackend::connectToNode(const String &targetSSID, return attemptDataTransfer(); } -void TcpIpMeshBackend::attemptTransmission(const String &message, bool scan, bool scanAllWiFiChannels, bool concludingDisconnect, bool initialDisconnect ) +transmission_status_t TcpIpMeshBackend::initiateTransmission(const TcpIpNetworkInfo &recipientInfo) +{ + WiFi.disconnect(); + yield(); + + assert(recipientInfo.SSID() != ""); // We need at least SSID to connect + String targetSSID = recipientInfo.SSID(); + int32_t targetWiFiChannel = recipientInfo.wifiChannel(); + uint8_t targetBSSID[6] {0}; + recipientInfo.getBSSID(targetBSSID); + + if(verboseMode()) // Avoid string generation if not required + { + printAPInfo(recipientInfo); + } + + return connectToNode(targetSSID, targetWiFiChannel, targetBSSID); +} + +void TcpIpMeshBackend::enterPostTransmissionState(bool concludingDisconnect) +{ + if(WiFi.status() == WL_CONNECTED && staticIP != emptyIP && !staticIPActivated) + { + verboseModePrint(F("Reactivating static IP to allow for faster re-connects.")); + setStaticIP(staticIP); + } + + // If we do not want to be connected at end of transmission, disconnect here so we can re-enable static IP first (above). + if(concludingDisconnect) + { + WiFi.disconnect(); + yield(); + } +} + +void TcpIpMeshBackend::attemptTransmission(const String &message, bool scan, bool scanAllWiFiChannels, bool concludingDisconnect, bool initialDisconnect) { MutexTracker mutexTracker(_tcpIpTransmissionMutex); if(!mutexTracker.mutexCaptured()) @@ -364,71 +428,68 @@ void TcpIpMeshBackend::attemptTransmission(const String &message, bool scan, boo setMessage(message); - latestTransmissionOutcomes.clear(); + latestTransmissionOutcomes().clear(); if(WiFi.status() == WL_CONNECTED) { transmission_status_t transmissionResult = attemptDataTransfer(); - latestTransmissionOutcomes.push_back(TransmissionResult(connectionQueue.back(), transmissionResult)); + latestTransmissionOutcomes().push_back(TransmissionOutcome(connectionQueue().back(), transmissionResult)); } else { if(scan) { + connectionQueue().clear(); scanForNetworks(scanAllWiFiChannels); } - for(NetworkInfo ¤tNetwork : connectionQueue) + for(TcpIpNetworkInfo ¤tNetwork : connectionQueue()) { - WiFi.disconnect(); - yield(); - - String currentSSID = ""; - int currentWiFiChannel = NETWORK_INFO_DEFAULT_INT; - uint8_t *currentBSSID = NULL; - - // If an SSID has been assigned, it is prioritized over an assigned networkIndex since the networkIndex is more likely to change. - if(currentNetwork.SSID != "") - { - currentSSID = currentNetwork.SSID; - currentWiFiChannel = currentNetwork.wifiChannel; - currentBSSID = currentNetwork.BSSID; - } - else // Use only networkIndex - { - currentSSID = WiFi.SSID(currentNetwork.networkIndex); - currentWiFiChannel = WiFi.channel(currentNetwork.networkIndex); - currentBSSID = WiFi.BSSID(currentNetwork.networkIndex); - } - - if(verboseMode()) // Avoid string generation if not required - { - printAPInfo(currentNetwork.networkIndex, currentSSID, currentWiFiChannel); - } - - transmission_status_t transmissionResult = connectToNode(currentSSID, currentWiFiChannel, currentBSSID); + transmission_status_t transmissionResult = initiateTransmission(currentNetwork); - latestTransmissionOutcomes.push_back(TransmissionResult{.origin = currentNetwork, .transmissionStatus = transmissionResult}); + latestTransmissionOutcomes().push_back(TransmissionOutcome{.origin = currentNetwork, .transmissionStatus = transmissionResult}); } } - if(WiFi.status() == WL_CONNECTED && staticIP != emptyIP && !staticIPActivated) + enterPostTransmissionState(concludingDisconnect); +} + +void TcpIpMeshBackend::attemptTransmission(const String &message, bool scan, bool scanAllWiFiChannels) +{ + attemptTransmission(message, scan, scanAllWiFiChannels, true, false); +} + +transmission_status_t TcpIpMeshBackend::attemptTransmission(const String &message, const TcpIpNetworkInfo &recipientInfo, bool concludingDisconnect, bool initialDisconnect) +{ + MutexTracker mutexTracker(_tcpIpTransmissionMutex); + if(!mutexTracker.mutexCaptured()) { - verboseModePrint(F("Reactivating static IP to allow for faster re-connects.")); - setStaticIP(staticIP); + assert(false && "ERROR! TCP/IP transmission in progress. Don't call attemptTransmission from callbacks as this may corrupt program state! Aborting."); + return TS_CONNECTION_FAILED; } - // If we do not want to be connected at end of transmission, disconnect here so we can re-enable static IP first (above). - if(concludingDisconnect) + transmission_status_t transmissionResult = TS_CONNECTION_FAILED; + setTemporaryMessage(message); + + if(initialDisconnect) { WiFi.disconnect(); yield(); } -} -void TcpIpMeshBackend::attemptTransmission(const String &message, bool scan, bool scanAllWiFiChannels) -{ - attemptTransmission(message, scan, scanAllWiFiChannels, true, false); + if(WiFi.status() == WL_CONNECTED && WiFi.SSID() == recipientInfo.SSID()) + { + transmissionResult = attemptDataTransfer(); + } + else + { + transmissionResult = initiateTransmission(recipientInfo); + } + + enterPostTransmissionState(concludingDisconnect); + clearTemporaryMessage(); + + return transmissionResult; } void TcpIpMeshBackend::acceptRequest() diff --git a/libraries/ESP8266WiFiMesh/src/TcpIpMeshBackend.h b/libraries/ESP8266WiFiMesh/src/TcpIpMeshBackend.h index 0c9f809666..e83cbac357 100644 --- a/libraries/ESP8266WiFiMesh/src/TcpIpMeshBackend.h +++ b/libraries/ESP8266WiFiMesh/src/TcpIpMeshBackend.h @@ -31,7 +31,7 @@ #include #include #include "MeshBackendBase.h" -#include "NetworkInfo.h" +#include "TcpIpNetworkInfo.h" class TcpIpMeshBackend : public MeshBackendBase { @@ -65,6 +65,24 @@ class TcpIpMeshBackend : public MeshBackendBase { const String &meshPassword, const String &ssidPrefix, const String &ssidSuffix, bool verboseMode = false, uint8 meshWiFiChannel = 1, uint16_t serverPort = 4011); + /** + * Returns a vector that contains the NetworkInfo for each WiFi network to connect to. + * This vector is unique for each mesh backend. + * The connectionQueue vector is cleared before each new scan and filled via the networkFilter callback function once the scan completes. + * WiFi connections will start with connectionQueue[0] and then incrementally proceed to higher vector positions. + * Note that old network indicies often are invalidated whenever a new WiFi network scan occurs. + */ + std::vector & connectionQueue(); + + /** + * Returns a vector with the TransmissionOutcome for each AP to which a transmission was attempted during the latest attemptTransmission call. + * This vector is unique for each mesh backend. + * The latestTransmissionOutcomes vector is cleared before each new transmission attempt. + * Connection attempts are indexed in the same order they were attempted. + * Note that old network indicies often are invalidated whenever a new WiFi network scan occurs. + */ + std::vector & latestTransmissionOutcomes() override; + /** * Initialises the node. */ @@ -86,12 +104,26 @@ class TcpIpMeshBackend : public MeshBackendBase { void attemptTransmission(const String &message, bool scan, bool scanAllWiFiChannels, bool concludingDisconnect, bool initialDisconnect = false); void attemptTransmission(const String &message, bool scan = true, bool scanAllWiFiChannels = false) override; + + /** + * Transmit message to a single recipient without changing the local transmission state (apart from connecting to the recipient if required). + * Will not change connectionQueue, latestTransmissionOutcomes or stored message. + * + * Note that if wifiChannel and BSSID are missing from recipientInfo, connection time will be longer. + */ + transmission_status_t attemptTransmission(const String &message, const TcpIpNetworkInfo &recipientInfo, bool concludingDisconnect = true, bool initialDisconnect = false); /** * If any clients are connected, accept their requests and call the requestHandler function for each one. */ void acceptRequest(); + /** + * Get the TCP/IP message that is currently scheduled for transmission. + * Unlike the getMessage() method, this will be correct even when the single recipient attemptTransmission method is used. + */ + String getCurrentMessage(); + /** * Set a static IP address for the ESP8266 and activate use of static IP. * The static IP needs to be at the same subnet as the server's gateway. @@ -165,6 +197,9 @@ class TcpIpMeshBackend : public MeshBackendBase { protected: + static std::vector _connectionQueue; + static std::vector _latestTransmissionOutcomes; + /** * Called just before we activate the AP. * Put _server.stop() in deactivateAPHook() in case you use _server.begin() here. @@ -189,6 +224,16 @@ class TcpIpMeshBackend : public MeshBackendBase { */ static bool transmissionInProgress(); + /** + * Set a message that will be sent to other nodes when calling attemptTransmission, instead of the regular getMessage(). + * This message is used until clearTemporaryMessage() is called. + * + * @param newMessage The message to send. + */ + void setTemporaryMessage(const String &newMessage); + String getTemporaryMessage(); + void clearTemporaryMessage(); + private: uint16_t _serverPort; @@ -198,6 +243,7 @@ class TcpIpMeshBackend : public MeshBackendBase { int _stationModeTimeoutMs = 5000; // int is the type used in the Arduino core for this particular API, not uint32_t, which is why we use int here. uint32_t _apModeTimeoutMs = 4500; + static String _temporaryMessage; static String lastSSID; static bool staticIPActivated; bool useStaticIP; @@ -212,6 +258,8 @@ class TcpIpMeshBackend : public MeshBackendBase { bool waitForClientTransmission(WiFiClient &currClient, uint32_t maxWait); transmission_status_t attemptDataTransfer(); transmission_status_t attemptDataTransferKernel(); + transmission_status_t initiateTransmission(const TcpIpNetworkInfo &recipientInfo); + void enterPostTransmissionState(bool concludingDisconnect); }; #endif diff --git a/libraries/ESP8266WiFiMesh/src/TcpIpNetworkInfo.cpp b/libraries/ESP8266WiFiMesh/src/TcpIpNetworkInfo.cpp new file mode 100644 index 0000000000..73a6e232e4 --- /dev/null +++ b/libraries/ESP8266WiFiMesh/src/TcpIpNetworkInfo.cpp @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2019 Anders Löfgren + * + * License (MIT license): + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include "TcpIpNetworkInfo.h" + +TcpIpNetworkInfo::TcpIpNetworkInfo(int networkIndex) : NetworkInfoBase(networkIndex) { }; + +TcpIpNetworkInfo::TcpIpNetworkInfo(const String &SSID, int32_t wifiChannel, const uint8_t BSSID[6], uint8_t encryptionType, int32_t RSSI , bool isHidden) + : NetworkInfoBase(SSID, wifiChannel, BSSID, encryptionType, RSSI, isHidden) +{ } diff --git a/libraries/ESP8266WiFiMesh/src/TcpIpNetworkInfo.h b/libraries/ESP8266WiFiMesh/src/TcpIpNetworkInfo.h new file mode 100644 index 0000000000..eed1a9981a --- /dev/null +++ b/libraries/ESP8266WiFiMesh/src/TcpIpNetworkInfo.h @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2019 Anders Löfgren + * + * License (MIT license): + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#ifndef __TCPIPNETWORKINFO_H__ +#define __TCPIPNETWORKINFO_H__ + +#include "NetworkInfoBase.h" + +class TcpIpNetworkInfo : public NetworkInfoBase { + +public: + + /** + * Automatically fill in the rest of the network info using networkIndex and the WiFi scan results. + */ + TcpIpNetworkInfo(int networkIndex); + + /** + * Without giving wifiChannel and BSSID, connection time is longer. + */ + TcpIpNetworkInfo(const String &SSID, int32_t wifiChannel = defaultWifiChannel, const uint8_t BSSID[6] = nullptr, uint8_t encryptionType = defaultEncryptionType, + int32_t RSSI = defaultRSSI, bool isHidden = defaultIsHidden); +}; + +#endif diff --git a/libraries/ESP8266WiFiMesh/src/TransmissionOutcome.cpp b/libraries/ESP8266WiFiMesh/src/TransmissionOutcome.cpp new file mode 100644 index 0000000000..24c741bca7 --- /dev/null +++ b/libraries/ESP8266WiFiMesh/src/TransmissionOutcome.cpp @@ -0,0 +1,36 @@ +/* + * Copyright (C) 2019 Anders Löfgren + * + * License (MIT license): + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include "TransmissionOutcome.h" + +TransmissionOutcome::TransmissionOutcome(const NetworkInfoBase &origin, transmission_status_t transmissionStatus) + : NetworkInfoBase(origin), _transmissionStatus(transmissionStatus) +{ } + +TransmissionOutcome::TransmissionOutcome(const String &SSID, int32_t wifiChannel, const uint8_t BSSID[6], uint8_t encryptionType, int32_t RSSI, bool isHidden, transmission_status_t transmissionStatus) + : NetworkInfoBase(SSID, wifiChannel, BSSID, encryptionType, RSSI, isHidden), _transmissionStatus(transmissionStatus) +{ } + +void TransmissionOutcome::setTransmissionStatus(transmission_status_t transmissionStatus) { _transmissionStatus = transmissionStatus; } +transmission_status_t TransmissionOutcome::transmissionStatus() const { return _transmissionStatus; } diff --git a/libraries/ESP8266WiFiMesh/src/TransmissionOutcome.h b/libraries/ESP8266WiFiMesh/src/TransmissionOutcome.h new file mode 100644 index 0000000000..026556d7b7 --- /dev/null +++ b/libraries/ESP8266WiFiMesh/src/TransmissionOutcome.h @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2019 Anders Löfgren + * + * License (MIT license): + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#ifndef __TRANSMISSIONOUTCOME_H__ +#define __TRANSMISSIONOUTCOME_H__ + +#include +#include "NetworkInfoBase.h" + +typedef enum +{ + TS_CONNECTION_FAILED = -1, + TS_TRANSMISSION_FAILED = 0, + TS_TRANSMISSION_COMPLETE = 1 +} transmission_status_t; + +class TransmissionOutcome : public NetworkInfoBase { + +public: + + TransmissionOutcome(const NetworkInfoBase &origin, transmission_status_t transmissionStatus); + + TransmissionOutcome(const String &SSID, int32_t wifiChannel, const uint8_t BSSID[6], uint8_t encryptionType, int32_t RSSI, bool isHidden, transmission_status_t transmissionStatus); + + void setTransmissionStatus(transmission_status_t transmissionStatus); + transmission_status_t transmissionStatus() const; + +private: + + transmission_status_t _transmissionStatus; +}; + +#endif diff --git a/libraries/ESP8266WiFiMesh/src/TransmissionResult.cpp b/libraries/ESP8266WiFiMesh/src/TransmissionResult.cpp index 81f9312461..29b37675c6 100644 --- a/libraries/ESP8266WiFiMesh/src/TransmissionResult.cpp +++ b/libraries/ESP8266WiFiMesh/src/TransmissionResult.cpp @@ -23,6 +23,29 @@ * THE SOFTWARE. */ + + + + + + +/******************************************************************************************** +* NOTE! +* +* This class is deprecated and will be removed in core version 3.0.0. +* If you are still using this class, please consider migrating to the new API shown in +* the EspnowNetworkInfo.h or TcpIpNetworkInfo.h source files. +* +* TODO: delete this file. +********************************************************************************************/ + + + + + + + + #include "TransmissionResult.h" TransmissionResult::TransmissionResult(int newNetworkIndex, transmission_status_t newTransmissionStatus, bool autofill) : diff --git a/libraries/ESP8266WiFiMesh/src/TransmissionResult.h b/libraries/ESP8266WiFiMesh/src/TransmissionResult.h index 8cc4cc020b..86c9b0b088 100644 --- a/libraries/ESP8266WiFiMesh/src/TransmissionResult.h +++ b/libraries/ESP8266WiFiMesh/src/TransmissionResult.h @@ -23,18 +23,36 @@ * THE SOFTWARE. */ + + + + + + +/******************************************************************************************** +* NOTE! +* +* This class is deprecated and will be removed in core version 3.0.0. +* If you are still using this class, please consider migrating to the new API shown in +* the EspnowNetworkInfo.h or TcpIpNetworkInfo.h source files. +* +* TODO: delete this file. +********************************************************************************************/ + + + + + + + + + #ifndef __TRANSMISSIONRESULT_H__ #define __TRANSMISSIONRESULT_H__ #include #include "NetworkInfo.h" - -typedef enum -{ - TS_CONNECTION_FAILED = -1, - TS_TRANSMISSION_FAILED = 0, - TS_TRANSMISSION_COMPLETE = 1 -} transmission_status_t; +#include "TransmissionOutcome.h" class TransmissionResult : public NetworkInfo { From f8ec4f1c72b9e48c882022bded6387df587ea076 Mon Sep 17 00:00:00 2001 From: Anders Date: Thu, 31 Oct 2019 22:25:12 +0100 Subject: [PATCH 04/30] - Make connectionQueue(), latestTransmissionOutcomes() and latestTransmissionSuccessful() methods static in order to match the underlying data storage. - Make it possible to transfer elements directly between connectionQueues. - Add defaultBSSID value. - Fix bug where encrypted Espnow-connections expired 1 ms too late. - Add MutexTracker::captureBan() functionality and use it in the espnowReceiveCallbackWrapper method to ensure a consistent mutex environment there. - Rename acceptRequest to acceptRequests since several requests can be accepted, not just one. - Reorganize EspnowMeshBackend.cpp. - Split sendEspnowResponses() method into sendEspnowResponses() and sendPeerRequestConfirmations(). - Add sendStoredEspnowMessages() method to provide the same functionality as the previous version of sendEspnowResponses(). - Add logic for handling peerRequestConfirmations received at the same time as a peer request is being made, to avoid lockups when there are simultaneous cyclic peer requests. - Add logic for handling simultaneous reciprocal peer requests. - Include MAC addresses in HMAC calculations for peer requests and use HMAC for all unencrypted peer request messages, to make sure we receive valid MAC combinations. - Add asserts to ensure ESP-NOW encryption integrity during code changes. - Add estimatedMaxDuration argument to performEspnowMaintainance and related methods. - Add methods to EncryptedConnectionData for setting peer MAC. - Remove createEncryptionRequestMessage function from JsonTranslator since it is not used, to increase clarity. - Add encryptedConnectionsSoftLimit() and related functionality. - Add mutex to protect connectionQueue usage during attemptTransmission. - Add _ongoingPeerRequestMac variable. - Add reservedEncryptedConnections() method. - Add TransmissionOutcomesUpdateHook() callback. - Add constConnectionQueue() method to allow connectionQueue usage while connectionQueue mutex is active. - Rearrange attemptAutoEncryptingTransmission argument order to increase efficiency. - Add functionality for serializing the unencrypted ESP-NOW connection. - Add some constness. - Improve comments. - Improve documentation. - Update keywords.txt. --- libraries/ESP8266WiFiMesh/keywords.txt | 5 +- .../src/EncryptedConnectionData.cpp | 9 +- .../src/EncryptedConnectionData.h | 2 + .../ESP8266WiFiMesh/src/EspnowMeshBackend.cpp | 1000 ++++++++++------- .../ESP8266WiFiMesh/src/EspnowMeshBackend.h | 191 +++- .../ESP8266WiFiMesh/src/EspnowNetworkInfo.cpp | 6 + .../ESP8266WiFiMesh/src/EspnowNetworkInfo.h | 2 + .../src/EspnowProtocolInterpreter.h | 1 + .../src/ExpiringTimeTracker.cpp | 4 +- .../ESP8266WiFiMesh/src/JsonTranslator.cpp | 59 +- .../ESP8266WiFiMesh/src/JsonTranslator.h | 11 +- .../ESP8266WiFiMesh/src/MeshBackendBase.cpp | 27 +- .../ESP8266WiFiMesh/src/MeshBackendBase.h | 51 +- .../ESP8266WiFiMesh/src/MutexTracker.cpp | 9 +- libraries/ESP8266WiFiMesh/src/MutexTracker.h | 9 + .../ESP8266WiFiMesh/src/NetworkInfoBase.cpp | 1 + .../ESP8266WiFiMesh/src/NetworkInfoBase.h | 3 +- .../ESP8266WiFiMesh/src/PeerRequestLog.cpp | 15 +- .../ESP8266WiFiMesh/src/PeerRequestLog.h | 13 +- .../ESP8266WiFiMesh/src/TcpIpMeshBackend.cpp | 42 +- .../ESP8266WiFiMesh/src/TcpIpMeshBackend.h | 28 +- .../ESP8266WiFiMesh/src/TcpIpNetworkInfo.cpp | 7 + .../ESP8266WiFiMesh/src/TcpIpNetworkInfo.h | 5 +- 23 files changed, 974 insertions(+), 526 deletions(-) diff --git a/libraries/ESP8266WiFiMesh/keywords.txt b/libraries/ESP8266WiFiMesh/keywords.txt index 2ea9f96aad..3574a231d5 100644 --- a/libraries/ESP8266WiFiMesh/keywords.txt +++ b/libraries/ESP8266WiFiMesh/keywords.txt @@ -12,9 +12,6 @@ ESP8266WiFiMesh KEYWORD3 # Datatypes (KEYWORD1) ####################################### -ESP8266WiFiMesh KEYWORD1 -NetworkInfo KEYWORD1 -TransmissionResult KEYWORD1 transmission_status_t KEYWORD1 ####################################### @@ -41,7 +38,7 @@ getSSID KEYWORD2 setMessage KEYWORD2 getMessage KEYWORD2 attemptTransmission KEYWORD2 -acceptRequest KEYWORD2 +acceptRequests KEYWORD2 setStaticIP KEYWORD2 getStaticIP KEYWORD2 disableStaticIP->KEYWORD2 diff --git a/libraries/ESP8266WiFiMesh/src/EncryptedConnectionData.cpp b/libraries/ESP8266WiFiMesh/src/EncryptedConnectionData.cpp index ae30afac92..95a5eaf4ed 100644 --- a/libraries/ESP8266WiFiMesh/src/EncryptedConnectionData.cpp +++ b/libraries/ESP8266WiFiMesh/src/EncryptedConnectionData.cpp @@ -89,6 +89,11 @@ uint8_t *EncryptedConnectionData::getPeerApMac(uint8_t *resultArray) const return resultArray; } +void EncryptedConnectionData::setPeerApMac(const uint8_t *peerApMac) +{ + std::copy_n(peerApMac, 6, _peerApMac); +} + bool EncryptedConnectionData::connectedTo(const uint8_t *peerMac) const { if(macEqual(peerMac, _peerStaMac) || macEqual(peerMac, _peerApMac)) @@ -146,10 +151,10 @@ bool EncryptedConnectionData::desync() const { return _desync; } String EncryptedConnectionData::serialize() const { - // Returns: {"connectionState":{"duration":"123","password":"abc","ownSessionKey":"1A2","peerSessionKey":"3B4","peerStaMac":"F2","peerApMac":"E3"}} + // Returns: {"connectionState":{"duration":"123","password":"abc","ownSK":"1A2","peerSK":"3B4","peerStaMac":"F2","peerApMac":"E3"}} return - "{\"connectionState\":{" + JsonTranslator::jsonConnectionState + (temporary() ? JsonTranslator::jsonDuration + "\"" + String(temporary()->remainingDuration()) + "\"," : "") + JsonTranslator::jsonDesync + "\"" + String(desync()) + "\"," + JsonTranslator::jsonOwnSessionKey + "\"" + uint64ToString(getOwnSessionKey()) + "\"," diff --git a/libraries/ESP8266WiFiMesh/src/EncryptedConnectionData.h b/libraries/ESP8266WiFiMesh/src/EncryptedConnectionData.h index 312b5139af..56443cbe6a 100644 --- a/libraries/ESP8266WiFiMesh/src/EncryptedConnectionData.h +++ b/libraries/ESP8266WiFiMesh/src/EncryptedConnectionData.h @@ -55,7 +55,9 @@ class EncryptedConnectionData { // @param resultArray At least size 6. uint8_t *getPeerStaMac(uint8_t *resultArray) const; + void setPeerStaMac(const uint8_t *peerStaMac) = delete; // A method for setPeerStaMac would sometimes require interacting with the ESP-NOW API to change encrypted connections, so it is not implemented. uint8_t *getPeerApMac(uint8_t *resultArray) const; + void setPeerApMac(const uint8_t *peerApMac); bool connectedTo(const uint8_t *peerMac) const; diff --git a/libraries/ESP8266WiFiMesh/src/EspnowMeshBackend.cpp b/libraries/ESP8266WiFiMesh/src/EspnowMeshBackend.cpp index 072bf7fb7a..28c9dc1d43 100644 --- a/libraries/ESP8266WiFiMesh/src/EspnowMeshBackend.cpp +++ b/libraries/ESP8266WiFiMesh/src/EspnowMeshBackend.cpp @@ -38,6 +38,7 @@ static const uint64_t uint64MSB = 0x8000000000000000; const uint8_t EspnowMeshBackend::broadcastMac[6] = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}; bool EspnowMeshBackend::_espnowTransmissionMutex = false; +bool EspnowMeshBackend::_espnowConnectionQueueMutex = false; EspnowMeshBackend *EspnowMeshBackend::_espnowRequestManager = nullptr; @@ -61,9 +62,11 @@ uint32_t EspnowMeshBackend::_encryptionRequestTimeoutMs = 300; bool EspnowMeshBackend::_espnowSendConfirmed = false; String EspnowMeshBackend::_ongoingPeerRequestNonce = ""; +uint8_t EspnowMeshBackend::_ongoingPeerRequestMac[6] = {0}; EspnowMeshBackend *EspnowMeshBackend::_ongoingPeerRequester = nullptr; encrypted_connection_status_t EspnowMeshBackend::_ongoingPeerRequestResult = ECS_MAX_CONNECTIONS_REACHED_SELF; uint32_t EspnowMeshBackend::_ongoingPeerRequestEncryptionStart = 0; +bool EspnowMeshBackend::_reciprocalPeerRequestConfirmation = false; uint8_t EspnowMeshBackend::_espnowEncryptionKok[espnowEncryptionKeyLength] = { 0 }; bool EspnowMeshBackend::_espnowEncryptionKokSet = false; @@ -136,8 +139,78 @@ void EspnowMeshBackend::begin() activateEspnow(); } +bool EspnowMeshBackend::activateEspnow() +{ + if (esp_now_init()==0) + { + if(_espnowEncryptionKokSet && esp_now_set_kok(_espnowEncryptionKok, espnowEncryptionKeyLength)) // esp_now_set_kok returns 0 on success. + warningPrint("Failed to set ESP-NOW KoK!"); + + if(getEspnowRequestManager() == nullptr) + { + setEspnowRequestManager(this); + } + + esp_now_register_recv_cb(espnowReceiveCallbackWrapper); + esp_now_register_send_cb([](uint8_t* mac, uint8_t sendStatus) { + if(_espnowSendConfirmed) + return; + else if(!sendStatus && macEqual(mac, _transmissionTargetBSSID)) // sendStatus == 0 when send was OK. + _espnowSendConfirmed = true; // We do not want to reset this to false. That only happens before transmissions. Otherwise subsequent failed send attempts may obscure an initial successful one. + }); + + // Role must be set before adding peers. Cannot be changed while having peers. + // With ESP_NOW_ROLE_CONTROLLER, we always transmit from the station interface, which gives predictability. + if(esp_now_set_self_role(ESP_NOW_ROLE_CONTROLLER)) // esp_now_set_self_role returns 0 on success. + warningPrint("Failed to set ESP-NOW role! Maybe ESP-NOW peers are already added?"); + + verboseModePrint("ESP-NOW activated."); + verboseModePrint("My ESP-NOW STA MAC: " + WiFi.macAddress() + "\n"); // Get the station MAC address. The softAP MAC is different. + + return true; + } + else + { + warningPrint("ESP-NOW init failed!"); + return false; + } +} + +bool EspnowMeshBackend::deactivateEspnow() +{ + // esp_now_deinit() clears all ESP-NOW API settings, including receive callback, send callback, Kok and peers. + // The node will however continue to give acks to received ESP-NOW transmissions as long as the receiving interface (AP or STA) is active, even though the transmissions will not be processed. + if(esp_now_deinit() == 0) + { + responsesToSend.clear(); + peerRequestConfirmationsToSend.clear(); + receivedEspnowTransmissions.clear(); + sentRequests.clear(); + receivedRequests.clear(); + encryptedConnections.clear(); + EncryptedConnectionLog::setNewRemovalsScheduled(false); + + return true; + } + else + { + return false; + } +} + std::vector & EspnowMeshBackend::connectionQueue() { + MutexTracker connectionQueueMutexTracker(_espnowConnectionQueueMutex); + if(!connectionQueueMutexTracker.mutexCaptured()) + { + assert(false && "ERROR! connectionQueue locked. Don't call connectionQueue() from callbacks other than NetworkFilter as this may corrupt program state!"); + } + + return _connectionQueue; +} + +const std::vector & EspnowMeshBackend::constConnectionQueue() +{ return _connectionQueue; } @@ -146,8 +219,15 @@ std::vector & EspnowMeshBackend::latestTransmissionOutcomes return _latestTransmissionOutcomes; } -void EspnowMeshBackend::performEspnowMaintainance() +bool EspnowMeshBackend::latestTransmissionSuccessful() +{ + return latestTransmissionSuccessfulBase(latestTransmissionOutcomes()); +} + +void EspnowMeshBackend::performEspnowMaintainance(uint32_t estimatedMaxDuration) { + ExpiringTimeTracker estimatedMaxDurationTracker = ExpiringTimeTracker(estimatedMaxDuration); + // Doing this during an ESP-NOW transmission could invalidate iterators MutexTracker mutexTracker(_espnowTransmissionMutex, handlePostponedRemovals); if(!mutexTracker.mutexCaptured()) @@ -165,7 +245,17 @@ void EspnowMeshBackend::performEspnowMaintainance() updateTemporaryEncryptedConnections(); } - sendEspnowResponses(); + if(estimatedMaxDuration > 0) + { + if(estimatedMaxDurationTracker.expired()) + return; + else + sendStoredEspnowMessages(&estimatedMaxDurationTracker); + } + else + { + sendStoredEspnowMessages(); + } } void EspnowMeshBackend::updateTemporaryEncryptedConnections(bool scheduledRemovalOnly) @@ -195,9 +285,109 @@ void EspnowMeshBackend::updateTemporaryEncryptedConnections(bool scheduledRemova EncryptedConnectionLog::setNewRemovalsScheduled(false); } +template +void EspnowMeshBackend::deleteExpiredLogEntries(std::map, T> &logEntries, uint32_t maxEntryLifetimeMs) +{ + for(typename std::map, T>::iterator entryIterator = logEntries.begin(); + entryIterator != logEntries.end(); ) + { + if(entryIterator->second.timeSinceCreation() > maxEntryLifetimeMs) + { + entryIterator = logEntries.erase(entryIterator); + } + else + ++entryIterator; + } +} + +void EspnowMeshBackend::deleteExpiredLogEntries(std::map, RequestData> &logEntries, uint32_t requestLifetimeMs, uint32_t broadcastLifetimeMs) +{ + for(typename std::map, RequestData>::iterator entryIterator = logEntries.begin(); + entryIterator != logEntries.end(); ) + { + bool broadcast = entryIterator->first.first == uint64BroadcastMac; + uint32_t timeSinceCreation = entryIterator->second.timeSinceCreation(); + + if((!broadcast && timeSinceCreation > requestLifetimeMs) + || (broadcast && timeSinceCreation > broadcastLifetimeMs)) + { + entryIterator = logEntries.erase(entryIterator); + } + else + ++entryIterator; + } +} + +template +void EspnowMeshBackend::deleteExpiredLogEntries(std::list &logEntries, uint32_t maxEntryLifetimeMs) +{ + for(typename std::list::iterator entryIterator = logEntries.begin(); + entryIterator != logEntries.end(); ) + { + if(entryIterator->timeSinceCreation() > maxEntryLifetimeMs) + { + entryIterator = logEntries.erase(entryIterator); + } + else + ++entryIterator; + } +} + +template <> +void EspnowMeshBackend::deleteExpiredLogEntries(std::list &logEntries, uint32_t maxEntryLifetimeMs) +{ + for(typename std::list::iterator entryIterator = logEntries.begin(); + entryIterator != logEntries.end(); ) + { + auto timeTrackerPointer = entryIterator->temporary(); + if(timeTrackerPointer && timeTrackerPointer->timeSinceCreation() > maxEntryLifetimeMs) + { + entryIterator = logEntries.erase(entryIterator); + } + else + ++entryIterator; + } +} + +template <> +void EspnowMeshBackend::deleteExpiredLogEntries(std::list &logEntries, uint32_t maxEntryLifetimeMs) +{ + for(typename std::list::iterator entryIterator = logEntries.begin(); + entryIterator != logEntries.end(); ) + { + auto timeTrackerPointer = entryIterator->temporary(); + if(timeTrackerPointer && timeTrackerPointer->timeSinceCreation() > maxEntryLifetimeMs) + { + entryIterator = logEntries.erase(entryIterator); + } + else + ++entryIterator; + } +} + +void EspnowMeshBackend::clearOldLogEntries() +{ + // Clearing all old log entries at the same time should help minimize heap fragmentation. + + // uint32_t startTime = millis(); + + _timeOfLastLogClear = millis(); + + deleteExpiredLogEntries(receivedEspnowTransmissions, logEntryLifetimeMs()); + deleteExpiredLogEntries(receivedRequests, logEntryLifetimeMs()); // Just needs to be long enough to not accept repeated transmissions by mistake. + deleteExpiredLogEntries(sentRequests, logEntryLifetimeMs(), broadcastResponseTimeoutMs()); + deleteExpiredLogEntries(responsesToSend, logEntryLifetimeMs()); + deleteExpiredLogEntries(peerRequestConfirmationsToSend, getEncryptionRequestTimeout()); +} + void EspnowMeshBackend::espnowReceiveCallbackWrapper(uint8_t *macaddr, uint8_t *dataArray, uint8_t len) { using namespace EspnowProtocolInterpreter; + + // Since this callback can be called during any delay(), we should always consider all mutexes captured. + // This provides a consistent mutex environment, which facilitates development and debugging. + // Otherwise we get issues such as _espnowTransmissionMutex will usually be free, but occasionally taken (when callback occurs in a delay() during attemptTransmission). + MutexTracker captureBanTracker(MutexTracker::captureBan()); if(len >= EspnowProtocolInterpreter::espnowProtocolBytesSize()) // If we do not receive at least the protocol bytes, the transmission is invalid. { @@ -320,7 +510,7 @@ void EspnowMeshBackend::handlePeerRequest(uint8_t *macaddr, uint8_t *dataArray, // Pairing process ends when encryptedConnectionVerificationHeader is received, maxConnectionsReachedHeader is sent or timeout is reached. // Pairing process stages for request receiver: // Receive: encryptionRequestHeader or temporaryEncryptionRequestHeader. - // Send: maxConnectionsReachedHeader / basicConnectionInfoHeader -> encryptedConnectionInfoHeader or maxConnectionsReachedHeader. + // Send: maxConnectionsReachedHeader / basicConnectionInfoHeader -> encryptedConnectionInfoHeader or softLimitEncryptedConnectionInfoHeader or maxConnectionsReachedHeader. // Receive: encryptedConnectionVerificationHeader. using namespace EspnowProtocolInterpreter; @@ -340,6 +530,9 @@ void EspnowMeshBackend::handlePeerRequest(uint8_t *macaddr, uint8_t *dataArray, { int32_t connectionRequestTypeEndIndex = message.indexOf(':', messageHeaderEndIndex + 1); String connectionRequestType = message.substring(messageHeaderEndIndex + 1, connectionRequestTypeEndIndex + 1); + connectionLogIterator encryptedConnection = connectionLogEndIterator(); + if(!getEncryptedConnectionIterator(macaddr, encryptedConnection)) + assert(false && "We must have an encrypted connection if we received an encryptedConnectionVerificationHeader which was encryptedCorrectly."); if(connectionRequestType == encryptionRequestHeader) { @@ -347,10 +540,6 @@ void EspnowMeshBackend::handlePeerRequest(uint8_t *macaddr, uint8_t *dataArray, } else if(connectionRequestType == temporaryEncryptionRequestHeader) { - connectionLogIterator encryptedConnection = connectionLogEndIterator(); - if(!getEncryptedConnectionIterator(macaddr, encryptedConnection)) - assert(false && "We must have an encrypted connection if we received an encryptedConnectionVerificationHeader which was encryptedCorrectly."); - if(encryptedConnection->temporary()) // Should not change duration for existing permanent connections. { uint32_t connectionDuration = 0; @@ -372,10 +561,35 @@ void EspnowMeshBackend::handlePeerRequest(uint8_t *macaddr, uint8_t *dataArray, if(EspnowMeshBackend *currentEspnowRequestManager = getEspnowRequestManager()) { String requestNonce = ""; - if(JsonTranslator::getNonce(message, requestNonce)) + + if(JsonTranslator::getNonce(message, requestNonce) && requestNonce.length() >= 12) // The destination MAC address requires 12 characters. { + uint8_t destinationMac[6] = {0}; + stringToMac(requestNonce, destinationMac); + + uint8_t apMac[6] {0}; + WiFi.softAPmacAddress(apMac); + + bool correctDestination = false; + if(macEqual(destinationMac, apMac)) + { + correctDestination = true; + } + else + { + uint8_t staMac[6] {0}; + WiFi.macAddress(staMac); + + if(macEqual(destinationMac, staMac)) + { + correctDestination = true; + } + } + uint8_t apMacArray[6] = { 0 }; - peerRequestConfirmationsToSend.emplace_back(receivedMessageID, encryptedCorrectly, currentEspnowRequestManager->getMeshPassword(), requestNonce, macaddr, espnowGetTransmissionMac(dataArray, apMacArray), currentEspnowRequestManager->getEspnowHashKey()); + if(correctDestination && JsonTranslator::verifyEncryptionRequestHmac(message, macaddr, espnowGetTransmissionMac(dataArray, apMacArray), currentEspnowRequestManager->getEspnowHashKey(), espnowHashKeyLength)) + peerRequestConfirmationsToSend.emplace_back(receivedMessageID, encryptedCorrectly, currentEspnowRequestManager->getMeshPassword(), currentEspnowRequestManager->encryptedConnectionsSoftLimit(), + requestNonce, macaddr, apMacArray, currentEspnowRequestManager->getEspnowHashKey()); } } } @@ -396,7 +610,7 @@ void EspnowMeshBackend::handlePeerRequestConfirmation(uint8_t *macaddr, uint8_t // Pairing process ends when _ongoingPeerRequestNonce == "" or timeout is reached. // Pairing process stages for request sender: // Send: encryptionRequestHeader or temporaryEncryptionRequestHeader. - // Receive: maxConnectionsReachedHeader / basicConnectionInfoHeader -> encryptedConnectionInfoHeader or maxConnectionsReachedHeader. + // Receive: maxConnectionsReachedHeader / basicConnectionInfoHeader -> encryptedConnectionInfoHeader or softLimitEncryptedConnectionInfoHeader or maxConnectionsReachedHeader. // Send: encryptedConnectionVerificationHeader. using namespace EspnowProtocolInterpreter; @@ -405,18 +619,20 @@ void EspnowMeshBackend::handlePeerRequestConfirmation(uint8_t *macaddr, uint8_t { String message = espnowGetMessageContent(dataArray, len); String requestNonce = ""; - uint8_t macArray[6] = { 0 }; + if(JsonTranslator::getNonce(message, requestNonce) && requestNonce == _ongoingPeerRequestNonce) { int32_t messageHeaderEndIndex = message.indexOf(':'); String messageHeader = message.substring(0, messageHeaderEndIndex + 1); String messageBody = message.substring(messageHeaderEndIndex + 1); + uint8_t apMacArray[6] = { 0 }; + espnowGetTransmissionMac(dataArray, apMacArray); if(messageHeader == basicConnectionInfoHeader) { - // _ongoingPeerRequestResult == ECS_CONNECTION_ESTABLISHED means we have already received a basicConnectionInfoHeader - if(_ongoingPeerRequestResult != ECS_CONNECTION_ESTABLISHED && - JsonTranslator::verifyHmac(message, _ongoingPeerRequester->getEspnowHashKey(), espnowHashKeyLength)) + // encryptedConnectionEstablished(_ongoingPeerRequestResult) means we have already received a basicConnectionInfoHeader + if(!encryptedConnectionEstablished(_ongoingPeerRequestResult) && + JsonTranslator::verifyEncryptionRequestHmac(message, macaddr, apMacArray, _ongoingPeerRequester->getEspnowHashKey(), espnowHashKeyLength)) { _ongoingPeerRequestEncryptionStart = millis(); @@ -425,8 +641,7 @@ void EspnowMeshBackend::handlePeerRequestConfirmation(uint8_t *macaddr, uint8_t if(!getEncryptedConnectionIterator(macaddr, existingEncryptedConnection)) { // Although the newly created session keys are normally never used (they are replaced with synchronized ones later), the session keys must still be randomized to prevent attacks until replaced. - _ongoingPeerRequestResult = _ongoingPeerRequester->addTemporaryEncryptedConnection(macaddr, espnowGetTransmissionMac(dataArray, macArray), - createSessionKey(), createSessionKey(), getEncryptionRequestTimeout()); + _ongoingPeerRequestResult = _ongoingPeerRequester->addTemporaryEncryptedConnection(macaddr, apMacArray, createSessionKey(), createSessionKey(), getEncryptionRequestTimeout()); } else { @@ -442,241 +657,244 @@ void EspnowMeshBackend::handlePeerRequestConfirmation(uint8_t *macaddr, uint8_t } } - if(_ongoingPeerRequestResult != ECS_CONNECTION_ESTABLISHED) + if(!encryptedConnectionEstablished(_ongoingPeerRequestResult)) { // Adding connection failed, abort ongoing peer request. _ongoingPeerRequestNonce = ""; } } } - else + else if(messageHeader == encryptedConnectionInfoHeader || messageHeader == softLimitEncryptedConnectionInfoHeader) { - if(messageHeader == encryptedConnectionInfoHeader) + String messagePassword = ""; + + if(JsonTranslator::getPassword(messageBody, messagePassword) && messagePassword == _ongoingPeerRequester->getMeshPassword()) { - String messagePassword = ""; + // The mesh password is only shared via encrypted messages, so now we know this message is valid since it was encrypted and contained the correct nonce. - if(JsonTranslator::getPassword(messageBody, messagePassword) && messagePassword == _ongoingPeerRequester->getMeshPassword()) + EncryptedConnectionLog *encryptedConnection = getEncryptedConnection(macaddr); + uint64_t peerSessionKey = 0; + uint64_t ownSessionKey = 0; + if(encryptedConnection && JsonTranslator::getPeerSessionKey(messageBody, peerSessionKey) && JsonTranslator::getOwnSessionKey(messageBody, ownSessionKey)) { - // The mesh password is only shared via encrypted messages, so now we know this message is valid since it was encrypted and contained the correct nonce. - - EncryptedConnectionLog *encryptedConnection = getEncryptedConnection(macaddr); - uint64_t peerSessionKey = 0; - uint64_t ownSessionKey = 0; - if(encryptedConnection && JsonTranslator::getPeerSessionKey(messageBody, peerSessionKey) && JsonTranslator::getOwnSessionKey(messageBody, ownSessionKey)) - { - encryptedConnection->setPeerSessionKey(peerSessionKey); - encryptedConnection->setOwnSessionKey(ownSessionKey); + encryptedConnection->setPeerSessionKey(peerSessionKey); + encryptedConnection->setOwnSessionKey(ownSessionKey); + + if(messageHeader == encryptedConnectionInfoHeader) _ongoingPeerRequestResult = ECS_CONNECTION_ESTABLISHED; - } + else if(messageHeader == softLimitEncryptedConnectionInfoHeader) + _ongoingPeerRequestResult = ECS_SOFT_LIMIT_CONNECTION_ESTABLISHED; else - { - _ongoingPeerRequestResult = ECS_REQUEST_TRANSMISSION_FAILED; - } - - _ongoingPeerRequestNonce = ""; + assert(false && "Unknown _ongoingPeerRequestResult!"); } - } - else if(messageHeader == maxConnectionsReachedHeader) - { - if(JsonTranslator::verifyHmac(message, _ongoingPeerRequester->getEspnowHashKey(), espnowHashKeyLength)) + else { - _ongoingPeerRequestResult = ECS_MAX_CONNECTIONS_REACHED_PEER; - _ongoingPeerRequestNonce = ""; + _ongoingPeerRequestResult = ECS_REQUEST_TRANSMISSION_FAILED; } + + _ongoingPeerRequestNonce = ""; } - else + } + else if(messageHeader == maxConnectionsReachedHeader) + { + if(JsonTranslator::verifyEncryptionRequestHmac(message, macaddr, apMacArray, _ongoingPeerRequester->getEspnowHashKey(), espnowHashKeyLength)) { - assert(messageHeader == basicConnectionInfoHeader || messageHeader == encryptedConnectionInfoHeader || messageHeader == maxConnectionsReachedHeader); + _ongoingPeerRequestResult = ECS_MAX_CONNECTIONS_REACHED_PEER; + _ongoingPeerRequestNonce = ""; } } + else + { + assert(messageHeader == basicConnectionInfoHeader || messageHeader == encryptedConnectionInfoHeader || + messageHeader == softLimitEncryptedConnectionInfoHeader || messageHeader == maxConnectionsReachedHeader); + } } } } -void EspnowMeshBackend::setEspnowRequestManager(EspnowMeshBackend *espnowMeshInstance) -{ - _espnowRequestManager = espnowMeshInstance; -} - -EspnowMeshBackend *EspnowMeshBackend::getEspnowRequestManager() {return _espnowRequestManager;} +void EspnowMeshBackend::espnowReceiveCallback(uint8_t *macaddr, uint8_t *dataArray, uint8_t len) +{ + using namespace EspnowProtocolInterpreter; + + ////// ////// + /* + if(messageStart) + { + storeTransmission + } + else + { + if(messageFound) + storeTransmission or (erase and return) + else + return + } + + if(transmissionsRemaining != 0) + return + + processMessage + */ + ////// ////// -bool EspnowMeshBackend::isEspnowRequestManager() -{ - return (this == getEspnowRequestManager()); -} + char messageType = espnowGetMessageType(dataArray); + uint8_t transmissionsRemaining = espnowGetTransmissionsRemaining(dataArray); + uint64_t uint64Mac = macToUint64(macaddr); + + // The MAC is 6 bytes so two bytes of uint64Mac are free. We must include the messageType there since it is possible that we will + // receive both a request and a response that shares the same messageID from the same uint64Mac, being distinguished only by the messageType. + // This would otherwise potentially cause the request and response to be mixed into one message when they are multi-part transmissions sent roughly at the same time. + macAndType_td macAndType = createMacAndTypeValue(uint64Mac, messageType); + uint64_t messageID = espnowGetMessageID(dataArray); + + //uint32_t methodStart = millis(); -bool EspnowMeshBackend::activateEspnow() -{ - if (esp_now_init()==0) - { - if(_espnowEncryptionKokSet && esp_now_set_kok(_espnowEncryptionKok, espnowEncryptionKeyLength)) // esp_now_set_kok returns 0 on success. - warningPrint("Failed to set ESP-NOW KoK!"); - - if(getEspnowRequestManager() == nullptr) + if(espnowIsMessageStart(dataArray)) + { + if(messageType == 'B') { - setEspnowRequestManager(this); + String message = espnowGetMessageContent(dataArray, len); + setSenderMac(macaddr); + setReceivedEncryptedMessage(usesEncryption(messageID)); + bool acceptBroadcast = getBroadcastFilter()(message, *this); + if(acceptBroadcast) + { + // Does nothing if key already in receivedEspnowTransmissions + receivedEspnowTransmissions.insert(std::make_pair(std::make_pair(macAndType, messageID), MessageData(message, espnowGetTransmissionsRemaining(dataArray)))); + } + else + { + return; + } } + else + { + // Does nothing if key already in receivedEspnowTransmissions + receivedEspnowTransmissions.insert(std::make_pair(std::make_pair(macAndType, messageID), MessageData(dataArray, len))); + } + } + else + { + std::map, MessageData>::iterator storedMessageIterator = receivedEspnowTransmissions.find(std::make_pair(macAndType, messageID)); - esp_now_register_recv_cb(espnowReceiveCallbackWrapper); - esp_now_register_send_cb([](uint8_t* mac, uint8_t sendStatus) { - if(_espnowSendConfirmed) - return; - else if(!sendStatus && macEqual(mac, _transmissionTargetBSSID)) // sendStatus == 0 when send was OK. - _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. - }); + if(storedMessageIterator != receivedEspnowTransmissions.end()) // If we have not stored the key already, we missed the first message part. + { + if(!storedMessageIterator->second.addToMessage(dataArray, len)) + { + // If we received the wrong message part, remove the whole message if we have missed a part. + // Otherwise just ignore the received part since it has already been stored. + + uint8_t transmissionsRemainingExpected = storedMessageIterator->second.getTransmissionsRemaining() - 1; + + if(transmissionsRemaining < transmissionsRemainingExpected) + { + receivedEspnowTransmissions.erase(storedMessageIterator); + return; + } + } + } + else + { + return; + } + } + + //Serial.println("methodStart storage done " + String(millis() - methodStart)); + + if(transmissionsRemaining != 0) + { + return; + } - // Role must be set before adding peers. Cannot be changed while having peers. - // With ESP_NOW_ROLE_CONTROLLER, we always transmit from the station interface, which gives predictability. - if(esp_now_set_self_role(ESP_NOW_ROLE_CONTROLLER)) // esp_now_set_self_role returns 0 on success. - warningPrint("Failed to set ESP-NOW role! Maybe ESP-NOW peers are already added?"); + std::map, MessageData>::iterator storedMessageIterator = receivedEspnowTransmissions.find(std::make_pair(macAndType, messageID)); + assert(storedMessageIterator != receivedEspnowTransmissions.end()); - verboseModePrint("ESP-NOW activated."); - verboseModePrint("My ESP-NOW STA MAC: " + WiFi.macAddress() + "\n"); // Get the station MAC address. The softAP MAC is different. - - return true; - } - else + // Copy totalMessage in case user callbacks (request/responseHandler) do something odd with receivedEspnowTransmissions list. + String totalMessage = storedMessageIterator->second.getTotalMessage(); // https://stackoverflow.com/questions/134731/returning-a-const-reference-to-an-object-instead-of-a-copy It is likely that most compilers will perform Named Value Return Value Optimisation in this case + + receivedEspnowTransmissions.erase(storedMessageIterator); // Erase the extra copy of the totalMessage, to save RAM. + + //Serial.println("methodStart erase done " + String(millis() - methodStart)); + + if(messageType == 'Q' || messageType == 'B') // Question (request) or Broadcast { - warningPrint("ESP-NOW init failed!"); - return false; + storeReceivedRequest(uint64Mac, messageID, TimeTracker(millis())); + //Serial.println("methodStart request stored " + String(millis() - methodStart)); + + setSenderMac(macaddr); + setReceivedEncryptedMessage(usesEncryption(messageID)); + String response = getRequestHandler()(totalMessage, *this); + //Serial.println("methodStart response acquired " + String(millis() - methodStart)); + + if(response.length() > 0) + { + responsesToSend.push_back(ResponseData(response, macaddr, messageID)); + + //Serial.println("methodStart Q done " + String(millis() - methodStart)); + } } -} - -bool EspnowMeshBackend::deactivateEspnow() -{ - // esp_now_deinit() clears all ESP-NOW API settings, including receive callback, send callback, Kok and peers. - // The node will however continue to give acks to received ESP-NOW transmissions as long as the receiving interface (AP or STA) is active, even though the transmissions will not be processed. - if(esp_now_deinit() == 0) + else if(messageType == 'A') // Answer (response) { - responsesToSend.clear(); - peerRequestConfirmationsToSend.clear(); - receivedEspnowTransmissions.clear(); - sentRequests.clear(); - receivedRequests.clear(); - encryptedConnections.clear(); - EncryptedConnectionLog::setNewRemovalsScheduled(false); + deleteSentRequest(uint64Mac, messageID); // Request has been answered, so stop accepting new answers about it. + + if(EncryptedConnectionLog *encryptedConnection = getEncryptedConnection(macaddr)) + { + if(encryptedConnection->getOwnSessionKey() == messageID) + { + encryptedConnection->setDesync(false); // We just received an answer to the latest request we sent to the node, so the node sending the answer must now be in sync. + encryptedConnection->incrementOwnSessionKey(); + } + } - return true; + setSenderMac(macaddr); + setReceivedEncryptedMessage(usesEncryption(messageID)); + getResponseHandler()(totalMessage, *this); } else { - return false; + assert(messageType == 'Q' || messageType == 'A' || messageType == 'B'); } -} - -uint32_t EspnowMeshBackend::logEntryLifetimeMs() -{ - return _logEntryLifetimeMs; -} + + ESP.wdtFeed(); // Prevents WDT reset in case we receive a lot of transmissions without break. -uint32_t EspnowMeshBackend::broadcastResponseTimeoutMs() -{ - return _broadcastResponseTimeoutMs; + //Serial.println("methodStart wdtFeed done " + String(millis() - methodStart)); } -void EspnowMeshBackend::setCriticalHeapLevelBuffer(uint32_t bufferInBytes) +void EspnowMeshBackend::setEspnowRequestManager(EspnowMeshBackend *espnowMeshInstance) { - _criticalHeapLevelBuffer = bufferInBytes; + _espnowRequestManager = espnowMeshInstance; } -uint32_t EspnowMeshBackend::criticalHeapLevelBuffer() -{ - return _criticalHeapLevelBuffer; -} +EspnowMeshBackend *EspnowMeshBackend::getEspnowRequestManager() {return _espnowRequestManager;} -template -void EspnowMeshBackend::deleteExpiredLogEntries(std::map, T> &logEntries, uint32_t maxEntryLifetimeMs) +bool EspnowMeshBackend::isEspnowRequestManager() { - for(typename std::map, T>::iterator entryIterator = logEntries.begin(); - entryIterator != logEntries.end(); ) - { - if(entryIterator->second.timeSinceCreation() > maxEntryLifetimeMs) - { - entryIterator = logEntries.erase(entryIterator); - } - else - ++entryIterator; - } + return (this == getEspnowRequestManager()); } -void EspnowMeshBackend::deleteExpiredLogEntries(std::map, RequestData> &logEntries, uint32_t requestLifetimeMs, uint32_t broadcastLifetimeMs) +bool EspnowMeshBackend::encryptedConnectionEstablished(encrypted_connection_status_t connectionStatus) { - for(typename std::map, RequestData>::iterator entryIterator = logEntries.begin(); - entryIterator != logEntries.end(); ) - { - bool broadcast = entryIterator->first.first == uint64BroadcastMac; - uint32_t timeSinceCreation = entryIterator->second.timeSinceCreation(); - - if((!broadcast && timeSinceCreation > requestLifetimeMs) - || (broadcast && timeSinceCreation > broadcastLifetimeMs)) - { - entryIterator = logEntries.erase(entryIterator); - } - else - ++entryIterator; - } + return connectionStatus > 0; } -template -void EspnowMeshBackend::deleteExpiredLogEntries(std::list &logEntries, uint32_t maxEntryLifetimeMs) +uint32_t EspnowMeshBackend::logEntryLifetimeMs() { - for(typename std::list::iterator entryIterator = logEntries.begin(); - entryIterator != logEntries.end(); ) - { - if(entryIterator->timeSinceCreation() > maxEntryLifetimeMs) - { - entryIterator = logEntries.erase(entryIterator); - } - else - ++entryIterator; - } + return _logEntryLifetimeMs; } -template <> -void EspnowMeshBackend::deleteExpiredLogEntries(std::list &logEntries, uint32_t maxEntryLifetimeMs) +uint32_t EspnowMeshBackend::broadcastResponseTimeoutMs() { - for(typename std::list::iterator entryIterator = logEntries.begin(); - entryIterator != logEntries.end(); ) - { - auto timeTrackerPointer = entryIterator->temporary(); - if(timeTrackerPointer && timeTrackerPointer->timeSinceCreation() > maxEntryLifetimeMs) - { - entryIterator = logEntries.erase(entryIterator); - } - else - ++entryIterator; - } + return _broadcastResponseTimeoutMs; } -template <> -void EspnowMeshBackend::deleteExpiredLogEntries(std::list &logEntries, uint32_t maxEntryLifetimeMs) +void EspnowMeshBackend::setCriticalHeapLevelBuffer(uint32_t bufferInBytes) { - for(typename std::list::iterator entryIterator = logEntries.begin(); - entryIterator != logEntries.end(); ) - { - auto timeTrackerPointer = entryIterator->temporary(); - if(timeTrackerPointer && timeTrackerPointer->timeSinceCreation() > maxEntryLifetimeMs) - { - entryIterator = logEntries.erase(entryIterator); - } - else - ++entryIterator; - } + _criticalHeapLevelBuffer = bufferInBytes; } -void EspnowMeshBackend::clearOldLogEntries() +uint32_t EspnowMeshBackend::criticalHeapLevelBuffer() { - // Clearing all old log entries at the same time should help minimize heap fragmentation. - - // uint32_t startTime = millis(); - - _timeOfLastLogClear = millis(); - - deleteExpiredLogEntries(receivedEspnowTransmissions, logEntryLifetimeMs()); - deleteExpiredLogEntries(receivedRequests, logEntryLifetimeMs()); // Just needs to be long enough to not accept repeated transmissions by mistake. - deleteExpiredLogEntries(sentRequests, logEntryLifetimeMs(), broadcastResponseTimeoutMs()); - deleteExpiredLogEntries(responsesToSend, logEntryLifetimeMs()); - deleteExpiredLogEntries(peerRequestConfirmationsToSend, getEncryptionRequestTimeout()); + return _criticalHeapLevelBuffer; } template @@ -807,6 +1025,8 @@ transmission_status_t EspnowMeshBackend::espnowSendToNode(const String &message, uint8_t encryptedMac[6] {0}; encryptedConnection->getEncryptedPeerMac(encryptedMac); + assert(esp_now_is_peer_exist(encryptedMac) > 0 && "ERROR! Attempting to send content marked as encrypted via unencrypted connection!"); + if(encryptedConnection->desync()) { espnowSendToNodeUnsynchronized(synchronizationRequestHeader, encryptedMac, 'S', generateMessageID(encryptedConnection), espnowInstance); @@ -987,6 +1207,7 @@ transmission_status_t EspnowMeshBackend::sendResponse(const String &message, uin if(encryptedConnection) { encryptedConnection->getEncryptedPeerMac(encryptedMac); + assert(esp_now_is_peer_exist(encryptedMac) > 0 && "ERROR! Attempting to send content marked as encrypted via unencrypted connection!"); } return espnowSendToNodeUnsynchronized(message, encryptedConnection ? encryptedMac : targetBSSID, 'A', requestID, this); @@ -1004,154 +1225,6 @@ uint64_t EspnowMeshBackend::macAndTypeToUint64Mac(const macAndType_td &macAndTyp return static_cast(macAndTypeValue) >> 8; } -void EspnowMeshBackend::espnowReceiveCallback(uint8_t *macaddr, uint8_t *dataArray, uint8_t len) -{ - using namespace EspnowProtocolInterpreter; - - ////// ////// - /* - if(messageStart) - { - storeTransmission - } - else - { - if(messageFound) - storeTransmission or (erase and return) - else - return - } - - if(transmissionsRemaining != 0) - return - - processMessage - */ - ////// ////// - - char messageType = espnowGetMessageType(dataArray); - uint8_t transmissionsRemaining = espnowGetTransmissionsRemaining(dataArray); - uint64_t uint64Mac = macToUint64(macaddr); - - // The MAC is 6 bytes so two bytes of uint64Mac are free. We must include the messageType there since it is possible that we will - // receive both a request and a response that shares the same messageID from the same uint64Mac, being distinguished only by the messageType. - // This would otherwise potentially cause the request and response to be mixed into one message when they are multi-part transmissions sent roughly at the same time. - macAndType_td macAndType = createMacAndTypeValue(uint64Mac, messageType); - uint64_t messageID = espnowGetMessageID(dataArray); - - //uint32_t methodStart = millis(); - - if(espnowIsMessageStart(dataArray)) - { - if(messageType == 'B') - { - String message = espnowGetMessageContent(dataArray, len); - setSenderMac(macaddr); - setReceivedEncryptedMessage(usesEncryption(messageID)); - bool acceptBroadcast = getBroadcastFilter()(message, *this); - if(acceptBroadcast) - { - // Does nothing if key already in receivedEspnowTransmissions - receivedEspnowTransmissions.insert(std::make_pair(std::make_pair(macAndType, messageID), MessageData(message, espnowGetTransmissionsRemaining(dataArray)))); - } - else - { - return; - } - } - else - { - // Does nothing if key already in receivedEspnowTransmissions - receivedEspnowTransmissions.insert(std::make_pair(std::make_pair(macAndType, messageID), MessageData(dataArray, len))); - } - } - else - { - std::map, MessageData>::iterator storedMessageIterator = receivedEspnowTransmissions.find(std::make_pair(macAndType, messageID)); - - if(storedMessageIterator != receivedEspnowTransmissions.end()) // If we have not stored the key already, we missed the first message part. - { - if(!storedMessageIterator->second.addToMessage(dataArray, len)) - { - // If we received the wrong message part, remove the whole message if we have missed a part. - // Otherwise just ignore the received part since it has already been stored. - - uint8_t transmissionsRemainingExpected = storedMessageIterator->second.getTransmissionsRemaining() - 1; - - if(transmissionsRemaining < transmissionsRemainingExpected) - { - receivedEspnowTransmissions.erase(storedMessageIterator); - return; - } - } - } - else - { - return; - } - } - - //Serial.println("methodStart storage done " + String(millis() - methodStart)); - - if(transmissionsRemaining != 0) - { - return; - } - - std::map, MessageData>::iterator storedMessageIterator = receivedEspnowTransmissions.find(std::make_pair(macAndType, messageID)); - assert(storedMessageIterator != receivedEspnowTransmissions.end()); - - // Copy totalMessage in case user callbacks (request/responseHandler) do something odd with receivedEspnowTransmissions list. - String totalMessage = storedMessageIterator->second.getTotalMessage(); // https://stackoverflow.com/questions/134731/returning-a-const-reference-to-an-object-instead-of-a-copy It is likely that most compilers will perform Named Value Return Value Optimisation in this case - - receivedEspnowTransmissions.erase(storedMessageIterator); // Erase the extra copy of the totalMessage, to save RAM. - - //Serial.println("methodStart erase done " + String(millis() - methodStart)); - - if(messageType == 'Q' || messageType == 'B') // Question (request) or Broadcast - { - storeReceivedRequest(uint64Mac, messageID, TimeTracker(millis())); - //Serial.println("methodStart request stored " + String(millis() - methodStart)); - - setSenderMac(macaddr); - setReceivedEncryptedMessage(usesEncryption(messageID)); - String response = getRequestHandler()(totalMessage, *this); - //Serial.println("methodStart response acquired " + String(millis() - methodStart)); - - if(response.length() > 0) - { - responsesToSend.push_back(ResponseData(response, macaddr, messageID)); - - //Serial.println("methodStart Q done " + String(millis() - methodStart)); - } - } - else if(messageType == 'A') // Answer (response) - { - deleteSentRequest(uint64Mac, messageID); // Request has been answered, so stop accepting new answers about it. - - if(EncryptedConnectionLog *encryptedConnection = getEncryptedConnection(macaddr)) - { - if(encryptedConnection->getOwnSessionKey() == messageID) - { - encryptedConnection->setDesync(false); // We just received an answer to the latest request we sent to the node, so the node sending the answer must now be in sync. - encryptedConnection->incrementOwnSessionKey(); - } - } - - setSenderMac(macaddr); - setReceivedEncryptedMessage(usesEncryption(messageID)); - getResponseHandler()(totalMessage, *this); - } - else - { - assert(messageType == 'Q' || messageType == 'A' || messageType == 'B'); - } - - ESP.wdtFeed(); // Prevents WDT reset in case we receive a lot of transmissions without break. - - //Serial.println("methodStart wdtFeed done " + String(millis() - methodStart)); -} - void EspnowMeshBackend::setEspnowEncryptionKey(const uint8_t espnowEncryptionKey[espnowEncryptionKeyLength]) { assert(espnowEncryptionKey != nullptr); @@ -1325,6 +1398,11 @@ uint8_t *EspnowMeshBackend::getSenderMac(uint8_t *macArray) void EspnowMeshBackend::setReceivedEncryptedMessage(bool receivedEncryptedMessage) { _receivedEncryptedMessage = receivedEncryptedMessage; } bool EspnowMeshBackend::receivedEncryptedMessage() {return _receivedEncryptedMessage;} +bool EspnowMeshBackend::addUnencryptedConnection(const String &serializedConnectionState) +{ + return JsonTranslator::getUnencryptedMessageID(serializedConnectionState, _unencryptedMessageID); +} + encrypted_connection_status_t EspnowMeshBackend::addEncryptedConnection(uint8_t *peerStaMac, uint8_t *peerApMac, uint64_t peerSessionKey, uint64_t ownSessionKey) { assert(encryptedConnections.size() <= maxEncryptedConnections); // If this is not the case, ESP-NOW is no longer in sync with the library @@ -1508,6 +1586,8 @@ encrypted_connection_status_t EspnowMeshBackend::requestEncryptedConnectionKerne _ongoingPeerRequestResult = ECS_REQUEST_TRANSMISSION_FAILED; _ongoingPeerRequestNonce = requestNonce; _ongoingPeerRequester = this; + _reciprocalPeerRequestConfirmation = false; + std::copy_n(peerMac, 6, _ongoingPeerRequestMac); String requestMessage = encryptionRequestBuilder(requestNonce, existingTimeTracker); verboseModePrint("Sending encrypted connection request to: " + macToString(peerMac)); @@ -1519,6 +1599,9 @@ encrypted_connection_status_t EspnowMeshBackend::requestEncryptedConnectionKerne // _ongoingPeerRequestNonce is set to "" when a peer confirmation response from the mac is received while(millis() - startTime < getEncryptionRequestTimeout() && _ongoingPeerRequestNonce != "") { + // For obvious reasons dividing by exactly 10 is a good choice. + ExpiringTimeTracker maxDurationTracker = ExpiringTimeTracker(getEncryptionRequestTimeout()/10); + sendPeerRequestConfirmations(&maxDurationTracker); // Must be called before delay() to ensure _ongoingPeerRequestNonce != "" is still true, so reciprocal peer request order is preserved. delay(1); } } @@ -1529,9 +1612,17 @@ encrypted_connection_status_t EspnowMeshBackend::requestEncryptedConnectionKerne _ongoingPeerRequestResult = ECS_REQUEST_TRANSMISSION_FAILED; _ongoingPeerRequestNonce = ""; } - else if(_ongoingPeerRequestResult == ECS_CONNECTION_ESTABLISHED) + else if(encryptedConnectionEstablished(_ongoingPeerRequestResult)) { - requestMessage = encryptionRequestBuilder(requestNonce, existingTimeTracker); // Give the builder a chance to update the message + if(_ongoingPeerRequestResult == ECS_CONNECTION_ESTABLISHED) + // Give the builder a chance to update the message + requestMessage = encryptionRequestBuilder(requestNonce, existingTimeTracker); + else if(_ongoingPeerRequestResult == ECS_SOFT_LIMIT_CONNECTION_ESTABLISHED) + // We will only get a soft limit connection. Adjust future actions based on this. + requestMessage = JsonTranslator::createEncryptionRequestHmacMessage(temporaryEncryptionRequestHeader, requestNonce, getEspnowHashKey(), + espnowHashKeyLength, getAutoEncryptionDuration()); + else + assert(false && "Unknown _ongoingPeerRequestResult during encrypted connection finalization!"); int32_t messageHeaderEndIndex = requestMessage.indexOf(':'); String messageHeader = requestMessage.substring(0, messageHeaderEndIndex + 1); @@ -1542,12 +1633,17 @@ encrypted_connection_status_t EspnowMeshBackend::requestEncryptedConnectionKerne && millis() - _ongoingPeerRequestEncryptionStart < getEncryptionRequestTimeout()) { EncryptedConnectionLog *encryptedConnection = getEncryptedConnection(peerMac); - if(!encryptedConnection || encryptedConnection->removalScheduled()) + if(!encryptedConnection) { assert(encryptedConnection && "requestEncryptedConnectionKernel cannot find an encrypted connection!"); // requestEncryptedConnectionRemoval received. _ongoingPeerRequestResult = ECS_REQUEST_TRANSMISSION_FAILED; } + else if(encryptedConnection->removalScheduled() || (encryptedConnection->temporary() && encryptedConnection->temporary()->expired())) + { + // Could possibly be caused by a simultaneous temporary peer request from the peer. + _ongoingPeerRequestResult = ECS_REQUEST_TRANSMISSION_FAILED; + } else { // Finalize connection @@ -1557,7 +1653,7 @@ encrypted_connection_status_t EspnowMeshBackend::requestEncryptedConnectionKerne } else if(messageHeader == temporaryEncryptionRequestHeader) { - if(!existingEncryptedConnection || existingEncryptedConnection->temporary()) + if(encryptedConnection->temporary()) { // Should not change duration of existing permanent connections. uint32_t connectionDuration = 0; @@ -1579,11 +1675,11 @@ encrypted_connection_status_t EspnowMeshBackend::requestEncryptedConnectionKerne } } - if(_ongoingPeerRequestResult != ECS_CONNECTION_ESTABLISHED) + if(!encryptedConnectionEstablished(_ongoingPeerRequestResult)) { - if(!existingEncryptedConnection) + if(!existingEncryptedConnection && !_reciprocalPeerRequestConfirmation) { - // Remove any connection that was added during the request attempt. + // Remove any connection that was added during the request attempt and is no longer in use. removeEncryptedConnectionUnprotected(peerMac); } } @@ -1593,31 +1689,16 @@ encrypted_connection_status_t EspnowMeshBackend::requestEncryptedConnectionKerne return _ongoingPeerRequestResult; } -String EspnowMeshBackend::defaultEncryptionRequestBuilder(const String &requestHeader, const uint32_t durationMs, +String EspnowMeshBackend::defaultEncryptionRequestBuilder(const String &requestHeader, const uint32_t durationMs, const uint8_t *hashKey, const String &requestNonce, const ExpiringTimeTracker &existingTimeTracker) { - using namespace JsonTranslator; - using EspnowProtocolInterpreter::temporaryEncryptionRequestHeader; - (void)existingTimeTracker; // This removes a "unused parameter" compiler warning. Does nothing else. - - String requestMessage = ""; - - if(requestHeader == temporaryEncryptionRequestHeader) - { - requestMessage += createEncryptionRequestIntro(requestHeader, durationMs); - } - else - { - requestMessage += createEncryptionRequestIntro(requestHeader); - } - - requestMessage += createEncryptionRequestEnding(requestNonce); - return requestMessage; + return JsonTranslator::createEncryptionRequestHmacMessage(requestHeader, requestNonce, hashKey, espnowHashKeyLength, durationMs); } -String EspnowMeshBackend::flexibleEncryptionRequestBuilder(const uint32_t minDurationMs, const String &requestNonce, const ExpiringTimeTracker &existingTimeTracker) +String EspnowMeshBackend::flexibleEncryptionRequestBuilder(const uint32_t minDurationMs, const uint8_t *hashKey, + const String &requestNonce, const ExpiringTimeTracker &existingTimeTracker) { using namespace JsonTranslator; using EspnowProtocolInterpreter::temporaryEncryptionRequestHeader; @@ -1625,25 +1706,26 @@ String EspnowMeshBackend::flexibleEncryptionRequestBuilder(const uint32_t minDur uint32_t connectionDuration = minDurationMs >= existingTimeTracker.remainingDuration() ? minDurationMs : existingTimeTracker.remainingDuration(); - return createEncryptionRequestIntro(temporaryEncryptionRequestHeader, connectionDuration) + createEncryptionRequestEnding(requestNonce); + return createEncryptionRequestHmacMessage(temporaryEncryptionRequestHeader, requestNonce, hashKey, espnowHashKeyLength, connectionDuration); } encrypted_connection_status_t EspnowMeshBackend::requestEncryptedConnection(uint8_t *peerMac) { using namespace std::placeholders; - return requestEncryptedConnectionKernel(peerMac, std::bind(defaultEncryptionRequestBuilder, EspnowProtocolInterpreter::encryptionRequestHeader, 0, _1, _2)); + return requestEncryptedConnectionKernel(peerMac, std::bind(defaultEncryptionRequestBuilder, EspnowProtocolInterpreter::encryptionRequestHeader, 0, getEspnowHashKey(), _1, _2)); } encrypted_connection_status_t EspnowMeshBackend::requestTemporaryEncryptedConnection(uint8_t *peerMac, uint32_t durationMs) { using namespace std::placeholders; - return requestEncryptedConnectionKernel(peerMac, std::bind(defaultEncryptionRequestBuilder, EspnowProtocolInterpreter::temporaryEncryptionRequestHeader, durationMs, _1, _2)); + return requestEncryptedConnectionKernel(peerMac, std::bind(defaultEncryptionRequestBuilder, EspnowProtocolInterpreter::temporaryEncryptionRequestHeader, + durationMs, getEspnowHashKey(), _1, _2)); } encrypted_connection_status_t EspnowMeshBackend::requestFlexibleTemporaryEncryptedConnection(uint8_t *peerMac, uint32_t minDurationMs) { using namespace std::placeholders; - return requestEncryptedConnectionKernel(peerMac, std::bind(flexibleEncryptionRequestBuilder, minDurationMs, _1, _2)); + return requestEncryptedConnectionKernel(peerMac, std::bind(flexibleEncryptionRequestBuilder, minDurationMs, getEspnowHashKey(), _1, _2)); } bool EspnowMeshBackend::temporaryEncryptedConnectionToPermanent(uint8_t *peerMac) @@ -1831,6 +1913,14 @@ encrypted_connection_removal_outcome_t EspnowMeshBackend::requestEncryptedConnec void EspnowMeshBackend::setAcceptsUnencryptedRequests(bool acceptsUnencryptedRequests) { _acceptsUnencryptedRequests = acceptsUnencryptedRequests; } bool EspnowMeshBackend::acceptsUnencryptedRequests() { return _acceptsUnencryptedRequests; } +void EspnowMeshBackend::setEncryptedConnectionsSoftLimit(uint8_t softLimit) +{ + assert(softLimit <= 6); // Valid values are 0 to 6, but uint8_t is always at least 0. + _encryptedConnectionsSoftLimit = softLimit; +} + +uint8_t EspnowMeshBackend::encryptedConnectionsSoftLimit() { return _encryptedConnectionsSoftLimit; } + template typename std::vector::iterator EspnowMeshBackend::getEncryptedConnectionIterator(const uint8_t *peerMac, typename std::vector &connectionVector) { @@ -1993,12 +2083,23 @@ void EspnowMeshBackend::attemptTransmission(const String &message, bool scan, bo } prepareForTransmission(message, scan, scanAllWiFiChannels); - - for(EspnowNetworkInfo ¤tNetwork : connectionQueue()) - { - transmission_status_t transmissionResult = initiateTransmission(getMessage(), currentNetwork); - latestTransmissionOutcomes().push_back(TransmissionOutcome{.origin = currentNetwork, .transmissionStatus = transmissionResult}); + MutexTracker connectionQueueMutexTracker(_espnowConnectionQueueMutex); + if(!connectionQueueMutexTracker.mutexCaptured()) + { + assert(false && "ERROR! connectionQueue locked. Don't call attemptTransmission from callbacks as this may corrupt program state! Aborting."); + } + else + { + for(const EspnowNetworkInfo ¤tNetwork : constConnectionQueue()) + { + transmission_status_t transmissionResult = initiateTransmission(getMessage(), currentNetwork); + + latestTransmissionOutcomes().push_back(TransmissionOutcome{.origin = currentNetwork, .transmissionStatus = transmissionResult}); + + if(!getTransmissionOutcomesUpdateHook()(*this)) + break; + } } printTransmissionStatistics(); @@ -2016,7 +2117,7 @@ transmission_status_t EspnowMeshBackend::attemptTransmission(const String &messa return initiateTransmission(message, recipientInfo); } -encrypted_connection_status_t EspnowMeshBackend::initiateAutoEncryptingConnection(const EspnowNetworkInfo &recipientInfo, bool createPermanentConnection, uint8_t *targetBSSID, EncryptedConnectionLog **encryptedConnection) +encrypted_connection_status_t EspnowMeshBackend::initiateAutoEncryptingConnection(const EspnowNetworkInfo &recipientInfo, bool requestPermanentConnection, uint8_t *targetBSSID, EncryptedConnectionLog **existingEncryptedConnection) { assert(recipientInfo.BSSID() != nullptr); // We need at least the BSSID to connect recipientInfo.getBSSID(targetBSSID); @@ -2027,10 +2128,10 @@ encrypted_connection_status_t EspnowMeshBackend::initiateAutoEncryptingConnectio verboseModePrint(F("")); } - *encryptedConnection = getEncryptedConnection(targetBSSID); + *existingEncryptedConnection = getEncryptedConnection(targetBSSID); encrypted_connection_status_t connectionStatus = ECS_MAX_CONNECTIONS_REACHED_SELF; - if(createPermanentConnection) + if(requestPermanentConnection) connectionStatus = requestEncryptedConnection(targetBSSID); else connectionStatus = requestFlexibleTemporaryEncryptedConnection(targetBSSID, getAutoEncryptionDuration()); @@ -2042,64 +2143,77 @@ transmission_status_t EspnowMeshBackend::initiateAutoEncryptingTransmission(cons { transmission_status_t transmissionResult = TS_CONNECTION_FAILED; - if(connectionStatus == ECS_CONNECTION_ESTABLISHED) + if(encryptedConnectionEstablished(connectionStatus)) { + uint8_t encryptedMac[6] {0}; + assert(getEncryptedMac(targetBSSID, encryptedMac) && esp_now_is_peer_exist(encryptedMac) > 0 && "ERROR! Attempting to send content marked as encrypted via unencrypted connection!"); transmissionResult = initiateTransmissionKernel(message, targetBSSID); } return transmissionResult; } -void EspnowMeshBackend::finalizeAutoEncryptingConnection(const uint8_t *targetBSSID, const EncryptedConnectionLog *encryptedConnection, bool createPermanentConnection) +void EspnowMeshBackend::finalizeAutoEncryptingConnection(const uint8_t *targetBSSID, const EncryptedConnectionLog *existingEncryptedConnection, bool requestPermanentConnection) { - if(!encryptedConnection && !createPermanentConnection) + if(!existingEncryptedConnection && !requestPermanentConnection && !_reciprocalPeerRequestConfirmation) { - // Remove any connection that was added during the transmission attempt. + // Remove any connection that was added during the transmission attempt and is no longer in use. removeEncryptedConnectionUnprotected(targetBSSID); } } -void EspnowMeshBackend::attemptAutoEncryptingTransmission(const String &message, bool scan, bool scanAllWiFiChannels, bool createPermanentConnections) +void EspnowMeshBackend::attemptAutoEncryptingTransmission(const String &message, bool requestPermanentConnections, bool scan, bool scanAllWiFiChannels) { MutexTracker outerMutexTracker(_espnowTransmissionMutex, handlePostponedRemovals); if(!outerMutexTracker.mutexCaptured()) { - assert(false && "ERROR! Transmission in progress. Don't call attemptTransmission from callbacks as this may corrupt program state! Aborting."); + assert(false && "ERROR! Transmission in progress. Don't call attemptAutoEncryptingTransmission from callbacks as this may corrupt program state! Aborting."); return; } prepareForTransmission(message, scan, scanAllWiFiChannels); outerMutexTracker.releaseMutex(); - - for(EspnowNetworkInfo ¤tNetwork : connectionQueue()) - { - uint8_t currentBSSID[6] {0}; - EncryptedConnectionLog *encryptedConnection = nullptr; - encrypted_connection_status_t connectionStatus = initiateAutoEncryptingConnection(currentNetwork, createPermanentConnections, currentBSSID, &encryptedConnection); - MutexTracker innerMutexTracker = MutexTracker(_espnowTransmissionMutex); - if(!innerMutexTracker.mutexCaptured()) - { - assert(false && "ERROR! Unable to recapture Mutex in attemptAutoEncryptingTransmission. Aborting."); - return; + MutexTracker connectionQueueMutexTracker(_espnowConnectionQueueMutex); + if(!connectionQueueMutexTracker.mutexCaptured()) + { + assert(false && "ERROR! connectionQueue locked. Don't call attemptAutoEncryptingTransmission from callbacks as this may corrupt program state! Aborting."); + } + else + { + for(const EspnowNetworkInfo ¤tNetwork : constConnectionQueue()) + { + uint8_t currentBSSID[6] {0}; + EncryptedConnectionLog *existingEncryptedConnection = nullptr; + encrypted_connection_status_t connectionStatus = initiateAutoEncryptingConnection(currentNetwork, requestPermanentConnections, currentBSSID, &existingEncryptedConnection); + + MutexTracker innerMutexTracker = MutexTracker(_espnowTransmissionMutex); + if(!innerMutexTracker.mutexCaptured()) + { + assert(false && "ERROR! Unable to recapture Mutex in attemptAutoEncryptingTransmission. Aborting."); + return; + } + + transmission_status_t transmissionResult = initiateAutoEncryptingTransmission(getMessage(), currentBSSID, connectionStatus); + + latestTransmissionOutcomes().push_back(TransmissionOutcome{.origin = currentNetwork, .transmissionStatus = transmissionResult}); + + finalizeAutoEncryptingConnection(currentBSSID, existingEncryptedConnection, requestPermanentConnections); + + if(!getTransmissionOutcomesUpdateHook()(*this)) + break; } - - transmission_status_t transmissionResult = initiateAutoEncryptingTransmission(getMessage(), currentBSSID, connectionStatus); - - latestTransmissionOutcomes().push_back(TransmissionOutcome{.origin = currentNetwork, .transmissionStatus = transmissionResult}); - - finalizeAutoEncryptingConnection(currentBSSID, encryptedConnection, createPermanentConnections); } printTransmissionStatistics(); } -transmission_status_t EspnowMeshBackend::attemptAutoEncryptingTransmission(const String &message, const EspnowNetworkInfo &recipientInfo, bool createPermanentConnection) +transmission_status_t EspnowMeshBackend::attemptAutoEncryptingTransmission(const String &message, const EspnowNetworkInfo &recipientInfo, bool requestPermanentConnection) { uint8_t targetBSSID[6] {0}; - EncryptedConnectionLog *encryptedConnection = nullptr; - encrypted_connection_status_t connectionStatus = initiateAutoEncryptingConnection(recipientInfo, createPermanentConnection, targetBSSID, &encryptedConnection); + EncryptedConnectionLog *existingEncryptedConnection = nullptr; + encrypted_connection_status_t connectionStatus = initiateAutoEncryptingConnection(recipientInfo, requestPermanentConnection, targetBSSID, &existingEncryptedConnection); MutexTracker mutexTracker(_espnowTransmissionMutex, handlePostponedRemovals); if(!mutexTracker.mutexCaptured()) @@ -2110,7 +2224,7 @@ transmission_status_t EspnowMeshBackend::attemptAutoEncryptingTransmission(const transmission_status_t transmissionResult = initiateAutoEncryptingTransmission(message, targetBSSID, connectionStatus); - finalizeAutoEncryptingConnection(targetBSSID, encryptedConnection, createPermanentConnection); + finalizeAutoEncryptingConnection(targetBSSID, existingEncryptedConnection, requestPermanentConnection); return transmissionResult; } @@ -2127,20 +2241,36 @@ void EspnowMeshBackend::broadcast(const String &message) espnowSendToNode(message, broadcastMac, 'B', this); } -void EspnowMeshBackend::sendEspnowResponses() +void EspnowMeshBackend::sendStoredEspnowMessages(const ExpiringTimeTracker *estimatedMaxDurationTracker) { - //uint32_t startTime = millis(); + sendPeerRequestConfirmations(estimatedMaxDurationTracker); - uint32_t bufferedCriticalHeapLevel = criticalHeapLevel() + criticalHeapLevelBuffer(); // We preferably want to start clearing the logs a bit before things get critical. + if(estimatedMaxDurationTracker && estimatedMaxDurationTracker->expired()) + return; + + sendEspnowResponses(estimatedMaxDurationTracker); +} +void EspnowMeshBackend::sendPeerRequestConfirmations(const ExpiringTimeTracker *estimatedMaxDurationTracker) +{ + uint32_t bufferedCriticalHeapLevel = criticalHeapLevel() + criticalHeapLevelBuffer(); // We preferably want to start clearing the logs a bit before things get critical. + // _ongoingPeerRequestNonce can change during every delay(), but we need to remember the initial value to know from where sendPeerRequestConfirmations was called. + String initialOngoingPeerRequestNonce = _ongoingPeerRequestNonce; + for(std::list::iterator confirmationsIterator = peerRequestConfirmationsToSend.begin(); confirmationsIterator != peerRequestConfirmationsToSend.end(); ) { using namespace EspnowProtocolInterpreter; + + // True if confirmationsIterator contains a peer request received from the same node we are currently sending a peer request to. + bool reciprocalPeerRequest = initialOngoingPeerRequestNonce != "" && confirmationsIterator->connectedTo(_ongoingPeerRequestMac); auto timeTrackerPointer = confirmationsIterator->temporary(); assert(timeTrackerPointer); // peerRequestConfirmations should always expire and so should always have a timeTracker - if(timeTrackerPointer->timeSinceCreation() > getEncryptionRequestTimeout()) + if(timeTrackerPointer->timeSinceCreation() > getEncryptionRequestTimeout() + || (reciprocalPeerRequest && confirmationsIterator->getPeerRequestNonce() <= initialOngoingPeerRequestNonce)) { + // The peer request has expired, + // or the peer request comes from the node we are currently making a peer request to ourselves and we are supposed to wait in this event to avoid simultaneous session key transfer. ++confirmationsIterator; continue; } @@ -2164,7 +2294,8 @@ void EspnowMeshBackend::sendEspnowResponses() staticVerboseModePrint("Responding to encrypted connection request from MAC " + macToString(defaultBSSID)); - if(!existingEncryptedConnection && encryptedConnections.size() >= maxEncryptedConnections) + if(!existingEncryptedConnection && + ((reciprocalPeerRequest && encryptedConnections.size() >= maxEncryptedConnections) || (!reciprocalPeerRequest && reservedEncryptedConnections() >= maxEncryptedConnections))) { espnowSendToNodeUnsynchronized(JsonTranslator::createEncryptionRequestHmacMessage(maxConnectionsReachedHeader, confirmationsIterator->getPeerRequestNonce(), hashKey, espnowHashKeyLength), @@ -2200,7 +2331,7 @@ void EspnowMeshBackend::sendEspnowResponses() { warningPrint("WARNING! Ignoring received encrypted connection request since no EspnowRequestManager is assigned."); } - + if(!existingEncryptedConnection) { // Send "node full" message @@ -2210,11 +2341,29 @@ void EspnowMeshBackend::sendEspnowResponses() } else { - delay(1); // Give some time for the peer to add an encrypted connection + if(reciprocalPeerRequest) + _reciprocalPeerRequestConfirmation = true; + + 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!"); + + String messageHeader = ""; + + if(existingEncryptedConnection->temporary() && // Should never change permanent connections + ((reciprocalPeerRequest && encryptedConnections.size() > confirmationsIterator->getEncryptedConnectionsSoftLimit()) + || (!reciprocalPeerRequest && reservedEncryptedConnections() > confirmationsIterator->getEncryptedConnectionsSoftLimit()))) + { + messageHeader = softLimitEncryptedConnectionInfoHeader; + } + else + { + messageHeader = encryptedConnectionInfoHeader; + } // Send password and keys. // Probably no need to know which connection type to use, that is stored in request node and will be sent over for finalization. - espnowSendToNodeUnsynchronized(JsonTranslator::createEncryptedConnectionInfo( + espnowSendToNodeUnsynchronized(JsonTranslator::createEncryptedConnectionInfo(messageHeader, confirmationsIterator->getPeerRequestNonce(), confirmationsIterator->getAuthenticationPassword(), existingEncryptedConnection->getOwnSessionKey(), existingEncryptedConnection->getPeerSessionKey()), defaultBSSID, 'C', generateMessageID(nullptr)); // Generates a new message ID to avoid sending encrypted sessionKeys over unencrypted connections. @@ -2235,7 +2384,15 @@ void EspnowMeshBackend::sendEspnowResponses() clearOldLogEntries(); return; // confirmationsIterator may be invalid now. Also, we should give the main loop a chance to respond to the situation. } + + if(estimatedMaxDurationTracker && estimatedMaxDurationTracker->expired()) + return; } +} + +void EspnowMeshBackend::sendEspnowResponses(const ExpiringTimeTracker *estimatedMaxDurationTracker) +{ + uint32_t bufferedCriticalHeapLevel = criticalHeapLevel() + criticalHeapLevelBuffer(); // We preferably want to start clearing the logs a bit before things get critical. for(std::list::iterator responseIterator = responsesToSend.begin(); responseIterator != responsesToSend.end(); ) { @@ -2268,6 +2425,9 @@ void EspnowMeshBackend::sendEspnowResponses() clearOldLogEntries(); return; // responseIterator may be invalid now. Also, we should give the main loop a chance to respond to the situation. } + + if(estimatedMaxDurationTracker && estimatedMaxDurationTracker->expired()) + return; } } @@ -2300,6 +2460,15 @@ uint8_t EspnowMeshBackend::numberOfEncryptedConnections() return encryptedConnections.size(); } +uint8_t EspnowMeshBackend::reservedEncryptedConnections() +{ + if(_ongoingPeerRequestNonce != "") + 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(); +} + espnow_connection_type_t EspnowMeshBackend::getConnectionInfoHelper(const EncryptedConnectionLog *encryptedConnection, uint32_t *remainingDuration, uint8_t *peerMac) { if(!encryptedConnection) @@ -2359,6 +2528,15 @@ void EspnowMeshBackend::resetTransmissionFailRate() _transmissionsTotal = 0; } +String EspnowMeshBackend::serializeUnencryptedConnection() +{ + using namespace JsonTranslator; + + // Returns: {"connectionState":{"uMessageID":"123"}} + + return jsonConnectionState + createJsonEndPair(jsonUnencryptedMessageID, String(_unencryptedMessageID)); +} + String EspnowMeshBackend::serializeEncryptedConnection(const uint8_t *peerMac) { EncryptedConnectionLog *encryptedConnection = nullptr; diff --git a/libraries/ESP8266WiFiMesh/src/EspnowMeshBackend.h b/libraries/ESP8266WiFiMesh/src/EspnowMeshBackend.h index 1bfcc2524c..ec935d9c65 100644 --- a/libraries/ESP8266WiFiMesh/src/EspnowMeshBackend.h +++ b/libraries/ESP8266WiFiMesh/src/EspnowMeshBackend.h @@ -24,22 +24,48 @@ // but this backend has a much higher data transfer speed than ESP-NOW once connected (100x faster or so). /** - * Encryption pairing process, schematic overview: + * This ESP-NOW framework uses a few different message types to enable easier interpretation of transmissions. + * The message type is stored in the first transmission byte, see EspnowProtocolInterpreter.h for more detailed information on the protocol. + * Available message types are 'Q' for question (request), 'A' for answer (response), + * 'B' for broadcast, 'S' for synchronization request, 'P' for peer request and 'C' for peer request confirmation. + * + * 'B', 'Q' and 'A' are the message types that are assigned to data transmitted by the user. + * 'S', 'P' and 'C' are used only for internal framework transmissions. * - * Connection | Peer sends: | Peer requester sends: | Connection - * encrypted: | | | encrypted: - * | | Peer request + Nonce | - * | StaMac + Nonce + HMAC | | - * | | Ack | - * X | SessionKeys + Nonce + Password | | X - * X | | Ack | X - * X | | SessionKey | X - * X | Ack | | X - * | | | + * Messages with type 'B' are only used for broadcasts. They cannot be encrypted. * + * Messages with type 'Q' are used for requests sent by the user. They can be encrypted. * + * Messages with type 'A' are used for responses given by the user when 'B' or 'Q' messages have been received. They can be encrypted. + * + * Messages with type 'P' and 'C' are used exclusively for automatically pairing two ESP-NOW nodes to each other. + * This enables flexible easy-to-use encrypted ESP-NOW communication. 'P' and 'C' messages can be encrypted. + * The encryption pairing process works as follows (from top to bottom): + * + * Encryption pairing process, schematic overview: + * + * Connection | Peer sends ('C'): | Peer requester sends ('P'): | Connection + * encrypted: | | | encrypted: + * | | Peer request + Nonce + HMAC | + * | StaMac + Nonce + HMAC | | + * | | Ack | + * X | SessionKeys + Nonce + Password | | X + * X | | Ack | X + * X | | SessionKey | X + * X | Ack | | X + * | | | + * + * * The ESP-NOW CCMP encryption should have replay attack protection built in, * but since there is no official documentation from Espressif about this a 128 bit random nonce is included in encrypted connection requests. + * + * Messages with type 'S' are used exclusively when we try to send an encrypted 'R' or 'P' transmission and the last such transmission we tried failed to receive an ack. + * Since we then do not know if the receiving node has incremented its corresponding session key or not, we first send an 'S' request to make sure the key is incremented. + * Once we get an ack for our 'S' request we send the new encrypted 'R' or 'P' transmission. 'S' messages are always encrypted. + * + * Messages of type 'A' and 'C' are response types, and thus use the same session key as the corresponding 'R' and 'P' message they are responding to. + * This means they can never cause a desynchronization to occur, and therefore they do not trigger 'S' messages. + * */ #ifndef __ESPNOWMESHBACKEND_H__ @@ -64,13 +90,15 @@ typedef enum ECT_PERMANENT_CONNECTION = 2 } espnow_connection_type_t; +// A value greater than 0 means that an encrypted connection has been established. typedef enum { ECS_MAX_CONNECTIONS_REACHED_SELF = -3, ECS_REQUEST_TRANSMISSION_FAILED = -2, ECS_MAX_CONNECTIONS_REACHED_PEER = -1, ECS_API_CALL_FAILED = 0, - ECS_CONNECTION_ESTABLISHED = 1 + ECS_CONNECTION_ESTABLISHED = 1, + ECS_SOFT_LIMIT_CONNECTION_ESTABLISHED = 2 // Only used if _encryptedConnectionsSoftLimit is less than 6. } encrypted_connection_status_t; typedef enum @@ -87,7 +115,7 @@ typedef enum * Note that if there is a lot of ESP-NOW transmission activity to the node during the espnowDelay, the desired duration may be overshot by several ms. * Thus, if precise timing is required, use standard delay() instead. * - * Should not be used inside responseHandler, requestHandler or networkFilter callbacks since performEspnowMaintainance() can alter the ESP-NOW state. + * Should not be used inside responseHandler, requestHandler, networkFilter or broadcastFilter callbacks since performEspnowMaintainance() can alter the ESP-NOW state. * * @param durationMs The shortest allowed delay duration, in milliseconds. */ @@ -133,13 +161,20 @@ class EspnowMeshBackend : public MeshBackendBase { /** * Returns a vector that contains the NetworkInfo for each WiFi network to connect to. - * This vector is unique for each mesh backend. + * This vector is unique for each mesh backend, but NetworkInfo elements can be directly transferred between the vectors as long as both SSID and BSSID are present. * The connectionQueue vector is cleared before each new scan and filled via the networkFilter callback function once the scan completes. * WiFi connections will start with connectionQueue[0] and then incrementally proceed to higher vector positions. * Note that old network indicies often are invalidated whenever a new WiFi network scan occurs. + * + * Since the connectionQueue() is iterated over during transmissions, always use constConnectionQueue() from callbacks other than NetworkFilter. */ - std::vector & connectionQueue(); + static std::vector & connectionQueue(); + /** + * Same as connectionQueue(), but can be called from all callbacks since the returned reference is const. + */ + static const std::vector & constConnectionQueue(); + /** * Returns a vector with the TransmissionOutcome for each AP to which a transmission was attempted during the latest attemptTransmission call. * This vector is unique for each mesh backend. @@ -147,7 +182,13 @@ class EspnowMeshBackend : public MeshBackendBase { * Connection attempts are indexed in the same order they were attempted. * Note that old network indicies often are invalidated whenever a new WiFi network scan occurs. */ - std::vector & latestTransmissionOutcomes() override; + static std::vector & latestTransmissionOutcomes(); + + /** + * @return True if latest transmission was successful (i.e. latestTransmissionOutcomes is not empty and all entries have transmissionStatus TS_TRANSMISSION_COMPLETE). False otherwise. + * The result is unique for each mesh backend. + */ + static bool latestTransmissionSuccessful(); /** * Initialises the node. @@ -161,9 +202,13 @@ class EspnowMeshBackend : public MeshBackendBase { * Note that depending on the amount of responses to send and their length, this method can take tens or even hundreds of milliseconds to complete. * More intense transmission activity and less frequent calls to performEspnowMaintainance will likely cause the method to take longer to complete, so plan accordingly. * - * Should not be used inside responseHandler, requestHandler or networkFilter callbacks since performEspnowMaintainance() can alter the ESP-NOW state. + * Should not be used inside responseHandler, requestHandler, networkFilter or broadcastFilter callbacks since performEspnowMaintainance() can alter the ESP-NOW state. + * + * @param estimatedMaxDuration The desired max duration for the method. If set to 0 there is no duration limit. + * Note that setting the estimatedMaxDuration too low may result in missed ESP-NOW transmissions because of too little time for maintainance. + * Also note that although the method will try to respect the max duration limit, there is no guarantee. Overshoots by tens of milliseconds are possible. */ - static void performEspnowMaintainance(); + static void performEspnowMaintainance(uint32_t estimatedMaxDuration = 0); /** * At critical heap level no more incoming requests are accepted. @@ -200,6 +245,8 @@ class EspnowMeshBackend : public MeshBackendBase { /** * Transmit message to a single recipient without changing the local transmission state. * Will not change connectionQueue, latestTransmissionOutcomes or stored message. + * + * @param recipientInfo The recipient information. */ transmission_status_t attemptTransmission(const String &message, const EspnowNetworkInfo &recipientInfo); @@ -208,29 +255,32 @@ class EspnowMeshBackend : public MeshBackendBase { * establishing a temporary encrypted connection with duration getAutoEncryptionDuration() first if neccessary. * If an encrypted connection cannot be established to a target node, no message will be sent to that node. * Note that if an encrypted connection to a target node is not present before this method is called, the response from said node will likely not be received - * since it will be encrypted and the auto encrypted connection to the node is immediately removed after transmission (unless the createPermanentConnections argument is set to true). + * since it will be encrypted and the auto encrypted connection to the node is immediately removed after transmission (unless the requestPermanentConnections argument is set to true). * Also note that if a temporary encrypted connection already exists to a target node, this method will slightly extend the connection duration * depending on the time it takes to verify the connection to the node. This can substantially increase the connection duration if many auto encrypting * transmissions occurs. * * @param message The message to send to other nodes. It will be stored in the class instance until replaced via attemptTransmission or setMessage. + * @param requestPermanentConnections If true, the method will request that encrypted connections used for this transmission become permanent so they are not removed once the transmission is complete. + * This means that encrypted responses to the transmission are received, as long as the encrypted connection is not removed by other means. + * The receiving node has no obligation to obey the request, although it normally will. + * If encryptedConnectionsSoftLimit() is set to less than 6 for the transmission receiver, + * it is possible that a short lived autoEncryptionConnection is created instead of a permanent encrypted connection. + * Note that a maximum of 6 encrypted ESP-NOW connections can be maintained at the same time by the node. + * Defaults to false. * @param scan Scan for new networks and call the networkFilter function with the scan results. When set to false, only the data already in connectionQueue will be used for the transmission. * @param scanAllWiFiChannels Scan all WiFi channels during a WiFi scan, instead of just the channel the MeshBackendBase instance is using. * Scanning all WiFi channels takes about 2100 ms, compared to just 60 ms if only channel 1 (standard) is scanned. * Note that if the ESP8266 has an active AP, that AP will switch WiFi channel to match that of any other AP the ESP8266 connects to. * This can make it impossible for other nodes to detect the AP if they are scanning the wrong WiFi channel. - * @param createPermanentConnections Ensures encrypted connections used for this transmission are permanent and not removed once the transmission is complete. - * This guarantees that encrypted responses to the transmission is received, as long as the encrypted connection is not removed by other means. - * Note that a maximum of 6 encrypted ESP-NOW connections can be maintained at the same time by the node. - * Defaults to false. */ - void attemptAutoEncryptingTransmission(const String &message, bool scan = true, bool scanAllWiFiChannels = false, bool createPermanentConnections = false); + void attemptAutoEncryptingTransmission(const String &message, bool requestPermanentConnections = false, bool scan = true, bool scanAllWiFiChannels = false); /** * Transmit message to a single recipient without changing the local transmission state (apart from encrypted connections). * Will not change connectionQueue, latestTransmissionOutcomes or stored message. */ - transmission_status_t attemptAutoEncryptingTransmission(const String &message, const EspnowNetworkInfo &recipientInfo, bool createPermanentConnection = false); + transmission_status_t attemptAutoEncryptingTransmission(const String &message, const EspnowNetworkInfo &recipientInfo, bool requestPermanentConnection = false); /** * Send a message simultaneously to all nearby nodes which have ESP-NOW activated. @@ -475,6 +525,17 @@ class EspnowMeshBackend : public MeshBackendBase { */ bool receivedEncryptedMessage(); + /** + * Should be used together with serializeUnencryptedConnection() if the node sends unencrypted transmissions + * and will go to sleep for less than logEntryLifetimeMs() while other nodes stay awake. + * Otherwise the message ID will be reset after sleep, which means that the nodes that stayed awake may ignore new unencrypted transmissions until logEntryLifetimeMs() ms has passed. + * + * @param serializedConnectionState A serialized state of an unencrypted ESP-NOW connection. + * + * @return True if connection was added. False otherwise (e.g. if there is faulty input). + */ + static bool addUnencryptedConnection(const String &serializedConnectionState); + // Updates connection with current stored encryption key. // At least one of the leftmost 32 bits in each of the session keys should be 1, since the key otherwise indicates the connection is unencrypted. encrypted_connection_status_t addEncryptedConnection(uint8_t *peerStaMac, uint8_t *peerApMac, uint64_t peerSessionKey, uint64_t ownSessionKey); @@ -514,18 +575,44 @@ class EspnowMeshBackend : public MeshBackendBase { */ void setAcceptsUnencryptedRequests(bool acceptsUnencryptedRequests); bool acceptsUnencryptedRequests(); + + /** + * Set a soft upper limit on the number of encrypted connections this node can have when receiving encrypted connection requests. + * The soft limit can be used to ensure there is normally a pool of free encrypted connection slots that can be used if required. + * Each EspnowMeshBackend instance can have a separate value. The value used is that of the current EspnowRequestManager. + * The hard upper limit is 6 encrypted connections, mandated by the ESP-NOW API. + * + * When a request for encrypted connection is received from a node to which there is no existing permanent encrypted connection, + * and the number of encrypted connections exceeds the soft limit, + * this request will automatically be converted to an autoEncryptionRequest. + * This means it will be a temporary connection with very short duration (with default framework settings). + * + * @param softLimit The new soft limit. Valid values are 0 to 6. Default is 6. + */ + void setEncryptedConnectionsSoftLimit(uint8_t softLimit); + uint8_t encryptedConnectionsSoftLimit(); /** - * @ returns The current number of encrypted ESP-NOW connections. + * @return The current number of encrypted ESP-NOW connections. */ static uint8_t numberOfEncryptedConnections(); // @return resultArray filled with the MAC to the encrypted interface of the node, if an encrypted connection exists. nulltpr otherwise. static uint8_t *getEncryptedMac(const uint8_t *peerMac, uint8_t *resultArray); + /** + * Should be used together with addUnencryptedConnection if the node sends unencrypted transmissions + * and will go to sleep for less than logEntryLifetimeMs() while other nodes stay awake. + * Otherwise the message ID will be reset after sleep, which means that the nodes that stayed awake may ignore new unencrypted transmissions until logEntryLifetimeMs() ms has passed. + * + * @return The serialized state of the unencrypted ESP-NOW connection. + */ + static String serializeUnencryptedConnection(); + // Create a string containing the current state of the encrypted connection for this node. The result can be used as input to addEncryptedConnection. // Note that transferring the serialized state over an unencrypted connection will compromise the security of the stored connection. - // @ returns A String containing the serialized encrypted connection, or an empty String if there is no matching encrypted connection. + // Also note that this saves the current state only, so if encrypted communication between the nodes happen after this, the stored state is invalid. + // @return A String containing the serialized encrypted connection, or an empty String if there is no matching encrypted connection. static String serializeEncryptedConnection(const uint8_t *peerMac); static String serializeEncryptedConnection(uint32_t connectionIndex); @@ -537,7 +624,7 @@ class EspnowMeshBackend : public MeshBackendBase { * @param remainingDuration An optional pointer to a uint32_t variable. * If supplied and the connection type is ECT_TEMPORARY_CONNECTION the variable will be set to the remaining duration of the connection. * Otherwise the variable value is not modified. - * @ returns The espnow_connection_type_t of the connection with peerMac. + * @return The espnow_connection_type_t of the connection with peerMac. */ static espnow_connection_type_t getConnectionInfo(uint8_t *peerMac, uint32_t *remainingDuration = nullptr); @@ -550,7 +637,7 @@ class EspnowMeshBackend : public MeshBackendBase { * Otherwise the variable value is not modified. * @param peerMac An optional pointer to an uint8_t array with at least size 6. It will be filled with the MAC of the encrypted peer interface if an encrypted connection exists. * Otherwise the array is not modified. - * @ returns The espnow_connection_type_t of the connection given by connectionIndex. + * @return The espnow_connection_type_t of the connection given by connectionIndex. */ static espnow_connection_type_t getConnectionInfo(uint32_t connectionIndex, uint32_t *remainingDuration = nullptr, uint8_t *peerMac = nullptr); @@ -576,6 +663,8 @@ class EspnowMeshBackend : public MeshBackendBase { static const uint64_t uint64BroadcastMac = 0xFFFFFFFFFFFF; bool activateEspnow(); + + static bool encryptedConnectionEstablished(encrypted_connection_status_t connectionStatus); /* * Note that ESP-NOW is not perfect and in rare cases messages may be dropped. @@ -584,8 +673,25 @@ class EspnowMeshBackend : public MeshBackendBase { * * Note that although responses will generally be sent in the order they were created, this is not guaranteed to be the case. * For example, response order will be mixed up if some responses fail to transmit while others transmit successfully. + * + * @param estimatedMaxDurationTracker A pointer to an ExpiringTimeTracker initialized with the desired max duration for the method. If set to nullptr there is no duration limit. + * Note that setting the estimatedMaxDuration too low may result in missed ESP-NOW transmissions because of too little time for maintainance. + * Also note that although the method will try to respect the max duration limit, there is no guarantee. Overshoots by tens of milliseconds are possible. */ - static void sendEspnowResponses(); + static void sendStoredEspnowMessages(const ExpiringTimeTracker *estimatedMaxDurationTracker = nullptr); + /* + * @param estimatedMaxDurationTracker A pointer to an ExpiringTimeTracker initialized with the desired max duration for the method. If set to nullptr there is no duration limit. + * Note that setting the estimatedMaxDuration too low may result in missed ESP-NOW transmissions because of too little time for maintainance. + * Also note that although the method will try to respect the max duration limit, there is no guarantee. Overshoots by tens of milliseconds are possible. + */ + static void sendPeerRequestConfirmations(const ExpiringTimeTracker *estimatedMaxDurationTracker = nullptr); + /* + * @param estimatedMaxDurationTracker A pointer to an ExpiringTimeTracker initialized with the desired max duration for the method. If set to nullptr there is no duration limit. + * Note that setting the estimatedMaxDuration too low may result in missed ESP-NOW transmissions because of too little time for maintainance. + * Also note that although the method will try to respect the max duration limit, there is no guarantee. Overshoots by tens of milliseconds are possible. + */ + static void sendEspnowResponses(const ExpiringTimeTracker *estimatedMaxDurationTracker = nullptr); + static void clearOldLogEntries(); static uint32_t getMaxBytesPerTransmission(); @@ -622,6 +728,11 @@ class EspnowMeshBackend : public MeshBackendBase { */ static bool _espnowTransmissionMutex; + /** + * Will be true when the connectionQueue should not be modified. + */ + static bool _espnowConnectionQueueMutex; + /** * Check if there is an ongoing ESP-NOW transmission in the library. Used to avoid interrupting transmissions. * @@ -670,8 +781,8 @@ class EspnowMeshBackend : public MeshBackendBase { private: typedef std::function encryptionRequestBuilderType; - static String defaultEncryptionRequestBuilder(const String &requestHeader, const uint32_t durationMs, const String &requestNonce, const ExpiringTimeTracker &existingTimeTracker); - static String flexibleEncryptionRequestBuilder(const uint32_t minDurationMs, const String &requestNonce, const ExpiringTimeTracker &existingTimeTracker); + static String defaultEncryptionRequestBuilder(const String &requestHeader, const uint32_t durationMs, const uint8_t *hashKey, const String &requestNonce, const ExpiringTimeTracker &existingTimeTracker); + static String flexibleEncryptionRequestBuilder(const uint32_t minDurationMs, const uint8_t *hashKey, const String &requestNonce, const ExpiringTimeTracker &existingTimeTracker); /** * We can't feed esp_now_register_recv_cb our EspnowMeshBackend instance's espnowReceiveCallback method directly, so this callback wrapper is a workaround. @@ -701,6 +812,8 @@ class EspnowMeshBackend : public MeshBackendBase { uint32_t _autoEncryptionDuration = 50; + uint8_t _encryptedConnectionsSoftLimit = 6; + static bool _staticVerboseMode; static EspnowMeshBackend *_espnowRequestManager; @@ -709,6 +822,14 @@ class EspnowMeshBackend : public MeshBackendBase { static std::map, RequestData> sentRequests; static std::map, TimeTracker> receivedRequests; + /** + * reservedEncryptedConnections never underestimates but sometimes temporarily overestimates. + * numberOfEncryptedConnections sometimes temporarily underestimates but never overestimates. + * + * @return The current number of encrypted ESP-NOW connections, but with an encrypted connection immediately reserved if required while making a peer request. + */ + static uint8_t reservedEncryptedConnections(); + static std::list responsesToSend; static std::list peerRequestConfirmationsToSend; @@ -755,9 +876,11 @@ class EspnowMeshBackend : public MeshBackendBase { broadcastFilterType _broadcastFilter; static String _ongoingPeerRequestNonce; + static uint8_t _ongoingPeerRequestMac[6]; static EspnowMeshBackend *_ongoingPeerRequester; static encrypted_connection_status_t _ongoingPeerRequestResult; static uint32_t _ongoingPeerRequestEncryptionStart; + static bool _reciprocalPeerRequestConfirmation; template static T *getMapValue(std::map &mapIn, uint64_t keyIn); @@ -830,9 +953,9 @@ class EspnowMeshBackend : public MeshBackendBase { transmission_status_t initiateTransmissionKernel(const String &message, const uint8_t *targetBSSID); void printTransmissionStatistics(); - encrypted_connection_status_t initiateAutoEncryptingConnection(const EspnowNetworkInfo &recipientInfo, bool createPermanentConnection, uint8_t *targetBSSID, EncryptedConnectionLog **encryptedConnection); + encrypted_connection_status_t initiateAutoEncryptingConnection(const EspnowNetworkInfo &recipientInfo, bool requestPermanentConnection, uint8_t *targetBSSID, EncryptedConnectionLog **existingEncryptedConnection); transmission_status_t initiateAutoEncryptingTransmission(const String &message, const uint8_t *targetBSSID, encrypted_connection_status_t connectionStatus); - void finalizeAutoEncryptingConnection(const uint8_t *targetBSSID, const EncryptedConnectionLog *encryptedConnection, bool createPermanentConnection); + void finalizeAutoEncryptingConnection(const uint8_t *targetBSSID, const EncryptedConnectionLog *existingEncryptedConnection, bool requestPermanentConnection); // Used for verboseMode printing in attemptTransmission, _AT suffix used to reduce namespace clutter uint32_t totalDurationWhenSuccessful_AT = 0; diff --git a/libraries/ESP8266WiFiMesh/src/EspnowNetworkInfo.cpp b/libraries/ESP8266WiFiMesh/src/EspnowNetworkInfo.cpp index a8940a5d20..d68f241566 100644 --- a/libraries/ESP8266WiFiMesh/src/EspnowNetworkInfo.cpp +++ b/libraries/ESP8266WiFiMesh/src/EspnowNetworkInfo.cpp @@ -23,9 +23,15 @@ */ #include "EspnowNetworkInfo.h" +#include EspnowNetworkInfo::EspnowNetworkInfo(int networkIndex) : NetworkInfoBase(networkIndex) { }; +EspnowNetworkInfo::EspnowNetworkInfo(const NetworkInfoBase &originalNetworkInfo) : NetworkInfoBase(originalNetworkInfo) +{ + assert(BSSID() != defaultBSSID); // We need at least BSSID to be able to connect. +}; + EspnowNetworkInfo::EspnowNetworkInfo(const uint8_t BSSID[6], const String &SSID, int32_t wifiChannel, uint8_t encryptionType, int32_t RSSI , bool isHidden) : NetworkInfoBase(SSID, wifiChannel, BSSID, encryptionType, RSSI, isHidden) { } diff --git a/libraries/ESP8266WiFiMesh/src/EspnowNetworkInfo.h b/libraries/ESP8266WiFiMesh/src/EspnowNetworkInfo.h index 947d1028e5..fecb7c5b3a 100644 --- a/libraries/ESP8266WiFiMesh/src/EspnowNetworkInfo.h +++ b/libraries/ESP8266WiFiMesh/src/EspnowNetworkInfo.h @@ -36,6 +36,8 @@ class EspnowNetworkInfo : public NetworkInfoBase { */ EspnowNetworkInfo(int networkIndex); + EspnowNetworkInfo(const NetworkInfoBase &originalNetworkInfo); + EspnowNetworkInfo(const uint8_t BSSID[6], const String &SSID = defaultSSID, int32_t wifiChannel = defaultWifiChannel, uint8_t encryptionType = defaultEncryptionType, int32_t RSSI = defaultRSSI, bool isHidden = defaultIsHidden); }; diff --git a/libraries/ESP8266WiFiMesh/src/EspnowProtocolInterpreter.h b/libraries/ESP8266WiFiMesh/src/EspnowProtocolInterpreter.h index 5ed6acdead..0a5720331b 100644 --- a/libraries/ESP8266WiFiMesh/src/EspnowProtocolInterpreter.h +++ b/libraries/ESP8266WiFiMesh/src/EspnowProtocolInterpreter.h @@ -44,6 +44,7 @@ namespace EspnowProtocolInterpreter const String temporaryEncryptionRequestHeader = "AddTEC:"; // Add temporary encrypted connection const String basicConnectionInfoHeader = "BasicCI:"; // Basic connection info const String encryptedConnectionInfoHeader = "EncryptedCI:"; // Encrypted connection info + const String softLimitEncryptedConnectionInfoHeader = "SLEncryptedCI:"; // Soft limit encrypted connection info const String maxConnectionsReachedHeader = "ECS_MAX_CONNECTIONS_REACHED_PEER:"; const String encryptedConnectionVerificationHeader = "ECVerified:"; // Encrypted connection verified const String encryptedConnectionRemovalRequestHeader = "RemoveEC:"; // Remove encrypted connection diff --git a/libraries/ESP8266WiFiMesh/src/ExpiringTimeTracker.cpp b/libraries/ESP8266WiFiMesh/src/ExpiringTimeTracker.cpp index 6c17d2dc21..73b6d32145 100644 --- a/libraries/ESP8266WiFiMesh/src/ExpiringTimeTracker.cpp +++ b/libraries/ESP8266WiFiMesh/src/ExpiringTimeTracker.cpp @@ -55,5 +55,5 @@ uint32_t ExpiringTimeTracker::remainingDuration() const bool ExpiringTimeTracker::expired() const { - return timeSinceCreation() > duration(); -} \ No newline at end of file + return timeSinceCreation() >= duration(); +} diff --git a/libraries/ESP8266WiFiMesh/src/JsonTranslator.cpp b/libraries/ESP8266WiFiMesh/src/JsonTranslator.cpp index c92f5f1577..1bbbb444fd 100644 --- a/libraries/ESP8266WiFiMesh/src/JsonTranslator.cpp +++ b/libraries/ESP8266WiFiMesh/src/JsonTranslator.cpp @@ -73,7 +73,8 @@ namespace JsonTranslator return false; } - bool verifyHmac(const String &encryptionRequestHmacMessage, const uint8_t *hashKey, uint8_t hashKeyLength) + bool verifyEncryptionRequestHmac(const String &encryptionRequestHmacMessage, const uint8_t *requesterStaMac, const uint8_t *requesterApMac, + const uint8_t *hashKey, uint8_t hashKeyLength) { String hmac = ""; if(getHmac(encryptionRequestHmacMessage, hmac)) @@ -82,7 +83,7 @@ namespace JsonTranslator if(hmacStartIndex < 0) return false; - if(verifyHmac(encryptionRequestHmacMessage.substring(0, hmacStartIndex), hmac, hashKey, hashKeyLength)) + if(verifyHmac(macToString(requesterStaMac) + macToString(requesterApMac) + encryptionRequestHmacMessage.substring(0, hmacStartIndex), hmac, hashKey, hashKeyLength)) { return true; } @@ -91,13 +92,12 @@ namespace JsonTranslator return false; } - String createEncryptedConnectionInfo(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) { - // Returns: Encrypted connection info:{"arguments":{"nonce":"1F2","password":"abc","ownSessionKey":"3B4","peerSessionKey":"1A2"}} - + // Returns: Encrypted connection info:{"arguments":{"nonce":"1F2","password":"abc","ownSK":"3B4","peerSK":"1A2"}} return - EspnowProtocolInterpreter::encryptedConnectionInfoHeader + "{\"arguments\":{" + infoHeader + "{\"arguments\":{" + createJsonPair(jsonNonce, requestNonce) + createJsonPair(jsonPassword, authenticationPassword) + createJsonPair(jsonOwnSessionKey, uint64ToString(peerSessionKey)) // Exchanges session keys since it should be valid for the receiver. @@ -116,15 +116,13 @@ namespace JsonTranslator return createJsonEndPair(jsonNonce, requestNonce); } - String createEncryptionRequestMessage(const String &requestHeader, const String &requestNonce, uint32_t duration) - { - return createEncryptionRequestIntro(requestHeader, duration) + createEncryptionRequestEnding(requestNonce); - } - String createEncryptionRequestHmacMessage(const String &requestHeader, const String &requestNonce, const uint8_t *hashKey, uint8_t hashKeyLength, uint32_t duration) { String mainMessage = createEncryptionRequestIntro(requestHeader, duration) + createJsonPair(jsonNonce, requestNonce); - String hmac = createHmac(mainMessage, hashKey, hashKeyLength); + uint8_t staMac[6] {0}; + uint8_t apMac[6] {0}; + String requesterStaApMac = macToString(WiFi.macAddress(staMac)) + macToString(WiFi.softAPmacAddress(apMac)); + String hmac = createHmac(requesterStaApMac + mainMessage, hashKey, hashKeyLength); return mainMessage + createJsonEndPair(jsonHmac, hmac); } @@ -148,6 +146,20 @@ namespace JsonTranslator return endIndex; } + + bool getConnectionState(const String &jsonString, String &result) + { + int32_t startIndex = jsonString.indexOf(jsonConnectionState); + if(startIndex < 0) + return false; + + int32_t endIndex = jsonString.indexOf("}"); + if(endIndex < 0) + return false; + + result = jsonString.substring(startIndex, endIndex + 1); + return true; + } bool getPassword(const String &jsonString, String &result) { @@ -266,4 +278,27 @@ namespace JsonTranslator result = bool(strtoul(jsonString.substring(startIndex).c_str(), nullptr, 0)); // strtoul stops reading input when an invalid character is discovered. return true; } + + bool getUnencryptedMessageID(const String &jsonString, uint32_t &result) + { + int32_t startIndex = getStartIndex(jsonString, jsonUnencryptedMessageID); + if(startIndex < 0) + return false; + + result = strtoul(jsonString.substring(startIndex).c_str(), nullptr, 0); // strtoul stops reading input when an invalid character is discovered. + return true; + } + + bool getMeshMessageCount(const String &jsonString, uint16_t &result) + { + int32_t startIndex = getStartIndex(jsonString, jsonMeshMessageCount); + if(startIndex < 0) + return false; + + uint32_t longResult = strtoul(jsonString.substring(startIndex).c_str(), nullptr, 0); // strtoul stops reading input when an invalid character is discovered. + assert(longResult <= 65535); // Must fit within uint16_t + + result = longResult; + return true; + } } diff --git a/libraries/ESP8266WiFiMesh/src/JsonTranslator.h b/libraries/ESP8266WiFiMesh/src/JsonTranslator.h index 7e75ab9f1c..8ff320e4dd 100644 --- a/libraries/ESP8266WiFiMesh/src/JsonTranslator.h +++ b/libraries/ESP8266WiFiMesh/src/JsonTranslator.h @@ -30,6 +30,7 @@ namespace JsonTranslator { + const String jsonConnectionState = "{\"connectionState\":{"; const String jsonPassword = "\"password\":"; const String jsonOwnSessionKey = "\"ownSK\":"; const String jsonPeerSessionKey = "\"peerSK\":"; @@ -39,6 +40,8 @@ namespace JsonTranslator const String jsonNonce = "\"nonce\":"; const String jsonHmac = "\"hmac\":"; const String jsonDesync = "\"desync\":"; + const String jsonUnencryptedMessageID = "\"uMessageID\":"; + const String jsonMeshMessageCount = "\"mMessageCount\":"; String createJsonPair(const String &valueIdentifier, const String &value); String createJsonEndPair(const String &valueIdentifier, const String &value); @@ -47,12 +50,11 @@ namespace JsonTranslator String createHmac(const String &message, const uint8_t *hashKey, uint8_t hashKeyLength); bool verifyHmac(const String &message, const String &messageHmac, const uint8_t *hashKey, uint8_t hashKeyLength); - bool verifyHmac(const String &encryptionRequestHmacMessage, const uint8_t *hashKey, uint8_t hashKeyLength); + bool verifyEncryptionRequestHmac(const String &encryptionRequestHmacMessage, const uint8_t *requesterStaMac, const uint8_t *requesterApMac, const uint8_t *hashKey, uint8_t hashKeyLength); - String createEncryptedConnectionInfo(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); String createEncryptionRequestIntro(const String &requestHeader, uint32_t duration = 0); String createEncryptionRequestEnding(const String &requestNonce); - String createEncryptionRequestMessage(const String &requestHeader, const String &requestNonce, uint32_t duration = 0); String createEncryptionRequestHmacMessage(const String &requestHeader, const String &requestNonce, const uint8_t *hashKey, uint8_t hashKeyLength, uint32_t duration = 0); /** @@ -76,6 +78,7 @@ namespace JsonTranslator */ int32_t getEndIndex(const String &jsonString, int32_t searchStartIndex); + bool getConnectionState(const String &jsonString, String &result); /** * Stores the value of the password field within jsonString into the result variable. * No changes to the result variable are made if jsonString does not contain a password. @@ -104,6 +107,8 @@ namespace JsonTranslator bool getNonce(const String &jsonString, String &result); bool getHmac(const String &jsonString, String &result); bool getDesync(const String &jsonString, bool &result); + bool getUnencryptedMessageID(const String &jsonString, uint32_t &result); + bool getMeshMessageCount(const String &jsonString, uint16_t &result); } #endif diff --git a/libraries/ESP8266WiFiMesh/src/MeshBackendBase.cpp b/libraries/ESP8266WiFiMesh/src/MeshBackendBase.cpp index 69232e5066..38f1054ef0 100644 --- a/libraries/ESP8266WiFiMesh/src/MeshBackendBase.cpp +++ b/libraries/ESP8266WiFiMesh/src/MeshBackendBase.cpp @@ -119,7 +119,7 @@ void MeshBackendBase::setWiFiChannel(uint8 newWiFiChannel) } } -uint8 MeshBackendBase::getWiFiChannel() +uint8 MeshBackendBase::getWiFiChannel() const { return _meshWiFiChannel; } @@ -147,28 +147,28 @@ void MeshBackendBase::setSSID(const String &newSSIDPrefix, const String &newSSID } } -String MeshBackendBase::getSSID() {return _SSID;} +String MeshBackendBase::getSSID() const {return _SSID;} void MeshBackendBase::setSSIDPrefix(const String &newSSIDPrefix) { setSSID(newSSIDPrefix); } -String MeshBackendBase::getSSIDPrefix() {return _SSIDPrefix;} +String MeshBackendBase::getSSIDPrefix() const {return _SSIDPrefix;} void MeshBackendBase::setSSIDRoot(const String &newSSIDRoot) { setSSID("", newSSIDRoot); } -String MeshBackendBase::getSSIDRoot() {return _SSIDRoot;} +String MeshBackendBase::getSSIDRoot() const {return _SSIDRoot;} void MeshBackendBase::setSSIDSuffix(const String &newSSIDSuffix) { setSSID("", "", newSSIDSuffix); } -String MeshBackendBase::getSSIDSuffix() {return _SSIDSuffix;} +String MeshBackendBase::getSSIDSuffix() const {return _SSIDSuffix;} void MeshBackendBase::setMeshName(const String &newMeshName) { @@ -195,10 +195,10 @@ void MeshBackendBase::setMeshPassword(const String &newMeshPassword) restartAP(); } -String MeshBackendBase::getMeshPassword() {return _meshPassword;} +String MeshBackendBase::getMeshPassword() const {return _meshPassword;} void MeshBackendBase::setMessage(const String &newMessage) {_message = newMessage;} -String MeshBackendBase::getMessage() {return _message;} +String MeshBackendBase::getMessage() const {return _message;} void MeshBackendBase::setRequestHandler(MeshBackendBase::requestHandlerType requestHandler) {_requestHandler = requestHandler;} MeshBackendBase::requestHandlerType MeshBackendBase::getRequestHandler() {return _requestHandler;} @@ -209,12 +209,15 @@ MeshBackendBase::responseHandlerType MeshBackendBase::getResponseHandler() {retu void MeshBackendBase::setNetworkFilter(MeshBackendBase::networkFilterType networkFilter) {_networkFilter = networkFilter;} MeshBackendBase::networkFilterType MeshBackendBase::getNetworkFilter() {return _networkFilter;} +void MeshBackendBase::setTransmissionOutcomesUpdateHook(MeshBackendBase::transmissionOutcomesUpdateHookType transmissionOutcomesUpdateHook) {_transmissionOutcomesUpdateHook = transmissionOutcomesUpdateHook;} +MeshBackendBase::transmissionOutcomesUpdateHookType MeshBackendBase::getTransmissionOutcomesUpdateHook() {return _transmissionOutcomesUpdateHook;} + void MeshBackendBase::setScanHidden(bool scanHidden) { _scanHidden = scanHidden; } -bool MeshBackendBase::getScanHidden() {return _scanHidden;} +bool MeshBackendBase::getScanHidden() const {return _scanHidden;} void MeshBackendBase::setAPHidden(bool apHidden) { @@ -228,14 +231,14 @@ void MeshBackendBase::setAPHidden(bool apHidden) } } -bool MeshBackendBase::getAPHidden() {return _apHidden;} +bool MeshBackendBase::getAPHidden() const {return _apHidden;} -bool MeshBackendBase::latestTransmissionSuccessful() +bool MeshBackendBase::latestTransmissionSuccessfulBase(const std::vector &latestTransmissionOutcomes) { - if(latestTransmissionOutcomes().empty()) + if(latestTransmissionOutcomes.empty()) return false; else - for(TransmissionOutcome &transmissionOutcome : latestTransmissionOutcomes()) + for(const TransmissionOutcome &transmissionOutcome : latestTransmissionOutcomes) if(transmissionOutcome.transmissionStatus() != TS_TRANSMISSION_COMPLETE) return false; diff --git a/libraries/ESP8266WiFiMesh/src/MeshBackendBase.h b/libraries/ESP8266WiFiMesh/src/MeshBackendBase.h index 778dbabddc..4820eab017 100644 --- a/libraries/ESP8266WiFiMesh/src/MeshBackendBase.h +++ b/libraries/ESP8266WiFiMesh/src/MeshBackendBase.h @@ -38,6 +38,7 @@ class MeshBackendBase { typedef std::function requestHandlerType; typedef std::function responseHandlerType; typedef std::function networkFilterType; + typedef std::function transmissionOutcomesUpdateHookType; public: @@ -45,20 +46,6 @@ class MeshBackendBase { virtual ~MeshBackendBase(); - /** - * Returns a vector with the TransmissionOutcome for each AP to which a transmission was attempted during the latest attemptTransmission call. - * This vector is unique for each mesh backend. - * The latestTransmissionOutcomes vector is cleared before each new transmission attempt. - * Connection attempts are indexed in the same order they were attempted. - * Note that old network indicies often are invalidated whenever a new WiFi network scan occurs. - */ - virtual std::vector & latestTransmissionOutcomes() = 0; - - /** - * @return True if latest transmission was successful (i.e. latestTransmissionOutcomes is not empty and all entries have transmissionStatus TS_TRANSMISSION_COMPLETE). False otherwise. - */ - bool latestTransmissionSuccessful(); - /** * Initialises the node. */ @@ -105,7 +92,7 @@ class MeshBackendBase { * */ void setWiFiChannel(uint8 newWiFiChannel); - uint8 getWiFiChannel(); + uint8 getWiFiChannel() const; /** * Change the SSID used by this MeshBackendBase instance. @@ -119,7 +106,7 @@ class MeshBackendBase { */ void setSSID(const String &newSSIDPrefix = ESP8266_MESH_EMPTY_STRING, const String &newSSIDRoot = ESP8266_MESH_EMPTY_STRING, const String &newSSIDSuffix = ESP8266_MESH_EMPTY_STRING); - String getSSID(); + String getSSID() const; /** * Change the first part of the SSID used by this MeshBackendBase instance. @@ -129,7 +116,7 @@ class MeshBackendBase { * @param newSSIDPrefix The new first part of the SSID. */ void setSSIDPrefix(const String &newSSIDPrefix); - String getSSIDPrefix(); + String getSSIDPrefix() const; /** * Change the middle part of the SSID used by this MeshBackendBase instance. @@ -139,7 +126,7 @@ class MeshBackendBase { * @param newSSIDPrefix The new middle part of the SSID. */ void setSSIDRoot(const String &newSSIDRoot); - String getSSIDRoot(); + String getSSIDRoot() const; /** * Change the last part of the SSID used by this MeshBackendBase instance. @@ -149,7 +136,7 @@ class MeshBackendBase { * @param newSSIDSuffix The new last part of the SSID. */ void setSSIDSuffix(const String &newSSIDSuffix); - String getSSIDSuffix(); + String getSSIDSuffix() const; /** * Change the mesh name used by this MeshBackendBase instance. @@ -183,7 +170,7 @@ class MeshBackendBase { * @param newMeshPassword The password to use. */ void setMeshPassword(const String &newMeshPassword); - String getMeshPassword(); + String getMeshPassword() const; /** * Set the message that will be sent to other nodes when calling attemptTransmission. @@ -191,7 +178,7 @@ class MeshBackendBase { * @param newMessage The message to send. */ void setMessage(const String &newMessage); - String getMessage(); + String getMessage() const; virtual void attemptTransmission(const String &message, bool scan = true, bool scanAllWiFiChannels = false) = 0; @@ -204,6 +191,16 @@ class MeshBackendBase { void setNetworkFilter(networkFilterType networkFilter); networkFilterType getNetworkFilter(); + /** + * Set a function that should be called after each update of the latestTransmissionOutcomes vector during attemptTransmission. (which happens after each individual transmission has finished) + * The function should return a bool. If this return value is true, attemptTransmission will continue with the next entry in the connectionQueue. If it is false, attemptTransmission will stop. + * The default transmissionOutcomesUpdateHook always returns true. + * + * Example use cases is modifying getMessage() between transmissions, or aborting attemptTransmission before all nodes in the connectionQueue have been contacted. + */ + void setTransmissionOutcomesUpdateHook(transmissionOutcomesUpdateHookType transmissionOutcomesUpdateHook); + transmissionOutcomesUpdateHookType getTransmissionOutcomesUpdateHook(); + /** * Set whether scan results from this MeshBackendBase instance will include WiFi networks with hidden SSIDs. * This is false by default. @@ -213,7 +210,7 @@ class MeshBackendBase { * @param scanHidden If true, WiFi networks with hidden SSIDs will be included in scan results. */ void setScanHidden(bool scanHidden); - bool getScanHidden(); + bool getScanHidden() const; /** * Set whether the AP controlled by this MeshBackendBase instance will have a WiFi network with hidden SSID. @@ -224,7 +221,7 @@ class MeshBackendBase { * @param apHidden If true, the WiFi network created will have a hidden SSID. */ void setAPHidden(bool apHidden); - bool getAPHidden(); + bool getAPHidden() const; /** * Set whether the normal events occurring in the library will be printed to Serial or not. Off by default. @@ -264,6 +261,13 @@ class MeshBackendBase { protected: + /** + * @param latestTransmissionOutcomes The transmission outcomes vector to check. + * + * @return True if latest transmission was successful (i.e. latestTransmissionOutcomes is not empty and all entries have transmissionStatus TS_TRANSMISSION_COMPLETE). False otherwise. + */ + static bool latestTransmissionSuccessfulBase(const std::vector &latestTransmissionOutcomes); + virtual void scanForNetworks(bool scanAllWiFiChannels); virtual void printAPInfo(const NetworkInfoBase &apNetworkInfo); @@ -303,6 +307,7 @@ class MeshBackendBase { requestHandlerType _requestHandler; responseHandlerType _responseHandler; networkFilterType _networkFilter; + transmissionOutcomesUpdateHookType _transmissionOutcomesUpdateHook = [](MeshBackendBase &){return true;}; static bool _printWarnings; }; diff --git a/libraries/ESP8266WiFiMesh/src/MutexTracker.cpp b/libraries/ESP8266WiFiMesh/src/MutexTracker.cpp index 97e84a75eb..c864ea76a9 100644 --- a/libraries/ESP8266WiFiMesh/src/MutexTracker.cpp +++ b/libraries/ESP8266WiFiMesh/src/MutexTracker.cpp @@ -24,6 +24,13 @@ #include "MutexTracker.h" +bool MutexTracker::_captureBan = false; + +bool &MutexTracker::captureBan() +{ + return _captureBan; +} + MutexTracker::MutexTracker(bool &mutexToCapture) { attemptMutexCapture(mutexToCapture); @@ -59,7 +66,7 @@ void MutexTracker::releaseMutex() bool MutexTracker::attemptMutexCapture(bool &mutexToCapture) { - if(!mutexToCapture) + if(!captureBan() && !mutexToCapture) { _capturedMutex = &mutexToCapture; *_capturedMutex = true; diff --git a/libraries/ESP8266WiFiMesh/src/MutexTracker.h b/libraries/ESP8266WiFiMesh/src/MutexTracker.h index a3922dc63b..6e70267719 100644 --- a/libraries/ESP8266WiFiMesh/src/MutexTracker.h +++ b/libraries/ESP8266WiFiMesh/src/MutexTracker.h @@ -34,6 +34,13 @@ class MutexTracker { public: + /* + * If captureBan is true, trying to capture a mutex will always fail. + * Set to false by default. + * captureBan can be managed by MutexTracker like any other mutex. + */ + static bool &captureBan(); + /** * Attempts to capture the mutex. Use the mutexCaptured() method to check success. */ @@ -57,6 +64,8 @@ class MutexTracker private: + static bool _captureBan; + bool *_capturedMutex = nullptr; std::function _destructorHook = [](){ }; diff --git a/libraries/ESP8266WiFiMesh/src/NetworkInfoBase.cpp b/libraries/ESP8266WiFiMesh/src/NetworkInfoBase.cpp index 9d3667d330..46620612db 100644 --- a/libraries/ESP8266WiFiMesh/src/NetworkInfoBase.cpp +++ b/libraries/ESP8266WiFiMesh/src/NetworkInfoBase.cpp @@ -24,6 +24,7 @@ #include "NetworkInfoBase.h" +uint8_t * const NetworkInfoBase::defaultBSSID = nullptr; const String NetworkInfoBase::defaultSSID = ""; const int32_t NetworkInfoBase::defaultWifiChannel = NETWORK_INFO_DEFAULT_INT; const uint8_t NetworkInfoBase::defaultEncryptionType = 0; diff --git a/libraries/ESP8266WiFiMesh/src/NetworkInfoBase.h b/libraries/ESP8266WiFiMesh/src/NetworkInfoBase.h index 8650322ee8..6fa7077b22 100644 --- a/libraries/ESP8266WiFiMesh/src/NetworkInfoBase.h +++ b/libraries/ESP8266WiFiMesh/src/NetworkInfoBase.h @@ -69,6 +69,7 @@ class NetworkInfoBase { void setIsHidden(bool isHidden); bool isHidden() const; + static uint8_t * const defaultBSSID; static const String defaultSSID; static const int32_t defaultWifiChannel; static const uint8_t defaultEncryptionType; @@ -90,7 +91,7 @@ class NetworkInfoBase { private: uint8_t _bssidArray[6] {0}; - uint8_t *_BSSID = nullptr; + uint8_t *_BSSID = defaultBSSID; String _SSID = defaultSSID; int32_t _wifiChannel = defaultWifiChannel; uint8_t _encryptionType = defaultEncryptionType; // see enum wl_enc_type for values diff --git a/libraries/ESP8266WiFiMesh/src/PeerRequestLog.cpp b/libraries/ESP8266WiFiMesh/src/PeerRequestLog.cpp index dd9d33dbd1..b606de5406 100644 --- a/libraries/ESP8266WiFiMesh/src/PeerRequestLog.cpp +++ b/libraries/ESP8266WiFiMesh/src/PeerRequestLog.cpp @@ -27,14 +27,18 @@ using EspnowProtocolInterpreter::espnowHashKeyLength; -PeerRequestLog::PeerRequestLog(uint64_t requestID, bool requestEncrypted, const String &authenticationPassword, const String &peerRequestNonce, const uint8_t peerStaMac[6], const uint8_t peerApMac[6], const uint8_t hashKey[espnowHashKeyLength]) +PeerRequestLog::PeerRequestLog(uint64_t requestID, bool requestEncrypted, const String &authenticationPassword, uint8_t encryptedConnectionsSoftLimit, + const String &peerRequestNonce, const uint8_t peerStaMac[6], const uint8_t peerApMac[6], const uint8_t hashKey[espnowHashKeyLength]) : EncryptedConnectionData(peerStaMac, peerApMac, 0, 0, EspnowMeshBackend::getEncryptionRequestTimeout(), hashKey), - _requestID(requestID), _requestEncrypted(requestEncrypted), _authenticationPassword(authenticationPassword), _peerRequestNonce(peerRequestNonce) + _requestID(requestID), _requestEncrypted(requestEncrypted), _authenticationPassword(authenticationPassword), + _encryptedConnectionsSoftLimit(encryptedConnectionsSoftLimit), _peerRequestNonce(peerRequestNonce) { } -PeerRequestLog::PeerRequestLog(uint64_t requestID, bool requestEncrypted, const String &authenticationPassword, const String &peerRequestNonce, const uint8_t peerStaMac[6], const uint8_t peerApMac[6], uint64_t peerSessionKey, uint64_t ownSessionKey, const uint8_t hashKey[espnowHashKeyLength]) +PeerRequestLog::PeerRequestLog(uint64_t requestID, bool requestEncrypted, const String &authenticationPassword, uint8_t encryptedConnectionsSoftLimit, const String &peerRequestNonce, + const uint8_t peerStaMac[6], const uint8_t peerApMac[6], uint64_t peerSessionKey, uint64_t ownSessionKey, const uint8_t hashKey[espnowHashKeyLength]) : EncryptedConnectionData(peerStaMac, peerApMac, peerSessionKey, ownSessionKey, EspnowMeshBackend::getEncryptionRequestTimeout(), hashKey), - _requestID(requestID), _requestEncrypted(requestEncrypted), _authenticationPassword(authenticationPassword), _peerRequestNonce(peerRequestNonce) + _requestID(requestID), _requestEncrypted(requestEncrypted), _authenticationPassword(authenticationPassword), + _encryptedConnectionsSoftLimit(encryptedConnectionsSoftLimit), _peerRequestNonce(peerRequestNonce) { } void PeerRequestLog::setRequestID(uint64_t requestID) { _requestID = requestID; } @@ -46,5 +50,8 @@ bool PeerRequestLog::requestEncrypted() { return _requestEncrypted; } void PeerRequestLog::setAuthenticationPassword(const String &password) { _authenticationPassword = password; } String PeerRequestLog::getAuthenticationPassword() { return _authenticationPassword; } +void PeerRequestLog::setEncryptedConnectionsSoftLimit(uint8_t softLimit) { _encryptedConnectionsSoftLimit = softLimit; } +uint8_t PeerRequestLog::getEncryptedConnectionsSoftLimit() { return _encryptedConnectionsSoftLimit; } + void PeerRequestLog::setPeerRequestNonce(const String &nonce) { _peerRequestNonce = nonce; } String PeerRequestLog::getPeerRequestNonce() { return _peerRequestNonce; } diff --git a/libraries/ESP8266WiFiMesh/src/PeerRequestLog.h b/libraries/ESP8266WiFiMesh/src/PeerRequestLog.h index 7f058f1719..2eb4c72bf0 100644 --- a/libraries/ESP8266WiFiMesh/src/PeerRequestLog.h +++ b/libraries/ESP8266WiFiMesh/src/PeerRequestLog.h @@ -32,10 +32,11 @@ class PeerRequestLog : public EncryptedConnectionData { public: - PeerRequestLog(uint64_t requestID, bool requestEncrypted, const String &authenticationPassword, const String &peerRequestNonce, const uint8_t peerStaMac[6], - const uint8_t peerApMac[6], const uint8_t hashKey[EspnowProtocolInterpreter::espnowHashKeyLength]); - PeerRequestLog(uint64_t requestID, bool requestEncrypted, const String &authenticationPassword, const String &peerRequestNonce, const uint8_t peerStaMac[6], - const uint8_t peerApMac[6], uint64_t peerSessionKey, uint64_t ownSessionKey, const uint8_t hashKey[EspnowProtocolInterpreter::espnowHashKeyLength]); + PeerRequestLog(uint64_t requestID, bool requestEncrypted, const String &authenticationPassword, uint8_t encryptedConnectionsSoftLimit, const String &peerRequestNonce, + const uint8_t peerStaMac[6], const uint8_t peerApMac[6], const uint8_t hashKey[EspnowProtocolInterpreter::espnowHashKeyLength]); + PeerRequestLog(uint64_t requestID, bool requestEncrypted, const String &authenticationPassword, uint8_t encryptedConnectionsSoftLimit, const String &peerRequestNonce, + const uint8_t peerStaMac[6], const uint8_t peerApMac[6], uint64_t peerSessionKey, uint64_t ownSessionKey, + const uint8_t hashKey[EspnowProtocolInterpreter::espnowHashKeyLength]); void setRequestID(uint64_t requestID); uint64_t getRequestID(); @@ -45,6 +46,9 @@ class PeerRequestLog : public EncryptedConnectionData { void setAuthenticationPassword(const String &password); String getAuthenticationPassword(); + + void setEncryptedConnectionsSoftLimit(uint8_t softLimit); + uint8_t getEncryptedConnectionsSoftLimit(); void setPeerRequestNonce(const String &nonce); String getPeerRequestNonce(); @@ -54,6 +58,7 @@ class PeerRequestLog : public EncryptedConnectionData { uint64_t _requestID; bool _requestEncrypted; String _authenticationPassword = ""; + uint8_t _encryptedConnectionsSoftLimit; String _peerRequestNonce = ""; }; diff --git a/libraries/ESP8266WiFiMesh/src/TcpIpMeshBackend.cpp b/libraries/ESP8266WiFiMesh/src/TcpIpMeshBackend.cpp index 8dcab87ab4..6932fcb3e3 100644 --- a/libraries/ESP8266WiFiMesh/src/TcpIpMeshBackend.cpp +++ b/libraries/ESP8266WiFiMesh/src/TcpIpMeshBackend.cpp @@ -30,6 +30,7 @@ const IPAddress TcpIpMeshBackend::emptyIP = IPAddress(); bool TcpIpMeshBackend::_tcpIpTransmissionMutex = false; +bool TcpIpMeshBackend::_tcpIpConnectionQueueMutex = false; String TcpIpMeshBackend::lastSSID = ""; bool TcpIpMeshBackend::staticIPActivated = false; @@ -58,6 +59,17 @@ TcpIpMeshBackend::TcpIpMeshBackend(requestHandlerType requestHandler, responseHa std::vector & TcpIpMeshBackend::connectionQueue() { + MutexTracker connectionQueueMutexTracker(_tcpIpConnectionQueueMutex); + if(!connectionQueueMutexTracker.mutexCaptured()) + { + assert(false && "ERROR! connectionQueue locked. Don't call connectionQueue() from callbacks other than NetworkFilter as this may corrupt program state!"); + } + + return _connectionQueue; +} + +const std::vector & TcpIpMeshBackend::constConnectionQueue() +{ return _connectionQueue; } @@ -66,6 +78,11 @@ std::vector & TcpIpMeshBackend::latestTransmissionOutcomes( return _latestTransmissionOutcomes; } +bool TcpIpMeshBackend::latestTransmissionSuccessful() +{ + return latestTransmissionSuccessfulBase(latestTransmissionOutcomes()); +} + void TcpIpMeshBackend::begin() { if(!TcpIpMeshBackend::getAPController()) // If there is no active AP controller @@ -433,7 +450,7 @@ void TcpIpMeshBackend::attemptTransmission(const String &message, bool scan, boo if(WiFi.status() == WL_CONNECTED) { transmission_status_t transmissionResult = attemptDataTransfer(); - latestTransmissionOutcomes().push_back(TransmissionOutcome(connectionQueue().back(), transmissionResult)); + latestTransmissionOutcomes().push_back(TransmissionOutcome(constConnectionQueue().back(), transmissionResult)); } else { @@ -443,11 +460,22 @@ void TcpIpMeshBackend::attemptTransmission(const String &message, bool scan, boo scanForNetworks(scanAllWiFiChannels); } - for(TcpIpNetworkInfo ¤tNetwork : connectionQueue()) + MutexTracker connectionQueueMutexTracker(_tcpIpConnectionQueueMutex); + if(!connectionQueueMutexTracker.mutexCaptured()) + { + assert(false && "ERROR! connectionQueue locked. Don't call attemptTransmission from callbacks as this may corrupt program state! Aborting."); + } + else { - transmission_status_t transmissionResult = initiateTransmission(currentNetwork); - - latestTransmissionOutcomes().push_back(TransmissionOutcome{.origin = currentNetwork, .transmissionStatus = transmissionResult}); + for(const TcpIpNetworkInfo ¤tNetwork : constConnectionQueue()) + { + transmission_status_t transmissionResult = initiateTransmission(currentNetwork); + + latestTransmissionOutcomes().push_back(TransmissionOutcome{.origin = currentNetwork, .transmissionStatus = transmissionResult}); + + if(!getTransmissionOutcomesUpdateHook()(*this)) + break; + } } } @@ -492,12 +520,12 @@ transmission_status_t TcpIpMeshBackend::attemptTransmission(const String &messag return transmissionResult; } -void TcpIpMeshBackend::acceptRequest() +void TcpIpMeshBackend::acceptRequests() { MutexTracker mutexTracker(_tcpIpTransmissionMutex); if(!mutexTracker.mutexCaptured()) { - assert(false && "ERROR! TCP/IP transmission in progress. Don't call acceptRequest from TCP/IP callbacks as this may corrupt program state! Aborting."); + assert(false && "ERROR! TCP/IP transmission in progress. Don't call acceptRequests from callbacks as this may corrupt program state! Aborting."); return; } diff --git a/libraries/ESP8266WiFiMesh/src/TcpIpMeshBackend.h b/libraries/ESP8266WiFiMesh/src/TcpIpMeshBackend.h index e83cbac357..b0929c7a60 100644 --- a/libraries/ESP8266WiFiMesh/src/TcpIpMeshBackend.h +++ b/libraries/ESP8266WiFiMesh/src/TcpIpMeshBackend.h @@ -67,12 +67,19 @@ class TcpIpMeshBackend : public MeshBackendBase { /** * Returns a vector that contains the NetworkInfo for each WiFi network to connect to. - * This vector is unique for each mesh backend. + * This vector is unique for each mesh backend, but NetworkInfo elements can be directly transferred between the vectors as long as both SSID and BSSID are present. * The connectionQueue vector is cleared before each new scan and filled via the networkFilter callback function once the scan completes. * WiFi connections will start with connectionQueue[0] and then incrementally proceed to higher vector positions. * Note that old network indicies often are invalidated whenever a new WiFi network scan occurs. + * + * Since the connectionQueue() is iterated over during transmissions, always use constConnectionQueue() from callbacks other than NetworkFilter. */ - std::vector & connectionQueue(); + static std::vector & connectionQueue(); + + /** + * Same as connectionQueue(), but can be called from all callbacks since the returned reference is const. + */ + static const std::vector & constConnectionQueue(); /** * Returns a vector with the TransmissionOutcome for each AP to which a transmission was attempted during the latest attemptTransmission call. @@ -81,7 +88,13 @@ class TcpIpMeshBackend : public MeshBackendBase { * Connection attempts are indexed in the same order they were attempted. * Note that old network indicies often are invalidated whenever a new WiFi network scan occurs. */ - std::vector & latestTransmissionOutcomes() override; + static std::vector & latestTransmissionOutcomes(); + + /** + * @return True if latest transmission was successful (i.e. latestTransmissionOutcomes is not empty and all entries have transmissionStatus TS_TRANSMISSION_COMPLETE). False otherwise. + * The result is unique for each mesh backend. + */ + static bool latestTransmissionSuccessful(); /** * Initialises the node. @@ -116,7 +129,7 @@ class TcpIpMeshBackend : public MeshBackendBase { /** * If any clients are connected, accept their requests and call the requestHandler function for each one. */ - void acceptRequest(); + void acceptRequests(); /** * Get the TCP/IP message that is currently scheduled for transmission. @@ -185,7 +198,7 @@ class TcpIpMeshBackend : public MeshBackendBase { /** * Set the timeout to use for transmissions when this TcpIpMeshBackend instance acts as an AP (i.e. when receiving connections from other stations). - * This will affect the timeout of the acceptRequest method. + * This will affect the timeout of the acceptRequests method. * The timeout is 4 500 ms by default. * Will also change the setting for the active AP (without an AP restart) * if this TcpIpMeshBackend instance is the current AP controller. @@ -217,6 +230,11 @@ class TcpIpMeshBackend : public MeshBackendBase { */ static bool _tcpIpTransmissionMutex; + /** + * Will be true when the connectionQueue should not be modified. + */ + static bool _tcpIpConnectionQueueMutex; + /** * Check if there is an ongoing TCP/IP transmission in the library. Used to avoid interrupting transmissions. * diff --git a/libraries/ESP8266WiFiMesh/src/TcpIpNetworkInfo.cpp b/libraries/ESP8266WiFiMesh/src/TcpIpNetworkInfo.cpp index 73a6e232e4..ad3b6fe163 100644 --- a/libraries/ESP8266WiFiMesh/src/TcpIpNetworkInfo.cpp +++ b/libraries/ESP8266WiFiMesh/src/TcpIpNetworkInfo.cpp @@ -23,9 +23,16 @@ */ #include "TcpIpNetworkInfo.h" +#include TcpIpNetworkInfo::TcpIpNetworkInfo(int networkIndex) : NetworkInfoBase(networkIndex) { }; + +TcpIpNetworkInfo::TcpIpNetworkInfo(const NetworkInfoBase &originalNetworkInfo) : NetworkInfoBase(originalNetworkInfo) +{ + assert(SSID() != defaultSSID); // We need at least SSID to be able to connect. +}; + TcpIpNetworkInfo::TcpIpNetworkInfo(const String &SSID, int32_t wifiChannel, const uint8_t BSSID[6], uint8_t encryptionType, int32_t RSSI , bool isHidden) : NetworkInfoBase(SSID, wifiChannel, BSSID, encryptionType, RSSI, isHidden) { } diff --git a/libraries/ESP8266WiFiMesh/src/TcpIpNetworkInfo.h b/libraries/ESP8266WiFiMesh/src/TcpIpNetworkInfo.h index eed1a9981a..e59bb56904 100644 --- a/libraries/ESP8266WiFiMesh/src/TcpIpNetworkInfo.h +++ b/libraries/ESP8266WiFiMesh/src/TcpIpNetworkInfo.h @@ -36,10 +36,13 @@ class TcpIpNetworkInfo : public NetworkInfoBase { */ TcpIpNetworkInfo(int networkIndex); + + TcpIpNetworkInfo(const NetworkInfoBase &originalNetworkInfo); + /** * Without giving wifiChannel and BSSID, connection time is longer. */ - TcpIpNetworkInfo(const String &SSID, int32_t wifiChannel = defaultWifiChannel, const uint8_t BSSID[6] = nullptr, uint8_t encryptionType = defaultEncryptionType, + TcpIpNetworkInfo(const String &SSID, int32_t wifiChannel = defaultWifiChannel, const uint8_t BSSID[6] = defaultBSSID, uint8_t encryptionType = defaultEncryptionType, int32_t RSSI = defaultRSSI, bool isHidden = defaultIsHidden); }; From 7ca5a7e5bbc952e49d8aa09522d77cbfb2a3421d Mon Sep 17 00:00:00 2001 From: Anders Date: Fri, 1 Nov 2019 12:17:24 +0100 Subject: [PATCH 05/30] Fix conflict with master branch. - Avoid single character String concatenations done via String literals instead of char literals, as this is inefficient because of temporary String creations (#6571). --- libraries/ESP8266WiFiMesh/src/ESP8266WiFiMesh.cpp | 8 ++++---- libraries/ESP8266WiFiMesh/src/EspnowMeshBackend.cpp | 2 +- libraries/ESP8266WiFiMesh/src/MeshBackendBase.cpp | 4 ++-- libraries/ESP8266WiFiMesh/src/TcpIpMeshBackend.cpp | 4 ++-- 4 files changed, 9 insertions(+), 9 deletions(-) diff --git a/libraries/ESP8266WiFiMesh/src/ESP8266WiFiMesh.cpp b/libraries/ESP8266WiFiMesh/src/ESP8266WiFiMesh.cpp index f4d198a5cb..f4839e3667 100644 --- a/libraries/ESP8266WiFiMesh/src/ESP8266WiFiMesh.cpp +++ b/libraries/ESP8266WiFiMesh/src/ESP8266WiFiMesh.cpp @@ -372,7 +372,7 @@ transmission_status_t ESP8266WiFiMesh::exchangeInfo(WiFiClient &currClient) { verboseModePrint("Transmitting"); // Not storing strings in flash (via F()) to avoid performance impacts when using the string. - currClient.print(getMessage() + "\r"); + currClient.print(getMessage() + '\r'); yield(); if (!waitForClientTransmission(currClient, _stationModeTimeoutMs)) @@ -585,11 +585,11 @@ void ESP8266WiFiMesh::attemptTransmission(const String &message, bool concluding if(_verboseMode) // Avoid string generation if not required { - verboseModePrint(String(F("AP acquired: ")) + currentSSID + String(F(", Ch:")) + String(currentWiFiChannel) + " ", false); + verboseModePrint(String(F("AP acquired: ")) + currentSSID + String(F(", Ch:")) + String(currentWiFiChannel) + ' ', false); if(currentNetwork.networkIndex != NETWORK_INFO_DEFAULT_INT) { - verboseModePrint("(" + String(WiFi.RSSI(currentNetwork.networkIndex)) + String(F("dBm) ")) + + verboseModePrint('(' + String(WiFi.RSSI(currentNetwork.networkIndex)) + String(F("dBm) ")) + (WiFi.encryptionType(currentNetwork.networkIndex) == ENC_TYPE_NONE ? String(F("open")) : ""), false); } @@ -665,7 +665,7 @@ void ESP8266WiFiMesh::acceptRequest() if (_client.connected()) { verboseModePrint("Responding"); // Not storing strings in flash (via F()) to avoid performance impacts when using the string. - _client.print(response + "\r"); + _client.print(response + '\r'); _client.flush(); yield(); } diff --git a/libraries/ESP8266WiFiMesh/src/EspnowMeshBackend.cpp b/libraries/ESP8266WiFiMesh/src/EspnowMeshBackend.cpp index 28c9dc1d43..42bc014635 100644 --- a/libraries/ESP8266WiFiMesh/src/EspnowMeshBackend.cpp +++ b/libraries/ESP8266WiFiMesh/src/EspnowMeshBackend.cpp @@ -165,7 +165,7 @@ bool EspnowMeshBackend::activateEspnow() warningPrint("Failed to set ESP-NOW role! Maybe ESP-NOW peers are already added?"); verboseModePrint("ESP-NOW activated."); - verboseModePrint("My ESP-NOW STA MAC: " + WiFi.macAddress() + "\n"); // Get the station MAC address. The softAP MAC is different. + verboseModePrint("My ESP-NOW STA MAC: " + WiFi.macAddress() + '\n'); // Get the station MAC address. The softAP MAC is different. return true; } diff --git a/libraries/ESP8266WiFiMesh/src/MeshBackendBase.cpp b/libraries/ESP8266WiFiMesh/src/MeshBackendBase.cpp index 38f1054ef0..30ff9fd614 100644 --- a/libraries/ESP8266WiFiMesh/src/MeshBackendBase.cpp +++ b/libraries/ESP8266WiFiMesh/src/MeshBackendBase.cpp @@ -282,11 +282,11 @@ void MeshBackendBase::printAPInfo(const NetworkInfoBase &apNetworkInfo) mainNetworkIdentifier = 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); if(apNetworkInfo.RSSI() != NetworkInfoBase::defaultRSSI) { - verboseModePrint("(" + String(apNetworkInfo.RSSI()) + String(F("dBm) ")) + + verboseModePrint('(' + String(apNetworkInfo.RSSI()) + String(F("dBm) ")) + (apNetworkInfo.encryptionType() == ENC_TYPE_NONE ? String(F("open")) : ""), false); } diff --git a/libraries/ESP8266WiFiMesh/src/TcpIpMeshBackend.cpp b/libraries/ESP8266WiFiMesh/src/TcpIpMeshBackend.cpp index 6932fcb3e3..0b8ac7e19b 100644 --- a/libraries/ESP8266WiFiMesh/src/TcpIpMeshBackend.cpp +++ b/libraries/ESP8266WiFiMesh/src/TcpIpMeshBackend.cpp @@ -247,7 +247,7 @@ transmission_status_t TcpIpMeshBackend::exchangeInfo(WiFiClient &currClient) { verboseModePrint("Transmitting"); // Not storing strings in flash (via F()) to avoid performance impacts when using the string. - currClient.print(getCurrentMessage() + "\r"); + currClient.print(getCurrentMessage() + '\r'); yield(); if (!waitForClientTransmission(currClient, _stationModeTimeoutMs)) @@ -550,7 +550,7 @@ void TcpIpMeshBackend::acceptRequests() if (_client.connected()) { verboseModePrint("Responding"); // Not storing strings in flash (via F()) to avoid performance impacts when using the string. - _client.print(response + "\r"); + _client.print(response + '\r'); _client.flush(); yield(); } From 8194197e9de4a2c13d44fccfcb089aedae684264 Mon Sep 17 00:00:00 2001 From: Anders Date: Fri, 1 Nov 2019 12:37:09 +0100 Subject: [PATCH 06/30] Even better fix of conflict with master branch. --- libraries/ESP8266WiFiMesh/src/ESP8266WiFiMesh.cpp | 2 +- libraries/ESP8266WiFiMesh/src/MeshBackendBase.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/libraries/ESP8266WiFiMesh/src/ESP8266WiFiMesh.cpp b/libraries/ESP8266WiFiMesh/src/ESP8266WiFiMesh.cpp index f4839e3667..583d34564f 100644 --- a/libraries/ESP8266WiFiMesh/src/ESP8266WiFiMesh.cpp +++ b/libraries/ESP8266WiFiMesh/src/ESP8266WiFiMesh.cpp @@ -589,7 +589,7 @@ void ESP8266WiFiMesh::attemptTransmission(const String &message, bool concluding if(currentNetwork.networkIndex != NETWORK_INFO_DEFAULT_INT) { - verboseModePrint('(' + String(WiFi.RSSI(currentNetwork.networkIndex)) + String(F("dBm) ")) + + verboseModePrint(String('(') + String(WiFi.RSSI(currentNetwork.networkIndex)) + String(F("dBm) ")) + (WiFi.encryptionType(currentNetwork.networkIndex) == ENC_TYPE_NONE ? String(F("open")) : ""), false); } diff --git a/libraries/ESP8266WiFiMesh/src/MeshBackendBase.cpp b/libraries/ESP8266WiFiMesh/src/MeshBackendBase.cpp index 30ff9fd614..4abe1567b0 100644 --- a/libraries/ESP8266WiFiMesh/src/MeshBackendBase.cpp +++ b/libraries/ESP8266WiFiMesh/src/MeshBackendBase.cpp @@ -286,7 +286,7 @@ void MeshBackendBase::printAPInfo(const NetworkInfoBase &apNetworkInfo) if(apNetworkInfo.RSSI() != NetworkInfoBase::defaultRSSI) { - verboseModePrint('(' + String(apNetworkInfo.RSSI()) + String(F("dBm) ")) + + verboseModePrint(String('(') + String(apNetworkInfo.RSSI()) + String(F("dBm) ")) + (apNetworkInfo.encryptionType() == ENC_TYPE_NONE ? String(F("open")) : ""), false); } From 176f2851e4ff2b9fcc50edbdcc9d8d418fb2e172 Mon Sep 17 00:00:00 2001 From: Anders Date: Fri, 1 Nov 2019 12:55:31 +0100 Subject: [PATCH 07/30] Fix space. --- libraries/ESP8266WiFiMesh/src/ESP8266WiFiMesh.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/ESP8266WiFiMesh/src/ESP8266WiFiMesh.cpp b/libraries/ESP8266WiFiMesh/src/ESP8266WiFiMesh.cpp index 583d34564f..df283f1d5a 100644 --- a/libraries/ESP8266WiFiMesh/src/ESP8266WiFiMesh.cpp +++ b/libraries/ESP8266WiFiMesh/src/ESP8266WiFiMesh.cpp @@ -664,7 +664,7 @@ void ESP8266WiFiMesh::acceptRequest() /* Send the response back to the client */ if (_client.connected()) { - verboseModePrint("Responding"); // Not storing strings in flash (via F()) to avoid performance impacts when using the string. + verboseModePrint("Responding"); // Not storing strings in flash (via F()) to avoid performance impacts when using the string. _client.print(response + '\r'); _client.flush(); yield(); From 6b763686def159ac71625d4fad3799915aae0af2 Mon Sep 17 00:00:00 2001 From: Anders Date: Sun, 3 Nov 2019 14:00:05 +0100 Subject: [PATCH 08/30] - Add working FloodingMesh. Unencrypted broadcasts should work well, but are untested in large mesh networks. Encrypted broadcast support is currently experimental. - Add BroadcastTransmissionRedundancy and related functionality to reduce the transmission loss during broadcasts. Broadcast transmissions are now re-transmitted once per default. Broadcast throughput halved per default. - Add getSenderAPMac method. - Add FloodingMesh example in the HelloMesh.ino file. - Improve JSON identifier names. - Improve comments. - Improve documentation. --- .../examples/HelloMesh/HelloMesh.ino | 208 ++++---- .../ESP8266WiFiMesh/src/EspnowMeshBackend.cpp | 87 ++- .../ESP8266WiFiMesh/src/EspnowMeshBackend.h | 40 +- .../ESP8266WiFiMesh/src/FloodingMesh.cpp | 503 ++++++++++++++++++ libraries/ESP8266WiFiMesh/src/FloodingMesh.h | 285 ++++++++++ .../ESP8266WiFiMesh/src/JsonTranslator.h | 4 +- libraries/ESP8266WiFiMesh/src/MessageData.cpp | 1 - .../ESP8266WiFiMesh/src/TcpIpMeshBackend.h | 2 +- .../src/TypeConversionFunctions.h | 2 +- 9 files changed, 998 insertions(+), 134 deletions(-) create mode 100644 libraries/ESP8266WiFiMesh/src/FloodingMesh.cpp create mode 100644 libraries/ESP8266WiFiMesh/src/FloodingMesh.h diff --git a/libraries/ESP8266WiFiMesh/examples/HelloMesh/HelloMesh.ino b/libraries/ESP8266WiFiMesh/examples/HelloMesh/HelloMesh.ino index cdd3e6d5c9..aedc758713 100644 --- a/libraries/ESP8266WiFiMesh/examples/HelloMesh/HelloMesh.ino +++ b/libraries/ESP8266WiFiMesh/examples/HelloMesh/HelloMesh.ino @@ -1,7 +1,7 @@ #include -#include #include #include +#include /** NOTE: Although we could define the strings below as normal String variables, @@ -14,84 +14,94 @@ https://github.com/esp8266/Arduino/issues/1143 https://arduino-esp8266.readthedocs.io/en/latest/PROGMEM.html */ -const char exampleMeshName[] PROGMEM = "MeshNode_"; -const char exampleWiFiPassword[] PROGMEM = "ChangeThisWiFiPassword_TODO"; +const char exampleMeshName[] PROGMEM = "MeshNode_"; // The name of the mesh network. Used as prefix for the node SSID and to find other network nodes in the example networkFilter and broadcastFilter functions below. +const char exampleWiFiPassword[] PROGMEM = "ChangeThisWiFiPassword_TODO"; // The password has to be min 8 and max 64 characters long, otherwise an AP which uses it will not be found during scans. -unsigned int requestNumber = 0; -unsigned int responseNumber = 0; +// 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. +uint8_t espnowEncryptionKey[16] = {0x33, 0x44, 0x33, 0x44, 0x33, 0x44, 0x33, 0x44, // This is the key for encrypting transmissions. + 0x33, 0x44, 0x33, 0x44, 0x33, 0x44, 0x32, 0x11 + }; +uint8_t espnowHashKey[16] = {0xEF, 0x44, 0x33, 0x0C, 0x33, 0x44, 0xFE, 0x44, // This is the secret key used for HMAC during encrypted connection requests. + 0x33, 0x44, 0x33, 0xB0, 0x33, 0x44, 0x32, 0xAD + }; -String manageRequest(const String &request, ESP8266WiFiMesh &meshInstance); -transmission_status_t manageResponse(const String &response, ESP8266WiFiMesh &meshInstance); -void networkFilter(int numberOfNetworks, ESP8266WiFiMesh &meshInstance); +bool meshMessageHandler(String &message, FloodingMesh &meshInstance); /* Create the mesh node object */ -ESP8266WiFiMesh meshNode = ESP8266WiFiMesh(manageRequest, manageResponse, networkFilter, FPSTR(exampleWiFiPassword), FPSTR(exampleMeshName), "", true); +FloodingMesh floodingMesh = FloodingMesh(meshMessageHandler, FPSTR(exampleWiFiPassword), espnowEncryptionKey, espnowHashKey, FPSTR(exampleMeshName), uint64ToString(ESP.getChipId()), true); -/** - Callback for when other nodes send you a request +bool theOne = true; +String theOneMac = ""; - @param request The request string received from another node in the mesh - @param meshInstance The ESP8266WiFiMesh instance that called the function. - @returns The string to send back to the other node -*/ -String manageRequest(const String &request, ESP8266WiFiMesh &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. - - /* Print out received message */ - Serial.print("Request received: "); - Serial.println(request); - - /* return a string to send back */ - return ("Hello world response #" + String(responseNumber++) + " from " + meshInstance.getMeshName() + meshInstance.getNodeID() + "."); -} +bool useLED = false; // Change this to true if you wish the onboard LED to mark The One. /** - Callback for when you get a response from other nodes - - @param response The response string received from another node in the mesh - @param meshInstance The ESP8266WiFiMesh instance that called the function. - @returns The status code resulting from the response, as an int + Callback for when a message is received from the mesh network. + + @param message The message String received from the mesh. + Modifications to this String are passed on when the message is forwarded from this node to other nodes. + However, the forwarded message will still use the same messageID. + Thus it will not be sent to nodes that have already received this messageID. + If you want to send a new message to the whole network, use a new broadcast from within the loop() instead. + @param meshInstance The FloodingMesh instance that received the message. + @return True if this node should forward the received message to other nodes. False otherwise. */ -transmission_status_t manageResponse(const String &response, ESP8266WiFiMesh &meshInstance) { - transmission_status_t statusCode = TS_TRANSMISSION_COMPLETE; +bool meshMessageHandler(String &message, FloodingMesh &meshInstance) { + int32_t delimiterIndex = message.indexOf(meshInstance.broadcastMetadataDelimiter()); + if (delimiterIndex == 0) { + Serial.print("Message received from STA " + meshInstance.getEspnowMeshBackend().getSenderMac() + ": "); + Serial.println(message.substring(1, 101)); + + String potentialMac = message.substring(1, 13); + + if (potentialMac > theOneMac) { + if (theOne) { + if (useLED) { + digitalWrite(LED_BUILTIN, HIGH); // Turn LED off (LED is active low) + } - /* Print out received message */ - Serial.print(F("Request sent: ")); - Serial.println(meshInstance.getMessage()); - Serial.print(F("Response received: ")); - Serial.println(response); + theOne = false; + } - // Our last request got a response, so time to create a new request. - meshInstance.setMessage(String(F("Hello world request #")) + String(++requestNumber) + String(F(" from ")) - + meshInstance.getMeshName() + meshInstance.getNodeID() + String(F("."))); + theOneMac = potentialMac; - // (void)meshInstance; // This is useful to remove a "unused parameter" compiler warning. Does nothing else. - return statusCode; -} + return true; + } else { + return false; + } + } else if (delimiterIndex > 0) { + if (meshInstance.getOriginMac() == theOneMac) { + uint32_t totalBroadcasts = strtoul(message.c_str(), nullptr, 0); // strtoul stops reading input when an invalid character is discovered. -/** - Callback used to decide which networks to connect to once a WiFi scan has been completed. + // Static variables are only initialized once. + static uint32_t firstBroadcast = totalBroadcasts; - @param numberOfNetworks The number of networks found in the WiFi scan. - @param meshInstance The ESP8266WiFiMesh instance that called the function. -*/ -void networkFilter(int numberOfNetworks, ESP8266WiFiMesh &meshInstance) { - for (int networkIndex = 0; networkIndex < numberOfNetworks; ++networkIndex) { - String currentSSID = WiFi.SSID(networkIndex); - int meshNameIndex = currentSSID.indexOf(meshInstance.getMeshName()); + if (totalBroadcasts - firstBroadcast >= 100) { // Wait a little to avoid start-up glitches + static uint32_t missedBroadcasts = 1; // Starting at one to compensate for initial -1 below. + static uint32_t previousTotalBroadcasts = totalBroadcasts; + static uint32_t totalReceivedBroadcasts = 0; + totalReceivedBroadcasts++; - /* Connect to any _suitable_ APs which contain meshInstance.getMeshName() */ - if (meshNameIndex >= 0) { - uint64_t targetNodeID = stringToUint64(currentSSID.substring(meshNameIndex + meshInstance.getMeshName().length())); + missedBroadcasts += totalBroadcasts - previousTotalBroadcasts - 1; // We expect an increment by 1. + previousTotalBroadcasts = totalBroadcasts; - if (targetNodeID < stringToUint64(meshInstance.getNodeID())) { - ESP8266WiFiMesh::connectionQueue.push_back(NetworkInfo(networkIndex)); + if (totalReceivedBroadcasts % 50 == 0) { + Serial.println("missed/total: " + String(missedBroadcasts) + '/' + String(totalReceivedBroadcasts)); + } + if (totalReceivedBroadcasts % 500 == 0) { + Serial.println("Benchmark message: " + message.substring(0, 100)); + } } } + } else { + // Only show first 100 characters because printing a large String takes a lot of time, which is a bad thing for a callback function. + // If you need to print the whole String it is better to store it and print it in the loop() later. + Serial.print("Message with origin " + meshInstance.getOriginMac() + " received: "); + Serial.println(message.substring(0, 100)); } + + return true; } void setup() { @@ -102,7 +112,7 @@ void setup() { Serial.begin(115200); delay(50); // Wait for Serial. - //yield(); // Use this if you don't want to wait for Serial. + //yield(); // Use this if you don't want to wait for Serial, but not with the ESP-NOW backend (yield() causes crashes with ESP-NOW). // The WiFi.disconnect() ensures that the WiFi is working correctly. If this is not done before receiving WiFi connections, // those WiFi connections will take a long time to make or sometimes will not work at all. @@ -111,52 +121,52 @@ void setup() { Serial.println(); Serial.println(); - Serial.println(F("Note that this library can use static IP:s for the nodes to speed up connection times.\n" - "Use the setStaticIP method as shown in this example to enable this.\n" - "Ensure that nodes connecting to the same AP have distinct static IP:s.\n" - "Also, remember to change the default mesh network password!\n\n")); + Serial.println(F("If you have an onboard LED on your ESP8266 it is recommended that you change the useLED variable to true.\n" + "That way you will get instant confirmation of the mesh communication.\n" + "Also, remember to change the default mesh network password and ESP-NOW keys!\n")); Serial.println(F("Setting up mesh node...")); - /* Initialise the mesh node */ - meshNode.begin(); - meshNode.activateAP(); // Each AP requires a separate server port. - meshNode.setStaticIP(IPAddress(192, 168, 4, 22)); // Activate static IP mode to speed up connection times. + floodingMesh.begin(); + + uint8_t apMacArray[6] {0}; + theOneMac = macToString(WiFi.softAPmacAddress(apMacArray)); + + if (useLED) { + pinMode(LED_BUILTIN, OUTPUT); // Initialize the LED_BUILTIN pin as an output + digitalWrite(LED_BUILTIN, LOW); // Turn LED on (LED is active low) + } + + floodingMeshDelay(5000); // Give some time for user to start the nodes } -int32_t timeOfLastScan = -10000; +int32_t timeOfLastProclamation = -10000; void loop() { - if (millis() - timeOfLastScan > 3000 // Give other nodes some time to connect between data transfers. - || (WiFi.status() != WL_CONNECTED && millis() - timeOfLastScan > 2000)) { // Scan for networks with two second intervals when not already connected. - String request = String(F("Hello world request #")) + String(requestNumber) + String(F(" from ")) + meshNode.getMeshName() + meshNode.getNodeID() + String(F(".")); - meshNode.attemptTransmission(request, false); - timeOfLastScan = millis(); - - // One way to check how attemptTransmission worked out - if (ESP8266WiFiMesh::latestTransmissionSuccessful()) { - Serial.println(F("Transmission successful.")); + static uint32_t benchmarkCount = 0; + static uint32_t loopStart = millis(); + + // The floodingMeshDelay() method performs all the background operations for the FloodingMesh (via FloodingMesh::performMeshMaintainance()). + // It is recommended to place one of these methods in the beginning of the loop(), unless there is a need to put them elsewhere. + // Among other things, the method cleans up old ESP-NOW log entries (freeing up RAM) and forwards received mesh messages. + // Note that depending on the amount of messages to forward and their length, this method can take tens or even hundreds of milliseconds to complete. + // More intense transmission activity and less frequent calls to performMeshMaintainance will likely cause the method to take longer to complete, so plan accordingly. + floodingMeshDelay(1); + + if (theOne) { + if (millis() - timeOfLastProclamation > 10000) { + uint32_t startTime = millis(); + floodingMesh.broadcast(String(floodingMesh.broadcastMetadataDelimiter()) + theOneMac + " is The One."); + Serial.println("Proclamation broadcast done in " + String(millis() - startTime) + " ms."); + + timeOfLastProclamation = millis(); + floodingMeshDelay(20); } - // Another way to check how attemptTransmission worked out - if (ESP8266WiFiMesh::latestTransmissionOutcomes.empty()) { - Serial.println(F("No mesh AP found.")); - } else { - for (TransmissionResult &transmissionResult : ESP8266WiFiMesh::latestTransmissionOutcomes) { - if (transmissionResult.transmissionStatus == TS_TRANSMISSION_FAILED) { - Serial.println(String(F("Transmission failed to mesh AP ")) + transmissionResult.SSID); - } else if (transmissionResult.transmissionStatus == TS_CONNECTION_FAILED) { - Serial.println(String(F("Connection failed to mesh AP ")) + transmissionResult.SSID); - } else if (transmissionResult.transmissionStatus == TS_TRANSMISSION_COMPLETE) { - // No need to do anything, transmission was successful. - } else { - Serial.println(String(F("Invalid transmission status for ")) + transmissionResult.SSID + String(F("!"))); - assert(F("Invalid transmission status returned from responseHandler!") && false); - } - } + if (millis() - loopStart > 23000) { // Start benchmarking the mesh once three proclamations have been made + uint32_t startTime = millis(); + floodingMesh.broadcast(String(benchmarkCount++) + String(floodingMesh.broadcastMetadataDelimiter()) + ": Not a spoon in sight."); + Serial.println("Benchmark broadcast done in " + String(millis() - startTime) + " ms."); + floodingMeshDelay(20); } - Serial.println(); - } else { - /* Accept any incoming connections */ - meshNode.acceptRequest(); } } diff --git a/libraries/ESP8266WiFiMesh/src/EspnowMeshBackend.cpp b/libraries/ESP8266WiFiMesh/src/EspnowMeshBackend.cpp index 42bc014635..20c39e2543 100644 --- a/libraries/ESP8266WiFiMesh/src/EspnowMeshBackend.cpp +++ b/libraries/ESP8266WiFiMesh/src/EspnowMeshBackend.cpp @@ -753,14 +753,19 @@ void EspnowMeshBackend::espnowReceiveCallback(uint8_t *macaddr, uint8_t *dataArr { if(messageType == 'B') { + auto key = std::make_pair(macAndType, messageID); + if(receivedEspnowTransmissions.find(key) != receivedEspnowTransmissions.end()) + return; // Should not call BroadcastFilter more than once for an accepted message + String message = espnowGetMessageContent(dataArray, len); setSenderMac(macaddr); + espnowGetTransmissionMac(dataArray, _senderAPMac); setReceivedEncryptedMessage(usesEncryption(messageID)); bool acceptBroadcast = getBroadcastFilter()(message, *this); if(acceptBroadcast) { // Does nothing if key already in receivedEspnowTransmissions - receivedEspnowTransmissions.insert(std::make_pair(std::make_pair(macAndType, messageID), MessageData(message, espnowGetTransmissionsRemaining(dataArray)))); + receivedEspnowTransmissions.insert(std::make_pair(key, MessageData(message, espnowGetTransmissionsRemaining(dataArray)))); } else { @@ -822,6 +827,7 @@ void EspnowMeshBackend::espnowReceiveCallback(uint8_t *macaddr, uint8_t *dataArr //Serial.println("methodStart request stored " + String(millis() - methodStart)); setSenderMac(macaddr); + espnowGetTransmissionMac(dataArray, _senderAPMac); setReceivedEncryptedMessage(usesEncryption(messageID)); String response = getRequestHandler()(totalMessage, *this); //Serial.println("methodStart response acquired " + String(millis() - methodStart)); @@ -847,6 +853,7 @@ void EspnowMeshBackend::espnowReceiveCallback(uint8_t *macaddr, uint8_t *dataArr } setSenderMac(macaddr); + espnowGetTransmissionMac(dataArray, _senderAPMac); setReceivedEncryptedMessage(usesEncryption(messageID)); getResponseHandler()(totalMessage, *this); } @@ -1135,39 +1142,46 @@ transmission_status_t EspnowMeshBackend::espnowSendToNodeUnsynchronized(const St ////// Transmit ////// - _espnowSendConfirmed = false; - uint32_t transmissionStartTime = millis(); - - while(!_espnowSendConfirmed && millis() - transmissionStartTime < getEspnowTransmissionTimeout()) + uint32_t retransmissions = 0; + if(messageType == 'B') + retransmissions = espnowInstance->getBroadcastTransmissionRedundancy(); + + for(uint32_t i = 0; i <= retransmissions; i++) { - if(esp_now_send(_transmissionTargetBSSID, transmission, transmissionSize) == 0) // == 0 => Success - { - uint32_t transmissionAttemptStart = millis(); - while(!_espnowSendConfirmed - && (millis() - transmissionAttemptStart < getEspnowRetransmissionInterval()) - && (millis() - transmissionStartTime < getEspnowTransmissionTimeout())) - { - delay(1); // Note that callbacks can be called during delay time, so it is possible to receive a transmission during this delay. - } - } - - if(_espnowSendConfirmed) + _espnowSendConfirmed = false; + uint32_t transmissionStartTime = millis(); + + while(!_espnowSendConfirmed && millis() - transmissionStartTime < getEspnowTransmissionTimeout()) { - if(messageStart) - { - if(encryptedConnection && !usesConstantSessionKey(messageType) && encryptedConnection->getOwnSessionKey() == messageID) - { - encryptedConnection->setDesync(false); - encryptedConnection->incrementOwnSessionKey(); + if(esp_now_send(_transmissionTargetBSSID, transmission, transmissionSize) == 0) // == 0 => Success + { + uint32_t transmissionAttemptStart = millis(); + while(!_espnowSendConfirmed + && (millis() - transmissionAttemptStart < getEspnowRetransmissionInterval()) + && (millis() - transmissionStartTime < getEspnowTransmissionTimeout())) + { + delay(1); // Note that callbacks can be called during delay time, so it is possible to receive a transmission during this delay. } + } + + if(_espnowSendConfirmed) + { + if(messageStart) + { + if(encryptedConnection && !usesConstantSessionKey(messageType) && encryptedConnection->getOwnSessionKey() == messageID) + { + encryptedConnection->setDesync(false); + encryptedConnection->incrementOwnSessionKey(); + } + + messageStart = false; + } - messageStart = false; - } - - break; + break; + } } } - + if(!_espnowSendConfirmed) { _transmissionsFailed++; @@ -1395,6 +1409,18 @@ uint8_t *EspnowMeshBackend::getSenderMac(uint8_t *macArray) return macArray; } +void EspnowMeshBackend::setSenderAPMac(uint8_t *macArray) +{ + std::copy_n(macArray, 6, _senderAPMac); +} + +String EspnowMeshBackend::getSenderAPMac() {return macToString(_senderAPMac);} +uint8_t *EspnowMeshBackend::getSenderAPMac(uint8_t *macArray) +{ + std::copy_n(_senderAPMac, 6, macArray); + return macArray; +} + void EspnowMeshBackend::setReceivedEncryptedMessage(bool receivedEncryptedMessage) { _receivedEncryptedMessage = receivedEncryptedMessage; } bool EspnowMeshBackend::receivedEncryptedMessage() {return _receivedEncryptedMessage;} @@ -2241,6 +2267,9 @@ void EspnowMeshBackend::broadcast(const String &message) espnowSendToNode(message, broadcastMac, 'B', this); } +void EspnowMeshBackend::setBroadcastTransmissionRedundancy(uint8_t redundancy) { _broadcastTransmissionRedundancy = redundancy; } +uint8_t EspnowMeshBackend::getBroadcastTransmissionRedundancy() { return _broadcastTransmissionRedundancy; } + void EspnowMeshBackend::sendStoredEspnowMessages(const ExpiringTimeTracker *estimatedMaxDurationTracker) { sendPeerRequestConfirmations(estimatedMaxDurationTracker); @@ -2532,7 +2561,7 @@ String EspnowMeshBackend::serializeUnencryptedConnection() { using namespace JsonTranslator; - // Returns: {"connectionState":{"uMessageID":"123"}} + // Returns: {"connectionState":{"unencMsgID":"123"}} return jsonConnectionState + createJsonEndPair(jsonUnencryptedMessageID, String(_unencryptedMessageID)); } diff --git a/libraries/ESP8266WiFiMesh/src/EspnowMeshBackend.h b/libraries/ESP8266WiFiMesh/src/EspnowMeshBackend.h index ec935d9c65..ad1b20a910 100644 --- a/libraries/ESP8266WiFiMesh/src/EspnowMeshBackend.h +++ b/libraries/ESP8266WiFiMesh/src/EspnowMeshBackend.h @@ -132,7 +132,7 @@ class EspnowMeshBackend : public MeshBackendBase { public: /** - * WiFiMesh Constructor method. Creates a WiFi Mesh Node, ready to be initialised. + * ESP-NOW constructor method. Creates an ESP-NOW node, ready to be initialised. * * @param requestHandler The callback handler for dealing with received requests. Takes a string as an argument which * is the request string received from another node and returns the string to send back. @@ -141,6 +141,8 @@ class EspnowMeshBackend : public MeshBackendBase { * @param networkFilter The callback handler for deciding which WiFi networks to connect to. * @param broadcastFilter The callback handler for deciding which ESP-NOW broadcasts to accept. * @param meshPassword The WiFi password for the mesh network. + * @param espnowEncryptionKey An uint8_t array containing the key used by this EspnowMeshBackend instance for creating encrypted ESP-NOW connections. + * @param espnowHashKey An uint8_t array containing the secret key used by this EspnowMeshBackend to generate HMACs for encrypted ESP-NOW connections. * @param ssidPrefix The prefix (first part) of the node SSID. * @param ssidSuffix The suffix (last part) of the node SSID. * @param verboseMode Determines if we should print the events occurring in the library to Serial. Off by default. This setting is shared by all EspnowMeshBackend instances. @@ -293,6 +295,15 @@ class EspnowMeshBackend : public MeshBackendBase { */ void broadcast(const String &message); + /** + * Set the number of redundant transmissions that will be made for every broadcast. + * A greater number increases the likelihood that the broadcast is received, but also means it takes longer time to send. + * + * @param redundancy The number of extra transmissions to make of each broadcast. Defaults to 1. + */ + void setBroadcastTransmissionRedundancy(uint8_t redundancy); + uint8_t getBroadcastTransmissionRedundancy(); + /** * Set the EspnowMeshBackend instance responsible for handling incoming requests. The requestHandler of the instance will be called upon receiving ESP-NOW requests. * @@ -518,6 +529,23 @@ class EspnowMeshBackend : public MeshBackendBase { */ uint8_t *getSenderMac(uint8_t *macArray); + /** + * Get the AP MAC address of the sender of the most recently received ESP-NOW request, response or broadcast to this EspnowMeshBackend instance. + * Returns a String. + * + * @return A String filled with a hexadecimal representation of the AP MAC, without delimiters. + */ + String getSenderAPMac(); + + /** + * Get the AP MAC address of the sender of the most recently received ESP-NOW request, response or broadcast to this EspnowMeshBackend instance. + * Returns a uint8_t array. + * + * @param macArray The array that should store the MAC address. Must be at least 6 bytes. + * @return macArray filled with the sender AP MAC. + */ + uint8_t *getSenderAPMac(uint8_t *macArray); + /** * Get whether the ESP-NOW request, response or broadcast which was most recently received by this EspnowMeshBackend instance was encrypted or not. * @@ -714,6 +742,13 @@ class EspnowMeshBackend : public MeshBackendBase { */ void setSenderMac(uint8_t *macArray); + /** + * Set the MAC address considered to be the AP MAC of the sender of the most recently received ESP-NOW request, response or broadcast. + * + * @param macArray An uint8_t array which contains the MAC address to store. The method will store the first 6 bytes of the array. + */ + void setSenderAPMac(uint8_t *macArray); + /** * Set whether the most recently received ESP-NOW request, response or broadcast is presented as having been encrypted or not. * @@ -875,6 +910,8 @@ class EspnowMeshBackend : public MeshBackendBase { broadcastFilterType _broadcastFilter; + uint8_t _broadcastTransmissionRedundancy = 1; + static String _ongoingPeerRequestNonce; static uint8_t _ongoingPeerRequestMac[6]; static EspnowMeshBackend *_ongoingPeerRequester; @@ -896,6 +933,7 @@ class EspnowMeshBackend : public MeshBackendBase { static uint32_t _unencryptedMessageID; uint8_t _senderMac[6] = {0}; + uint8_t _senderAPMac[6] = {0}; bool _receivedEncryptedMessage = false; static bool _espnowSendToNodeMutex; diff --git a/libraries/ESP8266WiFiMesh/src/FloodingMesh.cpp b/libraries/ESP8266WiFiMesh/src/FloodingMesh.cpp new file mode 100644 index 0000000000..c501417dd2 --- /dev/null +++ b/libraries/ESP8266WiFiMesh/src/FloodingMesh.cpp @@ -0,0 +1,503 @@ +/* + * Copyright (C) 2019 Anders Löfgren + * + * License (MIT license): + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include "FloodingMesh.h" +#include "TypeConversionFunctions.h" +#include "JsonTranslator.h" + +std::set FloodingMesh::availableFloodingMeshes = {}; + +char FloodingMesh::_broadcastMetadataDelimiter = 23; + +void floodingMeshDelay(uint32_t durationMs) +{ + uint32_t startingTime = millis(); + + while(millis() - startingTime < durationMs) + { + delay(1); + FloodingMesh::performMeshMaintainance(); + } +} + +FloodingMesh::FloodingMesh(messageHandlerType messageHandler, const String &meshPassword, const uint8_t espnowEncryptionKey[EspnowProtocolInterpreter::espnowEncryptionKeyLength], + const uint8_t espnowHashKey[EspnowProtocolInterpreter::espnowHashKeyLength], const String &ssidPrefix, + const String &ssidSuffix, bool verboseMode, uint8 meshWiFiChannel) + : _espnowBackend( + [this](const String &request, MeshBackendBase &meshInstance){ return _defaultRequestHandler(request, meshInstance); }, + [this](const String &response, MeshBackendBase &meshInstance){ return _defaultResponseHandler(response, meshInstance); }, + [this](int numberOfNetworks, MeshBackendBase &meshInstance){ return _defaultNetworkFilter(numberOfNetworks, meshInstance); }, + [this](String &firstTransmission, EspnowMeshBackend &meshInstance){ return _defaultBroadcastFilter(firstTransmission, meshInstance); }, + meshPassword, espnowEncryptionKey, espnowHashKey, ssidPrefix, ssidSuffix, verboseMode, meshWiFiChannel) +{ + setMessageHandler(messageHandler); + restoreDefaultTransmissionOutcomesUpdateHook(); +} + +FloodingMesh::FloodingMesh(const String &serializedMeshState, messageHandlerType messageHandler, const String &meshPassword, + const uint8_t espnowEncryptionKey[EspnowProtocolInterpreter::espnowEncryptionKeyLength], + const uint8_t espnowHashKey[EspnowProtocolInterpreter::espnowHashKeyLength], const String &ssidPrefix, + const String &ssidSuffix, bool verboseMode, uint8 meshWiFiChannel) + : FloodingMesh(messageHandler, meshPassword, espnowEncryptionKey, espnowHashKey, ssidPrefix, ssidSuffix, verboseMode, meshWiFiChannel) +{ + loadMeshState(serializedMeshState); +} + +FloodingMesh::~FloodingMesh() +{ + availableFloodingMeshes.erase(this); +} + +void FloodingMesh::begin() +{ + // Initialise the mesh node + getEspnowMeshBackend().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. + // Note that only one AP can be active at a time in total, and this will always be the one which was last activated. + // Thus the AP is shared by all backends. + getEspnowMeshBackend().activateAP(); + + availableFloodingMeshes.insert(this); // Returns std::pair +} + +void FloodingMesh::performMeshMaintainance() +{ + for(FloodingMesh *meshInstance : availableFloodingMeshes) + { + meshInstance->performMeshInstanceMaintainance(); + } +} + +void FloodingMesh::performMeshInstanceMaintainance() +{ + EspnowMeshBackend::performEspnowMaintainance(); + + for(std::list>::iterator backlogIterator = _forwardingBacklog.begin(); backlogIterator != _forwardingBacklog.end(); ) + { + std::pair &messageData = *backlogIterator; + if(messageData.second) // message encrypted + { + _macIgnoreList = messageData.first.substring(0, 12) + ','; // The message should contain the messageID first + encryptedBroadcastKernel(messageData.first); + _macIgnoreList = ""; + } + else + { + broadcastKernel(messageData.first); + } + + backlogIterator = _forwardingBacklog.erase(backlogIterator); + + EspnowMeshBackend::performEspnowMaintainance(); // It is best to performEspnowMaintainance frequently to keep the Espnow backend responsive. Especially if each encryptedBroadcast takes a lot of time. + } +} + +String FloodingMesh::serializeMeshState() +{ + using namespace JsonTranslator; + + // Returns: {"meshState":{"connectionState":{"unencMsgID":"123"},"meshMsgCount":"123"}} + + String connectionState = getEspnowMeshBackend().serializeUnencryptedConnection(); + + return + "{\"meshState\":{" + + connectionState.substring(1, connectionState.length() - 1) + "," + + createJsonEndPair(jsonMeshMessageCount, String(_messageCount)); +} + +void FloodingMesh::loadMeshState(const String &serializedMeshState) +{ + using namespace JsonTranslator; + + if(!getMeshMessageCount(serializedMeshState, _messageCount)) + getEspnowMeshBackend().warningPrint("WARNING! serializedMeshState did not contain MeshMessageCount. Using default instead."); + + String connectionState = ""; + if(!getConnectionState(serializedMeshState, connectionState) || !getEspnowMeshBackend().addUnencryptedConnection(connectionState)) + { + getEspnowMeshBackend().warningPrint("WARNING! serializedMeshState did not contain unencryptedMessageID. Using default instead."); + } +} + +String FloodingMesh::generateMessageID() +{ + char messageCountArray[2] = { 0 }; + sprintf(messageCountArray, "%04X", _messageCount++); + uint8_t apMac[6] {0}; + return macToString(WiFi.softAPmacAddress(apMac)) + String(messageCountArray); // We use the AP MAC address as ID since it is what shows up during WiFi scans +} + +void FloodingMesh::broadcast(const String &message) +{ + assert(message.length() <= maxUnencryptedMessageSize()); + + String messageID = generateMessageID(); + + // Remove getEspnowMeshBackend().getMeshName() from the broadcastMetadata below to broadcast to all ESP-NOW nodes regardless of MeshName. + String targetMeshName = getEspnowMeshBackend().getMeshName(); + + broadcastKernel(targetMeshName + String(broadcastMetadataDelimiter()) + messageID + String(broadcastMetadataDelimiter()) + message); +} + +void FloodingMesh::broadcastKernel(const String &message) +{ + getEspnowMeshBackend().broadcast(message); +} + +void FloodingMesh::setBroadcastReceptionRedundancy(uint8_t redundancy) +{ + assert(redundancy < 255); + _broadcastReceptionRedundancy = redundancy; +} +uint8_t FloodingMesh::getBroadcastReceptionRedundancy() { return _broadcastReceptionRedundancy; } + +void FloodingMesh::encryptedBroadcast(const String &message) +{ + assert(message.length() <= maxEncryptedMessageSize()); + + String messageID = generateMessageID(); + + encryptedBroadcastKernel(messageID + String(broadcastMetadataDelimiter()) + message); +} + +void FloodingMesh::encryptedBroadcastKernel(const String &message) +{ + getEspnowMeshBackend().attemptAutoEncryptingTransmission(message); +} + +void FloodingMesh::clearMessageLogs() +{ + _messageIDs.clear(); + std::queue().swap(_messageIdOrder); +} + +void FloodingMesh::clearForwardingBacklog() +{ + _forwardingBacklog.clear(); +} + +void FloodingMesh::setMessageHandler(messageHandlerType messageHandler) { _messageHandler = messageHandler; } +FloodingMesh::messageHandlerType FloodingMesh::getMessageHandler() { return _messageHandler; } + +void FloodingMesh::setOriginMac(uint8_t *macArray) +{ + std::copy_n(macArray, 6, _originMac); +} + +String FloodingMesh::getOriginMac() { return macToString(_originMac); } +uint8_t *FloodingMesh::getOriginMac(uint8_t *macArray) +{ + std::copy_n(_originMac, 6, macArray); + return macArray; +} + +uint32_t FloodingMesh::maxUnencryptedMessageSize() +{ + return getEspnowMeshBackend().getMaxMessageLength() - MESSAGE_ID_LENGTH - (getEspnowMeshBackend().getMeshName().length() + 1); // Need room for mesh name + delimiter +} + +uint32_t FloodingMesh::maxEncryptedMessageSize() +{ + // Need 1 extra delimiter character for maximum metadata efficiency (makes it possible to store exactly 18 MACs in metadata by adding an extra transmission) + return getEspnowMeshBackend().getMaxMessageLength() - MESSAGE_ID_LENGTH - 1; +} + +void FloodingMesh::setMessageLogSize(uint16_t messageLogSize) +{ + assert(messageLogSize >= 1); + _messageLogSize = messageLogSize; +} +uint16_t FloodingMesh::messageLogSize() { return _messageLogSize; } + +void FloodingMesh::setBroadcastMetadataDelimiter(char broadcastMetadataDelimiter) +{ + // Using HEX number characters as a delimiter is a bad idea regardless of broadcast type, since they are always in the broadcast metadata + assert(broadcastMetadataDelimiter < 48 || 57 < broadcastMetadataDelimiter); + assert(broadcastMetadataDelimiter < 65 || 70 < broadcastMetadataDelimiter); + + _broadcastMetadataDelimiter = broadcastMetadataDelimiter; +} +char FloodingMesh::broadcastMetadataDelimiter() { return _broadcastMetadataDelimiter; } + +EspnowMeshBackend &FloodingMesh::getEspnowMeshBackend() +{ + return _espnowBackend; +} + +bool FloodingMesh::insertPreliminaryMessageID(uint64_t messageID) +{ + uint8_t apMacArray[6] = { 0 }; + if(messageID >> 16 == macToUint64(WiFi.softAPmacAddress(apMacArray))) + return false; // The node should not receive its own messages. + + auto insertionResult = _messageIDs.emplace(messageID, 0); // Returns std::pair + + if(insertionResult.second) // Insertion succeeded. + updateMessageQueue(insertionResult.first); + else if(insertionResult.first->second < getBroadcastReceptionRedundancy()) // messageID exists but not with desired redundancy + insertionResult.first->second++; + else + return false; // messageID already existed in _messageIDs with desired redundancy + + return true; +} + +bool FloodingMesh::insertCompletedMessageID(uint64_t messageID) +{ + uint8_t apMacArray[6] = { 0 }; + if(messageID >> 16 == macToUint64(WiFi.softAPmacAddress(apMacArray))) + return false; // The node should not receive its own messages. + + auto insertionResult = _messageIDs.emplace(messageID, MESSAGE_COMPLETE); // Returns std::pair + + if(insertionResult.second) // Insertion succeeded. + updateMessageQueue(insertionResult.first); + else if(insertionResult.first->second < MESSAGE_COMPLETE) // messageID exists but is not complete + insertionResult.first->second = MESSAGE_COMPLETE; + else + return false; // messageID already existed in _messageIDs and is complete + + return true; +} + +void FloodingMesh::updateMessageQueue(messageQueueElementType messageIterator) +{ + _messageIdOrder.emplace(messageIterator); + + if(_messageIDs.size() > messageLogSize()) + { + _messageIDs.erase(_messageIdOrder.front()); + _messageIdOrder.pop(); + assert(_messageIDs.size() == messageLogSize()); // If this is false we either have too many elements in messageIDs or we deleted too many elements. + assert(_messageIDs.size() == _messageIdOrder.size()); // The containers should always be in sync + } +} + +void FloodingMesh::restoreDefaultRequestHandler() +{ + getEspnowMeshBackend().setRequestHandler([this](const String &request, MeshBackendBase &meshInstance){ return _defaultRequestHandler(request, meshInstance); }); +} + +void FloodingMesh::restoreDefaultResponseHandler() +{ + getEspnowMeshBackend().setResponseHandler([this](const String &response, MeshBackendBase &meshInstance){ return _defaultResponseHandler(response, meshInstance); }); +} + +void FloodingMesh::restoreDefaultNetworkFilter() +{ + getEspnowMeshBackend().setNetworkFilter([this](int numberOfNetworks, MeshBackendBase &meshInstance){ return _defaultNetworkFilter(numberOfNetworks, meshInstance); }); +} + +void FloodingMesh::restoreDefaultBroadcastFilter() +{ + getEspnowMeshBackend().setBroadcastFilter([this](String &firstTransmission, EspnowMeshBackend &meshInstance){ return _defaultBroadcastFilter(firstTransmission, meshInstance); }); +} + +void FloodingMesh::restoreDefaultTransmissionOutcomesUpdateHook() +{ + /* Optional way of doing things. Lambda is supposedly better https://stackoverflow.com/a/36596295 . + + using namespace std::placeholders; + + getEspnowMeshBackend().setTransmissionOutcomesUpdateHook(std::bind(&FloodingMesh::_defaultTransmissionOutcomesUpdateHook, this, _1)); + */ + + getEspnowMeshBackend().setTransmissionOutcomesUpdateHook([this](MeshBackendBase &meshInstance){ return _defaultTransmissionOutcomesUpdateHook(meshInstance); }); +} + +/** + * Callback for when other nodes send you a request + * + * @param request The request string received from another node in the mesh + * @param meshInstance The MeshBackendBase instance that called the function. + * @return The string to send back to the other node. For ESP-NOW, return an empy string ("") if no response should be sent. + */ +String FloodingMesh::_defaultRequestHandler(const String &request, MeshBackendBase &meshInstance) +{ + (void)meshInstance; // This is useful to remove a "unused parameter" compiler warning. Does nothing else. + + String broadcastTarget = ""; + String remainingRequest = ""; + + if(request.charAt(0) == broadcastMetadataDelimiter()) + { + int32_t broadcastTargetEndIndex = request.indexOf(broadcastMetadataDelimiter(), 1); + + if(broadcastTargetEndIndex == -1) + return ""; // broadcastMetadataDelimiter not found + + broadcastTarget = request.substring(1, broadcastTargetEndIndex + 1); // Include delimiter + remainingRequest = request.substring(broadcastTargetEndIndex + 1); + } + else + { + remainingRequest = request; + } + + int32_t messageIDEndIndex = remainingRequest.indexOf(broadcastMetadataDelimiter()); + + if(messageIDEndIndex == -1) + return ""; // broadcastMetadataDelimiter not found + + uint64_t messageID = stringToUint64(remainingRequest.substring(0, messageIDEndIndex)); + + if(insertCompletedMessageID(messageID)) + { + uint8_t originMacArray[6] = { 0 }; + setOriginMac(uint64ToMac(messageID >> 16, originMacArray)); + + String message = remainingRequest.substring(messageIDEndIndex + 1); + + if(getMessageHandler()(message, *this)) + { + message = broadcastTarget + remainingRequest.substring(0, messageIDEndIndex + 1) + message; + assert(message.length() <= _espnowBackend.getMaxMessageLength()); + _forwardingBacklog.emplace_back(message, getEspnowMeshBackend().receivedEncryptedMessage()); + } + } + + return ""; +} + +/** + * Callback for when you get a response from other nodes + * + * @param response The response string received from another node in the mesh + * @param meshInstance The MeshBackendBase instance that called the function. + * @return The status code resulting from the response, as an int + */ +transmission_status_t FloodingMesh::_defaultResponseHandler(const String &response, MeshBackendBase &meshInstance) +{ + transmission_status_t statusCode = TS_TRANSMISSION_COMPLETE; + + getEspnowMeshBackend().warningPrint("WARNING! Response to FloodingMesh broadcast received, but none is expected!"); + + (void)response; // This is useful to remove a "unused parameter" compiler warning. Does nothing else. + (void)meshInstance; // This is useful to remove a "unused parameter" compiler warning. Does nothing else. + + return statusCode; +} + +/** + * Callback used to decide which networks to connect to once a WiFi scan has been completed. + * + * @param numberOfNetworks The number of networks found in the WiFi scan. + * @param meshInstance The MeshBackendBase instance that called the function. + */ +void FloodingMesh::_defaultNetworkFilter(int numberOfNetworks, MeshBackendBase &meshInstance) +{ + // Note that the network index of a given node may change whenever a new scan is done. + for (int networkIndex = 0; networkIndex < numberOfNetworks; ++networkIndex) + { + String currentSSID = WiFi.SSID(networkIndex); + int meshNameIndex = currentSSID.indexOf(meshInstance.getMeshName()); + + // Connect to any APs which contain meshInstance.getMeshName() + if(meshNameIndex >= 0) + { + if(_macIgnoreList.indexOf(macToString(WiFi.BSSID(networkIndex))) == -1) // If the BSSID is not in the ignore list + { + if(EspnowMeshBackend *espnowInstance = meshBackendCast(&meshInstance)) + { + espnowInstance->connectionQueue().push_back(networkIndex); + } + else + { + Serial.println(String(F("Invalid mesh backend!"))); + } + } + } + } +} + +/** + * Callback used to decide which broadcast messages to accept. Only called for the first transmission in each broadcast. + * If true is returned from this callback, the first broadcast transmission is saved until the entire broadcast message has been received. + * The complete broadcast message will then be sent to the requestHandler. + * If false is returned from this callback, the broadcast message is discarded. + * Note that the BroadcastFilter may be called multiple times for messages that are discarded in this way, but is only called once for accepted messages. + * + * @param firstTransmission The first transmission of the broadcast. + * @param meshInstance The EspnowMeshBackend instance that called the function. + * + * @return True if the broadcast should be accepted. False otherwise. + */ +bool FloodingMesh::_defaultBroadcastFilter(String &firstTransmission, EspnowMeshBackend &meshInstance) +{ + // This broadcastFilter will accept a transmission if it contains the broadcastMetadataDelimiter + // and as metaData either no targetMeshName or a targetMeshName that matches the MeshName of meshInstance + // and insertPreliminaryMessageID(messageID) returns true. + + // Broadcast firstTransmission String structure: targetMeshName+messageID+message. + + int32_t metadataEndIndex = firstTransmission.indexOf(broadcastMetadataDelimiter()); + + if(metadataEndIndex == -1) + return false; // broadcastMetadataDelimiter not found + + String targetMeshName = firstTransmission.substring(0, metadataEndIndex); + + if(targetMeshName != "" && meshInstance.getMeshName() != targetMeshName) + { + return false; // Broadcast is for another mesh network + } + else + { + int32_t messageIDEndIndex = firstTransmission.indexOf(broadcastMetadataDelimiter(), metadataEndIndex + 1); + + if(messageIDEndIndex == -1) + return false; // broadcastMetadataDelimiter not found + + uint64_t messageID = stringToUint64(firstTransmission.substring(metadataEndIndex + 1, messageIDEndIndex)); + + if(insertPreliminaryMessageID(messageID)) + { + // Add broadcast identifier to stored message and mark as accepted broadcast. + firstTransmission = String(broadcastMetadataDelimiter()) + firstTransmission; + return true; + } + else + { + return false; // Broadcast has already been received the maximum number of times + } + } +} + +/** + * Once passed to the setTransmissionOutcomesUpdateHook method of the ESP-NOW backend, + * this function will be called after each update of the latestTransmissionOutcomes vector during attemptTransmission. + * (which happens after each individual transmission has finished) + * + * @param meshInstance The MeshBackendBase instance that called the function. + * + * @return True if attemptTransmission should continue with the next entry in the connectionQueue. False if attemptTransmission should stop. + */ +bool FloodingMesh::_defaultTransmissionOutcomesUpdateHook(MeshBackendBase &meshInstance) +{ + (void)meshInstance; // This is useful to remove a "unused parameter" compiler warning. Does nothing else. + + return true; +} diff --git a/libraries/ESP8266WiFiMesh/src/FloodingMesh.h b/libraries/ESP8266WiFiMesh/src/FloodingMesh.h new file mode 100644 index 0000000000..797ceebdd3 --- /dev/null +++ b/libraries/ESP8266WiFiMesh/src/FloodingMesh.h @@ -0,0 +1,285 @@ +/* + * Copyright (C) 2019 Anders Löfgren + * + * License (MIT license): + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#ifndef __FLOODINGMESH_H__ +#define __FLOODINGMESH_H__ + +#include "EspnowMeshBackend.h" +#include +#include +#include + +/** + * An alternative to standard delay(). Will continuously call performMeshMaintainance() during the waiting time, so that the FloodingMesh node remains responsive. + * Note that if there is a lot of FloodingMesh transmission activity to the node during the floodingMeshDelay, the desired duration may be overshot by several ms. + * Thus, if precise timing is required, use standard delay() instead. + * + * Should not be used inside callbacks since performMeshMaintainance() can alter the ESP-NOW state. + * + * @param durationMs The shortest allowed delay duration, in milliseconds. + */ +void floodingMeshDelay(uint32_t durationMs); + +class FloodingMesh { + +protected: + + typedef std::function messageHandlerType; + typedef std::unordered_map::iterator messageQueueElementType; + +public: + + /** + * FloodingMesh constructor method. Creates a FloodingMesh node, ready to be initialised. + * + * @param messageHandler The callback handler responsible for dealing with messages received from the mesh. + * @param meshPassword The WiFi password for the mesh network. + * @param espnowEncryptionKey An uint8_t array containing the key used by the EspnowMeshBackend instance for creating encrypted ESP-NOW connections. + * @param espnowHashKey An uint8_t array containing the secret key used by the EspnowMeshBackend instance to generate HMACs for encrypted ESP-NOW connections. + * @param ssidPrefix The prefix (first part) of the node SSID. + * @param ssidSuffix The suffix (last part) of the node SSID. + * @param verboseMode Determines if we should print the events occurring in the library to Serial. Off by default. This setting is shared by all EspnowMeshBackend instances. + * @param meshWiFiChannel The WiFi channel used by the mesh network. Valid values are integers from 1 to 13. Defaults to 1. + * WARNING: The ESP8266 has only one WiFi channel, and the the station/client mode is always prioritized for channel selection. + * This can cause problems if several mesh instances exist on the same ESP8266 and use different WiFi channels. + * In such a case, whenever the station of one mesh instance connects to an AP, it will silently force the + * WiFi channel of any active AP on the ESP8266 to match that of the station. This will cause disconnects and possibly + * make it impossible for other stations to detect the APs whose WiFi channels have changed. + * + */ + FloodingMesh(messageHandlerType messageHandler, const String &meshPassword, const uint8_t espnowEncryptionKey[EspnowProtocolInterpreter::espnowEncryptionKeyLength], + const uint8_t espnowHashKey[EspnowProtocolInterpreter::espnowHashKeyLength], const String &ssidPrefix, + const String &ssidSuffix, bool verboseMode = false, uint8 meshWiFiChannel = 1); + + /** + * This constructor should be used in combination with serializeMeshState() when the node has gone to sleep while other nodes stayed awake. + * Otherwise the message ID will be reset after sleep, which means that the nodes that stayed awake may ignore new broadcasts for a while. + * + * @param serializedMeshState A String with a serialized mesh node state that the node should use. + */ + FloodingMesh(const String &serializedMeshState, messageHandlerType messageHandler, const String &meshPassword, + const uint8_t espnowEncryptionKey[EspnowProtocolInterpreter::espnowEncryptionKeyLength], + const uint8_t espnowHashKey[EspnowProtocolInterpreter::espnowHashKeyLength], const String &ssidPrefix, + const String &ssidSuffix, bool verboseMode = false, uint8 meshWiFiChannel = 1); + + virtual ~FloodingMesh(); + + /** + * The method responsible for initialising this FloodingMesh instance. + * + * Since there is only one WiFi radio on the ESP8266, only the FloodingMesh instance that was the last to begin() will be visible to surrounding nodes. + * All FloodingMesh instances can still broadcast messages though, even if their AP is not visible. + */ + void begin(); + + /** + * Performs maintainance for all available Flooding Mesh instances + */ + static void performMeshMaintainance(); + + /** + * Performs maintainance for this particular Flooding Mesh instance + */ + void performMeshInstanceMaintainance(); + + /** + * Serialize the current mesh node state. Useful to save a state before the node goes to sleep. + * Note that this saves the current state only, so if a broadcast is made after this, the stored state is invalid. + * + * @return A string with the serialized current mesh node state. + */ + String serializeMeshState(); + + /** + * Make an unencrypted broadcast to the entire mesh network. + * + * It is recommended that there is at most one new message transmitted in the mesh every 10, 20, 30 ms for messages of length maxUnencryptedMessageSize()*n, + * where n is (roughly, depending on mesh name length) 1/4, 3/5 and 1 respectively. If transmissions are more frequent than this, message loss will increase. + * + * @param message The message to broadcast. Maximum message length is given by maxUnencryptedMessageSize(). The longer the message, the longer the transmission time. + */ + void broadcast(const String &message); + + /** + * Set the maximum number of redundant copies that will be received of every broadcast. (from different senders) + * A greater number increases the likelihood that at least one of the copies is received successfully, but will also use more RAM. + * + * @param redundancy The maximum number of extra copies that will be accepted. Defaults to 2. Valid values are 0 to 254. + */ + void setBroadcastReceptionRedundancy(uint8_t redundancy); + uint8_t getBroadcastReceptionRedundancy(); + + /** + * Make an encrypted broadcast to the entire mesh network. + * + * ########## WARNING! This an experimental feature. API may change at any time. Only use if you like it when things break. ########## + * Will be very slow compared to unencrypted broadcasts. Probably works OK in a small mesh with a maximum of one new message transmitted in the mesh every second. + * Because of the throughput difference, mixing encypted and unencrypted broadcasts is not recommended if there are frequent mesh broadcasts (multiple per second), + * since a lot of unencrypted broadcasts can build up while a single encrypted broadcast is sent. + * + * It is recommended that verboseMode is turned off if using this, to avoid slowdowns due to excessive Serial printing. + * + * @param message The message to broadcast. Maximum message length is given by maxEncryptedMessageSize(). The longer the message, the longer the transmission time. + */ + void encryptedBroadcast(const String &message); + + void clearMessageLogs(); + void clearForwardingBacklog(); + + /** + * Set the callback handler responsible for dealing with messages received from the mesh. + * + * @param messageHandler The message handler callback function to use. + */ + void setMessageHandler(messageHandlerType messageHandler); + messageHandlerType getMessageHandler(); + + /** + * Get the origin AP MAC address of the most recently received mesh message. + * Returns a String. + * + * @return A String filled with a hexadecimal representation of the MAC, without delimiters. + */ + String getOriginMac(); + + /** + * Get the origin AP MAC address of the most recently received mesh message. + * Returns a uint8_t array. + * + * @param macArray The array that should store the MAC address. Must be at least 6 bytes. + * @return macArray filled with the origin MAC. + */ + uint8_t *getOriginMac(uint8_t *macArray); + + /** + * The number of received messageID:s that will be stored by the node. Used to remember which messages have been received. + * Setting this too low will cause the same message to be received many times. + * Setting this too high will cause the node to run out of RAM. + * In practice, setting this value to more than 1337 is probably a bad idea since the node will run out of RAM quickly and crash as a result. + * + * Defaults to 100. + * + * @param messageLogSize The size of the message log for this FloodingMesh instance. Valid values are 1 to 65535 (uint16_t_max). + * If a value close to the maximum is chosen, there is a high risk the node will ignore transmissions on messageID rollover if they are sent only by one node + * (especially if some transmissions are missed), since the messageID also uses uint16_t. + */ + void setMessageLogSize(uint16_t messageLogSize); + uint16_t messageLogSize(); + + /** + * Hint: Use String.length() to get the ASCII length of a String. + * + * @return The maximum length in bytes an unencrypted ASCII message is allowed to be when broadcasted by this node. + * Note that non-ASCII characters usually require at least two bytes each. + * Also note that for unencrypted messages the maximum size will depend on getEspnowMeshBackend().getMeshName().length() + */ + uint32_t maxUnencryptedMessageSize(); + + /** + * Hint: Use String.length() to get the ASCII length of a String. + * + * @return The maximum length in bytes an encrypted ASCII message is allowed to be when broadcasted by this node. + * Note that non-ASCII characters usually require at least two bytes each. + */ + uint32_t maxEncryptedMessageSize(); + + /** + * Set the delimiter character used for metadata by every FloodingMesh instance. + * Using characters found in the mesh name or in HEX numbers is unwise, as is using ','. + * + * @param broadcastMetadataDelimiter The metadata delimiter character to use. + * Defaults to 23 = End-of-Transmission-Block (ETB) control character in ASCII + */ + static void setBroadcastMetadataDelimiter(char broadcastMetadataDelimiter); + static char broadcastMetadataDelimiter(); + + /* + * Gives you access to the EspnowMeshBackend used by the mesh node. + * The backend handles all mesh communication, and modifying it allows you to change every aspect of the mesh behaviour. + * Random interactions with the backend have a high chance of breaking the mesh network, + * and so are discouraged for those who prefer it when things just work. + */ + EspnowMeshBackend &getEspnowMeshBackend(); + + void restoreDefaultRequestHandler(); + void restoreDefaultResponseHandler(); + void restoreDefaultNetworkFilter(); + void restoreDefaultBroadcastFilter(); + void restoreDefaultTransmissionOutcomesUpdateHook(); + +protected: + + static std::set availableFloodingMeshes; + + String generateMessageID(); + + void broadcastKernel(const String &message); + + void encryptedBroadcastKernel(const String &message); + + bool insertPreliminaryMessageID(uint64_t messageID); + bool insertCompletedMessageID(uint64_t messageID); + void updateMessageQueue(messageQueueElementType messageIterator); + + void loadMeshState(const String &serializedMeshState); + + /** + * Set the MAC address considered to be the origin AP MAC address of the most recently received mesh message. + * + * @param macArray An uint8_t array which contains the MAC address to store. The method will store the first 6 bytes of the array. + */ + void setOriginMac(uint8_t *macArray); + +private: + + static const uint8_t MESSAGE_ID_LENGTH = 17; // 16 characters and one delimiter + static const uint8_t MESSAGE_COMPLETE = 255; + + EspnowMeshBackend _espnowBackend; + + messageHandlerType _messageHandler; + + uint16_t _messageCount = 0; + uint16_t _messageLogSize = 100; + + uint8_t _broadcastReceptionRedundancy = 2; + + static char _broadcastMetadataDelimiter; // Defaults to 23 = End-of-Transmission-Block (ETB) control character in ASCII + + uint8_t _originMac[6] = {0}; + + std::unordered_map _messageIDs = {}; + std::queue _messageIdOrder = {}; + std::list> _forwardingBacklog = {}; + + String _macIgnoreList = ""; + + String _defaultRequestHandler(const String &request, MeshBackendBase &meshInstance); + transmission_status_t _defaultResponseHandler(const String &response, MeshBackendBase &meshInstance); + void _defaultNetworkFilter(int numberOfNetworks, MeshBackendBase &meshInstance); + bool _defaultBroadcastFilter(String &firstTransmission, EspnowMeshBackend &meshInstance); + bool _defaultTransmissionOutcomesUpdateHook(MeshBackendBase &meshInstance); +}; + +#endif diff --git a/libraries/ESP8266WiFiMesh/src/JsonTranslator.h b/libraries/ESP8266WiFiMesh/src/JsonTranslator.h index 8ff320e4dd..93aadeb574 100644 --- a/libraries/ESP8266WiFiMesh/src/JsonTranslator.h +++ b/libraries/ESP8266WiFiMesh/src/JsonTranslator.h @@ -40,8 +40,8 @@ namespace JsonTranslator const String jsonNonce = "\"nonce\":"; const String jsonHmac = "\"hmac\":"; const String jsonDesync = "\"desync\":"; - const String jsonUnencryptedMessageID = "\"uMessageID\":"; - const String jsonMeshMessageCount = "\"mMessageCount\":"; + const String jsonUnencryptedMessageID = "\"unencMsgID\":"; + const String jsonMeshMessageCount = "\"meshMsgCount\":"; String createJsonPair(const String &valueIdentifier, const String &value); String createJsonEndPair(const String &valueIdentifier, const String &value); diff --git a/libraries/ESP8266WiFiMesh/src/MessageData.cpp b/libraries/ESP8266WiFiMesh/src/MessageData.cpp index 89508bd43d..721792433a 100644 --- a/libraries/ESP8266WiFiMesh/src/MessageData.cpp +++ b/libraries/ESP8266WiFiMesh/src/MessageData.cpp @@ -31,7 +31,6 @@ MessageData::MessageData(String &message, uint8_t transmissionsRemaining, uint32 TimeTracker(creationTimeMs) { _transmissionsExpected = transmissionsRemaining + 1; - assert(message.length() <= EspnowMeshBackend::getMaxMessageBytesPerTransmission()); // Should catch some cases where transmission is not null terminated. _totalMessage += message; _transmissionsReceived++; } diff --git a/libraries/ESP8266WiFiMesh/src/TcpIpMeshBackend.h b/libraries/ESP8266WiFiMesh/src/TcpIpMeshBackend.h index b0929c7a60..632bd075d8 100644 --- a/libraries/ESP8266WiFiMesh/src/TcpIpMeshBackend.h +++ b/libraries/ESP8266WiFiMesh/src/TcpIpMeshBackend.h @@ -38,7 +38,7 @@ class TcpIpMeshBackend : public MeshBackendBase { public: /** - * WiFiMesh Constructor method. Creates a WiFi Mesh Node, ready to be initialised. + * TCP/IP constructor method. Creates a TCP/IP node, ready to be initialised. * * @param requestHandler The callback handler for dealing with received requests. Takes a string as an argument which * is the request string received from another node and returns the string to send back. diff --git a/libraries/ESP8266WiFiMesh/src/TypeConversionFunctions.h b/libraries/ESP8266WiFiMesh/src/TypeConversionFunctions.h index 1e59dcc770..1511853399 100644 --- a/libraries/ESP8266WiFiMesh/src/TypeConversionFunctions.h +++ b/libraries/ESP8266WiFiMesh/src/TypeConversionFunctions.h @@ -82,7 +82,7 @@ uint8_t *stringToMac(const String &macString, uint8_t *macArray); uint64_t macToUint64(const uint8_t *macArray); /** - * Takes a uint64_t value and stores the bits of the first 6 bytes 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 macArray A uint8_t array that will hold the mac address once the function returns. Should have a size of at least 6 bytes. From 78812a7333f225d69ec89625dfee738a39453dde Mon Sep 17 00:00:00 2001 From: Anders Date: Sun, 3 Nov 2019 22:26:52 +0100 Subject: [PATCH 09/30] - Add HelloTcpIp.ino example for the TcpIpMeshBackend. - Update HelloEspnow.ino and HelloMesh.ino examples. - Fix bug with TransmissionOutcomesUpdateHook not being called when the TCP/IP backend retained WiFi.status() == WL_CONNECTED. --- .../examples/HelloEspnow/HelloEspnow.ino | 34 ++- .../examples/HelloMesh/HelloMesh.ino | 5 + .../examples/HelloTcpIp/HelloTcpIp.ino | 229 ++++++++++++++++++ .../ESP8266WiFiMesh/src/TcpIpMeshBackend.cpp | 2 + 4 files changed, 264 insertions(+), 6 deletions(-) create mode 100644 libraries/ESP8266WiFiMesh/examples/HelloTcpIp/HelloTcpIp.ino diff --git a/libraries/ESP8266WiFiMesh/examples/HelloEspnow/HelloEspnow.ino b/libraries/ESP8266WiFiMesh/examples/HelloEspnow/HelloEspnow.ino index d28e2c7079..b9ed1e172c 100644 --- a/libraries/ESP8266WiFiMesh/examples/HelloEspnow/HelloEspnow.ino +++ b/libraries/ESP8266WiFiMesh/examples/HelloEspnow/HelloEspnow.ino @@ -146,8 +146,9 @@ void networkFilter(int numberOfNetworks, MeshBackendBase &meshInstance) { If true is returned from this callback, the first broadcast transmission is saved until the entire broadcast message has been received. The complete broadcast message will then be sent to the requestHandler (manageRequest in this example). If false is returned from this callback, the broadcast message is discarded. + Note that the BroadcastFilter may be called multiple times for messages that are discarded in this way, but is only called once for accepted messages. - @param firstTransmission The first transmission of the broadcast. + @param firstTransmission The first transmission of the broadcast. Modifications to this String are passed on to the broadcast message. @param meshInstance The EspnowMeshBackend instance that called the function. @return True if the broadcast should be accepted. False otherwise. @@ -173,6 +174,25 @@ bool broadcastFilter(String &firstTransmission, EspnowMeshBackend &meshInstance) } } +/** + Once passed to the setTransmissionOutcomesUpdateHook method of the ESP-NOW backend, + this function will be called after each update of the latestTransmissionOutcomes vector during attemptTransmission. + (which happens after each individual transmission has finished) + + Example use cases is modifying getMessage() between transmissions, or aborting attemptTransmission before all nodes in the connectionQueue have been contacted. + + @param meshInstance The MeshBackendBase instance that called the function. + + @return True if attemptTransmission should continue with the next entry in the connectionQueue. False if attemptTransmission should stop. +*/ +bool exampleTransmissionOutcomesUpdateHook(MeshBackendBase &meshInstance) { + // Currently this is exactly the same as the default hook, but you can modify it to alter the behaviour of attemptTransmission. + + (void)meshInstance; // This is useful to remove a "unused parameter" compiler warning. Does nothing else. + + return true; +} + void setup() { // Prevents the flash memory from being worn out, see: https://github.com/esp8266/Arduino/issues/1054 . // This will however delay node WiFi start-up by about 700 ms. The delay is 900 ms if we otherwise would have stored the WiFi network we want to connect to. @@ -213,9 +233,11 @@ void setup() { espnowNode.activateAP(); // Storing our message in the EspnowMeshBackend instance is not required, but can be useful for organizing code, especially when using many EspnowMeshBackend instances. - // Note that calling espnowNode.attemptTransmission will replace the stored message with whatever message is transmitted. + // Note that calling the multi-recipient versions of espnowNode.attemptTransmission and espnowNode.attemptAutoEncryptingTransmission will replace the stored message with whatever message is transmitted. // Also note that the maximum allowed number of ASCII characters in a ESP-NOW message is given by EspnowMeshBackend::getMaxMessageLength(). espnowNode.setMessage(String(F("Hello world request #")) + String(requestNumber) + String(F(" from ")) + espnowNode.getMeshName() + espnowNode.getNodeID() + String(F("."))); + + espnowNode.setTransmissionOutcomesUpdateHook(exampleTransmissionOutcomesUpdateHook); } int32_t timeOfLastScan = -10000; @@ -226,7 +248,7 @@ void loop() { // Note that depending on the amount of responses to send and their length, this method can take tens or even hundreds of milliseconds to complete. // More intense transmission activity and less frequent calls to performEspnowMaintainance will likely cause the method to take longer to complete, so plan accordingly. - //Should not be used inside responseHandler, requestHandler or networkFilter callbacks since performEspnowMaintainance() can alter the ESP-NOW state. + //Should not be used inside responseHandler, requestHandler, networkFilter or broadcastFilter callbacks since performEspnowMaintainance() can alter the ESP-NOW state. EspnowMeshBackend::performEspnowMaintainance(); if (millis() - timeOfLastScan > 10000) { // Give other nodes some time to connect between data transfers. @@ -239,7 +261,7 @@ void loop() { timeOfLastScan = millis(); // Wait for response. espnowDelay continuously calls performEspnowMaintainance() so we will respond to ESP-NOW request while waiting. - // Should not be used inside responseHandler, requestHandler or networkFilter callbacks since performEspnowMaintainance() can alter the ESP-NOW state. + // Should not be used inside responseHandler, requestHandler, networkFilter or broadcastFilter callbacks since performEspnowMaintainance() can alter the ESP-NOW state. espnowDelay(100); // One way to check how attemptTransmission worked out @@ -283,7 +305,7 @@ void loop() { uint8_t targetBSSID[6] {0}; // We can create encrypted connections to individual nodes so that all ESP-NOW communication with the node will be encrypted. - if (espnowNode.connectionQueue()[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. String peerMac = macToString(targetBSSID); @@ -352,7 +374,7 @@ void loop() { // Or if we prefer we can just let the library automatically create brief encrypted connections which are long enough to transmit an encrypted message. // Note that encrypted responses will not be received, unless there already was an encrypted connection established with the peer before attemptAutoEncryptingTransmission was called. - // This can be remedied via the createPermanentConnections argument, though it must be noted that the maximum number of encrypted connections supported at a time is 6. + // This can be remedied via the requestPermanentConnections argument, though it must be noted that the maximum number of encrypted connections supported at a time is 6. espnowMessage = "This message is always encrypted, regardless of receiver."; Serial.println("\nTransmitting: " + espnowMessage); espnowNode.attemptAutoEncryptingTransmission(espnowMessage); diff --git a/libraries/ESP8266WiFiMesh/examples/HelloMesh/HelloMesh.ino b/libraries/ESP8266WiFiMesh/examples/HelloMesh/HelloMesh.ino index aedc758713..188cc225f5 100644 --- a/libraries/ESP8266WiFiMesh/examples/HelloMesh/HelloMesh.ino +++ b/libraries/ESP8266WiFiMesh/examples/HelloMesh/HelloMesh.ino @@ -150,8 +150,13 @@ void loop() { // Among other things, the method cleans up old ESP-NOW log entries (freeing up RAM) and forwards received mesh messages. // Note that depending on the amount of messages to forward and their length, this method can take tens or even hundreds of milliseconds to complete. // More intense transmission activity and less frequent calls to performMeshMaintainance will likely cause the method to take longer to complete, so plan accordingly. + // The maintainance methods should not be used inside the meshMessageHandler callback, since they can alter the mesh node state. The framework will alert you during runtime if you make this mistake. floodingMeshDelay(1); + // If you wish to transmit only to a single node, try using one of the following methods (requires the node to be within range and know the MAC of the recipient): + // Unencrypted: transmission_status_t floodingMesh.getEspnowMeshBackend().attemptTransmission(message, EspnowNetworkInfo(recipientMac)); + // Encrypted (slow): floodingMesh.getEspnowMeshBackend().attemptAutoEncryptingTransmission(message, EspnowNetworkInfo(recipientMac)); + if (theOne) { if (millis() - timeOfLastProclamation > 10000) { uint32_t startTime = millis(); diff --git a/libraries/ESP8266WiFiMesh/examples/HelloTcpIp/HelloTcpIp.ino b/libraries/ESP8266WiFiMesh/examples/HelloTcpIp/HelloTcpIp.ino new file mode 100644 index 0000000000..4b1547cb17 --- /dev/null +++ b/libraries/ESP8266WiFiMesh/examples/HelloTcpIp/HelloTcpIp.ino @@ -0,0 +1,229 @@ +#include +#include +#include +#include + +/** + 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). + The reason is that this approach will place the strings in flash memory which will help save RAM during program execution. + Reading strings from flash will be slower than reading them from RAM, + but this will be a negligible difference when printing them to Serial. + + More on F(), FPSTR() and PROGMEM: + https://github.com/esp8266/Arduino/issues/1143 + https://arduino-esp8266.readthedocs.io/en/latest/PROGMEM.html +*/ +const char exampleMeshName[] PROGMEM = "MeshNode_"; +const char exampleWiFiPassword[] PROGMEM = "ChangeThisWiFiPassword_TODO"; + +unsigned int requestNumber = 0; +unsigned int responseNumber = 0; + +String manageRequest(const String &request, MeshBackendBase &meshInstance); +transmission_status_t manageResponse(const String &response, MeshBackendBase &meshInstance); +void networkFilter(int numberOfNetworks, MeshBackendBase &meshInstance); + +/* Create the mesh node object */ +TcpIpMeshBackend tcpIpNode = TcpIpMeshBackend(manageRequest, manageResponse, networkFilter, FPSTR(exampleWiFiPassword), FPSTR(exampleMeshName), uint64ToString(ESP.getChipId()), true); + +/** + Callback for when other nodes send you a request + + @param request The request string received from another node in the mesh + @param meshInstance The MeshBackendBase instance that called the function. + @return The string to send back to the other node. For ESP-NOW, return an empy string ("") if no response should be sent. +*/ +String manageRequest(const String &request, MeshBackendBase &meshInstance) { + // We do not store strings in flash (via F()) in this function. + // The reason is that the other node will be waiting for our response, + // so keeping the strings in RAM will give a (small) improvement in response time. + // Of course, it is advised to adjust this approach based on RAM requirements. + + // To get the actual class of the polymorphic meshInstance, do as follows (meshBackendCast replaces dynamic_cast since RTTI is disabled) + if (EspnowMeshBackend *espnowInstance = meshBackendCast(&meshInstance)) { + String messageEncrypted = espnowInstance->receivedEncryptedMessage() ? ", Encrypted" : ", Unencrypted"; + Serial.print("ESP-NOW (" + espnowInstance->getSenderMac() + messageEncrypted + "): "); + } else if (TcpIpMeshBackend *tcpIpInstance = meshBackendCast(&meshInstance)) { + (void)tcpIpInstance; // This is useful to remove a "unused parameter" compiler warning. Does nothing else. + Serial.print("TCP/IP: "); + } else { + Serial.print("UNKNOWN!: "); + } + + /* Print out received message */ + // Only show first 100 characters because printing a large String takes a lot of time, which is a bad thing for a callback function. + // If you need to print the whole String it is better to store it and print it in the loop() later. + Serial.print("Request received: "); + Serial.println(request.substring(0, 100)); + + /* return a string to send back */ + return ("Hello world response #" + String(responseNumber++) + " from " + meshInstance.getMeshName() + meshInstance.getNodeID() + " with AP MAC " + WiFi.softAPmacAddress() + "."); +} + +/** + Callback for when you get a response from other nodes + + @param response The response string received from another node in the mesh + @param meshInstance The MeshBackendBase instance that called the function. + @return The status code resulting from the response, as an int +*/ +transmission_status_t manageResponse(const String &response, MeshBackendBase &meshInstance) { + transmission_status_t statusCode = TS_TRANSMISSION_COMPLETE; + + // To get the actual class of the polymorphic meshInstance, do as follows (meshBackendCast replaces dynamic_cast since RTTI is disabled) + if (EspnowMeshBackend *espnowInstance = meshBackendCast(&meshInstance)) { + String messageEncrypted = espnowInstance->receivedEncryptedMessage() ? ", Encrypted" : ", Unencrypted"; + Serial.print("ESP-NOW (" + espnowInstance->getSenderMac() + messageEncrypted + "): "); + } else if (TcpIpMeshBackend *tcpIpInstance = meshBackendCast(&meshInstance)) { + Serial.print("TCP/IP: "); + + // Getting the sent message like this will work as long as ONLY(!) TCP/IP is used. + // With TCP/IP the response will follow immediately after the request, so the stored message will not have changed. + // With ESP-NOW there is no guarantee when or if a response will show up, it can happen before or after the stored message is changed. + // So for ESP-NOW, adding unique identifiers in the response and request is required to associate a response with a request. + Serial.print(F("Request sent: ")); + Serial.println(tcpIpInstance->getCurrentMessage().substring(0, 100)); + + } else { + Serial.print("UNKNOWN!: "); + } + + /* Print out received message */ + // Only show first 100 characters because printing a large String takes a lot of time, which is a bad thing for a callback function. + // If you need to print the whole String it is better to store it and print it in the loop() later. + Serial.print(F("Response received: ")); + Serial.println(response.substring(0, 100)); + + return statusCode; +} + +/** + Callback used to decide which networks to connect to once a WiFi scan has been completed. + + @param numberOfNetworks The number of networks found in the WiFi scan. + @param meshInstance The MeshBackendBase instance that called the function. +*/ +void networkFilter(int numberOfNetworks, MeshBackendBase &meshInstance) { + // Note that the network index of a given node may change whenever a new scan is done. + for (int networkIndex = 0; networkIndex < numberOfNetworks; ++networkIndex) { + String currentSSID = WiFi.SSID(networkIndex); + int meshNameIndex = currentSSID.indexOf(meshInstance.getMeshName()); + + /* Connect to any _suitable_ APs which contain meshInstance.getMeshName() */ + if (meshNameIndex >= 0) { + uint64_t targetNodeID = stringToUint64(currentSSID.substring(meshNameIndex + meshInstance.getMeshName().length())); + + if (targetNodeID < stringToUint64(meshInstance.getNodeID())) { + if (EspnowMeshBackend *espnowInstance = meshBackendCast(&meshInstance)) { + espnowInstance->connectionQueue().push_back(networkIndex); + } else if (TcpIpMeshBackend *tcpIpInstance = meshBackendCast(&meshInstance)) { + tcpIpInstance->connectionQueue().push_back(networkIndex); + } else { + Serial.println(String(F("Invalid mesh backend!"))); + } + } + } + } +} + +/** + Once passed to the setTransmissionOutcomesUpdateHook method of the TCP/IP backend, + this function will be called after each update of the latestTransmissionOutcomes vector during attemptTransmission. + (which happens after each individual transmission has finished) + + Example use cases is modifying getMessage() between transmissions, or aborting attemptTransmission before all nodes in the connectionQueue have been contacted. + + @param meshInstance The MeshBackendBase instance that called the function. + + @return True if attemptTransmission should continue with the next entry in the connectionQueue. False if attemptTransmission should stop. +*/ +bool exampleTransmissionOutcomesUpdateHook(MeshBackendBase &meshInstance) { + // The default hook only returns true and does nothing else. + + if (TcpIpMeshBackend *tcpIpInstance = meshBackendCast(&meshInstance)) { + if (tcpIpInstance->latestTransmissionOutcomes().back().transmissionStatus() == TS_TRANSMISSION_COMPLETE) { + // Our last request got a response, so time to create a new request. + meshInstance.setMessage(String(F("Hello world request #")) + String(++requestNumber) + String(F(" from ")) + + meshInstance.getMeshName() + meshInstance.getNodeID() + String(F("."))); + } + } else { + Serial.println(String(F("Invalid mesh backend!"))); + } + + return true; +} + +void setup() { + // Prevents the flash memory from being worn out, see: https://github.com/esp8266/Arduino/issues/1054 . + // This will however delay node WiFi start-up by about 700 ms. The delay is 900 ms if we otherwise would have stored the WiFi network we want to connect to. + WiFi.persistent(false); + + Serial.begin(115200); + delay(50); // Wait for Serial. + + //yield(); // Use this if you don't want to wait for Serial. + + // The WiFi.disconnect() ensures that the WiFi is working correctly. If this is not done before receiving WiFi connections, + // those WiFi connections will take a long time to make or sometimes will not work at all. + WiFi.disconnect(); + + Serial.println(); + Serial.println(); + + Serial.println(F("Note that this library can use static IP:s for the nodes to speed up connection times.\n" + "Use the setStaticIP method as shown in this example to enable this.\n" + "Ensure that nodes connecting to the same AP have distinct static IP:s.\n" + "Also, remember to change the default mesh network password!\n\n")); + + Serial.println(F("Setting up mesh node...")); + + /* Initialise the mesh node */ + tcpIpNode.begin(); + tcpIpNode.activateAP(); // Each AP requires a separate server port. + tcpIpNode.setStaticIP(IPAddress(192, 168, 4, 22)); // Activate static IP mode to speed up connection times. + + // Storing our message in the TcpIpMeshBackend instance is not required, but can be useful for organizing code, especially when using many TcpIpMeshBackend instances. + // Note that calling the multi-recipient tcpIpNode.attemptTransmission will replace the stored message with whatever message is transmitted. + tcpIpNode.setMessage(String(F("Hello world request #")) + String(requestNumber) + String(F(" from ")) + tcpIpNode.getMeshName() + tcpIpNode.getNodeID() + String(F("."))); + + tcpIpNode.setTransmissionOutcomesUpdateHook(exampleTransmissionOutcomesUpdateHook); +} + +int32_t timeOfLastScan = -10000; +void loop() { + if (millis() - timeOfLastScan > 3000 // Give other nodes some time to connect between data transfers. + || (WiFi.status() != WL_CONNECTED && millis() - timeOfLastScan > 2000)) { // Scan for networks with two second intervals when not already connected. + + // attemptTransmission(message, scan, scanAllWiFiChannels, concludingDisconnect, initialDisconnect = false) + tcpIpNode.attemptTransmission(tcpIpNode.getMessage(), true, false, false); + timeOfLastScan = millis(); + + // One way to check how attemptTransmission worked out + if (tcpIpNode.latestTransmissionSuccessful()) { + Serial.println(F("Transmission successful.")); + } + + // Another way to check how attemptTransmission worked out + if (tcpIpNode.latestTransmissionOutcomes().empty()) { + Serial.println(F("No mesh AP found.")); + } else { + for (TransmissionOutcome &transmissionOutcome : tcpIpNode.latestTransmissionOutcomes()) { + if (transmissionOutcome.transmissionStatus() == TS_TRANSMISSION_FAILED) { + Serial.println(String(F("Transmission failed to mesh AP ")) + transmissionOutcome.SSID()); + } else if (transmissionOutcome.transmissionStatus() == TS_CONNECTION_FAILED) { + Serial.println(String(F("Connection failed to mesh AP ")) + transmissionOutcome.SSID()); + } else if (transmissionOutcome.transmissionStatus() == TS_TRANSMISSION_COMPLETE) { + // No need to do anything, transmission was successful. + } else { + Serial.println(String(F("Invalid transmission status for ")) + transmissionOutcome.SSID() + String(F("!"))); + assert(F("Invalid transmission status returned from responseHandler!") && false); + } + } + } + Serial.println(); + } else { + /* Accept any incoming connections */ + tcpIpNode.acceptRequests(); + } +} diff --git a/libraries/ESP8266WiFiMesh/src/TcpIpMeshBackend.cpp b/libraries/ESP8266WiFiMesh/src/TcpIpMeshBackend.cpp index 0b8ac7e19b..4d281fb508 100644 --- a/libraries/ESP8266WiFiMesh/src/TcpIpMeshBackend.cpp +++ b/libraries/ESP8266WiFiMesh/src/TcpIpMeshBackend.cpp @@ -451,6 +451,8 @@ void TcpIpMeshBackend::attemptTransmission(const String &message, bool scan, boo { transmission_status_t transmissionResult = attemptDataTransfer(); latestTransmissionOutcomes().push_back(TransmissionOutcome(constConnectionQueue().back(), transmissionResult)); + + getTransmissionOutcomesUpdateHook()(*this); } else { From afc88f26523543ed8c901e94f02bb86015415f9b Mon Sep 17 00:00:00 2001 From: Anders Date: Mon, 4 Nov 2019 12:59:49 +0100 Subject: [PATCH 10/30] - Improve HelloMesh.ino. --- .../examples/HelloMesh/HelloMesh.ino | 26 +++++++++++-------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/libraries/ESP8266WiFiMesh/examples/HelloMesh/HelloMesh.ino b/libraries/ESP8266WiFiMesh/examples/HelloMesh/HelloMesh.ino index 188cc225f5..0ba78e1907 100644 --- a/libraries/ESP8266WiFiMesh/examples/HelloMesh/HelloMesh.ino +++ b/libraries/ESP8266WiFiMesh/examples/HelloMesh/HelloMesh.ino @@ -50,21 +50,21 @@ bool useLED = false; // Change this to true if you wish the onboard LED to mark bool meshMessageHandler(String &message, FloodingMesh &meshInstance) { int32_t delimiterIndex = message.indexOf(meshInstance.broadcastMetadataDelimiter()); if (delimiterIndex == 0) { - Serial.print("Message received from STA " + meshInstance.getEspnowMeshBackend().getSenderMac() + ": "); - Serial.println(message.substring(1, 101)); + Serial.print("Message received from STA MAC " + meshInstance.getEspnowMeshBackend().getSenderMac() + ": "); + Serial.println(message.substring(2, 102)); - String potentialMac = message.substring(1, 13); - - if (potentialMac > theOneMac) { - if (theOne) { - if (useLED) { - digitalWrite(LED_BUILTIN, HIGH); // Turn LED off (LED is active low) - } + String potentialMac = message.substring(2, 14); + if (potentialMac >= theOneMac) { + if (potentialMac > theOneMac) { theOne = false; + theOneMac = potentialMac; } - theOneMac = potentialMac; + if (useLED && !theOne) { + bool ledState = message.charAt(1) == '1'; + digitalWrite(LED_BUILTIN, ledState); // Turn LED on/off (LED is active low) + } return true; } else { @@ -142,6 +142,7 @@ void setup() { int32_t timeOfLastProclamation = -10000; void loop() { + static bool ledState = 1; static uint32_t benchmarkCount = 0; static uint32_t loopStart = millis(); @@ -160,7 +161,10 @@ void loop() { if (theOne) { if (millis() - timeOfLastProclamation > 10000) { uint32_t startTime = millis(); - floodingMesh.broadcast(String(floodingMesh.broadcastMetadataDelimiter()) + theOneMac + " is The One."); + 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.maxUnencryptedMessageSize(). It is around 670 bytes by default. + floodingMesh.broadcast(String(floodingMesh.broadcastMetadataDelimiter()) + String(ledState) + theOneMac + " is The One."); Serial.println("Proclamation broadcast done in " + String(millis() - startTime) + " ms."); timeOfLastProclamation = millis(); From 3132325bf8b3116b2b974dcac26d2509606bb0b1 Mon Sep 17 00:00:00 2001 From: Anders Date: Tue, 5 Nov 2019 22:29:59 +0100 Subject: [PATCH 11/30] - Replace Crypto files with CryptoInterface which uses BearSSL as a cryptographic backend. - Move cryptographic functions from JsonTranslator to CryptoInterface. - Make AP activation separate from FloodingMesh::begin(). - Fix English bug. - Improve comments. --- .../examples/HelloEspnow/HelloEspnow.ino | 12 +- .../examples/HelloMesh/HelloMesh.ino | 18 +- libraries/ESP8266WiFiMesh/src/Crypto.cpp | 1002 ----------------- libraries/ESP8266WiFiMesh/src/Crypto.h | 254 ----- .../ESP8266WiFiMesh/src/CryptoInterface.cpp | 147 +++ .../ESP8266WiFiMesh/src/CryptoInterface.h | 120 ++ .../src/EncryptedConnectionData.cpp | 3 +- .../src/EncryptedConnectionLog.cpp | 2 +- .../ESP8266WiFiMesh/src/EspnowMeshBackend.cpp | 7 +- .../ESP8266WiFiMesh/src/EspnowMeshBackend.h | 19 +- .../ESP8266WiFiMesh/src/FloodingMesh.cpp | 22 +- libraries/ESP8266WiFiMesh/src/FloodingMesh.h | 21 +- .../ESP8266WiFiMesh/src/JsonTranslator.cpp | 79 +- .../ESP8266WiFiMesh/src/JsonTranslator.h | 9 +- .../src/TypeConversionFunctions.cpp | 1 - 15 files changed, 353 insertions(+), 1363 deletions(-) delete mode 100644 libraries/ESP8266WiFiMesh/src/Crypto.cpp delete mode 100644 libraries/ESP8266WiFiMesh/src/Crypto.h create mode 100644 libraries/ESP8266WiFiMesh/src/CryptoInterface.cpp create mode 100644 libraries/ESP8266WiFiMesh/src/CryptoInterface.h diff --git a/libraries/ESP8266WiFiMesh/examples/HelloEspnow/HelloEspnow.ino b/libraries/ESP8266WiFiMesh/examples/HelloEspnow/HelloEspnow.ino index b9ed1e172c..3d51c09f88 100644 --- a/libraries/ESP8266WiFiMesh/examples/HelloEspnow/HelloEspnow.ino +++ b/libraries/ESP8266WiFiMesh/examples/HelloEspnow/HelloEspnow.ino @@ -242,14 +242,14 @@ void setup() { int32_t timeOfLastScan = -10000; void loop() { - // The performEspnowMaintainance() method performs all the background operations for the EspnowMeshBackend. + // The performEspnowMaintenance() method performs all the background operations for the EspnowMeshBackend. // It is recommended to place it in the beginning of the loop(), unless there is a need to put it elsewhere. // Among other things, the method cleans up old Espnow log entries (freeing up RAM) and sends the responses you provide to Espnow requests. // Note that depending on the amount of responses to send and their length, this method can take tens or even hundreds of milliseconds to complete. - // More intense transmission activity and less frequent calls to performEspnowMaintainance will likely cause the method to take longer to complete, so plan accordingly. + // More intense transmission activity and less frequent calls to performEspnowMaintenance will likely cause the method to take longer to complete, so plan accordingly. - //Should not be used inside responseHandler, requestHandler, networkFilter or broadcastFilter callbacks since performEspnowMaintainance() can alter the ESP-NOW state. - EspnowMeshBackend::performEspnowMaintainance(); + //Should not be used inside responseHandler, requestHandler, networkFilter or broadcastFilter callbacks since performEspnowMaintenance() can alter the ESP-NOW state. + EspnowMeshBackend::performEspnowMaintenance(); if (millis() - timeOfLastScan > 10000) { // Give other nodes some time to connect between data transfers. Serial.println("\nPerforming unencrypted ESP-NOW transmissions."); @@ -260,8 +260,8 @@ void loop() { timeOfLastScan = millis(); - // Wait for response. espnowDelay continuously calls performEspnowMaintainance() so we will respond to ESP-NOW request while waiting. - // Should not be used inside responseHandler, requestHandler, networkFilter or broadcastFilter callbacks since performEspnowMaintainance() can alter the ESP-NOW state. + // Wait for response. espnowDelay continuously calls performEspnowMaintenance() so we will respond to ESP-NOW request while waiting. + // Should not be used inside responseHandler, requestHandler, networkFilter or broadcastFilter callbacks since performEspnowMaintenance() can alter the ESP-NOW state. espnowDelay(100); // One way to check how attemptTransmission worked out diff --git a/libraries/ESP8266WiFiMesh/examples/HelloMesh/HelloMesh.ino b/libraries/ESP8266WiFiMesh/examples/HelloMesh/HelloMesh.ino index 0ba78e1907..785c175835 100644 --- a/libraries/ESP8266WiFiMesh/examples/HelloMesh/HelloMesh.ino +++ b/libraries/ESP8266WiFiMesh/examples/HelloMesh/HelloMesh.ino @@ -1,3 +1,10 @@ +/** + This example makes every node broadcast their AP MAC to the rest of the network during the first 28 seconds, as long as the node thinks it has the highest AP MAC in the network. + Once 28 seconds have passed, the node that has the highest AP MAC will start broadcasting benchmark messages, which will allow you to see how many messages are lost at the other nodes. + If you have an onboard LED on your ESP8266 it is recommended that you change the useLED variable below to true. + That way you will get instant confirmation of the mesh communication without checking the Serial Monitor. +*/ + #include #include #include @@ -63,7 +70,7 @@ bool meshMessageHandler(String &message, FloodingMesh &meshInstance) { if (useLED && !theOne) { bool ledState = message.charAt(1) == '1'; - digitalWrite(LED_BUILTIN, ledState); // Turn LED on/off (LED is active low) + digitalWrite(LED_BUILTIN, ledState); // Turn LED on/off (LED_BUILTIN is active low) } return true; @@ -128,13 +135,14 @@ void setup() { Serial.println(F("Setting up mesh node...")); floodingMesh.begin(); + floodingMesh.activateAP(); uint8_t apMacArray[6] {0}; theOneMac = macToString(WiFi.softAPmacAddress(apMacArray)); if (useLED) { pinMode(LED_BUILTIN, OUTPUT); // Initialize the LED_BUILTIN pin as an output - digitalWrite(LED_BUILTIN, LOW); // Turn LED on (LED is active low) + digitalWrite(LED_BUILTIN, LOW); // Turn LED on (LED_BUILTIN is active low) } floodingMeshDelay(5000); // Give some time for user to start the nodes @@ -146,12 +154,12 @@ void loop() { static uint32_t benchmarkCount = 0; static uint32_t loopStart = millis(); - // The floodingMeshDelay() method performs all the background operations for the FloodingMesh (via FloodingMesh::performMeshMaintainance()). + // The floodingMeshDelay() method performs all the background operations for the FloodingMesh (via FloodingMesh::performMeshMaintenance()). // It is recommended to place one of these methods in the beginning of the loop(), unless there is a need to put them elsewhere. // Among other things, the method cleans up old ESP-NOW log entries (freeing up RAM) and forwards received mesh messages. // Note that depending on the amount of messages to forward and their length, this method can take tens or even hundreds of milliseconds to complete. - // More intense transmission activity and less frequent calls to performMeshMaintainance will likely cause the method to take longer to complete, so plan accordingly. - // The maintainance methods should not be used inside the meshMessageHandler callback, since they can alter the mesh node state. The framework will alert you during runtime if you make this mistake. + // More intense transmission activity and less frequent calls to performMeshMaintenance will likely cause the method to take longer to complete, so plan accordingly. + // The maintenance methods should not be used inside the meshMessageHandler callback, since they can alter the mesh node state. The framework will alert you during runtime if you make this mistake. floodingMeshDelay(1); // If you wish to transmit only to a single node, try using one of the following methods (requires the node to be within range and know the MAC of the recipient): diff --git a/libraries/ESP8266WiFiMesh/src/Crypto.cpp b/libraries/ESP8266WiFiMesh/src/Crypto.cpp deleted file mode 100644 index bd8f51dea9..0000000000 --- a/libraries/ESP8266WiFiMesh/src/Crypto.cpp +++ /dev/null @@ -1,1002 +0,0 @@ -/** - * An extremely minimal crypto library for Arduino devices. - * - * The SHA256 and AES implementations are derived from axTLS - * (http://axtls.sourceforge.net/), Copyright (c) 2008, Cameron Rich. - * - * Ported and refactored by Chris Ellis 2016. - * pkcs7 padding routines added by Mike Killewald Nov 26, 2017 (adopted from https://github.com/spaniakos/AES). - * - License - ======= - Balsa SCGI - Copyright (c) 2012, Chris Ellis - All rights reserved. - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions are met: - - 1. Redistributions of source code must retain the above copyright notice, this - list of conditions and the following disclaimer. - 2. Redistributions in binary form must reproduce the above copyright notice, - this list of conditions and the following disclaimer in the documentation - and/or other materials provided with the distribution. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND - ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR - ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES - (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; - LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND - ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -#include "Crypto.h" - -/** - * Byte order helpers - */ - - -//#if BYTE_ORDER == BIG_ENDIAN -/* -inline static uint16_t crypto_htons(uint16_t x) -{ - return x; -} - -inline static uint16_t crypto_ntohs(uint16_t x) -{ - return x; -} - -inline static uint32_t crypto_htonl(uint32_t x) -{ - return x; -} - -inline static uint32_t crypto_ntohl(uint32_t x) -{ - return x; -} -*/ -//#else - -inline static uint16_t crypto_htons(uint16_t x) -{ - return ( - ((x & 0xff) << 8) | - ((x & 0xff00) >> 8) - ); -} - -inline static uint16_t crypto_ntohs(uint16_t x) -{ - return ( - ((x & 0xff) << 8) | - ((x & 0xff00) >> 8) - ); -} - -inline static uint32_t crypto_htonl(uint32_t x) -{ - return ( - ((x & 0xff) << 24) | - ((x & 0xff00) << 8) | - ((x & 0xff0000UL) >> 8) | - ((x & 0xff000000UL) >> 24) - ); -} - -inline static uint32_t crypto_ntohl(uint32_t x) -{ - return ( - ((x & 0xff) << 24) | - ((x & 0xff00) << 8) | - ((x & 0xff0000UL) >> 8) | - ((x & 0xff000000UL) >> 24) - ); -} - -//#endif - -#define GET_UINT32(n,b,i) \ -{ \ - (n) = ((uint32_t) (b)[(i) ] << 24) \ - | ((uint32_t) (b)[(i) + 1] << 16) \ - | ((uint32_t) (b)[(i) + 2] << 8) \ - | ((uint32_t) (b)[(i) + 3] ); \ -} - -#define PUT_UINT32(n,b,i) \ -{ \ - (b)[(i) ] = (byte) ((n) >> 24); \ - (b)[(i) + 1] = (byte) ((n) >> 16); \ - (b)[(i) + 2] = (byte) ((n) >> 8); \ - (b)[(i) + 3] = (byte) ((n) ); \ -} - -static const byte sha256_padding[64] = -{ - 0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 -}; - -/** - * Initialize the SHA256 hash - */ -SHA256::SHA256() -{ - total[0] = 0; - total[1] = 0; - state[0] = 0x6A09E667; - state[1] = 0xBB67AE85; - state[2] = 0x3C6EF372; - state[3] = 0xA54FF53A; - state[4] = 0x510E527F; - state[5] = 0x9B05688C; - state[6] = 0x1F83D9AB; - state[7] = 0x5BE0CD19; -} - -void SHA256::SHA256_Process(const byte digest[64]) -{ - uint32_t temp1, temp2, W[64]; - uint32_t A, B, C, D, E, F, G, H; - - GET_UINT32(W[0], digest, 0); - GET_UINT32(W[1], digest, 4); - GET_UINT32(W[2], digest, 8); - GET_UINT32(W[3], digest, 12); - GET_UINT32(W[4], digest, 16); - GET_UINT32(W[5], digest, 20); - GET_UINT32(W[6], digest, 24); - GET_UINT32(W[7], digest, 28); - GET_UINT32(W[8], digest, 32); - GET_UINT32(W[9], digest, 36); - GET_UINT32(W[10], digest, 40); - GET_UINT32(W[11], digest, 44); - GET_UINT32(W[12], digest, 48); - GET_UINT32(W[13], digest, 52); - GET_UINT32(W[14], digest, 56); - GET_UINT32(W[15], digest, 60); - -#define SHR(x,n) ((x & 0xFFFFFFFF) >> n) -#define ROTR(x,n) (SHR(x,n) | (x << (32 - n))) - -#define S0(x) (ROTR(x, 7) ^ ROTR(x,18) ^ SHR(x, 3)) -#define S1(x) (ROTR(x,17) ^ ROTR(x,19) ^ SHR(x,10)) - -#define S2(x) (ROTR(x, 2) ^ ROTR(x,13) ^ ROTR(x,22)) -#define S3(x) (ROTR(x, 6) ^ ROTR(x,11) ^ ROTR(x,25)) - -#define F0(x,y,z) ((x & y) | (z & (x | y))) -#define F1(x,y,z) (z ^ (x & (y ^ z))) - -#define R(t) \ -( \ - W[t] = S1(W[t - 2]) + W[t - 7] + \ - S0(W[t - 15]) + W[t - 16] \ -) - -#define P(a,b,c,d,e,f,g,h,x,K) \ -{ \ - temp1 = h + S3(e) + F1(e,f,g) + K + x; \ - temp2 = S2(a) + F0(a,b,c); \ - d += temp1; h = temp1 + temp2; \ -} - - A = state[0]; - B = state[1]; - C = state[2]; - D = state[3]; - E = state[4]; - F = state[5]; - G = state[6]; - H = state[7]; - - P(A, B, C, D, E, F, G, H, W[ 0], 0x428A2F98); - P(H, A, B, C, D, E, F, G, W[ 1], 0x71374491); - P(G, H, A, B, C, D, E, F, W[ 2], 0xB5C0FBCF); - P(F, G, H, A, B, C, D, E, W[ 3], 0xE9B5DBA5); - P(E, F, G, H, A, B, C, D, W[ 4], 0x3956C25B); - P(D, E, F, G, H, A, B, C, W[ 5], 0x59F111F1); - P(C, D, E, F, G, H, A, B, W[ 6], 0x923F82A4); - P(B, C, D, E, F, G, H, A, W[ 7], 0xAB1C5ED5); - P(A, B, C, D, E, F, G, H, W[ 8], 0xD807AA98); - P(H, A, B, C, D, E, F, G, W[ 9], 0x12835B01); - P(G, H, A, B, C, D, E, F, W[10], 0x243185BE); - P(F, G, H, A, B, C, D, E, W[11], 0x550C7DC3); - P(E, F, G, H, A, B, C, D, W[12], 0x72BE5D74); - P(D, E, F, G, H, A, B, C, W[13], 0x80DEB1FE); - P(C, D, E, F, G, H, A, B, W[14], 0x9BDC06A7); - P(B, C, D, E, F, G, H, A, W[15], 0xC19BF174); - P(A, B, C, D, E, F, G, H, R(16), 0xE49B69C1); - P(H, A, B, C, D, E, F, G, R(17), 0xEFBE4786); - P(G, H, A, B, C, D, E, F, R(18), 0x0FC19DC6); - P(F, G, H, A, B, C, D, E, R(19), 0x240CA1CC); - P(E, F, G, H, A, B, C, D, R(20), 0x2DE92C6F); - P(D, E, F, G, H, A, B, C, R(21), 0x4A7484AA); - P(C, D, E, F, G, H, A, B, R(22), 0x5CB0A9DC); - P(B, C, D, E, F, G, H, A, R(23), 0x76F988DA); - P(A, B, C, D, E, F, G, H, R(24), 0x983E5152); - P(H, A, B, C, D, E, F, G, R(25), 0xA831C66D); - P(G, H, A, B, C, D, E, F, R(26), 0xB00327C8); - P(F, G, H, A, B, C, D, E, R(27), 0xBF597FC7); - P(E, F, G, H, A, B, C, D, R(28), 0xC6E00BF3); - P(D, E, F, G, H, A, B, C, R(29), 0xD5A79147); - P(C, D, E, F, G, H, A, B, R(30), 0x06CA6351); - P(B, C, D, E, F, G, H, A, R(31), 0x14292967); - P(A, B, C, D, E, F, G, H, R(32), 0x27B70A85); - P(H, A, B, C, D, E, F, G, R(33), 0x2E1B2138); - P(G, H, A, B, C, D, E, F, R(34), 0x4D2C6DFC); - P(F, G, H, A, B, C, D, E, R(35), 0x53380D13); - P(E, F, G, H, A, B, C, D, R(36), 0x650A7354); - P(D, E, F, G, H, A, B, C, R(37), 0x766A0ABB); - P(C, D, E, F, G, H, A, B, R(38), 0x81C2C92E); - P(B, C, D, E, F, G, H, A, R(39), 0x92722C85); - P(A, B, C, D, E, F, G, H, R(40), 0xA2BFE8A1); - P(H, A, B, C, D, E, F, G, R(41), 0xA81A664B); - P(G, H, A, B, C, D, E, F, R(42), 0xC24B8B70); - P(F, G, H, A, B, C, D, E, R(43), 0xC76C51A3); - P(E, F, G, H, A, B, C, D, R(44), 0xD192E819); - P(D, E, F, G, H, A, B, C, R(45), 0xD6990624); - P(C, D, E, F, G, H, A, B, R(46), 0xF40E3585); - P(B, C, D, E, F, G, H, A, R(47), 0x106AA070); - P(A, B, C, D, E, F, G, H, R(48), 0x19A4C116); - P(H, A, B, C, D, E, F, G, R(49), 0x1E376C08); - P(G, H, A, B, C, D, E, F, R(50), 0x2748774C); - P(F, G, H, A, B, C, D, E, R(51), 0x34B0BCB5); - P(E, F, G, H, A, B, C, D, R(52), 0x391C0CB3); - P(D, E, F, G, H, A, B, C, R(53), 0x4ED8AA4A); - P(C, D, E, F, G, H, A, B, R(54), 0x5B9CCA4F); - P(B, C, D, E, F, G, H, A, R(55), 0x682E6FF3); - P(A, B, C, D, E, F, G, H, R(56), 0x748F82EE); - P(H, A, B, C, D, E, F, G, R(57), 0x78A5636F); - P(G, H, A, B, C, D, E, F, R(58), 0x84C87814); - P(F, G, H, A, B, C, D, E, R(59), 0x8CC70208); - P(E, F, G, H, A, B, C, D, R(60), 0x90BEFFFA); - P(D, E, F, G, H, A, B, C, R(61), 0xA4506CEB); - P(C, D, E, F, G, H, A, B, R(62), 0xBEF9A3F7); - P(B, C, D, E, F, G, H, A, R(63), 0xC67178F2); - - state[0] += A; - state[1] += B; - state[2] += C; - state[3] += D; - state[4] += E; - state[5] += F; - state[6] += G; - state[7] += H; -#if defined ESP8266 - ESP.wdtFeed(); -#endif -} - -/** - * Accepts an array of octets as the next portion of the message. - */ -void SHA256::doUpdate(const byte * msg, uint32_t len) -{ - uint32_t left = total[0] & 0x3F; - uint32_t fill = 64 - left; - - total[0] += len; - total[0] &= 0xFFFFFFFF; - - if (total[0] < len) - total[1]++; - - if (left && len >= fill) - { - memcpy((void *) (buffer + left), (void *) msg, fill); - SHA256::SHA256_Process(buffer); - len -= fill; - msg += fill; - left = 0; - } - - while (len >= 64) - { - SHA256::SHA256_Process(msg); - len -= 64; - msg += 64; - } - - if (len) - { - memcpy((void *) (buffer + left), (void *) msg, len); - } -} - -/** - * Return the 256-bit message digest into the user's array - */ -void SHA256::doFinal(byte *digest) -{ - uint32_t last, padn; - uint32_t high, low; - byte msglen[8]; - - high = (total[0] >> 29) - | (total[1] << 3); - low = (total[0] << 3); - - PUT_UINT32(high, msglen, 0); - PUT_UINT32(low, msglen, 4); - - last = total[0] & 0x3F; - padn = (last < 56) ? (56 - last) : (120 - last); - - SHA256::doUpdate(sha256_padding, padn); - SHA256::doUpdate(msglen, 8); - - PUT_UINT32(state[0], digest, 0); - PUT_UINT32(state[1], digest, 4); - PUT_UINT32(state[2], digest, 8); - PUT_UINT32(state[3], digest, 12); - PUT_UINT32(state[4], digest, 16); - PUT_UINT32(state[5], digest, 20); - PUT_UINT32(state[6], digest, 24); - PUT_UINT32(state[7], digest, 28); -#if defined ESP8266 - ESP.wdtFeed(); -#endif -} - -bool SHA256::matches(const byte *expected) -{ - byte theDigest[SHA256_SIZE]; - doFinal(theDigest); - for (byte i = 0; i < SHA256_SIZE; i++) - { - if (expected[i] != theDigest[i]) - return false; - } -#if defined ESP8266 - ESP.wdtFeed(); -#endif - return true; -} - -/******************************************************************************/ - -#define rot1(x) (((x) << 24) | ((x) >> 8)) -#define rot2(x) (((x) << 16) | ((x) >> 16)) -#define rot3(x) (((x) << 8) | ((x) >> 24)) - -/* - * This cute trick does 4 'mul by two' at once. Stolen from - * Dr B. R. Gladman but I'm sure the u-(u>>7) is - * a standard graphics trick - * The key to this is that we need to xor with 0x1b if the top bit is set. - * a 1xxx xxxx 0xxx 0xxx First we mask the 7bit, - * b 1000 0000 0000 0000 then we shift right by 7 putting the 7bit in 0bit, - * c 0000 0001 0000 0000 we then subtract (c) from (b) - * d 0111 1111 0000 0000 and now we and with our mask - * e 0001 1011 0000 0000 - */ -#define mt 0x80808080 -#define ml 0x7f7f7f7f -#define mh 0xfefefefe -#define mm 0x1b1b1b1b -#define mul2(x,t) ((t)=((x)&mt), \ - ((((x)+(x))&mh)^(((t)-((t)>>7))&mm))) - -#define inv_mix_col(x,f2,f4,f8,f9) (\ - (f2)=mul2(x,f2), \ - (f4)=mul2(f2,f4), \ - (f8)=mul2(f4,f8), \ - (f9)=(x)^(f8), \ - (f8)=((f2)^(f4)^(f8)), \ - (f2)^=(f9), \ - (f4)^=(f9), \ - (f8)^=rot3(f2), \ - (f8)^=rot2(f4), \ - (f8)^rot1(f9)) - -/* - * AES S-box - */ -static const uint8_t aes_sbox[256] = -{ - 0x63,0x7C,0x77,0x7B,0xF2,0x6B,0x6F,0xC5, - 0x30,0x01,0x67,0x2B,0xFE,0xD7,0xAB,0x76, - 0xCA,0x82,0xC9,0x7D,0xFA,0x59,0x47,0xF0, - 0xAD,0xD4,0xA2,0xAF,0x9C,0xA4,0x72,0xC0, - 0xB7,0xFD,0x93,0x26,0x36,0x3F,0xF7,0xCC, - 0x34,0xA5,0xE5,0xF1,0x71,0xD8,0x31,0x15, - 0x04,0xC7,0x23,0xC3,0x18,0x96,0x05,0x9A, - 0x07,0x12,0x80,0xE2,0xEB,0x27,0xB2,0x75, - 0x09,0x83,0x2C,0x1A,0x1B,0x6E,0x5A,0xA0, - 0x52,0x3B,0xD6,0xB3,0x29,0xE3,0x2F,0x84, - 0x53,0xD1,0x00,0xED,0x20,0xFC,0xB1,0x5B, - 0x6A,0xCB,0xBE,0x39,0x4A,0x4C,0x58,0xCF, - 0xD0,0xEF,0xAA,0xFB,0x43,0x4D,0x33,0x85, - 0x45,0xF9,0x02,0x7F,0x50,0x3C,0x9F,0xA8, - 0x51,0xA3,0x40,0x8F,0x92,0x9D,0x38,0xF5, - 0xBC,0xB6,0xDA,0x21,0x10,0xFF,0xF3,0xD2, - 0xCD,0x0C,0x13,0xEC,0x5F,0x97,0x44,0x17, - 0xC4,0xA7,0x7E,0x3D,0x64,0x5D,0x19,0x73, - 0x60,0x81,0x4F,0xDC,0x22,0x2A,0x90,0x88, - 0x46,0xEE,0xB8,0x14,0xDE,0x5E,0x0B,0xDB, - 0xE0,0x32,0x3A,0x0A,0x49,0x06,0x24,0x5C, - 0xC2,0xD3,0xAC,0x62,0x91,0x95,0xE4,0x79, - 0xE7,0xC8,0x37,0x6D,0x8D,0xD5,0x4E,0xA9, - 0x6C,0x56,0xF4,0xEA,0x65,0x7A,0xAE,0x08, - 0xBA,0x78,0x25,0x2E,0x1C,0xA6,0xB4,0xC6, - 0xE8,0xDD,0x74,0x1F,0x4B,0xBD,0x8B,0x8A, - 0x70,0x3E,0xB5,0x66,0x48,0x03,0xF6,0x0E, - 0x61,0x35,0x57,0xB9,0x86,0xC1,0x1D,0x9E, - 0xE1,0xF8,0x98,0x11,0x69,0xD9,0x8E,0x94, - 0x9B,0x1E,0x87,0xE9,0xCE,0x55,0x28,0xDF, - 0x8C,0xA1,0x89,0x0D,0xBF,0xE6,0x42,0x68, - 0x41,0x99,0x2D,0x0F,0xB0,0x54,0xBB,0x16, -}; - -/* - * AES is-box - */ -static const uint8_t aes_isbox[256] = -{ - 0x52,0x09,0x6a,0xd5,0x30,0x36,0xa5,0x38, - 0xbf,0x40,0xa3,0x9e,0x81,0xf3,0xd7,0xfb, - 0x7c,0xe3,0x39,0x82,0x9b,0x2f,0xff,0x87, - 0x34,0x8e,0x43,0x44,0xc4,0xde,0xe9,0xcb, - 0x54,0x7b,0x94,0x32,0xa6,0xc2,0x23,0x3d, - 0xee,0x4c,0x95,0x0b,0x42,0xfa,0xc3,0x4e, - 0x08,0x2e,0xa1,0x66,0x28,0xd9,0x24,0xb2, - 0x76,0x5b,0xa2,0x49,0x6d,0x8b,0xd1,0x25, - 0x72,0xf8,0xf6,0x64,0x86,0x68,0x98,0x16, - 0xd4,0xa4,0x5c,0xcc,0x5d,0x65,0xb6,0x92, - 0x6c,0x70,0x48,0x50,0xfd,0xed,0xb9,0xda, - 0x5e,0x15,0x46,0x57,0xa7,0x8d,0x9d,0x84, - 0x90,0xd8,0xab,0x00,0x8c,0xbc,0xd3,0x0a, - 0xf7,0xe4,0x58,0x05,0xb8,0xb3,0x45,0x06, - 0xd0,0x2c,0x1e,0x8f,0xca,0x3f,0x0f,0x02, - 0xc1,0xaf,0xbd,0x03,0x01,0x13,0x8a,0x6b, - 0x3a,0x91,0x11,0x41,0x4f,0x67,0xdc,0xea, - 0x97,0xf2,0xcf,0xce,0xf0,0xb4,0xe6,0x73, - 0x96,0xac,0x74,0x22,0xe7,0xad,0x35,0x85, - 0xe2,0xf9,0x37,0xe8,0x1c,0x75,0xdf,0x6e, - 0x47,0xf1,0x1a,0x71,0x1d,0x29,0xc5,0x89, - 0x6f,0xb7,0x62,0x0e,0xaa,0x18,0xbe,0x1b, - 0xfc,0x56,0x3e,0x4b,0xc6,0xd2,0x79,0x20, - 0x9a,0xdb,0xc0,0xfe,0x78,0xcd,0x5a,0xf4, - 0x1f,0xdd,0xa8,0x33,0x88,0x07,0xc7,0x31, - 0xb1,0x12,0x10,0x59,0x27,0x80,0xec,0x5f, - 0x60,0x51,0x7f,0xa9,0x19,0xb5,0x4a,0x0d, - 0x2d,0xe5,0x7a,0x9f,0x93,0xc9,0x9c,0xef, - 0xa0,0xe0,0x3b,0x4d,0xae,0x2a,0xf5,0xb0, - 0xc8,0xeb,0xbb,0x3c,0x83,0x53,0x99,0x61, - 0x17,0x2b,0x04,0x7e,0xba,0x77,0xd6,0x26, - 0xe1,0x69,0x14,0x63,0x55,0x21,0x0c,0x7d -}; - -static const unsigned char Rcon[30]= -{ - 0x01,0x02,0x04,0x08,0x10,0x20,0x40,0x80, - 0x1b,0x36,0x6c,0xd8,0xab,0x4d,0x9a,0x2f, - 0x5e,0xbc,0x63,0xc6,0x97,0x35,0x6a,0xd4, - 0xb3,0x7d,0xfa,0xef,0xc5,0x91, -}; - -/* Perform doubling in Galois Field GF(2^8) using the irreducible polynomial - x^8+x^4+x^3+x+1 */ -static unsigned char AES_xtime(uint32_t x) -{ - return (x&0x80) ? (x<<1)^0x1b : x<<1; -} - - -/** - * Encrypt a single block (16 bytes) of data - */ -void AES::encrypt(uint32_t *data) -{ - /* To make this code smaller, generate the sbox entries on the fly. - * This will have a really heavy effect upon performance. - */ - uint32_t tmp[4]; - uint32_t tmp1, old_a0, a0, a1, a2, a3, row; - int curr_rnd; - int rounds = _rounds; - const uint32_t *k = _ks; - - /* Pre-round key addition */ - for (row = 0; row < 4; row++) - data[row] ^= *(k++); - - /* Encrypt one block. */ - for (curr_rnd = 0; curr_rnd < rounds; curr_rnd++) - { - /* Perform ByteSub and ShiftRow operations together */ - for (row = 0; row < 4; row++) - { - a0 = (uint32_t)aes_sbox[(data[row%4]>>24)&0xFF]; - a1 = (uint32_t)aes_sbox[(data[(row+1)%4]>>16)&0xFF]; - a2 = (uint32_t)aes_sbox[(data[(row+2)%4]>>8)&0xFF]; - a3 = (uint32_t)aes_sbox[(data[(row+3)%4])&0xFF]; - - /* Perform MixColumn iff not last round */ - if (curr_rnd < (rounds - 1)) - { - tmp1 = a0 ^ a1 ^ a2 ^ a3; - old_a0 = a0; - a0 ^= tmp1 ^ AES_xtime(a0 ^ a1); - a1 ^= tmp1 ^ AES_xtime(a1 ^ a2); - a2 ^= tmp1 ^ AES_xtime(a2 ^ a3); - a3 ^= tmp1 ^ AES_xtime(a3 ^ old_a0); - } - - tmp[row] = ((a0 << 24) | (a1 << 16) | (a2 << 8) | a3); - } - - /* KeyAddition - note that it is vital that this loop is separate from - the MixColumn operation, which must be atomic...*/ - for (row = 0; row < 4; row++) - data[row] = tmp[row] ^ *(k++); - } -#if defined ESP8266 - ESP.wdtFeed(); -#endif -} - -/** - * Decrypt a single block (16 bytes) of data - */ -void AES::decrypt(uint32_t *data) -{ - uint32_t tmp[4]; - uint32_t xt0,xt1,xt2,xt3,xt4,xt5,xt6; - uint32_t a0, a1, a2, a3, row; - int curr_rnd; - int rounds = _rounds; - const uint32_t *k = _ks + ((rounds+1)*4); - - /* pre-round key addition */ - for (row=4; row > 0;row--) - data[row-1] ^= *(--k); - - /* Decrypt one block */ - for (curr_rnd = 0; curr_rnd < rounds; curr_rnd++) - { - /* Perform ByteSub and ShiftRow operations together */ - for (row = 4; row > 0; row--) - { - a0 = aes_isbox[(data[(row+3)%4]>>24)&0xFF]; - a1 = aes_isbox[(data[(row+2)%4]>>16)&0xFF]; - a2 = aes_isbox[(data[(row+1)%4]>>8)&0xFF]; - a3 = aes_isbox[(data[row%4])&0xFF]; - - /* Perform MixColumn iff not last round */ - if (curr_rnd<(rounds-1)) - { - /* The MDS cofefficients (0x09, 0x0B, 0x0D, 0x0E) - are quite large compared to encryption; this - operation slows decryption down noticeably. */ - xt0 = AES_xtime(a0^a1); - xt1 = AES_xtime(a1^a2); - xt2 = AES_xtime(a2^a3); - xt3 = AES_xtime(a3^a0); - xt4 = AES_xtime(xt0^xt1); - xt5 = AES_xtime(xt1^xt2); - xt6 = AES_xtime(xt4^xt5); - - xt0 ^= a1^a2^a3^xt4^xt6; - xt1 ^= a0^a2^a3^xt5^xt6; - xt2 ^= a0^a1^a3^xt4^xt6; - xt3 ^= a0^a1^a2^xt5^xt6; - tmp[row-1] = ((xt0<<24)|(xt1<<16)|(xt2<<8)|xt3); - } - else - tmp[row-1] = ((a0<<24)|(a1<<16)|(a2<<8)|a3); - } - - for (row = 4; row > 0; row--) - data[row-1] = tmp[row-1] ^ *(--k); - } -#if defined ESP8266 - ESP.wdtFeed(); -#endif -} - -AES::AES(const uint8_t *key, const uint8_t *iv, AES_MODE mode, CIPHER_MODE cipherMode) -{ - _cipherMode = cipherMode; - - int i, ii; - uint32_t *W, tmp, tmp2; - const unsigned char *ip; - int words; - - _arr_pad[0] = 0x01; - _arr_pad[1] = 0x02; - _arr_pad[2] = 0x03; - _arr_pad[3] = 0x04; - _arr_pad[4] = 0x05; - _arr_pad[5] = 0x06; - _arr_pad[6] = 0x07; - _arr_pad[7] = 0x08; - _arr_pad[8] = 0x09; - _arr_pad[9] = 0x0a; - _arr_pad[10] = 0x0b; - _arr_pad[11] = 0x0c; - _arr_pad[12] = 0x0d; - _arr_pad[13] = 0x0e; - _arr_pad[14] = 0x0f; - - switch (mode) - { - case AES_MODE_128: - i = 10; - words = 4; - break; - - case AES_MODE_256: - i = 14; - words = 8; - break; - - default: /* fail silently */ - return; - } - - _rounds = i; - _key_size = words; - W = _ks; - for (i = 0; i < words; i+=2) - { - W[i+0]= ((uint32_t)key[ 0]<<24)| - ((uint32_t)key[ 1]<<16)| - ((uint32_t)key[ 2]<< 8)| - ((uint32_t)key[ 3] ); - W[i+1]= ((uint32_t)key[ 4]<<24)| - ((uint32_t)key[ 5]<<16)| - ((uint32_t)key[ 6]<< 8)| - ((uint32_t)key[ 7] ); - key += 8; - } - - ip = Rcon; - ii = 4 * (_rounds+1); - for (i = words; i> 8)&0xff]<<16; - tmp2|=(uint32_t)aes_sbox[(tmp>>16)&0xff]<<24; - tmp2|=(uint32_t)aes_sbox[(tmp>>24) ]; - tmp=tmp2^(((unsigned int)*ip)<<24); - ip++; - } - - if ((words == 8) && ((i % words) == 4)) - { - tmp2 =(uint32_t)aes_sbox[(tmp )&0xff] ; - tmp2|=(uint32_t)aes_sbox[(tmp>> 8)&0xff]<< 8; - tmp2|=(uint32_t)aes_sbox[(tmp>>16)&0xff]<<16; - tmp2|=(uint32_t)aes_sbox[(tmp>>24) ]<<24; - tmp=tmp2; - } - - W[i]=W[i-words]^tmp; - } - - /* copy the iv across */ - memcpy(_iv, iv, 16); - - /* Do we need to convert the key */ - if (_cipherMode == CIPHER_DECRYPT) - { - convertKey(); - } -#if defined ESP8266 - ESP.wdtFeed(); -#endif -} - -int AES::getSize() -{ - return _size; -} - -void AES::setSize(int size) -{ - _size = size; -} - -int AES::calcSizeAndPad(int in_size) -{ - in_size++; // +1 for null terminater on input string - int buf = round(in_size / AES_BLOCKSIZE) * AES_BLOCKSIZE; - _size = (buf <= in_size) ? buf + AES_BLOCKSIZE : buf; - _pad_size = _size - in_size; - return _size; -} - -void AES::padPlaintext(const uint8_t* in, uint8_t* out) -{ - memcpy(out, in, _size); - for (int i = _size - _pad_size; i < _size; i++) - { - out[i] = _arr_pad[_pad_size - 1]; - } -} - -bool AES::checkPad(uint8_t* in, int lsize) -{ - if (in[lsize-1] <= 0x0f) - { - int lpad = (int)in[lsize-1]; - for (int i = lsize - 1; i >= lsize-lpad; i--) - { - if (_arr_pad[lpad - 1] != in[i]) - { - return false; - } - } - } - else - { - return true; - } - return true; -} - -void AES::processNoPad(const uint8_t *in, uint8_t *out, int length) -{ - if (_cipherMode == CIPHER_ENCRYPT) - { - encryptCBC(in, out, length); - } - else - { - decryptCBC(in, out, length); - } -} - -void AES::process(const uint8_t *in, uint8_t *out, int length) -{ - if (_cipherMode == CIPHER_ENCRYPT) - { - calcSizeAndPad(length); - uint8_t in_pad[getSize()]; - padPlaintext(in, in_pad); - encryptCBC(in_pad, out, getSize()); - } - else - { - decryptCBC(in, out, length); - } -} - -void AES::encryptCBC(const uint8_t *in, uint8_t *out, int length) -{ - int i; - uint32_t tin[4], tout[4], iv[4]; - - memcpy(iv, _iv, AES_IV_SIZE); - for (i = 0; i < 4; i++) - tout[i] = crypto_ntohl(iv[i]); - - for (length -= AES_BLOCKSIZE; length >= 0; length -= AES_BLOCKSIZE) - { - uint32_t msg_32[4]; - uint32_t out_32[4]; - memcpy(msg_32, in, AES_BLOCKSIZE); - in += AES_BLOCKSIZE; - - for (i = 0; i < 4; i++) - tin[i] = crypto_ntohl(msg_32[i])^tout[i]; - - AES::encrypt(tin); - - for (i = 0; i < 4; i++) - { - tout[i] = tin[i]; - out_32[i] = crypto_htonl(tout[i]); - } - - memcpy(out, out_32, AES_BLOCKSIZE); - out += AES_BLOCKSIZE; - } - - for (i = 0; i < 4; i++) - iv[i] = crypto_htonl(tout[i]); - memcpy(_iv, iv, AES_IV_SIZE); -#if defined ESP8266 - ESP.wdtFeed(); -#endif -} - -void AES::decryptCBC(const uint8_t *in, uint8_t *out, int length) -{ - int i; - uint32_t tin[4], bufxor[4], tout[4], data[4], iv[4]; - - memcpy(iv, _iv, AES_IV_SIZE); - for (i = 0; i < 4; i++) - bufxor[i] = crypto_ntohl(iv[i]); - - for (length -= 16; length >= 0; length -= 16) - { - uint32_t msg_32[4]; - uint32_t out_32[4]; - memcpy(msg_32, in, AES_BLOCKSIZE); - in += AES_BLOCKSIZE; - - for (i = 0; i < 4; i++) - { - tin[i] = crypto_ntohl(msg_32[i]); - data[i] = tin[i]; - } - - AES::decrypt(data); - - for (i = 0; i < 4; i++) - { - tout[i] = data[i] ^ bufxor[i]; - bufxor[i] = tin[i]; - out_32[i] = crypto_htonl(tout[i]); - } - - memcpy(out, out_32, AES_BLOCKSIZE); - out += AES_BLOCKSIZE; - } - - for (i = 0; i < 4; i++) - iv[i] = crypto_htonl(bufxor[i]); - memcpy(_iv, iv, AES_IV_SIZE); -#if defined ESP8266 - ESP.wdtFeed(); -#endif -} - -void AES::convertKey() -{ - int i; - uint32_t *k,w,t1,t2,t3,t4; - - k = _ks; - k += 4; - - for (i= _rounds*4; i > 4; i--) - { - w= *k; - w = inv_mix_col(w,t1,t2,t3,t4); - *k++ =w; - } -} - -#if defined ESP8266 || defined ESP32 -/** - * ESP8266 and ESP32 specific hardware true random number generator. - * - * Acording to the ESP32 documentation, you should not call the tRNG - * faster than 5MHz - * - */ - -void RNG::fill(uint8_t *dst, unsigned int length) -{ - // ESP8266 and ESP32 only - for (uint32_t i = 0; i < length; i++) - { - dst[i] = get(); - } -#if defined ESP8266 - ESP.wdtFeed(); -#endif -} - -byte RNG::get() -{ -#if defined ESP32 - // ESP32 only - uint32_t* randReg = (uint32_t*) 0x3FF75144; - return (byte) *randReg; -#elif defined ESP8266 - // ESP8266 only - uint32_t* randReg = (uint32_t*) 0x3FF20E44L; - return (byte) *randReg; -#else - // NOT SUPPORTED - return 0; -#endif -} - -uint32_t RNG::getLong() -{ -#if defined ESP32 - // ESP32 only - uint32_t* randReg = (uint32_t*) 0x3FF75144; - return (byte) *randReg; -#elif defined ESP8266 - // ESP8266 only - uint32_t* randReg = (uint32_t*) 0x3FF20E44L; - return *randReg; -#else - // NOT SUPPORTED - return 0; -#endif -} -#endif - - -/** - * SHA256 HMAC - */ - -SHA256HMAC::SHA256HMAC(const byte *key, unsigned int keyLen) -{ - // sort out the key - byte theKey[SHA256HMAC_BLOCKSIZE]; - memset(theKey, 0, SHA256HMAC_BLOCKSIZE); - if (keyLen > SHA256HMAC_BLOCKSIZE) - { - // take a hash of the key - SHA256 keyHahser; - keyHahser.doUpdate(key, keyLen); - keyHahser.doFinal(theKey); - } - else - { - // we already set the buffer to 0s, so just copy keyLen - // bytes from key - memcpy(theKey, key, keyLen); - } - // explicitly zero pads - memset(_innerKey, 0, SHA256HMAC_BLOCKSIZE); - memset(_outerKey, 0, SHA256HMAC_BLOCKSIZE); - // compute the keys - blockXor(theKey, _innerKey, HMAC_IPAD, SHA256HMAC_BLOCKSIZE); - blockXor(theKey, _outerKey, HMAC_OPAD, SHA256HMAC_BLOCKSIZE); - // start the intermediate hash - _hash.doUpdate(_innerKey, SHA256HMAC_BLOCKSIZE); -} - -void SHA256HMAC::doUpdate(const byte *msg, unsigned int len) -{ - _hash.doUpdate(msg, len); -} - -void SHA256HMAC::doFinal(byte *digest) -{ - // compute the intermediate hash - byte interHash[SHA256_SIZE]; - _hash.doFinal(interHash); - // compute the final hash - SHA256 finalHash; - finalHash.doUpdate(_outerKey, SHA256HMAC_BLOCKSIZE); - finalHash.doUpdate(interHash, SHA256_SIZE); - finalHash.doFinal(digest); -} - -bool SHA256HMAC::matches(const byte *expected) -{ - byte theDigest[SHA256_SIZE]; - doFinal(theDigest); - for (byte i = 0; i < SHA256_SIZE; i++) - { - if (expected[i] != theDigest[i]) - return false; - } - return true; -} - -void SHA256HMAC::blockXor(const byte *in, byte *out, byte val, byte len) -{ - for (byte i = 0; i < len; i++) - { - out[i] = in[i] ^ val; - } -} diff --git a/libraries/ESP8266WiFiMesh/src/Crypto.h b/libraries/ESP8266WiFiMesh/src/Crypto.h deleted file mode 100644 index 9df82251bb..0000000000 --- a/libraries/ESP8266WiFiMesh/src/Crypto.h +++ /dev/null @@ -1,254 +0,0 @@ -/** - * An extremely minimal crypto library for Arduino devices. - * - * The SHA256 and AES implementations are derived from axTLS - * (http://axtls.sourceforge.net/), Copyright (c) 2008, Cameron Rich. - * - * Ported and refactored by Chris Ellis 2016. - * pkcs7 padding routines added by Mike Killewald Nov 26, 2017 (adopted from https://github.com/spaniakos/AES). - * - License - ======= - Balsa SCGI - Copyright (c) 2012, Chris Ellis - All rights reserved. - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions are met: - - 1. Redistributions of source code must retain the above copyright notice, this - list of conditions and the following disclaimer. - 2. Redistributions in binary form must reproduce the above copyright notice, - this list of conditions and the following disclaimer in the documentation - and/or other materials provided with the distribution. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND - ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR - ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES - (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; - LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND - ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -#ifndef CRYPTO_h -#define CRYPTO_h - -#include - -#if defined ESP8266 -#include -#endif - -#define SHA256_SIZE 32 -#define SHA256HMAC_SIZE 32 -#define SHA256HMAC_BLOCKSIZE 64 -#define AES_MAXROUNDS 14 -#define AES_BLOCKSIZE 16 -#define AES_IV_SIZE 16 -#define AES_IV_LENGTH 16 -#define AES_128_KEY_LENGTH 16 -#define AES_256_KEY_LENGTH 16 - -/** - * Compute a SHA256 hash - */ -class SHA256 -{ - public: - SHA256(); - /** - * Update the hash with new data - */ - void doUpdate(const byte *msg, uint32_t len); - void doUpdate(const char *msg, unsigned int len) { doUpdate((byte*) msg, len); } - void doUpdate(const char *msg) { doUpdate((byte*) msg, strlen(msg)); } - /** - * Compute the final hash and store it in [digest], digest must be - * at least 32 bytes - */ - void doFinal(byte *digest); - /** - * Compute the final hash and check it matches this given expected hash - */ - bool matches(const byte *expected); - private: - void SHA256_Process(const byte digest[64]); - uint32_t total[2]; - uint32_t state[8]; - uint8_t buffer[64]; -}; - -#define HMAC_OPAD 0x5C -#define HMAC_IPAD 0x36 - -/** - * Compute a HMAC using SHA256 - */ -class SHA256HMAC -{ - public: - /** - * Compute a SHA256 HMAC with the given [key] key of [length] bytes - * for authenticity - */ - SHA256HMAC(const byte *key, unsigned int keyLen); - /** - * Update the hash with new data - */ - void doUpdate(const byte *msg, unsigned int len); - void doUpdate(const char *msg, unsigned int len) { doUpdate((byte*) msg, len); } - void doUpdate(const char *msg) { doUpdate((byte*) msg, strlen(msg)); } - /** - * Compute the final hash and store it in [digest], digest must be - * at least 32 bytes - */ - void doFinal(byte *digest); - /** - * Compute the final hash and check it matches this given expected hash - */ - bool matches(const byte *expected); - private: - void blockXor(const byte *in, byte *out, byte val, byte len); - SHA256 _hash; - byte _innerKey[SHA256HMAC_BLOCKSIZE]; - byte _outerKey[SHA256HMAC_BLOCKSIZE]; -}; - -/** - * AES 128 and 256, based on code from axTLS - */ -class AES -{ - public: - typedef enum - { - AES_MODE_128, - AES_MODE_256 - } AES_MODE; - typedef enum - { - CIPHER_ENCRYPT = 0x01, - CIPHER_DECRYPT = 0x02 - } CIPHER_MODE; - - /** - * Create this cipher instance in either encrypt or decrypt mode - * - * Use the given [key] which must be 16 bytes long for AES 128 and - * 32 bytes for AES 256 - * - * Use the given [iv] initialistion vection which must be 16 bytes long - * - * Use the either AES 128 or AES 256 as specified by [mode] - * - * Either encrypt or decrypt as specified by [cipherMode] - */ - AES(const uint8_t *key, const uint8_t *iv, AES_MODE mode, CIPHER_MODE cipherMode); - - /** - * Either encrypt or decrypt [in] and store into [out] for [length] bytes, applying no padding - * - * Note: the length must be a multiple of 16 bytes - */ - void processNoPad(const uint8_t *in, uint8_t *out, int length); - - /** - * Either encrypt or decrypt [in] and store into [out] for [length] bytes, applying padding as needed - * - * Note: the length must be a multiple of 16 bytes - */ - void process(const uint8_t *in, uint8_t *out, int length); - - /** Getter method for size - * - * This function returns the size - * @return an integer, that is the size of the of the padded plaintext, - * thus, the size of the ciphertext. - */ - int getSize(); - - /** Setter method for size - * - * This function sets the size of the plaintext+pad - * - */ - void setSize(int size); - - /** Calculates the size of the plaintext and the padding. - * - * Calculates the size of the plaintext with the size of the - * padding needed. Moreover it stores them in their class variables. - * - * @param in_size the size of the byte array ex sizeof(plaintext) - * @return an int the size of the plaintext plus the padding - */ - int calcSizeAndPad(int in_size); - - /** Pads the plaintext - * - * This function pads the plaintext and returns an char array with the - * plaintext and the padding in order for the plaintext to be compatible with - * 16bit size blocks required by AES - * - * @param in the string of the plaintext in a byte array - * @param out The string of the out array. - * @return no return, The padded plaintext is stored in the out pointer. - */ - void padPlaintext(const uint8_t* in, uint8_t* out); - - /** Check the if the padding is correct. - * - * This functions checks the padding of the plaintext. - * - * @param in the string of the plaintext in a byte array - * @param size the size of the string - * @return true if correct / false if not - */ - bool checkPad(uint8_t* in, int lsize); - - private: - void encryptCBC(const uint8_t *in, uint8_t *out, int length); - void decryptCBC(const uint8_t *in, uint8_t *out, int length); - void convertKey(); - void encrypt(uint32_t *data); - void decrypt(uint32_t *data); - uint16_t _rounds; - uint16_t _key_size; - uint32_t _ks[(AES_MAXROUNDS+1)*8]; - uint8_t _iv[AES_IV_SIZE]; - int _pad_size; // size of padding to add to plaintext - int _size; // size of plaintext plus padding to be ciphered - uint8_t _arr_pad[15]; - - CIPHER_MODE _cipherMode; -}; - -#if defined ESP8266 || defined ESP32 -/** - * ESP8266 and ESP32 specific true random number generator - */ -class RNG -{ - public: - /** - * Fill the [dst] array with [length] random bytes - */ - static void fill(uint8_t *dst, unsigned int length); - /** - * Get a random byte - */ - static byte get(); - /** - * Get a 32bit random number - */ - static uint32_t getLong(); - private: -}; -#endif - - -#endif diff --git a/libraries/ESP8266WiFiMesh/src/CryptoInterface.cpp b/libraries/ESP8266WiFiMesh/src/CryptoInterface.cpp new file mode 100644 index 0000000000..8ed81823f1 --- /dev/null +++ b/libraries/ESP8266WiFiMesh/src/CryptoInterface.cpp @@ -0,0 +1,147 @@ +/* + * BearSSL Copyright (c) 2016 Thomas Pornin + * Rest of this file Copyright (C) 2019 Anders Löfgren + * + * License (MIT license): + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include "CryptoInterface.h" +#include "TypeConversionFunctions.h" + +#include + +namespace CryptoInterface +{ + uint8_t *createBearsslHmac(const String &message, const uint8_t *hashKey, uint8_t hashKeyLength, uint8_t *resultArray, size_t resultArrayLength) + { + assert(1 <= resultArrayLength); + + // Comments mainly from https://www.bearssl.org/apidoc/bearssl__hmac_8h.html + + // HMAC is initialized with a key and an underlying hash function; it then fills a "key context". That context contains the processed key. + // With the key context, a HMAC context can be initialized to process the input bytes and obtain the MAC output. The key context is not modified during that process, and can be reused. + + // hashType alternatives: &br_md5_vtable; &br_sha1_vtable; &br_sha224_vtable; &br_sha256_vtable; &br_sha384_vtable; &br_sha512_vtable; + // Use SHA256 to create the hash. + const br_hash_class *hashType = &br_sha256_vtable; + + br_hmac_key_context keyContext; // Holds general HMAC info + br_hmac_context hmacContext; // Holds general HMAC info + specific info for the current message + + // HMAC key context initialisation. + // Initialise the key context with the provided hash key, using the hash function identified by hashType. This supports arbitrary key lengths. + br_hmac_key_init(&keyContext, hashType, hashKey, hashKeyLength); + + // Initialise a HMAC context with a key context. The key context is unmodified. + // Relevant data from the key context is immediately copied; the key context can thus be independently reused, modified or released without impacting this HMAC computation. + // An explicit output length can be specified; the actual output length will be the minimum of that value and the natural HMAC output length. + // If resultArrayLength is 0, then the natural HMAC output length is selected. The "natural output length" is the output length of the underlying hash function. + br_hmac_init(&hmacContext, &keyContext, resultArrayLength); + + // Provide the HMAC context with the data to create a HMAC from. + // The provided message.length() bytes are injected as extra input in the HMAC computation incarnated by the hmacContext. + // It is acceptable that message.length() is zero, in which case data is ignored (and may be NULL) and this function does nothing. + br_hmac_update(&hmacContext, message.c_str(), message.length()); + + // Compute the HMAC output. + // The destination buffer MUST be large enough to accommodate the result; its length is at most the "natural length" of HMAC (i.e. the output length of the underlying hash function). + // The context is NOT modified; further bytes may be processed. Thus, "partial HMAC" values can be efficiently obtained. + // Optionally the constant-time version br_hmac_outCT() can be used. More info here: https://www.bearssl.org/constanttime.html . + br_hmac_out(&hmacContext, resultArray); // returns size_t outputLength + + return resultArray; + } + + String createBearsslHmac(const String &message, const uint8_t *hashKey, uint8_t hashKeyLength, size_t hmacLength) + { + assert(1 <= hmacLength && hmacLength <= SHA256HMAC_NATURAL_LENGTH); + byte hmac[hmacLength]; + createBearsslHmac(message, hashKey, hashKeyLength, hmac, hmacLength); + return uint8ArrayToHexString(hmac, hmacLength); + } + + uint8_t *createBearsslHmacCT(const String &message, const uint8_t *hashKey, uint8_t hashKeyLength, uint8_t *resultArray, size_t resultArrayLength) + { + assert(1 <= resultArrayLength); + + // Comments mainly from https://www.bearssl.org/apidoc/bearssl__hmac_8h.html + + // HMAC is initialized with a key and an underlying hash function; it then fills a "key context". That context contains the processed key. + // With the key context, a HMAC context can be initialized to process the input bytes and obtain the MAC output. The key context is not modified during that process, and can be reused. + + // hashType alternatives: &br_md5_vtable; &br_sha1_vtable; &br_sha224_vtable; &br_sha256_vtable; &br_sha384_vtable; &br_sha512_vtable; + // Use SHA256 to create the hash. + const br_hash_class *hashType = &br_sha256_vtable; + + br_hmac_key_context keyContext; // Holds general HMAC info + br_hmac_context hmacContext; // Holds general HMAC info + specific info for the current message + + // HMAC key context initialisation. + // Initialise the key context with the provided hash key, using the hash function identified by hashType. This supports arbitrary key lengths. + br_hmac_key_init(&keyContext, hashType, hashKey, hashKeyLength); + + // Initialise a HMAC context with a key context. The key context is unmodified. + // Relevant data from the key context is immediately copied; the key context can thus be independently reused, modified or released without impacting this HMAC computation. + // An explicit output length can be specified; the actual output length will be the minimum of that value and the natural HMAC output length. + // If resultArrayLength is 0, then the natural HMAC output length is selected. The "natural output length" is the output length of the underlying hash function. + br_hmac_init(&hmacContext, &keyContext, resultArrayLength); + + // Provide the HMAC context with the data to create a HMAC from. + // The provided message.length() bytes are injected as extra input in the HMAC computation incarnated by the hmacContext. + // It is acceptable that message.length() is zero, in which case data is ignored (and may be NULL) and this function does nothing. + // No need for br_hmac_update when using constant-time version it seems. If it is used, the data provided to br_hmac_outCT will just be appended. + // br_hmac_update(&hmacContext, message.c_str(), message.length()); + + // Compute the HMAC output. Assumes message is minimum 0 bytes and maximum 1000 bytes. + // As long as this is true, the correct HMAC output is calculated in constant-time. More constant-time info here: https://www.bearssl.org/constanttime.html + // Some extra input bytes are processed, then the output is computed. + // The extra input consists in the message.length() bytes pointed to by message.c_str(). The message.length() parameter must lie between min_len and max_len (inclusive); + // max_len bytes are actually read from data (indicating each data byte can be read multiple times, if message.length() < max_len). + // Computing time (and memory access pattern) will not depend upon the data byte contents or the value of len. + // The output is written in the resultArray buffer, that MUST be large enough to receive it. + // The difference max_len - min_len MUST be less than 2^30 (i.e. about one gigabyte). + // This function computes the output properly only if the underlying hash function uses MD padding (i.e. MD5, SHA-1, SHA-224, SHA-256, SHA-384 or SHA-512). + // The provided context is NOT modified. + size_t min_len = 0; + size_t max_len = 1000; + assert(min_len <= message.length() && message.length() <= max_len); + br_hmac_outCT(&hmacContext, message.c_str(), message.length(), min_len, max_len, resultArray); // returns size_t outputLength + + return resultArray; + } + + String createBearsslHmacCT(const String &message, const uint8_t *hashKey, uint8_t hashKeyLength, size_t hmacLength) + { + assert(1 <= hmacLength && hmacLength <= SHA256HMAC_NATURAL_LENGTH); + byte hmac[hmacLength]; + createBearsslHmacCT(message, hashKey, hashKeyLength, hmac, hmacLength); + return uint8ArrayToHexString(hmac, hmacLength); + } + + bool verifyBearsslHmac(const String &message, const String &messageHmac, const uint8_t *hashKey, uint8_t hashKeyLength) + { + String generatedHmac = createBearsslHmac(message, hashKey, hashKeyLength, messageHmac.length()/2); // We know that each HMAC byte should become 2 String characters due to uint8ArrayToHexString. + if(generatedHmac == messageHmac) + return true; + else + return false; + } +} diff --git a/libraries/ESP8266WiFiMesh/src/CryptoInterface.h b/libraries/ESP8266WiFiMesh/src/CryptoInterface.h new file mode 100644 index 0000000000..5a348a069f --- /dev/null +++ b/libraries/ESP8266WiFiMesh/src/CryptoInterface.h @@ -0,0 +1,120 @@ +/* + * BearSSL Copyright (c) 2016 Thomas Pornin + * Rest of this file Copyright (C) 2019 Anders Löfgren + * + * License (MIT license): + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include + +#ifndef __MESHCRYPTOINTERFACE_H__ +#define __MESHCRYPTOINTERFACE_H__ + +namespace CryptoInterface +{ + const uint8_t SHA256HMAC_NATURAL_LENGTH = 32; + + /** + * Create a SHA256 HMAC from the message, using the provided hashKey. The result will be resultArrayLength bytes long and stored in resultArray. + * Uses the BearSSL cryptographic library. + * + * @param message The string from which to create the HMAC. + * @param hashKey The hash key to use when creating the HMAC. + * @param hashKeyLength The length of the hash key in bytes. + * @param resultArray The array wherein to store the resulting HMAC. + * @param resultArrayLength The length of resultArray in bytes. Determines the HMAC length. If resultArrayLength is greater than SHA256HMAC_NATURAL_LENGTH, + * the first (lowest index) SHA256HMAC_NATURAL_LENGTH bytes of resultArray will be used for the HMAC. + * + * @return A pointer to resultArray. + */ + uint8_t *createBearsslHmac(const String &message, const uint8_t *hashKey, uint8_t hashKeyLength, uint8_t *resultArray, size_t resultArrayLength); + + /** + * Create a SHA256 HMAC from the message, using the provided hashKey. The result will be hmacLength bytes long and returned as a String in HEX format. + * Uses the BearSSL cryptographic library. + * + * @param message The string from which to create the HMAC. + * @param hashKey The hash key to use when creating the HMAC. + * @param hashKeyLength The length of the hash key in bytes. + * @param hmacLength The desired length of the generated HMAC, in bytes. Valid values are 1 to 32. Defaults to SHA256HMAC_NATURAL_LENGTH. + * + * @return A String with the generated HMAC in HEX format. + */ + String createBearsslHmac(const String &message, const uint8_t *hashKey, uint8_t hashKeyLength, size_t hmacLength = SHA256HMAC_NATURAL_LENGTH); + + /** + * Create a SHA256 HMAC from the message, using the provided hashKey. The result will be resultArrayLength bytes long and stored in resultArray. + * Uses the BearSSL cryptographic library. + * + * Constant-time version of createBearsslHmac(). More constant-time info here: https://www.bearssl.org/constanttime.html + * For small messages, it takes substantially longer time to complete than a normal HMAC (5 ms vs 2 ms in a quick benchmark, + * determined by the difference between min and max allowed message length), and it also sets a maximum length that messages can be (set to 1000 bytes here). + * Making the fixed max length variable would defeat the whole purpose of using constant-time, and not making it variable would create the wrong HMAC if message size exceeds the maximum. + * + * Also, HMAC is already partially constant-time. Quoting the link above: + * "Hash functions implemented by BearSSL (MD5, SHA-1, SHA-224, SHA-256, SHA-384 and SHA-512) consist in bitwise logical operations and additions on 32-bit or 64-bit words, + * naturally yielding constant-time operations. HMAC is naturally as constant-time as the underlying hash function. The size of the MACed data, and the size of the key, + * may leak, though; only the contents are protected." + * + * Thus the non constant-time version is used within the mesh framework instead. + * + * @param message The string from which to create the HMAC. Min size 0 bytes. Max size 1000 bytes. + * @param hashKey The hash key to use when creating the HMAC. + * @param hashKeyLength The length of the hash key in bytes. + * @param resultArray The array wherein to store the resulting HMAC. + * @param resultArrayLength The length of resultArray in bytes. Determines the HMAC length. If resultArrayLength is greater than SHA256HMAC_NATURAL_LENGTH, + * the first (lowest index) SHA256HMAC_NATURAL_LENGTH bytes of resultArray will be used for the HMAC. + * + * @return A pointer to resultArray. + */ + uint8_t *createBearsslHmacCT(const String &message, const uint8_t *hashKey, uint8_t hashKeyLength, uint8_t *resultArray, size_t resultArrayLength); + + /** + * Create a SHA256 HMAC from the message, using the provided hashKey. The result will be hmacLength bytes long and returned as a String in HEX format. + * Uses the BearSSL cryptographic library. + * + * Constant-time version of createBearsslHmac(). More constant-time info here: https://www.bearssl.org/constanttime.html + * Not used within the mesh framework for reasons outlined in the uint8_t *createBearsslHmacCT() description. + * + * @param message The string from which to create the HMAC. Min size 0 bytes. Max size 1000 bytes. + * @param hashKey The hash key to use when creating the HMAC. + * @param hashKeyLength The length of the hash key in bytes. + * @param hmacLength The desired length of the generated HMAC, in bytes. Valid values are 1 to 32. Defaults to SHA256HMAC_NATURAL_LENGTH. + * + * @return A String with the generated HMAC in HEX format. + */ + String createBearsslHmacCT(const String &message, const uint8_t *hashKey, uint8_t hashKeyLength, size_t hmacLength = SHA256HMAC_NATURAL_LENGTH); + + /** + * Verify a SHA256 HMAC which was created from the message using the provided hashKey. + * Uses the BearSSL cryptographic library. + * + * @param message The string from which the HMAC was created. + * @param messageHmac A string with the generated HMAC in HEX format. Valid messageHmac.length() is 2 to 64. + * @param hashKey The hash key to use when creating the HMAC. + * @param hashKeyLength The length of the hash key in bytes. + * + * @return True if the HMAC is correct. False otherwise. + */ + bool verifyBearsslHmac(const String &message, const String &messageHmac, const uint8_t *hashKey, uint8_t hashKeyLength); +} + +#endif diff --git a/libraries/ESP8266WiFiMesh/src/EncryptedConnectionData.cpp b/libraries/ESP8266WiFiMesh/src/EncryptedConnectionData.cpp index 95a5eaf4ed..d83ece4d2a 100644 --- a/libraries/ESP8266WiFiMesh/src/EncryptedConnectionData.cpp +++ b/libraries/ESP8266WiFiMesh/src/EncryptedConnectionData.cpp @@ -26,6 +26,7 @@ #include "UtilityFunctions.h" #include "TypeConversionFunctions.h" #include "JsonTranslator.h" +#include "CryptoInterface.h" using EspnowProtocolInterpreter::espnowHashKeyLength; @@ -127,7 +128,7 @@ uint64_t EncryptedConnectionData::getOwnSessionKey() const { return _ownSessionK uint64_t EncryptedConnectionData::incrementSessionKey(uint64_t sessionKey, const uint8_t *hashKey, uint8_t hashKeyLength) { - String hmac = JsonTranslator::createHmac(uint64ToString(sessionKey), hashKey, hashKeyLength); + String hmac = CryptoInterface::createBearsslHmac(uint64ToString(sessionKey), hashKey, hashKeyLength); /* 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 diff --git a/libraries/ESP8266WiFiMesh/src/EncryptedConnectionLog.cpp b/libraries/ESP8266WiFiMesh/src/EncryptedConnectionLog.cpp index 45bcd1dd0b..12ce4dd3d6 100644 --- a/libraries/ESP8266WiFiMesh/src/EncryptedConnectionLog.cpp +++ b/libraries/ESP8266WiFiMesh/src/EncryptedConnectionLog.cpp @@ -55,7 +55,7 @@ void EncryptedConnectionLog::removeDuration() void EncryptedConnectionLog::scheduleForRemoval() { - // When we give the connection 0 remaining duration it will be removed during the next performEspnowMaintainance() call. + // When we give the connection 0 remaining duration it will be removed during the next performEspnowMaintenance() call. // Duration must be changed before setting the scheduledForRemoval flag to true, since the flag is otherwise cleared. setRemainingDuration(0); setScheduledForRemoval(true); diff --git a/libraries/ESP8266WiFiMesh/src/EspnowMeshBackend.cpp b/libraries/ESP8266WiFiMesh/src/EspnowMeshBackend.cpp index 20c39e2543..6bd8294d59 100644 --- a/libraries/ESP8266WiFiMesh/src/EspnowMeshBackend.cpp +++ b/libraries/ESP8266WiFiMesh/src/EspnowMeshBackend.cpp @@ -26,7 +26,6 @@ extern "C" { #include "UtilityFunctions.h" #include "MutexTracker.h" #include "JsonTranslator.h" -#include "Crypto.h" using EspnowProtocolInterpreter::espnowEncryptionKeyLength; using EspnowProtocolInterpreter::espnowHashKeyLength; @@ -99,7 +98,7 @@ void espnowDelay(uint32_t durationMs) while(millis() - startingTime < durationMs) { delay(1); - EspnowMeshBackend::performEspnowMaintainance(); + EspnowMeshBackend::performEspnowMaintenance(); } } @@ -224,7 +223,7 @@ bool EspnowMeshBackend::latestTransmissionSuccessful() return latestTransmissionSuccessfulBase(latestTransmissionOutcomes()); } -void EspnowMeshBackend::performEspnowMaintainance(uint32_t estimatedMaxDuration) +void EspnowMeshBackend::performEspnowMaintenance(uint32_t estimatedMaxDuration) { ExpiringTimeTracker estimatedMaxDurationTracker = ExpiringTimeTracker(estimatedMaxDuration); @@ -232,7 +231,7 @@ void EspnowMeshBackend::performEspnowMaintainance(uint32_t estimatedMaxDuration) MutexTracker mutexTracker(_espnowTransmissionMutex, handlePostponedRemovals); if(!mutexTracker.mutexCaptured()) { - assert(false && "ERROR! Transmission in progress. Don't call performEspnowMaintainance from callbacks as this may corrupt program state! Aborting."); + assert(false && "ERROR! Transmission in progress. Don't call performEspnowMaintenance from callbacks as this may corrupt program state! Aborting."); return; } diff --git a/libraries/ESP8266WiFiMesh/src/EspnowMeshBackend.h b/libraries/ESP8266WiFiMesh/src/EspnowMeshBackend.h index ad1b20a910..def9e85773 100644 --- a/libraries/ESP8266WiFiMesh/src/EspnowMeshBackend.h +++ b/libraries/ESP8266WiFiMesh/src/EspnowMeshBackend.h @@ -80,7 +80,6 @@ #include "MessageData.h" #include #include -#include "Crypto.h" #include "EspnowNetworkInfo.h" typedef enum @@ -111,11 +110,11 @@ typedef enum /** - * An alternative to standard delay(). Will continuously call performEspnowMaintainance() during the waiting time, so that the ESP-NOW node remains responsive. + * An alternative to standard delay(). Will continuously call performEspnowMaintenance() during the waiting time, so that the ESP-NOW node remains responsive. * Note that if there is a lot of ESP-NOW transmission activity to the node during the espnowDelay, the desired duration may be overshot by several ms. * Thus, if precise timing is required, use standard delay() instead. * - * Should not be used inside responseHandler, requestHandler, networkFilter or broadcastFilter callbacks since performEspnowMaintainance() can alter the ESP-NOW state. + * Should not be used inside responseHandler, requestHandler, networkFilter or broadcastFilter callbacks since performEspnowMaintenance() can alter the ESP-NOW state. * * @param durationMs The shortest allowed delay duration, in milliseconds. */ @@ -202,15 +201,15 @@ class EspnowMeshBackend : public MeshBackendBase { * It is recommended to place it in the beginning of the loop(), unless there is a need to put it elsewhere. * Among other things, the method cleans up old Espnow log entries (freeing up RAM) and sends the responses you provide to Espnow requests. * Note that depending on the amount of responses to send and their length, this method can take tens or even hundreds of milliseconds to complete. - * More intense transmission activity and less frequent calls to performEspnowMaintainance will likely cause the method to take longer to complete, so plan accordingly. + * More intense transmission activity and less frequent calls to performEspnowMaintenance will likely cause the method to take longer to complete, so plan accordingly. * - * Should not be used inside responseHandler, requestHandler, networkFilter or broadcastFilter callbacks since performEspnowMaintainance() can alter the ESP-NOW state. + * Should not be used inside responseHandler, requestHandler, networkFilter or broadcastFilter callbacks since performEspnowMaintenance() can alter the ESP-NOW state. * * @param estimatedMaxDuration The desired max duration for the method. If set to 0 there is no duration limit. - * Note that setting the estimatedMaxDuration too low may result in missed ESP-NOW transmissions because of too little time for maintainance. + * Note that setting the estimatedMaxDuration too low may result in missed ESP-NOW transmissions because of too little time for maintenance. * Also note that although the method will try to respect the max duration limit, there is no guarantee. Overshoots by tens of milliseconds are possible. */ - static void performEspnowMaintainance(uint32_t estimatedMaxDuration = 0); + static void performEspnowMaintenance(uint32_t estimatedMaxDuration = 0); /** * At critical heap level no more incoming requests are accepted. @@ -703,19 +702,19 @@ class EspnowMeshBackend : public MeshBackendBase { * For example, response order will be mixed up if some responses fail to transmit while others transmit successfully. * * @param estimatedMaxDurationTracker A pointer to an ExpiringTimeTracker initialized with the desired max duration for the method. If set to nullptr there is no duration limit. - * Note that setting the estimatedMaxDuration too low may result in missed ESP-NOW transmissions because of too little time for maintainance. + * Note that setting the estimatedMaxDuration too low may result in missed ESP-NOW transmissions because of too little time for maintenance. * Also note that although the method will try to respect the max duration limit, there is no guarantee. Overshoots by tens of milliseconds are possible. */ static void sendStoredEspnowMessages(const ExpiringTimeTracker *estimatedMaxDurationTracker = nullptr); /* * @param estimatedMaxDurationTracker A pointer to an ExpiringTimeTracker initialized with the desired max duration for the method. If set to nullptr there is no duration limit. - * Note that setting the estimatedMaxDuration too low may result in missed ESP-NOW transmissions because of too little time for maintainance. + * Note that setting the estimatedMaxDuration too low may result in missed ESP-NOW transmissions because of too little time for maintenance. * Also note that although the method will try to respect the max duration limit, there is no guarantee. Overshoots by tens of milliseconds are possible. */ static void sendPeerRequestConfirmations(const ExpiringTimeTracker *estimatedMaxDurationTracker = nullptr); /* * @param estimatedMaxDurationTracker A pointer to an ExpiringTimeTracker initialized with the desired max duration for the method. If set to nullptr there is no duration limit. - * Note that setting the estimatedMaxDuration too low may result in missed ESP-NOW transmissions because of too little time for maintainance. + * Note that setting the estimatedMaxDuration too low may result in missed ESP-NOW transmissions because of too little time for maintenance. * Also note that although the method will try to respect the max duration limit, there is no guarantee. Overshoots by tens of milliseconds are possible. */ static void sendEspnowResponses(const ExpiringTimeTracker *estimatedMaxDurationTracker = nullptr); diff --git a/libraries/ESP8266WiFiMesh/src/FloodingMesh.cpp b/libraries/ESP8266WiFiMesh/src/FloodingMesh.cpp index c501417dd2..021a2bfe0c 100644 --- a/libraries/ESP8266WiFiMesh/src/FloodingMesh.cpp +++ b/libraries/ESP8266WiFiMesh/src/FloodingMesh.cpp @@ -37,7 +37,7 @@ void floodingMeshDelay(uint32_t durationMs) while(millis() - startingTime < durationMs) { delay(1); - FloodingMesh::performMeshMaintainance(); + FloodingMesh::performMeshMaintenance(); } } @@ -74,25 +74,25 @@ void FloodingMesh::begin() // Initialise the mesh node getEspnowMeshBackend().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. - // Note that only one AP can be active at a time in total, and this will always be the one which was last activated. - // Thus the AP is shared by all backends. - getEspnowMeshBackend().activateAP(); - availableFloodingMeshes.insert(this); // Returns std::pair } -void FloodingMesh::performMeshMaintainance() +void FloodingMesh::activateAP() +{ + getEspnowMeshBackend().activateAP(); +} + +void FloodingMesh::performMeshMaintenance() { for(FloodingMesh *meshInstance : availableFloodingMeshes) { - meshInstance->performMeshInstanceMaintainance(); + meshInstance->performMeshInstanceMaintenance(); } } -void FloodingMesh::performMeshInstanceMaintainance() +void FloodingMesh::performMeshInstanceMaintenance() { - EspnowMeshBackend::performEspnowMaintainance(); + EspnowMeshBackend::performEspnowMaintenance(); for(std::list>::iterator backlogIterator = _forwardingBacklog.begin(); backlogIterator != _forwardingBacklog.end(); ) { @@ -110,7 +110,7 @@ void FloodingMesh::performMeshInstanceMaintainance() backlogIterator = _forwardingBacklog.erase(backlogIterator); - EspnowMeshBackend::performEspnowMaintainance(); // It is best to performEspnowMaintainance frequently to keep the Espnow backend responsive. Especially if each encryptedBroadcast takes a lot of time. + EspnowMeshBackend::performEspnowMaintenance(); // It is best to performEspnowMaintenance frequently to keep the Espnow backend responsive. Especially if each encryptedBroadcast takes a lot of time. } } diff --git a/libraries/ESP8266WiFiMesh/src/FloodingMesh.h b/libraries/ESP8266WiFiMesh/src/FloodingMesh.h index 797ceebdd3..cc41768415 100644 --- a/libraries/ESP8266WiFiMesh/src/FloodingMesh.h +++ b/libraries/ESP8266WiFiMesh/src/FloodingMesh.h @@ -31,11 +31,11 @@ #include /** - * An alternative to standard delay(). Will continuously call performMeshMaintainance() during the waiting time, so that the FloodingMesh node remains responsive. + * An alternative to standard delay(). Will continuously call performMeshMaintenance() during the waiting time, so that the FloodingMesh node remains responsive. * Note that if there is a lot of FloodingMesh transmission activity to the node during the floodingMeshDelay, the desired duration may be overshot by several ms. * Thus, if precise timing is required, use standard delay() instead. * - * Should not be used inside callbacks since performMeshMaintainance() can alter the ESP-NOW state. + * Should not be used inside callbacks since performMeshMaintenance() can alter the ESP-NOW state. * * @param durationMs The shortest allowed delay duration, in milliseconds. */ @@ -92,16 +92,25 @@ class FloodingMesh { * All FloodingMesh instances can still broadcast messages though, even if their AP is not visible. */ 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. + * 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, and this will always be the one which was last activated. + * Thus the AP is shared by all backends. + */ + void activateAP(); /** - * Performs maintainance for all available Flooding Mesh instances + * Performs maintenance for all available Flooding Mesh instances */ - static void performMeshMaintainance(); + static void performMeshMaintenance(); /** - * Performs maintainance for this particular Flooding Mesh instance + * Performs maintenance for this particular Flooding Mesh instance */ - void performMeshInstanceMaintainance(); + void performMeshInstanceMaintenance(); /** * Serialize the current mesh node state. Useful to save a state before the node goes to sleep. diff --git a/libraries/ESP8266WiFiMesh/src/JsonTranslator.cpp b/libraries/ESP8266WiFiMesh/src/JsonTranslator.cpp index 1bbbb444fd..b78b1babe0 100644 --- a/libraries/ESP8266WiFiMesh/src/JsonTranslator.cpp +++ b/libraries/ESP8266WiFiMesh/src/JsonTranslator.cpp @@ -23,9 +23,9 @@ */ #include "JsonTranslator.h" -#include "Crypto.h" #include "EspnowProtocolInterpreter.h" #include "TypeConversionFunctions.h" +#include "CryptoInterface.h" namespace JsonTranslator { @@ -39,59 +39,6 @@ namespace JsonTranslator return valueIdentifier + "\"" + value + "\"}}"; } - uint8_t *createHmac(const String &message, const uint8_t *hashKey, uint8_t hashKeyLength, uint8_t resultArray[SHA256HMAC_SIZE]) - { - // Create the HMAC instance with our key - SHA256HMAC hmac(hashKey, hashKeyLength); - - // Update the HMAC with our message - hmac.doUpdate(message.c_str()); - - // Finish the HMAC calculation and return the authentication code - hmac.doFinal(resultArray); - - // resultArray now contains our SHA256HMAC_SIZE byte authentication code - return resultArray; - } - - String createHmac(const String &message, const uint8_t *hashKey, uint8_t hashKeyLength) - { - byte hmac[SHA256HMAC_SIZE]; - createHmac(message, hashKey, hashKeyLength, hmac); - return uint8ArrayToHexString(hmac, SHA256HMAC_SIZE); - } - - bool verifyHmac(const String &message, const String &messageHmac, const uint8_t *hashKey, uint8_t hashKeyLength) - { - if(messageHmac.length() != 2*SHA256HMAC_SIZE) // We know that each HMAC byte should become 2 String characters due to uint8ArrayToHexString. - return false; - - String generatedHmac = createHmac(message, hashKey, hashKeyLength); - if(generatedHmac == messageHmac) - return true; - else - return false; - } - - bool verifyEncryptionRequestHmac(const String &encryptionRequestHmacMessage, const uint8_t *requesterStaMac, const uint8_t *requesterApMac, - const uint8_t *hashKey, uint8_t hashKeyLength) - { - String hmac = ""; - if(getHmac(encryptionRequestHmacMessage, hmac)) - { - int32_t hmacStartIndex = encryptionRequestHmacMessage.indexOf(jsonHmac); - if(hmacStartIndex < 0) - return false; - - if(verifyHmac(macToString(requesterStaMac) + macToString(requesterApMac) + encryptionRequestHmacMessage.substring(0, hmacStartIndex), hmac, hashKey, hashKeyLength)) - { - return true; - } - } - - return false; - } - String createEncryptedConnectionInfo(const String &infoHeader, const String &requestNonce, const String &authenticationPassword, uint64_t ownSessionKey, uint64_t peerSessionKey) { // Returns: Encrypted connection info:{"arguments":{"nonce":"1F2","password":"abc","ownSK":"3B4","peerSK":"1A2"}} @@ -122,9 +69,31 @@ namespace JsonTranslator uint8_t staMac[6] {0}; uint8_t apMac[6] {0}; String requesterStaApMac = macToString(WiFi.macAddress(staMac)) + macToString(WiFi.softAPmacAddress(apMac)); - String hmac = createHmac(requesterStaApMac + mainMessage, hashKey, hashKeyLength); + String hmac = CryptoInterface::createBearsslHmac(requesterStaApMac + mainMessage, hashKey, hashKeyLength); return mainMessage + createJsonEndPair(jsonHmac, hmac); } + + bool verifyEncryptionRequestHmac(const String &encryptionRequestHmacMessage, const uint8_t *requesterStaMac, const uint8_t *requesterApMac, + const uint8_t *hashKey, uint8_t hashKeyLength) + { + using namespace CryptoInterface; + + String hmac = ""; + if(getHmac(encryptionRequestHmacMessage, hmac)) + { + int32_t hmacStartIndex = encryptionRequestHmacMessage.indexOf(jsonHmac); + if(hmacStartIndex < 0) + return false; + + if(hmac.length() == 2*SHA256HMAC_NATURAL_LENGTH // We know that each HMAC byte should become 2 String characters due to uint8ArrayToHexString. + && verifyBearsslHmac(macToString(requesterStaMac) + macToString(requesterApMac) + encryptionRequestHmacMessage.substring(0, hmacStartIndex), hmac, hashKey, hashKeyLength)) + { + return true; + } + } + + return false; + } int32_t getStartIndex(const String &jsonString, const String &valueIdentifier, int32_t searchStartIndex) { diff --git a/libraries/ESP8266WiFiMesh/src/JsonTranslator.h b/libraries/ESP8266WiFiMesh/src/JsonTranslator.h index 93aadeb574..3997ef6824 100644 --- a/libraries/ESP8266WiFiMesh/src/JsonTranslator.h +++ b/libraries/ESP8266WiFiMesh/src/JsonTranslator.h @@ -26,7 +26,6 @@ #define __ESPNOWJSONTRANSLATOR_H__ #include -#include "Crypto.h" namespace JsonTranslator { @@ -45,18 +44,14 @@ namespace JsonTranslator String createJsonPair(const String &valueIdentifier, const String &value); String createJsonEndPair(const String &valueIdentifier, const String &value); - - uint8_t *createHmac(const String &message, const uint8_t *hashKey, uint8_t hashKeyLength, uint8_t resultArray[SHA256HMAC_SIZE]); - String createHmac(const String &message, const uint8_t *hashKey, uint8_t hashKeyLength); - - bool verifyHmac(const String &message, const String &messageHmac, const uint8_t *hashKey, uint8_t hashKeyLength); - bool verifyEncryptionRequestHmac(const String &encryptionRequestHmacMessage, const uint8_t *requesterStaMac, const uint8_t *requesterApMac, const uint8_t *hashKey, uint8_t hashKeyLength); String createEncryptedConnectionInfo(const String &infoHeader, const String &requestNonce, const String &authenticationPassword, uint64_t ownSessionKey, uint64_t peerSessionKey); String createEncryptionRequestIntro(const String &requestHeader, uint32_t duration = 0); String createEncryptionRequestEnding(const String &requestNonce); String createEncryptionRequestHmacMessage(const String &requestHeader, const String &requestNonce, const uint8_t *hashKey, uint8_t hashKeyLength, uint32_t duration = 0); + bool verifyEncryptionRequestHmac(const String &encryptionRequestHmacMessage, const uint8_t *requesterStaMac, const uint8_t *requesterApMac, const uint8_t *hashKey, uint8_t hashKeyLength); + /** * Provides the index within jsonString where the value of valueIdentifier starts. * diff --git a/libraries/ESP8266WiFiMesh/src/TypeConversionFunctions.cpp b/libraries/ESP8266WiFiMesh/src/TypeConversionFunctions.cpp index 0458cc54c8..62eefdd609 100644 --- a/libraries/ESP8266WiFiMesh/src/TypeConversionFunctions.cpp +++ b/libraries/ESP8266WiFiMesh/src/TypeConversionFunctions.cpp @@ -24,7 +24,6 @@ */ #include "TypeConversionFunctions.h" -#include "Crypto.h" String uint64ToString(uint64_t number, byte base) { From 2fef67dcb0bbcb5aaa6096b12c14c1d1a5994333 Mon Sep 17 00:00:00 2001 From: Anders Date: Sun, 10 Nov 2019 21:50:43 +0100 Subject: [PATCH 12/30] - Generalize CryptoInterface. - Add more HMAC and hash functions to CryptoInterface. - Add MeshCryptoInterface as a holder of mesh specific crypto functionality. - Rename broadcastMetadataDelimiter to metadataDelimiter in FloodingMesh since it is not just used for broadcasts, and to save some typing. --- .../examples/HelloMesh/HelloMesh.ino | 6 +- .../ESP8266WiFiMesh/src/CryptoInterface.cpp | 363 +++++++++-- .../ESP8266WiFiMesh/src/CryptoInterface.h | 594 ++++++++++++++++-- .../src/EncryptedConnectionData.cpp | 4 +- .../ESP8266WiFiMesh/src/FloodingMesh.cpp | 40 +- libraries/ESP8266WiFiMesh/src/FloodingMesh.h | 8 +- .../ESP8266WiFiMesh/src/JsonTranslator.cpp | 10 +- .../src/MeshCryptoInterface.cpp | 42 ++ .../ESP8266WiFiMesh/src/MeshCryptoInterface.h | 72 +++ 9 files changed, 1028 insertions(+), 111 deletions(-) create mode 100644 libraries/ESP8266WiFiMesh/src/MeshCryptoInterface.cpp create mode 100644 libraries/ESP8266WiFiMesh/src/MeshCryptoInterface.h diff --git a/libraries/ESP8266WiFiMesh/examples/HelloMesh/HelloMesh.ino b/libraries/ESP8266WiFiMesh/examples/HelloMesh/HelloMesh.ino index 785c175835..d9f138ca20 100644 --- a/libraries/ESP8266WiFiMesh/examples/HelloMesh/HelloMesh.ino +++ b/libraries/ESP8266WiFiMesh/examples/HelloMesh/HelloMesh.ino @@ -55,7 +55,7 @@ bool useLED = false; // Change this to true if you wish the onboard LED to mark @return True if this node should forward the received message to other nodes. False otherwise. */ bool meshMessageHandler(String &message, FloodingMesh &meshInstance) { - int32_t delimiterIndex = message.indexOf(meshInstance.broadcastMetadataDelimiter()); + int32_t delimiterIndex = message.indexOf(meshInstance.metadataDelimiter()); if (delimiterIndex == 0) { Serial.print("Message received from STA MAC " + meshInstance.getEspnowMeshBackend().getSenderMac() + ": "); Serial.println(message.substring(2, 102)); @@ -172,7 +172,7 @@ void loop() { ledState = ledState ^ bool(benchmarkCount); // Make other nodes' LEDs alternate between on and off once benchmarking begins. // Note: The maximum length of an unencrypted broadcast message is given by floodingMesh.maxUnencryptedMessageSize(). It is around 670 bytes by default. - floodingMesh.broadcast(String(floodingMesh.broadcastMetadataDelimiter()) + String(ledState) + theOneMac + " is The One."); + floodingMesh.broadcast(String(floodingMesh.metadataDelimiter()) + String(ledState) + theOneMac + " is The One."); Serial.println("Proclamation broadcast done in " + String(millis() - startTime) + " ms."); timeOfLastProclamation = millis(); @@ -181,7 +181,7 @@ void loop() { if (millis() - loopStart > 23000) { // Start benchmarking the mesh once three proclamations have been made uint32_t startTime = millis(); - floodingMesh.broadcast(String(benchmarkCount++) + String(floodingMesh.broadcastMetadataDelimiter()) + ": Not a spoon in sight."); + floodingMesh.broadcast(String(benchmarkCount++) + String(floodingMesh.metadataDelimiter()) + ": Not a spoon in sight."); Serial.println("Benchmark broadcast done in " + String(millis() - startTime) + " ms."); floodingMeshDelay(20); } diff --git a/libraries/ESP8266WiFiMesh/src/CryptoInterface.cpp b/libraries/ESP8266WiFiMesh/src/CryptoInterface.cpp index 8ed81823f1..9499bf6565 100644 --- a/libraries/ESP8266WiFiMesh/src/CryptoInterface.cpp +++ b/libraries/ESP8266WiFiMesh/src/CryptoInterface.cpp @@ -28,9 +28,14 @@ #include -namespace CryptoInterface +namespace { - uint8_t *createBearsslHmac(const String &message, const uint8_t *hashKey, uint8_t hashKeyLength, uint8_t *resultArray, size_t resultArrayLength) + size_t _ctMinDataLength = 0; + size_t _ctMaxDataLength = 1024; + + bool _warningsEnabled = true; + + void *createBearsslHmac(const br_hash_class *hashType, const void *data, const size_t dataLength, const void *hashKey, const size_t hashKeyLength, void *resultArray, const size_t resultArrayLength) { assert(1 <= resultArrayLength); @@ -38,13 +43,9 @@ namespace CryptoInterface // HMAC is initialized with a key and an underlying hash function; it then fills a "key context". That context contains the processed key. // With the key context, a HMAC context can be initialized to process the input bytes and obtain the MAC output. The key context is not modified during that process, and can be reused. - - // hashType alternatives: &br_md5_vtable; &br_sha1_vtable; &br_sha224_vtable; &br_sha256_vtable; &br_sha384_vtable; &br_sha512_vtable; - // Use SHA256 to create the hash. - const br_hash_class *hashType = &br_sha256_vtable; br_hmac_key_context keyContext; // Holds general HMAC info - br_hmac_context hmacContext; // Holds general HMAC info + specific info for the current message + br_hmac_context hmacContext; // Holds general HMAC info + specific info for the current operation // HMAC key context initialisation. // Initialise the key context with the provided hash key, using the hash function identified by hashType. This supports arbitrary key lengths. @@ -57,9 +58,9 @@ namespace CryptoInterface br_hmac_init(&hmacContext, &keyContext, resultArrayLength); // Provide the HMAC context with the data to create a HMAC from. - // The provided message.length() bytes are injected as extra input in the HMAC computation incarnated by the hmacContext. - // It is acceptable that message.length() is zero, in which case data is ignored (and may be NULL) and this function does nothing. - br_hmac_update(&hmacContext, message.c_str(), message.length()); + // The provided dataLength bytes are injected as extra input in the HMAC computation incarnated by the hmacContext. + // It is acceptable that dataLength is zero, in which case data is ignored (and may be NULL) and this function does nothing. + br_hmac_update(&hmacContext, data, dataLength); // Compute the HMAC output. // The destination buffer MUST be large enough to accommodate the result; its length is at most the "natural length" of HMAC (i.e. the output length of the underlying hash function). @@ -70,29 +71,27 @@ namespace CryptoInterface return resultArray; } - String createBearsslHmac(const String &message, const uint8_t *hashKey, uint8_t hashKeyLength, size_t hmacLength) + String createBearsslHmac(const br_hash_class *hashType, const uint8_t hashTypeNaturalLength, const String &message, const void *hashKey, const size_t hashKeyLength, const size_t hmacLength) { - assert(1 <= hmacLength && hmacLength <= SHA256HMAC_NATURAL_LENGTH); - byte hmac[hmacLength]; - createBearsslHmac(message, hashKey, hashKeyLength, hmac, hmacLength); + assert(1 <= hmacLength && hmacLength <= hashTypeNaturalLength); + + uint8_t hmac[hmacLength]; + createBearsslHmac(hashType, message.c_str(), message.length(), hashKey, hashKeyLength, hmac, hmacLength); return uint8ArrayToHexString(hmac, hmacLength); } - uint8_t *createBearsslHmacCT(const String &message, const uint8_t *hashKey, uint8_t hashKeyLength, uint8_t *resultArray, size_t resultArrayLength) + 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 resultArrayLength) { assert(1 <= resultArrayLength); + assert(_ctMinDataLength <= dataLength && dataLength <= _ctMaxDataLength); // Comments mainly from https://www.bearssl.org/apidoc/bearssl__hmac_8h.html // HMAC is initialized with a key and an underlying hash function; it then fills a "key context". That context contains the processed key. // With the key context, a HMAC context can be initialized to process the input bytes and obtain the MAC output. The key context is not modified during that process, and can be reused. - - // hashType alternatives: &br_md5_vtable; &br_sha1_vtable; &br_sha224_vtable; &br_sha256_vtable; &br_sha384_vtable; &br_sha512_vtable; - // Use SHA256 to create the hash. - const br_hash_class *hashType = &br_sha256_vtable; br_hmac_key_context keyContext; // Holds general HMAC info - br_hmac_context hmacContext; // Holds general HMAC info + specific info for the current message + br_hmac_context hmacContext; // Holds general HMAC info + specific info for the current operation // HMAC key context initialisation. // Initialise the key context with the provided hash key, using the hash function identified by hashType. This supports arbitrary key lengths. @@ -105,43 +104,323 @@ namespace CryptoInterface br_hmac_init(&hmacContext, &keyContext, resultArrayLength); // Provide the HMAC context with the data to create a HMAC from. - // The provided message.length() bytes are injected as extra input in the HMAC computation incarnated by the hmacContext. - // It is acceptable that message.length() is zero, in which case data is ignored (and may be NULL) and this function does nothing. + // The provided dataLength bytes are injected as extra input in the HMAC computation incarnated by the hmacContext. + // It is acceptable that dataLength is zero, in which case data is ignored (and may be NULL) and this function does nothing. // No need for br_hmac_update when using constant-time version it seems. If it is used, the data provided to br_hmac_outCT will just be appended. - // br_hmac_update(&hmacContext, message.c_str(), message.length()); + // br_hmac_update(&hmacContext, data, dataLength); - // Compute the HMAC output. Assumes message is minimum 0 bytes and maximum 1000 bytes. + // Compute the HMAC output. Assumes message is minimum _ctMinDataLength bytes and maximum _ctMaxDataLength bytes. // As long as this is true, the correct HMAC output is calculated in constant-time. More constant-time info here: https://www.bearssl.org/constanttime.html // Some extra input bytes are processed, then the output is computed. - // The extra input consists in the message.length() bytes pointed to by message.c_str(). The message.length() parameter must lie between min_len and max_len (inclusive); - // max_len bytes are actually read from data (indicating each data byte can be read multiple times, if message.length() < max_len). - // Computing time (and memory access pattern) will not depend upon the data byte contents or the value of len. + // The extra input consists in the dataLength bytes pointed to by data. The dataLength parameter must lie between _ctMinDataLength and _ctMaxDataLength (inclusive); + // _ctMaxDataLength bytes are actually read from data (indicating each data byte can be read multiple times, if dataLength < _ctMaxDataLength). + // Computing time (and memory access pattern) will not depend upon the data byte contents or the value of dataLength. // The output is written in the resultArray buffer, that MUST be large enough to receive it. - // The difference max_len - min_len MUST be less than 2^30 (i.e. about one gigabyte). + // The difference _ctMaxDataLength - _ctMinDataLength MUST be less than 2^30 (i.e. about one gigabyte). // This function computes the output properly only if the underlying hash function uses MD padding (i.e. MD5, SHA-1, SHA-224, SHA-256, SHA-384 or SHA-512). // The provided context is NOT modified. - size_t min_len = 0; - size_t max_len = 1000; - assert(min_len <= message.length() && message.length() <= max_len); - br_hmac_outCT(&hmacContext, message.c_str(), message.length(), min_len, max_len, resultArray); // returns size_t outputLength + br_hmac_outCT(&hmacContext, data, dataLength, _ctMinDataLength, _ctMaxDataLength, resultArray); // returns size_t outputLength return resultArray; } - String createBearsslHmacCT(const String &message, const uint8_t *hashKey, uint8_t hashKeyLength, size_t hmacLength) + String createBearsslHmacCT(const br_hash_class *hashType, const uint8_t hashTypeNaturalLength, const String &message, const void *hashKey, const size_t hashKeyLength, const size_t hmacLength) { - assert(1 <= hmacLength && hmacLength <= SHA256HMAC_NATURAL_LENGTH); - byte hmac[hmacLength]; - createBearsslHmacCT(message, hashKey, hashKeyLength, hmac, hmacLength); + assert(1 <= hmacLength && hmacLength <= hashTypeNaturalLength); + + uint8_t hmac[hmacLength]; + createBearsslHmacCT(hashType, message.c_str(), message.length(), hashKey, hashKeyLength, hmac, hmacLength); return uint8ArrayToHexString(hmac, hmacLength); } +} + +namespace CryptoInterface +{ + void setCtMinDataLength(const size_t ctMinDataLength) + { + assert(ctMaxDataLength() - ctMinDataLength <= ctMaxDiff); + _ctMinDataLength = ctMinDataLength; + } + size_t ctMinDataLength() {return _ctMinDataLength;} + + void setCtMaxDataLength(const size_t ctMaxDataLength) + { + assert(ctMaxDataLength - ctMinDataLength() <= ctMaxDiff); + _ctMaxDataLength = ctMaxDataLength; + } + size_t ctMaxDataLength() {return _ctMaxDataLength;} + + void setWarningsEnabled(bool warningsEnabled) { _warningsEnabled = warningsEnabled; } + bool warningsEnabled() { return _warningsEnabled; } + + + // #################### MD5 #################### + + // resultArray must have size MD5_NATURAL_LENGTH or greater + void *md5Hash(const void *data, const size_t dataLength, void *resultArray) + { + if(warningsEnabled()) + Serial.println(F("\nWARNING! The MD5 hash is broken in terms of attacker resistance.\n" + "Only use it in those cases where attacker resistance is not important. Prefer SHA-256 or higher otherwise.\n" + "Use CryptoInterface::setWarningsEnabled(false) to turn off this warning.\n")); + + br_md5_context context; + br_md5_init(&context); + br_md5_update(&context, data, dataLength); + br_md5_out(&context, resultArray); + return resultArray; + } + + String md5Hash(const String &message) + { + uint8_t hash[MD5_NATURAL_LENGTH]; + md5Hash(message.c_str(), message.length(), hash); + return 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 resultArrayLength) + { + return createBearsslHmac(&br_md5_vtable, data, dataLength, hashKey, hashKeyLength, resultArray, resultArrayLength); + } + + String md5Hmac(const String &message, const void *hashKey, const size_t hashKeyLength, const size_t hmacLength) + { + return createBearsslHmac(&br_md5_vtable, MD5_NATURAL_LENGTH, message, hashKey, hashKeyLength, hmacLength); + } + + void *md5HmacCT(const void *data, const size_t dataLength, const void *hashKey, const size_t hashKeyLength, void *resultArray, const size_t resultArrayLength) + { + return createBearsslHmacCT(&br_md5_vtable, data, dataLength, hashKey, hashKeyLength, resultArray, resultArrayLength); + } + + String md5HmacCT(const String &message, const void *hashKey, const size_t hashKeyLength, const size_t hmacLength) + { + return createBearsslHmacCT(&br_md5_vtable, MD5_NATURAL_LENGTH, message, hashKey, hashKeyLength, hmacLength); + } + + + // #################### SHA-1 #################### + + // resultArray must have size SHA1_NATURAL_LENGTH or greater + void *sha1Hash(const void *data, const size_t dataLength, void *resultArray) + { + if(warningsEnabled()) + Serial.println(F("\nWARNING! The SHA-1 hash is broken in terms of attacker resistance.\n" + "Only use it in those cases where attacker resistance is not important. Prefer SHA-256 or higher otherwise.\n" + "Use CryptoInterface::setWarningsEnabled(false) to turn off this warning.\n")); + + br_sha1_context context; + br_sha1_init(&context); + br_sha1_update(&context, data, dataLength); + br_sha1_out(&context, resultArray); + return resultArray; + } + + String sha1Hash(const String &message) + { + uint8_t hash[SHA1_NATURAL_LENGTH]; + sha1Hash(message.c_str(), message.length(), hash); + return 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 resultArrayLength) + { + return createBearsslHmac(&br_sha1_vtable, data, dataLength, hashKey, hashKeyLength, resultArray, resultArrayLength); + } + + String sha1Hmac(const String &message, const void *hashKey, const size_t hashKeyLength, const size_t hmacLength) + { + return createBearsslHmac(&br_sha1_vtable, SHA1_NATURAL_LENGTH, message, hashKey, hashKeyLength, hmacLength); + } + + void *sha1HmacCT(const void *data, const size_t dataLength, const void *hashKey, const size_t hashKeyLength, void *resultArray, const size_t resultArrayLength) + { + return createBearsslHmacCT(&br_sha1_vtable, data, dataLength, hashKey, hashKeyLength, resultArray, resultArrayLength); + } + + String sha1HmacCT(const String &message, const void *hashKey, const size_t hashKeyLength, const size_t hmacLength) + { + return createBearsslHmacCT(&br_sha1_vtable, SHA1_NATURAL_LENGTH, message, hashKey, hashKeyLength, hmacLength); + } + + + // #################### SHA-224 #################### + + // resultArray must have size SHA224_NATURAL_LENGTH or greater + void *sha224Hash(const void *data, const size_t dataLength, void *resultArray) + { + br_sha224_context context; + br_sha224_init(&context); + br_sha224_update(&context, data, dataLength); + br_sha224_out(&context, resultArray); + return resultArray; + } + + String sha224Hash(const String &message) + { + uint8_t hash[SHA224_NATURAL_LENGTH]; + sha224Hash(message.c_str(), message.length(), hash); + return 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 resultArrayLength) + { + return createBearsslHmac(&br_sha224_vtable, data, dataLength, hashKey, hashKeyLength, resultArray, resultArrayLength); + } + + String sha224Hmac(const String &message, const void *hashKey, const size_t hashKeyLength, const size_t hmacLength) + { + return createBearsslHmac(&br_sha224_vtable, SHA224_NATURAL_LENGTH, message, hashKey, hashKeyLength, hmacLength); + } + + void *sha224HmacCT(const void *data, const size_t dataLength, const void *hashKey, const size_t hashKeyLength, void *resultArray, const size_t resultArrayLength) + { + return createBearsslHmacCT(&br_sha224_vtable, data, dataLength, hashKey, hashKeyLength, resultArray, resultArrayLength); + } + + String sha224HmacCT(const String &message, const void *hashKey, const size_t hashKeyLength, const size_t hmacLength) + { + return createBearsslHmacCT(&br_sha224_vtable, SHA224_NATURAL_LENGTH, message, hashKey, hashKeyLength, hmacLength); + } + + + // #################### SHA-256 #################### + + // resultArray must have size SHA256_NATURAL_LENGTH or greater + void *sha256Hash(const void *data, const size_t dataLength, void *resultArray) + { + br_sha256_context context; + br_sha256_init(&context); + br_sha256_update(&context, data, dataLength); + br_sha256_out(&context, resultArray); + return resultArray; + } + + String sha256Hash(const String &message) + { + uint8_t hash[SHA256_NATURAL_LENGTH]; + sha256Hash(message.c_str(), message.length(), hash); + return 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 resultArrayLength) + { + return createBearsslHmac(&br_sha256_vtable, data, dataLength, hashKey, hashKeyLength, resultArray, resultArrayLength); + } + + String sha256Hmac(const String &message, const void *hashKey, const size_t hashKeyLength, const size_t hmacLength) + { + return createBearsslHmac(&br_sha256_vtable, SHA256_NATURAL_LENGTH, message, hashKey, hashKeyLength, hmacLength); + } + + void *sha256HmacCT(const void *data, const size_t dataLength, const void *hashKey, const size_t hashKeyLength, void *resultArray, const size_t resultArrayLength) + { + return createBearsslHmacCT(&br_sha256_vtable, data, dataLength, hashKey, hashKeyLength, resultArray, resultArrayLength); + } + + String sha256HmacCT(const String &message, const void *hashKey, const size_t hashKeyLength, const size_t hmacLength) + { + return createBearsslHmacCT(&br_sha256_vtable, SHA256_NATURAL_LENGTH, message, hashKey, hashKeyLength, hmacLength); + } + + + // #################### SHA-384 #################### + + // resultArray must have size SHA384_NATURAL_LENGTH or greater + void *sha384Hash(const void *data, const size_t dataLength, void *resultArray) + { + br_sha384_context context; + br_sha384_init(&context); + br_sha384_update(&context, data, dataLength); + br_sha384_out(&context, resultArray); + return resultArray; + } + + String sha384Hash(const String &message) + { + uint8_t hash[SHA384_NATURAL_LENGTH]; + sha384Hash(message.c_str(), message.length(), hash); + return 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 resultArrayLength) + { + return createBearsslHmac(&br_sha384_vtable, data, dataLength, hashKey, hashKeyLength, resultArray, resultArrayLength); + } + + String sha384Hmac(const String &message, const void *hashKey, const size_t hashKeyLength, const size_t hmacLength) + { + return createBearsslHmac(&br_sha384_vtable, SHA384_NATURAL_LENGTH, message, hashKey, hashKeyLength, hmacLength); + } + + void *sha384HmacCT(const void *data, const size_t dataLength, const void *hashKey, const size_t hashKeyLength, void *resultArray, const size_t resultArrayLength) + { + return createBearsslHmacCT(&br_sha384_vtable, data, dataLength, hashKey, hashKeyLength, resultArray, resultArrayLength); + } + + String sha384HmacCT(const String &message, const void *hashKey, const size_t hashKeyLength, const size_t hmacLength) + { + return createBearsslHmacCT(&br_sha384_vtable, SHA384_NATURAL_LENGTH, message, hashKey, hashKeyLength, hmacLength); + } + + + // #################### SHA-512 #################### - bool verifyBearsslHmac(const String &message, const String &messageHmac, const uint8_t *hashKey, uint8_t hashKeyLength) + // resultArray must have size SHA512_NATURAL_LENGTH or greater + void *sha512Hash(const void *data, const size_t dataLength, void *resultArray) + { + br_sha512_context context; + br_sha512_init(&context); + br_sha512_update(&context, data, dataLength); + br_sha512_out(&context, resultArray); + return resultArray; + } + + String sha512Hash(const String &message) { - String generatedHmac = createBearsslHmac(message, hashKey, hashKeyLength, messageHmac.length()/2); // We know that each HMAC byte should become 2 String characters due to uint8ArrayToHexString. - if(generatedHmac == messageHmac) - return true; - else - return false; + uint8_t hash[SHA512_NATURAL_LENGTH]; + sha512Hash(message.c_str(), message.length(), hash); + return 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 resultArrayLength) + { + return createBearsslHmac(&br_sha512_vtable, data, dataLength, hashKey, hashKeyLength, resultArray, resultArrayLength); } + + String sha512Hmac(const String &message, const void *hashKey, const size_t hashKeyLength, const size_t hmacLength) + { + return createBearsslHmac(&br_sha512_vtable, SHA512_NATURAL_LENGTH, message, hashKey, hashKeyLength, hmacLength); + } + + void *sha512HmacCT(const void *data, const size_t dataLength, const void *hashKey, const size_t hashKeyLength, void *resultArray, const size_t resultArrayLength) + { + return createBearsslHmacCT(&br_sha512_vtable, data, dataLength, hashKey, hashKeyLength, resultArray, resultArrayLength); + } + + String sha512HmacCT(const String &message, const void *hashKey, const size_t hashKeyLength, const size_t hmacLength) + { + return createBearsslHmacCT(&br_sha512_vtable, SHA512_NATURAL_LENGTH, message, hashKey, hashKeyLength, hmacLength); + } + + + // #################### MD5+SHA-1 #################### + + // resultArray must have size MD5SHA1_NATURAL_LENGTH or greater + void *md5sha1Hash(const void *data, const size_t dataLength, void *resultArray) + { + br_md5sha1_context context; + br_md5sha1_init(&context); + br_md5sha1_update(&context, data, dataLength); + br_md5sha1_out(&context, resultArray); + return resultArray; + } + + String md5sha1Hash(const String &message) + { + uint8_t hash[MD5SHA1_NATURAL_LENGTH]; + md5sha1Hash(message.c_str(), message.length(), hash); + return uint8ArrayToHexString(hash, MD5SHA1_NATURAL_LENGTH); + } + } diff --git a/libraries/ESP8266WiFiMesh/src/CryptoInterface.h b/libraries/ESP8266WiFiMesh/src/CryptoInterface.h index 5a348a069f..2b87c44fd1 100644 --- a/libraries/ESP8266WiFiMesh/src/CryptoInterface.h +++ b/libraries/ESP8266WiFiMesh/src/CryptoInterface.h @@ -23,98 +23,622 @@ * THE SOFTWARE. */ -#include +#ifndef __ESP8266ARDUINOCRYPTOINTERFACE_H__ +#define __ESP8266ARDUINOCRYPTOINTERFACE_H__ -#ifndef __MESHCRYPTOINTERFACE_H__ -#define __MESHCRYPTOINTERFACE_H__ +#include namespace CryptoInterface { - const uint8_t SHA256HMAC_NATURAL_LENGTH = 32; + /** + * Regarding constant-time (CT) HMAC: + * + * Basically, constant-time algorithms makes it harder for attackers to learn things about your system based on the execution time of code. + * Good intro here: https://www.bearssl.org/constanttime.html + * + * It should be noted that every HMAC is already partially constant-time. Quoting the link above: + * "Hash functions implemented by BearSSL (MD5, SHA-1, SHA-224, SHA-256, SHA-384 and SHA-512) consist in bitwise logical operations and additions on 32-bit or 64-bit words, + * naturally yielding constant-time operations. HMAC is naturally as constant-time as the underlying hash function. The size of the MACed data, and the size of the key, + * may leak, though; only the contents are protected." + * + * For messages much smaller than ctMaxDataLength(), constant-time processing takes substantially longer time to complete than a normal HMAC, + * determined by the size of (ctMaxDataLength() - ctMinDataLength()). + * Constant-time processing also sets limits on the data length. + * + * Making the fixed data length limits variable will generally defeat the purpose of using constant-time. + * Using data that exceeds the fixed data length limits will create the wrong HMAC. + */ + + + constexpr uint8_t MD5_NATURAL_LENGTH = 16; + constexpr uint8_t SHA1_NATURAL_LENGTH = 20; + constexpr uint8_t SHA224_NATURAL_LENGTH = 28; + constexpr uint8_t SHA256_NATURAL_LENGTH = 32; + constexpr uint8_t SHA384_NATURAL_LENGTH = 48; + constexpr uint8_t SHA512_NATURAL_LENGTH = 64; + + /** + * MD5+SHA-1 is the concatenation of MD5 and SHA-1 computed over the same input; in the implementation, the internal data buffer is shared, + * thus making it more memory-efficient than separate MD5 and SHA-1. It can be useful in implementing SSL 3.0, TLS 1.0 and TLS 1.1. + */ + constexpr uint8_t MD5SHA1_NATURAL_LENGTH = 36; + + constexpr uint32_t ctMaxDiff = 1073741823; // 2^30 - 1 + + /** + * This function allows for fine-tuning of the specifications for the constant time calculations. + * It should not be changed once a constant time function has been used at least once. + * Otherwise the constant time will not be constant for the used functions. + * + * The difference ctMaxDataLength() - ctMinDataLength() MUST be less than 2^30 (i.e. about one gigabyte). + */ + void setCtMinDataLength(const size_t ctMinDataLength); + /** + * 0 by default. + */ + size_t ctMinDataLength(); + + /** + * This function allows for fine-tuning of the specifications for the constant time calculations. + * It should not be changed once a constant time function has been used at least once. + * Otherwise the constant time will not be constant for the used functions. + * + * The difference ctMaxDataLength() - ctMinDataLength() MUST be less than 2^30 (i.e. about one gigabyte). + */ + void setCtMaxDataLength(const size_t ctMaxDataLength); + /** + * 1024 by default. + */ + size_t ctMaxDataLength(); + + /** + * Turn on or off warning Serial prints from the CryptoInterface functions. + * + * @param warningsEnabled If true, warnings will be printed to Serial. + */ + void setWarningsEnabled(bool warningsEnabled); + bool warningsEnabled(); + + // #################### MD5 #################### + + /** + * Create a MD5 hash of the data. The result will be MD5_NATURAL_LENGTH bytes long and stored in resultArray. + * Uses the BearSSL cryptographic library. + * + * @param data The data array from which to create the hash. + * @param dataLength The length of the data array in bytes. + * @param resultArray The array wherein to store the resulting hash. MUST be be able to contain MD5_NATURAL_LENGTH bytes or more. + * + * @return A pointer to resultArray. + */ + void *md5Hash(const void *data, const size_t dataLength, void *resultArray); + + /** + * Create a MD5 hash of the data. The result will be MD5_NATURAL_LENGTH bytes long and returned as a String in HEX format. + * Uses the BearSSL cryptographic library. + * + * @param message The string from which to create the hash. + * + * @return A String with the generated hash in HEX format. + */ + String md5Hash(const String &message); + /** - * Create a SHA256 HMAC from the message, using the provided hashKey. The result will be resultArrayLength bytes long and stored in resultArray. + * Create a MD5 HMAC from the data, using the provided hashKey. The result will be resultArrayLength bytes long and stored in resultArray. + * Uses the BearSSL cryptographic library. + * + * @param data The data array from which to create the HMAC. + * @param dataLength The length of the data array in bytes. + * @param hashKey The hash key to use when creating the HMAC. + * @param hashKeyLength The length of the hash key in bytes. + * @param resultArray The array wherein to store the resulting HMAC. + * @param resultArrayLength The length of resultArray in bytes. Determines the HMAC length. If resultArrayLength is greater than MD5_NATURAL_LENGTH, + * the first (lowest index) MD5_NATURAL_LENGTH bytes of resultArray will be used for the HMAC. + * + * @return A pointer to resultArray. + */ + void *md5Hmac(const void *data, const size_t dataLength, const void *hashKey, const size_t hashKeyLength, void *resultArray, const size_t resultArrayLength); + + /** + * Create a MD5 HMAC from the message, using the provided hashKey. The result will be hmacLength bytes long and returned as a String in HEX format. * Uses the BearSSL cryptographic library. * * @param message The string from which to create the HMAC. * @param hashKey The hash key to use when creating the HMAC. * @param hashKeyLength The length of the hash key in bytes. + * @param hmacLength The desired length of the generated HMAC, in bytes. Valid values are 1 to MD5_NATURAL_LENGTH. + * + * @return A String with the generated HMAC in HEX format. + */ + String md5Hmac(const String &message, const void *hashKey, const size_t hashKeyLength, const size_t hmacLength); + + /** + * Create a MD5 HMAC from the data, using the provided hashKey. The result will be resultArrayLength bytes long and stored in resultArray. + * Constant-time version. + * Uses the BearSSL cryptographic library. + * + * @param data The data array from which to create the HMAC. + * @param dataLength The length of the data array in bytes. Valid values are in the range [ctMinDataLength(), ctMaxDataLength()]. + * @param hashKey The hash key to use when creating the HMAC. + * @param hashKeyLength The length of the hash key in bytes. * @param resultArray The array wherein to store the resulting HMAC. - * @param resultArrayLength The length of resultArray in bytes. Determines the HMAC length. If resultArrayLength is greater than SHA256HMAC_NATURAL_LENGTH, - * the first (lowest index) SHA256HMAC_NATURAL_LENGTH bytes of resultArray will be used for the HMAC. + * @param resultArrayLength The length of resultArray in bytes. Determines the HMAC length. If resultArrayLength is greater than MD5_NATURAL_LENGTH, + * the first (lowest index) MD5_NATURAL_LENGTH bytes of resultArray will be used for the HMAC. * * @return A pointer to resultArray. */ - uint8_t *createBearsslHmac(const String &message, const uint8_t *hashKey, uint8_t hashKeyLength, uint8_t *resultArray, size_t resultArrayLength); + void *md5HmacCT(const void *data, const size_t dataLength, const void *hashKey, const size_t hashKeyLength, void *resultArray, const size_t resultArrayLength); + + /** + * Create a MD5 HMAC from the message, using the provided hashKey. The result will be hmacLength bytes long and returned as a String in HEX format. + * Constant-time version. + * Uses the BearSSL cryptographic library. + * + * @param message The string from which to create the HMAC. Must have a length in the range [ctMinDataLength(), ctMaxDataLength()]. + * @param hashKey The hash key to use when creating the HMAC. + * @param hashKeyLength The length of the hash key in bytes. + * @param hmacLength The desired length of the generated HMAC, in bytes. Valid values are 1 to MD5_NATURAL_LENGTH. + * + * @return A String with the generated HMAC in HEX format. + */ + String md5HmacCT(const String &message, const void *hashKey, const size_t hashKeyLength, const size_t hmacLength); + + + // #################### SHA-1 #################### + + /** + * Create a SHA1 hash of the data. The result will be SHA1_NATURAL_LENGTH bytes long and stored in resultArray. + * Uses the BearSSL cryptographic library. + * + * @param data The data array from which to create the hash. + * @param dataLength The length of the data array in bytes. + * @param resultArray The array wherein to store the resulting hash. MUST be be able to contain SHA1_NATURAL_LENGTH bytes or more. + * + * @return A pointer to resultArray. + */ + void *sha1Hash(const void *data, const size_t dataLength, void *resultArray); /** - * Create a SHA256 HMAC from the message, using the provided hashKey. The result will be hmacLength bytes long and returned as a String in HEX format. + * Create a SHA1 hash of the data. The result will be SHA1_NATURAL_LENGTH bytes long and returned as a String in HEX format. + * Uses the BearSSL cryptographic library. + * + * @param message The string from which to create the hash. + * + * @return A String with the generated hash in HEX format. + */ + String sha1Hash(const String &message); + + /** + * Create a SHA1 HMAC from the data, using the provided hashKey. The result will be resultArrayLength bytes long and stored in resultArray. + * Uses the BearSSL cryptographic library. + * + * @param data The data array from which to create the HMAC. + * @param dataLength The length of the data array in bytes. + * @param hashKey The hash key to use when creating the HMAC. + * @param hashKeyLength The length of the hash key in bytes. + * @param resultArray The array wherein to store the resulting HMAC. + * @param resultArrayLength The length of resultArray in bytes. Determines the HMAC length. If resultArrayLength is greater than SHA1_NATURAL_LENGTH, + * the first (lowest index) SHA1_NATURAL_LENGTH bytes of resultArray will be used for the HMAC. + * + * @return A pointer to resultArray. + */ + void *sha1Hmac(const void *data, const size_t dataLength, const void *hashKey, const size_t hashKeyLength, void *resultArray, const size_t resultArrayLength); + + /** + * Create a SHA1 HMAC from the message, using the provided hashKey. The result will be hmacLength bytes long and returned as a String in HEX format. * Uses the BearSSL cryptographic library. * * @param message The string from which to create the HMAC. * @param hashKey The hash key to use when creating the HMAC. * @param hashKeyLength The length of the hash key in bytes. - * @param hmacLength The desired length of the generated HMAC, in bytes. Valid values are 1 to 32. Defaults to SHA256HMAC_NATURAL_LENGTH. + * @param hmacLength The desired length of the generated HMAC, in bytes. Valid values are 1 to SHA1_NATURAL_LENGTH. * * @return A String with the generated HMAC in HEX format. */ - String createBearsslHmac(const String &message, const uint8_t *hashKey, uint8_t hashKeyLength, size_t hmacLength = SHA256HMAC_NATURAL_LENGTH); + String sha1Hmac(const String &message, const void *hashKey, const size_t hashKeyLength, const size_t hmacLength); + + /** + * Create a SHA1 HMAC from the data, using the provided hashKey. The result will be resultArrayLength bytes long and stored in resultArray. + * Constant-time version. + * Uses the BearSSL cryptographic library. + * + * @param data The data array from which to create the HMAC. + * @param dataLength The length of the data array in bytes. Valid values are in the range [ctMinDataLength(), ctMaxDataLength()]. + * @param hashKey The hash key to use when creating the HMAC. + * @param hashKeyLength The length of the hash key in bytes. + * @param resultArray The array wherein to store the resulting HMAC. + * @param resultArrayLength The length of resultArray in bytes. Determines the HMAC length. If resultArrayLength is greater than SHA1_NATURAL_LENGTH, + * the first (lowest index) SHA1_NATURAL_LENGTH bytes of resultArray will be used for the HMAC. + * + * @return A pointer to resultArray. + */ + void *sha1HmacCT(const void *data, const size_t dataLength, const void *hashKey, const size_t hashKeyLength, void *resultArray, const size_t resultArrayLength); + + /** + * Create a SHA1 HMAC from the message, using the provided hashKey. The result will be hmacLength bytes long and returned as a String in HEX format. + * Constant-time version. + * Uses the BearSSL cryptographic library. + * + * @param message The string from which to create the HMAC. Must have a length in the range [ctMinDataLength(), ctMaxDataLength()]. + * @param hashKey The hash key to use when creating the HMAC. + * @param hashKeyLength The length of the hash key in bytes. + * @param hmacLength The desired length of the generated HMAC, in bytes. Valid values are 1 to SHA1_NATURAL_LENGTH. + * + * @return A String with the generated HMAC in HEX format. + */ + String sha1HmacCT(const String &message, const void *hashKey, const size_t hashKeyLength, const size_t hmacLength); + + + // #################### SHA-224 #################### + + /** + * Create a SHA224 hash of the data. The result will be SHA224_NATURAL_LENGTH bytes long and stored in resultArray. + * Uses the BearSSL cryptographic library. + * + * @param data The data array from which to create the hash. + * @param dataLength The length of the data array in bytes. + * @param resultArray The array wherein to store the resulting hash. MUST be be able to contain SHA224_NATURAL_LENGTH bytes or more. + * + * @return A pointer to resultArray. + */ + void *sha224Hash(const void *data, const size_t dataLength, void *resultArray); /** - * Create a SHA256 HMAC from the message, using the provided hashKey. The result will be resultArrayLength bytes long and stored in resultArray. + * Create a SHA224 hash of the data. The result will be SHA224_NATURAL_LENGTH bytes long and returned as a String in HEX format. * Uses the BearSSL cryptographic library. * - * Constant-time version of createBearsslHmac(). More constant-time info here: https://www.bearssl.org/constanttime.html - * For small messages, it takes substantially longer time to complete than a normal HMAC (5 ms vs 2 ms in a quick benchmark, - * determined by the difference between min and max allowed message length), and it also sets a maximum length that messages can be (set to 1000 bytes here). - * Making the fixed max length variable would defeat the whole purpose of using constant-time, and not making it variable would create the wrong HMAC if message size exceeds the maximum. + * @param message The string from which to create the hash. * - * Also, HMAC is already partially constant-time. Quoting the link above: - * "Hash functions implemented by BearSSL (MD5, SHA-1, SHA-224, SHA-256, SHA-384 and SHA-512) consist in bitwise logical operations and additions on 32-bit or 64-bit words, - * naturally yielding constant-time operations. HMAC is naturally as constant-time as the underlying hash function. The size of the MACed data, and the size of the key, - * may leak, though; only the contents are protected." + * @return A String with the generated hash in HEX format. + */ + String sha224Hash(const String &message); + + /** + * Create a SHA224 HMAC from the data, using the provided hashKey. The result will be resultArrayLength bytes long and stored in resultArray. + * Uses the BearSSL cryptographic library. + * + * @param data The data array from which to create the HMAC. + * @param dataLength The length of the data array in bytes. + * @param hashKey The hash key to use when creating the HMAC. + * @param hashKeyLength The length of the hash key in bytes. + * @param resultArray The array wherein to store the resulting HMAC. + * @param resultArrayLength The length of resultArray in bytes. Determines the HMAC length. If resultArrayLength is greater than SHA224_NATURAL_LENGTH, + * the first (lowest index) SHA224_NATURAL_LENGTH bytes of resultArray will be used for the HMAC. + * + * @return A pointer to resultArray. + */ + void *sha224Hmac(const void *data, const size_t dataLength, const void *hashKey, const size_t hashKeyLength, void *resultArray, const size_t resultArrayLength); + + /** + * Create a SHA224 HMAC from the message, using the provided hashKey. The result will be hmacLength bytes long and returned as a String in HEX format. + * Uses the BearSSL cryptographic library. + * + * @param message The string from which to create the HMAC. + * @param hashKey The hash key to use when creating the HMAC. + * @param hashKeyLength The length of the hash key in bytes. + * @param hmacLength The desired length of the generated HMAC, in bytes. Valid values are 1 to SHA224_NATURAL_LENGTH. * - * Thus the non constant-time version is used within the mesh framework instead. + * @return A String with the generated HMAC in HEX format. + */ + String sha224Hmac(const String &message, const void *hashKey, const size_t hashKeyLength, const size_t hmacLength); + + /** + * Create a SHA224 HMAC from the data, using the provided hashKey. The result will be resultArrayLength bytes long and stored in resultArray. + * Constant-time version. + * Uses the BearSSL cryptographic library. * - * @param message The string from which to create the HMAC. Min size 0 bytes. Max size 1000 bytes. + * @param data The data array from which to create the HMAC. + * @param dataLength The length of the data array in bytes. Valid values are in the range [ctMinDataLength(), ctMaxDataLength()]. * @param hashKey The hash key to use when creating the HMAC. * @param hashKeyLength The length of the hash key in bytes. * @param resultArray The array wherein to store the resulting HMAC. - * @param resultArrayLength The length of resultArray in bytes. Determines the HMAC length. If resultArrayLength is greater than SHA256HMAC_NATURAL_LENGTH, - * the first (lowest index) SHA256HMAC_NATURAL_LENGTH bytes of resultArray will be used for the HMAC. + * @param resultArrayLength The length of resultArray in bytes. Determines the HMAC length. If resultArrayLength is greater than SHA224_NATURAL_LENGTH, + * the first (lowest index) SHA224_NATURAL_LENGTH bytes of resultArray will be used for the HMAC. * * @return A pointer to resultArray. */ - uint8_t *createBearsslHmacCT(const String &message, const uint8_t *hashKey, uint8_t hashKeyLength, uint8_t *resultArray, size_t resultArrayLength); + void *sha224HmacCT(const void *data, const size_t dataLength, const void *hashKey, const size_t hashKeyLength, void *resultArray, const size_t resultArrayLength); + + /** + * Create a SHA224 HMAC from the message, using the provided hashKey. The result will be hmacLength bytes long and returned as a String in HEX format. + * Constant-time version. + * Uses the BearSSL cryptographic library. + * + * @param message The string from which to create the HMAC. Must have a length in the range [ctMinDataLength(), ctMaxDataLength()]. + * @param hashKey The hash key to use when creating the HMAC. + * @param hashKeyLength The length of the hash key in bytes. + * @param hmacLength The desired length of the generated HMAC, in bytes. Valid values are 1 to SHA224_NATURAL_LENGTH. + * + * @return A String with the generated HMAC in HEX format. + */ + String sha224HmacCT(const String &message, const void *hashKey, const size_t hashKeyLength, const size_t hmacLength); + + + // #################### SHA-256 #################### + + /** + * Create a SHA256 hash of the data. The result will be SHA256_NATURAL_LENGTH bytes long and stored in resultArray. + * Uses the BearSSL cryptographic library. + * + * @param data The data array from which to create the hash. + * @param dataLength The length of the data array in bytes. + * @param resultArray The array wherein to store the resulting hash. MUST be be able to contain SHA256_NATURAL_LENGTH bytes or more. + * + * @return A pointer to resultArray. + */ + void *sha256Hash(const void *data, const size_t dataLength, void *resultArray); + /** + * Create a SHA256 hash of the data. The result will be SHA256_NATURAL_LENGTH bytes long and returned as a String in HEX format. + * Uses the BearSSL cryptographic library. + * + * @param message The string from which to create the hash. + * + * @return A String with the generated hash in HEX format. + */ + String sha256Hash(const String &message); + + /** + * Create a SHA256 HMAC from the data, using the provided hashKey. The result will be resultArrayLength bytes long and stored in resultArray. + * Uses the BearSSL cryptographic library. + * + * @param data The data array from which to create the HMAC. + * @param dataLength The length of the data array in bytes. + * @param hashKey The hash key to use when creating the HMAC. + * @param hashKeyLength The length of the hash key in bytes. + * @param resultArray The array wherein to store the resulting HMAC. + * @param resultArrayLength The length of resultArray in bytes. Determines the HMAC length. If resultArrayLength is greater than SHA256_NATURAL_LENGTH, + * the first (lowest index) SHA256_NATURAL_LENGTH bytes of resultArray will be used for the HMAC. + * + * @return A pointer to resultArray. + */ + void *sha256Hmac(const void *data, const size_t dataLength, const void *hashKey, const size_t hashKeyLength, void *resultArray, const size_t resultArrayLength); + /** * Create a SHA256 HMAC from the message, using the provided hashKey. The result will be hmacLength bytes long and returned as a String in HEX format. * Uses the BearSSL cryptographic library. * - * Constant-time version of createBearsslHmac(). More constant-time info here: https://www.bearssl.org/constanttime.html - * Not used within the mesh framework for reasons outlined in the uint8_t *createBearsslHmacCT() description. + * @param message The string from which to create the HMAC. + * @param hashKey The hash key to use when creating the HMAC. + * @param hashKeyLength The length of the hash key in bytes. + * @param hmacLength The desired length of the generated HMAC, in bytes. Valid values are 1 to SHA256_NATURAL_LENGTH. + * + * @return A String with the generated HMAC in HEX format. + */ + String sha256Hmac(const String &message, const void *hashKey, const size_t hashKeyLength, const size_t hmacLength); + + /** + * Create a SHA256 HMAC from the data, using the provided hashKey. The result will be resultArrayLength bytes long and stored in resultArray. + * Constant-time version. + * Uses the BearSSL cryptographic library. + * + * @param data The data array from which to create the HMAC. + * @param dataLength The length of the data array in bytes. Valid values are in the range [ctMinDataLength(), ctMaxDataLength()]. + * @param hashKey The hash key to use when creating the HMAC. + * @param hashKeyLength The length of the hash key in bytes. + * @param resultArray The array wherein to store the resulting HMAC. + * @param resultArrayLength The length of resultArray in bytes. Determines the HMAC length. If resultArrayLength is greater than SHA256_NATURAL_LENGTH, + * the first (lowest index) SHA256_NATURAL_LENGTH bytes of resultArray will be used for the HMAC. + * + * @return A pointer to resultArray. + */ + void *sha256HmacCT(const void *data, const size_t dataLength, const void *hashKey, const size_t hashKeyLength, void *resultArray, const size_t resultArrayLength); + + /** + * Create a SHA256 HMAC from the message, using the provided hashKey. The result will be hmacLength bytes long and returned as a String in HEX format. + * Constant-time version. + * Uses the BearSSL cryptographic library. + * + * @param message The string from which to create the HMAC. Must have a length in the range [ctMinDataLength(), ctMaxDataLength()]. + * @param hashKey The hash key to use when creating the HMAC. + * @param hashKeyLength The length of the hash key in bytes. + * @param hmacLength The desired length of the generated HMAC, in bytes. Valid values are 1 to SHA256_NATURAL_LENGTH. + * + * @return A String with the generated HMAC in HEX format. + */ + String sha256HmacCT(const String &message, const void *hashKey, const size_t hashKeyLength, const size_t hmacLength); + + + // #################### SHA-384 #################### + + /** + * Create a SHA384 hash of the data. The result will be SHA384_NATURAL_LENGTH bytes long and stored in resultArray. + * Uses the BearSSL cryptographic library. + * + * @param data The data array from which to create the hash. + * @param dataLength The length of the data array in bytes. + * @param resultArray The array wherein to store the resulting hash. MUST be be able to contain SHA384_NATURAL_LENGTH bytes or more. + * + * @return A pointer to resultArray. + */ + void *sha384Hash(const void *data, const size_t dataLength, void *resultArray); + + /** + * Create a SHA384 hash of the data. The result will be SHA384_NATURAL_LENGTH bytes long and returned as a String in HEX format. + * Uses the BearSSL cryptographic library. + * + * @param message The string from which to create the hash. + * + * @return A String with the generated hash in HEX format. + */ + String sha384Hash(const String &message); + + /** + * Create a SHA384 HMAC from the data, using the provided hashKey. The result will be resultArrayLength bytes long and stored in resultArray. + * Uses the BearSSL cryptographic library. + * + * @param data The data array from which to create the HMAC. + * @param dataLength The length of the data array in bytes. + * @param hashKey The hash key to use when creating the HMAC. + * @param hashKeyLength The length of the hash key in bytes. + * @param resultArray The array wherein to store the resulting HMAC. + * @param resultArrayLength The length of resultArray in bytes. Determines the HMAC length. If resultArrayLength is greater than SHA384_NATURAL_LENGTH, + * the first (lowest index) SHA384_NATURAL_LENGTH bytes of resultArray will be used for the HMAC. + * + * @return A pointer to resultArray. + */ + void *sha384Hmac(const void *data, const size_t dataLength, const void *hashKey, const size_t hashKeyLength, void *resultArray, const size_t resultArrayLength); + + /** + * Create a SHA384 HMAC from the message, using the provided hashKey. The result will be hmacLength bytes long and returned as a String in HEX format. + * Uses the BearSSL cryptographic library. + * + * @param message The string from which to create the HMAC. + * @param hashKey The hash key to use when creating the HMAC. + * @param hashKeyLength The length of the hash key in bytes. + * @param hmacLength The desired length of the generated HMAC, in bytes. Valid values are 1 to SHA384_NATURAL_LENGTH. + * + * @return A String with the generated HMAC in HEX format. + */ + String sha384Hmac(const String &message, const void *hashKey, const size_t hashKeyLength, const size_t hmacLength); + + /** + * Create a SHA384 HMAC from the data, using the provided hashKey. The result will be resultArrayLength bytes long and stored in resultArray. + * Constant-time version. + * Uses the BearSSL cryptographic library. + * + * @param data The data array from which to create the HMAC. + * @param dataLength The length of the data array in bytes. Valid values are in the range [ctMinDataLength(), ctMaxDataLength()]. + * @param hashKey The hash key to use when creating the HMAC. + * @param hashKeyLength The length of the hash key in bytes. + * @param resultArray The array wherein to store the resulting HMAC. + * @param resultArrayLength The length of resultArray in bytes. Determines the HMAC length. If resultArrayLength is greater than SHA384_NATURAL_LENGTH, + * the first (lowest index) SHA384_NATURAL_LENGTH bytes of resultArray will be used for the HMAC. + * + * @return A pointer to resultArray. + */ + void *sha384HmacCT(const void *data, const size_t dataLength, const void *hashKey, const size_t hashKeyLength, void *resultArray, const size_t resultArrayLength); + + /** + * Create a SHA384 HMAC from the message, using the provided hashKey. The result will be hmacLength bytes long and returned as a String in HEX format. + * Constant-time version. + * Uses the BearSSL cryptographic library. * - * @param message The string from which to create the HMAC. Min size 0 bytes. Max size 1000 bytes. + * @param message The string from which to create the HMAC. Must have a length in the range [ctMinDataLength(), ctMaxDataLength()]. * @param hashKey The hash key to use when creating the HMAC. * @param hashKeyLength The length of the hash key in bytes. - * @param hmacLength The desired length of the generated HMAC, in bytes. Valid values are 1 to 32. Defaults to SHA256HMAC_NATURAL_LENGTH. + * @param hmacLength The desired length of the generated HMAC, in bytes. Valid values are 1 to SHA384_NATURAL_LENGTH. * * @return A String with the generated HMAC in HEX format. */ - String createBearsslHmacCT(const String &message, const uint8_t *hashKey, uint8_t hashKeyLength, size_t hmacLength = SHA256HMAC_NATURAL_LENGTH); + String sha384HmacCT(const String &message, const void *hashKey, const size_t hashKeyLength, const size_t hmacLength); + + + // #################### SHA-512 #################### /** - * Verify a SHA256 HMAC which was created from the message using the provided hashKey. + * Create a SHA512 hash of the data. The result will be SHA512_NATURAL_LENGTH bytes long and stored in resultArray. * Uses the BearSSL cryptographic library. * - * @param message The string from which the HMAC was created. - * @param messageHmac A string with the generated HMAC in HEX format. Valid messageHmac.length() is 2 to 64. + * @param data The data array from which to create the hash. + * @param dataLength The length of the data array in bytes. + * @param resultArray The array wherein to store the resulting hash. MUST be be able to contain SHA512_NATURAL_LENGTH bytes or more. + * + * @return A pointer to resultArray. + */ + void *sha512Hash(const void *data, const size_t dataLength, void *resultArray); + + /** + * Create a SHA512 hash of the data. The result will be SHA512_NATURAL_LENGTH bytes long and returned as a String in HEX format. + * Uses the BearSSL cryptographic library. + * + * @param message The string from which to create the hash. + * + * @return A String with the generated hash in HEX format. + */ + String sha512Hash(const String &message); + + /** + * Create a SHA512 HMAC from the data, using the provided hashKey. The result will be resultArrayLength bytes long and stored in resultArray. + * Uses the BearSSL cryptographic library. + * + * @param data The data array from which to create the HMAC. + * @param dataLength The length of the data array in bytes. * @param hashKey The hash key to use when creating the HMAC. * @param hashKeyLength The length of the hash key in bytes. + * @param resultArray The array wherein to store the resulting HMAC. + * @param resultArrayLength The length of resultArray in bytes. Determines the HMAC length. If resultArrayLength is greater than SHA512_NATURAL_LENGTH, + * the first (lowest index) SHA512_NATURAL_LENGTH bytes of resultArray will be used for the HMAC. + * + * @return A pointer to resultArray. + */ + void *sha512Hmac(const void *data, const size_t dataLength, const void *hashKey, const size_t hashKeyLength, void *resultArray, const size_t resultArrayLength); + + /** + * Create a SHA512 HMAC from the message, using the provided hashKey. The result will be hmacLength bytes long and returned as a String in HEX format. + * Uses the BearSSL cryptographic library. + * + * @param message The string from which to create the HMAC. + * @param hashKey The hash key to use when creating the HMAC. + * @param hashKeyLength The length of the hash key in bytes. + * @param hmacLength The desired length of the generated HMAC, in bytes. Valid values are 1 to SHA512_NATURAL_LENGTH. + * + * @return A String with the generated HMAC in HEX format. + */ + String sha512Hmac(const String &message, const void *hashKey, const size_t hashKeyLength, const size_t hmacLength); + + /** + * Create a SHA512 HMAC from the data, using the provided hashKey. The result will be resultArrayLength bytes long and stored in resultArray. + * Constant-time version. + * Uses the BearSSL cryptographic library. + * + * @param data The data array from which to create the HMAC. + * @param dataLength The length of the data array in bytes. Valid values are in the range [ctMinDataLength(), ctMaxDataLength()]. + * @param hashKey The hash key to use when creating the HMAC. + * @param hashKeyLength The length of the hash key in bytes. + * @param resultArray The array wherein to store the resulting HMAC. + * @param resultArrayLength The length of resultArray in bytes. Determines the HMAC length. If resultArrayLength is greater than SHA512_NATURAL_LENGTH, + * the first (lowest index) SHA512_NATURAL_LENGTH bytes of resultArray will be used for the HMAC. + * + * @return A pointer to resultArray. + */ + void *sha512HmacCT(const void *data, const size_t dataLength, const void *hashKey, const size_t hashKeyLength, void *resultArray, const size_t resultArrayLength); + + /** + * Create a SHA512 HMAC from the message, using the provided hashKey. The result will be hmacLength bytes long and returned as a String in HEX format. + * Constant-time version. + * Uses the BearSSL cryptographic library. + * + * @param message The string from which to create the HMAC. Must have a length in the range [ctMinDataLength(), ctMaxDataLength()]. + * @param hashKey The hash key to use when creating the HMAC. + * @param hashKeyLength The length of the hash key in bytes. + * @param hmacLength The desired length of the generated HMAC, in bytes. Valid values are 1 to SHA512_NATURAL_LENGTH. + * + * @return A String with the generated HMAC in HEX format. + */ + String sha512HmacCT(const String &message, const void *hashKey, const size_t hashKeyLength, const size_t hmacLength); + + + // #################### MD5+SHA-1 #################### + + /** + * Create a MD5+SHA-1 hash of the data. The result will be MD5SHA1_NATURAL_LENGTH bytes long and stored in resultArray. + * Uses the BearSSL cryptographic library. + * + * MD5+SHA-1 is the concatenation of MD5 and SHA-1 computed over the same input; in the implementation, the internal data buffer is shared, + * thus making it more memory-efficient than separate MD5 and SHA-1. It can be useful in implementing SSL 3.0, TLS 1.0 and TLS 1.1. + * + * @param data The data array from which to create the hash. + * @param dataLength The length of the data array in bytes. + * @param resultArray The array wherein to store the resulting hash. MUST be be able to contain MD5SHA1_NATURAL_LENGTH bytes or more. + * + * @return A pointer to resultArray. + */ + void *md5sha1Hash(const void *data, const size_t dataLength, void *resultArray); + + /** + * Create a MD5+SHA-1 hash of the data. The result will be MD5SHA1_NATURAL_LENGTH bytes long and returned as a String in HEX format. + * Uses the BearSSL cryptographic library. + * + * MD5+SHA-1 is the concatenation of MD5 and SHA-1 computed over the same input; in the implementation, the internal data buffer is shared, + * thus making it more memory-efficient than separate MD5 and SHA-1. It can be useful in implementing SSL 3.0, TLS 1.0 and TLS 1.1. + * + * @param message The string from which to create the hash. * - * @return True if the HMAC is correct. False otherwise. + * @return A String with the generated hash in HEX format. */ - bool verifyBearsslHmac(const String &message, const String &messageHmac, const uint8_t *hashKey, uint8_t hashKeyLength); + String md5sha1Hash(const String &message); } #endif diff --git a/libraries/ESP8266WiFiMesh/src/EncryptedConnectionData.cpp b/libraries/ESP8266WiFiMesh/src/EncryptedConnectionData.cpp index d83ece4d2a..c4992a7ae4 100644 --- a/libraries/ESP8266WiFiMesh/src/EncryptedConnectionData.cpp +++ b/libraries/ESP8266WiFiMesh/src/EncryptedConnectionData.cpp @@ -26,7 +26,7 @@ #include "UtilityFunctions.h" #include "TypeConversionFunctions.h" #include "JsonTranslator.h" -#include "CryptoInterface.h" +#include "MeshCryptoInterface.h" using EspnowProtocolInterpreter::espnowHashKeyLength; @@ -128,7 +128,7 @@ uint64_t EncryptedConnectionData::getOwnSessionKey() const { return _ownSessionK uint64_t EncryptedConnectionData::incrementSessionKey(uint64_t sessionKey, const uint8_t *hashKey, uint8_t hashKeyLength) { - String hmac = CryptoInterface::createBearsslHmac(uint64ToString(sessionKey), hashKey, hashKeyLength); + String hmac = MeshCryptoInterface::createMeshHmac(uint64ToString(sessionKey), hashKey, hashKeyLength); /* 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 diff --git a/libraries/ESP8266WiFiMesh/src/FloodingMesh.cpp b/libraries/ESP8266WiFiMesh/src/FloodingMesh.cpp index 021a2bfe0c..ab4dd4e108 100644 --- a/libraries/ESP8266WiFiMesh/src/FloodingMesh.cpp +++ b/libraries/ESP8266WiFiMesh/src/FloodingMesh.cpp @@ -28,7 +28,7 @@ std::set FloodingMesh::availableFloodingMeshes = {}; -char FloodingMesh::_broadcastMetadataDelimiter = 23; +char FloodingMesh::_metadataDelimiter = 23; void floodingMeshDelay(uint32_t durationMs) { @@ -156,10 +156,10 @@ void FloodingMesh::broadcast(const String &message) String messageID = generateMessageID(); - // Remove getEspnowMeshBackend().getMeshName() from the broadcastMetadata below to broadcast to all ESP-NOW nodes regardless of MeshName. + // Remove getEspnowMeshBackend().getMeshName() from the metadata below to broadcast to all ESP-NOW nodes regardless of MeshName. String targetMeshName = getEspnowMeshBackend().getMeshName(); - broadcastKernel(targetMeshName + String(broadcastMetadataDelimiter()) + messageID + String(broadcastMetadataDelimiter()) + message); + broadcastKernel(targetMeshName + String(metadataDelimiter()) + messageID + String(metadataDelimiter()) + message); } void FloodingMesh::broadcastKernel(const String &message) @@ -180,7 +180,7 @@ void FloodingMesh::encryptedBroadcast(const String &message) String messageID = generateMessageID(); - encryptedBroadcastKernel(messageID + String(broadcastMetadataDelimiter()) + message); + encryptedBroadcastKernel(messageID + String(metadataDelimiter()) + message); } void FloodingMesh::encryptedBroadcastKernel(const String &message) @@ -232,15 +232,15 @@ void FloodingMesh::setMessageLogSize(uint16_t messageLogSize) } uint16_t FloodingMesh::messageLogSize() { return _messageLogSize; } -void FloodingMesh::setBroadcastMetadataDelimiter(char broadcastMetadataDelimiter) +void FloodingMesh::setMetadataDelimiter(char metadataDelimiter) { // Using HEX number characters as a delimiter is a bad idea regardless of broadcast type, since they are always in the broadcast metadata - assert(broadcastMetadataDelimiter < 48 || 57 < broadcastMetadataDelimiter); - assert(broadcastMetadataDelimiter < 65 || 70 < broadcastMetadataDelimiter); + assert(metadataDelimiter < 48 || 57 < metadataDelimiter); + assert(metadataDelimiter < 65 || 70 < metadataDelimiter); - _broadcastMetadataDelimiter = broadcastMetadataDelimiter; + _metadataDelimiter = metadataDelimiter; } -char FloodingMesh::broadcastMetadataDelimiter() { return _broadcastMetadataDelimiter; } +char FloodingMesh::metadataDelimiter() { return _metadataDelimiter; } EspnowMeshBackend &FloodingMesh::getEspnowMeshBackend() { @@ -342,12 +342,12 @@ String FloodingMesh::_defaultRequestHandler(const String &request, MeshBackendBa String broadcastTarget = ""; String remainingRequest = ""; - if(request.charAt(0) == broadcastMetadataDelimiter()) + if(request.charAt(0) == metadataDelimiter()) { - int32_t broadcastTargetEndIndex = request.indexOf(broadcastMetadataDelimiter(), 1); + int32_t broadcastTargetEndIndex = request.indexOf(metadataDelimiter(), 1); if(broadcastTargetEndIndex == -1) - return ""; // broadcastMetadataDelimiter not found + return ""; // metadataDelimiter not found broadcastTarget = request.substring(1, broadcastTargetEndIndex + 1); // Include delimiter remainingRequest = request.substring(broadcastTargetEndIndex + 1); @@ -357,10 +357,10 @@ String FloodingMesh::_defaultRequestHandler(const String &request, MeshBackendBa remainingRequest = request; } - int32_t messageIDEndIndex = remainingRequest.indexOf(broadcastMetadataDelimiter()); + int32_t messageIDEndIndex = remainingRequest.indexOf(metadataDelimiter()); if(messageIDEndIndex == -1) - return ""; // broadcastMetadataDelimiter not found + return ""; // metadataDelimiter not found uint64_t messageID = stringToUint64(remainingRequest.substring(0, messageIDEndIndex)); @@ -447,16 +447,16 @@ void FloodingMesh::_defaultNetworkFilter(int numberOfNetworks, MeshBackendBase & */ bool FloodingMesh::_defaultBroadcastFilter(String &firstTransmission, EspnowMeshBackend &meshInstance) { - // This broadcastFilter will accept a transmission if it contains the broadcastMetadataDelimiter + // This broadcastFilter will accept a transmission if it contains the metadataDelimiter // and as metaData either no targetMeshName or a targetMeshName that matches the MeshName of meshInstance // and insertPreliminaryMessageID(messageID) returns true. // Broadcast firstTransmission String structure: targetMeshName+messageID+message. - int32_t metadataEndIndex = firstTransmission.indexOf(broadcastMetadataDelimiter()); + int32_t metadataEndIndex = firstTransmission.indexOf(metadataDelimiter()); if(metadataEndIndex == -1) - return false; // broadcastMetadataDelimiter not found + return false; // metadataDelimiter not found String targetMeshName = firstTransmission.substring(0, metadataEndIndex); @@ -466,17 +466,17 @@ bool FloodingMesh::_defaultBroadcastFilter(String &firstTransmission, EspnowMesh } else { - int32_t messageIDEndIndex = firstTransmission.indexOf(broadcastMetadataDelimiter(), metadataEndIndex + 1); + int32_t messageIDEndIndex = firstTransmission.indexOf(metadataDelimiter(), metadataEndIndex + 1); if(messageIDEndIndex == -1) - return false; // broadcastMetadataDelimiter not found + return false; // metadataDelimiter not found uint64_t messageID = stringToUint64(firstTransmission.substring(metadataEndIndex + 1, messageIDEndIndex)); if(insertPreliminaryMessageID(messageID)) { // Add broadcast identifier to stored message and mark as accepted broadcast. - firstTransmission = String(broadcastMetadataDelimiter()) + firstTransmission; + firstTransmission = String(metadataDelimiter()) + firstTransmission; return true; } else diff --git a/libraries/ESP8266WiFiMesh/src/FloodingMesh.h b/libraries/ESP8266WiFiMesh/src/FloodingMesh.h index cc41768415..6bd7f0afb4 100644 --- a/libraries/ESP8266WiFiMesh/src/FloodingMesh.h +++ b/libraries/ESP8266WiFiMesh/src/FloodingMesh.h @@ -217,11 +217,11 @@ class FloodingMesh { * Set the delimiter character used for metadata by every FloodingMesh instance. * Using characters found in the mesh name or in HEX numbers is unwise, as is using ','. * - * @param broadcastMetadataDelimiter The metadata delimiter character to use. + * @param metadataDelimiter The metadata delimiter character to use. * Defaults to 23 = End-of-Transmission-Block (ETB) control character in ASCII */ - static void setBroadcastMetadataDelimiter(char broadcastMetadataDelimiter); - static char broadcastMetadataDelimiter(); + static void setMetadataDelimiter(char metadataDelimiter); + static char metadataDelimiter(); /* * Gives you access to the EspnowMeshBackend used by the mesh node. @@ -274,7 +274,7 @@ class FloodingMesh { uint8_t _broadcastReceptionRedundancy = 2; - static char _broadcastMetadataDelimiter; // Defaults to 23 = End-of-Transmission-Block (ETB) control character in ASCII + static char _metadataDelimiter; // Defaults to 23 = End-of-Transmission-Block (ETB) control character in ASCII uint8_t _originMac[6] = {0}; diff --git a/libraries/ESP8266WiFiMesh/src/JsonTranslator.cpp b/libraries/ESP8266WiFiMesh/src/JsonTranslator.cpp index b78b1babe0..f953b9933b 100644 --- a/libraries/ESP8266WiFiMesh/src/JsonTranslator.cpp +++ b/libraries/ESP8266WiFiMesh/src/JsonTranslator.cpp @@ -25,7 +25,7 @@ #include "JsonTranslator.h" #include "EspnowProtocolInterpreter.h" #include "TypeConversionFunctions.h" -#include "CryptoInterface.h" +#include "MeshCryptoInterface.h" namespace JsonTranslator { @@ -69,14 +69,14 @@ namespace JsonTranslator uint8_t staMac[6] {0}; uint8_t apMac[6] {0}; String requesterStaApMac = macToString(WiFi.macAddress(staMac)) + macToString(WiFi.softAPmacAddress(apMac)); - String hmac = CryptoInterface::createBearsslHmac(requesterStaApMac + mainMessage, hashKey, hashKeyLength); + String hmac = MeshCryptoInterface::createMeshHmac(requesterStaApMac + mainMessage, hashKey, hashKeyLength); return mainMessage + createJsonEndPair(jsonHmac, hmac); } bool verifyEncryptionRequestHmac(const String &encryptionRequestHmacMessage, const uint8_t *requesterStaMac, const uint8_t *requesterApMac, const uint8_t *hashKey, uint8_t hashKeyLength) { - using namespace CryptoInterface; + using MeshCryptoInterface::verifyMeshHmac; String hmac = ""; if(getHmac(encryptionRequestHmacMessage, hmac)) @@ -85,8 +85,8 @@ namespace JsonTranslator if(hmacStartIndex < 0) return false; - if(hmac.length() == 2*SHA256HMAC_NATURAL_LENGTH // We know that each HMAC byte should become 2 String characters due to uint8ArrayToHexString. - && verifyBearsslHmac(macToString(requesterStaMac) + macToString(requesterApMac) + encryptionRequestHmacMessage.substring(0, hmacStartIndex), hmac, hashKey, hashKeyLength)) + 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)) { return true; } diff --git a/libraries/ESP8266WiFiMesh/src/MeshCryptoInterface.cpp b/libraries/ESP8266WiFiMesh/src/MeshCryptoInterface.cpp new file mode 100644 index 0000000000..607f6cbf01 --- /dev/null +++ b/libraries/ESP8266WiFiMesh/src/MeshCryptoInterface.cpp @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2019 Anders Löfgren + * + * License (MIT license): + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include "MeshCryptoInterface.h" + +namespace MeshCryptoInterface +{ + String createMeshHmac(const String &message, const void *hashKey, const size_t hashKeyLength, const size_t hmacLength) + { + return CryptoInterface::sha256Hmac(message, hashKey, hashKeyLength, hmacLength); + } + + bool verifyMeshHmac(const String &message, const String &messageHmac, const uint8_t *hashKey, uint8_t hashKeyLength) + { + String generatedHmac = createMeshHmac(message, hashKey, hashKeyLength, messageHmac.length()/2); // We know that each HMAC byte should become 2 String characters due to uint8ArrayToHexString. + if(generatedHmac == messageHmac) + return true; + else + return false; + } +} diff --git a/libraries/ESP8266WiFiMesh/src/MeshCryptoInterface.h b/libraries/ESP8266WiFiMesh/src/MeshCryptoInterface.h new file mode 100644 index 0000000000..25f90eb49c --- /dev/null +++ b/libraries/ESP8266WiFiMesh/src/MeshCryptoInterface.h @@ -0,0 +1,72 @@ +/* + * Copyright (C) 2019 Anders Löfgren + * + * License (MIT license): + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#ifndef __MESHCRYPTOINTERFACE_H__ +#define __MESHCRYPTOINTERFACE_H__ + +#include +#include "CryptoInterface.h" + +namespace MeshCryptoInterface +{ + /** + * There is a constant-time HMAC version available. More constant-time info here: https://www.bearssl.org/constanttime.html + * For small messages, it takes substantially longer time to complete than a normal HMAC (5 ms vs 2 ms in a quick benchmark, + * determined by the difference between min and max allowed message length), and it also sets a maximum length that messages can be (1024 bytes by default). + * Making the fixed max length variable would defeat the whole purpose of using constant-time, and not making it variable would create the wrong HMAC if message size exceeds the maximum. + * + * Also, HMAC is already partially constant-time. Quoting the link above: + * "Hash functions implemented by BearSSL (MD5, SHA-1, SHA-224, SHA-256, SHA-384 and SHA-512) consist in bitwise logical operations and additions on 32-bit or 64-bit words, + * naturally yielding constant-time operations. HMAC is naturally as constant-time as the underlying hash function. The size of the MACed data, and the size of the key, + * may leak, though; only the contents are protected." + * + * Thus the non constant-time version is used within the mesh framework instead. + */ + + /** + * Create a SHA256 HMAC from the message, using the provided hashKey. The result will be hmacLength bytes long and returned as a String in HEX format. + * + * @param message The string from which to create the HMAC. + * @param hashKey The hash key to use when creating the HMAC. + * @param hashKeyLength The length of the hash key in bytes. + * @param hmacLength The desired length of the generated HMAC, in bytes. Valid values are 1 to 32. Defaults to CryptoInterface::SHA256_NATURAL_LENGTH. + * + * @return A String with the generated HMAC in HEX format. + */ + String createMeshHmac(const String &message, const void *hashKey, const size_t hashKeyLength, const size_t hmacLength = CryptoInterface::SHA256_NATURAL_LENGTH); + + /** + * Verify a SHA256 HMAC which was created from the message using the provided hashKey. + * + * @param message The string from which the HMAC was created. + * @param messageHmac A string with the generated HMAC in HEX format. Valid messageHmac.length() is 2 to 64. + * @param hashKey The hash key to use when creating the HMAC. + * @param hashKeyLength The length of the hash key in bytes. + * + * @return True if the HMAC is correct. False otherwise. + */ + bool verifyMeshHmac(const String &message, const String &messageHmac, const uint8_t *hashKey, uint8_t hashKeyLength); +} + +#endif From 962a23d253b2e0d8eb2e4be6fbaa442e9902d32b Mon Sep 17 00:00:00 2001 From: Anders Date: Wed, 4 Dec 2019 02:30:16 +0100 Subject: [PATCH 13/30] - Make it possible to transfer Strings containing null values via ESP-NOW and FloodingMesh. - Add uint8ArrayToMultiString and bufferedUint8ArrayToMultiString TypeConversionFunctions to facilitate transfer of Strings containing null values. - Add HKDF to CryptoInterface. - Add ChaCha20 + Poly1305 AEAD to CryptoInterface. - Add customizable nonce generator to CryptoInterface. - Add ability to automatically encrypt/decrypt ESP-NOW messages via AEAD (ChaCha20 + Poly1305), independent from encrypted ESP-NOW connections. - Greatly improve performance of incrementSessionKey, espnowGetMessageID, espnowSetMessageID and all non-template TypeConversionFunctions. The average performance increase is roughly a factor 5. Fun fact: Printing a MAC to a HEX String is now over twice as fast when using TypeConversionFunctions compared to using standard functionality like sprintf. - Add uint64ToUint8Array and uint8ArrayToUint64 TypeConversionFunctions. - Make it possible to use String values as ESP-NOW and FloodingMesh key seeds, instead of just requiring plain key arrays. - Add customizable responseTransmittedHook to sendEspnowResponses. - Add _responsesToSendMutex to make the new responseTransmittedHook safe to use. - Remove verboseModePrinting from sendPeerRequestConfirmations method to reduce performance variations. - Fix faulty messageID generation in FloodingMesh. - Make assert checks more complete and easier to understand in the setMetadataDelimiter method of FloodingMesh. - Rename EspnowEncryptionKey to EspnowEncryptedConnectionKey since there are now multiple encryption keys. - Rename acceptsUnencryptedRequests to acceptsUnverifiedRequests, unencryptedMessageID to unsynchronizedMessageID, receivedEncryptedMessage to receivedEncryptedTransmission, since there are now multiple modes of encryption. - Rename resultArrayLength to outputLength in CryptoInterface and remove its value restrictions in order to match the BearSSL functionality. - Improve performance of FloodingMesh::encryptedBroadcast. - Rename FloodingMesh methods maxUnencryptedMessageSize/maxEncryptedMessageSize to maxUnencryptedMessageLength/maxEncryptedMessageLength, so that String length naming is consistent within the library. - Update examples to illustrate the new features. - Improve comments. --- .../examples/HelloEspnow/HelloEspnow.ino | 69 ++++- .../examples/HelloMesh/HelloMesh.ino | 18 +- .../examples/HelloTcpIp/HelloTcpIp.ino | 11 +- .../ESP8266WiFiMesh/src/CryptoInterface.cpp | 205 +++++++++++--- .../ESP8266WiFiMesh/src/CryptoInterface.h | 251 ++++++++++++++---- .../src/EncryptedConnectionData.cpp | 13 +- .../src/EncryptedConnectionData.h | 2 +- .../ESP8266WiFiMesh/src/EspnowMeshBackend.cpp | 234 ++++++++++++---- .../ESP8266WiFiMesh/src/EspnowMeshBackend.h | 230 +++++++++++++--- .../src/EspnowProtocolInterpreter.cpp | 43 ++- .../src/EspnowProtocolInterpreter.h | 10 +- .../ESP8266WiFiMesh/src/FloodingMesh.cpp | 100 +++++-- libraries/ESP8266WiFiMesh/src/FloodingMesh.h | 69 +++-- .../ESP8266WiFiMesh/src/JsonTranslator.cpp | 4 +- .../ESP8266WiFiMesh/src/JsonTranslator.h | 4 +- .../ESP8266WiFiMesh/src/MeshBackendBase.h | 2 +- .../src/MeshCryptoInterface.cpp | 10 + .../ESP8266WiFiMesh/src/MeshCryptoInterface.h | 11 + .../src/TypeConversionFunctions.cpp | 164 +++++++++--- .../src/TypeConversionFunctions.h | 68 ++++- 20 files changed, 1201 insertions(+), 317 deletions(-) diff --git a/libraries/ESP8266WiFiMesh/examples/HelloEspnow/HelloEspnow.ino b/libraries/ESP8266WiFiMesh/examples/HelloEspnow/HelloEspnow.ino index 3d51c09f88..8c8f2628bd 100644 --- a/libraries/ESP8266WiFiMesh/examples/HelloEspnow/HelloEspnow.ino +++ b/libraries/ESP8266WiFiMesh/examples/HelloEspnow/HelloEspnow.ino @@ -19,10 +19,11 @@ const char exampleWiFiPassword[] PROGMEM = "ChangeThisWiFiPassword_TODO"; // The // 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. -uint8_t espnowEncryptionKey[16] = {0x33, 0x44, 0x33, 0x44, 0x33, 0x44, 0x33, 0x44, // This is the key for encrypting transmissions. - 0x33, 0x44, 0x33, 0x44, 0x33, 0x44, 0x32, 0x11 - }; -uint8_t espnowEncryptionKok[16] = {0x22, 0x44, 0x33, 0x44, 0x33, 0x44, 0x33, 0x44, // This is the key for encrypting the encryption key. +// Note that it is also possible to use Strings as key seeds instead of arrays. +uint8_t espnowEncryptedConnectionKey[16] = {0x33, 0x44, 0x33, 0x44, 0x33, 0x44, 0x33, 0x44, // This is the key for encrypting transmissions of encrypted connections. + 0x33, 0x44, 0x33, 0x44, 0x33, 0x44, 0x32, 0x11 + }; +uint8_t espnowEncryptionKok[16] = {0x22, 0x44, 0x33, 0x44, 0x33, 0x44, 0x33, 0x44, // This is the key for encrypting the encrypted connection key. 0x33, 0x44, 0x33, 0x44, 0x33, 0x44, 0x32, 0x33 }; uint8_t espnowHashKey[16] = {0xEF, 0x44, 0x33, 0x0C, 0x33, 0x44, 0xFE, 0x44, // This is the secret key used for HMAC during encrypted connection requests. @@ -40,7 +41,7 @@ void networkFilter(int numberOfNetworks, MeshBackendBase &meshInstance); bool broadcastFilter(String &firstTransmission, EspnowMeshBackend &meshInstance); /* Create the mesh node object */ -EspnowMeshBackend espnowNode = EspnowMeshBackend(manageRequest, manageResponse, networkFilter, broadcastFilter, FPSTR(exampleWiFiPassword), espnowEncryptionKey, espnowHashKey, FPSTR(exampleMeshName), uint64ToString(ESP.getChipId()), true); +EspnowMeshBackend espnowNode = EspnowMeshBackend(manageRequest, manageResponse, networkFilter, broadcastFilter, FPSTR(exampleWiFiPassword), espnowEncryptedConnectionKey, espnowHashKey, FPSTR(exampleMeshName), uint64ToString(ESP.getChipId()), true); /** Callback for when other nodes send you a request @@ -57,8 +58,8 @@ String manageRequest(const String &request, MeshBackendBase &meshInstance) { // To get the actual class of the polymorphic meshInstance, do as follows (meshBackendCast replaces dynamic_cast since RTTI is disabled) if (EspnowMeshBackend *espnowInstance = meshBackendCast(&meshInstance)) { - String messageEncrypted = espnowInstance->receivedEncryptedMessage() ? ", Encrypted" : ", Unencrypted"; - Serial.print("ESP-NOW (" + espnowInstance->getSenderMac() + messageEncrypted + "): "); + String transmissionEncrypted = espnowInstance->receivedEncryptedTransmission() ? ", Encrypted transmission" : ", Unencrypted transmission"; + Serial.print("ESP-NOW (" + espnowInstance->getSenderMac() + transmissionEncrypted + "): "); } else if (TcpIpMeshBackend *tcpIpInstance = meshBackendCast(&meshInstance)) { (void)tcpIpInstance; // This is useful to remove a "unused parameter" compiler warning. Does nothing else. Serial.print("TCP/IP: "); @@ -69,6 +70,7 @@ String manageRequest(const String &request, MeshBackendBase &meshInstance) { /* Print out received message */ // Only show first 100 characters because printing a large String takes a lot of time, which is a bad thing for a callback function. // If you need to print the whole String it is better to store it and print it in the loop() later. + // Note that request.substring will not work as expected if the String contains null values as data. Serial.print("Request received: "); Serial.println(request.substring(0, 100)); @@ -88,8 +90,8 @@ transmission_status_t manageResponse(const String &response, MeshBackendBase &me // To get the actual class of the polymorphic meshInstance, do as follows (meshBackendCast replaces dynamic_cast since RTTI is disabled) if (EspnowMeshBackend *espnowInstance = meshBackendCast(&meshInstance)) { - String messageEncrypted = espnowInstance->receivedEncryptedMessage() ? ", Encrypted" : ", Unencrypted"; - Serial.print("ESP-NOW (" + espnowInstance->getSenderMac() + messageEncrypted + "): "); + String transmissionEncrypted = espnowInstance->receivedEncryptedTransmission() ? ", Encrypted transmission" : ", Unencrypted transmission"; + Serial.print("ESP-NOW (" + espnowInstance->getSenderMac() + transmissionEncrypted + "): "); } else if (TcpIpMeshBackend *tcpIpInstance = meshBackendCast(&meshInstance)) { Serial.print("TCP/IP: "); @@ -106,6 +108,7 @@ transmission_status_t manageResponse(const String &response, MeshBackendBase &me /* Print out received message */ // Only show first 100 characters because printing a large String takes a lot of time, which is a bad thing for a callback function. // If you need to print the whole String it is better to store it and print it in the loop() later. + // Note that response.substring will not work as expected if the String contains null values as data. Serial.print(F("Response received: ")); Serial.println(response.substring(0, 100)); @@ -169,7 +172,9 @@ bool broadcastFilter(String &firstTransmission, EspnowMeshBackend &meshInstance) return false; // Broadcast is for another mesh network } else { // Remove metadata from message and mark as accepted broadcast. - firstTransmission = firstTransmission.substring(metadataEndIndex + 1); + // Note that when you modify firstTransmission it is best to avoid using substring or other String methods that rely on null values for String length determination. + // Otherwise your broadcasts cannot include null values in the message bytes. + firstTransmission.remove(0, metadataEndIndex + 1); return true; } } @@ -193,6 +198,31 @@ bool exampleTransmissionOutcomesUpdateHook(MeshBackendBase &meshInstance) { return true; } +/** + Once passed to the setResponseTransmittedHook method of the ESP-NOW backend, + this function will be called after each successful ESP-NOW response transmission, just before the response is removed from the waiting list. + If a particular response is not sent, there will be no function call for it. + Only the hook of the EspnowMeshBackend instance that is getEspnowRequestManager() will be called. + + @param response The sent response. + @param recipientMac The MAC address the response was sent to. + @param responseIndex The index of the response in the waiting list. + @param meshInstance The EspnowMeshBackend instance that called the function. + + @return True if the response transmission process should continue with the next response in the waiting list. + False if the response transmission process should stop after removing the just sent response from the waiting list. +*/ +bool exampleResponseTransmittedHook(const String &response, const uint8_t *recipientMac, uint32_t responseIndex, EspnowMeshBackend &meshInstance) { + // Currently this is exactly the same as the default hook, but you can modify it to alter the behaviour of sendEspnowResponses. + + (void)response; // This is useful to remove a "unused parameter" compiler warning. Does nothing else. + (void)recipientMac; + (void)responseIndex; + (void)meshInstance; + + return true; +} + void setup() { // Prevents the flash memory from being worn out, see: https://github.com/esp8266/Arduino/issues/1054 . // This will however delay node WiFi start-up by about 700 ms. The delay is 900 ms if we otherwise would have stored the WiFi network we want to connect to. @@ -222,10 +252,10 @@ void setup() { // Note: This changes the Kok for all EspnowMeshBackend instances on this ESP8266. // Encrypted connections added before the Kok change will retain their old Kok. - // Both Kok and encryption key must match in an encrypted connection pair for encrypted communication to be possible. + // Both Kok and encrypted connection key must match in an encrypted connection pair for encrypted communication to be possible. // Otherwise the transmissions will never reach the recipient, even though acks are received by the sender. EspnowMeshBackend::setEspnowEncryptionKok(espnowEncryptionKok); - espnowNode.setEspnowEncryptionKey(espnowEncryptionKey); + espnowNode.setEspnowEncryptedConnectionKey(espnowEncryptedConnectionKey); // Makes it possible to find the node through scans, and also makes it possible to recover from an encrypted connection where only the other node is encrypted. // Note that only one AP can be active at a time in total, and this will always be the one which was last activated. @@ -238,6 +268,21 @@ void setup() { espnowNode.setMessage(String(F("Hello world request #")) + String(requestNumber) + String(F(" from ")) + espnowNode.getMeshName() + espnowNode.getNodeID() + String(F("."))); espnowNode.setTransmissionOutcomesUpdateHook(exampleTransmissionOutcomesUpdateHook); + espnowNode.setResponseTransmittedHook(exampleResponseTransmittedHook); + + // In addition to using encrypted ESP-NOW connections the framework can also send automatically encrypted messages (AEAD) over both encrypted and unencrypted connections. + // Using AEAD will only encrypt the message content, not the transmission metadata. + // The AEAD encryption does not require any pairing, and is thus faster for single messages than establishing a new encrypted connection before transfer. + // AEAD encryption also works with ESP-NOW broadcasts and supports an unlimited number of nodes, which is not true for encrypted connections. + // Encrypted ESP-NOW connections do however come with built in replay attack protection, which is not provided by the framework when using AEAD encryption, + // and allow EspnowProtocolInterpreter::aeadMetadataSize extra message bytes per transmission. + // Transmissions via encrypted connections are also slightly faster than via AEAD once a connection has been established. + // + // Uncomment the lines below to use automatic AEAD encryption/decryption of messages sent/received. + // All nodes this node wishes to communicate with must then also use encrypted messages with the same getEspnowMessageEncryptionKey(), or messages will not be accepted. + // Note that using AEAD encrypted messages will reduce the number of message bytes that can be transmitted. + //espnowNode.setEspnowMessageEncryptionKey("ChangeThisKeySeed_TODO"); // The message encryption key should always be set manually. Otherwise a default key (all zeroes) is used. + //espnowNode.setUseEncryptedMessages(true); } int32_t timeOfLastScan = -10000; diff --git a/libraries/ESP8266WiFiMesh/examples/HelloMesh/HelloMesh.ino b/libraries/ESP8266WiFiMesh/examples/HelloMesh/HelloMesh.ino index d9f138ca20..bfa63a5c6d 100644 --- a/libraries/ESP8266WiFiMesh/examples/HelloMesh/HelloMesh.ino +++ b/libraries/ESP8266WiFiMesh/examples/HelloMesh/HelloMesh.ino @@ -26,9 +26,10 @@ const char exampleWiFiPassword[] PROGMEM = "ChangeThisWiFiPassword_TODO"; // The // 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. -uint8_t espnowEncryptionKey[16] = {0x33, 0x44, 0x33, 0x44, 0x33, 0x44, 0x33, 0x44, // This is the key for encrypting transmissions. - 0x33, 0x44, 0x33, 0x44, 0x33, 0x44, 0x32, 0x11 - }; +// Note that it is also possible to use Strings as key seeds instead of arrays. +uint8_t espnowEncryptedConnectionKey[16] = {0x33, 0x44, 0x33, 0x44, 0x33, 0x44, 0x33, 0x44, // This is the key for encrypting transmissions of encrypted connections. + 0x33, 0x44, 0x33, 0x44, 0x33, 0x44, 0x32, 0x11 + }; uint8_t espnowHashKey[16] = {0xEF, 0x44, 0x33, 0x0C, 0x33, 0x44, 0xFE, 0x44, // This is the secret key used for HMAC during encrypted connection requests. 0x33, 0x44, 0x33, 0xB0, 0x33, 0x44, 0x32, 0xAD }; @@ -36,7 +37,7 @@ uint8_t espnowHashKey[16] = {0xEF, 0x44, 0x33, 0x0C, 0x33, 0x44, 0xFE, 0x44, // bool meshMessageHandler(String &message, FloodingMesh &meshInstance); /* Create the mesh node object */ -FloodingMesh floodingMesh = FloodingMesh(meshMessageHandler, FPSTR(exampleWiFiPassword), espnowEncryptionKey, espnowHashKey, FPSTR(exampleMeshName), uint64ToString(ESP.getChipId()), true); +FloodingMesh floodingMesh = FloodingMesh(meshMessageHandler, FPSTR(exampleWiFiPassword), espnowEncryptedConnectionKey, espnowHashKey, FPSTR(exampleMeshName), uint64ToString(ESP.getChipId()), true); bool theOne = true; String theOneMac = ""; @@ -145,6 +146,13 @@ void setup() { digitalWrite(LED_BUILTIN, LOW); // Turn LED on (LED_BUILTIN is active low) } + // Uncomment the lines below to use automatic AEAD encryption/decryption of messages sent/received via broadcast() and encryptedBroadcast(). + // The main benefit of AEAD encryption is that it can be used with normal broadcasts (which are substantially faster than encryptedBroadcasts). + // The main drawbacks are that AEAD only encrypts the message data (not transmission metadata), transfers less data per message and lacks replay attack protection. + // When using AEAD, potential replay attacks must thus be handled manually. + //floodingMesh.getEspnowMeshBackend().setEspnowMessageEncryptionKey("ChangeThisKeySeed_TODO"); // The message encryption key should always be set manually. Otherwise a default key (all zeroes) is used. + //floodingMesh.getEspnowMeshBackend().setUseEncryptedMessages(true); + floodingMeshDelay(5000); // Give some time for user to start the nodes } @@ -171,7 +179,7 @@ void loop() { uint32_t startTime = millis(); 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.maxUnencryptedMessageSize(). 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."); Serial.println("Proclamation broadcast done in " + String(millis() - startTime) + " ms."); diff --git a/libraries/ESP8266WiFiMesh/examples/HelloTcpIp/HelloTcpIp.ino b/libraries/ESP8266WiFiMesh/examples/HelloTcpIp/HelloTcpIp.ino index 4b1547cb17..04d26c2ef6 100644 --- a/libraries/ESP8266WiFiMesh/examples/HelloTcpIp/HelloTcpIp.ino +++ b/libraries/ESP8266WiFiMesh/examples/HelloTcpIp/HelloTcpIp.ino @@ -42,8 +42,8 @@ String manageRequest(const String &request, MeshBackendBase &meshInstance) { // To get the actual class of the polymorphic meshInstance, do as follows (meshBackendCast replaces dynamic_cast since RTTI is disabled) if (EspnowMeshBackend *espnowInstance = meshBackendCast(&meshInstance)) { - String messageEncrypted = espnowInstance->receivedEncryptedMessage() ? ", Encrypted" : ", Unencrypted"; - Serial.print("ESP-NOW (" + espnowInstance->getSenderMac() + messageEncrypted + "): "); + String transmissionEncrypted = espnowInstance->receivedEncryptedTransmission() ? ", Encrypted transmission" : ", Unencrypted transmission"; + Serial.print("ESP-NOW (" + espnowInstance->getSenderMac() + transmissionEncrypted + "): "); } else if (TcpIpMeshBackend *tcpIpInstance = meshBackendCast(&meshInstance)) { (void)tcpIpInstance; // This is useful to remove a "unused parameter" compiler warning. Does nothing else. Serial.print("TCP/IP: "); @@ -54,6 +54,7 @@ String manageRequest(const String &request, MeshBackendBase &meshInstance) { /* Print out received message */ // Only show first 100 characters because printing a large String takes a lot of time, which is a bad thing for a callback function. // If you need to print the whole String it is better to store it and print it in the loop() later. + // Note that request.substring will not work as expected if the String contains null values as data. Serial.print("Request received: "); Serial.println(request.substring(0, 100)); @@ -73,8 +74,8 @@ transmission_status_t manageResponse(const String &response, MeshBackendBase &me // To get the actual class of the polymorphic meshInstance, do as follows (meshBackendCast replaces dynamic_cast since RTTI is disabled) if (EspnowMeshBackend *espnowInstance = meshBackendCast(&meshInstance)) { - String messageEncrypted = espnowInstance->receivedEncryptedMessage() ? ", Encrypted" : ", Unencrypted"; - Serial.print("ESP-NOW (" + espnowInstance->getSenderMac() + messageEncrypted + "): "); + String transmissionEncrypted = espnowInstance->receivedEncryptedTransmission() ? ", Encrypted transmission" : ", Unencrypted transmission"; + Serial.print("ESP-NOW (" + espnowInstance->getSenderMac() + transmissionEncrypted + "): "); } else if (TcpIpMeshBackend *tcpIpInstance = meshBackendCast(&meshInstance)) { Serial.print("TCP/IP: "); @@ -84,7 +85,6 @@ transmission_status_t manageResponse(const String &response, MeshBackendBase &me // So for ESP-NOW, adding unique identifiers in the response and request is required to associate a response with a request. Serial.print(F("Request sent: ")); Serial.println(tcpIpInstance->getCurrentMessage().substring(0, 100)); - } else { Serial.print("UNKNOWN!: "); } @@ -92,6 +92,7 @@ transmission_status_t manageResponse(const String &response, MeshBackendBase &me /* Print out received message */ // Only show first 100 characters because printing a large String takes a lot of time, which is a bad thing for a callback function. // If you need to print the whole String it is better to store it and print it in the loop() later. + // Note that response.substring will not work as expected if the String contains null values as data. Serial.print(F("Response received: ")); Serial.println(response.substring(0, 100)); diff --git a/libraries/ESP8266WiFiMesh/src/CryptoInterface.cpp b/libraries/ESP8266WiFiMesh/src/CryptoInterface.cpp index 9499bf6565..786dade93f 100644 --- a/libraries/ESP8266WiFiMesh/src/CryptoInterface.cpp +++ b/libraries/ESP8266WiFiMesh/src/CryptoInterface.cpp @@ -34,11 +34,64 @@ namespace size_t _ctMaxDataLength = 1024; bool _warningsEnabled = true; - - void *createBearsslHmac(const br_hash_class *hashType, const void *data, const size_t dataLength, const void *hashKey, const size_t hashKeyLength, void *resultArray, const size_t resultArrayLength) - { - assert(1 <= resultArrayLength); + + br_hkdf_context _storedHkdfContext; + bool _hkdfContextStored = false; + + uint8_t *defaultNonceGenerator(uint8_t *nonceArray, const size_t nonceLength) + { + /** + * The ESP32 Technical Reference Manual v4.1 chapter 24 has the following to say about random number generation (no information found for ESP8266): + * + * "When used correctly, every 32-bit value the system reads from the RNG_DATA_REG register of the random number generator is a true random number. + * These true random numbers are generated based on the noise in the Wi-Fi/BT RF system. + * When Wi-Fi and BT are disabled, the random number generator will give out pseudo-random numbers. + * + * When Wi-Fi or BT is enabled, the random number generator is fed two bits of entropy every APB clock cycle (normally 80 MHz). + * Thus, for the maximum amount of entropy, it is advisable to read the random register at a maximum rate of 5 MHz. + * A data sample of 2 GB, read from the random number generator with Wi-Fi enabled and the random register read at 5 MHz, + * has been tested using the Dieharder Random Number Testsuite (version 3.31.1). + * The sample passed all tests." + * + * Since ESP32 is the sequal to ESP8266 it is unlikely that the ESP8266 is able to generate random numbers more quickly than 5 MHz when run at a 80 MHz frequency. + * A maximum random number frequency of 0.5 MHz is used here to leave some margin for possibly inferior components in the ESP8266. + * It should be noted that the ESP8266 has no Bluetooth functionality, so turning the WiFi off is likely to cause RANDOM_REG32 to use pseudo-random numbers. + * + * It is possible that yield() must be called on the ESP8266 to properly feed the hardware random number generator new bits, since there is only one processor core available. + * However, no feeding requirements are mentioned in the ESP32 documentation, and using yield() could possibly cause extended delays during nonce generation. + * Thus only delayMicroseconds() is used below. + */ + + constexpr uint8_t cooldownMicros = 2; + static uint32_t lastCalledMicros = micros() - cooldownMicros; + + uint32_t randomNumber = 0; + for(size_t byteIndex = 0; byteIndex < nonceLength; ++byteIndex) + { + if(byteIndex % 4 == 0) + { + // Old random number has been used up (random number could be exactly 0, so we can't check for that) + + uint32_t timeSinceLastCall = micros() - lastCalledMicros; + if(timeSinceLastCall < cooldownMicros) + delayMicroseconds(cooldownMicros - timeSinceLastCall); + + randomNumber = RANDOM_REG32; + lastCalledMicros = micros(); + } + + nonceArray[byteIndex] = randomNumber; + randomNumber >>= 8; + } + + return nonceArray; + } + + CryptoInterface::nonceGeneratorType _nonceGenerator = defaultNonceGenerator; + + void *createBearsslHmac(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) + { // Comments mainly from https://www.bearssl.org/apidoc/bearssl__hmac_8h.html // HMAC is initialized with a key and an underlying hash function; it then fills a "key context". That context contains the processed key. @@ -54,8 +107,8 @@ namespace // Initialise a HMAC context with a key context. The key context is unmodified. // Relevant data from the key context is immediately copied; the key context can thus be independently reused, modified or released without impacting this HMAC computation. // An explicit output length can be specified; the actual output length will be the minimum of that value and the natural HMAC output length. - // If resultArrayLength is 0, then the natural HMAC output length is selected. The "natural output length" is the output length of the underlying hash function. - br_hmac_init(&hmacContext, &keyContext, resultArrayLength); + // If outputLength is 0, then the natural HMAC output length is selected. The "natural output length" is the output length of the underlying hash function. + br_hmac_init(&hmacContext, &keyContext, outputLength); // Provide the HMAC context with the data to create a HMAC from. // The provided dataLength bytes are injected as extra input in the HMAC computation incarnated by the hmacContext. @@ -80,9 +133,8 @@ namespace return 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 resultArrayLength) + 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) { - assert(1 <= resultArrayLength); assert(_ctMinDataLength <= dataLength && dataLength <= _ctMaxDataLength); // Comments mainly from https://www.bearssl.org/apidoc/bearssl__hmac_8h.html @@ -100,8 +152,8 @@ namespace // Initialise a HMAC context with a key context. The key context is unmodified. // Relevant data from the key context is immediately copied; the key context can thus be independently reused, modified or released without impacting this HMAC computation. // An explicit output length can be specified; the actual output length will be the minimum of that value and the natural HMAC output length. - // If resultArrayLength is 0, then the natural HMAC output length is selected. The "natural output length" is the output length of the underlying hash function. - br_hmac_init(&hmacContext, &keyContext, resultArrayLength); + // If outputLength is 0, then the natural HMAC output length is selected. The "natural output length" is the output length of the underlying hash function. + br_hmac_init(&hmacContext, &keyContext, outputLength); // Provide the HMAC context with the data to create a HMAC from. // The provided dataLength bytes are injected as extra input in the HMAC computation incarnated by the hmacContext. @@ -153,6 +205,9 @@ namespace CryptoInterface void setWarningsEnabled(bool warningsEnabled) { _warningsEnabled = warningsEnabled; } bool warningsEnabled() { return _warningsEnabled; } + void setNonceGenerator(nonceGeneratorType nonceGenerator) { _nonceGenerator = nonceGenerator; } + nonceGeneratorType getNonceGenerator() { return _nonceGenerator; } + // #################### MD5 #################### @@ -178,9 +233,9 @@ namespace CryptoInterface return 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 resultArrayLength) + void *md5Hmac(const void *data, const size_t dataLength, const void *hashKey, const size_t hashKeyLength, void *resultArray, const size_t outputLength) { - return createBearsslHmac(&br_md5_vtable, data, dataLength, hashKey, hashKeyLength, resultArray, resultArrayLength); + return createBearsslHmac(&br_md5_vtable, data, dataLength, hashKey, hashKeyLength, resultArray, outputLength); } String md5Hmac(const String &message, const void *hashKey, const size_t hashKeyLength, const size_t hmacLength) @@ -188,9 +243,9 @@ namespace CryptoInterface return createBearsslHmac(&br_md5_vtable, MD5_NATURAL_LENGTH, message, hashKey, hashKeyLength, hmacLength); } - void *md5HmacCT(const void *data, const size_t dataLength, const void *hashKey, const size_t hashKeyLength, void *resultArray, const size_t resultArrayLength) + void *md5HmacCT(const void *data, const size_t dataLength, const void *hashKey, const size_t hashKeyLength, void *resultArray, const size_t outputLength) { - return createBearsslHmacCT(&br_md5_vtable, data, dataLength, hashKey, hashKeyLength, resultArray, resultArrayLength); + return createBearsslHmacCT(&br_md5_vtable, data, dataLength, hashKey, hashKeyLength, resultArray, outputLength); } String md5HmacCT(const String &message, const void *hashKey, const size_t hashKeyLength, const size_t hmacLength) @@ -223,9 +278,9 @@ namespace CryptoInterface return 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 resultArrayLength) + void *sha1Hmac(const void *data, const size_t dataLength, const void *hashKey, const size_t hashKeyLength, void *resultArray, const size_t outputLength) { - return createBearsslHmac(&br_sha1_vtable, data, dataLength, hashKey, hashKeyLength, resultArray, resultArrayLength); + return createBearsslHmac(&br_sha1_vtable, data, dataLength, hashKey, hashKeyLength, resultArray, outputLength); } String sha1Hmac(const String &message, const void *hashKey, const size_t hashKeyLength, const size_t hmacLength) @@ -233,9 +288,9 @@ namespace CryptoInterface return createBearsslHmac(&br_sha1_vtable, SHA1_NATURAL_LENGTH, message, hashKey, hashKeyLength, hmacLength); } - void *sha1HmacCT(const void *data, const size_t dataLength, const void *hashKey, const size_t hashKeyLength, void *resultArray, const size_t resultArrayLength) + void *sha1HmacCT(const void *data, const size_t dataLength, const void *hashKey, const size_t hashKeyLength, void *resultArray, const size_t outputLength) { - return createBearsslHmacCT(&br_sha1_vtable, data, dataLength, hashKey, hashKeyLength, resultArray, resultArrayLength); + return createBearsslHmacCT(&br_sha1_vtable, data, dataLength, hashKey, hashKeyLength, resultArray, outputLength); } String sha1HmacCT(const String &message, const void *hashKey, const size_t hashKeyLength, const size_t hmacLength) @@ -263,9 +318,9 @@ namespace CryptoInterface return 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 resultArrayLength) + void *sha224Hmac(const void *data, const size_t dataLength, const void *hashKey, const size_t hashKeyLength, void *resultArray, const size_t outputLength) { - return createBearsslHmac(&br_sha224_vtable, data, dataLength, hashKey, hashKeyLength, resultArray, resultArrayLength); + return createBearsslHmac(&br_sha224_vtable, data, dataLength, hashKey, hashKeyLength, resultArray, outputLength); } String sha224Hmac(const String &message, const void *hashKey, const size_t hashKeyLength, const size_t hmacLength) @@ -273,9 +328,9 @@ namespace CryptoInterface return createBearsslHmac(&br_sha224_vtable, SHA224_NATURAL_LENGTH, message, hashKey, hashKeyLength, hmacLength); } - void *sha224HmacCT(const void *data, const size_t dataLength, const void *hashKey, const size_t hashKeyLength, void *resultArray, const size_t resultArrayLength) + void *sha224HmacCT(const void *data, const size_t dataLength, const void *hashKey, const size_t hashKeyLength, void *resultArray, const size_t outputLength) { - return createBearsslHmacCT(&br_sha224_vtable, data, dataLength, hashKey, hashKeyLength, resultArray, resultArrayLength); + return createBearsslHmacCT(&br_sha224_vtable, data, dataLength, hashKey, hashKeyLength, resultArray, outputLength); } String sha224HmacCT(const String &message, const void *hashKey, const size_t hashKeyLength, const size_t hmacLength) @@ -303,9 +358,9 @@ namespace CryptoInterface return 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 resultArrayLength) + void *sha256Hmac(const void *data, const size_t dataLength, const void *hashKey, const size_t hashKeyLength, void *resultArray, const size_t outputLength) { - return createBearsslHmac(&br_sha256_vtable, data, dataLength, hashKey, hashKeyLength, resultArray, resultArrayLength); + return createBearsslHmac(&br_sha256_vtable, data, dataLength, hashKey, hashKeyLength, resultArray, outputLength); } String sha256Hmac(const String &message, const void *hashKey, const size_t hashKeyLength, const size_t hmacLength) @@ -313,9 +368,9 @@ namespace CryptoInterface return createBearsslHmac(&br_sha256_vtable, SHA256_NATURAL_LENGTH, message, hashKey, hashKeyLength, hmacLength); } - void *sha256HmacCT(const void *data, const size_t dataLength, const void *hashKey, const size_t hashKeyLength, void *resultArray, const size_t resultArrayLength) + void *sha256HmacCT(const void *data, const size_t dataLength, const void *hashKey, const size_t hashKeyLength, void *resultArray, const size_t outputLength) { - return createBearsslHmacCT(&br_sha256_vtable, data, dataLength, hashKey, hashKeyLength, resultArray, resultArrayLength); + return createBearsslHmacCT(&br_sha256_vtable, data, dataLength, hashKey, hashKeyLength, resultArray, outputLength); } String sha256HmacCT(const String &message, const void *hashKey, const size_t hashKeyLength, const size_t hmacLength) @@ -343,9 +398,9 @@ namespace CryptoInterface return 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 resultArrayLength) + void *sha384Hmac(const void *data, const size_t dataLength, const void *hashKey, const size_t hashKeyLength, void *resultArray, const size_t outputLength) { - return createBearsslHmac(&br_sha384_vtable, data, dataLength, hashKey, hashKeyLength, resultArray, resultArrayLength); + return createBearsslHmac(&br_sha384_vtable, data, dataLength, hashKey, hashKeyLength, resultArray, outputLength); } String sha384Hmac(const String &message, const void *hashKey, const size_t hashKeyLength, const size_t hmacLength) @@ -353,9 +408,9 @@ namespace CryptoInterface return createBearsslHmac(&br_sha384_vtable, SHA384_NATURAL_LENGTH, message, hashKey, hashKeyLength, hmacLength); } - void *sha384HmacCT(const void *data, const size_t dataLength, const void *hashKey, const size_t hashKeyLength, void *resultArray, const size_t resultArrayLength) + void *sha384HmacCT(const void *data, const size_t dataLength, const void *hashKey, const size_t hashKeyLength, void *resultArray, const size_t outputLength) { - return createBearsslHmacCT(&br_sha384_vtable, data, dataLength, hashKey, hashKeyLength, resultArray, resultArrayLength); + return createBearsslHmacCT(&br_sha384_vtable, data, dataLength, hashKey, hashKeyLength, resultArray, outputLength); } String sha384HmacCT(const String &message, const void *hashKey, const size_t hashKeyLength, const size_t hmacLength) @@ -383,9 +438,9 @@ namespace CryptoInterface return 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 resultArrayLength) + void *sha512Hmac(const void *data, const size_t dataLength, const void *hashKey, const size_t hashKeyLength, void *resultArray, const size_t outputLength) { - return createBearsslHmac(&br_sha512_vtable, data, dataLength, hashKey, hashKeyLength, resultArray, resultArrayLength); + return createBearsslHmac(&br_sha512_vtable, data, dataLength, hashKey, hashKeyLength, resultArray, outputLength); } String sha512Hmac(const String &message, const void *hashKey, const size_t hashKeyLength, const size_t hmacLength) @@ -393,9 +448,9 @@ namespace CryptoInterface return createBearsslHmac(&br_sha512_vtable, SHA512_NATURAL_LENGTH, message, hashKey, hashKeyLength, hmacLength); } - void *sha512HmacCT(const void *data, const size_t dataLength, const void *hashKey, const size_t hashKeyLength, void *resultArray, const size_t resultArrayLength) + void *sha512HmacCT(const void *data, const size_t dataLength, const void *hashKey, const size_t hashKeyLength, void *resultArray, const size_t outputLength) { - return createBearsslHmacCT(&br_sha512_vtable, data, dataLength, hashKey, hashKeyLength, resultArray, resultArrayLength); + return createBearsslHmacCT(&br_sha512_vtable, data, dataLength, hashKey, hashKeyLength, resultArray, outputLength); } String sha512HmacCT(const String &message, const void *hashKey, const size_t hashKeyLength, const size_t hmacLength) @@ -423,4 +478,86 @@ namespace CryptoInterface return uint8ArrayToHexString(hash, MD5SHA1_NATURAL_LENGTH); } + + // #################### HKDF #################### + + void hkdfInit(const void *keyMaterial, const size_t keyMaterialLength, const void *salt, const size_t saltLength) + { + // Comments mainly from https://www.bearssl.org/apidoc/bearssl__kdf_8h.html + + br_hkdf_context context; + + // Initialize an HKDF context, with a hash function, and the salt. This starts the HKDF-Extract process. + br_hkdf_init(&context, &br_sha256_vtable, salt, saltLength); + + // Inject more input bytes. This function may be called repeatedly if the input data is provided by chunks, after br_hkdf_init() but before br_hkdf_flip(). + br_hkdf_inject(&context, keyMaterial, keyMaterialLength); + + // End the HKDF-Extract process, and start the HKDF-Expand process. + br_hkdf_flip(&context); + + _storedHkdfContext = context; + _hkdfContextStored = true; + } + + size_t hkdfProduce(void *resultArray, const size_t outputLength, const void *info, const size_t infoLength) + { + // Comments mainly from https://www.bearssl.org/apidoc/bearssl__kdf_8h.html + + if(!_hkdfContextStored) // hkdfInit has not yet been executed + return 0; + + // HKDF output production (HKDF-Expand). + // Produces more output bytes from the current state. This function may be called several times, but only after br_hkdf_flip(). + // Returned value is the number of actually produced bytes. The total output length is limited to 255 times the output length of the underlying hash function. + return br_hkdf_produce(&_storedHkdfContext, info, infoLength, resultArray, outputLength); + } + + + // #################### Authenticated Encryption with Associated Data (AEAD) #################### + + + // #################### ChaCha20+Poly1305 AEAD #################### + + void chacha20Poly1305Kernel(const int encrypt, void *data, const size_t dataLength, const void *key, const void *keySalt, const size_t keySaltLength, + const void *nonce, void *tag, const void *aad, const size_t aadLength) + { + if(keySalt == nullptr) + { + br_poly1305_ctmul32_run(key, nonce, data, dataLength, aad, aadLength, tag, br_chacha20_ct_run, encrypt); + } + else + { + hkdfInit(key, ENCRYPTION_KEY_LENGTH, keySalt, keySaltLength); + uint8_t derivedEncryptionKey[ENCRYPTION_KEY_LENGTH] {0}; + hkdfProduce(derivedEncryptionKey, ENCRYPTION_KEY_LENGTH); + br_poly1305_ctmul32_run(derivedEncryptionKey, nonce, data, dataLength, aad, aadLength, tag, br_chacha20_ct_run, encrypt); + } + } + + void chacha20Poly1305Encrypt(void *data, const size_t dataLength, const void *key, const void *keySalt, const size_t keySaltLength, + void *resultingNonce, void *resultingTag, const void *aad, const size_t aadLength) + { + uint8_t *nonce = (uint8_t *)resultingNonce; + getNonceGenerator()(nonce, 12); + + chacha20Poly1305Kernel(1, data, dataLength, key, keySalt, keySaltLength, nonce, resultingTag, aad, aadLength); + } + + bool chacha20Poly1305Decrypt(void *data, const size_t dataLength, const void *key, const void *keySalt, const size_t keySaltLength, + const void *encryptionNonce, const void *encryptionTag, const void *aad, const size_t aadLength) + { + const uint8_t *oldTag = (const uint8_t *)encryptionTag; + uint8_t newTag[16] {0}; + + chacha20Poly1305Kernel(0, data, dataLength, key, keySalt, keySaltLength, encryptionNonce, newTag, aad, aadLength); + + for(uint32_t i = 0; i < sizeof newTag; ++i) + { + if(newTag[i] != oldTag[i]) + return false; + } + + return true; + } } diff --git a/libraries/ESP8266WiFiMesh/src/CryptoInterface.h b/libraries/ESP8266WiFiMesh/src/CryptoInterface.h index 2b87c44fd1..90ee9b90ec 100644 --- a/libraries/ESP8266WiFiMesh/src/CryptoInterface.h +++ b/libraries/ESP8266WiFiMesh/src/CryptoInterface.h @@ -49,6 +49,12 @@ namespace CryptoInterface * Using data that exceeds the fixed data length limits will create the wrong HMAC. */ + + /** + * The nonce generator should take an uint8_t array with a given size in bytes and fill it with the nonce. + * The uint8_t array should then be returned by the nonce generator. + */ + using nonceGeneratorType = std::function; constexpr uint8_t MD5_NATURAL_LENGTH = 16; constexpr uint8_t SHA1_NATURAL_LENGTH = 20; @@ -63,6 +69,8 @@ namespace CryptoInterface */ constexpr uint8_t MD5SHA1_NATURAL_LENGTH = 36; + constexpr uint8_t ENCRYPTION_KEY_LENGTH = 32; + constexpr uint32_t ctMaxDiff = 1073741823; // 2^30 - 1 /** @@ -98,6 +106,14 @@ namespace CryptoInterface */ void setWarningsEnabled(bool warningsEnabled); bool warningsEnabled(); + + /** + * Set the nonce generator used by the CryptoInterface functions. + * + * @param nonceGenerator The nonce generator to use. + */ + void setNonceGenerator(nonceGeneratorType nonceGenerator); + nonceGeneratorType getNonceGenerator(); // #################### MD5 #################### @@ -125,7 +141,7 @@ namespace CryptoInterface String md5Hash(const String &message); /** - * Create a MD5 HMAC from the data, using the provided hashKey. The result will be resultArrayLength bytes long and stored in resultArray. + * Create a MD5 HMAC from the data, using the provided hashKey. The result will be up to outputLength bytes long and stored in resultArray. * Uses the BearSSL cryptographic library. * * @param data The data array from which to create the HMAC. @@ -133,12 +149,13 @@ namespace CryptoInterface * @param hashKey The hash key to use when creating the HMAC. * @param hashKeyLength The length of the hash key in bytes. * @param resultArray The array wherein to store the resulting HMAC. - * @param resultArrayLength The length of resultArray in bytes. Determines the HMAC length. If resultArrayLength is greater than MD5_NATURAL_LENGTH, - * the first (lowest index) MD5_NATURAL_LENGTH bytes of resultArray will be used for the HMAC. + * @param outputLength The desired length of the generated HMAC, in bytes. Must fit within resultArray. If outputLength is greater than MD5_NATURAL_LENGTH, + * the first (lowest index) MD5_NATURAL_LENGTH bytes of resultArray will be used for the HMAC. + * If outputLength is 0, then the natural HMAC output length is selected. * * @return A pointer to resultArray. */ - void *md5Hmac(const void *data, const size_t dataLength, const void *hashKey, const size_t hashKeyLength, void *resultArray, const size_t resultArrayLength); + void *md5Hmac(const void *data, const size_t dataLength, const void *hashKey, const size_t hashKeyLength, void *resultArray, const size_t outputLength); /** * Create a MD5 HMAC from the message, using the provided hashKey. The result will be hmacLength bytes long and returned as a String in HEX format. @@ -154,7 +171,7 @@ namespace CryptoInterface String md5Hmac(const String &message, const void *hashKey, const size_t hashKeyLength, const size_t hmacLength); /** - * Create a MD5 HMAC from the data, using the provided hashKey. The result will be resultArrayLength bytes long and stored in resultArray. + * Create a MD5 HMAC from the data, using the provided hashKey. The result will be up to outputLength bytes long and stored in resultArray. * Constant-time version. * Uses the BearSSL cryptographic library. * @@ -163,12 +180,13 @@ namespace CryptoInterface * @param hashKey The hash key to use when creating the HMAC. * @param hashKeyLength The length of the hash key in bytes. * @param resultArray The array wherein to store the resulting HMAC. - * @param resultArrayLength The length of resultArray in bytes. Determines the HMAC length. If resultArrayLength is greater than MD5_NATURAL_LENGTH, - * the first (lowest index) MD5_NATURAL_LENGTH bytes of resultArray will be used for the HMAC. + * @param outputLength The desired length of the generated HMAC, in bytes. Must fit within resultArray. If outputLength is greater than MD5_NATURAL_LENGTH, + * the first (lowest index) MD5_NATURAL_LENGTH bytes of resultArray will be used for the HMAC. + * If outputLength is 0, then the natural HMAC output length is selected. * * @return A pointer to resultArray. */ - void *md5HmacCT(const void *data, const size_t dataLength, const void *hashKey, const size_t hashKeyLength, void *resultArray, const size_t resultArrayLength); + void *md5HmacCT(const void *data, const size_t dataLength, const void *hashKey, const size_t hashKeyLength, void *resultArray, const size_t outputLength); /** * Create a MD5 HMAC from the message, using the provided hashKey. The result will be hmacLength bytes long and returned as a String in HEX format. @@ -210,7 +228,7 @@ namespace CryptoInterface String sha1Hash(const String &message); /** - * Create a SHA1 HMAC from the data, using the provided hashKey. The result will be resultArrayLength bytes long and stored in resultArray. + * Create a SHA1 HMAC from the data, using the provided hashKey. The result will be up to outputLength bytes long and stored in resultArray. * Uses the BearSSL cryptographic library. * * @param data The data array from which to create the HMAC. @@ -218,12 +236,13 @@ namespace CryptoInterface * @param hashKey The hash key to use when creating the HMAC. * @param hashKeyLength The length of the hash key in bytes. * @param resultArray The array wherein to store the resulting HMAC. - * @param resultArrayLength The length of resultArray in bytes. Determines the HMAC length. If resultArrayLength is greater than SHA1_NATURAL_LENGTH, - * the first (lowest index) SHA1_NATURAL_LENGTH bytes of resultArray will be used for the HMAC. + * @param outputLength The desired length of the generated HMAC, in bytes. Must fit within resultArray. If outputLength is greater than SHA1_NATURAL_LENGTH, + * the first (lowest index) SHA1_NATURAL_LENGTH bytes of resultArray will be used for the HMAC. + * If outputLength is 0, then the natural HMAC output length is selected. * * @return A pointer to resultArray. */ - void *sha1Hmac(const void *data, const size_t dataLength, const void *hashKey, const size_t hashKeyLength, void *resultArray, const size_t resultArrayLength); + void *sha1Hmac(const void *data, const size_t dataLength, const void *hashKey, const size_t hashKeyLength, void *resultArray, const size_t outputLength); /** * Create a SHA1 HMAC from the message, using the provided hashKey. The result will be hmacLength bytes long and returned as a String in HEX format. @@ -239,7 +258,7 @@ namespace CryptoInterface String sha1Hmac(const String &message, const void *hashKey, const size_t hashKeyLength, const size_t hmacLength); /** - * Create a SHA1 HMAC from the data, using the provided hashKey. The result will be resultArrayLength bytes long and stored in resultArray. + * Create a SHA1 HMAC from the data, using the provided hashKey. The result will be up to outputLength bytes long and stored in resultArray. * Constant-time version. * Uses the BearSSL cryptographic library. * @@ -248,12 +267,13 @@ namespace CryptoInterface * @param hashKey The hash key to use when creating the HMAC. * @param hashKeyLength The length of the hash key in bytes. * @param resultArray The array wherein to store the resulting HMAC. - * @param resultArrayLength The length of resultArray in bytes. Determines the HMAC length. If resultArrayLength is greater than SHA1_NATURAL_LENGTH, - * the first (lowest index) SHA1_NATURAL_LENGTH bytes of resultArray will be used for the HMAC. + * @param outputLength The desired length of the generated HMAC, in bytes. Must fit within resultArray. If outputLength is greater than SHA1_NATURAL_LENGTH, + * the first (lowest index) SHA1_NATURAL_LENGTH bytes of resultArray will be used for the HMAC. + * If outputLength is 0, then the natural HMAC output length is selected. * * @return A pointer to resultArray. */ - void *sha1HmacCT(const void *data, const size_t dataLength, const void *hashKey, const size_t hashKeyLength, void *resultArray, const size_t resultArrayLength); + void *sha1HmacCT(const void *data, const size_t dataLength, const void *hashKey, const size_t hashKeyLength, void *resultArray, const size_t outputLength); /** * Create a SHA1 HMAC from the message, using the provided hashKey. The result will be hmacLength bytes long and returned as a String in HEX format. @@ -295,7 +315,7 @@ namespace CryptoInterface String sha224Hash(const String &message); /** - * Create a SHA224 HMAC from the data, using the provided hashKey. The result will be resultArrayLength bytes long and stored in resultArray. + * Create a SHA224 HMAC from the data, using the provided hashKey. The result will be up to outputLength bytes long and stored in resultArray. * Uses the BearSSL cryptographic library. * * @param data The data array from which to create the HMAC. @@ -303,12 +323,13 @@ namespace CryptoInterface * @param hashKey The hash key to use when creating the HMAC. * @param hashKeyLength The length of the hash key in bytes. * @param resultArray The array wherein to store the resulting HMAC. - * @param resultArrayLength The length of resultArray in bytes. Determines the HMAC length. If resultArrayLength is greater than SHA224_NATURAL_LENGTH, - * the first (lowest index) SHA224_NATURAL_LENGTH bytes of resultArray will be used for the HMAC. + * @param outputLength The desired length of the generated HMAC, in bytes. Must fit within resultArray. If outputLength is greater than SHA224_NATURAL_LENGTH, + * the first (lowest index) SHA224_NATURAL_LENGTH bytes of resultArray will be used for the HMAC. + * If outputLength is 0, then the natural HMAC output length is selected. * * @return A pointer to resultArray. */ - void *sha224Hmac(const void *data, const size_t dataLength, const void *hashKey, const size_t hashKeyLength, void *resultArray, const size_t resultArrayLength); + void *sha224Hmac(const void *data, const size_t dataLength, const void *hashKey, const size_t hashKeyLength, void *resultArray, const size_t outputLength); /** * Create a SHA224 HMAC from the message, using the provided hashKey. The result will be hmacLength bytes long and returned as a String in HEX format. @@ -324,7 +345,7 @@ namespace CryptoInterface String sha224Hmac(const String &message, const void *hashKey, const size_t hashKeyLength, const size_t hmacLength); /** - * Create a SHA224 HMAC from the data, using the provided hashKey. The result will be resultArrayLength bytes long and stored in resultArray. + * Create a SHA224 HMAC from the data, using the provided hashKey. The result will be up to outputLength bytes long and stored in resultArray. * Constant-time version. * Uses the BearSSL cryptographic library. * @@ -333,12 +354,13 @@ namespace CryptoInterface * @param hashKey The hash key to use when creating the HMAC. * @param hashKeyLength The length of the hash key in bytes. * @param resultArray The array wherein to store the resulting HMAC. - * @param resultArrayLength The length of resultArray in bytes. Determines the HMAC length. If resultArrayLength is greater than SHA224_NATURAL_LENGTH, - * the first (lowest index) SHA224_NATURAL_LENGTH bytes of resultArray will be used for the HMAC. + * @param outputLength The desired length of the generated HMAC, in bytes. Must fit within resultArray. If outputLength is greater than SHA224_NATURAL_LENGTH, + * the first (lowest index) SHA224_NATURAL_LENGTH bytes of resultArray will be used for the HMAC. + * If outputLength is 0, then the natural HMAC output length is selected. * * @return A pointer to resultArray. */ - void *sha224HmacCT(const void *data, const size_t dataLength, const void *hashKey, const size_t hashKeyLength, void *resultArray, const size_t resultArrayLength); + void *sha224HmacCT(const void *data, const size_t dataLength, const void *hashKey, const size_t hashKeyLength, void *resultArray, const size_t outputLength); /** * Create a SHA224 HMAC from the message, using the provided hashKey. The result will be hmacLength bytes long and returned as a String in HEX format. @@ -380,7 +402,7 @@ namespace CryptoInterface String sha256Hash(const String &message); /** - * Create a SHA256 HMAC from the data, using the provided hashKey. The result will be resultArrayLength bytes long and stored in resultArray. + * Create a SHA256 HMAC from the data, using the provided hashKey. The result will be up to outputLength bytes long and stored in resultArray. * Uses the BearSSL cryptographic library. * * @param data The data array from which to create the HMAC. @@ -388,12 +410,13 @@ namespace CryptoInterface * @param hashKey The hash key to use when creating the HMAC. * @param hashKeyLength The length of the hash key in bytes. * @param resultArray The array wherein to store the resulting HMAC. - * @param resultArrayLength The length of resultArray in bytes. Determines the HMAC length. If resultArrayLength is greater than SHA256_NATURAL_LENGTH, - * the first (lowest index) SHA256_NATURAL_LENGTH bytes of resultArray will be used for the HMAC. + * @param outputLength The desired length of the generated HMAC, in bytes. Must fit within resultArray. If outputLength is greater than SHA256_NATURAL_LENGTH, + * the first (lowest index) SHA256_NATURAL_LENGTH bytes of resultArray will be used for the HMAC. + * If outputLength is 0, then the natural HMAC output length is selected. * * @return A pointer to resultArray. */ - void *sha256Hmac(const void *data, const size_t dataLength, const void *hashKey, const size_t hashKeyLength, void *resultArray, const size_t resultArrayLength); + void *sha256Hmac(const void *data, const size_t dataLength, const void *hashKey, const size_t hashKeyLength, void *resultArray, const size_t outputLength); /** * Create a SHA256 HMAC from the message, using the provided hashKey. The result will be hmacLength bytes long and returned as a String in HEX format. @@ -409,7 +432,7 @@ namespace CryptoInterface String sha256Hmac(const String &message, const void *hashKey, const size_t hashKeyLength, const size_t hmacLength); /** - * Create a SHA256 HMAC from the data, using the provided hashKey. The result will be resultArrayLength bytes long and stored in resultArray. + * Create a SHA256 HMAC from the data, using the provided hashKey. The result will be up to outputLength bytes long and stored in resultArray. * Constant-time version. * Uses the BearSSL cryptographic library. * @@ -418,12 +441,13 @@ namespace CryptoInterface * @param hashKey The hash key to use when creating the HMAC. * @param hashKeyLength The length of the hash key in bytes. * @param resultArray The array wherein to store the resulting HMAC. - * @param resultArrayLength The length of resultArray in bytes. Determines the HMAC length. If resultArrayLength is greater than SHA256_NATURAL_LENGTH, - * the first (lowest index) SHA256_NATURAL_LENGTH bytes of resultArray will be used for the HMAC. + * @param outputLength The desired length of the generated HMAC, in bytes. Must fit within resultArray. If outputLength is greater than SHA256_NATURAL_LENGTH, + * the first (lowest index) SHA256_NATURAL_LENGTH bytes of resultArray will be used for the HMAC. + * If outputLength is 0, then the natural HMAC output length is selected. * * @return A pointer to resultArray. */ - void *sha256HmacCT(const void *data, const size_t dataLength, const void *hashKey, const size_t hashKeyLength, void *resultArray, const size_t resultArrayLength); + void *sha256HmacCT(const void *data, const size_t dataLength, const void *hashKey, const size_t hashKeyLength, void *resultArray, const size_t outputLength); /** * Create a SHA256 HMAC from the message, using the provided hashKey. The result will be hmacLength bytes long and returned as a String in HEX format. @@ -465,7 +489,7 @@ namespace CryptoInterface String sha384Hash(const String &message); /** - * Create a SHA384 HMAC from the data, using the provided hashKey. The result will be resultArrayLength bytes long and stored in resultArray. + * Create a SHA384 HMAC from the data, using the provided hashKey. The result will be up to outputLength bytes long and stored in resultArray. * Uses the BearSSL cryptographic library. * * @param data The data array from which to create the HMAC. @@ -473,12 +497,13 @@ namespace CryptoInterface * @param hashKey The hash key to use when creating the HMAC. * @param hashKeyLength The length of the hash key in bytes. * @param resultArray The array wherein to store the resulting HMAC. - * @param resultArrayLength The length of resultArray in bytes. Determines the HMAC length. If resultArrayLength is greater than SHA384_NATURAL_LENGTH, - * the first (lowest index) SHA384_NATURAL_LENGTH bytes of resultArray will be used for the HMAC. + * @param outputLength The desired length of the generated HMAC, in bytes. Must fit within resultArray. If outputLength is greater than SHA384_NATURAL_LENGTH, + * the first (lowest index) SHA384_NATURAL_LENGTH bytes of resultArray will be used for the HMAC. + * If outputLength is 0, then the natural HMAC output length is selected. * * @return A pointer to resultArray. */ - void *sha384Hmac(const void *data, const size_t dataLength, const void *hashKey, const size_t hashKeyLength, void *resultArray, const size_t resultArrayLength); + void *sha384Hmac(const void *data, const size_t dataLength, const void *hashKey, const size_t hashKeyLength, void *resultArray, const size_t outputLength); /** * Create a SHA384 HMAC from the message, using the provided hashKey. The result will be hmacLength bytes long and returned as a String in HEX format. @@ -494,7 +519,7 @@ namespace CryptoInterface String sha384Hmac(const String &message, const void *hashKey, const size_t hashKeyLength, const size_t hmacLength); /** - * Create a SHA384 HMAC from the data, using the provided hashKey. The result will be resultArrayLength bytes long and stored in resultArray. + * Create a SHA384 HMAC from the data, using the provided hashKey. The result will be up to outputLength bytes long and stored in resultArray. * Constant-time version. * Uses the BearSSL cryptographic library. * @@ -503,12 +528,13 @@ namespace CryptoInterface * @param hashKey The hash key to use when creating the HMAC. * @param hashKeyLength The length of the hash key in bytes. * @param resultArray The array wherein to store the resulting HMAC. - * @param resultArrayLength The length of resultArray in bytes. Determines the HMAC length. If resultArrayLength is greater than SHA384_NATURAL_LENGTH, - * the first (lowest index) SHA384_NATURAL_LENGTH bytes of resultArray will be used for the HMAC. + * @param outputLength The desired length of the generated HMAC, in bytes. Must fit within resultArray. If outputLength is greater than SHA384_NATURAL_LENGTH, + * the first (lowest index) SHA384_NATURAL_LENGTH bytes of resultArray will be used for the HMAC. + * If outputLength is 0, then the natural HMAC output length is selected. * * @return A pointer to resultArray. */ - void *sha384HmacCT(const void *data, const size_t dataLength, const void *hashKey, const size_t hashKeyLength, void *resultArray, const size_t resultArrayLength); + void *sha384HmacCT(const void *data, const size_t dataLength, const void *hashKey, const size_t hashKeyLength, void *resultArray, const size_t outputLength); /** * Create a SHA384 HMAC from the message, using the provided hashKey. The result will be hmacLength bytes long and returned as a String in HEX format. @@ -550,7 +576,7 @@ namespace CryptoInterface String sha512Hash(const String &message); /** - * Create a SHA512 HMAC from the data, using the provided hashKey. The result will be resultArrayLength bytes long and stored in resultArray. + * Create a SHA512 HMAC from the data, using the provided hashKey. The result will be up to outputLength bytes long and stored in resultArray. * Uses the BearSSL cryptographic library. * * @param data The data array from which to create the HMAC. @@ -558,12 +584,13 @@ namespace CryptoInterface * @param hashKey The hash key to use when creating the HMAC. * @param hashKeyLength The length of the hash key in bytes. * @param resultArray The array wherein to store the resulting HMAC. - * @param resultArrayLength The length of resultArray in bytes. Determines the HMAC length. If resultArrayLength is greater than SHA512_NATURAL_LENGTH, - * the first (lowest index) SHA512_NATURAL_LENGTH bytes of resultArray will be used for the HMAC. + * @param outputLength The desired length of the generated HMAC, in bytes. Must fit within resultArray. If outputLength is greater than SHA512_NATURAL_LENGTH, + * the first (lowest index) SHA512_NATURAL_LENGTH bytes of resultArray will be used for the HMAC. + * If outputLength is 0, then the natural HMAC output length is selected. * * @return A pointer to resultArray. */ - void *sha512Hmac(const void *data, const size_t dataLength, const void *hashKey, const size_t hashKeyLength, void *resultArray, const size_t resultArrayLength); + void *sha512Hmac(const void *data, const size_t dataLength, const void *hashKey, const size_t hashKeyLength, void *resultArray, const size_t outputLength); /** * Create a SHA512 HMAC from the message, using the provided hashKey. The result will be hmacLength bytes long and returned as a String in HEX format. @@ -579,7 +606,7 @@ namespace CryptoInterface String sha512Hmac(const String &message, const void *hashKey, const size_t hashKeyLength, const size_t hmacLength); /** - * Create a SHA512 HMAC from the data, using the provided hashKey. The result will be resultArrayLength bytes long and stored in resultArray. + * Create a SHA512 HMAC from the data, using the provided hashKey. The result will be up to outputLength bytes long and stored in resultArray. * Constant-time version. * Uses the BearSSL cryptographic library. * @@ -588,12 +615,13 @@ namespace CryptoInterface * @param hashKey The hash key to use when creating the HMAC. * @param hashKeyLength The length of the hash key in bytes. * @param resultArray The array wherein to store the resulting HMAC. - * @param resultArrayLength The length of resultArray in bytes. Determines the HMAC length. If resultArrayLength is greater than SHA512_NATURAL_LENGTH, - * the first (lowest index) SHA512_NATURAL_LENGTH bytes of resultArray will be used for the HMAC. + * @param outputLength The desired length of the generated HMAC, in bytes. Must fit within resultArray. If outputLength is greater than SHA512_NATURAL_LENGTH, + * the first (lowest index) SHA512_NATURAL_LENGTH bytes of resultArray will be used for the HMAC. + * If outputLength is 0, then the natural HMAC output length is selected. * * @return A pointer to resultArray. */ - void *sha512HmacCT(const void *data, const size_t dataLength, const void *hashKey, const size_t hashKeyLength, void *resultArray, const size_t resultArrayLength); + void *sha512HmacCT(const void *data, const size_t dataLength, const void *hashKey, const size_t hashKeyLength, void *resultArray, const size_t outputLength); /** * Create a SHA512 HMAC from the message, using the provided hashKey. The result will be hmacLength bytes long and returned as a String in HEX format. @@ -639,6 +667,133 @@ namespace CryptoInterface * @return A String with the generated hash in HEX format. */ String md5sha1Hash(const String &message); + + + // #################### 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. + * 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. + * Uses the BearSSL cryptographic library. + * + * Must be called at least once before hkdfProduce() can be used. + * + * @param keyMaterial An array containing the key material to use when deriving subkeys. Typically this would be the master key. + * @param keyMaterialLength The length of keyMaterial in bytes. + * @param salt An array containing the salt to use when ingesting key material. Salt is non-secret and can be empty. + * Its role is normally to bind the input to a conventional identifier that qualify it within the used protocol or application. + * @param saltLength The length of the salt array, in bytes. + */ + void hkdfInit(const void *keyMaterial, const size_t keyMaterialLength, const void *salt = nullptr, const size_t saltLength = 0); + + /** + * Produce more output bytes from the current HKDF state. This function may be called several times to obtain the full output by chunks. + * The total output size is limited to 255 * SHA256_NATURAL_LENGTH bytes per unique hkdfInit() call. + * Uses the BearSSL cryptographic library. + * + * Should only be used when hkdfInit() has been called at least once. + * + * @param resultArray The array wherein to store the resulting HKDF. + * @param outputLength The requested number of bytes to fill with HKDF output in resultArray. + * @param info NOTE: For correct HKDF processing, the same "info" string must be provided for every call until there's a new unique hkdfInit(). + * An array containing the information string to use when producing output. Info is non-secret and can be empty. + * Its role is normally to bind the output to a conventional identifier that qualify it within the used protocol or application. + * @param infoLength The length of the info array, in bytes. + * + * @return The number of HKDF bytes actually produced. + */ + size_t hkdfProduce(void *resultArray, const size_t outputLength, const void *info = nullptr, size_t infoLength = 0); + + + // #################### Authenticated Encryption with Associated Data (AEAD) #################### + + /** + * From https://www.bearssl.org/apidoc/bearssl__aead_8h.html + * + * An AEAD algorithm processes messages and provides confidentiality (encryption) and checked integrity (MAC). It uses the following parameters: + * + * - A symmetric key. Exact size depends on the AEAD algorithm. + * - A nonce (IV). Size depends on the AEAD algorithm; for most algorithms, it is crucial for security that any given nonce value is never used twice for the same key and distinct messages. + * - Data to encrypt and protect. + * - Additional authenticated data, which is covered by the MAC but otherwise left untouched (i.e. not encrypted). + * + * The AEAD algorithm encrypts the data, and produces an authentication tag. + * It is assumed that the encrypted data, the tag, the additional authenticated data and the nonce are sent to the receiver; + * the additional data and the nonce may be implicit (e.g. using elements of the underlying transport protocol, such as record sequence numbers). + * The receiver will recompute the tag value and compare it with the one received; + * if they match, then the data is correct, and can be decrypted and used; + * otherwise, at least one of the elements was altered in transit, normally leading to wholesale rejection of the complete message. + */ + + + // #################### ChaCha20+Poly1305 AEAD #################### + + /** + * Encrypt the data array using the ChaCha20 stream cipher and use Poly1305 for message authentication. + * The function generates in place an equal-length ChaCha20 encrypted version of the data array. + * More information about this encryption standard can be found here: https://tools.ietf.org/html/rfc7539 , https://tools.ietf.org/html/rfc8439 + * Uses the BearSSL cryptographic library. + * + * Encryption of small messages (up to a few hundred data bytes) takes around 0.5-1 ms with the default nonceGenerator, half of this without keySalt. + * + * The output values of chacha20Poly1305Encrypt should be passed as input values to chacha20Poly1305Decrypt. + * + * Note that a 12 byte nonce is generated via getNonceGenerator() every time chacha20Poly1305Encrypt is called. + * If the same key and nonce combination is used more than once for distinct messages, the encryption will be broken, so keep the following in mind: + * + * By default the nonce is generated via the hardware random number generator of the ESP8266. + * The entropy of this source may not be sufficient to avoid nonce collisions, so to further reduce the risk of encryption failure + * it is recommended that a keySalt is always provided when using the default nonceGenerator. Using a keySalt will create a + * pseudorandom subkey from the original key via HKDF, and use that for the encryption/decryption. + * The same key + keySalt will always generate the same subkey. + * + * An alternative to using a keySalt is to change the nonceGenerator so that it does not rely on random numbers. + * One way to do this would be to use a counter that guarantees the same key + nonce combination is never used. + * This may not be easily achievable in all scenarios, however. + * + * @param data An array containing the data to encrypt. The encrypted data is generated in place, so when the function returns the data array will contain the encrypted data. + * @param dataLength The length of the data array in bytes. + * @param key The secret encryption key to use. + * @param keySalt The salt to use when generating a subkey from key. Note that the same salt must be used during decryption as during encryption. Set to nullptr to prevent subkey generation. + * @param keySaltLength The length of keySalt in bytes. + * @param resultingNonce The array that will store the nonce generated during encryption. Must be able to contain at least 12 bytes. The nonce is not secret and must be passed to the decryption function. + * @param resultingTag The array that will store the message authentication tag generated during encryption. Must be able to contain at least 16 bytes. The tag is not secret and must be passed to the decryption function. + * @param aad Additional authenticated data. This data will be covered by the Poly1305 MAC, but not encrypted. + * You can include the unencrypted parts of your message as AAD to ensure that the encrypted content cannot + * be re-sent with replaced unencrypted data by an attacker. + * Defaults to nullptr. + * @param aadLength The length of the aad array in bytes. Defaults to 0. + */ + void chacha20Poly1305Encrypt(void *data, const size_t dataLength, const void *key, const void *keySalt, const size_t keySaltLength, void *resultingNonce, void *resultingTag, const void *aad = nullptr, const size_t aadLength = 0); + + /** + * Decrypt the data array using the ChaCha20 stream cipher and use Poly1305 for message authentication. + * The function generates in place an equal-length ChaCha20 decrypted version of the data array. + * More information about this encryption standard can be found here: https://tools.ietf.org/html/rfc7539 , https://tools.ietf.org/html/rfc8439 + * Uses the BearSSL cryptographic library. + * + * Decryption of small messages (up to a few hundred data bytes) takes around 0.5-1 ms, half of this without keySalt. + * + * The output values of chacha20Poly1305Encrypt should be passed as input values to chacha20Poly1305Decrypt. + * + * @param data An array containing the data to decrypt. The decrypted data is generated in place, so when the function returns the data array will contain the decrypted data. + * @param dataLength The length of the data array in bytes. + * @param key The secret encryption key to use. + * @param keySalt The salt to use when generating a subkey from key. Note that the same salt must be used during decryption as during encryption. Set to nullptr to prevent subkey generation. + * @param keySaltLength The length of keySalt in bytes. + * @param encryptionNonce An array containing the nonce that was generated during encryption. The nonce should be 12 bytes. + * @param encryptionTag An array containing the message authentication tag that was generated during encryption. The tag should be 16 bytes. + * @param aad Additional authenticated data. This data will be covered by the Poly1305 MAC, but not decrypted. + * You can include the unencrypted parts of your message as AAD to ensure that the encrypted content cannot + * be re-sent with replaced unencrypted data by an attacker. + * Defaults to nullptr. + * @param aadLength The length of the aad array in bytes. Defaults to 0. + * + * @return True if the decryption was successful (the generated tag matches encryptionTag). False otherwise. Note that the data array is modified regardless of this outcome. + */ + bool chacha20Poly1305Decrypt(void *data, const size_t dataLength, const void *key, const void *keySalt, const size_t keySaltLength, const void *encryptionNonce, const void *encryptionTag, const void *aad = nullptr, const size_t aadLength = 0); } #endif diff --git a/libraries/ESP8266WiFiMesh/src/EncryptedConnectionData.cpp b/libraries/ESP8266WiFiMesh/src/EncryptedConnectionData.cpp index c4992a7ae4..55fc05d62d 100644 --- a/libraries/ESP8266WiFiMesh/src/EncryptedConnectionData.cpp +++ b/libraries/ESP8266WiFiMesh/src/EncryptedConnectionData.cpp @@ -128,18 +128,21 @@ uint64_t EncryptedConnectionData::getOwnSessionKey() const { return _ownSessionK uint64_t EncryptedConnectionData::incrementSessionKey(uint64_t sessionKey, const uint8_t *hashKey, uint8_t hashKeyLength) { - String hmac = MeshCryptoInterface::createMeshHmac(uint64ToString(sessionKey), hashKey, hashKeyLength); - + uint8_t inputArray[8] {0}; + uint8_t hmacArray[CryptoInterface::SHA256_NATURAL_LENGTH] {0}; + CryptoInterface::sha256Hmac(uint64ToUint8Array(sessionKey, inputArray), 8, hashKey, hashKeyLength, hmacArray, CryptoInterface::SHA256_NATURAL_LENGTH); + /* HMAC truncation should be OK since hmac sha256 is a PRF and we are truncating to the leftmost (MSB) bits. PRF: https://crypto.stackexchange.com/questions/26410/whats-the-gcm-sha-256-of-a-tls-protocol/26434#26434 Truncate to leftmost bits: https://tools.ietf.org/html/rfc2104#section-5 */ - uint64_t newLeftmostBits = strtoul(hmac.substring(0, 8).c_str(), nullptr, HEX); // strtoul stops reading input when an invalid character is discovered. + uint64_t newLeftmostBits = uint8ArrayToUint64(hmacArray) & EspnowProtocolInterpreter::uint64LeftmostBits; + if(newLeftmostBits == 0) - newLeftmostBits = RANDOM_REG32 | (1 << 31); // 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. uint64_t newRightmostBits = (uint32_t)(sessionKey + 1); - return (newLeftmostBits << 32) | newRightmostBits; + return newLeftmostBits | newRightmostBits; } void EncryptedConnectionData::incrementOwnSessionKey() diff --git a/libraries/ESP8266WiFiMesh/src/EncryptedConnectionData.h b/libraries/ESP8266WiFiMesh/src/EncryptedConnectionData.h index 56443cbe6a..be0d191710 100644 --- a/libraries/ESP8266WiFiMesh/src/EncryptedConnectionData.h +++ b/libraries/ESP8266WiFiMesh/src/EncryptedConnectionData.h @@ -76,7 +76,7 @@ class EncryptedConnectionData { void setDesync(bool desync); bool desync() const; - // Note that the espnowEncryptionKey, espnowEncryptionKok and espnowHashKey are not serialized. + // Note that the espnowEncryptedConnectionKey, espnowEncryptionKok, espnowHashKey and espnowMessageEncryptionKey are not serialized. // These will be set to the values of the EspnowMeshBackend instance that is adding the serialized encrypted connection. String serialize() const; diff --git a/libraries/ESP8266WiFiMesh/src/EspnowMeshBackend.cpp b/libraries/ESP8266WiFiMesh/src/EspnowMeshBackend.cpp index 6bd8294d59..872feb9942 100644 --- a/libraries/ESP8266WiFiMesh/src/EspnowMeshBackend.cpp +++ b/libraries/ESP8266WiFiMesh/src/EspnowMeshBackend.cpp @@ -26,8 +26,9 @@ extern "C" { #include "UtilityFunctions.h" #include "MutexTracker.h" #include "JsonTranslator.h" +#include "MeshCryptoInterface.h" -using EspnowProtocolInterpreter::espnowEncryptionKeyLength; +using EspnowProtocolInterpreter::espnowEncryptedConnectionKeyLength; using EspnowProtocolInterpreter::espnowHashKeyLength; 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. @@ -38,6 +39,7 @@ const uint8_t EspnowMeshBackend::broadcastMac[6] = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF bool EspnowMeshBackend::_espnowTransmissionMutex = false; bool EspnowMeshBackend::_espnowConnectionQueueMutex = false; +bool EspnowMeshBackend::_responsesToSendMutex = false; EspnowMeshBackend *EspnowMeshBackend::_espnowRequestManager = nullptr; @@ -67,10 +69,13 @@ encrypted_connection_status_t EspnowMeshBackend::_ongoingPeerRequestResult = ECS uint32_t EspnowMeshBackend::_ongoingPeerRequestEncryptionStart = 0; bool EspnowMeshBackend::_reciprocalPeerRequestConfirmation = false; -uint8_t EspnowMeshBackend::_espnowEncryptionKok[espnowEncryptionKeyLength] = { 0 }; +uint8_t EspnowMeshBackend::_espnowEncryptionKok[espnowEncryptedConnectionKeyLength] = { 0 }; bool EspnowMeshBackend::_espnowEncryptionKokSet = false; -uint32_t EspnowMeshBackend::_unencryptedMessageID = 0; +uint8_t EspnowMeshBackend::_espnowMessageEncryptionKey[CryptoInterface::ENCRYPTION_KEY_LENGTH] = { 0 }; +bool EspnowMeshBackend::_useEncryptedMessages = false; + +uint32_t EspnowMeshBackend::_unsynchronizedMessageID = 0; // _logEntryLifetimeMs is based on someone storing 40 responses of 750 bytes each = 30 000 bytes (roughly full memory), // which takes 2000 ms + some margin to send. Also, we want to avoid old entries taking up memory if they cannot be sent, @@ -103,8 +108,7 @@ void espnowDelay(uint32_t durationMs) } EspnowMeshBackend::EspnowMeshBackend(requestHandlerType requestHandler, responseHandlerType responseHandler, networkFilterType networkFilter, - broadcastFilterType broadcastFilter, const String &meshPassword, const uint8_t espnowEncryptionKey[espnowEncryptionKeyLength], - const uint8_t espnowHashKey[espnowHashKeyLength], const String &ssidPrefix, const String &ssidSuffix, bool verboseMode, + broadcastFilterType broadcastFilter, const String &meshPassword, const String &ssidPrefix, const String &ssidSuffix, bool verboseMode, uint8 meshWiFiChannel) : MeshBackendBase(requestHandler, responseHandler, networkFilter, MB_ESP_NOW) { @@ -114,12 +118,30 @@ EspnowMeshBackend::EspnowMeshBackend(requestHandlerType requestHandler, response setBroadcastFilter(broadcastFilter); setSSID(ssidPrefix, "", ssidSuffix); setMeshPassword(meshPassword); - setEspnowEncryptionKey(espnowEncryptionKey); - setEspnowHashKey(espnowHashKey); setVerboseModeState(verboseMode); setWiFiChannel(meshWiFiChannel); } +EspnowMeshBackend::EspnowMeshBackend(requestHandlerType requestHandler, responseHandlerType responseHandler, networkFilterType networkFilter, + broadcastFilterType broadcastFilter, const String &meshPassword, const uint8_t espnowEncryptedConnectionKey[espnowEncryptedConnectionKeyLength], + const uint8_t espnowHashKey[espnowHashKeyLength], const String &ssidPrefix, const String &ssidSuffix, bool verboseMode, + uint8 meshWiFiChannel) + : EspnowMeshBackend(requestHandler, responseHandler, networkFilter, broadcastFilter, meshPassword, ssidPrefix, ssidSuffix, verboseMode, meshWiFiChannel) +{ + setEspnowEncryptedConnectionKey(espnowEncryptedConnectionKey); + setEspnowHashKey(espnowHashKey); +} + +EspnowMeshBackend::EspnowMeshBackend(requestHandlerType requestHandler, responseHandlerType responseHandler, networkFilterType networkFilter, + broadcastFilterType broadcastFilter, const String &meshPassword, const String &espnowEncryptedConnectionKeySeed, + const String &espnowHashKeySeed, const String &ssidPrefix, const String &ssidSuffix, bool verboseMode, + uint8 meshWiFiChannel) + : EspnowMeshBackend(requestHandler, responseHandler, networkFilter, broadcastFilter, meshPassword, ssidPrefix, ssidSuffix, verboseMode, meshWiFiChannel) +{ + setEspnowEncryptedConnectionKey(espnowEncryptedConnectionKeySeed); + setEspnowHashKey(espnowHashKeySeed); +} + EspnowMeshBackend::~EspnowMeshBackend() { if(isEspnowRequestManager()) @@ -142,7 +164,7 @@ bool EspnowMeshBackend::activateEspnow() { if (esp_now_init()==0) { - if(_espnowEncryptionKokSet && esp_now_set_kok(_espnowEncryptionKok, espnowEncryptionKeyLength)) // 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!"); if(getEspnowRequestManager() == nullptr) @@ -388,7 +410,7 @@ void EspnowMeshBackend::espnowReceiveCallbackWrapper(uint8_t *macaddr, uint8_t * // Otherwise we get issues such as _espnowTransmissionMutex will usually be free, but occasionally taken (when callback occurs in a delay() during attemptTransmission). MutexTracker captureBanTracker(MutexTracker::captureBan()); - if(len >= EspnowProtocolInterpreter::espnowProtocolBytesSize()) // If we do not receive at least the protocol bytes, the transmission is invalid. + if(len >= espnowMetadataSize()) // If we do not receive at least the metadata bytes, the transmission is invalid. { //uint32_t callbackStart = millis(); @@ -398,11 +420,21 @@ void EspnowMeshBackend::espnowReceiveCallbackWrapper(uint8_t *macaddr, uint8_t * char messageType = espnowGetMessageType(dataArray); uint64_t receivedMessageID = espnowGetMessageID(dataArray); - if(currentEspnowRequestManager && !currentEspnowRequestManager->acceptsUnencryptedRequests() + if(currentEspnowRequestManager && !currentEspnowRequestManager->acceptsUnverifiedRequests() && !usesConstantSessionKey(messageType) && !verifyPeerSessionKey(receivedMessageID, macaddr, messageType)) { return; } + + if(useEncryptedMessages()) + { + // chacha20Poly1305Decrypt decrypts dataArray in place. + if(!CryptoInterface::chacha20Poly1305Decrypt(dataArray + espnowMetadataSize(), len - espnowMetadataSize(), getEspnowMessageEncryptionKey(), dataArray, + espnowProtocolBytesSize, dataArray + espnowProtocolBytesSize, dataArray + espnowProtocolBytesSize + 12)) + { + return; // Decryption of message failed. + } + } uint64_t uint64StationMac = macToUint64(macaddr); bool transmissionEncrypted = usesEncryption(receivedMessageID); @@ -759,7 +791,7 @@ void EspnowMeshBackend::espnowReceiveCallback(uint8_t *macaddr, uint8_t *dataArr String message = espnowGetMessageContent(dataArray, len); setSenderMac(macaddr); espnowGetTransmissionMac(dataArray, _senderAPMac); - setReceivedEncryptedMessage(usesEncryption(messageID)); + setReceivedEncryptedTransmission(usesEncryption(messageID)); bool acceptBroadcast = getBroadcastFilter()(message, *this); if(acceptBroadcast) { @@ -815,7 +847,7 @@ void EspnowMeshBackend::espnowReceiveCallback(uint8_t *macaddr, uint8_t *dataArr // Copy totalMessage in case user callbacks (request/responseHandler) do something odd with receivedEspnowTransmissions list. String totalMessage = storedMessageIterator->second.getTotalMessage(); // https://stackoverflow.com/questions/134731/returning-a-const-reference-to-an-object-instead-of-a-copy It is likely that most compilers will perform Named Value Return Value Optimisation in this case - + receivedEspnowTransmissions.erase(storedMessageIterator); // Erase the extra copy of the totalMessage, to save RAM. //Serial.println("methodStart erase done " + String(millis() - methodStart)); @@ -827,7 +859,7 @@ void EspnowMeshBackend::espnowReceiveCallback(uint8_t *macaddr, uint8_t *dataArr setSenderMac(macaddr); espnowGetTransmissionMac(dataArray, _senderAPMac); - setReceivedEncryptedMessage(usesEncryption(messageID)); + setReceivedEncryptedTransmission(usesEncryption(messageID)); String response = getRequestHandler()(totalMessage, *this); //Serial.println("methodStart response acquired " + String(millis() - methodStart)); @@ -853,7 +885,7 @@ void EspnowMeshBackend::espnowReceiveCallback(uint8_t *macaddr, uint8_t *dataArr setSenderMac(macaddr); espnowGetTransmissionMac(dataArray, _senderAPMac); - setReceivedEncryptedMessage(usesEncryption(messageID)); + setReceivedEncryptedTransmission(usesEncryption(messageID)); getResponseHandler()(totalMessage, *this); } else @@ -979,7 +1011,7 @@ uint64_t EspnowMeshBackend::generateMessageID(EncryptedConnectionLog *encryptedC return encryptedConnection->getOwnSessionKey(); } - return _unencryptedMessageID++; + return _unsynchronizedMessageID++; } uint64_t EspnowMeshBackend::createSessionKey() @@ -1073,7 +1105,7 @@ transmission_status_t EspnowMeshBackend::espnowSendToNodeUnsynchronized(const St int32_t transmissionsRemaining = transmissionsRequired > 1 ? transmissionsRequired - 1 : 0; _transmissionsTotal++; - + // Though it is possible to handle messages requiring more than 3 transmissions with the current design, transmission fail rates would increase dramatically. // Messages composed of up to 128 transmissions can be handled without modification, but RAM limitations on the ESP8266 would make this hard in practice. // We thus prefer to keep the code simple and performant instead. @@ -1087,7 +1119,7 @@ transmission_status_t EspnowMeshBackend::espnowSendToNodeUnsynchronized(const St uint8_t transmissionSize = 0; bool messageStart = true; - uint8_t sizeOfProtocolBytes = espnowProtocolBytesSize(); + uint8_t metadataSize = espnowMetadataSize(); do { @@ -1105,12 +1137,19 @@ transmission_status_t EspnowMeshBackend::espnowSendToNodeUnsynchronized(const St ////// Create transmission array ////// if(transmissionsRemaining > 0) + { transmissionSize = getMaxBytesPerTransmission(); - else if(message.length() == 0) - transmissionSize = sizeOfProtocolBytes; + } else - transmissionSize = sizeOfProtocolBytes + (message.length() % getMaxMessageBytesPerTransmission() == 0 ? - getMaxMessageBytesPerTransmission() : message.length() % getMaxMessageBytesPerTransmission()); + { + transmissionSize = metadataSize; + + if(message.length() > 0) + { + uint32_t remainingLength = message.length() % getMaxMessageBytesPerTransmission(); + transmissionSize += (remainingLength == 0 ? getMaxMessageBytesPerTransmission() : remainingLength); + } + } uint8_t transmission[transmissionSize]; @@ -1136,9 +1175,17 @@ transmission_status_t EspnowMeshBackend::espnowSendToNodeUnsynchronized(const St ////// Fill message bytes ////// int32_t transmissionStartIndex = (transmissionsRequired - transmissionsRemaining - 1) * getMaxMessageBytesPerTransmission(); - std::copy_n(message.substring(transmissionStartIndex, transmissionStartIndex + transmissionSize - sizeOfProtocolBytes).c_str(), - transmissionSize - sizeOfProtocolBytes, transmission + sizeOfProtocolBytes); + + std::copy_n(message.begin() + transmissionStartIndex, transmissionSize - metadataSize, transmission + metadataSize); + if(useEncryptedMessages()) + { + // chacha20Poly1305Encrypt encrypts transmission in place. + // We are using the protocol bytes as a key salt. + CryptoInterface::chacha20Poly1305Encrypt(transmission + metadataSize, transmissionSize - metadataSize, getEspnowMessageEncryptionKey(), transmission, + espnowProtocolBytesSize, transmission + espnowProtocolBytesSize, transmission + espnowProtocolBytesSize + 12); + } + ////// Transmit ////// uint32_t retransmissions = 0; @@ -1238,33 +1285,38 @@ uint64_t EspnowMeshBackend::macAndTypeToUint64Mac(const macAndType_td &macAndTyp return static_cast(macAndTypeValue) >> 8; } -void EspnowMeshBackend::setEspnowEncryptionKey(const uint8_t espnowEncryptionKey[espnowEncryptionKeyLength]) +void EspnowMeshBackend::setEspnowEncryptedConnectionKey(const uint8_t espnowEncryptedConnectionKey[espnowEncryptedConnectionKeyLength]) { - assert(espnowEncryptionKey != nullptr); + assert(espnowEncryptedConnectionKey != nullptr); - for(int i = 0; i < espnowEncryptionKeyLength; i++) + for(int i = 0; i < espnowEncryptedConnectionKeyLength; i++) { - _espnowEncryptionKey[i] = espnowEncryptionKey[i]; + _espnowEncryptedConnectionKey[i] = espnowEncryptedConnectionKey[i]; } } -const uint8_t *EspnowMeshBackend::getEspnowEncryptionKey() +void EspnowMeshBackend::setEspnowEncryptedConnectionKey(const String &espnowEncryptedConnectionKeySeed) +{ + MeshCryptoInterface::initializeKey(_espnowEncryptedConnectionKey, espnowEncryptedConnectionKeyLength, espnowEncryptedConnectionKeySeed); +} + +const uint8_t *EspnowMeshBackend::getEspnowEncryptedConnectionKey() { - return _espnowEncryptionKey; + return _espnowEncryptedConnectionKey; } -uint8_t *EspnowMeshBackend::getEspnowEncryptionKey(uint8_t resultArray[espnowEncryptionKeyLength]) +uint8_t *EspnowMeshBackend::getEspnowEncryptedConnectionKey(uint8_t resultArray[espnowEncryptedConnectionKeyLength]) { - std::copy_n(_espnowEncryptionKey, espnowEncryptionKeyLength, resultArray); + std::copy_n(_espnowEncryptedConnectionKey, espnowEncryptedConnectionKeyLength, resultArray); return resultArray; } -bool EspnowMeshBackend::setEspnowEncryptionKok(uint8_t espnowEncryptionKok[espnowEncryptionKeyLength]) +bool EspnowMeshBackend::setEspnowEncryptionKok(uint8_t espnowEncryptionKok[espnowEncryptedConnectionKeyLength]) { - if(espnowEncryptionKok == nullptr || esp_now_set_kok(espnowEncryptionKok, espnowEncryptionKeyLength)) // esp_now_set_kok failed if not == 0 + if(espnowEncryptionKok == nullptr || esp_now_set_kok(espnowEncryptionKok, espnowEncryptedConnectionKeyLength)) // esp_now_set_kok failed if not == 0 return false; - for(int i = 0; i < espnowEncryptionKeyLength; i++) + for(int i = 0; i < espnowEncryptedConnectionKeyLength; i++) { _espnowEncryptionKok[i] = espnowEncryptionKok[i]; } @@ -1274,6 +1326,14 @@ bool EspnowMeshBackend::setEspnowEncryptionKok(uint8_t espnowEncryptionKok[espno return true; } +bool EspnowMeshBackend::setEspnowEncryptionKok(const String &espnowEncryptionKokSeed) +{ + uint8_t espnowEncryptionKok[espnowEncryptedConnectionKeyLength] {}; + MeshCryptoInterface::initializeKey(espnowEncryptionKok, espnowEncryptedConnectionKeyLength, espnowEncryptionKokSeed); + + return setEspnowEncryptionKok(espnowEncryptionKok); +} + const uint8_t *EspnowMeshBackend::getEspnowEncryptionKok() { if(_espnowEncryptionKokSet) @@ -1292,11 +1352,48 @@ void EspnowMeshBackend::setEspnowHashKey(const uint8_t espnowHashKey[espnowHashK } } +void EspnowMeshBackend::setEspnowHashKey(const String &espnowHashKeySeed) +{ + MeshCryptoInterface::initializeKey(_espnowHashKey, espnowHashKeyLength, espnowHashKeySeed); +} + const uint8_t *EspnowMeshBackend::getEspnowHashKey() { return _espnowHashKey; } +void EspnowMeshBackend::setEspnowMessageEncryptionKey(uint8_t espnowMessageEncryptionKey[CryptoInterface::ENCRYPTION_KEY_LENGTH]) +{ + assert(espnowMessageEncryptionKey != nullptr); + + for(int i = 0; i < CryptoInterface::ENCRYPTION_KEY_LENGTH; i++) + { + _espnowMessageEncryptionKey[i] = espnowMessageEncryptionKey[i]; + } +} + +void EspnowMeshBackend::setEspnowMessageEncryptionKey(const String &espnowMessageEncryptionKeySeed) +{ + MeshCryptoInterface::initializeKey(_espnowMessageEncryptionKey, CryptoInterface::ENCRYPTION_KEY_LENGTH, espnowMessageEncryptionKeySeed); +} + +const uint8_t *EspnowMeshBackend::getEspnowMessageEncryptionKey() +{ + return _espnowMessageEncryptionKey; +} + +void EspnowMeshBackend::setUseEncryptedMessages(bool useEncryptedMessages) +{ + MutexTracker mutexTracker(_espnowSendToNodeMutex); + if(!mutexTracker.mutexCaptured()) + { + assert(false && "ERROR! espnowSendToNode in progress. Don't call setUseEncryptedMessages from non-hook callbacks since this may modify the ESP-NOW transmission parameters during ongoing transmissions! Aborting."); + } + + _useEncryptedMessages = useEncryptedMessages; +} +bool EspnowMeshBackend::useEncryptedMessages() { return _useEncryptedMessages; } + bool EspnowMeshBackend::verifyPeerSessionKey(uint64_t sessionKey, const uint8_t *peerMac, char messageType) { if(EncryptedConnectionLog *encryptedConnection = getEncryptedConnection(peerMac)) @@ -1375,16 +1472,27 @@ const uint8_t *EspnowMeshBackend::getScheduledResponseRecipient(uint32_t respons return getScheduledResponse(responseIndex)->getRecipientMac(); } +uint32_t EspnowMeshBackend::numberOfScheduledResponses() {return responsesToSend.size();} + void EspnowMeshBackend::clearAllScheduledResponses() { + MutexTracker responsesToSendMutexTracker(_responsesToSendMutex); + if(!responsesToSendMutexTracker.mutexCaptured()) + { + assert(false && "ERROR! responsesToSend locked. Don't call clearAllScheduledResponses from callbacks as this may corrupt program state! Aborting."); + } + responsesToSend.clear(); } -uint32_t EspnowMeshBackend::numberOfScheduledResponses() {return responsesToSend.size();} - - void EspnowMeshBackend::deleteScheduledResponsesByRecipient(const uint8_t *recipientMac, bool encryptedOnly) { + MutexTracker responsesToSendMutexTracker(_responsesToSendMutex); + if(!responsesToSendMutexTracker.mutexCaptured()) + { + assert(false && "ERROR! responsesToSend locked. Don't call deleteScheduledResponsesByRecipient from callbacks as this may corrupt program state! Aborting."); + } + for(auto responseIterator = responsesToSend.begin(); responseIterator != responsesToSend.end(); ) { if(macEqual(responseIterator->getRecipientMac(), recipientMac) && (!encryptedOnly || EspnowProtocolInterpreter::usesEncryption(responseIterator->getRequestID()))) @@ -1420,19 +1528,19 @@ uint8_t *EspnowMeshBackend::getSenderAPMac(uint8_t *macArray) return macArray; } -void EspnowMeshBackend::setReceivedEncryptedMessage(bool receivedEncryptedMessage) { _receivedEncryptedMessage = receivedEncryptedMessage; } -bool EspnowMeshBackend::receivedEncryptedMessage() {return _receivedEncryptedMessage;} +void EspnowMeshBackend::setReceivedEncryptedTransmission(bool receivedEncryptedTransmission) { _receivedEncryptedTransmission = receivedEncryptedTransmission; } +bool EspnowMeshBackend::receivedEncryptedTransmission() {return _receivedEncryptedTransmission;} bool EspnowMeshBackend::addUnencryptedConnection(const String &serializedConnectionState) { - return JsonTranslator::getUnencryptedMessageID(serializedConnectionState, _unencryptedMessageID); + return JsonTranslator::getUnsynchronizedMessageID(serializedConnectionState, _unsynchronizedMessageID); } encrypted_connection_status_t EspnowMeshBackend::addEncryptedConnection(uint8_t *peerStaMac, uint8_t *peerApMac, uint64_t peerSessionKey, uint64_t ownSessionKey) { assert(encryptedConnections.size() <= maxEncryptedConnections); // If this is not the case, ESP-NOW is no longer in sync with the library - uint8_t encryptionKeyArray[espnowEncryptionKeyLength] = { 0 }; + uint8_t encryptionKeyArray[espnowEncryptedConnectionKeyLength] = { 0 }; if(EncryptedConnectionLog *encryptedConnection = getEncryptedConnection(peerStaMac)) { @@ -1440,7 +1548,7 @@ encrypted_connection_status_t EspnowMeshBackend::addEncryptedConnection(uint8_t temporaryEncryptedConnectionToPermanent(peerStaMac); encryptedConnection->setPeerSessionKey(peerSessionKey); encryptedConnection->setOwnSessionKey(ownSessionKey); - esp_now_set_peer_key(peerStaMac, getEspnowEncryptionKey(encryptionKeyArray), espnowEncryptionKeyLength); + esp_now_set_peer_key(peerStaMac, getEspnowEncryptedConnectionKey(encryptionKeyArray), espnowEncryptedConnectionKeyLength); encryptedConnection->setHashKey(getEspnowHashKey()); return ECS_CONNECTION_ESTABLISHED; @@ -1453,7 +1561,7 @@ encrypted_connection_status_t EspnowMeshBackend::addEncryptedConnection(uint8_t } // returns 0 on success: int esp_now_add_peer(u8 *mac_addr, u8 role, u8 channel, u8 *key, u8 key_len) // Only MAC, encryption key and key length (16) actually matter. The rest is not used by ESP-NOW. - else if(0 == esp_now_add_peer(peerStaMac, ESP_NOW_ROLE_CONTROLLER, getWiFiChannel(), getEspnowEncryptionKey(encryptionKeyArray), espnowEncryptionKeyLength)) + else if(0 == esp_now_add_peer(peerStaMac, ESP_NOW_ROLE_CONTROLLER, getWiFiChannel(), getEspnowEncryptedConnectionKey(encryptionKeyArray), espnowEncryptedConnectionKeyLength)) { encryptedConnections.emplace_back(peerStaMac, peerApMac, peerSessionKey, ownSessionKey, getEspnowHashKey()); return ECS_CONNECTION_ESTABLISHED; @@ -1506,7 +1614,7 @@ encrypted_connection_status_t EspnowMeshBackend::addTemporaryEncryptedConnection { assert(encryptedConnections.size() <= maxEncryptedConnections); // If this is not the case, ESP-NOW is no longer in sync with the library - uint8_t encryptionKeyArray[espnowEncryptionKeyLength] = { 0 }; + uint8_t encryptionKeyArray[espnowEncryptedConnectionKeyLength] = { 0 }; connectionLogIterator encryptedConnection = connectionLogEndIterator(); @@ -1515,7 +1623,7 @@ encrypted_connection_status_t EspnowMeshBackend::addTemporaryEncryptedConnection // There is already an encrypted connection to this mac, so no need to replace it, just updating is enough. encryptedConnection->setPeerSessionKey(peerSessionKey); encryptedConnection->setOwnSessionKey(ownSessionKey); - esp_now_set_peer_key(peerStaMac, getEspnowEncryptionKey(encryptionKeyArray), espnowEncryptionKeyLength); + esp_now_set_peer_key(peerStaMac, getEspnowEncryptedConnectionKey(encryptionKeyArray), espnowEncryptedConnectionKeyLength); encryptedConnection->setHashKey(getEspnowHashKey()); if(encryptedConnection->temporary()) @@ -1935,8 +2043,8 @@ encrypted_connection_removal_outcome_t EspnowMeshBackend::requestEncryptedConnec } } -void EspnowMeshBackend::setAcceptsUnencryptedRequests(bool acceptsUnencryptedRequests) { _acceptsUnencryptedRequests = acceptsUnencryptedRequests; } -bool EspnowMeshBackend::acceptsUnencryptedRequests() { return _acceptsUnencryptedRequests; } +void EspnowMeshBackend::setAcceptsUnverifiedRequests(bool acceptsUnverifiedRequests) { _acceptsUnverifiedRequests = acceptsUnverifiedRequests; } +bool EspnowMeshBackend::acceptsUnverifiedRequests() { return _acceptsUnverifiedRequests; } void EspnowMeshBackend::setEncryptedConnectionsSoftLimit(uint8_t softLimit) { @@ -2269,6 +2377,9 @@ void EspnowMeshBackend::broadcast(const String &message) void EspnowMeshBackend::setBroadcastTransmissionRedundancy(uint8_t redundancy) { _broadcastTransmissionRedundancy = redundancy; } uint8_t EspnowMeshBackend::getBroadcastTransmissionRedundancy() { return _broadcastTransmissionRedundancy; } +void EspnowMeshBackend::setResponseTransmittedHook(responseTransmittedHookType responseTransmittedHook) { _responseTransmittedHook = responseTransmittedHook; } +EspnowMeshBackend::responseTransmittedHookType EspnowMeshBackend::getResponseTransmittedHook() { return _responseTransmittedHook; } + void EspnowMeshBackend::sendStoredEspnowMessages(const ExpiringTimeTracker *estimatedMaxDurationTracker) { sendPeerRequestConfirmations(estimatedMaxDurationTracker); @@ -2320,8 +2431,6 @@ void EspnowMeshBackend::sendPeerRequestConfirmations(const ExpiringTimeTracker * // Note that callbacks can be called during delay time, so it is possible to receive a transmission during espnowSendToNode // (which may add an element to the peerRequestConfirmationsToSend list). - staticVerboseModePrint("Responding to encrypted connection request from MAC " + macToString(defaultBSSID)); - if(!existingEncryptedConnection && ((reciprocalPeerRequest && encryptedConnections.size() >= maxEncryptedConnections) || (!reciprocalPeerRequest && reservedEncryptedConnections() >= maxEncryptedConnections))) { @@ -2421,8 +2530,15 @@ void EspnowMeshBackend::sendPeerRequestConfirmations(const ExpiringTimeTracker * void EspnowMeshBackend::sendEspnowResponses(const ExpiringTimeTracker *estimatedMaxDurationTracker) { uint32_t bufferedCriticalHeapLevel = criticalHeapLevel() + criticalHeapLevelBuffer(); // We preferably want to start clearing the logs a bit before things get critical. - - for(std::list::iterator responseIterator = responsesToSend.begin(); responseIterator != responsesToSend.end(); ) + + MutexTracker responsesToSendMutexTracker(_responsesToSendMutex); + if(!responsesToSendMutexTracker.mutexCaptured()) + { + assert(false && "ERROR! responsesToSend locked. Don't call sendEspnowResponses from callbacks as this may corrupt program state! Aborting."); + } + + uint32_t responseIndex = 0; + for(std::list::iterator responseIterator = responsesToSend.begin(); responseIterator != responsesToSend.end(); ++responseIndex) { if(responseIterator->timeSinceCreation() > logEntryLifetimeMs()) { @@ -2433,12 +2549,17 @@ void EspnowMeshBackend::sendEspnowResponses(const ExpiringTimeTracker *estimated continue; } + bool hookOutcome = true; // Note that callbacks can be called during delay time, so it is possible to receive a transmission during espnowSendToNode // (which may add an element to the responsesToSend list). if(espnowSendToNodeUnsynchronized(responseIterator->getMessage(), responseIterator->getRecipientMac(), 'A', responseIterator->getRequestID()) == TS_TRANSMISSION_COMPLETE) - { + { + if(EspnowMeshBackend *currentEspnowRequestManager = getEspnowRequestManager()) + hookOutcome = currentEspnowRequestManager->getResponseTransmittedHook()(responseIterator->getMessage(), responseIterator->getRecipientMac(), responseIndex, *currentEspnowRequestManager); + responseIterator = responsesToSend.erase(responseIterator); + --responseIndex; } else { @@ -2454,7 +2575,7 @@ void EspnowMeshBackend::sendEspnowResponses(const ExpiringTimeTracker *estimated return; // responseIterator may be invalid now. Also, we should give the main loop a chance to respond to the situation. } - if(estimatedMaxDurationTracker && estimatedMaxDurationTracker->expired()) + if(!hookOutcome || (estimatedMaxDurationTracker && estimatedMaxDurationTracker->expired())) return; } } @@ -2466,7 +2587,8 @@ uint32_t EspnowMeshBackend::getMaxBytesPerTransmission() uint32_t EspnowMeshBackend::getMaxMessageBytesPerTransmission() { - return getMaxBytesPerTransmission() - EspnowProtocolInterpreter::espnowProtocolBytesSize(); + using namespace EspnowProtocolInterpreter; + return getMaxBytesPerTransmission() - espnowMetadataSize(); } void EspnowMeshBackend::setMaxTransmissionsPerMessage(uint8_t maxTransmissionsPerMessage) @@ -2560,9 +2682,9 @@ String EspnowMeshBackend::serializeUnencryptedConnection() { using namespace JsonTranslator; - // Returns: {"connectionState":{"unencMsgID":"123"}} + // Returns: {"connectionState":{"unsyncMsgID":"123"}} - return jsonConnectionState + createJsonEndPair(jsonUnencryptedMessageID, String(_unencryptedMessageID)); + return jsonConnectionState + createJsonEndPair(jsonUnsynchronizedMessageID, String(_unsynchronizedMessageID)); } String EspnowMeshBackend::serializeEncryptedConnection(const uint8_t *peerMac) diff --git a/libraries/ESP8266WiFiMesh/src/EspnowMeshBackend.h b/libraries/ESP8266WiFiMesh/src/EspnowMeshBackend.h index def9e85773..623ec87163 100644 --- a/libraries/ESP8266WiFiMesh/src/EspnowMeshBackend.h +++ b/libraries/ESP8266WiFiMesh/src/EspnowMeshBackend.h @@ -42,7 +42,7 @@ * This enables flexible easy-to-use encrypted ESP-NOW communication. 'P' and 'C' messages can be encrypted. * The encryption pairing process works as follows (from top to bottom): * - * Encryption pairing process, schematic overview: + * Encrypted connection pairing process, schematic overview: * * Connection | Peer sends ('C'): | Peer requester sends ('P'): | Connection * encrypted: | | | encrypted: @@ -66,6 +66,13 @@ * Messages of type 'A' and 'C' are response types, and thus use the same session key as the corresponding 'R' and 'P' message they are responding to. * This means they can never cause a desynchronization to occur, and therefore they do not trigger 'S' messages. * + * In addition to using encrypted ESP-NOW connections the framework can also send automatically encrypted messages (AEAD) over both encrypted and unencrypted connections. + * Using AEAD will only encrypt the message content, not the transmission metadata. + * The AEAD encryption does not require any pairing, and is thus faster for single messages than establishing a new encrypted connection before transfer. + * AEAD encryption also works with ESP-NOW broadcasts and supports an unlimited number of nodes, which is not true for encrypted connections. + * Encrypted ESP-NOW connections do however come with built in replay attack protection, which is not provided by the framework when using AEAD encryption, + * and allow EspnowProtocolInterpreter::aeadMetadataSize extra message bytes per transmission. + * Transmissions via encrypted connections are also slightly faster than via AEAD once a connection has been established. */ #ifndef __ESPNOWMESHBACKEND_H__ @@ -81,6 +88,7 @@ #include #include #include "EspnowNetworkInfo.h" +#include "CryptoInterface.h" typedef enum { @@ -127,6 +135,7 @@ class EspnowMeshBackend : public MeshBackendBase { protected: typedef std::function broadcastFilterType; + typedef std::function responseTransmittedHookType; public: @@ -140,7 +149,7 @@ class EspnowMeshBackend : public MeshBackendBase { * @param networkFilter The callback handler for deciding which WiFi networks to connect to. * @param broadcastFilter The callback handler for deciding which ESP-NOW broadcasts to accept. * @param meshPassword The WiFi password for the mesh network. - * @param espnowEncryptionKey An uint8_t array containing the key used by this EspnowMeshBackend instance for creating encrypted ESP-NOW connections. + * @param espnowEncryptedConnectionKey An uint8_t array containing the secret key used by this EspnowMeshBackend instance for creating encrypted ESP-NOW connections. * @param espnowHashKey An uint8_t array containing the secret key used by this EspnowMeshBackend to generate HMACs for encrypted ESP-NOW connections. * @param ssidPrefix The prefix (first part) of the node SSID. * @param ssidSuffix The suffix (last part) of the node SSID. @@ -154,10 +163,37 @@ class EspnowMeshBackend : public MeshBackendBase { * */ EspnowMeshBackend(requestHandlerType requestHandler, responseHandlerType responseHandler, networkFilterType networkFilter, broadcastFilterType broadcastFilter, - const String &meshPassword, const uint8_t espnowEncryptionKey[EspnowProtocolInterpreter::espnowEncryptionKeyLength], + const String &meshPassword, const uint8_t espnowEncryptedConnectionKey[EspnowProtocolInterpreter::espnowEncryptedConnectionKeyLength], const uint8_t espnowHashKey[EspnowProtocolInterpreter::espnowHashKeyLength], const String &ssidPrefix, const String &ssidSuffix, bool verboseMode = false, uint8 meshWiFiChannel = 1); + /** + * ESP-NOW constructor method. Creates an ESP-NOW node, ready to be initialised. + * + * @param requestHandler The callback handler for dealing with received requests. Takes a string as an argument which + * is the request string received from another node and returns the string to send back. + * @param responseHandler The callback handler for dealing with received responses. Takes a string as an argument which + * is the response string received from another node. Returns a transmission status code as a transmission_status_t. + * @param networkFilter The callback handler for deciding which WiFi networks to connect to. + * @param broadcastFilter The callback handler for deciding which ESP-NOW broadcasts to accept. + * @param meshPassword The WiFi password for the mesh network. + * @param espnowEncryptedConnectionKeySeed A string containing the seed that will generate the secret key used by this EspnowMeshBackend instance for creating encrypted ESP-NOW connections. + * @param espnowHashKeySeed A string containing the seed that will generate the secret key used by this EspnowMeshBackend to generate HMACs for encrypted ESP-NOW connections. + * @param ssidPrefix The prefix (first part) of the node SSID. + * @param ssidSuffix The suffix (last part) of the node SSID. + * @param verboseMode Determines if we should print the events occurring in the library to Serial. Off by default. This setting is shared by all EspnowMeshBackend instances. + * @param meshWiFiChannel The WiFi channel used by the mesh network. Valid values are integers from 1 to 13. Defaults to 1. + * WARNING: The ESP8266 has only one WiFi channel, and the the station/client mode is always prioritized for channel selection. + * This can cause problems if several mesh instances exist on the same ESP8266 and use different WiFi channels. + * In such a case, whenever the station of one mesh instance connects to an AP, it will silently force the + * WiFi channel of any active AP on the ESP8266 to match that of the station. This will cause disconnects and possibly + * make it impossible for other stations to detect the APs whose WiFi channels have changed. + * + */ + EspnowMeshBackend(requestHandlerType requestHandler, responseHandlerType responseHandler, networkFilterType networkFilter, broadcastFilterType broadcastFilter, + const String &meshPassword, const String &espnowEncryptedConnectionKeySeed, const String &espnowHashKeySeed, const String &ssidPrefix, + const String &ssidSuffix, bool verboseMode = false, uint8 meshWiFiChannel = 1); + ~EspnowMeshBackend() override; /** @@ -327,20 +363,50 @@ class EspnowMeshBackend : public MeshBackendBase { * * NOTE: Encrypted connections added before the encryption key change will retain their old encryption key. * Only changes the encryption key used by this EspnowMeshBackend instance, so each instance can use a separate key. - * Both Kok and encryption key must match in an encrypted connection pair for encrypted communication to be possible. + * Both Kok and encrypted connection key must match in an encrypted connection pair for encrypted communication to be possible. + * Otherwise the transmissions will never reach the recipient, even though acks are received by the sender. + * + * @param espnowEncryptedConnectionKey An array containing the espnowEncryptedConnectionKeyLength bytes that will be used as the encryption key. + */ + void setEspnowEncryptedConnectionKey(const uint8_t espnowEncryptedConnectionKey[EspnowProtocolInterpreter::espnowEncryptedConnectionKeyLength]); + + /** + * 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 encrypted connections requested or added by this EspnowMeshBackend instance. + * + * NOTE: Encrypted connections added before the encryption key change will retain their old encryption key. + * Only changes the encryption key used by this EspnowMeshBackend instance, so each instance can use a separate key. + * Both Kok and encrypted connection key must match in an encrypted connection pair for encrypted communication to be possible. * Otherwise the transmissions will never reach the recipient, even though acks are received by the sender. * - * @param espnowEncryptionKey An array containing the espnowEncryptionKeyLength bytes that will be used as the encryption key. + * @param espnowHashKeySeed A string that will be used to generate the encryption key. The same string will always generate the same key. + * A minimum of 8 random characters are recommended to ensure sufficient key variation. */ - void setEspnowEncryptionKey(const uint8_t espnowEncryptionKey[EspnowProtocolInterpreter::espnowEncryptionKeyLength]); + void setEspnowEncryptedConnectionKey(const String &espnowEncryptedConnectionKeySeed); /** * Get the encryption key used by this EspnowMeshBackend instance for creating encrypted ESP-NOW connections. * - * @return The current espnowEncryptionKey for this EspnowMeshBackend instance. + * @return The current espnowEncryptedConnectionKey for this EspnowMeshBackend instance. + */ + const uint8_t *getEspnowEncryptedConnectionKey(); + uint8_t *getEspnowEncryptedConnectionKey(uint8_t resultArray[EspnowProtocolInterpreter::espnowEncryptedConnectionKeyLength]); + + /** + * Change the key used to encrypt/decrypt the encrypted connection key when creating encrypted ESP-NOW connections. (Kok = key of keys, perhaps) If no Kok is provided by the user, a default Kok is used. + * Will apply to any new encrypted connections. + * Must be called after begin() to take effect. + * + * NOTE: Encrypted connections added before the Kok change will retain their old Kok. + * This changes the Kok for all EspnowMeshBackend instances on this ESP8266. + * Both Kok and encrypted connection key must match in an encrypted connection pair for encrypted communication to be possible. + * Otherwise the transmissions will never reach the recipient, even though acks are received by the sender. + * + * @param espnowEncryptionKok An array containing the espnowEncryptedConnectionKeyLength bytes that will be used as the Kok. + * @return True if Kok was changed successfully. False if Kok was not changed. */ - const uint8_t *getEspnowEncryptionKey(); - uint8_t *getEspnowEncryptionKey(uint8_t resultArray[EspnowProtocolInterpreter::espnowEncryptionKeyLength]); + static bool setEspnowEncryptionKok(uint8_t espnowEncryptionKok[EspnowProtocolInterpreter::espnowEncryptedConnectionKeyLength]); /** * Change the key used to encrypt/decrypt the encryption key when creating encrypted ESP-NOW connections. (Kok = key of keys, perhaps) If no Kok is provided by the user, a default Kok is used. @@ -349,13 +415,14 @@ class EspnowMeshBackend : public MeshBackendBase { * * NOTE: Encrypted connections added before the Kok change will retain their old Kok. * This changes the Kok for all EspnowMeshBackend instances on this ESP8266. - * Both Kok and encryption key must match in an encrypted connection pair for encrypted communication to be possible. + * Both Kok and encrypted connection key must match in an encrypted connection pair for encrypted communication to be possible. * Otherwise the transmissions will never reach the recipient, even though acks are received by the sender. * - * @param espnowEncryptionKok An array containing the espnowEncryptionKeyLength bytes that will be used as the Kok. + * @param espnowEncryptionKokSeed A string that will be used to generate the KoK. The same string will always generate the same KoK. + * A minimum of 8 random characters are recommended to ensure sufficient KoK variation. * @return True if Kok was changed successfully. False if Kok was not changed. */ - static bool setEspnowEncryptionKok(uint8_t espnowEncryptionKok[EspnowProtocolInterpreter::espnowEncryptionKeyLength]); + static bool setEspnowEncryptionKok(const String &espnowEncryptionKokSeed); /** * Get the key used to encrypt the encryption keys when creating encrypted ESP-NOW connections. (Kok = key of keys, perhaps) Returns nullptr if no Kok has been provided by the user. @@ -364,7 +431,7 @@ class EspnowMeshBackend : public MeshBackendBase { */ static const uint8_t *getEspnowEncryptionKok(); - /** + /** * Change the secret key used to generate HMACs for 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 encrypted connections requested or added by this EspnowMeshBackend instance. @@ -375,12 +442,75 @@ class EspnowMeshBackend : public MeshBackendBase { * @param espnowHashKey An array containing the espnowHashKeyLength bytes that will be used as the HMAC key. */ void setEspnowHashKey(const uint8_t espnowHashKey[EspnowProtocolInterpreter::espnowHashKeyLength]); + + /** + * Change the secret key used to generate HMACs for 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 encrypted connections requested or added by this EspnowMeshBackend instance. + * + * NOTE: Encrypted connections added before the key change will retain their old key. + * Only changes the secret hash key used by this EspnowMeshBackend instance, so each instance can use a separate secret key. + * + * @param espnowHashKeySeed A string that will be used to generate the HMAC key. The same string will always generate the same key. + * A minimum of 8 random characters are recommended to ensure sufficient key variation. + */ + void setEspnowHashKey(const String &espnowHashKeySeed); + const uint8_t *getEspnowHashKey(); + + /** + * Change the key used to encrypt/decrypt messages when using AEAD encryption. + * If no message encryption key is provided by the user, a default key consisting of all zeroes is used. + * + * This changes the message encryption key for all EspnowMeshBackend instances on this ESP8266. + * + * @param espnowMessageEncryptionKey An array containing the CryptoInterface::ENCRYPTION_KEY_LENGTH bytes that will be used as the message encryption key. + */ + static void setEspnowMessageEncryptionKey(uint8_t espnowMessageEncryptionKey[CryptoInterface::ENCRYPTION_KEY_LENGTH]); + + /** + * Change the key used to encrypt/decrypt messages when using AEAD encryption. + * If no message encryption key is provided by the user, a default key consisting of all zeroes is used. + * + * This changes the message encryption key for all EspnowMeshBackend instances on this ESP8266. + * + * @param espnowMessageEncryptionKeySeed A string that will be used to generate the message encryption key. The same string will always generate the same key. + * A minimum of 8 random characters are recommended to ensure sufficient key variation. + */ + static void setEspnowMessageEncryptionKey(const String &espnowMessageEncryptionKeySeed); + + /** + * Get the key used to encrypt/decrypt messages when using AEAD encryption. + * + * @return An uint8_t array with size CryptoInterface::ENCRYPTION_KEY_LENGTH containing the currently used message encryption key. + */ + static const uint8_t *getEspnowMessageEncryptionKey(); + + /** + * If true, AEAD will be used to encrypt/decrypt all messages sent/received by this node via ESP-NOW, regardless of whether the connection is encrypted or not. + * All nodes this node wishes to communicate with must then also use encrypted messages with the same getEspnowMessageEncryptionKey(), or messages will not be accepted. + * Note that using encrypted messages will reduce the number of message bytes that can be transmitted. + * + * Using AEAD will only encrypt the message content, not the transmission metadata. + * The AEAD encryption does not require any pairing, and is thus faster for single messages than establishing a new encrypted connection before transfer. + * AEAD encryption also works with ESP-NOW broadcasts and supports an unlimited number of nodes, which is not true for encrypted connections. + * Encrypted ESP-NOW connections do however come with built in replay attack protection, which is not provided by the framework when using AEAD encryption, + * and allow EspnowProtocolInterpreter::aeadMetadataSize extra message bytes per transmission. + * Transmissions via encrypted connections are also slightly faster than via AEAD once a connection has been established. + * + * useEncryptedMessages() is false by default. + * + * @param useEncryptedMessages If true, AEAD encryption/decryption is enabled. If false, AEAD encryption/decryption is disabled. + */ + static void setUseEncryptedMessages(bool useEncryptedMessages); + static bool useEncryptedMessages(); /** * Hint: Use String.length() to get the ASCII length of a String. * - * @return The maximum number of bytes (or ASCII characters) a transmission can contain. Note that non-ASCII characters usually require the space of at least two ASCII characters each. + * @return The maximum number of bytes (or ASCII characters) a transmission can contain. + * Note that non-ASCII characters usually require the space of at least two ASCII characters each. + * Also note that this value will be reduced by EspnowProtocolInterpreter::aeadMetadataSize if useEncryptedMessages() is true. */ static uint32_t getMaxMessageBytesPerTransmission(); @@ -402,7 +532,9 @@ class EspnowMeshBackend : public MeshBackendBase { /** * Hint: Use String.length() to get the ASCII length of a String. * - * @return The maximum length in bytes an ASCII message is allowed to be when transmitted/broadcasted by this node. Note that non-ASCII characters usually require at least two bytes each. + * @return The maximum length in bytes an ASCII message is allowed to be when transmitted/broadcasted by this node. + * Note that non-ASCII characters usually require at least two bytes each. + * Also note that this value will be reduced if useEncryptedMessages() is true. */ static uint32_t getMaxMessageLength(); @@ -506,6 +638,19 @@ class EspnowMeshBackend : public MeshBackendBase { void setBroadcastFilter(broadcastFilterType broadcastFilter); broadcastFilterType getBroadcastFilter(); + + /** + * Set a function that should be called after each successful ESP-NOW response transmission, just before the response is removed from the waiting list. + * If a particular response is not sent, there will be no function call for it. + * Only the hook of the EspnowMeshBackend instance that is getEspnowRequestManager() will be called. + * + * The hook should return a bool. + * If this return value is true, the response transmission process will continue with the next response in the waiting list. + * If it is false, the response transmission process will stop after removing the just sent response from the waiting list. + * The default responseTransmittedHook always returns true. + */ + void setResponseTransmittedHook(responseTransmittedHookType responseTransmittedHook); + responseTransmittedHookType getResponseTransmittedHook(); /** * Get the MAC address of the sender of the most recently received ESP-NOW request, response or broadcast to this EspnowMeshBackend instance. @@ -546,11 +691,11 @@ class EspnowMeshBackend : public MeshBackendBase { uint8_t *getSenderAPMac(uint8_t *macArray); /** - * Get whether the ESP-NOW request, response or broadcast which was most recently received by this EspnowMeshBackend instance was encrypted or not. + * Get whether the ESP-NOW request, response or broadcast which was most recently received by this EspnowMeshBackend instance was sent over an encrypted connection or not. * - * @return If true, the request, response or broadcast was encrypted. If false, it was unencrypted. + * @return If true, the request, response or broadcast was sent over an encrypted connection. If false, the connection was unencrypted. */ - bool receivedEncryptedMessage(); + bool receivedEncryptedTransmission(); /** * Should be used together with serializeUnencryptedConnection() if the node sends unencrypted transmissions @@ -563,10 +708,10 @@ class EspnowMeshBackend : public MeshBackendBase { */ static bool addUnencryptedConnection(const String &serializedConnectionState); - // Updates connection with current stored encryption key. + // Updates connection with current stored encrypted connection key. // At least one of the leftmost 32 bits in each of the session keys should be 1, since the key otherwise indicates the connection is unencrypted. encrypted_connection_status_t addEncryptedConnection(uint8_t *peerStaMac, uint8_t *peerApMac, uint64_t peerSessionKey, uint64_t ownSessionKey); - // Note that the espnowEncryptionKey, espnowEncryptionKok and espnowHashKey are not serialized. + // Note that the espnowEncryptedConnectionKey, espnowEncryptionKok, espnowHashKey and espnowMessageEncryptionKey are not serialized. // These will be set to the values of the EspnowMeshBackend instance that is adding the serialized encrypted connection. // @param ignoreDuration Ignores any stored duration serializedConnectionState, guaranteeing that the created connection will be permanent. Returns: ECS_REQUEST_TRANSMISSION_FAILED indicates malformed serializedConnectionState. encrypted_connection_status_t addEncryptedConnection(const String &serializedConnectionState, bool ignoreDuration = false); @@ -575,7 +720,7 @@ class EspnowMeshBackend : public MeshBackendBase { // As with all these methods, changes will only take effect once the requester proves it has the ability to decrypt the session key. // At least one of the leftmost 32 bits in each of the session keys should be 1, since the key otherwise indicates the connection is unencrypted. encrypted_connection_status_t addTemporaryEncryptedConnection(uint8_t *peerStaMac, uint8_t *peerApMac, uint64_t peerSessionKey, uint64_t ownSessionKey, uint32_t duration); - // Note that the espnowEncryptionKey, espnowEncryptionKok and espnowHashKey are not serialized. + // Note that the espnowEncryptedConnectionKey, espnowEncryptionKok, espnowHashKey and espnowMessageEncryptionKey are not serialized. // These will be set to the values of the EspnowMeshBackend instance that is adding the serialized encrypted connection. // Uses duration argument instead of any stored duration in serializedConnectionState. Returns: ECS_REQUEST_TRANSMISSION_FAILED indicates malformed serializedConnectionState. encrypted_connection_status_t addTemporaryEncryptedConnection(const String &serializedConnectionState, uint32_t duration); @@ -593,15 +738,17 @@ class EspnowMeshBackend : public MeshBackendBase { encrypted_connection_removal_outcome_t requestEncryptedConnectionRemoval(uint8_t *peerMac); /** - * Set whether this EspnowMeshBackend instance will accept unencrypted ESP-NOW requests or not, when acting as EspnowRequestManager. + * Set whether this EspnowMeshBackend instance will accept ESP-NOW requests from unencrypted connections or not, when acting as EspnowRequestManager. * When set to false and combined with already existing encrypted connections, this can be used to ensure only encrypted transmissions are processed. - * When set to false it will also make it impossible to send unencrypted requests for encrypted connection to the node, + * When set to false it will also make it impossible to send requests for encrypted connection to the node over an unencrypted connection, * which can be useful if too many such requests could otherwise be expected. * - * @param acceptsUnencryptedRequests If and only if true, unencrypted requests will be processed when this EspnowMeshBackend instance is acting as EspnowRequestManager. True by default. + * True by default. + * + * @param acceptsUnverifiedRequests If and only if true, requests from unencrypted connections will be processed when this EspnowMeshBackend instance is acting as EspnowRequestManager. */ - void setAcceptsUnencryptedRequests(bool acceptsUnencryptedRequests); - bool acceptsUnencryptedRequests(); + void setAcceptsUnverifiedRequests(bool acceptsUnverifiedRequests); + bool acceptsUnverifiedRequests(); /** * Set a soft upper limit on the number of encrypted connections this node can have when receiving encrypted connection requests. @@ -609,12 +756,14 @@ class EspnowMeshBackend : public MeshBackendBase { * Each EspnowMeshBackend instance can have a separate value. The value used is that of the current EspnowRequestManager. * The hard upper limit is 6 encrypted connections, mandated by the ESP-NOW API. * + * Default is 6. + * * When a request for encrypted connection is received from a node to which there is no existing permanent encrypted connection, * and the number of encrypted connections exceeds the soft limit, * this request will automatically be converted to an autoEncryptionRequest. * This means it will be a temporary connection with very short duration (with default framework settings). * - * @param softLimit The new soft limit. Valid values are 0 to 6. Default is 6. + * @param softLimit The new soft limit. Valid values are 0 to 6. */ void setEncryptedConnectionsSoftLimit(uint8_t softLimit); uint8_t encryptedConnectionsSoftLimit(); @@ -749,11 +898,11 @@ class EspnowMeshBackend : public MeshBackendBase { void setSenderAPMac(uint8_t *macArray); /** - * Set whether the most recently received ESP-NOW request, response or broadcast is presented as having been encrypted or not. + * Set whether the most recently received ESP-NOW request, response or broadcast is presented as having been sent over an encrypted connection or not * - * @param receivedEncryptedMessage If true, the request, response or broadcast is presented as having been encrypted. + * @param receivedEncryptedTransmission If true, the request, response or broadcast is presented as having been sent over an encrypted connection. */ - void setReceivedEncryptedMessage(bool receivedEncryptedMessage); + void setReceivedEncryptedTransmission(bool receivedEncryptedTransmission); static bool temporaryEncryptedConnectionToPermanent(uint8_t *peerMac); @@ -767,6 +916,11 @@ class EspnowMeshBackend : public MeshBackendBase { */ static bool _espnowConnectionQueueMutex; + /** + * Will be true when no responsesToSend element should be removed. + */ + static bool _responsesToSendMutex; + /** * Check if there is an ongoing ESP-NOW transmission in the library. Used to avoid interrupting transmissions. * @@ -814,6 +968,9 @@ class EspnowMeshBackend : public MeshBackendBase { private: + EspnowMeshBackend(requestHandlerType requestHandler, responseHandlerType responseHandler, networkFilterType networkFilter, broadcastFilterType broadcastFilter, + const String &meshPassword, const String &ssidPrefix, const String &ssidSuffix, bool verboseMode, uint8 meshWiFiChannel); + typedef std::function encryptionRequestBuilderType; static String defaultEncryptionRequestBuilder(const String &requestHeader, const uint32_t durationMs, const uint8_t *hashKey, const String &requestNonce, const ExpiringTimeTracker &existingTimeTracker); static String flexibleEncryptionRequestBuilder(const uint32_t minDurationMs, const uint8_t *hashKey, const String &requestNonce, const ExpiringTimeTracker &existingTimeTracker); @@ -908,6 +1065,7 @@ class EspnowMeshBackend : public MeshBackendBase { static bool _espnowSendConfirmed; broadcastFilterType _broadcastFilter; + responseTransmittedHookType _responseTransmittedHook = [](const String &, const uint8_t *, uint32_t, EspnowMeshBackend &){ return true; }; uint8_t _broadcastTransmissionRedundancy = 1; @@ -923,17 +1081,19 @@ class EspnowMeshBackend : public MeshBackendBase { static bool usesConstantSessionKey(char messageType); - bool _acceptsUnencryptedRequests = true; + bool _acceptsUnverifiedRequests = true; - uint8_t _espnowEncryptionKey[EspnowProtocolInterpreter::espnowEncryptionKeyLength] {0}; + uint8_t _espnowEncryptedConnectionKey[EspnowProtocolInterpreter::espnowEncryptedConnectionKeyLength] {0}; uint8_t _espnowHashKey[EspnowProtocolInterpreter::espnowHashKeyLength] {0}; - static uint8_t _espnowEncryptionKok[EspnowProtocolInterpreter::espnowEncryptionKeyLength]; + static uint8_t _espnowEncryptionKok[EspnowProtocolInterpreter::espnowEncryptedConnectionKeyLength]; static bool _espnowEncryptionKokSet; - static uint32_t _unencryptedMessageID; + static uint8_t _espnowMessageEncryptionKey[CryptoInterface::ENCRYPTION_KEY_LENGTH]; + static bool _useEncryptedMessages; + static uint32_t _unsynchronizedMessageID; uint8_t _senderMac[6] = {0}; uint8_t _senderAPMac[6] = {0}; - bool _receivedEncryptedMessage = false; + bool _receivedEncryptedTransmission = false; static bool _espnowSendToNodeMutex; static uint8_t _transmissionTargetBSSID[6]; diff --git a/libraries/ESP8266WiFiMesh/src/EspnowProtocolInterpreter.cpp b/libraries/ESP8266WiFiMesh/src/EspnowProtocolInterpreter.cpp index b5b284fa9c..05af40a390 100644 --- a/libraries/ESP8266WiFiMesh/src/EspnowProtocolInterpreter.cpp +++ b/libraries/ESP8266WiFiMesh/src/EspnowProtocolInterpreter.cpp @@ -25,30 +25,27 @@ #include "EspnowProtocolInterpreter.h" #include "TypeConversionFunctions.h" #include +#include "EspnowMeshBackend.h" namespace EspnowProtocolInterpreter -{ - const uint64_t uint64LeftmostBits = 0xFFFFFFFF00000000; - - uint8_t espnowProtocolBytesSize() +{ + uint8_t espnowMetadataSize() { - return 16; + return espnowProtocolBytesSize + (EspnowMeshBackend::useEncryptedMessages() ? aeadMetadataSize : 0); } - String espnowGetMessageContent(uint8_t *transmission, uint8_t transmissionLength) + String espnowGetMessageContent(uint8_t *transmissionDataArray, uint8_t transmissionLength) { - if(transmissionLength < espnowProtocolBytesSize()) - { - return ""; - } - else + String messageContent = emptyString; + + if(transmissionLength >= espnowMetadataSize()) { - // Ensure we have a NULL terminated character array so the String() constructor knows where to stop. - uint8_t bufferedTransmission[transmissionLength + 1]; - std::copy_n(transmission, transmissionLength, bufferedTransmission); - bufferedTransmission[transmissionLength] = 0; - return String((char *)(bufferedTransmission + espnowProtocolBytesSize())); + uint8_t messageSize = transmissionLength - espnowMetadataSize(); + + messageContent = uint8ArrayToMultiString(transmissionDataArray + espnowMetadataSize(), messageSize); } + + return messageContent; } char espnowGetMessageType(const uint8_t *transmissionDataArray) @@ -79,22 +76,12 @@ namespace EspnowProtocolInterpreter uint64_t espnowGetMessageID(const uint8_t *transmissionDataArray) { - uint64_t outcome = 0; - for(int shiftingFortune = 56; shiftingFortune >= 0; shiftingFortune -= 8) - { - outcome |= ((uint64_t)transmissionDataArray[espnowMessageIDIndex + 7 - shiftingFortune/8] << shiftingFortune); - } - - return outcome; + return uint8ArrayToUint64(transmissionDataArray + espnowMessageIDIndex); } uint8_t *espnowSetMessageID(uint8_t *transmissionDataArray, uint64_t messageID) { - for(int shiftingFortune = 56; shiftingFortune >= 0; shiftingFortune -= 8) - { - transmissionDataArray[espnowMessageIDIndex + 7 - shiftingFortune/8] = messageID >> shiftingFortune & 0xFF; - } - return transmissionDataArray; + return uint64ToUint8Array(messageID, transmissionDataArray + espnowMessageIDIndex); } bool usesEncryption(uint64_t messageID) diff --git a/libraries/ESP8266WiFiMesh/src/EspnowProtocolInterpreter.h b/libraries/ESP8266WiFiMesh/src/EspnowProtocolInterpreter.h index 0a5720331b..8d8937e765 100644 --- a/libraries/ESP8266WiFiMesh/src/EspnowProtocolInterpreter.h +++ b/libraries/ESP8266WiFiMesh/src/EspnowProtocolInterpreter.h @@ -54,12 +54,16 @@ namespace EspnowProtocolInterpreter const uint8_t espnowTransmissionMacIndex = 2; const uint8_t espnowMessageIDIndex = 8; - uint8_t espnowProtocolBytesSize(); + constexpr uint8_t espnowProtocolBytesSize = 16; + constexpr uint8_t aeadMetadataSize = 28; + uint8_t espnowMetadataSize(); - const uint8_t espnowEncryptionKeyLength = 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 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. - String espnowGetMessageContent(uint8_t *transmission, uint8_t transmissionLength); + constexpr uint64_t uint64LeftmostBits = 0xFFFFFFFF00000000; + + String espnowGetMessageContent(uint8_t *transmissionDataArray, uint8_t transmissionLength); char espnowGetMessageType(const uint8_t *transmissionDataArray); uint8_t espnowGetTransmissionsRemaining(const uint8_t *transmissionDataArray); bool espnowIsMessageStart(const uint8_t *transmissionDataArray); diff --git a/libraries/ESP8266WiFiMesh/src/FloodingMesh.cpp b/libraries/ESP8266WiFiMesh/src/FloodingMesh.cpp index ab4dd4e108..be011d09b3 100644 --- a/libraries/ESP8266WiFiMesh/src/FloodingMesh.cpp +++ b/libraries/ESP8266WiFiMesh/src/FloodingMesh.cpp @@ -41,7 +41,7 @@ void floodingMeshDelay(uint32_t durationMs) } } -FloodingMesh::FloodingMesh(messageHandlerType messageHandler, const String &meshPassword, const uint8_t espnowEncryptionKey[EspnowProtocolInterpreter::espnowEncryptionKeyLength], +FloodingMesh::FloodingMesh(messageHandlerType messageHandler, const String &meshPassword, const uint8_t espnowEncryptedConnectionKey[EspnowProtocolInterpreter::espnowEncryptedConnectionKeyLength], const uint8_t espnowHashKey[EspnowProtocolInterpreter::espnowHashKeyLength], const String &ssidPrefix, const String &ssidSuffix, bool verboseMode, uint8 meshWiFiChannel) : _espnowBackend( @@ -49,17 +49,35 @@ FloodingMesh::FloodingMesh(messageHandlerType messageHandler, const String &mesh [this](const String &response, MeshBackendBase &meshInstance){ return _defaultResponseHandler(response, meshInstance); }, [this](int numberOfNetworks, MeshBackendBase &meshInstance){ return _defaultNetworkFilter(numberOfNetworks, meshInstance); }, [this](String &firstTransmission, EspnowMeshBackend &meshInstance){ return _defaultBroadcastFilter(firstTransmission, meshInstance); }, - meshPassword, espnowEncryptionKey, espnowHashKey, ssidPrefix, ssidSuffix, verboseMode, meshWiFiChannel) + meshPassword, espnowEncryptedConnectionKey, espnowHashKey, ssidPrefix, ssidSuffix, verboseMode, meshWiFiChannel) { setMessageHandler(messageHandler); restoreDefaultTransmissionOutcomesUpdateHook(); + restoreDefaultResponseTransmittedHook(); +} + +FloodingMesh::FloodingMesh(messageHandlerType messageHandler, const String &meshPassword, const String &espnowEncryptedConnectionKeySeed, const String &espnowHashKeySeed, + const String &ssidPrefix, const String &ssidSuffix, bool verboseMode, uint8 meshWiFiChannel) + : FloodingMesh(messageHandler, meshPassword, (const uint8_t[EspnowProtocolInterpreter::espnowEncryptedConnectionKeyLength]){0}, + (const uint8_t[EspnowProtocolInterpreter::espnowHashKeyLength]){0}, ssidPrefix, ssidSuffix, verboseMode, meshWiFiChannel) +{ + getEspnowMeshBackend().setEspnowEncryptedConnectionKey(espnowEncryptedConnectionKeySeed); + getEspnowMeshBackend().setEspnowHashKey(espnowHashKeySeed); } FloodingMesh::FloodingMesh(const String &serializedMeshState, messageHandlerType messageHandler, const String &meshPassword, - const uint8_t espnowEncryptionKey[EspnowProtocolInterpreter::espnowEncryptionKeyLength], + const uint8_t espnowEncryptedConnectionKey[EspnowProtocolInterpreter::espnowEncryptedConnectionKeyLength], const uint8_t espnowHashKey[EspnowProtocolInterpreter::espnowHashKeyLength], const String &ssidPrefix, const String &ssidSuffix, bool verboseMode, uint8 meshWiFiChannel) - : FloodingMesh(messageHandler, meshPassword, espnowEncryptionKey, espnowHashKey, ssidPrefix, ssidSuffix, verboseMode, meshWiFiChannel) + : FloodingMesh(messageHandler, meshPassword, espnowEncryptedConnectionKey, espnowHashKey, ssidPrefix, ssidSuffix, verboseMode, meshWiFiChannel) +{ + loadMeshState(serializedMeshState); +} + +FloodingMesh::FloodingMesh(const String &serializedMeshState, messageHandlerType messageHandler, const String &meshPassword, + const String &espnowEncryptedConnectionKeySeed, const String &espnowHashKeySeed, const String &ssidPrefix, + const String &ssidSuffix, bool verboseMode, uint8 meshWiFiChannel) + : FloodingMesh(messageHandler, meshPassword, espnowEncryptedConnectionKeySeed, espnowHashKeySeed, ssidPrefix, ssidSuffix, verboseMode, meshWiFiChannel) { loadMeshState(serializedMeshState); } @@ -73,6 +91,9 @@ void FloodingMesh::begin() { // Initialise the mesh node getEspnowMeshBackend().begin(); + + // Used for encrypted broadcasts + getEspnowMeshBackend().setEncryptedConnectionsSoftLimit(3); availableFloodingMeshes.insert(this); // Returns std::pair } @@ -118,7 +139,7 @@ String FloodingMesh::serializeMeshState() { using namespace JsonTranslator; - // Returns: {"meshState":{"connectionState":{"unencMsgID":"123"},"meshMsgCount":"123"}} + // Returns: {"meshState":{"connectionState":{"unsyncMsgID":"123"},"meshMsgCount":"123"}} String connectionState = getEspnowMeshBackend().serializeUnencryptedConnection(); @@ -138,21 +159,21 @@ void FloodingMesh::loadMeshState(const String &serializedMeshState) String connectionState = ""; if(!getConnectionState(serializedMeshState, connectionState) || !getEspnowMeshBackend().addUnencryptedConnection(connectionState)) { - getEspnowMeshBackend().warningPrint("WARNING! serializedMeshState did not contain unencryptedMessageID. Using default instead."); + getEspnowMeshBackend().warningPrint("WARNING! serializedMeshState did not contain unsynchronizedMessageID. Using default instead."); } } String FloodingMesh::generateMessageID() { - char messageCountArray[2] = { 0 }; - sprintf(messageCountArray, "%04X", _messageCount++); + char messageCountArray[5] = { 0 }; + snprintf(messageCountArray, 5, "%04X", _messageCount++); uint8_t apMac[6] {0}; return macToString(WiFi.softAPmacAddress(apMac)) + String(messageCountArray); // We use the AP MAC address as ID since it is what shows up during WiFi scans } void FloodingMesh::broadcast(const String &message) { - assert(message.length() <= maxUnencryptedMessageSize()); + assert(message.length() <= maxUnencryptedMessageLength()); String messageID = generateMessageID(); @@ -176,7 +197,7 @@ uint8_t FloodingMesh::getBroadcastReceptionRedundancy() { return _broadcastRecep void FloodingMesh::encryptedBroadcast(const String &message) { - assert(message.length() <= maxEncryptedMessageSize()); + assert(message.length() <= maxEncryptedMessageLength()); String messageID = generateMessageID(); @@ -185,7 +206,7 @@ void FloodingMesh::encryptedBroadcast(const String &message) void FloodingMesh::encryptedBroadcastKernel(const String &message) { - getEspnowMeshBackend().attemptAutoEncryptingTransmission(message); + getEspnowMeshBackend().attemptAutoEncryptingTransmission(message, true); } void FloodingMesh::clearMessageLogs() @@ -214,12 +235,12 @@ uint8_t *FloodingMesh::getOriginMac(uint8_t *macArray) return macArray; } -uint32_t FloodingMesh::maxUnencryptedMessageSize() +uint32_t FloodingMesh::maxUnencryptedMessageLength() { return getEspnowMeshBackend().getMaxMessageLength() - MESSAGE_ID_LENGTH - (getEspnowMeshBackend().getMeshName().length() + 1); // Need room for mesh name + delimiter } -uint32_t FloodingMesh::maxEncryptedMessageSize() +uint32_t FloodingMesh::maxEncryptedMessageLength() { // Need 1 extra delimiter character for maximum metadata efficiency (makes it possible to store exactly 18 MACs in metadata by adding an extra transmission) return getEspnowMeshBackend().getMaxMessageLength() - MESSAGE_ID_LENGTH - 1; @@ -234,9 +255,11 @@ uint16_t FloodingMesh::messageLogSize() { return _messageLogSize; } void FloodingMesh::setMetadataDelimiter(char metadataDelimiter) { - // Using HEX number characters as a delimiter is a bad idea regardless of broadcast type, since they are always in the broadcast metadata - assert(metadataDelimiter < 48 || 57 < metadataDelimiter); - assert(metadataDelimiter < 65 || 70 < metadataDelimiter); + // Using HEX number characters as a delimiter is a bad idea regardless of broadcast type, since they are always in the broadcast metadata. + // We therefore check for those characters below. + assert(metadataDelimiter < '0' || '9' < metadataDelimiter); + assert(metadataDelimiter < 'A' || 'F' < metadataDelimiter); + assert(metadataDelimiter < 'a' || 'f' < metadataDelimiter); _metadataDelimiter = metadataDelimiter; } @@ -328,6 +351,12 @@ void FloodingMesh::restoreDefaultTransmissionOutcomesUpdateHook() getEspnowMeshBackend().setTransmissionOutcomesUpdateHook([this](MeshBackendBase &meshInstance){ return _defaultTransmissionOutcomesUpdateHook(meshInstance); }); } +void FloodingMesh::restoreDefaultResponseTransmittedHook() +{ + getEspnowMeshBackend().setResponseTransmittedHook([this](const String &response, const uint8_t *recipientMac, uint32_t responseIndex, EspnowMeshBackend &meshInstance) + { return _defaultResponseTransmittedHook(response, recipientMac, responseIndex, meshInstance); }); +} + /** * Callback for when other nodes send you a request * @@ -340,7 +369,7 @@ String FloodingMesh::_defaultRequestHandler(const String &request, MeshBackendBa (void)meshInstance; // This is useful to remove a "unused parameter" compiler warning. Does nothing else. String broadcastTarget = ""; - String remainingRequest = ""; + String remainingRequest = request; if(request.charAt(0) == metadataDelimiter()) { @@ -350,11 +379,7 @@ String FloodingMesh::_defaultRequestHandler(const String &request, MeshBackendBa return ""; // metadataDelimiter not found broadcastTarget = request.substring(1, broadcastTargetEndIndex + 1); // Include delimiter - remainingRequest = request.substring(broadcastTargetEndIndex + 1); - } - else - { - remainingRequest = request; + remainingRequest.remove(0, broadcastTargetEndIndex + 1); } int32_t messageIDEndIndex = remainingRequest.indexOf(metadataDelimiter()); @@ -367,15 +392,16 @@ String FloodingMesh::_defaultRequestHandler(const String &request, MeshBackendBa if(insertCompletedMessageID(messageID)) { uint8_t originMacArray[6] = { 0 }; - setOriginMac(uint64ToMac(messageID >> 16, originMacArray)); + setOriginMac(uint64ToMac(messageID >> 16, originMacArray)); // messageID consists of MAC + 16 bit counter - String message = remainingRequest.substring(messageIDEndIndex + 1); + String message = remainingRequest; + message.remove(0, messageIDEndIndex + 1); // This approach avoids the null value removal of substring() if(getMessageHandler()(message, *this)) { message = broadcastTarget + remainingRequest.substring(0, messageIDEndIndex + 1) + message; assert(message.length() <= _espnowBackend.getMaxMessageLength()); - _forwardingBacklog.emplace_back(message, getEspnowMeshBackend().receivedEncryptedMessage()); + _forwardingBacklog.emplace_back(message, getEspnowMeshBackend().receivedEncryptedTransmission()); } } @@ -501,3 +527,27 @@ bool FloodingMesh::_defaultTransmissionOutcomesUpdateHook(MeshBackendBase &meshI return true; } + +/** + * Once passed to the setResponseTransmittedHook method of the ESP-NOW backend, + * this function will be called after each successful ESP-NOW response transmission, just before the response is removed from the waiting list. + * If a particular response is not sent, there will be no function call for it. + * Only the hook of the EspnowMeshBackend instance that is getEspnowRequestManager() will be called. + * + * @param response The sent response. + * @param recipientMac The MAC address the response was sent to. + * @param responseIndex The index of the response in the waiting list. + * @param meshInstance The EspnowMeshBackend instance that called the function. + * + * @return True if the response transmission process should continue with the next response in the waiting list. + * False if the response transmission process should stop after removing the just sent response from the waiting list. + */ +bool FloodingMesh::_defaultResponseTransmittedHook(const String &response, const uint8_t *recipientMac, uint32_t responseIndex, EspnowMeshBackend &meshInstance) +{ + (void)response; // This is useful to remove a "unused parameter" compiler warning. Does nothing else. + (void)recipientMac; + (void)responseIndex; + (void)meshInstance; + + return true; +} diff --git a/libraries/ESP8266WiFiMesh/src/FloodingMesh.h b/libraries/ESP8266WiFiMesh/src/FloodingMesh.h index 6bd7f0afb4..353c8c6e34 100644 --- a/libraries/ESP8266WiFiMesh/src/FloodingMesh.h +++ b/libraries/ESP8266WiFiMesh/src/FloodingMesh.h @@ -55,7 +55,7 @@ class FloodingMesh { * * @param messageHandler The callback handler responsible for dealing with messages received from the mesh. * @param meshPassword The WiFi password for the mesh network. - * @param espnowEncryptionKey An uint8_t array containing the key used by the EspnowMeshBackend instance for creating encrypted ESP-NOW connections. + * @param espnowEncryptedConnectionKey An uint8_t array containing the secret key used by the EspnowMeshBackend instance for creating encrypted ESP-NOW connections. * @param espnowHashKey An uint8_t array containing the secret key used by the EspnowMeshBackend instance to generate HMACs for encrypted ESP-NOW connections. * @param ssidPrefix The prefix (first part) of the node SSID. * @param ssidSuffix The suffix (last part) of the node SSID. @@ -68,10 +68,31 @@ class FloodingMesh { * make it impossible for other stations to detect the APs whose WiFi channels have changed. * */ - FloodingMesh(messageHandlerType messageHandler, const String &meshPassword, const uint8_t espnowEncryptionKey[EspnowProtocolInterpreter::espnowEncryptionKeyLength], + FloodingMesh(messageHandlerType messageHandler, const String &meshPassword, const uint8_t espnowEncryptedConnectionKey[EspnowProtocolInterpreter::espnowEncryptedConnectionKeyLength], const uint8_t espnowHashKey[EspnowProtocolInterpreter::espnowHashKeyLength], const String &ssidPrefix, const String &ssidSuffix, bool verboseMode = false, uint8 meshWiFiChannel = 1); + /** + * FloodingMesh constructor method. Creates a FloodingMesh node, ready to be initialised. + * + * @param messageHandler The callback handler responsible for dealing with messages received from the mesh. + * @param meshPassword The WiFi password for the mesh network. + * @param espnowEncryptedConnectionKeySeed A string containing the seed that will generate the secret key used by the EspnowMeshBackend instance for creating encrypted ESP-NOW connections. + * @param espnowHashKeySeed A string containing the seed that will generate the secret key used by the EspnowMeshBackend to generate HMACs for encrypted ESP-NOW connections. + * @param ssidPrefix The prefix (first part) of the node SSID. + * @param ssidSuffix The suffix (last part) of the node SSID. + * @param verboseMode Determines if we should print the events occurring in the library to Serial. Off by default. This setting is shared by all EspnowMeshBackend instances. + * @param meshWiFiChannel The WiFi channel used by the mesh network. Valid values are integers from 1 to 13. Defaults to 1. + * WARNING: The ESP8266 has only one WiFi channel, and the the station/client mode is always prioritized for channel selection. + * This can cause problems if several mesh instances exist on the same ESP8266 and use different WiFi channels. + * In such a case, whenever the station of one mesh instance connects to an AP, it will silently force the + * WiFi channel of any active AP on the ESP8266 to match that of the station. This will cause disconnects and possibly + * make it impossible for other stations to detect the APs whose WiFi channels have changed. + * + */ + FloodingMesh(messageHandlerType messageHandler, const String &meshPassword, const String &espnowEncryptedConnectionKeySeed, const String &espnowHashKeySeed, + const String &ssidPrefix, const String &ssidSuffix, bool verboseMode = false, uint8 meshWiFiChannel = 1); + /** * This constructor should be used in combination with serializeMeshState() when the node has gone to sleep while other nodes stayed awake. * Otherwise the message ID will be reset after sleep, which means that the nodes that stayed awake may ignore new broadcasts for a while. @@ -79,17 +100,23 @@ class FloodingMesh { * @param serializedMeshState A String with a serialized mesh node state that the node should use. */ FloodingMesh(const String &serializedMeshState, messageHandlerType messageHandler, const String &meshPassword, - const uint8_t espnowEncryptionKey[EspnowProtocolInterpreter::espnowEncryptionKeyLength], + const uint8_t espnowEncryptedConnectionKey[EspnowProtocolInterpreter::espnowEncryptedConnectionKeyLength], const uint8_t espnowHashKey[EspnowProtocolInterpreter::espnowHashKeyLength], const String &ssidPrefix, const String &ssidSuffix, bool verboseMode = false, uint8 meshWiFiChannel = 1); + /** + * This constructor should be used in combination with serializeMeshState() when the node has gone to sleep while other nodes stayed awake. + * Otherwise the message ID will be reset after sleep, which means that the nodes that stayed awake may ignore new broadcasts for a while. + * + * @param serializedMeshState A String with a serialized mesh node state that the node should use. + */ + FloodingMesh(const String &serializedMeshState, messageHandlerType messageHandler, const String &meshPassword, const String &espnowEncryptedConnectionKeySeed, + const String &espnowHashKeySeed, const String &ssidPrefix, const String &ssidSuffix, bool verboseMode = false, uint8 meshWiFiChannel = 1); + virtual ~FloodingMesh(); /** * The method responsible for initialising this FloodingMesh instance. - * - * Since there is only one WiFi radio on the ESP8266, only the FloodingMesh instance that was the last to begin() will be visible to surrounding nodes. - * All FloodingMesh instances can still broadcast messages though, even if their AP is not visible. */ void begin(); @@ -97,8 +124,9 @@ class FloodingMesh { * Makes it possible to find the node through scans, and also makes it possible to recover from an encrypted ESP-NOW connection where only the other node is encrypted. * Required for encryptedBroadcast() usage, but also slows down the start-up of the node. * - * Note that only one AP can be active at a time in total, 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. * Thus the AP is shared by all backends. + * All FloodingMesh instances can still broadcast messages though, even if their AP is not visible. */ void activateAP(); @@ -123,10 +151,10 @@ class FloodingMesh { /** * Make an unencrypted broadcast to the entire mesh network. * - * It is recommended that there is at most one new message transmitted in the mesh every 10, 20, 30 ms for messages of length maxUnencryptedMessageSize()*n, + * It is recommended that there is at most one new message transmitted in the mesh every 10, 20, 30 ms for messages up to length maxUnencryptedMessageLength()*n, * where n is (roughly, depending on mesh name length) 1/4, 3/5 and 1 respectively. If transmissions are more frequent than this, message loss will increase. * - * @param message The message to broadcast. Maximum message length is given by maxUnencryptedMessageSize(). The longer the message, the longer the transmission time. + * @param message The message to broadcast. Maximum message length is given by maxUnencryptedMessageLength(). The longer the message, the longer the transmission time. */ void broadcast(const String &message); @@ -143,17 +171,24 @@ class FloodingMesh { * Make an encrypted broadcast to the entire mesh network. * * ########## WARNING! This an experimental feature. API may change at any time. Only use if you like it when things break. ########## - * Will be very slow compared to unencrypted broadcasts. Probably works OK in a small mesh with a maximum of one new message transmitted in the mesh every second. + * Will be very slow compared to unencrypted broadcasts. Probably works OK in a small mesh with a maximum of 2-3 new messages transmitted in the mesh every second. * Because of the throughput difference, mixing encypted and unencrypted broadcasts is not recommended if there are frequent mesh broadcasts (multiple per second), * since a lot of unencrypted broadcasts can build up while a single encrypted broadcast is sent. * * It is recommended that verboseMode is turned off if using this, to avoid slowdowns due to excessive Serial printing. * - * @param message The message to broadcast. Maximum message length is given by maxEncryptedMessageSize(). The longer the message, the longer the transmission time. + * @param message The message to broadcast. Maximum message length is given by maxEncryptedMessageLength(). The longer the message, the longer the transmission time. */ void encryptedBroadcast(const String &message); - - void clearMessageLogs(); + + /** + * Clear the logs used for remembering which messages this node has received from the mesh network. + */ + void clearMessageLogs(); + + /** + * Remove all messages received from the mesh network which are stored waiting to be forwarded by this node. + */ void clearForwardingBacklog(); /** @@ -203,7 +238,7 @@ class FloodingMesh { * Note that non-ASCII characters usually require at least two bytes each. * Also note that for unencrypted messages the maximum size will depend on getEspnowMeshBackend().getMeshName().length() */ - uint32_t maxUnencryptedMessageSize(); + uint32_t maxUnencryptedMessageLength(); /** * Hint: Use String.length() to get the ASCII length of a String. @@ -211,14 +246,14 @@ class FloodingMesh { * @return The maximum length in bytes an encrypted ASCII message is allowed to be when broadcasted by this node. * Note that non-ASCII characters usually require at least two bytes each. */ - uint32_t maxEncryptedMessageSize(); + uint32_t maxEncryptedMessageLength(); /** * Set the delimiter character used for metadata by every FloodingMesh instance. * Using characters found in the mesh name or in HEX numbers is unwise, as is using ','. * * @param metadataDelimiter The metadata delimiter character to use. - * Defaults to 23 = End-of-Transmission-Block (ETB) control character in ASCII + * Defaults to 23 = End-of-Transmission-Block (ETB) control character in ASCII */ static void setMetadataDelimiter(char metadataDelimiter); static char metadataDelimiter(); @@ -236,6 +271,7 @@ class FloodingMesh { void restoreDefaultNetworkFilter(); void restoreDefaultBroadcastFilter(); void restoreDefaultTransmissionOutcomesUpdateHook(); + void restoreDefaultResponseTransmittedHook(); protected: @@ -289,6 +325,7 @@ class FloodingMesh { void _defaultNetworkFilter(int numberOfNetworks, MeshBackendBase &meshInstance); bool _defaultBroadcastFilter(String &firstTransmission, EspnowMeshBackend &meshInstance); bool _defaultTransmissionOutcomesUpdateHook(MeshBackendBase &meshInstance); + bool _defaultResponseTransmittedHook(const String &response, const uint8_t *recipientMac, uint32_t responseIndex, EspnowMeshBackend &meshInstance); }; #endif diff --git a/libraries/ESP8266WiFiMesh/src/JsonTranslator.cpp b/libraries/ESP8266WiFiMesh/src/JsonTranslator.cpp index f953b9933b..599a91f3da 100644 --- a/libraries/ESP8266WiFiMesh/src/JsonTranslator.cpp +++ b/libraries/ESP8266WiFiMesh/src/JsonTranslator.cpp @@ -248,9 +248,9 @@ namespace JsonTranslator return true; } - bool getUnencryptedMessageID(const String &jsonString, uint32_t &result) + bool getUnsynchronizedMessageID(const String &jsonString, uint32_t &result) { - int32_t startIndex = getStartIndex(jsonString, jsonUnencryptedMessageID); + int32_t startIndex = getStartIndex(jsonString, jsonUnsynchronizedMessageID); if(startIndex < 0) return false; diff --git a/libraries/ESP8266WiFiMesh/src/JsonTranslator.h b/libraries/ESP8266WiFiMesh/src/JsonTranslator.h index 3997ef6824..ad0265ee73 100644 --- a/libraries/ESP8266WiFiMesh/src/JsonTranslator.h +++ b/libraries/ESP8266WiFiMesh/src/JsonTranslator.h @@ -39,7 +39,7 @@ namespace JsonTranslator const String jsonNonce = "\"nonce\":"; const String jsonHmac = "\"hmac\":"; const String jsonDesync = "\"desync\":"; - const String jsonUnencryptedMessageID = "\"unencMsgID\":"; + const String jsonUnsynchronizedMessageID = "\"unsyncMsgID\":"; const String jsonMeshMessageCount = "\"meshMsgCount\":"; String createJsonPair(const String &valueIdentifier, const String &value); @@ -102,7 +102,7 @@ namespace JsonTranslator bool getNonce(const String &jsonString, String &result); bool getHmac(const String &jsonString, String &result); bool getDesync(const String &jsonString, bool &result); - bool getUnencryptedMessageID(const String &jsonString, uint32_t &result); + bool getUnsynchronizedMessageID(const String &jsonString, uint32_t &result); bool getMeshMessageCount(const String &jsonString, uint16_t &result); } diff --git a/libraries/ESP8266WiFiMesh/src/MeshBackendBase.h b/libraries/ESP8266WiFiMesh/src/MeshBackendBase.h index 4820eab017..dbdac67e03 100644 --- a/libraries/ESP8266WiFiMesh/src/MeshBackendBase.h +++ b/libraries/ESP8266WiFiMesh/src/MeshBackendBase.h @@ -193,7 +193,7 @@ class MeshBackendBase { /** * Set a function that should be called after each update of the latestTransmissionOutcomes vector during attemptTransmission. (which happens after each individual transmission has finished) - * The function should return a bool. If this return value is true, attemptTransmission will continue with the next entry in the connectionQueue. If it is false, attemptTransmission will stop. + * The hook should return a bool. If this return value is true, attemptTransmission will continue with the next entry in the connectionQueue. If it is false, attemptTransmission will stop. * The default transmissionOutcomesUpdateHook always returns true. * * Example use cases is modifying getMessage() between transmissions, or aborting attemptTransmission before all nodes in the connectionQueue have been contacted. diff --git a/libraries/ESP8266WiFiMesh/src/MeshCryptoInterface.cpp b/libraries/ESP8266WiFiMesh/src/MeshCryptoInterface.cpp index 607f6cbf01..2ccfbab29d 100644 --- a/libraries/ESP8266WiFiMesh/src/MeshCryptoInterface.cpp +++ b/libraries/ESP8266WiFiMesh/src/MeshCryptoInterface.cpp @@ -23,6 +23,7 @@ */ #include "MeshCryptoInterface.h" +#include namespace MeshCryptoInterface { @@ -39,4 +40,13 @@ namespace MeshCryptoInterface else return false; } + + uint8_t *initializeKey(uint8_t *key, uint8_t keyLength, const String &keySeed) + { + assert(keyLength <= CryptoInterface::SHA256_NATURAL_LENGTH); + uint8_t hashArray[CryptoInterface::SHA256_NATURAL_LENGTH] {}; + CryptoInterface::sha256Hash(keySeed.c_str(), keySeed.length(), hashArray); + memcpy(key, hashArray, keyLength); + return key; + } } diff --git a/libraries/ESP8266WiFiMesh/src/MeshCryptoInterface.h b/libraries/ESP8266WiFiMesh/src/MeshCryptoInterface.h index 25f90eb49c..9311d3bf4e 100644 --- a/libraries/ESP8266WiFiMesh/src/MeshCryptoInterface.h +++ b/libraries/ESP8266WiFiMesh/src/MeshCryptoInterface.h @@ -67,6 +67,17 @@ namespace MeshCryptoInterface * @return True if the HMAC is correct. False otherwise. */ bool verifyMeshHmac(const String &message, const String &messageHmac, const uint8_t *hashKey, uint8_t hashKeyLength); + + /** + * Initialize key with a SHA-256 hash of keySeed. + * + * @param key A uint8_t array containing the key to be initialized. + * @param keyLength The length of the key array in bytes. Maximum value is CryptoInterface::SHA256_NATURAL_LENGTH. + * @param keySeed The key seed. + * + * @return A pointer to the initialized key array. + */ + uint8_t *initializeKey(uint8_t *key, uint8_t keyLength, const String &keySeed); } #endif diff --git a/libraries/ESP8266WiFiMesh/src/TypeConversionFunctions.cpp b/libraries/ESP8266WiFiMesh/src/TypeConversionFunctions.cpp index 62eefdd609..49530f2329 100644 --- a/libraries/ESP8266WiFiMesh/src/TypeConversionFunctions.cpp +++ b/libraries/ESP8266WiFiMesh/src/TypeConversionFunctions.cpp @@ -1,6 +1,6 @@ /* * TypeConversionFunctions - * Copyright (C) 2018 Anders Löfgren + * Copyright (C) 2018-2019 Anders Löfgren * * License (MIT license): * @@ -25,19 +25,39 @@ #include "TypeConversionFunctions.h" +namespace +{ + constexpr char chars[36] = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z'}; + constexpr uint8_t charValues[75] {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 0, 0, 0, 0, 0, 0, // 0 to 10 + 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, 0, 0, 0, 0, 0, 0, // Upper 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 +} + + String uint64ToString(uint64_t number, byte base) { assert(2 <= base && base <= 36); - String result = ""; - - while(number > 0) + String result; + + if(base == 16) { - result = String((uint32_t)(number % base), base) + result; - number /= base; + do { + result += chars[ number % base ]; + number >>= 4; // We could write number /= 16; and the compiler would optimize it to a shift, but the explicit shift notation makes it clearer where the speed-up comes from. + } while ( number ); } + else + { + do { + result += chars[ number % base ]; + number /= base; + } while ( number ); + } + + std::reverse( result.begin(), result.end() ); - return (result == "" ? "0" : result); + return result; } uint64_t stringToUint64(const String &string, byte base) @@ -46,12 +66,21 @@ uint64_t stringToUint64(const String &string, byte base) uint64_t result = 0; - char currentCharacter[1]; - for(uint32_t i = 0; i < string.length(); i++) + if(base == 16) { - result *= base; - currentCharacter[0] = string.charAt(i); - result += strtoul(currentCharacter, NULL, base); + for(uint32_t i = 0; i < string.length(); ++i) + { + result <<= 4; // We could write result *= 16; and the compiler would optimize it to a shift, but the explicit shift notation makes it clearer where the speed-up comes from. + result += charValues[string.charAt(i) - '0']; + } + } + else + { + for(uint32_t i = 0; i < string.length(); ++i) + { + result *= base; + result += charValues[string.charAt(i) - '0']; + } } return result; @@ -59,33 +88,78 @@ uint64_t stringToUint64(const String &string, byte base) String uint8ArrayToHexString(const uint8_t *uint8Array, uint32_t arrayLength) { - char hexString[2*arrayLength + 1]; // Each uint8_t will become two characters (00 to FF) and we want a null terminated char array. - hexString[arrayLength + 1] = { 0 }; - for(uint32_t i = 0; i < arrayLength; i++) + String hexString; + if(!hexString.reserve(2*arrayLength)) // Each uint8_t will become two characters (00 to FF) + return emptyString; + + for(uint32_t i = 0; i < arrayLength; ++i) { - sprintf(hexString + 2*i, "%02X", uint8Array[i]); + hexString += chars[ uint8Array[i] >> 4 ]; + hexString += chars[ uint8Array[i] % 16 ]; } - - return String(hexString); + + return hexString; } uint8_t *hexStringToUint8Array(const String &hexString, uint8_t *uint8Array, uint32_t arrayLength) { assert(hexString.length() >= arrayLength*2); // Each array element can hold two hexString characters - for(uint32_t i = 0; i < arrayLength; i++) + for(uint32_t i = 0; i < arrayLength; ++i) { - uint8Array[i] = strtoul(hexString.substring(i*2, (i+1)*2).c_str(), nullptr, 16); + uint8Array[i] = (charValues[hexString.charAt(i*2) - '0'] << 4) + charValues[hexString.charAt(i*2 + 1) - '0']; } return uint8Array; } +String uint8ArrayToMultiString(uint8_t *uint8Array, uint32_t arrayLength) +{ + String multiString; + if(!multiString.reserve(arrayLength)) + return emptyString; + + // Ensure we have a NULL terminated character array so the String() constructor knows where to stop. + char finalChar = uint8Array[arrayLength - 1]; + uint8Array[arrayLength - 1] = 0; + + multiString += (char *)(uint8Array); + while(multiString.length() < arrayLength - 1) + { + multiString += (char)0; // String construction only stops for null values, so we need to add those manually. + multiString += (char *)(uint8Array + multiString.length()); + } + + multiString += finalChar; + uint8Array[arrayLength - 1] = finalChar; + + return multiString; +} + +String bufferedUint8ArrayToMultiString(const uint8_t *uint8Array, uint32_t arrayLength) +{ + String multiString; + if(!multiString.reserve(arrayLength)) + return emptyString; + + // Ensure we have a NULL terminated character array so the String() constructor knows where to stop. + uint8_t bufferedData[arrayLength + 1]; + std::copy_n(uint8Array, arrayLength, bufferedData); + bufferedData[arrayLength] = 0; + + multiString += (char *)(bufferedData); + while(multiString.length() < arrayLength) + { + multiString += (char)0; // String construction only stops for null values, so we need to add those manually. + multiString += (char *)(bufferedData + multiString.length()); + } + + return multiString; +} + String macToString(const uint8_t *mac) { - char macString[13] = { 0 }; - sprintf(macString, "%02X%02X%02X%02X%02X%02X", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]); - return String(macString); + return uint8ArrayToHexString(mac, 6); } uint8_t *stringToMac(const String &macString, uint8_t *macArray) @@ -95,25 +169,45 @@ uint8_t *stringToMac(const String &macString, uint8_t *macArray) uint64_t macToUint64(const uint8_t *macArray) { - uint64_t outcome = 0; - for(int shiftingFortune = 40; shiftingFortune >= 0; shiftingFortune -= 8) - { - outcome |= ((uint64_t)macArray[5 - shiftingFortune/8] << shiftingFortune); - } - - return outcome; + uint64_t result = (uint64_t)macArray[0] << 40 | (uint64_t)macArray[1] << 32 | (uint64_t)macArray[2] << 24 | (uint64_t)macArray[3] << 16 | (uint64_t)macArray[4] << 8 | (uint64_t)macArray[5]; + return result; } uint8_t *uint64ToMac(uint64_t macValue, uint8_t *macArray) { assert(macValue <= 0xFFFFFFFFFFFF); // Overflow will occur if value can't fit within 6 bytes - for(int shiftingFortune = 40; shiftingFortune >= 0; shiftingFortune -= 8) - { - macArray[5 - shiftingFortune/8] = macValue >> shiftingFortune & 0xFF; - } + macArray[5] = macValue; + macArray[4] = macValue >> 8; + macArray[3] = macValue >> 16; + macArray[2] = macValue >> 24; + macArray[1] = macValue >> 32; + macArray[0] = macValue >> 40; + return macArray; -} +} + +uint8_t *uint64ToUint8Array(uint64_t value, uint8_t *resultArray) +{ + resultArray[7] = value; + resultArray[6] = value >> 8; + resultArray[5] = value >> 16; + resultArray[4] = value >> 24; + resultArray[3] = value >> 32; + resultArray[2] = value >> 40; + resultArray[1] = value >> 48; + resultArray[0] = value >> 56; + + return resultArray; +} + +uint64_t uint8ArrayToUint64(const uint8_t *inputArray) +{ + uint64_t result = (uint64_t)inputArray[0] << 56 | (uint64_t)inputArray[1] << 48 | (uint64_t)inputArray[2] << 40 | (uint64_t)inputArray[3] << 32 + | (uint64_t)inputArray[4] << 24 | (uint64_t)inputArray[5] << 16 | (uint64_t)inputArray[6] << 8 | (uint64_t)inputArray[7]; + + return result; +} /** * Helper function for meshBackendCast. diff --git a/libraries/ESP8266WiFiMesh/src/TypeConversionFunctions.h b/libraries/ESP8266WiFiMesh/src/TypeConversionFunctions.h index 1511853399..08a1ab5361 100644 --- a/libraries/ESP8266WiFiMesh/src/TypeConversionFunctions.h +++ b/libraries/ESP8266WiFiMesh/src/TypeConversionFunctions.h @@ -1,6 +1,6 @@ /* * TypeConversionFunctions - * Copyright (C) 2018 Anders Löfgren + * Copyright (C) 2018-2019 Anders Löfgren * * License (MIT license): * @@ -33,7 +33,8 @@ #include "EspnowMeshBackend.h" /** - * Note that using a base higher than 16 increases likelihood of randomly generating SSID strings containing controversial words. + * Note that using base 10 instead of 16 increases conversion time by roughly a factor of 5, due to unfavourable 64-bit arithmetic. + * Note that using a base higher than 16 increases likelihood of randomly generating SSID strings containing controversial words. * * @param number The number to convert to a string with radix "base". * @param base The radix to convert "number" into. Must be between 2 and 36. @@ -42,6 +43,7 @@ String uint64ToString(uint64_t number, byte base = 16); /** + * Note that using base 10 instead of 16 increases conversion time by roughly a factor of 2, due to unfavourable 64-bit arithmetic. * Note that using a base higher than 16 increases likelihood of randomly generating SSID strings containing controversial words. * * @param string The string to convert to uint64_t. String must use radix "base". @@ -50,12 +52,53 @@ String uint64ToString(uint64_t number, byte base = 16); */ uint64_t stringToUint64(const String &string, byte base = 16); -// All array elements will be padded with zeroes to ensure they are converted to 2 string characters each. +/** + * Convert the contents of a uint8_t array to a String in HEX format. The resulting String starts from index 0 of the array. + * All array elements will be padded with zeroes to ensure they are converted to 2 String characters each. + * + * @param uint8Array The array to make into a HEX String. + * @param arrayLength The size of uint8Array, in bytes. + * @return Normally a String containing the HEX representation of the uint8Array. An empty String if the memory allocation for the String failed. + */ String uint8ArrayToHexString(const uint8_t *uint8Array, uint32_t arrayLength); -// There must be 2 string characters for each array element. Use padding with zeroes where required. +/** + * Convert the contents of a String in HEX format to a uint8_t array. Index 0 of the array will represent the start of the String. + * There must be 2 String characters for each array element. Use padding with zeroes where required. + * + * @param hexString The HEX String to convert to a uint8_t array. Must contain at least 2*arrayLength characters. + * @param uint8Array The array to fill with the contents of the hexString. + * @param arrayLength The number of bytes to fill in uint8Array. + * @return A pointer to the uint8Array. + */ uint8_t *hexStringToUint8Array(const String &hexString, uint8_t *uint8Array, uint32_t arrayLength); +/** + * Stores the exact values of uint8Array in a String, even null values. + * Note that Strings containing null values will look like several separate Strings to methods that rely on null values to find the String end, such as String::substring. + * In these cases, it may be helpful to use String::c_str() or String::begin() to access the String data buffer directly instead. + * + * The unbuffered version temporarily edits uint8Array during execution, but restores the array to its original state when returning in a controlled manner. + * + * @param uint8Array The array to make into a multiString. + * @param arrayLength The size of uint8Array, in bytes. + * @return Normally a String containing the same data as the uint8Array. An empty String if the memory allocation for the String failed. + */ +String uint8ArrayToMultiString(uint8_t *uint8Array, uint32_t arrayLength); + +/** + * Stores the exact values of uint8Array in a String, even null values. + * Note that Strings containing null values will look like several separate Strings to methods that rely on null values to find the String end, such as String::substring. + * In these cases, it may be helpful to use String::c_str() or String::begin() to access the String data buffer directly instead. + * + * The buffered version is slower and uses more memory than the unbuffered version, but can operate on const arrays. + * + * @param uint8Array The array to make into a multiString. + * @param arrayLength The size of uint8Array, in bytes. + * @return Normally a String containing the same data as the uint8Array. An empty String if the memory allocation for the String failed. + */ +String bufferedUint8ArrayToMultiString(const uint8_t *uint8Array, uint32_t arrayLength); + /** * Takes a uint8_t array and converts the first 6 bytes to a hexadecimal string. * @@ -90,6 +133,23 @@ uint64_t macToUint64(const 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. + * + * @param value The uint64_t value to convert to a uint8_t array. + * @param resultArray A uint8_t array that will hold the result once the function returns. Should have a size of at least 8 bytes. + * @return The resultArray. + */ +uint8_t *uint64ToUint8Array(uint64_t value, uint8_t *resultArray); + +/** + * Takes a uint8_t array and converts the first 8 (lowest index) elements to a uint64_t. Assumes index 0 of the array contains MSB. + * + * @param inputArray A uint8_t array containing the data to convert to a uint64_t. Should have a size of at least 8 bytes. + * @return A uint64_t representation of the first 8 bytes of the array. + */ +uint64_t uint8ArrayToUint64(const uint8_t *inputArray); + /** * Conversion function that can be used on MeshBackend classes instead of dynamic_cast since RTTI is disabled. * From a49f0470963a5282d87a6fe662ba55b1ab308c2d Mon Sep 17 00:00:00 2001 From: Anders Date: Sat, 21 Dec 2019 16:53:18 +0100 Subject: [PATCH 14/30] - 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. --- .../examples/HelloEspnow/HelloEspnow.ino | 44 ++- .../examples/HelloMesh/HelloMesh.ino | 32 +- .../examples/HelloTcpIp/HelloTcpIp.ino | 65 ++-- .../ESP8266WiFiMesh/src/CryptoInterface.cpp | 24 +- .../ESP8266WiFiMesh/src/CryptoInterface.h | 8 +- .../ESP8266WiFiMesh/src/ESP8266WiFiMesh.cpp | 4 +- .../src/EncryptedConnectionData.cpp | 21 +- .../ESP8266WiFiMesh/src/EspnowMeshBackend.cpp | 237 +++++++------ .../ESP8266WiFiMesh/src/EspnowMeshBackend.h | 28 +- .../src/EspnowProtocolInterpreter.cpp | 10 +- .../src/EspnowProtocolInterpreter.h | 30 +- .../ESP8266WiFiMesh/src/FloodingMesh.cpp | 57 +-- libraries/ESP8266WiFiMesh/src/FloodingMesh.h | 17 +- .../ESP8266WiFiMesh/src/JsonTranslator.cpp | 68 ++-- .../ESP8266WiFiMesh/src/JsonTranslator.h | 24 +- .../ESP8266WiFiMesh/src/MeshBackendBase.cpp | 33 +- .../ESP8266WiFiMesh/src/MeshBackendBase.h | 41 ++- libraries/ESP8266WiFiMesh/src/MessageData.h | 2 +- libraries/ESP8266WiFiMesh/src/NetworkInfo.h | 2 +- .../ESP8266WiFiMesh/src/NetworkInfoBase.cpp | 2 +- .../ESP8266WiFiMesh/src/PeerRequestLog.h | 4 +- libraries/ESP8266WiFiMesh/src/ResponseData.h | 2 +- .../ESP8266WiFiMesh/src/TcpIpMeshBackend.cpp | 43 +-- .../src/TypeConversionFunctions.cpp | 332 +++++++++--------- .../src/TypeConversionFunctions.h | 279 ++++++++------- .../ESP8266WiFiMesh/src/UtilityFunctions.cpp | 25 +- .../ESP8266WiFiMesh/src/UtilityFunctions.h | 9 +- 27 files changed, 790 insertions(+), 653 deletions(-) diff --git a/libraries/ESP8266WiFiMesh/examples/HelloEspnow/HelloEspnow.ino b/libraries/ESP8266WiFiMesh/examples/HelloEspnow/HelloEspnow.ino index 8c8f2628bd..3a4225e586 100644 --- a/libraries/ESP8266WiFiMesh/examples/HelloEspnow/HelloEspnow.ino +++ b/libraries/ESP8266WiFiMesh/examples/HelloEspnow/HelloEspnow.ino @@ -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 #include #include #include +namespace TypeCast = MeshTypeConversionFunctions; + /** NOTE: Although we could define the strings below as normal String variables, here we are using PROGMEM combined with the FPSTR() macro (and also just the F() macro further down in the file). @@ -14,8 +18,8 @@ https://github.com/esp8266/Arduino/issues/1143 https://arduino-esp8266.readthedocs.io/en/latest/PROGMEM.html */ -const char exampleMeshName[] PROGMEM = "MeshNode_"; // The name of the mesh network. Used as prefix for the node SSID and to find other network nodes in the example networkFilter and broadcastFilter functions below. -const char exampleWiFiPassword[] PROGMEM = "ChangeThisWiFiPassword_TODO"; // The password has to be min 8 and max 64 characters long, otherwise an AP which uses it will not be found during scans. +constexpr char exampleMeshName[] PROGMEM = "MeshNode_"; // The name of the mesh network. Used as prefix for the node SSID and to find other network nodes in the example networkFilter and broadcastFilter functions below. +constexpr char exampleWiFiPassword[] PROGMEM = "ChangeThisWiFiPassword_TODO"; // The password has to be min 8 and max 64 characters long, otherwise an AP which uses it will not be found during scans. // A custom encryption key is required when using encrypted ESP-NOW transmissions. There is always a default Kok set, but it can be replaced if desired. // All ESP-NOW keys below must match in an encrypted connection pair for encrypted communication to be possible. @@ -41,7 +45,7 @@ void networkFilter(int numberOfNetworks, MeshBackendBase &meshInstance); bool broadcastFilter(String &firstTransmission, EspnowMeshBackend &meshInstance); /* Create the mesh node object */ -EspnowMeshBackend espnowNode = EspnowMeshBackend(manageRequest, manageResponse, networkFilter, broadcastFilter, FPSTR(exampleWiFiPassword), espnowEncryptedConnectionKey, espnowHashKey, FPSTR(exampleMeshName), uint64ToString(ESP.getChipId()), true); +EspnowMeshBackend espnowNode = EspnowMeshBackend(manageRequest, manageResponse, networkFilter, broadcastFilter, FPSTR(exampleWiFiPassword), espnowEncryptedConnectionKey, espnowHashKey, FPSTR(exampleMeshName), TypeCast::uint64ToString(ESP.getChipId()), true); /** Callback for when other nodes send you a request @@ -57,10 +61,10 @@ String manageRequest(const String &request, MeshBackendBase &meshInstance) { // Of course, it is advised to adjust this approach based on RAM requirements. // To get the actual class of the polymorphic meshInstance, do as follows (meshBackendCast replaces dynamic_cast since RTTI is disabled) - if (EspnowMeshBackend *espnowInstance = meshBackendCast(&meshInstance)) { + if (EspnowMeshBackend *espnowInstance = TypeCast::meshBackendCast(&meshInstance)) { String transmissionEncrypted = espnowInstance->receivedEncryptedTransmission() ? ", Encrypted transmission" : ", Unencrypted transmission"; Serial.print("ESP-NOW (" + espnowInstance->getSenderMac() + transmissionEncrypted + "): "); - } else if (TcpIpMeshBackend *tcpIpInstance = meshBackendCast(&meshInstance)) { + } else if (TcpIpMeshBackend *tcpIpInstance = TypeCast::meshBackendCast(&meshInstance)) { (void)tcpIpInstance; // This is useful to remove a "unused parameter" compiler warning. Does nothing else. Serial.print("TCP/IP: "); } else { @@ -72,7 +76,12 @@ String manageRequest(const String &request, MeshBackendBase &meshInstance) { // If you need to print the whole String it is better to store it and print it in the loop() later. // Note that request.substring will not work as expected if the String contains null values as data. Serial.print("Request received: "); - Serial.println(request.substring(0, 100)); + + if (request.charAt(0) == 0) { + Serial.println(request); // substring will not work for multiStrings. + } else { + Serial.println(request.substring(0, 100)); + } /* return a string to send back */ return ("Hello world response #" + String(responseNumber++) + " from " + meshInstance.getMeshName() + meshInstance.getNodeID() + " with AP MAC " + WiFi.softAPmacAddress() + "."); @@ -89,10 +98,10 @@ transmission_status_t manageResponse(const String &response, MeshBackendBase &me transmission_status_t statusCode = TS_TRANSMISSION_COMPLETE; // To get the actual class of the polymorphic meshInstance, do as follows (meshBackendCast replaces dynamic_cast since RTTI is disabled) - if (EspnowMeshBackend *espnowInstance = meshBackendCast(&meshInstance)) { + if (EspnowMeshBackend *espnowInstance = TypeCast::meshBackendCast(&meshInstance)) { String transmissionEncrypted = espnowInstance->receivedEncryptedTransmission() ? ", Encrypted transmission" : ", Unencrypted transmission"; Serial.print("ESP-NOW (" + espnowInstance->getSenderMac() + transmissionEncrypted + "): "); - } else if (TcpIpMeshBackend *tcpIpInstance = meshBackendCast(&meshInstance)) { + } else if (TcpIpMeshBackend *tcpIpInstance = TypeCast::meshBackendCast(&meshInstance)) { Serial.print("TCP/IP: "); // Getting the sent message like this will work as long as ONLY(!) TCP/IP is used. @@ -129,12 +138,12 @@ void networkFilter(int numberOfNetworks, MeshBackendBase &meshInstance) { /* Connect to any _suitable_ APs which contain meshInstance.getMeshName() */ if (meshNameIndex >= 0) { - uint64_t targetNodeID = stringToUint64(currentSSID.substring(meshNameIndex + meshInstance.getMeshName().length())); + uint64_t targetNodeID = TypeCast::stringToUint64(currentSSID.substring(meshNameIndex + meshInstance.getMeshName().length())); - if (targetNodeID < stringToUint64(meshInstance.getNodeID())) { - if (EspnowMeshBackend *espnowInstance = meshBackendCast(&meshInstance)) { + if (targetNodeID < TypeCast::stringToUint64(meshInstance.getNodeID())) { + if (EspnowMeshBackend *espnowInstance = TypeCast::meshBackendCast(&meshInstance)) { espnowInstance->connectionQueue().push_back(networkIndex); - } else if (TcpIpMeshBackend *tcpIpInstance = meshBackendCast(&meshInstance)) { + } else if (TcpIpMeshBackend *tcpIpInstance = TypeCast::meshBackendCast(&meshInstance)) { tcpIpInstance->connectionQueue().push_back(networkIndex); } else { Serial.println(String(F("Invalid mesh backend!"))); @@ -345,6 +354,15 @@ void loop() { espnowDelay(100); // Wait for responses (broadcasts can receive an unlimited number of responses, other transmissions can only receive one response). + // If you have a data array containing null values it is possible to transmit the raw data by making the array into a multiString as shown below. + // You can use String::c_str() or String::begin() to retreive the data array later. + // Note that certain String methods such as String::substring use null values to determine String length, which means they will not work as normal with multiStrings. + uint8_t dataArray[] = {0, '\'', 0, '\'', ' ', '(', 'n', 'u', 'l', 'l', ')', ' ', 'v', 'a', 'l', 'u', 'e'}; + String espnowMessage = TypeCast::uint8ArrayToMultiString(dataArray, sizeof dataArray) + String(F(" from ")) + espnowNode.getMeshName() + espnowNode.getNodeID() + String(F(".")); + Serial.println("\nTransmitting: " + espnowMessage); + espnowNode.attemptTransmission(espnowMessage, false); + espnowDelay(100); // Wait for response. + Serial.println("\nPerforming encrypted ESP-NOW transmissions."); uint8_t targetBSSID[6] {0}; @@ -352,7 +370,7 @@ void loop() { // We can create encrypted connections to individual nodes so that all ESP-NOW communication with the node will be encrypted. if (espnowNode.constConnectionQueue()[0].getBSSID(targetBSSID) && espnowNode.requestEncryptedConnection(targetBSSID) == ECS_CONNECTION_ESTABLISHED) { // The WiFi scan will detect the AP MAC, but this will automatically be converted to the encrypted STA MAC by the framework. - String peerMac = macToString(targetBSSID); + String peerMac = TypeCast::macToString(targetBSSID); Serial.println("Encrypted ESP-NOW connection with " + peerMac + " established!"); diff --git a/libraries/ESP8266WiFiMesh/examples/HelloMesh/HelloMesh.ino b/libraries/ESP8266WiFiMesh/examples/HelloMesh/HelloMesh.ino index bfa63a5c6d..22836e17c5 100644 --- a/libraries/ESP8266WiFiMesh/examples/HelloMesh/HelloMesh.ino +++ b/libraries/ESP8266WiFiMesh/examples/HelloMesh/HelloMesh.ino @@ -5,11 +5,15 @@ That way you will get instant confirmation of the mesh communication without checking the Serial Monitor. */ +#define ESP8266WIFIMESH_DISABLE_COMPATIBILITY // Excludes redundant compatibility code. Should be used for new code until the compatibility code is removed with release 3.0.0 of the Arduino core. + #include #include #include #include +namespace TypeCast = MeshTypeConversionFunctions; + /** NOTE: Although we could define the strings below as normal String variables, here we are using PROGMEM combined with the FPSTR() macro (and also just the F() macro further down in the file). @@ -21,8 +25,8 @@ https://github.com/esp8266/Arduino/issues/1143 https://arduino-esp8266.readthedocs.io/en/latest/PROGMEM.html */ -const char exampleMeshName[] PROGMEM = "MeshNode_"; // The name of the mesh network. Used as prefix for the node SSID and to find other network nodes in the example networkFilter and broadcastFilter functions below. -const char exampleWiFiPassword[] PROGMEM = "ChangeThisWiFiPassword_TODO"; // The password has to be min 8 and max 64 characters long, otherwise an AP which uses it will not be found during scans. +constexpr char exampleMeshName[] PROGMEM = "MeshNode_"; // The name of the mesh network. Used as prefix for the node SSID and to find other network nodes in the example networkFilter and broadcastFilter functions below. +constexpr char exampleWiFiPassword[] PROGMEM = "ChangeThisWiFiPassword_TODO"; // The password has to be min 8 and max 64 characters long, otherwise an AP which uses it will not be found during scans. // A custom encryption key is required when using encrypted ESP-NOW transmissions. There is always a default Kok set, but it can be replaced if desired. // All ESP-NOW keys below must match in an encrypted connection pair for encrypted communication to be possible. @@ -37,10 +41,10 @@ uint8_t espnowHashKey[16] = {0xEF, 0x44, 0x33, 0x0C, 0x33, 0x44, 0xFE, 0x44, // bool meshMessageHandler(String &message, FloodingMesh &meshInstance); /* Create the mesh node object */ -FloodingMesh floodingMesh = FloodingMesh(meshMessageHandler, FPSTR(exampleWiFiPassword), espnowEncryptedConnectionKey, espnowHashKey, FPSTR(exampleMeshName), uint64ToString(ESP.getChipId()), true); +FloodingMesh floodingMesh = FloodingMesh(meshMessageHandler, FPSTR(exampleWiFiPassword), espnowEncryptedConnectionKey, espnowHashKey, FPSTR(exampleMeshName), TypeCast::uint64ToString(ESP.getChipId()), true); bool theOne = true; -String theOneMac = ""; +String theOneMac; bool useLED = false; // Change this to true if you wish the onboard LED to mark The One. @@ -58,7 +62,7 @@ bool useLED = false; // Change this to true if you wish the onboard LED to mark bool meshMessageHandler(String &message, FloodingMesh &meshInstance) { int32_t delimiterIndex = message.indexOf(meshInstance.metadataDelimiter()); if (delimiterIndex == 0) { - Serial.print("Message received from STA MAC " + meshInstance.getEspnowMeshBackend().getSenderMac() + ": "); + Serial.print(String(F("Message received from STA MAC ")) + meshInstance.getEspnowMeshBackend().getSenderMac() + F(": ")); Serial.println(message.substring(2, 102)); String potentialMac = message.substring(2, 14); @@ -95,17 +99,17 @@ bool meshMessageHandler(String &message, FloodingMesh &meshInstance) { previousTotalBroadcasts = totalBroadcasts; if (totalReceivedBroadcasts % 50 == 0) { - Serial.println("missed/total: " + String(missedBroadcasts) + '/' + String(totalReceivedBroadcasts)); + Serial.println(String(F("missed/total: ")) + String(missedBroadcasts) + '/' + String(totalReceivedBroadcasts)); } if (totalReceivedBroadcasts % 500 == 0) { - Serial.println("Benchmark message: " + message.substring(0, 100)); + Serial.println(String(F("Benchmark message: ")) + message.substring(0, 100)); } } } } else { // Only show first 100 characters because printing a large String takes a lot of time, which is a bad thing for a callback function. // If you need to print the whole String it is better to store it and print it in the loop() later. - Serial.print("Message with origin " + meshInstance.getOriginMac() + " received: "); + Serial.print(String(F("Message with origin ")) + meshInstance.getOriginMac() + F(" received: ")); Serial.println(message.substring(0, 100)); } @@ -139,7 +143,7 @@ void setup() { floodingMesh.activateAP(); uint8_t apMacArray[6] {0}; - theOneMac = macToString(WiFi.softAPmacAddress(apMacArray)); + theOneMac = TypeCast::macToString(WiFi.softAPmacAddress(apMacArray)); if (useLED) { pinMode(LED_BUILTIN, OUTPUT); // Initialize the LED_BUILTIN pin as an output @@ -150,7 +154,7 @@ void setup() { // The main benefit of AEAD encryption is that it can be used with normal broadcasts (which are substantially faster than encryptedBroadcasts). // The main drawbacks are that AEAD only encrypts the message data (not transmission metadata), transfers less data per message and lacks replay attack protection. // When using AEAD, potential replay attacks must thus be handled manually. - //floodingMesh.getEspnowMeshBackend().setEspnowMessageEncryptionKey("ChangeThisKeySeed_TODO"); // The message encryption key should always be set manually. Otherwise a default key (all zeroes) is used. + //floodingMesh.getEspnowMeshBackend().setEspnowMessageEncryptionKey(F("ChangeThisKeySeed_TODO")); // The message encryption key should always be set manually. Otherwise a default key (all zeroes) is used. //floodingMesh.getEspnowMeshBackend().setUseEncryptedMessages(true); floodingMeshDelay(5000); // Give some time for user to start the nodes @@ -180,8 +184,8 @@ void loop() { ledState = ledState ^ bool(benchmarkCount); // Make other nodes' LEDs alternate between on and off once benchmarking begins. // Note: The maximum length of an unencrypted broadcast message is given by floodingMesh.maxUnencryptedMessageLength(). It is around 670 bytes by default. - floodingMesh.broadcast(String(floodingMesh.metadataDelimiter()) + String(ledState) + theOneMac + " is The One."); - Serial.println("Proclamation broadcast done in " + String(millis() - startTime) + " ms."); + floodingMesh.broadcast(String(floodingMesh.metadataDelimiter()) + String(ledState) + theOneMac + F(" is The One.")); + Serial.println(String(F("Proclamation broadcast done in ")) + String(millis() - startTime) + F(" ms.")); timeOfLastProclamation = millis(); floodingMeshDelay(20); @@ -189,8 +193,8 @@ void loop() { if (millis() - loopStart > 23000) { // Start benchmarking the mesh once three proclamations have been made uint32_t startTime = millis(); - floodingMesh.broadcast(String(benchmarkCount++) + String(floodingMesh.metadataDelimiter()) + ": Not a spoon in sight."); - Serial.println("Benchmark broadcast done in " + String(millis() - startTime) + " ms."); + floodingMesh.broadcast(String(benchmarkCount++) + String(floodingMesh.metadataDelimiter()) + F(": Not a spoon in sight.")); + Serial.println(String(F("Benchmark broadcast done in ")) + String(millis() - startTime) + F(" ms.")); floodingMeshDelay(20); } } diff --git a/libraries/ESP8266WiFiMesh/examples/HelloTcpIp/HelloTcpIp.ino b/libraries/ESP8266WiFiMesh/examples/HelloTcpIp/HelloTcpIp.ino index 04d26c2ef6..d91946e0f7 100644 --- a/libraries/ESP8266WiFiMesh/examples/HelloTcpIp/HelloTcpIp.ino +++ b/libraries/ESP8266WiFiMesh/examples/HelloTcpIp/HelloTcpIp.ino @@ -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 #include #include #include +namespace TypeCast = MeshTypeConversionFunctions; + /** NOTE: Although we could define the strings below as normal String variables, here we are using PROGMEM combined with the FPSTR() macro (and also just the F() macro further down in the file). @@ -14,8 +18,8 @@ https://github.com/esp8266/Arduino/issues/1143 https://arduino-esp8266.readthedocs.io/en/latest/PROGMEM.html */ -const char exampleMeshName[] PROGMEM = "MeshNode_"; -const char exampleWiFiPassword[] PROGMEM = "ChangeThisWiFiPassword_TODO"; +constexpr char exampleMeshName[] PROGMEM = "MeshNode_"; +constexpr char exampleWiFiPassword[] PROGMEM = "ChangeThisWiFiPassword_TODO"; unsigned int requestNumber = 0; unsigned int responseNumber = 0; @@ -25,7 +29,7 @@ transmission_status_t manageResponse(const String &response, MeshBackendBase &me void networkFilter(int numberOfNetworks, MeshBackendBase &meshInstance); /* Create the mesh node object */ -TcpIpMeshBackend tcpIpNode = TcpIpMeshBackend(manageRequest, manageResponse, networkFilter, FPSTR(exampleWiFiPassword), FPSTR(exampleMeshName), uint64ToString(ESP.getChipId()), true); +TcpIpMeshBackend tcpIpNode = TcpIpMeshBackend(manageRequest, manageResponse, networkFilter, FPSTR(exampleWiFiPassword), FPSTR(exampleMeshName), TypeCast::uint64ToString(ESP.getChipId()), true); /** Callback for when other nodes send you a request @@ -35,31 +39,26 @@ TcpIpMeshBackend tcpIpNode = TcpIpMeshBackend(manageRequest, manageResponse, net @return The string to send back to the other node. For ESP-NOW, return an empy string ("") if no response should be sent. */ String manageRequest(const String &request, MeshBackendBase &meshInstance) { - // We do not store strings in flash (via F()) in this function. - // The reason is that the other node will be waiting for our response, - // so keeping the strings in RAM will give a (small) improvement in response time. - // Of course, it is advised to adjust this approach based on RAM requirements. - // To get the actual class of the polymorphic meshInstance, do as follows (meshBackendCast replaces dynamic_cast since RTTI is disabled) - if (EspnowMeshBackend *espnowInstance = meshBackendCast(&meshInstance)) { - String transmissionEncrypted = espnowInstance->receivedEncryptedTransmission() ? ", Encrypted transmission" : ", Unencrypted transmission"; - Serial.print("ESP-NOW (" + espnowInstance->getSenderMac() + transmissionEncrypted + "): "); - } else if (TcpIpMeshBackend *tcpIpInstance = meshBackendCast(&meshInstance)) { + if (EspnowMeshBackend *espnowInstance = TypeCast::meshBackendCast(&meshInstance)) { + String transmissionEncrypted = espnowInstance->receivedEncryptedTransmission() ? F(", Encrypted transmission") : F(", Unencrypted transmission"); + Serial.print(String(F("ESP-NOW (")) + espnowInstance->getSenderMac() + transmissionEncrypted + F("): ")); + } else if (TcpIpMeshBackend *tcpIpInstance = TypeCast::meshBackendCast(&meshInstance)) { (void)tcpIpInstance; // This is useful to remove a "unused parameter" compiler warning. Does nothing else. - Serial.print("TCP/IP: "); + Serial.print(F("TCP/IP: ")); } else { - Serial.print("UNKNOWN!: "); + Serial.print(F("UNKNOWN!: ")); } /* Print out received message */ // Only show first 100 characters because printing a large String takes a lot of time, which is a bad thing for a callback function. // If you need to print the whole String it is better to store it and print it in the loop() later. // Note that request.substring will not work as expected if the String contains null values as data. - Serial.print("Request received: "); + Serial.print(F("Request received: ")); Serial.println(request.substring(0, 100)); /* return a string to send back */ - return ("Hello world response #" + String(responseNumber++) + " from " + meshInstance.getMeshName() + meshInstance.getNodeID() + " with AP MAC " + WiFi.softAPmacAddress() + "."); + return (String(F("Hello world response #")) + String(responseNumber++) + F(" from ") + meshInstance.getMeshName() + meshInstance.getNodeID() + F(" with AP MAC ") + WiFi.softAPmacAddress() + String('.')); } /** @@ -73,11 +72,11 @@ transmission_status_t manageResponse(const String &response, MeshBackendBase &me transmission_status_t statusCode = TS_TRANSMISSION_COMPLETE; // To get the actual class of the polymorphic meshInstance, do as follows (meshBackendCast replaces dynamic_cast since RTTI is disabled) - if (EspnowMeshBackend *espnowInstance = meshBackendCast(&meshInstance)) { - String transmissionEncrypted = espnowInstance->receivedEncryptedTransmission() ? ", Encrypted transmission" : ", Unencrypted transmission"; - Serial.print("ESP-NOW (" + espnowInstance->getSenderMac() + transmissionEncrypted + "): "); - } else if (TcpIpMeshBackend *tcpIpInstance = meshBackendCast(&meshInstance)) { - Serial.print("TCP/IP: "); + if (EspnowMeshBackend *espnowInstance = TypeCast::meshBackendCast(&meshInstance)) { + String transmissionEncrypted = espnowInstance->receivedEncryptedTransmission() ? F(", Encrypted transmission") : F(", Unencrypted transmission"); + Serial.print(String(F("ESP-NOW (")) + espnowInstance->getSenderMac() + transmissionEncrypted + F("): ")); + } else if (TcpIpMeshBackend *tcpIpInstance = TypeCast::meshBackendCast(&meshInstance)) { + Serial.print(F("TCP/IP: ")); // Getting the sent message like this will work as long as ONLY(!) TCP/IP is used. // With TCP/IP the response will follow immediately after the request, so the stored message will not have changed. @@ -86,7 +85,7 @@ transmission_status_t manageResponse(const String &response, MeshBackendBase &me Serial.print(F("Request sent: ")); Serial.println(tcpIpInstance->getCurrentMessage().substring(0, 100)); } else { - Serial.print("UNKNOWN!: "); + Serial.print(F("UNKNOWN!: ")); } /* Print out received message */ @@ -113,15 +112,15 @@ void networkFilter(int numberOfNetworks, MeshBackendBase &meshInstance) { /* Connect to any _suitable_ APs which contain meshInstance.getMeshName() */ if (meshNameIndex >= 0) { - uint64_t targetNodeID = stringToUint64(currentSSID.substring(meshNameIndex + meshInstance.getMeshName().length())); + uint64_t targetNodeID = TypeCast::stringToUint64(currentSSID.substring(meshNameIndex + meshInstance.getMeshName().length())); - if (targetNodeID < stringToUint64(meshInstance.getNodeID())) { - if (EspnowMeshBackend *espnowInstance = meshBackendCast(&meshInstance)) { + if (targetNodeID < TypeCast::stringToUint64(meshInstance.getNodeID())) { + if (EspnowMeshBackend *espnowInstance = TypeCast::meshBackendCast(&meshInstance)) { espnowInstance->connectionQueue().push_back(networkIndex); - } else if (TcpIpMeshBackend *tcpIpInstance = meshBackendCast(&meshInstance)) { + } else if (TcpIpMeshBackend *tcpIpInstance = TypeCast::meshBackendCast(&meshInstance)) { tcpIpInstance->connectionQueue().push_back(networkIndex); } else { - Serial.println(String(F("Invalid mesh backend!"))); + Serial.println(F("Invalid mesh backend!")); } } } @@ -142,14 +141,14 @@ void networkFilter(int numberOfNetworks, MeshBackendBase &meshInstance) { bool exampleTransmissionOutcomesUpdateHook(MeshBackendBase &meshInstance) { // The default hook only returns true and does nothing else. - if (TcpIpMeshBackend *tcpIpInstance = meshBackendCast(&meshInstance)) { + if (TcpIpMeshBackend *tcpIpInstance = TypeCast::meshBackendCast(&meshInstance)) { if (tcpIpInstance->latestTransmissionOutcomes().back().transmissionStatus() == TS_TRANSMISSION_COMPLETE) { // Our last request got a response, so time to create a new request. - meshInstance.setMessage(String(F("Hello world request #")) + String(++requestNumber) + String(F(" from ")) - + meshInstance.getMeshName() + meshInstance.getNodeID() + String(F("."))); + meshInstance.setMessage(String(F("Hello world request #")) + String(++requestNumber) + F(" from ") + + meshInstance.getMeshName() + meshInstance.getNodeID() + String('.')); } } else { - Serial.println(String(F("Invalid mesh backend!"))); + Serial.println(F("Invalid mesh backend!")); } return true; @@ -186,7 +185,7 @@ void setup() { // Storing our message in the TcpIpMeshBackend instance is not required, but can be useful for organizing code, especially when using many TcpIpMeshBackend instances. // Note that calling the multi-recipient tcpIpNode.attemptTransmission will replace the stored message with whatever message is transmitted. - tcpIpNode.setMessage(String(F("Hello world request #")) + String(requestNumber) + String(F(" from ")) + tcpIpNode.getMeshName() + tcpIpNode.getNodeID() + String(F("."))); + tcpIpNode.setMessage(String(F("Hello world request #")) + String(requestNumber) + F(" from ") + tcpIpNode.getMeshName() + tcpIpNode.getNodeID() + String('.')); tcpIpNode.setTransmissionOutcomesUpdateHook(exampleTransmissionOutcomesUpdateHook); } @@ -217,7 +216,7 @@ void loop() { } else if (transmissionOutcome.transmissionStatus() == TS_TRANSMISSION_COMPLETE) { // No need to do anything, transmission was successful. } else { - Serial.println(String(F("Invalid transmission status for ")) + transmissionOutcome.SSID() + String(F("!"))); + Serial.println(String(F("Invalid transmission status for ")) + transmissionOutcome.SSID() + String('!')); assert(F("Invalid transmission status returned from responseHandler!") && false); } } diff --git a/libraries/ESP8266WiFiMesh/src/CryptoInterface.cpp b/libraries/ESP8266WiFiMesh/src/CryptoInterface.cpp index 786dade93f..449be1d331 100644 --- a/libraries/ESP8266WiFiMesh/src/CryptoInterface.cpp +++ b/libraries/ESP8266WiFiMesh/src/CryptoInterface.cpp @@ -28,6 +28,8 @@ #include +namespace TypeCast = MeshTypeConversionFunctions; + namespace { size_t _ctMinDataLength = 0; @@ -130,7 +132,7 @@ namespace uint8_t hmac[hmacLength]; createBearsslHmac(hashType, message.c_str(), message.length(), hashKey, hashKeyLength, hmac, hmacLength); - return uint8ArrayToHexString(hmac, hmacLength); + return TypeCast::uint8ArrayToHexString(hmac, hmacLength); } void *createBearsslHmacCT(const br_hash_class *hashType, const void *data, const size_t dataLength, const void *hashKey, const size_t hashKeyLength, void *resultArray, const size_t outputLength) @@ -182,7 +184,7 @@ namespace uint8_t hmac[hmacLength]; createBearsslHmacCT(hashType, message.c_str(), message.length(), hashKey, hashKeyLength, hmac, hmacLength); - return uint8ArrayToHexString(hmac, hmacLength); + return TypeCast::uint8ArrayToHexString(hmac, hmacLength); } } @@ -190,14 +192,14 @@ namespace CryptoInterface { void setCtMinDataLength(const size_t ctMinDataLength) { - assert(ctMaxDataLength() - ctMinDataLength <= ctMaxDiff); + assert(ctMaxDataLength() - ctMinDataLength <= CT_MAX_DIFF); _ctMinDataLength = ctMinDataLength; } size_t ctMinDataLength() {return _ctMinDataLength;} void setCtMaxDataLength(const size_t ctMaxDataLength) { - assert(ctMaxDataLength - ctMinDataLength() <= ctMaxDiff); + assert(ctMaxDataLength - ctMinDataLength() <= CT_MAX_DIFF); _ctMaxDataLength = ctMaxDataLength; } size_t ctMaxDataLength() {return _ctMaxDataLength;} @@ -230,7 +232,7 @@ namespace CryptoInterface { uint8_t hash[MD5_NATURAL_LENGTH]; md5Hash(message.c_str(), message.length(), hash); - return uint8ArrayToHexString(hash, MD5_NATURAL_LENGTH); + return TypeCast::uint8ArrayToHexString(hash, MD5_NATURAL_LENGTH); } void *md5Hmac(const void *data, const size_t dataLength, const void *hashKey, const size_t hashKeyLength, void *resultArray, const size_t outputLength) @@ -275,7 +277,7 @@ namespace CryptoInterface { uint8_t hash[SHA1_NATURAL_LENGTH]; sha1Hash(message.c_str(), message.length(), hash); - return uint8ArrayToHexString(hash, SHA1_NATURAL_LENGTH); + return TypeCast::uint8ArrayToHexString(hash, SHA1_NATURAL_LENGTH); } void *sha1Hmac(const void *data, const size_t dataLength, const void *hashKey, const size_t hashKeyLength, void *resultArray, const size_t outputLength) @@ -315,7 +317,7 @@ namespace CryptoInterface { uint8_t hash[SHA224_NATURAL_LENGTH]; sha224Hash(message.c_str(), message.length(), hash); - return uint8ArrayToHexString(hash, SHA224_NATURAL_LENGTH); + return TypeCast::uint8ArrayToHexString(hash, SHA224_NATURAL_LENGTH); } void *sha224Hmac(const void *data, const size_t dataLength, const void *hashKey, const size_t hashKeyLength, void *resultArray, const size_t outputLength) @@ -355,7 +357,7 @@ namespace CryptoInterface { uint8_t hash[SHA256_NATURAL_LENGTH]; sha256Hash(message.c_str(), message.length(), hash); - return uint8ArrayToHexString(hash, SHA256_NATURAL_LENGTH); + return TypeCast::uint8ArrayToHexString(hash, SHA256_NATURAL_LENGTH); } void *sha256Hmac(const void *data, const size_t dataLength, const void *hashKey, const size_t hashKeyLength, void *resultArray, const size_t outputLength) @@ -395,7 +397,7 @@ namespace CryptoInterface { uint8_t hash[SHA384_NATURAL_LENGTH]; sha384Hash(message.c_str(), message.length(), hash); - return uint8ArrayToHexString(hash, SHA384_NATURAL_LENGTH); + return TypeCast::uint8ArrayToHexString(hash, SHA384_NATURAL_LENGTH); } void *sha384Hmac(const void *data, const size_t dataLength, const void *hashKey, const size_t hashKeyLength, void *resultArray, const size_t outputLength) @@ -435,7 +437,7 @@ namespace CryptoInterface { uint8_t hash[SHA512_NATURAL_LENGTH]; sha512Hash(message.c_str(), message.length(), hash); - return uint8ArrayToHexString(hash, SHA512_NATURAL_LENGTH); + return TypeCast::uint8ArrayToHexString(hash, SHA512_NATURAL_LENGTH); } void *sha512Hmac(const void *data, const size_t dataLength, const void *hashKey, const size_t hashKeyLength, void *resultArray, const size_t outputLength) @@ -475,7 +477,7 @@ namespace CryptoInterface { uint8_t hash[MD5SHA1_NATURAL_LENGTH]; md5sha1Hash(message.c_str(), message.length(), hash); - return uint8ArrayToHexString(hash, MD5SHA1_NATURAL_LENGTH); + return TypeCast::uint8ArrayToHexString(hash, MD5SHA1_NATURAL_LENGTH); } diff --git a/libraries/ESP8266WiFiMesh/src/CryptoInterface.h b/libraries/ESP8266WiFiMesh/src/CryptoInterface.h index 90ee9b90ec..b29d93abdc 100644 --- a/libraries/ESP8266WiFiMesh/src/CryptoInterface.h +++ b/libraries/ESP8266WiFiMesh/src/CryptoInterface.h @@ -71,7 +71,7 @@ namespace CryptoInterface constexpr uint8_t ENCRYPTION_KEY_LENGTH = 32; - constexpr uint32_t ctMaxDiff = 1073741823; // 2^30 - 1 + constexpr uint32_t CT_MAX_DIFF = 1073741823; // 2^30 - 1 /** * This function allows for fine-tuning of the specifications for the constant time calculations. @@ -672,7 +672,7 @@ namespace CryptoInterface // #################### HKDF #################### /** - * KDF are functions that takes a variable length input, and provide a variable length output, meant to be used to derive subkeys from a master key. + * KDFs (key derivation functions) are functions that takes a variable length input, and provide a variable length output, meant to be used to derive subkeys from a master key. * HKDF is a KDF defined by RFC 5869. It is based on HMAC. The provided implementation uses SHA-256 as the underlying hash function. * * This function initializes the HKDF implementation with the input data to use for HKDF processing. @@ -755,7 +755,7 @@ namespace CryptoInterface * * @param data An array containing the data to encrypt. The encrypted data is generated in place, so when the function returns the data array will contain the encrypted data. * @param dataLength The length of the data array in bytes. - * @param key The secret encryption key to use. + * @param key The secret encryption key to use. Must be 32 bytes (ENCRYPTION_KEY_LENGTH) long. * @param keySalt The salt to use when generating a subkey from key. Note that the same salt must be used during decryption as during encryption. Set to nullptr to prevent subkey generation. * @param keySaltLength The length of keySalt in bytes. * @param resultingNonce The array that will store the nonce generated during encryption. Must be able to contain at least 12 bytes. The nonce is not secret and must be passed to the decryption function. @@ -780,7 +780,7 @@ namespace CryptoInterface * * @param data An array containing the data to decrypt. The decrypted data is generated in place, so when the function returns the data array will contain the decrypted data. * @param dataLength The length of the data array in bytes. - * @param key The secret encryption key to use. + * @param key The secret encryption key to use. Must be 32 bytes (ENCRYPTION_KEY_LENGTH) long. * @param keySalt The salt to use when generating a subkey from key. Note that the same salt must be used during decryption as during encryption. Set to nullptr to prevent subkey generation. * @param keySaltLength The length of keySalt in bytes. * @param encryptionNonce An array containing the nonce that was generated during encryption. The nonce should be 12 bytes. diff --git a/libraries/ESP8266WiFiMesh/src/ESP8266WiFiMesh.cpp b/libraries/ESP8266WiFiMesh/src/ESP8266WiFiMesh.cpp index df283f1d5a..19e64d69f3 100644 --- a/libraries/ESP8266WiFiMesh/src/ESP8266WiFiMesh.cpp +++ b/libraries/ESP8266WiFiMesh/src/ESP8266WiFiMesh.cpp @@ -48,6 +48,8 @@ #include "ESP8266WiFiMesh.h" #include "TypeConversionFunctions.h" +namespace TypeCast = MeshTypeConversionFunctions; + #define SERVER_IP_ADDR "192.168.4.1" const IPAddress ESP8266WiFiMesh::emptyIP = IPAddress(); @@ -74,7 +76,7 @@ ESP8266WiFiMesh::ESP8266WiFiMesh(ESP8266WiFiMesh::requestHandlerType requestHand const String &nodeID, bool verboseMode, uint8 meshWiFiChannel, uint16_t serverPort) : _server(serverPort) { - updateNetworkNames(meshName, (nodeID != "" ? nodeID : uint64ToString(ESP.getChipId()))); + updateNetworkNames(meshName, (nodeID != "" ? nodeID : TypeCast::uint64ToString(ESP.getChipId()))); _requestHandler = requestHandler; _responseHandler = responseHandler; setWiFiChannel(meshWiFiChannel); diff --git a/libraries/ESP8266WiFiMesh/src/EncryptedConnectionData.cpp b/libraries/ESP8266WiFiMesh/src/EncryptedConnectionData.cpp index 55fc05d62d..486bf7844b 100644 --- a/libraries/ESP8266WiFiMesh/src/EncryptedConnectionData.cpp +++ b/libraries/ESP8266WiFiMesh/src/EncryptedConnectionData.cpp @@ -29,6 +29,7 @@ #include "MeshCryptoInterface.h" using EspnowProtocolInterpreter::espnowHashKeyLength; +namespace TypeCast = MeshTypeConversionFunctions; EncryptedConnectionData::EncryptedConnectionData(const uint8_t peerStaMac[6], const uint8_t peerApMac[6], uint64_t peerSessionKey, uint64_t ownSessionKey, const uint8_t hashKey[espnowHashKeyLength]) : _peerSessionKey(peerSessionKey), _ownSessionKey(ownSessionKey) @@ -97,7 +98,7 @@ void EncryptedConnectionData::setPeerApMac(const uint8_t *peerApMac) bool EncryptedConnectionData::connectedTo(const uint8_t *peerMac) const { - if(macEqual(peerMac, _peerStaMac) || macEqual(peerMac, _peerApMac)) + if(MeshUtilityFunctions::macEqual(peerMac, _peerStaMac) || MeshUtilityFunctions::macEqual(peerMac, _peerApMac)) { return true; } @@ -130,12 +131,12 @@ uint64_t EncryptedConnectionData::incrementSessionKey(uint64_t sessionKey, const { uint8_t inputArray[8] {0}; uint8_t hmacArray[CryptoInterface::SHA256_NATURAL_LENGTH] {0}; - CryptoInterface::sha256Hmac(uint64ToUint8Array(sessionKey, inputArray), 8, hashKey, hashKeyLength, hmacArray, CryptoInterface::SHA256_NATURAL_LENGTH); + CryptoInterface::sha256Hmac(TypeCast::uint64ToUint8Array(sessionKey, inputArray), 8, hashKey, hashKeyLength, hmacArray, CryptoInterface::SHA256_NATURAL_LENGTH); /* HMAC truncation should be OK since hmac sha256 is a PRF and we are truncating to the leftmost (MSB) bits. PRF: https://crypto.stackexchange.com/questions/26410/whats-the-gcm-sha-256-of-a-tls-protocol/26434#26434 Truncate to leftmost bits: https://tools.ietf.org/html/rfc2104#section-5 */ - uint64_t newLeftmostBits = uint8ArrayToUint64(hmacArray) & EspnowProtocolInterpreter::uint64LeftmostBits; + uint64_t newLeftmostBits = TypeCast::uint8ArrayToUint64(hmacArray) & EspnowProtocolInterpreter::uint64LeftmostBits; if(newLeftmostBits == 0) newLeftmostBits = ((uint64_t)RANDOM_REG32 | (1 << 31)) << 32; // We never want newLeftmostBits == 0 since that would indicate an unencrypted transmission. @@ -158,13 +159,13 @@ String EncryptedConnectionData::serialize() const // Returns: {"connectionState":{"duration":"123","password":"abc","ownSK":"1A2","peerSK":"3B4","peerStaMac":"F2","peerApMac":"E3"}} return - JsonTranslator::jsonConnectionState - + (temporary() ? JsonTranslator::jsonDuration + "\"" + String(temporary()->remainingDuration()) + "\"," : "") - + JsonTranslator::jsonDesync + "\"" + String(desync()) + "\"," - + JsonTranslator::jsonOwnSessionKey + "\"" + uint64ToString(getOwnSessionKey()) + "\"," - + JsonTranslator::jsonPeerSessionKey + "\"" + uint64ToString(getPeerSessionKey()) + "\"," - + JsonTranslator::jsonPeerStaMac + "\"" + macToString(_peerStaMac) + "\"," - + JsonTranslator::jsonPeerApMac + "\"" + macToString(_peerApMac) + "\"}}"; + String(FPSTR(JsonTranslator::jsonConnectionState)) + + (temporary() ? String(FPSTR(JsonTranslator::jsonDuration)) + '\"' + String(temporary()->remainingDuration()) + F("\",") : emptyString) + + FPSTR(JsonTranslator::jsonDesync) + '\"' + String(desync()) + F("\",") + + FPSTR(JsonTranslator::jsonOwnSessionKey) + '\"' + TypeCast::uint64ToString(getOwnSessionKey()) + F("\",") + + FPSTR(JsonTranslator::jsonPeerSessionKey) + '\"' + TypeCast::uint64ToString(getPeerSessionKey()) + F("\",") + + FPSTR(JsonTranslator::jsonPeerStaMac) + '\"' + TypeCast::macToString(_peerStaMac) + F("\",") + + FPSTR(JsonTranslator::jsonPeerApMac) + '\"' + TypeCast::macToString(_peerApMac) + F("\"}}"); } const ExpiringTimeTracker *EncryptedConnectionData::temporary() const diff --git a/libraries/ESP8266WiFiMesh/src/EspnowMeshBackend.cpp b/libraries/ESP8266WiFiMesh/src/EspnowMeshBackend.cpp index 872feb9942..ee10b34161 100644 --- a/libraries/ESP8266WiFiMesh/src/EspnowMeshBackend.cpp +++ b/libraries/ESP8266WiFiMesh/src/EspnowMeshBackend.cpp @@ -31,6 +31,8 @@ extern "C" { using EspnowProtocolInterpreter::espnowEncryptedConnectionKeyLength; using EspnowProtocolInterpreter::espnowHashKeyLength; +namespace TypeCast = MeshTypeConversionFunctions; + static const uint8_t maxEncryptedConnections = 6; // This is limited by the ESP-NOW API. Max 6 in AP or AP+STA mode. Max 10 in STA mode. See "ESP-NOW User Guide" for more info. static const uint64_t uint64MSB = 0x8000000000000000; @@ -62,7 +64,7 @@ uint32_t EspnowMeshBackend::_encryptionRequestTimeoutMs = 300; bool EspnowMeshBackend::_espnowSendConfirmed = false; -String EspnowMeshBackend::_ongoingPeerRequestNonce = ""; +String EspnowMeshBackend::_ongoingPeerRequestNonce; uint8_t EspnowMeshBackend::_ongoingPeerRequestMac[6] = {0}; EspnowMeshBackend *EspnowMeshBackend::_ongoingPeerRequester = nullptr; encrypted_connection_status_t EspnowMeshBackend::_ongoingPeerRequestResult = ECS_MAX_CONNECTIONS_REACHED_SELF; @@ -116,7 +118,7 @@ EspnowMeshBackend::EspnowMeshBackend(requestHandlerType requestHandler, response encryptedConnections.reserve(maxEncryptedConnections); setBroadcastFilter(broadcastFilter); - setSSID(ssidPrefix, "", ssidSuffix); + setSSID(ssidPrefix, emptyString, ssidSuffix); setMeshPassword(meshPassword); setVerboseModeState(verboseMode); setWiFiChannel(meshWiFiChannel); @@ -165,7 +167,7 @@ bool EspnowMeshBackend::activateEspnow() if (esp_now_init()==0) { if(_espnowEncryptionKokSet && esp_now_set_kok(_espnowEncryptionKok, espnowEncryptedConnectionKeyLength)) // esp_now_set_kok returns 0 on success. - warningPrint("Failed to set ESP-NOW KoK!"); + warningPrint(String(F("Failed to set ESP-NOW KoK!"))); if(getEspnowRequestManager() == nullptr) { @@ -176,23 +178,23 @@ bool EspnowMeshBackend::activateEspnow() esp_now_register_send_cb([](uint8_t* mac, uint8_t sendStatus) { if(_espnowSendConfirmed) return; - else if(!sendStatus && macEqual(mac, _transmissionTargetBSSID)) // sendStatus == 0 when send was OK. + else if(!sendStatus && MeshUtilityFunctions::macEqual(mac, _transmissionTargetBSSID)) // sendStatus == 0 when send was OK. _espnowSendConfirmed = true; // We do not want to reset this to false. That only happens before transmissions. Otherwise subsequent failed send attempts may obscure an initial successful one. }); // Role must be set before adding peers. Cannot be changed while having peers. // With ESP_NOW_ROLE_CONTROLLER, we always transmit from the station interface, which gives predictability. if(esp_now_set_self_role(ESP_NOW_ROLE_CONTROLLER)) // esp_now_set_self_role returns 0 on success. - warningPrint("Failed to set ESP-NOW role! Maybe ESP-NOW peers are already added?"); + warningPrint(String(F("Failed to set ESP-NOW role! Maybe ESP-NOW peers are already added?"))); - verboseModePrint("ESP-NOW activated."); - verboseModePrint("My ESP-NOW STA MAC: " + WiFi.macAddress() + '\n'); // Get the station MAC address. The softAP MAC is different. + verboseModePrint(String(F("ESP-NOW activated."))); + verboseModePrint(String(F("My ESP-NOW STA MAC: ")) + WiFi.macAddress() + '\n'); // Get the station MAC address. The softAP MAC is different. return true; } else { - warningPrint("ESP-NOW init failed!"); + warningPrint(String(F("ESP-NOW init failed!"))); return false; } } @@ -224,7 +226,7 @@ std::vector & EspnowMeshBackend::connectionQueue() MutexTracker connectionQueueMutexTracker(_espnowConnectionQueueMutex); if(!connectionQueueMutexTracker.mutexCaptured()) { - assert(false && "ERROR! connectionQueue locked. Don't call connectionQueue() from callbacks other than NetworkFilter as this may corrupt program state!"); + assert(false && String(F("ERROR! connectionQueue locked. Don't call connectionQueue() from callbacks other than NetworkFilter as this may corrupt program state!"))); } return _connectionQueue; @@ -253,7 +255,7 @@ void EspnowMeshBackend::performEspnowMaintenance(uint32_t estimatedMaxDuration) MutexTracker mutexTracker(_espnowTransmissionMutex, handlePostponedRemovals); if(!mutexTracker.mutexCaptured()) { - assert(false && "ERROR! Transmission in progress. Don't call performEspnowMaintenance from callbacks as this may corrupt program state! Aborting."); + assert(false && String(F("ERROR! Transmission in progress. Don't call performEspnowMaintenance from callbacks as this may corrupt program state! Aborting."))); return; } @@ -429,6 +431,7 @@ void EspnowMeshBackend::espnowReceiveCallbackWrapper(uint8_t *macaddr, uint8_t * if(useEncryptedMessages()) { // chacha20Poly1305Decrypt decrypts dataArray in place. + // We are using the protocol bytes as a key salt. if(!CryptoInterface::chacha20Poly1305Decrypt(dataArray + espnowMetadataSize(), len - espnowMetadataSize(), getEspnowMessageEncryptionKey(), dataArray, espnowProtocolBytesSize, dataArray + espnowProtocolBytesSize, dataArray + espnowProtocolBytesSize + 12)) { @@ -436,11 +439,11 @@ void EspnowMeshBackend::espnowReceiveCallbackWrapper(uint8_t *macaddr, uint8_t * } } - uint64_t uint64StationMac = macToUint64(macaddr); + uint64_t uint64StationMac = TypeCast::macToUint64(macaddr); bool transmissionEncrypted = usesEncryption(receivedMessageID); // Useful when debugging the protocol - //Serial.print("Received from Mac: " + macToString(macaddr) + " ID: " + uint64ToString(receivedMessageID)); + //Serial.print("Received from Mac: " + TypeCast::macToString(macaddr) + " ID: " + TypeCast::uint64ToString(receivedMessageID)); //Serial.println(transmissionEncrypted ? " Encrypted" : " Unencrypted"); if(messageType == 'Q' || messageType == 'B') // Question (request) or Broadcast @@ -512,7 +515,7 @@ void EspnowMeshBackend::espnowReceiveCallbackWrapper(uint8_t *macaddr, uint8_t * { uint8_t macArray[6] = { 0 }; - requestSender->espnowReceiveCallback(uint64ToMac(requestMac, macArray), dataArray, len); + requestSender->espnowReceiveCallback(TypeCast::uint64ToMac(requestMac, macArray), dataArray, len); } } else if(messageType == 'S') // Synchronization request @@ -555,7 +558,7 @@ void EspnowMeshBackend::handlePeerRequest(uint8_t *macaddr, uint8_t *dataArray, int32_t messageHeaderEndIndex = message.indexOf(':'); String messageHeader = message.substring(0, messageHeaderEndIndex + 1); - if(messageHeader == encryptedConnectionVerificationHeader) + if(messageHeader == FPSTR(encryptedConnectionVerificationHeader)) { if(encryptedCorrectly) { @@ -563,13 +566,13 @@ void EspnowMeshBackend::handlePeerRequest(uint8_t *macaddr, uint8_t *dataArray, String connectionRequestType = message.substring(messageHeaderEndIndex + 1, connectionRequestTypeEndIndex + 1); connectionLogIterator encryptedConnection = connectionLogEndIterator(); if(!getEncryptedConnectionIterator(macaddr, encryptedConnection)) - assert(false && "We must have an encrypted connection if we received an encryptedConnectionVerificationHeader which was encryptedCorrectly."); + assert(false && String(F("We must have an encrypted connection if we received an encryptedConnectionVerificationHeader which was encryptedCorrectly."))); - if(connectionRequestType == encryptionRequestHeader) + if(connectionRequestType == FPSTR(encryptionRequestHeader)) { temporaryEncryptedConnectionToPermanent(macaddr); } - else if(connectionRequestType == temporaryEncryptionRequestHeader) + else if(connectionRequestType == FPSTR(temporaryEncryptionRequestHeader)) { if(encryptedConnection->temporary()) // Should not change duration for existing permanent connections. { @@ -582,27 +585,27 @@ void EspnowMeshBackend::handlePeerRequest(uint8_t *macaddr, uint8_t *dataArray, } else { - assert(false && "Unknown P-type verification message received!"); + assert(false && String(F("Unknown P-type verification message received!"))); } } } - else if(messageHeader == encryptionRequestHeader || messageHeader == temporaryEncryptionRequestHeader) + else if(messageHeader == FPSTR(encryptionRequestHeader) || messageHeader == FPSTR(temporaryEncryptionRequestHeader)) { // If there is a espnowRequestManager, get it if(EspnowMeshBackend *currentEspnowRequestManager = getEspnowRequestManager()) { - String requestNonce = ""; + String requestNonce; if(JsonTranslator::getNonce(message, requestNonce) && requestNonce.length() >= 12) // The destination MAC address requires 12 characters. { uint8_t destinationMac[6] = {0}; - stringToMac(requestNonce, destinationMac); + TypeCast::stringToMac(requestNonce, destinationMac); uint8_t apMac[6] {0}; WiFi.softAPmacAddress(apMac); bool correctDestination = false; - if(macEqual(destinationMac, apMac)) + if(MeshUtilityFunctions::macEqual(destinationMac, apMac)) { correctDestination = true; } @@ -611,7 +614,7 @@ void EspnowMeshBackend::handlePeerRequest(uint8_t *macaddr, uint8_t *dataArray, uint8_t staMac[6] {0}; WiFi.macAddress(staMac); - if(macEqual(destinationMac, staMac)) + if(MeshUtilityFunctions::macEqual(destinationMac, staMac)) { correctDestination = true; } @@ -624,14 +627,14 @@ void EspnowMeshBackend::handlePeerRequest(uint8_t *macaddr, uint8_t *dataArray, } } } - else if(messageHeader == encryptedConnectionRemovalRequestHeader) + else if(messageHeader == FPSTR(encryptedConnectionRemovalRequestHeader)) { if(encryptedCorrectly) removeEncryptedConnection(macaddr); } else { - assert(false && "Unknown P-type message received!"); + assert(false && String(F("Unknown P-type message received!"))); } } } @@ -646,10 +649,10 @@ void EspnowMeshBackend::handlePeerRequestConfirmation(uint8_t *macaddr, uint8_t using namespace EspnowProtocolInterpreter; - if(_ongoingPeerRequestNonce != "") + if(!_ongoingPeerRequestNonce.isEmpty()) { String message = espnowGetMessageContent(dataArray, len); - String requestNonce = ""; + String requestNonce; if(JsonTranslator::getNonce(message, requestNonce) && requestNonce == _ongoingPeerRequestNonce) { @@ -659,7 +662,7 @@ void EspnowMeshBackend::handlePeerRequestConfirmation(uint8_t *macaddr, uint8_t uint8_t apMacArray[6] = { 0 }; espnowGetTransmissionMac(dataArray, apMacArray); - if(messageHeader == basicConnectionInfoHeader) + if(messageHeader == FPSTR(basicConnectionInfoHeader)) { // encryptedConnectionEstablished(_ongoingPeerRequestResult) means we have already received a basicConnectionInfoHeader if(!encryptedConnectionEstablished(_ongoingPeerRequestResult) && @@ -691,13 +694,13 @@ void EspnowMeshBackend::handlePeerRequestConfirmation(uint8_t *macaddr, uint8_t if(!encryptedConnectionEstablished(_ongoingPeerRequestResult)) { // Adding connection failed, abort ongoing peer request. - _ongoingPeerRequestNonce = ""; + _ongoingPeerRequestNonce.clear(); } } } - else if(messageHeader == encryptedConnectionInfoHeader || messageHeader == softLimitEncryptedConnectionInfoHeader) + else if(messageHeader == FPSTR(encryptedConnectionInfoHeader) || messageHeader == FPSTR(softLimitEncryptedConnectionInfoHeader)) { - String messagePassword = ""; + String messagePassword; if(JsonTranslator::getPassword(messageBody, messagePassword) && messagePassword == _ongoingPeerRequester->getMeshPassword()) { @@ -711,33 +714,33 @@ void EspnowMeshBackend::handlePeerRequestConfirmation(uint8_t *macaddr, uint8_t encryptedConnection->setPeerSessionKey(peerSessionKey); encryptedConnection->setOwnSessionKey(ownSessionKey); - if(messageHeader == encryptedConnectionInfoHeader) + if(messageHeader == FPSTR(encryptedConnectionInfoHeader)) _ongoingPeerRequestResult = ECS_CONNECTION_ESTABLISHED; - else if(messageHeader == softLimitEncryptedConnectionInfoHeader) + else if(messageHeader == FPSTR(softLimitEncryptedConnectionInfoHeader)) _ongoingPeerRequestResult = ECS_SOFT_LIMIT_CONNECTION_ESTABLISHED; else - assert(false && "Unknown _ongoingPeerRequestResult!"); + assert(false && String(F("Unknown _ongoingPeerRequestResult!"))); } else { _ongoingPeerRequestResult = ECS_REQUEST_TRANSMISSION_FAILED; } - _ongoingPeerRequestNonce = ""; + _ongoingPeerRequestNonce.clear(); } } - else if(messageHeader == maxConnectionsReachedHeader) + else if(messageHeader == FPSTR(maxConnectionsReachedHeader)) { if(JsonTranslator::verifyEncryptionRequestHmac(message, macaddr, apMacArray, _ongoingPeerRequester->getEspnowHashKey(), espnowHashKeyLength)) { _ongoingPeerRequestResult = ECS_MAX_CONNECTIONS_REACHED_PEER; - _ongoingPeerRequestNonce = ""; + _ongoingPeerRequestNonce.clear(); } } else { - assert(messageHeader == basicConnectionInfoHeader || messageHeader == encryptedConnectionInfoHeader || - messageHeader == softLimitEncryptedConnectionInfoHeader || messageHeader == maxConnectionsReachedHeader); + assert(messageHeader == FPSTR(basicConnectionInfoHeader) || messageHeader == FPSTR(encryptedConnectionInfoHeader) || + messageHeader == FPSTR(softLimitEncryptedConnectionInfoHeader) || messageHeader == FPSTR(maxConnectionsReachedHeader)); } } } @@ -770,7 +773,7 @@ void EspnowMeshBackend::espnowReceiveCallback(uint8_t *macaddr, uint8_t *dataArr char messageType = espnowGetMessageType(dataArray); uint8_t transmissionsRemaining = espnowGetTransmissionsRemaining(dataArray); - uint64_t uint64Mac = macToUint64(macaddr); + uint64_t uint64Mac = TypeCast::macToUint64(macaddr); // The MAC is 6 bytes so two bytes of uint64Mac are free. We must include the messageType there since it is possible that we will // receive both a request and a response that shares the same messageID from the same uint64Mac, being distinguished only by the messageType. @@ -915,15 +918,17 @@ bool EspnowMeshBackend::encryptedConnectionEstablished(encrypted_connection_stat return connectionStatus > 0; } -uint32_t EspnowMeshBackend::logEntryLifetimeMs() +void EspnowMeshBackend::setLogEntryLifetimeMs(uint32_t logEntryLifetimeMs) { - return _logEntryLifetimeMs; + _logEntryLifetimeMs = logEntryLifetimeMs; } +uint32_t EspnowMeshBackend::logEntryLifetimeMs() { return _logEntryLifetimeMs; } -uint32_t EspnowMeshBackend::broadcastResponseTimeoutMs() +void EspnowMeshBackend::setBroadcastResponseTimeoutMs(uint32_t broadcastResponseTimeoutMs) { - return _broadcastResponseTimeoutMs; + _broadcastResponseTimeoutMs = broadcastResponseTimeoutMs; } +uint32_t EspnowMeshBackend::broadcastResponseTimeoutMs() { return _broadcastResponseTimeoutMs; } void EspnowMeshBackend::setCriticalHeapLevelBuffer(uint32_t bufferInBytes) { @@ -1016,7 +1021,7 @@ uint64_t EspnowMeshBackend::generateMessageID(EncryptedConnectionLog *encryptedC uint64_t EspnowMeshBackend::createSessionKey() { - uint64_t newSessionKey = randomUint64(); + uint64_t newSessionKey = MeshUtilityFunctions::randomUint64(); return EspnowProtocolInterpreter::usesEncryption(newSessionKey) ? newSessionKey : (newSessionKey | ((uint64_t)RANDOM_REG32) << 32 | uint64MSB); } @@ -1063,11 +1068,11 @@ transmission_status_t EspnowMeshBackend::espnowSendToNode(const String &message, uint8_t encryptedMac[6] {0}; encryptedConnection->getEncryptedPeerMac(encryptedMac); - assert(esp_now_is_peer_exist(encryptedMac) > 0 && "ERROR! Attempting to send content marked as encrypted via unencrypted connection!"); + assert(esp_now_is_peer_exist(encryptedMac) > 0 && String(F("ERROR! Attempting to send content marked as encrypted via unencrypted connection!"))); if(encryptedConnection->desync()) { - espnowSendToNodeUnsynchronized(synchronizationRequestHeader, encryptedMac, 'S', generateMessageID(encryptedConnection), espnowInstance); + espnowSendToNodeUnsynchronized(FPSTR(synchronizationRequestHeader), encryptedMac, 'S', generateMessageID(encryptedConnection), espnowInstance); if(encryptedConnection->desync()) { @@ -1090,7 +1095,7 @@ transmission_status_t EspnowMeshBackend::espnowSendToNodeUnsynchronized(const St MutexTracker mutexTracker(_espnowSendToNodeMutex); if(!mutexTracker.mutexCaptured()) { - assert(false && "ERROR! espnowSendToNode already in progress. Don't call espnowSendToNode from callbacks as this will make it impossible to know which transmissions succeed! Aborting."); + assert(false && String(F("ERROR! espnowSendToNode already in progress. Don't call espnowSendToNode from callbacks as this will make it impossible to know which transmissions succeed! Aborting."))); return TS_TRANSMISSION_FAILED; } @@ -1131,7 +1136,7 @@ transmission_status_t EspnowMeshBackend::espnowSendToNodeUnsynchronized(const St // If we are sending the last transmission of a request we should store the sent request in the log no matter if we receive an ack for the final transmission or not. // That way we will always be ready to receive the response to the request when there is a chance the request message was transmitted successfully, // even if the final ack for the request message was lost. - storeSentRequest(macToUint64(_transmissionTargetBSSID), messageID, RequestData(*espnowInstance)); + storeSentRequest(TypeCast::macToUint64(_transmissionTargetBSSID), messageID, RequestData(*espnowInstance)); } ////// Create transmission array ////// @@ -1232,9 +1237,9 @@ transmission_status_t EspnowMeshBackend::espnowSendToNodeUnsynchronized(const St { _transmissionsFailed++; - staticVerboseModePrint("espnowSendToNode failed!"); - staticVerboseModePrint("Transmission #: " + String(transmissionsRequired - transmissionsRemaining) + "/" + String(transmissionsRequired)); - staticVerboseModePrint("Transmission fail rate (up) " + String(getTransmissionFailRate())); + staticVerboseModePrint(String(F("espnowSendToNode failed!"))); + staticVerboseModePrint(String(F("Transmission #: ")) + String(transmissionsRequired - transmissionsRemaining) + String('/') + String(transmissionsRequired)); + staticVerboseModePrint(String(F("Transmission fail rate (up) ")) + String(getTransmissionFailRate())); if(messageStart && encryptedConnection && !usesConstantSessionKey(messageType) && encryptedConnection->getOwnSessionKey() == messageID) encryptedConnection->setDesync(true); @@ -1247,7 +1252,7 @@ transmission_status_t EspnowMeshBackend::espnowSendToNodeUnsynchronized(const St } while(transmissionsRemaining >= 0); // Useful when debugging the protocol - //staticVerboseModePrint("Sent to Mac: " + macToString(_transmissionTargetBSSID) + " ID: " + uint64ToString(messageID)); + //staticVerboseModePrint("Sent to Mac: " + TypeCast::macToString(_transmissionTargetBSSID) + " ID: " + TypeCast::uint64ToString(messageID)); return TS_TRANSMISSION_COMPLETE; } @@ -1267,7 +1272,7 @@ transmission_status_t EspnowMeshBackend::sendResponse(const String &message, uin if(encryptedConnection) { encryptedConnection->getEncryptedPeerMac(encryptedMac); - assert(esp_now_is_peer_exist(encryptedMac) > 0 && "ERROR! Attempting to send content marked as encrypted via unencrypted connection!"); + assert(esp_now_is_peer_exist(encryptedMac) > 0 && String(F("ERROR! Attempting to send content marked as encrypted via unencrypted connection!"))); } return espnowSendToNodeUnsynchronized(message, encryptedConnection ? encryptedMac : targetBSSID, 'A', requestID, this); @@ -1387,7 +1392,7 @@ void EspnowMeshBackend::setUseEncryptedMessages(bool useEncryptedMessages) MutexTracker mutexTracker(_espnowSendToNodeMutex); if(!mutexTracker.mutexCaptured()) { - assert(false && "ERROR! espnowSendToNode in progress. Don't call setUseEncryptedMessages from non-hook callbacks since this may modify the ESP-NOW transmission parameters during ongoing transmissions! Aborting."); + assert(false && String(F("ERROR! espnowSendToNode in progress. Don't call setUseEncryptedMessages from non-hook callbacks since this may modify the ESP-NOW transmission parameters during ongoing transmissions! Aborting."))); } _useEncryptedMessages = useEncryptedMessages; @@ -1398,7 +1403,7 @@ bool EspnowMeshBackend::verifyPeerSessionKey(uint64_t sessionKey, const uint8_t { if(EncryptedConnectionLog *encryptedConnection = getEncryptedConnection(peerMac)) { - return verifyPeerSessionKey(sessionKey, *encryptedConnection, macToUint64(peerMac), messageType); + return verifyPeerSessionKey(sessionKey, *encryptedConnection, TypeCast::macToUint64(peerMac), messageType); } return false; @@ -1479,7 +1484,7 @@ void EspnowMeshBackend::clearAllScheduledResponses() MutexTracker responsesToSendMutexTracker(_responsesToSendMutex); if(!responsesToSendMutexTracker.mutexCaptured()) { - assert(false && "ERROR! responsesToSend locked. Don't call clearAllScheduledResponses from callbacks as this may corrupt program state! Aborting."); + assert(false && String(F("ERROR! responsesToSend locked. Don't call clearAllScheduledResponses from callbacks as this may corrupt program state! Aborting."))); } responsesToSend.clear(); @@ -1490,12 +1495,13 @@ void EspnowMeshBackend::deleteScheduledResponsesByRecipient(const uint8_t *recip MutexTracker responsesToSendMutexTracker(_responsesToSendMutex); if(!responsesToSendMutexTracker.mutexCaptured()) { - assert(false && "ERROR! responsesToSend locked. Don't call deleteScheduledResponsesByRecipient from callbacks as this may corrupt program state! Aborting."); + assert(false && String(F("ERROR! responsesToSend locked. Don't call deleteScheduledResponsesByRecipient from callbacks as this may corrupt program state! Aborting."))); } for(auto responseIterator = responsesToSend.begin(); responseIterator != responsesToSend.end(); ) { - if(macEqual(responseIterator->getRecipientMac(), recipientMac) && (!encryptedOnly || EspnowProtocolInterpreter::usesEncryption(responseIterator->getRequestID()))) + if(MeshUtilityFunctions::macEqual(responseIterator->getRecipientMac(), recipientMac) && + (!encryptedOnly || EspnowProtocolInterpreter::usesEncryption(responseIterator->getRequestID()))) { responseIterator = responsesToSend.erase(responseIterator); } @@ -1509,7 +1515,7 @@ void EspnowMeshBackend::setSenderMac(uint8_t *macArray) std::copy_n(macArray, 6, _senderMac); } -String EspnowMeshBackend::getSenderMac() {return macToString(_senderMac);} +String EspnowMeshBackend::getSenderMac() {return TypeCast::macToString(_senderMac);} uint8_t *EspnowMeshBackend::getSenderMac(uint8_t *macArray) { std::copy_n(_senderMac, 6, macArray); @@ -1521,7 +1527,7 @@ void EspnowMeshBackend::setSenderAPMac(uint8_t *macArray) std::copy_n(macArray, 6, _senderAPMac); } -String EspnowMeshBackend::getSenderAPMac() {return macToString(_senderAPMac);} +String EspnowMeshBackend::getSenderAPMac() {return TypeCast::macToString(_senderAPMac);} uint8_t *EspnowMeshBackend::getSenderAPMac(uint8_t *macArray) { std::copy_n(_senderAPMac, 6, macArray); @@ -1639,7 +1645,7 @@ encrypted_connection_status_t EspnowMeshBackend::addTemporaryEncryptedConnection if(result == ECS_CONNECTION_ESTABLISHED) { if(!getEncryptedConnectionIterator(peerStaMac, encryptedConnection)) - assert(false && "No connection found despite being added in addTemporaryEncryptedConnection."); + assert(false && String(F("No connection found despite being added in addTemporaryEncryptedConnection."))); encryptedConnection->setRemainingDuration(duration); } @@ -1680,7 +1686,7 @@ void EspnowMeshBackend::handlePostponedRemovals() MutexTracker mutexTracker(_espnowTransmissionMutex); if(!mutexTracker.mutexCaptured()) { - assert(false && "ERROR! Transmission in progress. Don't call handlePostponedRemovals from callbacks as this may corrupt program state! Aborting."); + assert(false && String(F("ERROR! Transmission in progress. Don't call handlePostponedRemovals from callbacks as this may corrupt program state! Aborting."))); return; } @@ -1699,7 +1705,7 @@ encrypted_connection_status_t EspnowMeshBackend::requestEncryptedConnectionKerne MutexTracker mutexTracker(_espnowTransmissionMutex, handlePostponedRemovals); if(!mutexTracker.mutexCaptured()) { - assert(false && "ERROR! Transmission in progress. Don't call requestEncryptedConnection from callbacks as this may corrupt program state! Aborting."); + assert(false && String(F("ERROR! Transmission in progress. Don't call requestEncryptedConnection from callbacks as this may corrupt program state! Aborting."))); return ECS_REQUEST_TRANSMISSION_FAILED; } @@ -1715,7 +1721,8 @@ encrypted_connection_status_t EspnowMeshBackend::requestEncryptedConnectionKerne return ECS_MAX_CONNECTIONS_REACHED_SELF; } - String requestNonce = macToString(peerMac) + uint64ToString(randomUint64()) + uint64ToString(randomUint64()); + String requestNonce = TypeCast::macToString(peerMac) + TypeCast::uint64ToString(MeshUtilityFunctions::randomUint64()) + + TypeCast::uint64ToString(MeshUtilityFunctions::randomUint64()); _ongoingPeerRequestResult = ECS_REQUEST_TRANSMISSION_FAILED; _ongoingPeerRequestNonce = requestNonce; _ongoingPeerRequester = this; @@ -1723,27 +1730,27 @@ encrypted_connection_status_t EspnowMeshBackend::requestEncryptedConnectionKerne std::copy_n(peerMac, 6, _ongoingPeerRequestMac); String requestMessage = encryptionRequestBuilder(requestNonce, existingTimeTracker); - verboseModePrint("Sending encrypted connection request to: " + macToString(peerMac)); + verboseModePrint(String(F("Sending encrypted connection request to: ")) + TypeCast::macToString(peerMac)); if(espnowSendToNode(requestMessage, peerMac, 'P') == TS_TRANSMISSION_COMPLETE) { uint32_t startTime = millis(); // _ongoingPeerRequestNonce is set to "" when a peer confirmation response from the mac is received - while(millis() - startTime < getEncryptionRequestTimeout() && _ongoingPeerRequestNonce != "") + while(millis() - startTime < getEncryptionRequestTimeout() && !_ongoingPeerRequestNonce.isEmpty()) { // For obvious reasons dividing by exactly 10 is a good choice. ExpiringTimeTracker maxDurationTracker = ExpiringTimeTracker(getEncryptionRequestTimeout()/10); - sendPeerRequestConfirmations(&maxDurationTracker); // Must be called before delay() to ensure _ongoingPeerRequestNonce != "" is still true, so reciprocal peer request order is preserved. + sendPeerRequestConfirmations(&maxDurationTracker); // Must be called before delay() to ensure !_ongoingPeerRequestNonce.isEmpty() is still true, so reciprocal peer request order is preserved. delay(1); } } - if(_ongoingPeerRequestNonce != "") + if(!_ongoingPeerRequestNonce.isEmpty()) { // If nonce != "" we only received the basic connection info, so the pairing process is incomplete _ongoingPeerRequestResult = ECS_REQUEST_TRANSMISSION_FAILED; - _ongoingPeerRequestNonce = ""; + _ongoingPeerRequestNonce.clear(); } else if(encryptedConnectionEstablished(_ongoingPeerRequestResult)) { @@ -1752,23 +1759,23 @@ encrypted_connection_status_t EspnowMeshBackend::requestEncryptedConnectionKerne requestMessage = encryptionRequestBuilder(requestNonce, existingTimeTracker); else if(_ongoingPeerRequestResult == ECS_SOFT_LIMIT_CONNECTION_ESTABLISHED) // We will only get a soft limit connection. Adjust future actions based on this. - requestMessage = JsonTranslator::createEncryptionRequestHmacMessage(temporaryEncryptionRequestHeader, requestNonce, getEspnowHashKey(), + requestMessage = JsonTranslator::createEncryptionRequestHmacMessage(FPSTR(temporaryEncryptionRequestHeader), requestNonce, getEspnowHashKey(), espnowHashKeyLength, getAutoEncryptionDuration()); else - assert(false && "Unknown _ongoingPeerRequestResult during encrypted connection finalization!"); + assert(false && String(F("Unknown _ongoingPeerRequestResult during encrypted connection finalization!"))); int32_t messageHeaderEndIndex = requestMessage.indexOf(':'); String messageHeader = requestMessage.substring(0, messageHeaderEndIndex + 1); String messageBody = requestMessage.substring(messageHeaderEndIndex + 1); // If we do not get an ack within getEncryptionRequestTimeout() the peer has probably had the time to delete the temporary encrypted connection. - if(espnowSendToNode(encryptedConnectionVerificationHeader + requestMessage, peerMac, 'P') == TS_TRANSMISSION_COMPLETE + if(espnowSendToNode(String(FPSTR(encryptedConnectionVerificationHeader)) + requestMessage, peerMac, 'P') == TS_TRANSMISSION_COMPLETE && millis() - _ongoingPeerRequestEncryptionStart < getEncryptionRequestTimeout()) { EncryptedConnectionLog *encryptedConnection = getEncryptedConnection(peerMac); if(!encryptedConnection) { - assert(encryptedConnection && "requestEncryptedConnectionKernel cannot find an encrypted connection!"); + assert(encryptedConnection && String(F("requestEncryptedConnectionKernel cannot find an encrypted connection!"))); // requestEncryptedConnectionRemoval received. _ongoingPeerRequestResult = ECS_REQUEST_TRANSMISSION_FAILED; } @@ -1780,11 +1787,11 @@ encrypted_connection_status_t EspnowMeshBackend::requestEncryptedConnectionKerne else { // Finalize connection - if(messageHeader == encryptionRequestHeader) + if(messageHeader == FPSTR(encryptionRequestHeader)) { temporaryEncryptedConnectionToPermanent(peerMac); } - else if(messageHeader == temporaryEncryptionRequestHeader) + else if(messageHeader == FPSTR(temporaryEncryptionRequestHeader)) { if(encryptedConnection->temporary()) { @@ -1797,7 +1804,7 @@ encrypted_connection_status_t EspnowMeshBackend::requestEncryptedConnectionKerne } else { - assert(false && "Unknown messageHeader during encrypted connection finalization!"); + assert(false && String(F("Unknown messageHeader during encrypted connection finalization!"))); _ongoingPeerRequestResult = ECS_API_CALL_FAILED; } } @@ -1839,19 +1846,19 @@ String EspnowMeshBackend::flexibleEncryptionRequestBuilder(const uint32_t minDur uint32_t connectionDuration = minDurationMs >= existingTimeTracker.remainingDuration() ? minDurationMs : existingTimeTracker.remainingDuration(); - return createEncryptionRequestHmacMessage(temporaryEncryptionRequestHeader, requestNonce, hashKey, espnowHashKeyLength, connectionDuration); + return createEncryptionRequestHmacMessage(FPSTR(temporaryEncryptionRequestHeader), requestNonce, hashKey, espnowHashKeyLength, connectionDuration); } encrypted_connection_status_t EspnowMeshBackend::requestEncryptedConnection(uint8_t *peerMac) { using namespace std::placeholders; - return requestEncryptedConnectionKernel(peerMac, std::bind(defaultEncryptionRequestBuilder, EspnowProtocolInterpreter::encryptionRequestHeader, 0, getEspnowHashKey(), _1, _2)); + return requestEncryptedConnectionKernel(peerMac, std::bind(defaultEncryptionRequestBuilder, FPSTR(EspnowProtocolInterpreter::encryptionRequestHeader), 0, getEspnowHashKey(), _1, _2)); } encrypted_connection_status_t EspnowMeshBackend::requestTemporaryEncryptedConnection(uint8_t *peerMac, uint32_t durationMs) { using namespace std::placeholders; - return requestEncryptedConnectionKernel(peerMac, std::bind(defaultEncryptionRequestBuilder, EspnowProtocolInterpreter::temporaryEncryptionRequestHeader, + return requestEncryptedConnectionKernel(peerMac, std::bind(defaultEncryptionRequestBuilder, FPSTR(EspnowProtocolInterpreter::temporaryEncryptionRequestHeader), durationMs, getEspnowHashKey(), _1, _2)); } @@ -1913,7 +1920,7 @@ encrypted_connection_removal_outcome_t EspnowMeshBackend::removeEncryptedConnect { uint8_t encryptedMac[6] {0}; connectionIterator->getEncryptedPeerMac(encryptedMac); - staticVerboseModePrint("Removing connection " + macToString(encryptedMac) + "... ", false); + staticVerboseModePrint(String(F("Removing connection ")) + TypeCast::macToString(encryptedMac) + String(F("... ")), false); bool removalSucceeded = esp_now_del_peer(encryptedMac) == 0; if(removalSucceeded) @@ -1926,7 +1933,7 @@ encrypted_connection_removal_outcome_t EspnowMeshBackend::removeEncryptedConnect { encryptedConnections.erase(connectionIterator); } - staticVerboseModePrint("Removal succeeded"); + staticVerboseModePrint(String(F("Removal succeeded"))); // Not deleting encrypted responses here would cause them to be sent unencrypted, // exposing the peer session key which can be misused later if the encrypted connection is re-established. @@ -1942,7 +1949,7 @@ encrypted_connection_removal_outcome_t EspnowMeshBackend::removeEncryptedConnect } else { - staticVerboseModePrint("Removal failed"); + staticVerboseModePrint(String(F("Removal failed"))); return ECRO_REMOVAL_FAILED; } } @@ -1961,7 +1968,7 @@ void EspnowMeshBackend::deleteEntriesByMac(std::map, T>::iterator entryIterator = logEntries.begin(); entryIterator != logEntries.end(); ) { - if(macAndTypeToUint64Mac(entryIterator->first.first) == macToUint64(peerMac)) + if(macAndTypeToUint64Mac(entryIterator->first.first) == TypeCast::macToUint64(peerMac)) { macFound = true; @@ -1989,7 +1996,7 @@ void EspnowMeshBackend::deleteEntriesByMac(std::map, T>::iterator entryIterator = logEntries.begin(); entryIterator != logEntries.end(); ) { - if(entryIterator->first.first == macToUint64(peerMac)) + if(entryIterator->first.first == TypeCast::macToUint64(peerMac)) { macFound = true; @@ -2018,13 +2025,13 @@ encrypted_connection_removal_outcome_t EspnowMeshBackend::requestEncryptedConnec MutexTracker mutexTracker(_espnowTransmissionMutex, handlePostponedRemovals); if(!mutexTracker.mutexCaptured()) { - assert(false && "ERROR! Transmission in progress. Don't call requestEncryptedConnectionRemoval from callbacks as this may corrupt program state! Aborting."); + assert(false && String(F("ERROR! Transmission in progress. Don't call requestEncryptedConnectionRemoval from callbacks as this may corrupt program state! Aborting."))); return ECRO_REMOVAL_REQUEST_FAILED; } if(EncryptedConnectionLog *encryptedConnection = getEncryptedConnection(peerMac)) { - if(espnowSendToNode(encryptedConnectionRemovalRequestHeader, peerMac, 'P') == TS_TRANSMISSION_COMPLETE) + if(espnowSendToNode(FPSTR(encryptedConnectionRemovalRequestHeader), peerMac, 'P') == TS_TRANSMISSION_COMPLETE) { return removeEncryptedConnectionUnprotected(peerMac); } @@ -2167,7 +2174,7 @@ transmission_status_t EspnowMeshBackend::initiateTransmission(const String &mess if(verboseMode()) // Avoid string generation if not required { printAPInfo(recipientInfo); - verboseModePrint(F("")); + verboseModePrint(emptyString); } return initiateTransmissionKernel(message, targetBSSID); @@ -2197,12 +2204,12 @@ void EspnowMeshBackend::printTransmissionStatistics() { if(verboseMode() && successfulTransmissions_AT > 0) // Avoid calculations if not required { - verboseModePrint("Average duration of successful transmissions: " + String(totalDurationWhenSuccessful_AT/successfulTransmissions_AT) + " ms."); - verboseModePrint("Maximum duration of successful transmissions: " + String(maxTransmissionDuration_AT) + " ms."); + verboseModePrint(String(F("Average duration of successful transmissions: ")) + String(totalDurationWhenSuccessful_AT/successfulTransmissions_AT) + String(F(" ms."))); + verboseModePrint(String(F("Maximum duration of successful transmissions: ")) + String(maxTransmissionDuration_AT) + String(F(" ms."))); } else { - verboseModePrint("No successful transmission."); + verboseModePrint(String(F("No successful transmission."))); } } @@ -2211,7 +2218,7 @@ void EspnowMeshBackend::attemptTransmission(const String &message, bool scan, bo MutexTracker mutexTracker(_espnowTransmissionMutex, handlePostponedRemovals); if(!mutexTracker.mutexCaptured()) { - assert(false && "ERROR! Transmission in progress. Don't call attemptTransmission from callbacks as this may corrupt program state! Aborting."); + assert(false && String(F("ERROR! Transmission in progress. Don't call attemptTransmission from callbacks as this may corrupt program state! Aborting."))); return; } @@ -2220,7 +2227,7 @@ void EspnowMeshBackend::attemptTransmission(const String &message, bool scan, bo MutexTracker connectionQueueMutexTracker(_espnowConnectionQueueMutex); if(!connectionQueueMutexTracker.mutexCaptured()) { - assert(false && "ERROR! connectionQueue locked. Don't call attemptTransmission from callbacks as this may corrupt program state! Aborting."); + assert(false && String(F("ERROR! connectionQueue locked. Don't call attemptTransmission from callbacks as this may corrupt program state! Aborting."))); } else { @@ -2243,7 +2250,7 @@ transmission_status_t EspnowMeshBackend::attemptTransmission(const String &messa MutexTracker mutexTracker(_espnowTransmissionMutex, handlePostponedRemovals); if(!mutexTracker.mutexCaptured()) { - assert(false && "ERROR! Transmission in progress. Don't call attemptTransmission from callbacks as this may corrupt program state! Aborting."); + assert(false && String(F("ERROR! Transmission in progress. Don't call attemptTransmission from callbacks as this may corrupt program state! Aborting."))); return TS_CONNECTION_FAILED; } @@ -2258,7 +2265,7 @@ encrypted_connection_status_t EspnowMeshBackend::initiateAutoEncryptingConnectio if(verboseMode()) // Avoid string generation if not required { printAPInfo(recipientInfo); - verboseModePrint(F("")); + verboseModePrint(emptyString); } *existingEncryptedConnection = getEncryptedConnection(targetBSSID); @@ -2279,7 +2286,7 @@ transmission_status_t EspnowMeshBackend::initiateAutoEncryptingTransmission(cons if(encryptedConnectionEstablished(connectionStatus)) { uint8_t encryptedMac[6] {0}; - assert(getEncryptedMac(targetBSSID, encryptedMac) && esp_now_is_peer_exist(encryptedMac) > 0 && "ERROR! Attempting to send content marked as encrypted via unencrypted connection!"); + assert(getEncryptedMac(targetBSSID, encryptedMac) && esp_now_is_peer_exist(encryptedMac) > 0 && String(F("ERROR! Attempting to send content marked as encrypted via unencrypted connection!"))); transmissionResult = initiateTransmissionKernel(message, targetBSSID); } @@ -2300,7 +2307,7 @@ void EspnowMeshBackend::attemptAutoEncryptingTransmission(const String &message, MutexTracker outerMutexTracker(_espnowTransmissionMutex, handlePostponedRemovals); if(!outerMutexTracker.mutexCaptured()) { - assert(false && "ERROR! Transmission in progress. Don't call attemptAutoEncryptingTransmission from callbacks as this may corrupt program state! Aborting."); + assert(false && String(F("ERROR! Transmission in progress. Don't call attemptAutoEncryptingTransmission from callbacks as this may corrupt program state! Aborting."))); return; } @@ -2311,7 +2318,7 @@ void EspnowMeshBackend::attemptAutoEncryptingTransmission(const String &message, MutexTracker connectionQueueMutexTracker(_espnowConnectionQueueMutex); if(!connectionQueueMutexTracker.mutexCaptured()) { - assert(false && "ERROR! connectionQueue locked. Don't call attemptAutoEncryptingTransmission from callbacks as this may corrupt program state! Aborting."); + assert(false && String(F("ERROR! connectionQueue locked. Don't call attemptAutoEncryptingTransmission from callbacks as this may corrupt program state! Aborting."))); } else { @@ -2324,7 +2331,7 @@ void EspnowMeshBackend::attemptAutoEncryptingTransmission(const String &message, MutexTracker innerMutexTracker = MutexTracker(_espnowTransmissionMutex); if(!innerMutexTracker.mutexCaptured()) { - assert(false && "ERROR! Unable to recapture Mutex in attemptAutoEncryptingTransmission. Aborting."); + assert(false && String(F("ERROR! Unable to recapture Mutex in attemptAutoEncryptingTransmission. Aborting."))); return; } @@ -2351,7 +2358,7 @@ transmission_status_t EspnowMeshBackend::attemptAutoEncryptingTransmission(const MutexTracker mutexTracker(_espnowTransmissionMutex, handlePostponedRemovals); if(!mutexTracker.mutexCaptured()) { - assert(false && "ERROR! Transmission in progress. Don't call attemptTransmission from callbacks as this may corrupt program state! Aborting."); + assert(false && String(F("ERROR! Transmission in progress. Don't call attemptTransmission from callbacks as this may corrupt program state! Aborting."))); return TS_CONNECTION_FAILED; } @@ -2367,7 +2374,7 @@ void EspnowMeshBackend::broadcast(const String &message) MutexTracker mutexTracker(_espnowTransmissionMutex, handlePostponedRemovals); if(!mutexTracker.mutexCaptured()) { - assert(false && "ERROR! Transmission in progress. Don't call broadcast from callbacks as this may corrupt program state! Aborting."); + assert(false && String(F("ERROR! Transmission in progress. Don't call broadcast from callbacks as this may corrupt program state! Aborting."))); return; } @@ -2401,7 +2408,7 @@ void EspnowMeshBackend::sendPeerRequestConfirmations(const ExpiringTimeTracker * using namespace EspnowProtocolInterpreter; // True if confirmationsIterator contains a peer request received from the same node we are currently sending a peer request to. - bool reciprocalPeerRequest = initialOngoingPeerRequestNonce != "" && confirmationsIterator->connectedTo(_ongoingPeerRequestMac); + bool reciprocalPeerRequest = !initialOngoingPeerRequestNonce.isEmpty() && confirmationsIterator->connectedTo(_ongoingPeerRequestMac); auto timeTrackerPointer = confirmationsIterator->temporary(); assert(timeTrackerPointer); // peerRequestConfirmations should always expire and so should always have a timeTracker @@ -2434,13 +2441,13 @@ void EspnowMeshBackend::sendPeerRequestConfirmations(const ExpiringTimeTracker * if(!existingEncryptedConnection && ((reciprocalPeerRequest && encryptedConnections.size() >= maxEncryptedConnections) || (!reciprocalPeerRequest && reservedEncryptedConnections() >= maxEncryptedConnections))) { - espnowSendToNodeUnsynchronized(JsonTranslator::createEncryptionRequestHmacMessage(maxConnectionsReachedHeader, + espnowSendToNodeUnsynchronized(JsonTranslator::createEncryptionRequestHmacMessage(FPSTR(maxConnectionsReachedHeader), confirmationsIterator->getPeerRequestNonce(), hashKey, espnowHashKeyLength), defaultBSSID, 'C', generateMessageID(nullptr)); // Generates a new message ID to avoid sending encrypted sessionKeys over unencrypted connections. confirmationsIterator = peerRequestConfirmationsToSend.erase(confirmationsIterator); } - else if(espnowSendToNodeUnsynchronized(JsonTranslator::createEncryptionRequestHmacMessage(basicConnectionInfoHeader, + else if(espnowSendToNodeUnsynchronized(JsonTranslator::createEncryptionRequestHmacMessage(FPSTR(basicConnectionInfoHeader), confirmationsIterator->getPeerRequestNonce(), hashKey, espnowHashKeyLength), sendToDefaultBSSID ? defaultBSSID : unencryptedBSSID, 'C', generateMessageID(nullptr)) // Generates a new message ID to avoid sending encrypted sessionKeys over unencrypted connections. == TS_TRANSMISSION_COMPLETE) @@ -2466,13 +2473,13 @@ void EspnowMeshBackend::sendPeerRequestConfirmations(const ExpiringTimeTracker * } else { - warningPrint("WARNING! Ignoring received encrypted connection request since no EspnowRequestManager is assigned."); + warningPrint(String(F("WARNING! Ignoring received encrypted connection request since no EspnowRequestManager is assigned."))); } if(!existingEncryptedConnection) { // Send "node full" message - espnowSendToNodeUnsynchronized(JsonTranslator::createEncryptionRequestHmacMessage(maxConnectionsReachedHeader, + espnowSendToNodeUnsynchronized(JsonTranslator::createEncryptionRequestHmacMessage(FPSTR(maxConnectionsReachedHeader), confirmationsIterator->getPeerRequestNonce(), hashKey, espnowHashKeyLength), defaultBSSID, 'C', generateMessageID(nullptr)); // Generates a new message ID to avoid sending encrypted sessionKeys over unencrypted connections. } @@ -2483,19 +2490,19 @@ void EspnowMeshBackend::sendPeerRequestConfirmations(const ExpiringTimeTracker * delay(5); // Give some time for the peer to add an encrypted connection - assert(esp_now_is_peer_exist(defaultBSSID) > 0 && "ERROR! Attempting to send content marked as encrypted via unencrypted connection!"); + assert(esp_now_is_peer_exist(defaultBSSID) > 0 && String(F("ERROR! Attempting to send content marked as encrypted via unencrypted connection!"))); - String messageHeader = ""; + String messageHeader; if(existingEncryptedConnection->temporary() && // Should never change permanent connections ((reciprocalPeerRequest && encryptedConnections.size() > confirmationsIterator->getEncryptedConnectionsSoftLimit()) || (!reciprocalPeerRequest && reservedEncryptedConnections() > confirmationsIterator->getEncryptedConnectionsSoftLimit()))) { - messageHeader = softLimitEncryptedConnectionInfoHeader; + messageHeader = FPSTR(softLimitEncryptedConnectionInfoHeader); } else { - messageHeader = encryptedConnectionInfoHeader; + messageHeader = FPSTR(encryptedConnectionInfoHeader); } // Send password and keys. @@ -2534,7 +2541,7 @@ void EspnowMeshBackend::sendEspnowResponses(const ExpiringTimeTracker *estimated MutexTracker responsesToSendMutexTracker(_responsesToSendMutex); if(!responsesToSendMutexTracker.mutexCaptured()) { - assert(false && "ERROR! responsesToSend locked. Don't call sendEspnowResponses from callbacks as this may corrupt program state! Aborting."); + assert(false && String(F("ERROR! responsesToSend locked. Don't call sendEspnowResponses from callbacks as this may corrupt program state! Aborting."))); } uint32_t responseIndex = 0; @@ -2612,7 +2619,7 @@ uint8_t EspnowMeshBackend::numberOfEncryptedConnections() uint8_t EspnowMeshBackend::reservedEncryptedConnections() { - if(_ongoingPeerRequestNonce != "") + if(!_ongoingPeerRequestNonce.isEmpty()) if(!getEncryptedConnection(_ongoingPeerRequestMac)) return encryptedConnections.size() + 1; // Reserve one connection spot if we are currently making a peer request to a new node. @@ -2684,7 +2691,7 @@ String EspnowMeshBackend::serializeUnencryptedConnection() // Returns: {"connectionState":{"unsyncMsgID":"123"}} - return jsonConnectionState + createJsonEndPair(jsonUnsynchronizedMessageID, String(_unsynchronizedMessageID)); + return String(FPSTR(jsonConnectionState)) + createJsonEndPair(FPSTR(jsonUnsynchronizedMessageID), String(_unsynchronizedMessageID)); } String EspnowMeshBackend::serializeEncryptedConnection(const uint8_t *peerMac) @@ -2697,7 +2704,7 @@ String EspnowMeshBackend::serializeEncryptedConnection(const uint8_t *peerMac) if(encryptedConnection) return encryptedConnection->serialize(); else - return ""; + return emptyString; } String EspnowMeshBackend::serializeEncryptedConnection(uint32_t connectionIndex) @@ -2705,5 +2712,5 @@ String EspnowMeshBackend::serializeEncryptedConnection(uint32_t connectionIndex) if(connectionIndex < numberOfEncryptedConnections()) return encryptedConnections[connectionIndex].serialize(); else - return ""; + return emptyString; } diff --git a/libraries/ESP8266WiFiMesh/src/EspnowMeshBackend.h b/libraries/ESP8266WiFiMesh/src/EspnowMeshBackend.h index 623ec87163..17ca2d26d3 100644 --- a/libraries/ESP8266WiFiMesh/src/EspnowMeshBackend.h +++ b/libraries/ESP8266WiFiMesh/src/EspnowMeshBackend.h @@ -356,6 +356,32 @@ class EspnowMeshBackend : public MeshBackendBase { */ bool isEspnowRequestManager(); + /** + * Set the duration of most ESP-NOW log entries. Used for all ESP-NOW communication except for broadcasts and encrypted connection requests. + * Setting the duration too long may cause the node to run out of RAM, especially if there is intense transmission activity. + * Setting the duration too short may cause ESP-NOW transmissions to stop working, or make the node receive the same transmission multiple times. + * + * Set to 2500 ms by default. + * + * @param logEntryLifetimeMs The duration to use for most ESP-NOW log entries, in milliseconds. + */ + static void setLogEntryLifetimeMs(uint32_t logEntryLifetimeMs); + static uint32_t logEntryLifetimeMs(); + + /** + * Set the duration during which sent ESP-NOW broadcast are stored in the log and can receive responses. + * This is shorter by default than logEntryLifetimeMs() in order to preserve RAM since broadcasts are always kept in the log until they expire, + * whereas normal transmissions are only kept till they receive a response. + * Setting the duration too long may cause the node to run out of RAM, especially if there is intense broadcast activity. + * Setting the duration too short may cause ESP-NOW broadcasts to stop working, or make the node never receive responses to broadcasts. + * + * Set to 1000 ms by default. + * + * @param broadcastResponseTimeoutMs The duration sent ESP-NOW broadcasts will be stored in the log, in milliseconds. + */ + static void setBroadcastResponseTimeoutMs(uint32_t broadcastResponseTimeoutMs); + static uint32_t broadcastResponseTimeoutMs(); + /** * Change the key used by this EspnowMeshBackend instance for creating encrypted ESP-NOW connections. * Will apply to any new received requests for encrypted connection if this EspnowMeshBackend instance is the current request manager. @@ -1052,9 +1078,7 @@ class EspnowMeshBackend : public MeshBackendBase { static void deleteExpiredLogEntries(std::list &logEntries, uint32_t maxEntryLifetimeMs); static uint32_t _logEntryLifetimeMs; - static uint32_t logEntryLifetimeMs(); static uint32_t _broadcastResponseTimeoutMs; - static uint32_t broadcastResponseTimeoutMs(); static uint32_t _encryptionRequestTimeoutMs; diff --git a/libraries/ESP8266WiFiMesh/src/EspnowProtocolInterpreter.cpp b/libraries/ESP8266WiFiMesh/src/EspnowProtocolInterpreter.cpp index 05af40a390..cbbacd538b 100644 --- a/libraries/ESP8266WiFiMesh/src/EspnowProtocolInterpreter.cpp +++ b/libraries/ESP8266WiFiMesh/src/EspnowProtocolInterpreter.cpp @@ -27,6 +27,8 @@ #include #include "EspnowMeshBackend.h" +namespace TypeCast = MeshTypeConversionFunctions; + namespace EspnowProtocolInterpreter { uint8_t espnowMetadataSize() @@ -42,7 +44,7 @@ namespace EspnowProtocolInterpreter { uint8_t messageSize = transmissionLength - espnowMetadataSize(); - messageContent = uint8ArrayToMultiString(transmissionDataArray + espnowMetadataSize(), messageSize); + messageContent = TypeCast::uint8ArrayToMultiString(transmissionDataArray + espnowMetadataSize(), messageSize); } return messageContent; @@ -65,7 +67,7 @@ namespace EspnowProtocolInterpreter uint64_t espnowGetTransmissionMac(const uint8_t *transmissionDataArray) { - return macToUint64(transmissionDataArray + espnowTransmissionMacIndex); + return TypeCast::macToUint64(transmissionDataArray + espnowTransmissionMacIndex); } uint8_t *espnowGetTransmissionMac(const uint8_t *transmissionDataArray, uint8_t *resultArray) @@ -76,12 +78,12 @@ namespace EspnowProtocolInterpreter uint64_t espnowGetMessageID(const uint8_t *transmissionDataArray) { - return uint8ArrayToUint64(transmissionDataArray + espnowMessageIDIndex); + return TypeCast::uint8ArrayToUint64(transmissionDataArray + espnowMessageIDIndex); } uint8_t *espnowSetMessageID(uint8_t *transmissionDataArray, uint64_t messageID) { - return uint64ToUint8Array(messageID, transmissionDataArray + espnowMessageIDIndex); + return TypeCast::uint64ToUint8Array(messageID, transmissionDataArray + espnowMessageIDIndex); } bool usesEncryption(uint64_t messageID) diff --git a/libraries/ESP8266WiFiMesh/src/EspnowProtocolInterpreter.h b/libraries/ESP8266WiFiMesh/src/EspnowProtocolInterpreter.h index 8d8937e765..42b56232bf 100644 --- a/libraries/ESP8266WiFiMesh/src/EspnowProtocolInterpreter.h +++ b/libraries/ESP8266WiFiMesh/src/EspnowProtocolInterpreter.h @@ -39,27 +39,27 @@ namespace EspnowProtocolInterpreter { - const String synchronizationRequestHeader = "Synchronization request."; - const String encryptionRequestHeader = "AddEC:"; // Add encrypted connection - const String temporaryEncryptionRequestHeader = "AddTEC:"; // Add temporary encrypted connection - const String basicConnectionInfoHeader = "BasicCI:"; // Basic connection info - const String encryptedConnectionInfoHeader = "EncryptedCI:"; // Encrypted connection info - const String softLimitEncryptedConnectionInfoHeader = "SLEncryptedCI:"; // Soft limit encrypted connection info - const String maxConnectionsReachedHeader = "ECS_MAX_CONNECTIONS_REACHED_PEER:"; - const String encryptedConnectionVerificationHeader = "ECVerified:"; // Encrypted connection verified - const String encryptedConnectionRemovalRequestHeader = "RemoveEC:"; // Remove encrypted connection + constexpr char synchronizationRequestHeader[] PROGMEM = "Synchronization request."; + constexpr char encryptionRequestHeader[] PROGMEM = "AddEC:"; // Add encrypted connection + constexpr char temporaryEncryptionRequestHeader[] PROGMEM = "AddTEC:"; // Add temporary encrypted connection + constexpr char basicConnectionInfoHeader[] PROGMEM = "BasicCI:"; // Basic connection info + constexpr char encryptedConnectionInfoHeader[] PROGMEM = "EncryptedCI:"; // Encrypted connection info + constexpr char softLimitEncryptedConnectionInfoHeader[] PROGMEM = "SLEncryptedCI:"; // Soft limit encrypted connection info + constexpr char maxConnectionsReachedHeader[] PROGMEM = "ECS_MAX_CONNECTIONS_REACHED_PEER:"; + constexpr char encryptedConnectionVerificationHeader[] PROGMEM = "ECVerified:"; // Encrypted connection verified + constexpr char encryptedConnectionRemovalRequestHeader[] PROGMEM = "RemoveEC:"; // Remove encrypted connection - const uint8_t espnowMessageTypeIndex = 0; - const uint8_t espnowTransmissionsRemainingIndex = 1; - const uint8_t espnowTransmissionMacIndex = 2; - const uint8_t espnowMessageIDIndex = 8; + constexpr uint8_t espnowMessageTypeIndex = 0; + constexpr uint8_t espnowTransmissionsRemainingIndex = 1; + constexpr uint8_t espnowTransmissionMacIndex = 2; + constexpr uint8_t espnowMessageIDIndex = 8; constexpr uint8_t espnowProtocolBytesSize = 16; constexpr uint8_t aeadMetadataSize = 28; uint8_t espnowMetadataSize(); - const uint8_t espnowEncryptedConnectionKeyLength = 16; // This is restricted to exactly 16 bytes by the ESP-NOW API. It should not be changed unless the ESP-NOW API is changed. - const uint8_t espnowHashKeyLength = 16; // This can be changed to any value up to 255. Common values are 16 and 32. + constexpr uint8_t espnowEncryptedConnectionKeyLength = 16; // This is restricted to exactly 16 bytes by the ESP-NOW API. It should not be changed unless the ESP-NOW API is changed. + constexpr uint8_t espnowHashKeyLength = 16; // This can be changed to any value up to 255. Common values are 16 and 32. constexpr uint64_t uint64LeftmostBits = 0xFFFFFFFF00000000; diff --git a/libraries/ESP8266WiFiMesh/src/FloodingMesh.cpp b/libraries/ESP8266WiFiMesh/src/FloodingMesh.cpp index be011d09b3..9b0280938f 100644 --- a/libraries/ESP8266WiFiMesh/src/FloodingMesh.cpp +++ b/libraries/ESP8266WiFiMesh/src/FloodingMesh.cpp @@ -26,6 +26,14 @@ #include "TypeConversionFunctions.h" #include "JsonTranslator.h" +namespace TypeCast = MeshTypeConversionFunctions; + +namespace +{ + constexpr uint8_t MESSAGE_ID_LENGTH = 17; // 16 characters and one delimiter + constexpr uint8_t MESSAGE_COMPLETE = 255; +} + std::set FloodingMesh::availableFloodingMeshes = {}; char FloodingMesh::_metadataDelimiter = 23; @@ -103,6 +111,11 @@ void FloodingMesh::activateAP() getEspnowMeshBackend().activateAP(); } +void FloodingMesh::deactivateAP() +{ + MeshBackendBase::deactivateAP(); +} + void FloodingMesh::performMeshMaintenance() { for(FloodingMesh *meshInstance : availableFloodingMeshes) @@ -122,7 +135,7 @@ void FloodingMesh::performMeshInstanceMaintenance() { _macIgnoreList = messageData.first.substring(0, 12) + ','; // The message should contain the messageID first encryptedBroadcastKernel(messageData.first); - _macIgnoreList = ""; + _macIgnoreList = emptyString; } else { @@ -144,9 +157,9 @@ String FloodingMesh::serializeMeshState() String connectionState = getEspnowMeshBackend().serializeUnencryptedConnection(); return - "{\"meshState\":{" - + connectionState.substring(1, connectionState.length() - 1) + "," - + createJsonEndPair(jsonMeshMessageCount, String(_messageCount)); + String(F("{\"meshState\":{")) + + connectionState.substring(1, connectionState.length() - 1) + String(',') + + createJsonEndPair(FPSTR(jsonMeshMessageCount), String(_messageCount)); } void FloodingMesh::loadMeshState(const String &serializedMeshState) @@ -154,12 +167,12 @@ void FloodingMesh::loadMeshState(const String &serializedMeshState) using namespace JsonTranslator; if(!getMeshMessageCount(serializedMeshState, _messageCount)) - getEspnowMeshBackend().warningPrint("WARNING! serializedMeshState did not contain MeshMessageCount. Using default instead."); + getEspnowMeshBackend().warningPrint(String(F("WARNING! serializedMeshState did not contain MeshMessageCount. Using default instead."))); - String connectionState = ""; + String connectionState; if(!getConnectionState(serializedMeshState, connectionState) || !getEspnowMeshBackend().addUnencryptedConnection(connectionState)) { - getEspnowMeshBackend().warningPrint("WARNING! serializedMeshState did not contain unsynchronizedMessageID. Using default instead."); + getEspnowMeshBackend().warningPrint(String(F("WARNING! serializedMeshState did not contain unsynchronizedMessageID. Using default instead."))); } } @@ -168,7 +181,7 @@ String FloodingMesh::generateMessageID() char messageCountArray[5] = { 0 }; snprintf(messageCountArray, 5, "%04X", _messageCount++); uint8_t apMac[6] {0}; - return macToString(WiFi.softAPmacAddress(apMac)) + String(messageCountArray); // We use the AP MAC address as ID since it is what shows up during WiFi scans + return TypeCast::macToString(WiFi.softAPmacAddress(apMac)) + String(messageCountArray); // We use the AP MAC address as ID since it is what shows up during WiFi scans } void FloodingMesh::broadcast(const String &message) @@ -228,7 +241,7 @@ void FloodingMesh::setOriginMac(uint8_t *macArray) std::copy_n(macArray, 6, _originMac); } -String FloodingMesh::getOriginMac() { return macToString(_originMac); } +String FloodingMesh::getOriginMac() { return TypeCast::macToString(_originMac); } uint8_t *FloodingMesh::getOriginMac(uint8_t *macArray) { std::copy_n(_originMac, 6, macArray); @@ -273,7 +286,7 @@ EspnowMeshBackend &FloodingMesh::getEspnowMeshBackend() bool FloodingMesh::insertPreliminaryMessageID(uint64_t messageID) { uint8_t apMacArray[6] = { 0 }; - if(messageID >> 16 == macToUint64(WiFi.softAPmacAddress(apMacArray))) + if(messageID >> 16 == TypeCast::macToUint64(WiFi.softAPmacAddress(apMacArray))) return false; // The node should not receive its own messages. auto insertionResult = _messageIDs.emplace(messageID, 0); // Returns std::pair @@ -291,7 +304,7 @@ bool FloodingMesh::insertPreliminaryMessageID(uint64_t messageID) bool FloodingMesh::insertCompletedMessageID(uint64_t messageID) { uint8_t apMacArray[6] = { 0 }; - if(messageID >> 16 == macToUint64(WiFi.softAPmacAddress(apMacArray))) + if(messageID >> 16 == TypeCast::macToUint64(WiFi.softAPmacAddress(apMacArray))) return false; // The node should not receive its own messages. auto insertionResult = _messageIDs.emplace(messageID, MESSAGE_COMPLETE); // Returns std::pair @@ -368,7 +381,7 @@ String FloodingMesh::_defaultRequestHandler(const String &request, MeshBackendBa { (void)meshInstance; // This is useful to remove a "unused parameter" compiler warning. Does nothing else. - String broadcastTarget = ""; + String broadcastTarget; String remainingRequest = request; if(request.charAt(0) == metadataDelimiter()) @@ -376,7 +389,7 @@ String FloodingMesh::_defaultRequestHandler(const String &request, MeshBackendBa int32_t broadcastTargetEndIndex = request.indexOf(metadataDelimiter(), 1); if(broadcastTargetEndIndex == -1) - return ""; // metadataDelimiter not found + return emptyString; // metadataDelimiter not found broadcastTarget = request.substring(1, broadcastTargetEndIndex + 1); // Include delimiter remainingRequest.remove(0, broadcastTargetEndIndex + 1); @@ -385,14 +398,14 @@ String FloodingMesh::_defaultRequestHandler(const String &request, MeshBackendBa int32_t messageIDEndIndex = remainingRequest.indexOf(metadataDelimiter()); if(messageIDEndIndex == -1) - return ""; // metadataDelimiter not found + return emptyString; // metadataDelimiter not found - uint64_t messageID = stringToUint64(remainingRequest.substring(0, messageIDEndIndex)); + uint64_t messageID = TypeCast::stringToUint64(remainingRequest.substring(0, messageIDEndIndex)); if(insertCompletedMessageID(messageID)) { uint8_t originMacArray[6] = { 0 }; - setOriginMac(uint64ToMac(messageID >> 16, originMacArray)); // messageID consists of MAC + 16 bit counter + setOriginMac(TypeCast::uint64ToMac(messageID >> 16, originMacArray)); // messageID consists of MAC + 16 bit counter String message = remainingRequest; message.remove(0, messageIDEndIndex + 1); // This approach avoids the null value removal of substring() @@ -405,7 +418,7 @@ String FloodingMesh::_defaultRequestHandler(const String &request, MeshBackendBa } } - return ""; + return emptyString; } /** @@ -419,7 +432,7 @@ transmission_status_t FloodingMesh::_defaultResponseHandler(const String &respon { transmission_status_t statusCode = TS_TRANSMISSION_COMPLETE; - getEspnowMeshBackend().warningPrint("WARNING! Response to FloodingMesh broadcast received, but none is expected!"); + getEspnowMeshBackend().warningPrint(String(F("WARNING! Response to FloodingMesh broadcast received, but none is expected!"))); (void)response; // This is useful to remove a "unused parameter" compiler warning. Does nothing else. (void)meshInstance; // This is useful to remove a "unused parameter" compiler warning. Does nothing else. @@ -444,9 +457,9 @@ void FloodingMesh::_defaultNetworkFilter(int numberOfNetworks, MeshBackendBase & // Connect to any APs which contain meshInstance.getMeshName() if(meshNameIndex >= 0) { - if(_macIgnoreList.indexOf(macToString(WiFi.BSSID(networkIndex))) == -1) // If the BSSID is not in the ignore list + if(_macIgnoreList.indexOf(TypeCast::macToString(WiFi.BSSID(networkIndex))) == -1) // If the BSSID is not in the ignore list { - if(EspnowMeshBackend *espnowInstance = meshBackendCast(&meshInstance)) + if(EspnowMeshBackend *espnowInstance = TypeCast::meshBackendCast(&meshInstance)) { espnowInstance->connectionQueue().push_back(networkIndex); } @@ -486,7 +499,7 @@ bool FloodingMesh::_defaultBroadcastFilter(String &firstTransmission, EspnowMesh String targetMeshName = firstTransmission.substring(0, metadataEndIndex); - if(targetMeshName != "" && meshInstance.getMeshName() != targetMeshName) + if(!targetMeshName.isEmpty() && meshInstance.getMeshName() != targetMeshName) { return false; // Broadcast is for another mesh network } @@ -497,7 +510,7 @@ bool FloodingMesh::_defaultBroadcastFilter(String &firstTransmission, EspnowMesh if(messageIDEndIndex == -1) return false; // metadataDelimiter not found - uint64_t messageID = stringToUint64(firstTransmission.substring(metadataEndIndex + 1, messageIDEndIndex)); + uint64_t messageID = TypeCast::stringToUint64(firstTransmission.substring(metadataEndIndex + 1, messageIDEndIndex)); if(insertPreliminaryMessageID(messageID)) { diff --git a/libraries/ESP8266WiFiMesh/src/FloodingMesh.h b/libraries/ESP8266WiFiMesh/src/FloodingMesh.h index 353c8c6e34..b35e828b65 100644 --- a/libraries/ESP8266WiFiMesh/src/FloodingMesh.h +++ b/libraries/ESP8266WiFiMesh/src/FloodingMesh.h @@ -121,7 +121,8 @@ class FloodingMesh { void begin(); /** - * Makes it possible to find the node through scans, and also makes it possible to recover from an encrypted ESP-NOW connection where only the other node is encrypted. + * Activate the WiFi access point of this ESP8266. + * This makes it possible to find the node through scans, and also makes it possible to recover from an encrypted ESP-NOW connection where only the other node is encrypted. * Required for encryptedBroadcast() usage, but also slows down the start-up of the node. * * Note that only one AP can be active at a time in total (there is only one WiFi radio on the ESP8266), and this will always be the one which was last activated. @@ -129,6 +130,15 @@ class FloodingMesh { * All FloodingMesh instances can still broadcast messages though, even if their AP is not visible. */ void activateAP(); + + /** + * Deactivate the WiFi access point of this ESP8266. + * + * Note that only one AP can be active at a time in total (there is only one WiFi radio on the ESP8266), and this will always be the one which was last activated. + * Thus the AP is shared by all backends. + * All FloodingMesh instances can still broadcast messages though, even if their AP is not visible. + */ + static void deactivateAP(); /** * Performs maintenance for all available Flooding Mesh instances @@ -298,9 +308,6 @@ class FloodingMesh { private: - static const uint8_t MESSAGE_ID_LENGTH = 17; // 16 characters and one delimiter - static const uint8_t MESSAGE_COMPLETE = 255; - EspnowMeshBackend _espnowBackend; messageHandlerType _messageHandler; @@ -318,7 +325,7 @@ class FloodingMesh { std::queue _messageIdOrder = {}; std::list> _forwardingBacklog = {}; - String _macIgnoreList = ""; + String _macIgnoreList; String _defaultRequestHandler(const String &request, MeshBackendBase &meshInstance); transmission_status_t _defaultResponseHandler(const String &response, MeshBackendBase &meshInstance); diff --git a/libraries/ESP8266WiFiMesh/src/JsonTranslator.cpp b/libraries/ESP8266WiFiMesh/src/JsonTranslator.cpp index 599a91f3da..89531ec5f6 100644 --- a/libraries/ESP8266WiFiMesh/src/JsonTranslator.cpp +++ b/libraries/ESP8266WiFiMesh/src/JsonTranslator.cpp @@ -27,16 +27,18 @@ #include "TypeConversionFunctions.h" #include "MeshCryptoInterface.h" +namespace TypeCast = MeshTypeConversionFunctions; + namespace JsonTranslator { String createJsonPair(const String &valueIdentifier, const String &value) { - return valueIdentifier + "\"" + value + "\","; + return valueIdentifier + '\"' + value + F("\","); } String createJsonEndPair(const String &valueIdentifier, const String &value) { - return valueIdentifier + "\"" + value + "\"}}"; + return valueIdentifier + '\"' + value + F("\"}}"); } String createEncryptedConnectionInfo(const String &infoHeader, const String &requestNonce, const String &authenticationPassword, uint64_t ownSessionKey, uint64_t peerSessionKey) @@ -44,33 +46,33 @@ namespace JsonTranslator // Returns: Encrypted connection info:{"arguments":{"nonce":"1F2","password":"abc","ownSK":"3B4","peerSK":"1A2"}} return - infoHeader + "{\"arguments\":{" - + createJsonPair(jsonNonce, requestNonce) - + createJsonPair(jsonPassword, authenticationPassword) - + createJsonPair(jsonOwnSessionKey, uint64ToString(peerSessionKey)) // Exchanges session keys since it should be valid for the receiver. - + createJsonEndPair(jsonPeerSessionKey, uint64ToString(ownSessionKey)); + infoHeader + String(F("{\"arguments\":{")) + + createJsonPair(FPSTR(jsonNonce), requestNonce) + + createJsonPair(FPSTR(jsonPassword), authenticationPassword) + + createJsonPair(FPSTR(jsonOwnSessionKey), TypeCast::uint64ToString(peerSessionKey)) // Exchanges session keys since it should be valid for the receiver. + + createJsonEndPair(FPSTR(jsonPeerSessionKey), TypeCast::uint64ToString(ownSessionKey)); } String createEncryptionRequestIntro(const String &requestHeader, uint32_t duration) { return - requestHeader + "{\"arguments\":{" - + (requestHeader == EspnowProtocolInterpreter::temporaryEncryptionRequestHeader ? createJsonPair(jsonDuration, String(duration)) : ""); + requestHeader + String(F("{\"arguments\":{")) + + (requestHeader == FPSTR(EspnowProtocolInterpreter::temporaryEncryptionRequestHeader) ? createJsonPair(FPSTR(jsonDuration), String(duration)) : emptyString); } String createEncryptionRequestEnding(const String &requestNonce) { - return createJsonEndPair(jsonNonce, requestNonce); + return createJsonEndPair(FPSTR(jsonNonce), requestNonce); } String createEncryptionRequestHmacMessage(const String &requestHeader, const String &requestNonce, const uint8_t *hashKey, uint8_t hashKeyLength, uint32_t duration) { - String mainMessage = createEncryptionRequestIntro(requestHeader, duration) + createJsonPair(jsonNonce, requestNonce); + String mainMessage = createEncryptionRequestIntro(requestHeader, duration) + createJsonPair(FPSTR(jsonNonce), requestNonce); uint8_t staMac[6] {0}; uint8_t apMac[6] {0}; - String requesterStaApMac = macToString(WiFi.macAddress(staMac)) + macToString(WiFi.softAPmacAddress(apMac)); + String requesterStaApMac = TypeCast::macToString(WiFi.macAddress(staMac)) + TypeCast::macToString(WiFi.softAPmacAddress(apMac)); String hmac = MeshCryptoInterface::createMeshHmac(requesterStaApMac + mainMessage, hashKey, hashKeyLength); - return mainMessage + createJsonEndPair(jsonHmac, hmac); + return mainMessage + createJsonEndPair(FPSTR(jsonHmac), hmac); } bool verifyEncryptionRequestHmac(const String &encryptionRequestHmacMessage, const uint8_t *requesterStaMac, const uint8_t *requesterApMac, @@ -78,15 +80,15 @@ namespace JsonTranslator { using MeshCryptoInterface::verifyMeshHmac; - String hmac = ""; + String hmac; if(getHmac(encryptionRequestHmacMessage, hmac)) { - int32_t hmacStartIndex = encryptionRequestHmacMessage.indexOf(jsonHmac); + int32_t hmacStartIndex = encryptionRequestHmacMessage.indexOf(FPSTR(jsonHmac)); if(hmacStartIndex < 0) return false; if(hmac.length() == 2*CryptoInterface::SHA256_NATURAL_LENGTH // We know that each HMAC byte should become 2 String characters due to uint8ArrayToHexString. - && verifyMeshHmac(macToString(requesterStaMac) + macToString(requesterApMac) + encryptionRequestHmacMessage.substring(0, hmacStartIndex), hmac, hashKey, hashKeyLength)) + && verifyMeshHmac(TypeCast::macToString(requesterStaMac) + TypeCast::macToString(requesterApMac) + encryptionRequestHmacMessage.substring(0, hmacStartIndex), hmac, hashKey, hashKeyLength)) { return true; } @@ -118,11 +120,11 @@ namespace JsonTranslator bool getConnectionState(const String &jsonString, String &result) { - int32_t startIndex = jsonString.indexOf(jsonConnectionState); + int32_t startIndex = jsonString.indexOf(FPSTR(jsonConnectionState)); if(startIndex < 0) return false; - int32_t endIndex = jsonString.indexOf("}"); + int32_t endIndex = jsonString.indexOf('}'); if(endIndex < 0) return false; @@ -132,7 +134,7 @@ namespace JsonTranslator bool getPassword(const String &jsonString, String &result) { - int32_t startIndex = getStartIndex(jsonString, jsonPassword); + int32_t startIndex = getStartIndex(jsonString, FPSTR(jsonPassword)); if(startIndex < 0) return false; @@ -146,7 +148,7 @@ namespace JsonTranslator bool getOwnSessionKey(const String &jsonString, uint64_t &result) { - int32_t startIndex = getStartIndex(jsonString, jsonOwnSessionKey); + int32_t startIndex = getStartIndex(jsonString, FPSTR(jsonOwnSessionKey)); if(startIndex < 0) return false; @@ -154,13 +156,13 @@ namespace JsonTranslator if(endIndex < 0) return false; - result = stringToUint64(jsonString.substring(startIndex, endIndex)); + result = TypeCast::stringToUint64(jsonString.substring(startIndex, endIndex)); return true; } bool getPeerSessionKey(const String &jsonString, uint64_t &result) { - int32_t startIndex = getStartIndex(jsonString, jsonPeerSessionKey); + int32_t startIndex = getStartIndex(jsonString, FPSTR(jsonPeerSessionKey)); if(startIndex < 0) return false; @@ -168,13 +170,13 @@ namespace JsonTranslator if(endIndex < 0) return false; - result = stringToUint64(jsonString.substring(startIndex, endIndex)); + result = TypeCast::stringToUint64(jsonString.substring(startIndex, endIndex)); return true; } bool getPeerStaMac(const String &jsonString, uint8_t *resultArray) { - int32_t startIndex = getStartIndex(jsonString, jsonPeerStaMac); + int32_t startIndex = getStartIndex(jsonString, FPSTR(jsonPeerStaMac)); if(startIndex < 0) return false; @@ -182,13 +184,13 @@ namespace JsonTranslator if(endIndex < 0 || endIndex - startIndex != 12) // Mac String is always 12 characters long return false; - stringToMac(jsonString.substring(startIndex, endIndex), resultArray); + TypeCast::stringToMac(jsonString.substring(startIndex, endIndex), resultArray); return true; } bool getPeerApMac(const String &jsonString, uint8_t *resultArray) { - int32_t startIndex = getStartIndex(jsonString, jsonPeerApMac); + int32_t startIndex = getStartIndex(jsonString, FPSTR(jsonPeerApMac)); if(startIndex < 0) return false; @@ -196,13 +198,13 @@ namespace JsonTranslator if(endIndex < 0 || endIndex - startIndex != 12) // Mac String is always 12 characters long return false; - stringToMac(jsonString.substring(startIndex, endIndex), resultArray); + TypeCast::stringToMac(jsonString.substring(startIndex, endIndex), resultArray); return true; } bool getDuration(const String &jsonString, uint32_t &result) { - int32_t startIndex = getStartIndex(jsonString, jsonDuration); + int32_t startIndex = getStartIndex(jsonString, FPSTR(jsonDuration)); if(startIndex < 0) return false; @@ -212,7 +214,7 @@ namespace JsonTranslator bool getNonce(const String &jsonString, String &result) { - int32_t startIndex = getStartIndex(jsonString, jsonNonce); + int32_t startIndex = getStartIndex(jsonString, FPSTR(jsonNonce)); if(startIndex < 0) return false; @@ -226,7 +228,7 @@ namespace JsonTranslator bool getHmac(const String &jsonString, String &result) { - int32_t startIndex = getStartIndex(jsonString, jsonHmac); + int32_t startIndex = getStartIndex(jsonString, FPSTR(jsonHmac)); if(startIndex < 0) return false; @@ -240,7 +242,7 @@ namespace JsonTranslator bool getDesync(const String &jsonString, bool &result) { - int32_t startIndex = getStartIndex(jsonString, jsonDesync); + int32_t startIndex = getStartIndex(jsonString, FPSTR(jsonDesync)); if(startIndex < 0) return false; @@ -250,7 +252,7 @@ namespace JsonTranslator bool getUnsynchronizedMessageID(const String &jsonString, uint32_t &result) { - int32_t startIndex = getStartIndex(jsonString, jsonUnsynchronizedMessageID); + int32_t startIndex = getStartIndex(jsonString, FPSTR(jsonUnsynchronizedMessageID)); if(startIndex < 0) return false; @@ -260,7 +262,7 @@ namespace JsonTranslator bool getMeshMessageCount(const String &jsonString, uint16_t &result) { - int32_t startIndex = getStartIndex(jsonString, jsonMeshMessageCount); + int32_t startIndex = getStartIndex(jsonString, FPSTR(jsonMeshMessageCount)); if(startIndex < 0) return false; diff --git a/libraries/ESP8266WiFiMesh/src/JsonTranslator.h b/libraries/ESP8266WiFiMesh/src/JsonTranslator.h index ad0265ee73..3070e9a999 100644 --- a/libraries/ESP8266WiFiMesh/src/JsonTranslator.h +++ b/libraries/ESP8266WiFiMesh/src/JsonTranslator.h @@ -29,18 +29,18 @@ namespace JsonTranslator { - const String jsonConnectionState = "{\"connectionState\":{"; - const String jsonPassword = "\"password\":"; - const String jsonOwnSessionKey = "\"ownSK\":"; - const String jsonPeerSessionKey = "\"peerSK\":"; - const String jsonPeerStaMac = "\"peerStaMac\":"; - const String jsonPeerApMac = "\"peerApMac\":"; - const String jsonDuration = "\"duration\":"; - const String jsonNonce = "\"nonce\":"; - const String jsonHmac = "\"hmac\":"; - const String jsonDesync = "\"desync\":"; - const String jsonUnsynchronizedMessageID = "\"unsyncMsgID\":"; - const String jsonMeshMessageCount = "\"meshMsgCount\":"; + constexpr char jsonConnectionState[] PROGMEM = "{\"connectionState\":{"; + constexpr char jsonPassword[] PROGMEM = "\"password\":"; + constexpr char jsonOwnSessionKey[] PROGMEM = "\"ownSK\":"; + constexpr char jsonPeerSessionKey[] PROGMEM = "\"peerSK\":"; + constexpr char jsonPeerStaMac[] PROGMEM = "\"peerStaMac\":"; + constexpr char jsonPeerApMac[] PROGMEM = "\"peerApMac\":"; + constexpr char jsonDuration[] PROGMEM = "\"duration\":"; + constexpr char jsonNonce[] PROGMEM = "\"nonce\":"; + constexpr char jsonHmac[] PROGMEM = "\"hmac\":"; + constexpr char jsonDesync[] PROGMEM = "\"desync\":"; + constexpr char jsonUnsynchronizedMessageID[] PROGMEM = "\"unsyncMsgID\":"; + constexpr char jsonMeshMessageCount[] PROGMEM = "\"meshMsgCount\":"; String createJsonPair(const String &valueIdentifier, const String &value); String createJsonEndPair(const String &valueIdentifier, const String &value); diff --git a/libraries/ESP8266WiFiMesh/src/MeshBackendBase.cpp b/libraries/ESP8266WiFiMesh/src/MeshBackendBase.cpp index 4abe1567b0..aafc45ab05 100644 --- a/libraries/ESP8266WiFiMesh/src/MeshBackendBase.cpp +++ b/libraries/ESP8266WiFiMesh/src/MeshBackendBase.cpp @@ -22,6 +22,8 @@ #include +namespace TypeCast = MeshTypeConversionFunctions; + MeshBackendBase *MeshBackendBase::apController = nullptr; bool MeshBackendBase::_scanMutex = false; @@ -38,7 +40,7 @@ MeshBackendBase::MeshBackendBase(requestHandlerType requestHandler, responseHand MeshBackendBase::~MeshBackendBase() { - deactivateAP(); + deactivateControlledAP(); } void MeshBackendBase::setClassType(mesh_backend_t classType) @@ -51,8 +53,7 @@ mesh_backend_t MeshBackendBase::getClassType() {return _classType;} void MeshBackendBase::activateAP() { // Deactivate active AP to avoid two servers using the same port, which can lead to crashes. - if(MeshBackendBase *currentAPController = MeshBackendBase::getAPController()) - currentAPController->deactivateAP(); + deactivateAP(); activateAPHook(); @@ -67,6 +68,12 @@ void MeshBackendBase::activateAPHook() } void MeshBackendBase::deactivateAP() +{ + if(MeshBackendBase *currentAPController = MeshBackendBase::getAPController()) + currentAPController->deactivateControlledAP(); +} + +bool MeshBackendBase::deactivateControlledAP() { if(isAPController()) { @@ -77,7 +84,11 @@ void MeshBackendBase::deactivateAP() // Since there is no active AP controller now, make the apController variable point to nothing. apController = nullptr; + + return true; } + + return false; } void MeshBackendBase::deactivateAPHook() @@ -126,11 +137,11 @@ uint8 MeshBackendBase::getWiFiChannel() const void MeshBackendBase::setSSID(const String &newSSIDPrefix, const String &newSSIDRoot, const String &newSSIDSuffix) { - if(newSSIDPrefix != "") + if(!newSSIDPrefix.isEmpty()) _SSIDPrefix = newSSIDPrefix; - if(newSSIDRoot != "") + if(!newSSIDRoot.isEmpty()) _SSIDRoot = newSSIDRoot; - if(newSSIDSuffix != "") + if(!newSSIDSuffix.isEmpty()) _SSIDSuffix = newSSIDSuffix; String newSSID = _SSIDPrefix + _SSIDRoot + _SSIDSuffix; @@ -158,14 +169,14 @@ String MeshBackendBase::getSSIDPrefix() const {return _SSIDPrefix;} void MeshBackendBase::setSSIDRoot(const String &newSSIDRoot) { - setSSID("", newSSIDRoot); + setSSID(emptyString, newSSIDRoot); } String MeshBackendBase::getSSIDRoot() const {return _SSIDRoot;} void MeshBackendBase::setSSIDSuffix(const String &newSSIDSuffix) { - setSSID("", "", newSSIDSuffix); + setSSID(emptyString, emptyString, newSSIDSuffix); } String MeshBackendBase::getSSIDSuffix() const {return _SSIDSuffix;} @@ -250,7 +261,7 @@ void MeshBackendBase::scanForNetworks(bool scanAllWiFiChannels) MutexTracker mutexTracker(_scanMutex); if(!mutexTracker.mutexCaptured()) { - assert(false && "ERROR! Scan already in progress. Don't call scanForNetworks from callbacks as this may corrupt program state! Aborting."); + assert(false && String(F("ERROR! Scan already in progress. Don't call scanForNetworks from callbacks as this may corrupt program state! Aborting."))); return; } @@ -279,7 +290,7 @@ void MeshBackendBase::printAPInfo(const NetworkInfoBase &apNetworkInfo) String mainNetworkIdentifier = apNetworkInfo.SSID(); if(mainNetworkIdentifier == NetworkInfoBase::defaultSSID) // If SSID not provided, use BSSID instead { - mainNetworkIdentifier = macToString(apNetworkInfo.BSSID()); + mainNetworkIdentifier = TypeCast::macToString(apNetworkInfo.BSSID()); } verboseModePrint(String(F("AP acquired: ")) + mainNetworkIdentifier + String(F(", Ch:")) + String(apNetworkInfo.wifiChannel()) + ' ', false); @@ -287,7 +298,7 @@ void MeshBackendBase::printAPInfo(const NetworkInfoBase &apNetworkInfo) if(apNetworkInfo.RSSI() != NetworkInfoBase::defaultRSSI) { verboseModePrint(String('(') + String(apNetworkInfo.RSSI()) + String(F("dBm) ")) + - (apNetworkInfo.encryptionType() == ENC_TYPE_NONE ? String(F("open")) : ""), false); + (apNetworkInfo.encryptionType() == ENC_TYPE_NONE ? String(F("open")) : emptyString), false); } verboseModePrint(F("... "), false); diff --git a/libraries/ESP8266WiFiMesh/src/MeshBackendBase.h b/libraries/ESP8266WiFiMesh/src/MeshBackendBase.h index dbdac67e03..63c9c5d507 100644 --- a/libraries/ESP8266WiFiMesh/src/MeshBackendBase.h +++ b/libraries/ESP8266WiFiMesh/src/MeshBackendBase.h @@ -23,8 +23,6 @@ #include "TransmissionOutcome.h" #include "NetworkInfoBase.h" -const String ESP8266_MESH_EMPTY_STRING = ""; - typedef enum { MB_TCP_IP = 0, @@ -52,11 +50,40 @@ class MeshBackendBase { virtual void begin() = 0; /** - * Each AP requires a separate server port. If two AP:s are using the same server port, they will not be able to have both server instances active at the same time. + * Activate the WiFi access point of this ESP8266. + * + * For TCP/IP, each AP requires a separate server port. If two AP:s are using the same server port, they will not be able to have both server instances active at the same time. * This is managed automatically by the activateAP method. + * + * Note that only one AP can be active at a time in total (there is only one WiFi radio on the ESP8266), and this will always be the one which was last activated. + * Thus the AP is shared by all backends. */ void activateAP(); - void deactivateAP(); + + /** + * Deactivate the WiFi access point of this ESP8266, regardless of which MeshBackendBase instance is in control of the AP. + * + * Note that only one AP can be active at a time in total (there is only one WiFi radio on the ESP8266), and this will always be the one which was last activated. + * Thus the AP is shared by all backends. + */ + static void deactivateAP(); + + /** + * Deactivate the WiFi access point of this ESP8266, provided that this MeshBackendBase instance is in control of the AP (which normally is the case for the MeshBackendBase instance that did the last activateAP() call). + * + * Note that only one AP can be active at a time in total (there is only one WiFi radio on the ESP8266), and this will always be the one which was last activated. + * Thus the AP is shared by all backends. + * + * @return True if the AP was deactivated. False otherwise. + */ + bool deactivateControlledAP(); + + /** + * Restart the WiFi access point of this ESP8266. + * + * Note that only one AP can be active at a time in total (there is only one WiFi radio on the ESP8266), and this will always be the one which was last activated. + * Thus the AP is shared by all backends. + */ void restartAP(); /** @@ -104,8 +131,8 @@ class MeshBackendBase { * @param newSSIDRoot The middle part of the new SSID. * @param newSSIDSuffix The last part of the new SSID. */ - void setSSID(const String &newSSIDPrefix = ESP8266_MESH_EMPTY_STRING, const String &newSSIDRoot = ESP8266_MESH_EMPTY_STRING, - const String &newSSIDSuffix = ESP8266_MESH_EMPTY_STRING); + void setSSID(const String &newSSIDPrefix = emptyString, const String &newSSIDRoot = emptyString, + const String &newSSIDSuffix = emptyString); String getSSID() const; /** @@ -300,7 +327,7 @@ class MeshBackendBase { String _meshPassword; uint8 _meshWiFiChannel; bool _verboseMode; - String _message = ESP8266_MESH_EMPTY_STRING; + String _message; bool _scanHidden = false; bool _apHidden = false; diff --git a/libraries/ESP8266WiFiMesh/src/MessageData.h b/libraries/ESP8266WiFiMesh/src/MessageData.h index 71e291fb1f..7dc5adac2a 100644 --- a/libraries/ESP8266WiFiMesh/src/MessageData.h +++ b/libraries/ESP8266WiFiMesh/src/MessageData.h @@ -48,7 +48,7 @@ class MessageData : public TimeTracker { uint8_t _transmissionsReceived = 0; uint8_t _transmissionsExpected; - String _totalMessage = ""; + String _totalMessage; }; diff --git a/libraries/ESP8266WiFiMesh/src/NetworkInfo.h b/libraries/ESP8266WiFiMesh/src/NetworkInfo.h index 9416cf7658..4462759f94 100644 --- a/libraries/ESP8266WiFiMesh/src/NetworkInfo.h +++ b/libraries/ESP8266WiFiMesh/src/NetworkInfo.h @@ -60,7 +60,7 @@ class NetworkInfo { public: - String SSID = ""; + String SSID; int wifiChannel = NETWORK_INFO_DEFAULT_INT; uint8_t *BSSID = NULL; int networkIndex = NETWORK_INFO_DEFAULT_INT; diff --git a/libraries/ESP8266WiFiMesh/src/NetworkInfoBase.cpp b/libraries/ESP8266WiFiMesh/src/NetworkInfoBase.cpp index 46620612db..2cfed766e6 100644 --- a/libraries/ESP8266WiFiMesh/src/NetworkInfoBase.cpp +++ b/libraries/ESP8266WiFiMesh/src/NetworkInfoBase.cpp @@ -25,7 +25,7 @@ #include "NetworkInfoBase.h" uint8_t * const NetworkInfoBase::defaultBSSID = nullptr; -const String NetworkInfoBase::defaultSSID = ""; +const String NetworkInfoBase::defaultSSID; const int32_t NetworkInfoBase::defaultWifiChannel = NETWORK_INFO_DEFAULT_INT; const uint8_t NetworkInfoBase::defaultEncryptionType = 0; const int32_t NetworkInfoBase::defaultRSSI = ~0; diff --git a/libraries/ESP8266WiFiMesh/src/PeerRequestLog.h b/libraries/ESP8266WiFiMesh/src/PeerRequestLog.h index 2eb4c72bf0..f1064a84b4 100644 --- a/libraries/ESP8266WiFiMesh/src/PeerRequestLog.h +++ b/libraries/ESP8266WiFiMesh/src/PeerRequestLog.h @@ -57,9 +57,9 @@ class PeerRequestLog : public EncryptedConnectionData { uint64_t _requestID; bool _requestEncrypted; - String _authenticationPassword = ""; + String _authenticationPassword; uint8_t _encryptedConnectionsSoftLimit; - String _peerRequestNonce = ""; + String _peerRequestNonce; }; #endif diff --git a/libraries/ESP8266WiFiMesh/src/ResponseData.h b/libraries/ESP8266WiFiMesh/src/ResponseData.h index 7bd527d6e3..17b0bcc7d6 100644 --- a/libraries/ESP8266WiFiMesh/src/ResponseData.h +++ b/libraries/ESP8266WiFiMesh/src/ResponseData.h @@ -52,7 +52,7 @@ class ResponseData : public TimeTracker { uint8_t _recipientMacArray[6] {0}; uint8_t *_recipientMac = nullptr; - String _message = ""; + String _message; uint64_t _requestID = 0; }; diff --git a/libraries/ESP8266WiFiMesh/src/TcpIpMeshBackend.cpp b/libraries/ESP8266WiFiMesh/src/TcpIpMeshBackend.cpp index 4d281fb508..0b1e6dfa8b 100644 --- a/libraries/ESP8266WiFiMesh/src/TcpIpMeshBackend.cpp +++ b/libraries/ESP8266WiFiMesh/src/TcpIpMeshBackend.cpp @@ -25,22 +25,25 @@ #include "TypeConversionFunctions.h" #include "MutexTracker.h" -#define SERVER_IP_ADDR "192.168.4.1" +namespace +{ + constexpr char SERVER_IP_ADDR[] PROGMEM = "192.168.4.1"; +} -const IPAddress TcpIpMeshBackend::emptyIP = IPAddress(); +const IPAddress TcpIpMeshBackend::emptyIP; bool TcpIpMeshBackend::_tcpIpTransmissionMutex = false; bool TcpIpMeshBackend::_tcpIpConnectionQueueMutex = false; -String TcpIpMeshBackend::lastSSID = ""; +String TcpIpMeshBackend::lastSSID; bool TcpIpMeshBackend::staticIPActivated = false; -String TcpIpMeshBackend::_temporaryMessage = ""; +String TcpIpMeshBackend::_temporaryMessage; // IP needs to be at the same subnet as server gateway (192.168.4 in this case). Station gateway ip must match ip for server. -IPAddress TcpIpMeshBackend::staticIP = emptyIP; -IPAddress TcpIpMeshBackend::gateway = IPAddress(192,168,4,1); -IPAddress TcpIpMeshBackend::subnetMask = IPAddress(255,255,255,0); +IPAddress TcpIpMeshBackend::staticIP; +IPAddress TcpIpMeshBackend::gateway(192,168,4,1); +IPAddress TcpIpMeshBackend::subnetMask(255,255,255,0); std::vector TcpIpMeshBackend::_connectionQueue = {}; std::vector TcpIpMeshBackend::_latestTransmissionOutcomes = {}; @@ -50,7 +53,7 @@ TcpIpMeshBackend::TcpIpMeshBackend(requestHandlerType requestHandler, responseHa const String &ssidSuffix, bool verboseMode, uint8 meshWiFiChannel, uint16_t serverPort) : MeshBackendBase(requestHandler, responseHandler, networkFilter, MB_TCP_IP), _server(serverPort) { - setSSID(ssidPrefix, "", ssidSuffix); + setSSID(ssidPrefix, emptyString, ssidSuffix); setMeshPassword(meshPassword); setVerboseModeState(verboseMode); setWiFiChannel(meshWiFiChannel); @@ -62,7 +65,7 @@ std::vector & TcpIpMeshBackend::connectionQueue() MutexTracker connectionQueueMutexTracker(_tcpIpConnectionQueueMutex); if(!connectionQueueMutexTracker.mutexCaptured()) { - assert(false && "ERROR! connectionQueue locked. Don't call connectionQueue() from callbacks other than NetworkFilter as this may corrupt program state!"); + assert(false && String(F("ERROR! connectionQueue locked. Don't call connectionQueue() from callbacks other than NetworkFilter as this may corrupt program state!"))); } return _connectionQueue; @@ -112,13 +115,13 @@ bool TcpIpMeshBackend::transmissionInProgress(){return _tcpIpTransmissionMutex;} void TcpIpMeshBackend::setTemporaryMessage(const String &newTemporaryMessage) {_temporaryMessage = newTemporaryMessage;} String TcpIpMeshBackend::getTemporaryMessage() {return _temporaryMessage;} -void TcpIpMeshBackend::clearTemporaryMessage() {_temporaryMessage = "";} +void TcpIpMeshBackend::clearTemporaryMessage() {_temporaryMessage.clear();} String TcpIpMeshBackend::getCurrentMessage() { String message = getTemporaryMessage(); - if(message == "") // If no temporary message stored + if(message.isEmpty()) // If no temporary message stored message = getMessage(); return message; @@ -245,7 +248,7 @@ bool TcpIpMeshBackend::waitForClientTransmission(WiFiClient &currClient, uint32_ */ transmission_status_t TcpIpMeshBackend::exchangeInfo(WiFiClient &currClient) { - verboseModePrint("Transmitting"); // Not storing strings in flash (via F()) to avoid performance impacts when using the string. + verboseModePrint(String(F("Transmitting"))); currClient.print(getCurrentMessage() + '\r'); yield(); @@ -301,7 +304,7 @@ transmission_status_t TcpIpMeshBackend::attemptDataTransferKernel() currClient.setTimeout(_stationModeTimeoutMs); /* Connect to the node's server */ - if (!currClient.connect(SERVER_IP_ADDR, getServerPort())) + if (!currClient.connect(FPSTR(SERVER_IP_ADDR), getServerPort())) { fullStop(currClient); verboseModePrint(F("Server unavailable")); @@ -342,7 +345,7 @@ void TcpIpMeshBackend::initiateConnectionToAP(const String &targetSSID, int targ */ transmission_status_t TcpIpMeshBackend::connectToNode(const String &targetSSID, int targetChannel, uint8_t *targetBSSID) { - if(staticIPActivated && lastSSID != "" && lastSSID != targetSSID) // So we only do this once per connection, in case there is a performance impact. + if(staticIPActivated && !lastSSID.isEmpty() && lastSSID != targetSSID) // So we only do this once per connection, in case there is a performance impact. { #if LWIP_VERSION_MAJOR >= 2 // Can be used with Arduino core for ESP8266 version 2.4.2 or higher with lwIP2 enabled to keep static IP on even during network switches. @@ -398,7 +401,7 @@ transmission_status_t TcpIpMeshBackend::initiateTransmission(const TcpIpNetworkI WiFi.disconnect(); yield(); - assert(recipientInfo.SSID() != ""); // We need at least SSID to connect + assert(!recipientInfo.SSID().isEmpty()); // We need at least SSID to connect String targetSSID = recipientInfo.SSID(); int32_t targetWiFiChannel = recipientInfo.wifiChannel(); uint8_t targetBSSID[6] {0}; @@ -433,7 +436,7 @@ void TcpIpMeshBackend::attemptTransmission(const String &message, bool scan, boo MutexTracker mutexTracker(_tcpIpTransmissionMutex); if(!mutexTracker.mutexCaptured()) { - assert(false && "ERROR! TCP/IP transmission in progress. Don't call attemptTransmission from callbacks as this may corrupt program state! Aborting."); + assert(false && String(F("ERROR! TCP/IP transmission in progress. Don't call attemptTransmission from callbacks as this may corrupt program state! Aborting."))); return; } @@ -465,7 +468,7 @@ void TcpIpMeshBackend::attemptTransmission(const String &message, bool scan, boo MutexTracker connectionQueueMutexTracker(_tcpIpConnectionQueueMutex); if(!connectionQueueMutexTracker.mutexCaptured()) { - assert(false && "ERROR! connectionQueue locked. Don't call attemptTransmission from callbacks as this may corrupt program state! Aborting."); + assert(false && String(F("ERROR! connectionQueue locked. Don't call attemptTransmission from callbacks as this may corrupt program state! Aborting."))); } else { @@ -494,7 +497,7 @@ transmission_status_t TcpIpMeshBackend::attemptTransmission(const String &messag MutexTracker mutexTracker(_tcpIpTransmissionMutex); if(!mutexTracker.mutexCaptured()) { - assert(false && "ERROR! TCP/IP transmission in progress. Don't call attemptTransmission from callbacks as this may corrupt program state! Aborting."); + assert(false && String(F("ERROR! TCP/IP transmission in progress. Don't call attemptTransmission from callbacks as this may corrupt program state! Aborting."))); return TS_CONNECTION_FAILED; } @@ -527,7 +530,7 @@ void TcpIpMeshBackend::acceptRequests() MutexTracker mutexTracker(_tcpIpTransmissionMutex); if(!mutexTracker.mutexCaptured()) { - assert(false && "ERROR! TCP/IP transmission in progress. Don't call acceptRequests from callbacks as this may corrupt program state! Aborting."); + assert(false && String(F("ERROR! TCP/IP transmission in progress. Don't call acceptRequests from callbacks as this may corrupt program state! Aborting."))); return; } @@ -551,7 +554,7 @@ void TcpIpMeshBackend::acceptRequests() /* Send the response back to the client */ if (_client.connected()) { - verboseModePrint("Responding"); // Not storing strings in flash (via F()) to avoid performance impacts when using the string. + verboseModePrint(String(F("Responding"))); _client.print(response + '\r'); _client.flush(); yield(); diff --git a/libraries/ESP8266WiFiMesh/src/TypeConversionFunctions.cpp b/libraries/ESP8266WiFiMesh/src/TypeConversionFunctions.cpp index 49530f2329..1faa8d5629 100644 --- a/libraries/ESP8266WiFiMesh/src/TypeConversionFunctions.cpp +++ b/libraries/ESP8266WiFiMesh/src/TypeConversionFunctions.cpp @@ -33,206 +33,208 @@ namespace 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35}; // Lower case letters } - -String uint64ToString(uint64_t number, byte base) +namespace MeshTypeConversionFunctions { - assert(2 <= base && base <= 36); + String uint64ToString(uint64_t number, byte base) + { + assert(2 <= base && base <= 36); + + String result; + + if(base == 16) + { + do { + result += chars[ number % base ]; + number >>= 4; // We could write number /= 16; and the compiler would optimize it to a shift, but the explicit shift notation makes it clearer where the speed-up comes from. + } while ( number ); + } + else + { + do { + result += chars[ number % base ]; + number /= base; + } while ( number ); + } + + std::reverse( result.begin(), result.end() ); + + return result; + } + + uint64_t stringToUint64(const String &string, byte base) + { + assert(2 <= base && base <= 36); + + uint64_t result = 0; - String result; + if(base == 16) + { + for(uint32_t i = 0; i < string.length(); ++i) + { + result <<= 4; // We could write result *= 16; and the compiler would optimize it to a shift, but the explicit shift notation makes it clearer where the speed-up comes from. + result += charValues[string.charAt(i) - '0']; + } + } + else + { + for(uint32_t i = 0; i < string.length(); ++i) + { + result *= base; + result += charValues[string.charAt(i) - '0']; + } + } + + return result; + } - if(base == 16) + String uint8ArrayToHexString(const uint8_t *uint8Array, uint32_t arrayLength) { - do { - result += chars[ number % base ]; - number >>= 4; // We could write number /= 16; and the compiler would optimize it to a shift, but the explicit shift notation makes it clearer where the speed-up comes from. - } while ( number ); + String hexString; + if(!hexString.reserve(2*arrayLength)) // Each uint8_t will become two characters (00 to FF) + return emptyString; + + for(uint32_t i = 0; i < arrayLength; ++i) + { + hexString += chars[ uint8Array[i] >> 4 ]; + hexString += chars[ uint8Array[i] % 16 ]; + } + + return hexString; } - else + + uint8_t *hexStringToUint8Array(const String &hexString, uint8_t *uint8Array, uint32_t arrayLength) { - do { - result += chars[ number % base ]; - number /= base; - } while ( number ); + assert(hexString.length() >= arrayLength*2); // Each array element can hold two hexString characters + + for(uint32_t i = 0; i < arrayLength; ++i) + { + uint8Array[i] = (charValues[hexString.charAt(i*2) - '0'] << 4) + charValues[hexString.charAt(i*2 + 1) - '0']; + } + + return uint8Array; } - std::reverse( result.begin(), result.end() ); + String uint8ArrayToMultiString(uint8_t *uint8Array, uint32_t arrayLength) + { + String multiString; + if(!multiString.reserve(arrayLength)) + return emptyString; - return result; -} - -uint64_t stringToUint64(const String &string, byte base) -{ - assert(2 <= base && base <= 36); + // Ensure we have a NULL terminated character array so the String() constructor knows where to stop. + char finalChar = uint8Array[arrayLength - 1]; + uint8Array[arrayLength - 1] = 0; - uint64_t result = 0; - - if(base == 16) - { - for(uint32_t i = 0; i < string.length(); ++i) + multiString += (char *)(uint8Array); + while(multiString.length() < arrayLength - 1) { - result <<= 4; // We could write result *= 16; and the compiler would optimize it to a shift, but the explicit shift notation makes it clearer where the speed-up comes from. - result += charValues[string.charAt(i) - '0']; + multiString += (char)0; // String construction only stops for null values, so we need to add those manually. + multiString += (char *)(uint8Array + multiString.length()); } + + multiString += finalChar; + uint8Array[arrayLength - 1] = finalChar; + + return multiString; } - else + + String bufferedUint8ArrayToMultiString(const uint8_t *uint8Array, uint32_t arrayLength) { - for(uint32_t i = 0; i < string.length(); ++i) + String multiString; + if(!multiString.reserve(arrayLength)) + return emptyString; + + // Ensure we have a NULL terminated character array so the String() constructor knows where to stop. + uint8_t bufferedData[arrayLength + 1]; + std::copy_n(uint8Array, arrayLength, bufferedData); + bufferedData[arrayLength] = 0; + + multiString += (char *)(bufferedData); + while(multiString.length() < arrayLength) { - result *= base; - result += charValues[string.charAt(i) - '0']; + multiString += (char)0; // String construction only stops for null values, so we need to add those manually. + multiString += (char *)(bufferedData + multiString.length()); } - } - return result; -} - -String uint8ArrayToHexString(const uint8_t *uint8Array, uint32_t arrayLength) -{ - String hexString; - if(!hexString.reserve(2*arrayLength)) // Each uint8_t will become two characters (00 to FF) - return emptyString; + return multiString; + } - for(uint32_t i = 0; i < arrayLength; ++i) + String macToString(const uint8_t *mac) { - hexString += chars[ uint8Array[i] >> 4 ]; - hexString += chars[ uint8Array[i] % 16 ]; + return MeshTypeConversionFunctions::uint8ArrayToHexString(mac, 6); } - return hexString; -} - -uint8_t *hexStringToUint8Array(const String &hexString, uint8_t *uint8Array, uint32_t arrayLength) -{ - assert(hexString.length() >= arrayLength*2); // Each array element can hold two hexString characters + uint8_t *stringToMac(const String &macString, uint8_t *macArray) + { + return MeshTypeConversionFunctions::hexStringToUint8Array(macString, macArray, 6); + } - for(uint32_t i = 0; i < arrayLength; ++i) + uint64_t macToUint64(const uint8_t *macArray) { - uint8Array[i] = (charValues[hexString.charAt(i*2) - '0'] << 4) + charValues[hexString.charAt(i*2 + 1) - '0']; + 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 uint8Array; -} - -String uint8ArrayToMultiString(uint8_t *uint8Array, uint32_t arrayLength) -{ - String multiString; - if(!multiString.reserve(arrayLength)) - return emptyString; - - // Ensure we have a NULL terminated character array so the String() constructor knows where to stop. - char finalChar = uint8Array[arrayLength - 1]; - uint8Array[arrayLength - 1] = 0; - - multiString += (char *)(uint8Array); - while(multiString.length() < arrayLength - 1) + uint8_t *uint64ToMac(uint64_t macValue, uint8_t *macArray) { - multiString += (char)0; // String construction only stops for null values, so we need to add those manually. - multiString += (char *)(uint8Array + multiString.length()); + assert(macValue <= 0xFFFFFFFFFFFF); // Overflow will occur if value can't fit within 6 bytes + + macArray[5] = macValue; + macArray[4] = macValue >> 8; + macArray[3] = macValue >> 16; + macArray[2] = macValue >> 24; + macArray[1] = macValue >> 32; + macArray[0] = macValue >> 40; + + return macArray; } - - multiString += finalChar; - uint8Array[arrayLength - 1] = finalChar; - - return multiString; -} - -String bufferedUint8ArrayToMultiString(const uint8_t *uint8Array, uint32_t arrayLength) -{ - String multiString; - if(!multiString.reserve(arrayLength)) - return emptyString; - // Ensure we have a NULL terminated character array so the String() constructor knows where to stop. - uint8_t bufferedData[arrayLength + 1]; - std::copy_n(uint8Array, arrayLength, bufferedData); - bufferedData[arrayLength] = 0; + uint8_t *uint64ToUint8Array(uint64_t value, uint8_t *resultArray) + { + resultArray[7] = value; + resultArray[6] = value >> 8; + resultArray[5] = value >> 16; + resultArray[4] = value >> 24; + resultArray[3] = value >> 32; + resultArray[2] = value >> 40; + resultArray[1] = value >> 48; + resultArray[0] = value >> 56; - multiString += (char *)(bufferedData); - while(multiString.length() < arrayLength) + return resultArray; + } + + uint64_t uint8ArrayToUint64(const uint8_t *inputArray) { - multiString += (char)0; // String construction only stops for null values, so we need to add those manually. - multiString += (char *)(bufferedData + multiString.length()); + uint64_t result = (uint64_t)inputArray[0] << 56 | (uint64_t)inputArray[1] << 48 | (uint64_t)inputArray[2] << 40 | (uint64_t)inputArray[3] << 32 + | (uint64_t)inputArray[4] << 24 | (uint64_t)inputArray[5] << 16 | (uint64_t)inputArray[6] << 8 | (uint64_t)inputArray[7]; + + return result; } - - return multiString; -} - -String macToString(const uint8_t *mac) -{ - return uint8ArrayToHexString(mac, 6); -} - -uint8_t *stringToMac(const String &macString, uint8_t *macArray) -{ - return hexStringToUint8Array(macString, macArray, 6); -} - -uint64_t macToUint64(const uint8_t *macArray) -{ - uint64_t result = (uint64_t)macArray[0] << 40 | (uint64_t)macArray[1] << 32 | (uint64_t)macArray[2] << 24 | (uint64_t)macArray[3] << 16 | (uint64_t)macArray[4] << 8 | (uint64_t)macArray[5]; - return result; -} - -uint8_t *uint64ToMac(uint64_t macValue, uint8_t *macArray) -{ - assert(macValue <= 0xFFFFFFFFFFFF); // Overflow will occur if value can't fit within 6 bytes - macArray[5] = macValue; - macArray[4] = macValue >> 8; - macArray[3] = macValue >> 16; - macArray[2] = macValue >> 24; - macArray[1] = macValue >> 32; - macArray[0] = macValue >> 40; + /** + * Helper function for meshBackendCast. + */ + template + T attemptPointerCast(MeshBackendBase *meshBackendBaseInstance, mesh_backend_t resultClassType) + { + if(meshBackendBaseInstance && meshBackendBaseInstance->getClassType() == resultClassType) + { + return static_cast(meshBackendBaseInstance); + } + else + { + return nullptr; + } + } - return macArray; -} - -uint8_t *uint64ToUint8Array(uint64_t value, uint8_t *resultArray) -{ - resultArray[7] = value; - resultArray[6] = value >> 8; - resultArray[5] = value >> 16; - resultArray[4] = value >> 24; - resultArray[3] = value >> 32; - resultArray[2] = value >> 40; - resultArray[1] = value >> 48; - resultArray[0] = value >> 56; - - return resultArray; -} - -uint64_t uint8ArrayToUint64(const uint8_t *inputArray) -{ - uint64_t result = (uint64_t)inputArray[0] << 56 | (uint64_t)inputArray[1] << 48 | (uint64_t)inputArray[2] << 40 | (uint64_t)inputArray[3] << 32 - | (uint64_t)inputArray[4] << 24 | (uint64_t)inputArray[5] << 16 | (uint64_t)inputArray[6] << 8 | (uint64_t)inputArray[7]; - - return result; -} - -/** - * Helper function for meshBackendCast. - */ -template -T attemptPointerCast(MeshBackendBase *meshBackendBaseInstance, mesh_backend_t resultClassType) -{ - if(meshBackendBaseInstance && meshBackendBaseInstance->getClassType() == resultClassType) + template <> + EspnowMeshBackend *meshBackendCast(MeshBackendBase *meshBackendBaseInstance) { - return static_cast(meshBackendBaseInstance); + return attemptPointerCast(meshBackendBaseInstance, MB_ESP_NOW); } - else + + template <> + TcpIpMeshBackend *meshBackendCast(MeshBackendBase *meshBackendBaseInstance) { - return nullptr; + return attemptPointerCast(meshBackendBaseInstance, MB_TCP_IP); } } - -template <> -EspnowMeshBackend *meshBackendCast(MeshBackendBase *meshBackendBaseInstance) -{ - return attemptPointerCast(meshBackendBaseInstance, MB_ESP_NOW); -} - -template <> -TcpIpMeshBackend *meshBackendCast(MeshBackendBase *meshBackendBaseInstance) -{ - return attemptPointerCast(meshBackendBaseInstance, MB_TCP_IP); -} diff --git a/libraries/ESP8266WiFiMesh/src/TypeConversionFunctions.h b/libraries/ESP8266WiFiMesh/src/TypeConversionFunctions.h index 08a1ab5361..07591a8167 100644 --- a/libraries/ESP8266WiFiMesh/src/TypeConversionFunctions.h +++ b/libraries/ESP8266WiFiMesh/src/TypeConversionFunctions.h @@ -32,144 +32,151 @@ #include "TcpIpMeshBackend.h" #include "EspnowMeshBackend.h" -/** - * Note that using base 10 instead of 16 increases conversion time by roughly a factor of 5, due to unfavourable 64-bit arithmetic. - * Note that using a base higher than 16 increases likelihood of randomly generating SSID strings containing controversial words. - * - * @param number The number to convert to a string with radix "base". - * @param base The radix to convert "number" into. Must be between 2 and 36. - * @return A string of "number" encoded in radix "base". - */ -String uint64ToString(uint64_t number, byte base = 16); - -/** - * Note that using base 10 instead of 16 increases conversion time by roughly a factor of 2, due to unfavourable 64-bit arithmetic. - * Note that using a base higher than 16 increases likelihood of randomly generating SSID strings containing controversial words. - * - * @param string The string to convert to uint64_t. String must use radix "base". - * @param base The radix of "string". Must be between 2 and 36. - * @return A uint64_t of the string, using radix "base" during decoding. - */ -uint64_t stringToUint64(const String &string, byte base = 16); - -/** - * Convert the contents of a uint8_t array to a String in HEX format. The resulting String starts from index 0 of the array. - * All array elements will be padded with zeroes to ensure they are converted to 2 String characters each. - * - * @param uint8Array The array to make into a HEX String. - * @param arrayLength The size of uint8Array, in bytes. - * @return Normally a String containing the HEX representation of the uint8Array. An empty String if the memory allocation for the String failed. - */ -String uint8ArrayToHexString(const uint8_t *uint8Array, uint32_t arrayLength); - -/** - * Convert the contents of a String in HEX format to a uint8_t array. Index 0 of the array will represent the start of the String. - * There must be 2 String characters for each array element. Use padding with zeroes where required. - * - * @param hexString The HEX String to convert to a uint8_t array. Must contain at least 2*arrayLength characters. - * @param uint8Array The array to fill with the contents of the hexString. - * @param arrayLength The number of bytes to fill in uint8Array. - * @return A pointer to the uint8Array. - */ -uint8_t *hexStringToUint8Array(const String &hexString, uint8_t *uint8Array, uint32_t arrayLength); - -/** - * Stores the exact values of uint8Array in a String, even null values. - * Note that Strings containing null values will look like several separate Strings to methods that rely on null values to find the String end, such as String::substring. - * In these cases, it may be helpful to use String::c_str() or String::begin() to access the String data buffer directly instead. - * - * The unbuffered version temporarily edits uint8Array during execution, but restores the array to its original state when returning in a controlled manner. - * - * @param uint8Array The array to make into a multiString. - * @param arrayLength The size of uint8Array, in bytes. - * @return Normally a String containing the same data as the uint8Array. An empty String if the memory allocation for the String failed. - */ -String uint8ArrayToMultiString(uint8_t *uint8Array, uint32_t arrayLength); - -/** - * Stores the exact values of uint8Array in a String, even null values. - * Note that Strings containing null values will look like several separate Strings to methods that rely on null values to find the String end, such as String::substring. - * In these cases, it may be helpful to use String::c_str() or String::begin() to access the String data buffer directly instead. - * - * The buffered version is slower and uses more memory than the unbuffered version, but can operate on const arrays. - * - * @param uint8Array The array to make into a multiString. - * @param arrayLength The size of uint8Array, in bytes. - * @return Normally a String containing the same data as the uint8Array. An empty String if the memory allocation for the String failed. - */ -String bufferedUint8ArrayToMultiString(const uint8_t *uint8Array, uint32_t arrayLength); - -/** - * Takes a uint8_t array and converts the first 6 bytes to a hexadecimal string. - * - * @param mac A uint8_t array with the mac address to convert to a string. Should be 6 bytes in total. - * @return A hexadecimal string representation of the mac. - */ -String macToString(const uint8_t *mac); - -/** - * Takes a String and converts the first 12 characters to uint8_t numbers which are stored in the macArray from low to high index. Assumes hexadecimal number encoding. - * - * @param macString A String which begins with the mac address to store in the array as a hexadecimal number. - * @param macArray A uint8_t array that will hold the mac address once the function returns. Should have a size of at least 6 bytes. - * @return The macArray. - */ -uint8_t *stringToMac(const String &macString, uint8_t *macArray); - -/** - * Takes a uint8_t array and converts the first 6 bytes to a uint64_t. Assumes index 0 of the array contains MSB. - * - * @param macArray A uint8_t array with the mac address to convert to a uint64_t. Should be 6 bytes in total. - * @return A uint64_t representation of the mac. - */ -uint64_t macToUint64(const uint8_t *macArray); - -/** - * Takes a uint64_t value and stores the bits of the first 6 bytes (LSB) in a uint8_t array. Assumes index 0 of the array should contain MSB. - * - * @param macValue The uint64_t value to convert to a mac array. Value must fit within 6 bytes. - * @param macArray A uint8_t array that will hold the mac address once the function returns. Should have a size of at least 6 bytes. - * @return The macArray. - */ -uint8_t *uint64ToMac(uint64_t macValue, uint8_t *macArray); - -/** - * Takes a uint64_t value and stores the bits in a uint8_t array. Assumes index 0 of the array should contain MSB. - * - * @param value The uint64_t value to convert to a uint8_t array. - * @param resultArray A uint8_t array that will hold the result once the function returns. Should have a size of at least 8 bytes. - * @return The resultArray. - */ -uint8_t *uint64ToUint8Array(uint64_t value, uint8_t *resultArray); - -/** - * Takes a uint8_t array and converts the first 8 (lowest index) elements to a uint64_t. Assumes index 0 of the array contains MSB. - * - * @param inputArray A uint8_t array containing the data to convert to a uint64_t. Should have a size of at least 8 bytes. - * @return A uint64_t representation of the first 8 bytes of the array. - */ -uint64_t uint8ArrayToUint64(const uint8_t *inputArray); - -/** - * Conversion function that can be used on MeshBackend classes instead of dynamic_cast since RTTI is disabled. - * - * @param T The MeshBackend class pointer type to cast the meshBackendBaseInstance pointer into. - * @param meshBackendBaseInstance The instance pointer to cast. - * @return A pointer of type T to meshBackendBaseInstance if meshBackendBaseInstance is of type T. nullptr otherwise. - */ -template -T meshBackendCast(MeshBackendBase *meshBackendBaseInstance) +namespace MeshTypeConversionFunctions { - // The only valid template arguments are handled by the template specializations below, so ending up here is an error. - static_assert(std::is_same::value || std::is_same::value, - "Error: Invalid MeshBackend class type. Make sure the template argument to meshBackendCast is supported!"); + /** + * Note that using base 10 instead of 16 increases conversion time by roughly a factor of 5, due to unfavourable 64-bit arithmetic. + * Note that using a base higher than 16 increases likelihood of randomly generating SSID strings containing controversial words. + * + * @param number The number to convert to a string with radix "base". + * @param base The radix to convert "number" into. Must be between 2 and 36. + * @return A string of "number" encoded in radix "base". + */ + String uint64ToString(uint64_t number, byte base = 16); + + /** + * Note that using base 10 instead of 16 increases conversion time by roughly a factor of 2, due to unfavourable 64-bit arithmetic. + * Note that using a base higher than 16 increases likelihood of randomly generating SSID strings containing controversial words. + * + * @param string The string to convert to uint64_t. String must use radix "base". + * @param base The radix of "string". Must be between 2 and 36. + * @return A uint64_t of the string, using radix "base" during decoding. + */ + uint64_t stringToUint64(const String &string, byte base = 16); + + /** + * Convert the contents of a uint8_t array to a String in HEX format. The resulting String starts from index 0 of the array. + * All array elements will be padded with zeroes to ensure they are converted to 2 String characters each. + * + * @param uint8Array The array to make into a HEX String. + * @param arrayLength The size of uint8Array, in bytes. + * @return Normally a String containing the HEX representation of the uint8Array. An empty String if the memory allocation for the String failed. + */ + String uint8ArrayToHexString(const uint8_t *uint8Array, uint32_t arrayLength); + + /** + * Convert the contents of a String in HEX format to a uint8_t array. Index 0 of the array will represent the start of the String. + * There must be 2 String characters for each array element. Use padding with zeroes where required. + * + * @param hexString The HEX String to convert to a uint8_t array. Must contain at least 2*arrayLength characters. + * @param uint8Array The array to fill with the contents of the hexString. + * @param arrayLength The number of bytes to fill in uint8Array. + * @return A pointer to the uint8Array. + */ + uint8_t *hexStringToUint8Array(const String &hexString, uint8_t *uint8Array, uint32_t arrayLength); + + /** + * Stores the exact values of uint8Array in a String, even null values. + * Note that Strings containing null values will look like several separate Strings to methods that rely on null values to find the String end, such as String::substring. + * In these cases, it may be helpful to use String::c_str() or String::begin() to access the String data buffer directly instead. + * + * The unbuffered version temporarily edits uint8Array during execution, but restores the array to its original state when returning in a controlled manner. + * + * @param uint8Array The array to make into a multiString. + * @param arrayLength The size of uint8Array, in bytes. + * @return Normally a String containing the same data as the uint8Array. An empty String if the memory allocation for the String failed. + */ + String uint8ArrayToMultiString(uint8_t *uint8Array, uint32_t arrayLength); + + /** + * Stores the exact values of uint8Array in a String, even null values. + * Note that Strings containing null values will look like several separate Strings to methods that rely on null values to find the String end, such as String::substring. + * In these cases, it may be helpful to use String::c_str() or String::begin() to access the String data buffer directly instead. + * + * The buffered version is slower and uses more memory than the unbuffered version, but can operate on const arrays. + * + * @param uint8Array The array to make into a multiString. + * @param arrayLength The size of uint8Array, in bytes. + * @return Normally a String containing the same data as the uint8Array. An empty String if the memory allocation for the String failed. + */ + String bufferedUint8ArrayToMultiString(const uint8_t *uint8Array, uint32_t arrayLength); + + /** + * Takes a uint8_t array and converts the first 6 bytes to a hexadecimal string. + * + * @param mac A uint8_t array with the mac address to convert to a string. Should be 6 bytes in total. + * @return A hexadecimal string representation of the mac. + */ + String macToString(const uint8_t *mac); + + /** + * Takes a String and converts the first 12 characters to uint8_t numbers which are stored in the macArray from low to high index. Assumes hexadecimal number encoding. + * + * @param macString A String which begins with the mac address to store in the array as a hexadecimal number. + * @param macArray A uint8_t array that will hold the mac address once the function returns. Should have a size of at least 6 bytes. + * @return The macArray. + */ + uint8_t *stringToMac(const String &macString, uint8_t *macArray); + + /** + * Takes a uint8_t array and converts the first 6 bytes to a uint64_t. Assumes index 0 of the array contains MSB. + * + * @param macArray A uint8_t array with the mac address to convert to a uint64_t. Should be 6 bytes in total. + * @return A uint64_t representation of the mac. + */ + uint64_t macToUint64(const uint8_t *macArray); + + /** + * Takes a uint64_t value and stores the bits of the first 6 bytes (LSB) in a uint8_t array. Assumes index 0 of the array should contain MSB. + * + * @param macValue The uint64_t value to convert to a mac array. Value must fit within 6 bytes. + * @param macArray A uint8_t array that will hold the mac address once the function returns. Should have a size of at least 6 bytes. + * @return The macArray. + */ + uint8_t *uint64ToMac(uint64_t macValue, uint8_t *macArray); + + /** + * Takes a uint64_t value and stores the bits in a uint8_t array. Assumes index 0 of the array should contain MSB. + * + * @param value The uint64_t value to convert to a uint8_t array. + * @param resultArray A uint8_t array that will hold the result once the function returns. Should have a size of at least 8 bytes. + * @return The resultArray. + */ + uint8_t *uint64ToUint8Array(uint64_t value, uint8_t *resultArray); + + /** + * Takes a uint8_t array and converts the first 8 (lowest index) elements to a uint64_t. Assumes index 0 of the array contains MSB. + * + * @param inputArray A uint8_t array containing the data to convert to a uint64_t. Should have a size of at least 8 bytes. + * @return A uint64_t representation of the first 8 bytes of the array. + */ + uint64_t uint8ArrayToUint64(const uint8_t *inputArray); + + /** + * Conversion function that can be used on MeshBackend classes instead of dynamic_cast since RTTI is disabled. + * + * @param T The MeshBackend class pointer type to cast the meshBackendBaseInstance pointer into. + * @param meshBackendBaseInstance The instance pointer to cast. + * @return A pointer of type T to meshBackendBaseInstance if meshBackendBaseInstance is of type T. nullptr otherwise. + */ + template + T meshBackendCast(MeshBackendBase *meshBackendBaseInstance) + { + // The only valid template arguments are handled by the template specializations below, so ending up here is an error. + static_assert(std::is_same::value || std::is_same::value, + "Error: Invalid MeshBackend class type. Make sure the template argument to meshBackendCast is supported!"); + } + + // These template specializations allow us to put the main template functionality in the .cpp file (which gives better encapsulation). + template <> + EspnowMeshBackend *meshBackendCast(MeshBackendBase *meshBackendBaseInstance); + + template <> + TcpIpMeshBackend *meshBackendCast(MeshBackendBase *meshBackendBaseInstance); } -// These template specializations allow us to put the main template functionality in the .cpp file (which gives better encapsulation). -template <> -EspnowMeshBackend *meshBackendCast(MeshBackendBase *meshBackendBaseInstance); - -template <> -TcpIpMeshBackend *meshBackendCast(MeshBackendBase *meshBackendBaseInstance); +#ifndef ESP8266WIFIMESH_DISABLE_COMPATIBILITY +using namespace MeshTypeConversionFunctions; // Required to retain backwards compatibility. TODO: Remove in core release 3.0.0 +#endif #endif diff --git a/libraries/ESP8266WiFiMesh/src/UtilityFunctions.cpp b/libraries/ESP8266WiFiMesh/src/UtilityFunctions.cpp index 6b4af4c018..08dfb51111 100644 --- a/libraries/ESP8266WiFiMesh/src/UtilityFunctions.cpp +++ b/libraries/ESP8266WiFiMesh/src/UtilityFunctions.cpp @@ -26,20 +26,23 @@ #include "UtilityFunctions.h" #include -bool macEqual(const uint8_t *macOne, const uint8_t *macTwo) +namespace MeshUtilityFunctions { - for(int i = 0; i <= 5; i++) + bool macEqual(const uint8_t *macOne, const uint8_t *macTwo) { - if(macOne[i] != macTwo[i]) + for(int i = 0; i <= 5; i++) { - return false; + if(macOne[i] != macTwo[i]) + { + return false; + } } + + return true; + } + + uint64_t randomUint64() + { + return (((uint64_t)RANDOM_REG32 << 32) | (uint64_t)RANDOM_REG32); } - - return true; -} - -uint64_t randomUint64() -{ - return (((uint64_t)RANDOM_REG32 << 32) | (uint64_t)RANDOM_REG32); } diff --git a/libraries/ESP8266WiFiMesh/src/UtilityFunctions.h b/libraries/ESP8266WiFiMesh/src/UtilityFunctions.h index 6fa0afb617..fefc7fb0be 100644 --- a/libraries/ESP8266WiFiMesh/src/UtilityFunctions.h +++ b/libraries/ESP8266WiFiMesh/src/UtilityFunctions.h @@ -27,9 +27,12 @@ #define __UTILITYFUNCTIONS_H__ #include - -bool macEqual(const uint8_t *macOne, const uint8_t *macTwo); -uint64_t randomUint64(); +namespace MeshUtilityFunctions +{ + bool macEqual(const uint8_t *macOne, const uint8_t *macTwo); + + uint64_t randomUint64(); +} #endif From 16801f3dacf5e913ffef0c53051b83a8d155413a Mon Sep 17 00:00:00 2001 From: Anders Date: Thu, 5 Mar 2020 15:30:20 +0100 Subject: [PATCH 15/30] - Rework ExpiringTimeTracker to be based on PolledTimeout. - Ensure espnowDelay and floodingMeshDelay always performs maintenance. - Rework MutexTracker to use shared_ptr. - Change enums to enum class. - Change typedef to using. - Add HeapMonitor class. - Make _messageIDs be a map instead of an unordered_map to reduce heap usage. - Use the possibly broken wifi_country ESP8266 API to check for legal WiFi channels when setting WiFi channels. - Make MessageData, RequestData and ResponseData contain a TimeTracker rather than inherit from TimeTracker. - Add deprecated attribute to TransmissionResult. - Remove superfluous elses. - Reduce cyclomatic complexity. - Change postfix ++ and -- to prefix. - Generalize getEncryptedConnectionIterator method. - Increase code NRVO compatibility. - Change _connectionAttemptTimeoutMs type from int32_t to uint32_t. - Add deprecated attribute to ESP8266WiFiMesh. - Add some constness to TypeConversionFunctions. - Move base36 arrays to PROGMEM in TypeConversionFunctions.cpp. - Add deprecated atttribute to SHA1 and MD5 hashes. - Remove _warningsEnabled in CryptoInterface since this has been replaced by the deprecated attribute. - Prefix all TypeConversion getters with "get". - Improve comments. - Fix merge conflict. --- .../examples/HelloEspnow/HelloEspnow.ino | 115 +++-- .../examples/HelloMesh/HelloMesh.ino | 4 +- .../examples/HelloTcpIp/HelloTcpIp.ino | 16 +- libraries/ESP8266WiFiMesh/keywords.txt | 2 +- .../ESP8266WiFiMesh/src/CryptoInterface.cpp | 64 ++- .../ESP8266WiFiMesh/src/CryptoInterface.h | 64 +-- .../ESP8266WiFiMesh/src/ESP8266WiFiMesh.cpp | 12 +- .../ESP8266WiFiMesh/src/ESP8266WiFiMesh.h | 5 +- .../src/EncryptedConnectionData.cpp | 6 +- .../ESP8266WiFiMesh/src/EspnowMeshBackend.cpp | 417 +++++++++--------- .../ESP8266WiFiMesh/src/EspnowMeshBackend.h | 153 +++---- .../src/EspnowProtocolInterpreter.h | 2 +- .../src/ExpiringTimeTracker.cpp | 95 +++- .../ESP8266WiFiMesh/src/ExpiringTimeTracker.h | 42 +- .../ESP8266WiFiMesh/src/FloodingMesh.cpp | 43 +- libraries/ESP8266WiFiMesh/src/FloodingMesh.h | 13 +- libraries/ESP8266WiFiMesh/src/HeapMonitor.cpp | 63 +++ libraries/ESP8266WiFiMesh/src/HeapMonitor.h | 66 +++ .../ESP8266WiFiMesh/src/MeshBackendBase.cpp | 20 +- .../ESP8266WiFiMesh/src/MeshBackendBase.h | 32 +- libraries/ESP8266WiFiMesh/src/MessageData.cpp | 10 +- libraries/ESP8266WiFiMesh/src/MessageData.h | 4 +- .../ESP8266WiFiMesh/src/MutexTracker.cpp | 43 +- libraries/ESP8266WiFiMesh/src/MutexTracker.h | 20 +- .../ESP8266WiFiMesh/src/NetworkInfoBase.cpp | 2 +- libraries/ESP8266WiFiMesh/src/RequestData.cpp | 7 +- libraries/ESP8266WiFiMesh/src/RequestData.h | 8 +- .../ESP8266WiFiMesh/src/ResponseData.cpp | 31 +- libraries/ESP8266WiFiMesh/src/ResponseData.h | 8 +- .../ESP8266WiFiMesh/src/TcpIpMeshBackend.cpp | 66 ++- .../ESP8266WiFiMesh/src/TcpIpMeshBackend.h | 28 +- libraries/ESP8266WiFiMesh/src/TimeTracker.h | 1 + .../src/TransmissionOutcome.cpp | 8 +- .../ESP8266WiFiMesh/src/TransmissionOutcome.h | 20 +- .../ESP8266WiFiMesh/src/TransmissionResult.h | 13 +- .../src/TypeConversionFunctions.cpp | 46 +- .../src/TypeConversionFunctions.h | 16 +- .../ESP8266WiFiMesh/src/UtilityFunctions.cpp | 2 +- 38 files changed, 906 insertions(+), 661 deletions(-) create mode 100644 libraries/ESP8266WiFiMesh/src/HeapMonitor.cpp create mode 100644 libraries/ESP8266WiFiMesh/src/HeapMonitor.h diff --git a/libraries/ESP8266WiFiMesh/examples/HelloEspnow/HelloEspnow.ino b/libraries/ESP8266WiFiMesh/examples/HelloEspnow/HelloEspnow.ino index 3a4225e586..3a03b8416c 100644 --- a/libraries/ESP8266WiFiMesh/examples/HelloEspnow/HelloEspnow.ino +++ b/libraries/ESP8266WiFiMesh/examples/HelloEspnow/HelloEspnow.ino @@ -1,4 +1,4 @@ -#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. +#define ESP8266WIFIMESH_DISABLE_COMPATIBILITY // Excludes redundant compatibility code. TODO: Should be used for new code until the compatibility code is removed with release 3.0.0 of the Arduino core. #include #include @@ -40,7 +40,7 @@ unsigned int responseNumber = 0; const char broadcastMetadataDelimiter = 23; // 23 = End-of-Transmission-Block (ETB) control character in ASCII String manageRequest(const String &request, MeshBackendBase &meshInstance); -transmission_status_t manageResponse(const String &response, MeshBackendBase &meshInstance); +TransmissionStatusType manageResponse(const String &response, MeshBackendBase &meshInstance); void networkFilter(int numberOfNetworks, MeshBackendBase &meshInstance); bool broadcastFilter(String &firstTransmission, EspnowMeshBackend &meshInstance); @@ -55,27 +55,22 @@ EspnowMeshBackend espnowNode = EspnowMeshBackend(manageRequest, manageResponse, @return The string to send back to the other node. For ESP-NOW, return an empy string ("") if no response should be sent. */ String manageRequest(const String &request, MeshBackendBase &meshInstance) { - // We do not store strings in flash (via F()) in this function. - // The reason is that the other node will be waiting for our response, - // so keeping the strings in RAM will give a (small) improvement in response time. - // Of course, it is advised to adjust this approach based on RAM requirements. - // To get the actual class of the polymorphic meshInstance, do as follows (meshBackendCast replaces dynamic_cast since RTTI is disabled) if (EspnowMeshBackend *espnowInstance = TypeCast::meshBackendCast(&meshInstance)) { - String transmissionEncrypted = espnowInstance->receivedEncryptedTransmission() ? ", Encrypted transmission" : ", Unencrypted transmission"; - Serial.print("ESP-NOW (" + espnowInstance->getSenderMac() + transmissionEncrypted + "): "); + String transmissionEncrypted = espnowInstance->receivedEncryptedTransmission() ? F(", Encrypted transmission") : F(", Unencrypted transmission"); + Serial.print(String(F("ESP-NOW (")) + espnowInstance->getSenderMac() + transmissionEncrypted + F("): ")); } else if (TcpIpMeshBackend *tcpIpInstance = TypeCast::meshBackendCast(&meshInstance)) { (void)tcpIpInstance; // This is useful to remove a "unused parameter" compiler warning. Does nothing else. - Serial.print("TCP/IP: "); + Serial.print(F("TCP/IP: ")); } else { - Serial.print("UNKNOWN!: "); + Serial.print(F("UNKNOWN!: ")); } /* Print out received message */ // Only show first 100 characters because printing a large String takes a lot of time, which is a bad thing for a callback function. // If you need to print the whole String it is better to store it and print it in the loop() later. // Note that request.substring will not work as expected if the String contains null values as data. - Serial.print("Request received: "); + Serial.print(F("Request received: ")); if (request.charAt(0) == 0) { Serial.println(request); // substring will not work for multiStrings. @@ -84,7 +79,7 @@ String manageRequest(const String &request, MeshBackendBase &meshInstance) { } /* 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('.')); } /** @@ -94,15 +89,15 @@ String manageRequest(const String &request, MeshBackendBase &meshInstance) { @param meshInstance The MeshBackendBase instance that called the function. @return The status code resulting from the response, as an int */ -transmission_status_t manageResponse(const String &response, MeshBackendBase &meshInstance) { - transmission_status_t statusCode = TS_TRANSMISSION_COMPLETE; +TransmissionStatusType manageResponse(const String &response, MeshBackendBase &meshInstance) { + TransmissionStatusType statusCode = TransmissionStatusType::TRANSMISSION_COMPLETE; // To get the actual class of the polymorphic meshInstance, do as follows (meshBackendCast replaces dynamic_cast since RTTI is disabled) if (EspnowMeshBackend *espnowInstance = TypeCast::meshBackendCast(&meshInstance)) { - String transmissionEncrypted = espnowInstance->receivedEncryptedTransmission() ? ", Encrypted transmission" : ", Unencrypted transmission"; - Serial.print("ESP-NOW (" + espnowInstance->getSenderMac() + transmissionEncrypted + "): "); + String transmissionEncrypted = espnowInstance->receivedEncryptedTransmission() ? F(", Encrypted transmission") : F(", Unencrypted transmission"); + Serial.print(String(F("ESP-NOW (")) + espnowInstance->getSenderMac() + transmissionEncrypted + F("): ")); } else if (TcpIpMeshBackend *tcpIpInstance = TypeCast::meshBackendCast(&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. // With TCP/IP the response will follow immediately after the request, so the stored message will not have changed. @@ -111,7 +106,7 @@ transmission_status_t manageResponse(const String &response, MeshBackendBase &me Serial.print(F("Request sent: ")); Serial.println(tcpIpInstance->getCurrentMessage().substring(0, 100)); } else { - Serial.print("UNKNOWN!: "); + Serial.print(F("UNKNOWN!: ")); } /* Print out received message */ @@ -146,7 +141,7 @@ void networkFilter(int numberOfNetworks, MeshBackendBase &meshInstance) { } else if (TcpIpMeshBackend *tcpIpInstance = TypeCast::meshBackendCast(&meshInstance)) { tcpIpInstance->connectionQueue().push_back(networkIndex); } else { - Serial.println(String(F("Invalid mesh backend!"))); + Serial.println(F("Invalid mesh backend!")); } } } @@ -177,7 +172,7 @@ bool broadcastFilter(String &firstTransmission, EspnowMeshBackend &meshInstance) 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 } else { // Remove metadata from message and mark as accepted broadcast. @@ -274,7 +269,7 @@ void setup() { // Storing our message in the EspnowMeshBackend instance is not required, but can be useful for organizing code, especially when using many EspnowMeshBackend instances. // Note that calling the multi-recipient versions of espnowNode.attemptTransmission and espnowNode.attemptAutoEncryptingTransmission will replace the stored message with whatever message is transmitted. // Also note that the maximum allowed number of ASCII characters in a ESP-NOW message is given by EspnowMeshBackend::getMaxMessageLength(). - espnowNode.setMessage(String(F("Hello world request #")) + String(requestNumber) + String(F(" from ")) + espnowNode.getMeshName() + espnowNode.getNodeID() + String(F("."))); + espnowNode.setMessage(String(F("Hello world request #")) + String(requestNumber) + F(" from ") + espnowNode.getMeshName() + espnowNode.getNodeID() + String('.')); espnowNode.setTransmissionOutcomesUpdateHook(exampleTransmissionOutcomesUpdateHook); espnowNode.setResponseTransmittedHook(exampleResponseTransmittedHook); @@ -290,7 +285,7 @@ void setup() { // Uncomment the lines below to use automatic AEAD encryption/decryption of messages sent/received. // All nodes this node wishes to communicate with must then also use encrypted messages with the same getEspnowMessageEncryptionKey(), or messages will not be accepted. // Note that using AEAD encrypted messages will reduce the number of message bytes that can be transmitted. - //espnowNode.setEspnowMessageEncryptionKey("ChangeThisKeySeed_TODO"); // The message encryption key should always be set manually. Otherwise a default key (all zeroes) is used. + //espnowNode.setEspnowMessageEncryptionKey(F("ChangeThisKeySeed_TODO")); // The message encryption key should always be set manually. Otherwise a default key (all zeroes) is used. //espnowNode.setUseEncryptedMessages(true); } @@ -306,11 +301,11 @@ void loop() { EspnowMeshBackend::performEspnowMaintenance(); if (millis() - timeOfLastScan > 10000) { // Give other nodes some time to connect between data transfers. - Serial.println("\nPerforming unencrypted ESP-NOW transmissions."); + Serial.println(F("\nPerforming unencrypted ESP-NOW transmissions.")); uint32_t startTime = millis(); espnowNode.attemptTransmission(espnowNode.getMessage()); - Serial.println("Scan and " + String(espnowNode.latestTransmissionOutcomes().size()) + " transmissions done in " + String(millis() - startTime) + " ms."); + Serial.println(String(F("Scan and ")) + String(espnowNode.latestTransmissionOutcomes().size()) + F(" transmissions done in ") + String(millis() - startTime) + F(" ms.")); timeOfLastScan = millis(); @@ -328,19 +323,19 @@ void loop() { Serial.println(F("No mesh AP found.")); } else { for (TransmissionOutcome &transmissionOutcome : espnowNode.latestTransmissionOutcomes()) { - if (transmissionOutcome.transmissionStatus() == TS_TRANSMISSION_FAILED) { + if (transmissionOutcome.transmissionStatus() == TransmissionStatusType::TRANSMISSION_FAILED) { Serial.println(String(F("Transmission failed to mesh AP ")) + transmissionOutcome.SSID()); - } else if (transmissionOutcome.transmissionStatus() == TS_CONNECTION_FAILED) { + } else if (transmissionOutcome.transmissionStatus() == TransmissionStatusType::CONNECTION_FAILED) { Serial.println(String(F("Connection failed to mesh AP ")) + transmissionOutcome.SSID()); - } else if (transmissionOutcome.transmissionStatus() == TS_TRANSMISSION_COMPLETE) { + } else if (transmissionOutcome.transmissionStatus() == TransmissionStatusType::TRANSMISSION_COMPLETE) { // No need to do anything, transmission was successful. } else { - Serial.println(String(F("Invalid transmission status for ")) + transmissionOutcome.SSID() + String(F("!"))); + Serial.println(String(F("Invalid transmission status for ")) + transmissionOutcome.SSID() + String('!')); assert(F("Invalid transmission status returned from responseHandler!") && false); } } - Serial.println("\nPerforming ESP-NOW broadcast."); + Serial.println(F("\nPerforming ESP-NOW broadcast.")); startTime = millis(); @@ -348,9 +343,9 @@ void loop() { // Note that data that comes before broadcastMetadataDelimiter should not contain any broadcastMetadataDelimiter characters, // otherwise the broadcastFilter function used in this example file will not work. String broadcastMetadata = espnowNode.getMeshName() + String(broadcastMetadataDelimiter); - String broadcastMessage = String(F("Broadcast #")) + String(requestNumber) + String(F(" from ")) + espnowNode.getMeshName() + espnowNode.getNodeID() + String(F(".")); + String broadcastMessage = String(F("Broadcast #")) + String(requestNumber) + F(" from ") + espnowNode.getMeshName() + espnowNode.getNodeID() + String('.'); espnowNode.broadcast(broadcastMetadata + broadcastMessage); - Serial.println("Broadcast to all mesh nodes done in " + String(millis() - startTime) + " ms."); + Serial.println(String(F("Broadcast to all mesh nodes done in ")) + String(millis() - startTime) + F(" ms.")); espnowDelay(100); // Wait for responses (broadcasts can receive an unlimited number of responses, other transmissions can only receive one response). @@ -358,25 +353,25 @@ void loop() { // 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); + String espnowMessage = TypeCast::uint8ArrayToMultiString(dataArray, sizeof dataArray) + F(" from ") + espnowNode.getMeshName() + espnowNode.getNodeID() + String('.'); + Serial.println(String(F("\nTransmitting: ")) + espnowMessage); espnowNode.attemptTransmission(espnowMessage, false); espnowDelay(100); // Wait for response. - Serial.println("\nPerforming encrypted ESP-NOW transmissions."); + Serial.println(F("\nPerforming encrypted ESP-NOW transmissions.")); uint8_t targetBSSID[6] {0}; // 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) == EncryptedConnectionStatus::CONNECTION_ESTABLISHED) { // The WiFi scan will detect the AP MAC, but this will automatically be converted to the encrypted STA MAC by the framework. String peerMac = TypeCast::macToString(targetBSSID); - Serial.println("Encrypted ESP-NOW connection with " + peerMac + " established!"); + Serial.println(String(F("Encrypted ESP-NOW connection with ")) + peerMac + F(" established!")); // Making a transmission now will cause messages to targetBSSID to be encrypted. - String espnowMessage = "This message is encrypted only when received by node " + peerMac; - Serial.println("\nTransmitting: " + espnowMessage); + String espnowMessage = String(F("This message is encrypted only when received by node ")) + peerMac; + Serial.println(String(F("\nTransmitting: ")) + espnowMessage); espnowNode.attemptTransmission(espnowMessage, false); espnowDelay(100); // Wait for response. @@ -389,48 +384,48 @@ void loop() { espnowNode.removeEncryptedConnection(targetBSSID); // Note that the peer will still be encrypted, so although we can send unencrypted messages to the peer, we cannot read the encrypted responses it sends back. - espnowMessage = "This message is no longer encrypted when received by node " + peerMac; - Serial.println("\nTransmitting: " + espnowMessage); + espnowMessage = String(F("This message is no longer encrypted when received by node ")) + peerMac; + Serial.println(String(F("\nTransmitting: ")) + espnowMessage); espnowNode.attemptTransmission(espnowMessage, false); espnowDelay(100); // Wait for response. - Serial.println("Cannot read the encrypted response..."); + Serial.println(F("Cannot read the encrypted response...")); // Let's re-add our stored connection so we can communicate properly with targetBSSID again! espnowNode.addEncryptedConnection(serializedEncryptedConnection); - espnowMessage = "This message is once again encrypted when received by node " + peerMac; - Serial.println("\nTransmitting: " + espnowMessage); + espnowMessage = String(F("This message is once again encrypted when received by node ")) + peerMac; + Serial.println(String(F("\nTransmitting: ")) + espnowMessage); espnowNode.attemptTransmission(espnowMessage, false); espnowDelay(100); // Wait for response. Serial.println(); // If we want to remove the encrypted connection on both nodes, we can do it like this. - encrypted_connection_removal_outcome_t removalOutcome = espnowNode.requestEncryptedConnectionRemoval(targetBSSID); - if (removalOutcome == ECRO_REMOVAL_SUCCEEDED) { - Serial.println(peerMac + " is no longer encrypted!"); + EncryptedConnectionRemovalOutcome removalOutcome = espnowNode.requestEncryptedConnectionRemoval(targetBSSID); + if (removalOutcome == EncryptedConnectionRemovalOutcome::REMOVAL_SUCCEEDED) { + Serial.println(peerMac + F(" is no longer encrypted!")); - espnowMessage = "This message is only received by node " + peerMac + ". Transmitting in this way will not change the transmission state of the sender."; - Serial.println("Transmitting: " + espnowMessage); + espnowMessage = String(F("This message is only received by node ")) + peerMac + F(". Transmitting in this way will not change the transmission state of the sender."); + Serial.println(String(F("Transmitting: ")) + espnowMessage); espnowNode.attemptTransmission(espnowMessage, EspnowNetworkInfo(targetBSSID)); espnowDelay(100); // Wait for response. Serial.println(); // Of course, we can also just create a temporary encrypted connection that will remove itself once its duration has passed. - if (espnowNode.requestTemporaryEncryptedConnection(targetBSSID, 1000) == ECS_CONNECTION_ESTABLISHED) { + if (espnowNode.requestTemporaryEncryptedConnection(targetBSSID, 1000) == EncryptedConnectionStatus::CONNECTION_ESTABLISHED) { espnowDelay(42); uint32_t remainingDuration = 0; EspnowMeshBackend::getConnectionInfo(targetBSSID, &remainingDuration); - espnowMessage = "Messages this node sends to " + peerMac + " will be encrypted for " + String(remainingDuration) + " ms more."; - Serial.println("\nTransmitting: " + espnowMessage); + espnowMessage = String(F("Messages this node sends to ")) + peerMac + F(" will be encrypted for ") + String(remainingDuration) + F(" ms more."); + Serial.println(String(F("\nTransmitting: ")) + espnowMessage); espnowNode.attemptTransmission(espnowMessage, false); EspnowMeshBackend::getConnectionInfo(targetBSSID, &remainingDuration); espnowDelay(remainingDuration + 100); - espnowMessage = "Due to encrypted connection expiration, this message is no longer encrypted when received by node " + peerMac; - Serial.println("\nTransmitting: " + espnowMessage); + espnowMessage = String(F("Due to encrypted connection expiration, this message is no longer encrypted when received by node ")) + peerMac; + Serial.println(String(F("\nTransmitting: ")) + espnowMessage); espnowNode.attemptTransmission(espnowMessage, false); espnowDelay(100); // Wait for response. } @@ -438,24 +433,24 @@ void loop() { // Or if we prefer we can just let the library automatically create brief encrypted connections which are long enough to transmit an encrypted message. // Note that encrypted responses will not be received, unless there already was an encrypted connection established with the peer before attemptAutoEncryptingTransmission was called. // This can be remedied via the requestPermanentConnections argument, though it must be noted that the maximum number of encrypted connections supported at a time is 6. - espnowMessage = "This message is always encrypted, regardless of receiver."; - Serial.println("\nTransmitting: " + espnowMessage); + espnowMessage = F("This message is always encrypted, regardless of receiver."); + Serial.println(String(F("\nTransmitting: ")) + espnowMessage); espnowNode.attemptAutoEncryptingTransmission(espnowMessage); espnowDelay(100); // Wait for response. } else { - Serial.println("Ooops! Encrypted connection removal failed. Status: " + String(removalOutcome)); + Serial.println(String(F("Ooops! Encrypted connection removal failed. Status: ")) + String(static_cast(removalOutcome))); } // Finally, should you ever want to stop other parties from sending unencrypted messages to the node // setAcceptsUnencryptedRequests(false); // can be used for this. It applies to both encrypted connection requests and regular transmissions. - Serial.println("\n##############################################################################################"); + Serial.println(F("\n##############################################################################################")); } // Our last request was sent to all nodes found, so time to create a new request. - espnowNode.setMessage(String(F("Hello world request #")) + String(++requestNumber) + String(F(" from ")) - + espnowNode.getMeshName() + espnowNode.getNodeID() + String(F("."))); + espnowNode.setMessage(String(F("Hello world request #")) + String(++requestNumber) + F(" from ") + + espnowNode.getMeshName() + espnowNode.getNodeID() + String('.')); } Serial.println(); diff --git a/libraries/ESP8266WiFiMesh/examples/HelloMesh/HelloMesh.ino b/libraries/ESP8266WiFiMesh/examples/HelloMesh/HelloMesh.ino index 22836e17c5..5855604b36 100644 --- a/libraries/ESP8266WiFiMesh/examples/HelloMesh/HelloMesh.ino +++ b/libraries/ESP8266WiFiMesh/examples/HelloMesh/HelloMesh.ino @@ -5,7 +5,7 @@ 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. +#define ESP8266WIFIMESH_DISABLE_COMPATIBILITY // Excludes redundant compatibility code. TODO: Should be used for new code until the compatibility code is removed with release 3.0.0 of the Arduino core. #include #include @@ -175,7 +175,7 @@ void loop() { floodingMeshDelay(1); // If you wish to transmit only to a single node, try using one of the following methods (requires the node to be within range and know the MAC of the recipient): - // Unencrypted: transmission_status_t floodingMesh.getEspnowMeshBackend().attemptTransmission(message, EspnowNetworkInfo(recipientMac)); + // Unencrypted: TransmissionStatusType floodingMesh.getEspnowMeshBackend().attemptTransmission(message, EspnowNetworkInfo(recipientMac)); // Encrypted (slow): floodingMesh.getEspnowMeshBackend().attemptAutoEncryptingTransmission(message, EspnowNetworkInfo(recipientMac)); if (theOne) { diff --git a/libraries/ESP8266WiFiMesh/examples/HelloTcpIp/HelloTcpIp.ino b/libraries/ESP8266WiFiMesh/examples/HelloTcpIp/HelloTcpIp.ino index d91946e0f7..3fad54aa11 100644 --- a/libraries/ESP8266WiFiMesh/examples/HelloTcpIp/HelloTcpIp.ino +++ b/libraries/ESP8266WiFiMesh/examples/HelloTcpIp/HelloTcpIp.ino @@ -1,4 +1,4 @@ -#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. +#define ESP8266WIFIMESH_DISABLE_COMPATIBILITY // Excludes redundant compatibility code. TODO: Should be used for new code until the compatibility code is removed with release 3.0.0 of the Arduino core. #include #include @@ -25,7 +25,7 @@ unsigned int requestNumber = 0; unsigned int responseNumber = 0; String manageRequest(const String &request, MeshBackendBase &meshInstance); -transmission_status_t manageResponse(const String &response, MeshBackendBase &meshInstance); +TransmissionStatusType manageResponse(const String &response, MeshBackendBase &meshInstance); void networkFilter(int numberOfNetworks, MeshBackendBase &meshInstance); /* Create the mesh node object */ @@ -68,8 +68,8 @@ String manageRequest(const String &request, MeshBackendBase &meshInstance) { @param meshInstance The MeshBackendBase instance that called the function. @return The status code resulting from the response, as an int */ -transmission_status_t manageResponse(const String &response, MeshBackendBase &meshInstance) { - transmission_status_t statusCode = TS_TRANSMISSION_COMPLETE; +TransmissionStatusType manageResponse(const String &response, MeshBackendBase &meshInstance) { + TransmissionStatusType statusCode = TransmissionStatusType::TRANSMISSION_COMPLETE; // To get the actual class of the polymorphic meshInstance, do as follows (meshBackendCast replaces dynamic_cast since RTTI is disabled) if (EspnowMeshBackend *espnowInstance = TypeCast::meshBackendCast(&meshInstance)) { @@ -142,7 +142,7 @@ bool exampleTransmissionOutcomesUpdateHook(MeshBackendBase &meshInstance) { // The default hook only returns true and does nothing else. if (TcpIpMeshBackend *tcpIpInstance = TypeCast::meshBackendCast(&meshInstance)) { - if (tcpIpInstance->latestTransmissionOutcomes().back().transmissionStatus() == TS_TRANSMISSION_COMPLETE) { + if (tcpIpInstance->latestTransmissionOutcomes().back().transmissionStatus() == TransmissionStatusType::TRANSMISSION_COMPLETE) { // Our last request got a response, so time to create a new request. meshInstance.setMessage(String(F("Hello world request #")) + String(++requestNumber) + F(" from ") + meshInstance.getMeshName() + meshInstance.getNodeID() + String('.')); @@ -209,11 +209,11 @@ void loop() { Serial.println(F("No mesh AP found.")); } else { for (TransmissionOutcome &transmissionOutcome : tcpIpNode.latestTransmissionOutcomes()) { - if (transmissionOutcome.transmissionStatus() == TS_TRANSMISSION_FAILED) { + if (transmissionOutcome.transmissionStatus() == TransmissionStatusType::TRANSMISSION_FAILED) { Serial.println(String(F("Transmission failed to mesh AP ")) + transmissionOutcome.SSID()); - } else if (transmissionOutcome.transmissionStatus() == TS_CONNECTION_FAILED) { + } else if (transmissionOutcome.transmissionStatus() == TransmissionStatusType::CONNECTION_FAILED) { Serial.println(String(F("Connection failed to mesh AP ")) + transmissionOutcome.SSID()); - } else if (transmissionOutcome.transmissionStatus() == TS_TRANSMISSION_COMPLETE) { + } else if (transmissionOutcome.transmissionStatus() == TransmissionStatusType::TRANSMISSION_COMPLETE) { // No need to do anything, transmission was successful. } else { Serial.println(String(F("Invalid transmission status for ")) + transmissionOutcome.SSID() + String('!')); diff --git a/libraries/ESP8266WiFiMesh/keywords.txt b/libraries/ESP8266WiFiMesh/keywords.txt index 3574a231d5..3e1088a6bc 100644 --- a/libraries/ESP8266WiFiMesh/keywords.txt +++ b/libraries/ESP8266WiFiMesh/keywords.txt @@ -12,7 +12,7 @@ ESP8266WiFiMesh KEYWORD3 # Datatypes (KEYWORD1) ####################################### -transmission_status_t KEYWORD1 +TransmissionStatusType KEYWORD1 ####################################### # Methods and Functions (KEYWORD2) diff --git a/libraries/ESP8266WiFiMesh/src/CryptoInterface.cpp b/libraries/ESP8266WiFiMesh/src/CryptoInterface.cpp index 449be1d331..8e0777babb 100644 --- a/libraries/ESP8266WiFiMesh/src/CryptoInterface.cpp +++ b/libraries/ESP8266WiFiMesh/src/CryptoInterface.cpp @@ -34,8 +34,6 @@ namespace { size_t _ctMinDataLength = 0; size_t _ctMaxDataLength = 1024; - - bool _warningsEnabled = true; br_hkdf_context _storedHkdfContext; bool _hkdfContextStored = false; @@ -186,52 +184,61 @@ namespace createBearsslHmacCT(hashType, message.c_str(), message.length(), hashKey, hashKeyLength, hmac, hmacLength); return TypeCast::uint8ArrayToHexString(hmac, hmacLength); } + + + // Helper function to avoid deprecated warnings. + void *md5HashHelper(const void *data, const size_t dataLength, void *resultArray) + { + br_md5_context context; + br_md5_init(&context); + br_md5_update(&context, data, dataLength); + br_md5_out(&context, resultArray); + return resultArray; + } + + // Helper function to avoid deprecated warnings. + void *sha1HashHelper(const void *data, const size_t dataLength, void *resultArray) + { + br_sha1_context context; + br_sha1_init(&context); + br_sha1_update(&context, data, dataLength); + br_sha1_out(&context, resultArray); + return resultArray; + } } namespace CryptoInterface { void setCtMinDataLength(const size_t ctMinDataLength) { - assert(ctMaxDataLength() - ctMinDataLength <= CT_MAX_DIFF); + assert(getCtMaxDataLength() - ctMinDataLength <= CT_MAX_DIFF); _ctMinDataLength = ctMinDataLength; } - size_t ctMinDataLength() {return _ctMinDataLength;} + size_t getCtMinDataLength() {return _ctMinDataLength;} void setCtMaxDataLength(const size_t ctMaxDataLength) { - assert(ctMaxDataLength - ctMinDataLength() <= CT_MAX_DIFF); + assert(ctMaxDataLength - getCtMinDataLength() <= CT_MAX_DIFF); _ctMaxDataLength = ctMaxDataLength; } - size_t ctMaxDataLength() {return _ctMaxDataLength;} - - void setWarningsEnabled(bool warningsEnabled) { _warningsEnabled = warningsEnabled; } - bool warningsEnabled() { return _warningsEnabled; } + size_t getCtMaxDataLength() {return _ctMaxDataLength;} void setNonceGenerator(nonceGeneratorType nonceGenerator) { _nonceGenerator = nonceGenerator; } nonceGeneratorType getNonceGenerator() { return _nonceGenerator; } // #################### MD5 #################### - + // resultArray must have size MD5_NATURAL_LENGTH or greater void *md5Hash(const void *data, const size_t dataLength, void *resultArray) { - if(warningsEnabled()) - Serial.println(F("\nWARNING! The MD5 hash is broken in terms of attacker resistance.\n" - "Only use it in those cases where attacker resistance is not important. Prefer SHA-256 or higher otherwise.\n" - "Use CryptoInterface::setWarningsEnabled(false) to turn off this warning.\n")); - - br_md5_context context; - br_md5_init(&context); - br_md5_update(&context, data, dataLength); - br_md5_out(&context, resultArray); - return resultArray; + return md5HashHelper(data, dataLength, resultArray); } String md5Hash(const String &message) { uint8_t hash[MD5_NATURAL_LENGTH]; - md5Hash(message.c_str(), message.length(), hash); + md5HashHelper(message.c_str(), message.length(), hash); return TypeCast::uint8ArrayToHexString(hash, MD5_NATURAL_LENGTH); } @@ -260,23 +267,14 @@ namespace CryptoInterface // resultArray must have size SHA1_NATURAL_LENGTH or greater void *sha1Hash(const void *data, const size_t dataLength, void *resultArray) - { - if(warningsEnabled()) - Serial.println(F("\nWARNING! The SHA-1 hash is broken in terms of attacker resistance.\n" - "Only use it in those cases where attacker resistance is not important. Prefer SHA-256 or higher otherwise.\n" - "Use CryptoInterface::setWarningsEnabled(false) to turn off this warning.\n")); - - br_sha1_context context; - br_sha1_init(&context); - br_sha1_update(&context, data, dataLength); - br_sha1_out(&context, resultArray); - return resultArray; + { + return sha1HashHelper(data, dataLength, resultArray); } String sha1Hash(const String &message) { uint8_t hash[SHA1_NATURAL_LENGTH]; - sha1Hash(message.c_str(), message.length(), hash); + sha1HashHelper(message.c_str(), message.length(), hash); return TypeCast::uint8ArrayToHexString(hash, SHA1_NATURAL_LENGTH); } diff --git a/libraries/ESP8266WiFiMesh/src/CryptoInterface.h b/libraries/ESP8266WiFiMesh/src/CryptoInterface.h index b29d93abdc..9efc0df0ca 100644 --- a/libraries/ESP8266WiFiMesh/src/CryptoInterface.h +++ b/libraries/ESP8266WiFiMesh/src/CryptoInterface.h @@ -41,8 +41,8 @@ namespace CryptoInterface * naturally yielding constant-time operations. HMAC is naturally as constant-time as the underlying hash function. The size of the MACed data, and the size of the key, * may leak, though; only the contents are protected." * - * For messages much smaller than ctMaxDataLength(), constant-time processing takes substantially longer time to complete than a normal HMAC, - * determined by the size of (ctMaxDataLength() - ctMinDataLength()). + * For messages much smaller than getCtMaxDataLength(), constant-time processing takes substantially longer time to complete than a normal HMAC, + * determined by the size of (getCtMaxDataLength() - getCtMinDataLength()). * Constant-time processing also sets limits on the data length. * * Making the fixed data length limits variable will generally defeat the purpose of using constant-time. @@ -78,34 +78,26 @@ namespace CryptoInterface * It should not be changed once a constant time function has been used at least once. * Otherwise the constant time will not be constant for the used functions. * - * The difference ctMaxDataLength() - ctMinDataLength() MUST be less than 2^30 (i.e. about one gigabyte). + * The difference getCtMaxDataLength() - getCtMinDataLength() MUST be less than 2^30 (i.e. about one gigabyte). */ void setCtMinDataLength(const size_t ctMinDataLength); /** * 0 by default. */ - size_t ctMinDataLength(); + size_t getCtMinDataLength(); /** * This function allows for fine-tuning of the specifications for the constant time calculations. * It should not be changed once a constant time function has been used at least once. * Otherwise the constant time will not be constant for the used functions. * - * The difference ctMaxDataLength() - ctMinDataLength() MUST be less than 2^30 (i.e. about one gigabyte). + * The difference getCtMaxDataLength() - getCtMinDataLength() MUST be less than 2^30 (i.e. about one gigabyte). */ void setCtMaxDataLength(const size_t ctMaxDataLength); /** * 1024 by default. */ - size_t ctMaxDataLength(); - - /** - * Turn on or off warning Serial prints from the CryptoInterface functions. - * - * @param warningsEnabled If true, warnings will be printed to Serial. - */ - void setWarningsEnabled(bool warningsEnabled); - bool warningsEnabled(); + size_t getCtMaxDataLength(); /** * Set the nonce generator used by the CryptoInterface functions. @@ -119,6 +111,9 @@ namespace CryptoInterface // #################### MD5 #################### /** + * WARNING! The MD5 hash is broken in terms of attacker resistance. + * Only use it in those cases where attacker resistance is not important. Prefer SHA-256 or higher otherwise. + * * Create a MD5 hash of the data. The result will be MD5_NATURAL_LENGTH bytes long and stored in resultArray. * Uses the BearSSL cryptographic library. * @@ -128,9 +123,12 @@ namespace CryptoInterface * * @return A pointer to resultArray. */ - void *md5Hash(const void *data, const size_t dataLength, void *resultArray); + void *md5Hash(const void *data, const size_t dataLength, void *resultArray) __attribute__((deprecated)); /** + * WARNING! The MD5 hash is broken in terms of attacker resistance. + * Only use it in those cases where attacker resistance is not important. Prefer SHA-256 or higher otherwise. + * * Create a MD5 hash of the data. The result will be MD5_NATURAL_LENGTH bytes long and returned as a String in HEX format. * Uses the BearSSL cryptographic library. * @@ -138,7 +136,7 @@ namespace CryptoInterface * * @return A String with the generated hash in HEX format. */ - String md5Hash(const String &message); + String md5Hash(const String &message) __attribute__((deprecated)); /** * Create a MD5 HMAC from the data, using the provided hashKey. The result will be up to outputLength bytes long and stored in resultArray. @@ -176,7 +174,7 @@ namespace CryptoInterface * Uses the BearSSL cryptographic library. * * @param data The data array from which to create the HMAC. - * @param dataLength The length of the data array in bytes. Valid values are in the range [ctMinDataLength(), ctMaxDataLength()]. + * @param dataLength The length of the data array in bytes. Valid values are in the range [getCtMinDataLength(), getCtMaxDataLength()]. * @param hashKey The hash key to use when creating the HMAC. * @param hashKeyLength The length of the hash key in bytes. * @param resultArray The array wherein to store the resulting HMAC. @@ -193,7 +191,7 @@ namespace CryptoInterface * Constant-time version. * Uses the BearSSL cryptographic library. * - * @param message The string from which to create the HMAC. Must have a length in the range [ctMinDataLength(), ctMaxDataLength()]. + * @param message The string from which to create the HMAC. Must have a length in the range [getCtMinDataLength(), getCtMaxDataLength()]. * @param hashKey The hash key to use when creating the HMAC. * @param hashKeyLength The length of the hash key in bytes. * @param hmacLength The desired length of the generated HMAC, in bytes. Valid values are 1 to MD5_NATURAL_LENGTH. @@ -206,6 +204,9 @@ namespace CryptoInterface // #################### SHA-1 #################### /** + * WARNING! The SHA-1 hash is broken in terms of attacker resistance. + * Only use it in those cases where attacker resistance is not important. Prefer SHA-256 or higher otherwise. + * * Create a SHA1 hash of the data. The result will be SHA1_NATURAL_LENGTH bytes long and stored in resultArray. * Uses the BearSSL cryptographic library. * @@ -215,9 +216,12 @@ namespace CryptoInterface * * @return A pointer to resultArray. */ - void *sha1Hash(const void *data, const size_t dataLength, void *resultArray); + void *sha1Hash(const void *data, const size_t dataLength, void *resultArray) __attribute__((deprecated)); /** + * WARNING! The SHA-1 hash is broken in terms of attacker resistance. + * Only use it in those cases where attacker resistance is not important. Prefer SHA-256 or higher otherwise. + * * Create a SHA1 hash of the data. The result will be SHA1_NATURAL_LENGTH bytes long and returned as a String in HEX format. * Uses the BearSSL cryptographic library. * @@ -225,7 +229,7 @@ namespace CryptoInterface * * @return A String with the generated hash in HEX format. */ - String sha1Hash(const String &message); + String sha1Hash(const String &message) __attribute__((deprecated)); /** * Create a SHA1 HMAC from the data, using the provided hashKey. The result will be up to outputLength bytes long and stored in resultArray. @@ -263,7 +267,7 @@ namespace CryptoInterface * Uses the BearSSL cryptographic library. * * @param data The data array from which to create the HMAC. - * @param dataLength The length of the data array in bytes. Valid values are in the range [ctMinDataLength(), ctMaxDataLength()]. + * @param dataLength The length of the data array in bytes. Valid values are in the range [getCtMinDataLength(), getCtMaxDataLength()]. * @param hashKey The hash key to use when creating the HMAC. * @param hashKeyLength The length of the hash key in bytes. * @param resultArray The array wherein to store the resulting HMAC. @@ -280,7 +284,7 @@ namespace CryptoInterface * Constant-time version. * Uses the BearSSL cryptographic library. * - * @param message The string from which to create the HMAC. Must have a length in the range [ctMinDataLength(), ctMaxDataLength()]. + * @param message The string from which to create the HMAC. Must have a length in the range [getCtMinDataLength(), getCtMaxDataLength()]. * @param hashKey The hash key to use when creating the HMAC. * @param hashKeyLength The length of the hash key in bytes. * @param hmacLength The desired length of the generated HMAC, in bytes. Valid values are 1 to SHA1_NATURAL_LENGTH. @@ -350,7 +354,7 @@ namespace CryptoInterface * Uses the BearSSL cryptographic library. * * @param data The data array from which to create the HMAC. - * @param dataLength The length of the data array in bytes. Valid values are in the range [ctMinDataLength(), ctMaxDataLength()]. + * @param dataLength The length of the data array in bytes. Valid values are in the range [getCtMinDataLength(), getCtMaxDataLength()]. * @param hashKey The hash key to use when creating the HMAC. * @param hashKeyLength The length of the hash key in bytes. * @param resultArray The array wherein to store the resulting HMAC. @@ -367,7 +371,7 @@ namespace CryptoInterface * Constant-time version. * Uses the BearSSL cryptographic library. * - * @param message The string from which to create the HMAC. Must have a length in the range [ctMinDataLength(), ctMaxDataLength()]. + * @param message The string from which to create the HMAC. Must have a length in the range [getCtMinDataLength(), getCtMaxDataLength()]. * @param hashKey The hash key to use when creating the HMAC. * @param hashKeyLength The length of the hash key in bytes. * @param hmacLength The desired length of the generated HMAC, in bytes. Valid values are 1 to SHA224_NATURAL_LENGTH. @@ -437,7 +441,7 @@ namespace CryptoInterface * Uses the BearSSL cryptographic library. * * @param data The data array from which to create the HMAC. - * @param dataLength The length of the data array in bytes. Valid values are in the range [ctMinDataLength(), ctMaxDataLength()]. + * @param dataLength The length of the data array in bytes. Valid values are in the range [getCtMinDataLength(), getCtMaxDataLength()]. * @param hashKey The hash key to use when creating the HMAC. * @param hashKeyLength The length of the hash key in bytes. * @param resultArray The array wherein to store the resulting HMAC. @@ -454,7 +458,7 @@ namespace CryptoInterface * Constant-time version. * Uses the BearSSL cryptographic library. * - * @param message The string from which to create the HMAC. Must have a length in the range [ctMinDataLength(), ctMaxDataLength()]. + * @param message The string from which to create the HMAC. Must have a length in the range [getCtMinDataLength(), getCtMaxDataLength()]. * @param hashKey The hash key to use when creating the HMAC. * @param hashKeyLength The length of the hash key in bytes. * @param hmacLength The desired length of the generated HMAC, in bytes. Valid values are 1 to SHA256_NATURAL_LENGTH. @@ -524,7 +528,7 @@ namespace CryptoInterface * Uses the BearSSL cryptographic library. * * @param data The data array from which to create the HMAC. - * @param dataLength The length of the data array in bytes. Valid values are in the range [ctMinDataLength(), ctMaxDataLength()]. + * @param dataLength The length of the data array in bytes. Valid values are in the range [getCtMinDataLength(), getCtMaxDataLength()]. * @param hashKey The hash key to use when creating the HMAC. * @param hashKeyLength The length of the hash key in bytes. * @param resultArray The array wherein to store the resulting HMAC. @@ -541,7 +545,7 @@ namespace CryptoInterface * Constant-time version. * Uses the BearSSL cryptographic library. * - * @param message The string from which to create the HMAC. Must have a length in the range [ctMinDataLength(), ctMaxDataLength()]. + * @param message The string from which to create the HMAC. Must have a length in the range [getCtMinDataLength(), getCtMaxDataLength()]. * @param hashKey The hash key to use when creating the HMAC. * @param hashKeyLength The length of the hash key in bytes. * @param hmacLength The desired length of the generated HMAC, in bytes. Valid values are 1 to SHA384_NATURAL_LENGTH. @@ -611,7 +615,7 @@ namespace CryptoInterface * Uses the BearSSL cryptographic library. * * @param data The data array from which to create the HMAC. - * @param dataLength The length of the data array in bytes. Valid values are in the range [ctMinDataLength(), ctMaxDataLength()]. + * @param dataLength The length of the data array in bytes. Valid values are in the range [getCtMinDataLength(), getCtMaxDataLength()]. * @param hashKey The hash key to use when creating the HMAC. * @param hashKeyLength The length of the hash key in bytes. * @param resultArray The array wherein to store the resulting HMAC. @@ -628,7 +632,7 @@ namespace CryptoInterface * Constant-time version. * Uses the BearSSL cryptographic library. * - * @param message The string from which to create the HMAC. Must have a length in the range [ctMinDataLength(), ctMaxDataLength()]. + * @param message The string from which to create the HMAC. Must have a length in the range [getCtMinDataLength(), getCtMaxDataLength()]. * @param hashKey The hash key to use when creating the HMAC. * @param hashKeyLength The length of the hash key in bytes. * @param hmacLength The desired length of the generated HMAC, in bytes. Valid values are 1 to SHA512_NATURAL_LENGTH. diff --git a/libraries/ESP8266WiFiMesh/src/ESP8266WiFiMesh.cpp b/libraries/ESP8266WiFiMesh/src/ESP8266WiFiMesh.cpp index 19e64d69f3..1678f9d001 100644 --- a/libraries/ESP8266WiFiMesh/src/ESP8266WiFiMesh.cpp +++ b/libraries/ESP8266WiFiMesh/src/ESP8266WiFiMesh.cpp @@ -54,7 +54,7 @@ namespace TypeCast = MeshTypeConversionFunctions; const IPAddress ESP8266WiFiMesh::emptyIP = IPAddress(); -String ESP8266WiFiMesh::lastSSID = ""; +String ESP8266WiFiMesh::lastSSID; bool ESP8266WiFiMesh::staticIPActivated = false; // 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. @@ -88,9 +88,9 @@ ESP8266WiFiMesh::ESP8266WiFiMesh(ESP8266WiFiMesh::requestHandlerType requestHand void ESP8266WiFiMesh::updateNetworkNames(const String &newMeshName, const String &newNodeID) { - if(newMeshName != "") + if(!newMeshName.isEmpty()) _meshName = newMeshName; - if(newNodeID != "") + if(!newNodeID.isEmpty()) _nodeID = newNodeID; String newSSID = _meshName + _nodeID; @@ -469,7 +469,7 @@ void ESP8266WiFiMesh::initiateConnectionToAP(const String &targetSSID, int targe */ transmission_status_t ESP8266WiFiMesh::connectToNode(const String &targetSSID, int targetChannel, uint8_t *targetBSSID) { - if(staticIPActivated && lastSSID != "" && lastSSID != targetSSID) // So we only do this once per connection, in case there is a performance impact. + if(staticIPActivated && !lastSSID.isEmpty() && lastSSID != targetSSID) // So we only do this once per connection, in case there is a performance impact. { #if LWIP_VERSION_MAJOR >= 2 // Can be used with Arduino core for ESP8266 version 2.4.2 or higher with lwIP2 enabled to keep static IP on even during network switches. @@ -567,12 +567,12 @@ void ESP8266WiFiMesh::attemptTransmission(const String &message, bool concluding WiFi.disconnect(); yield(); - String currentSSID = ""; + String currentSSID; int currentWiFiChannel = NETWORK_INFO_DEFAULT_INT; uint8_t *currentBSSID = NULL; // If an SSID has been assigned, it is prioritized over an assigned networkIndex since the networkIndex is more likely to change. - if(currentNetwork.SSID != "") + if(!currentNetwork.SSID.isEmpty()) { currentSSID = currentNetwork.SSID; currentWiFiChannel = currentNetwork.wifiChannel; diff --git a/libraries/ESP8266WiFiMesh/src/ESP8266WiFiMesh.h b/libraries/ESP8266WiFiMesh/src/ESP8266WiFiMesh.h index 139b2e980f..8647502bb3 100644 --- a/libraries/ESP8266WiFiMesh/src/ESP8266WiFiMesh.h +++ b/libraries/ESP8266WiFiMesh/src/ESP8266WiFiMesh.h @@ -47,8 +47,9 @@ #include #include #include -#include "NetworkInfo.h" #include "TransmissionResult.h" +#include "NetworkInfo.h" + const String WIFI_MESH_EMPTY_STRING = ""; @@ -175,7 +176,7 @@ class ESP8266WiFiMesh { */ ESP8266WiFiMesh(requestHandlerType requestHandler, responseHandlerType responseHandler, networkFilterType networkFilter, const String &meshPassword, const String &meshName = "MeshNode_", const String &nodeID = WIFI_MESH_EMPTY_STRING, bool verboseMode = false, - uint8 meshWiFiChannel = 1, uint16_t serverPort = 4011); + uint8 meshWiFiChannel = 1, uint16_t serverPort = 4011) __attribute__((deprecated)); /** * A vector that contains the NetworkInfo for each WiFi network to connect to. diff --git a/libraries/ESP8266WiFiMesh/src/EncryptedConnectionData.cpp b/libraries/ESP8266WiFiMesh/src/EncryptedConnectionData.cpp index 486bf7844b..9f5cc45d37 100644 --- a/libraries/ESP8266WiFiMesh/src/EncryptedConnectionData.cpp +++ b/libraries/ESP8266WiFiMesh/src/EncryptedConnectionData.cpp @@ -102,10 +102,8 @@ bool EncryptedConnectionData::connectedTo(const uint8_t *peerMac) const { return true; } - else - { - return false; - } + + return false; } void EncryptedConnectionData::setHashKey(const uint8_t hashKey[espnowHashKeyLength]) diff --git a/libraries/ESP8266WiFiMesh/src/EspnowMeshBackend.cpp b/libraries/ESP8266WiFiMesh/src/EspnowMeshBackend.cpp index ee10b34161..ceb458cd81 100644 --- a/libraries/ESP8266WiFiMesh/src/EspnowMeshBackend.cpp +++ b/libraries/ESP8266WiFiMesh/src/EspnowMeshBackend.cpp @@ -39,9 +39,9 @@ static const uint64_t uint64MSB = 0x8000000000000000; const uint8_t EspnowMeshBackend::broadcastMac[6] = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}; -bool EspnowMeshBackend::_espnowTransmissionMutex = false; -bool EspnowMeshBackend::_espnowConnectionQueueMutex = false; -bool EspnowMeshBackend::_responsesToSendMutex = false; +std::shared_ptr EspnowMeshBackend::_espnowTransmissionMutex = std::make_shared(false); +std::shared_ptr EspnowMeshBackend::_espnowConnectionQueueMutex = std::make_shared(false); +std::shared_ptr EspnowMeshBackend::_responsesToSendMutex = std::make_shared(false); EspnowMeshBackend *EspnowMeshBackend::_espnowRequestManager = nullptr; @@ -67,8 +67,8 @@ bool EspnowMeshBackend::_espnowSendConfirmed = false; String EspnowMeshBackend::_ongoingPeerRequestNonce; uint8_t EspnowMeshBackend::_ongoingPeerRequestMac[6] = {0}; EspnowMeshBackend *EspnowMeshBackend::_ongoingPeerRequester = nullptr; -encrypted_connection_status_t EspnowMeshBackend::_ongoingPeerRequestResult = ECS_MAX_CONNECTIONS_REACHED_SELF; -uint32_t EspnowMeshBackend::_ongoingPeerRequestEncryptionStart = 0; +EncryptedConnectionStatus EspnowMeshBackend::_ongoingPeerRequestResult = EncryptedConnectionStatus::MAX_CONNECTIONS_REACHED_SELF; +ExpiringTimeTracker _ongoingPeerRequestEncryptionTimeout([](){ return EspnowMeshBackend::getEncryptionRequestTimeout(); }); bool EspnowMeshBackend::_reciprocalPeerRequestConfirmation = false; uint8_t EspnowMeshBackend::_espnowEncryptionKok[espnowEncryptedConnectionKeyLength] = { 0 }; @@ -84,13 +84,13 @@ uint32_t EspnowMeshBackend::_unsynchronizedMessageID = 0; // so storage duration should not be too long. uint32_t EspnowMeshBackend::_logEntryLifetimeMs = 2500; uint32_t EspnowMeshBackend::_broadcastResponseTimeoutMs = 1000; // This is shorter than _logEntryLifetimeMs to preserve RAM since broadcasts are not deleted from sentRequests until they expire. -uint32_t EspnowMeshBackend::_timeOfLastLogClear = 0; +ExpiringTimeTracker _logClearingCooldown(500); uint32_t EspnowMeshBackend::_criticalHeapLevel = 6000; // In bytes uint32_t EspnowMeshBackend::_criticalHeapLevelBuffer = 6000; // In bytes uint8_t EspnowMeshBackend::_maxTransmissionsPerMessage = 3; -bool EspnowMeshBackend::_espnowSendToNodeMutex = false; +std::shared_ptr EspnowMeshBackend::_espnowSendToNodeMutex = std::make_shared(false); uint8_t EspnowMeshBackend::_transmissionTargetBSSID[6] = {0}; double EspnowMeshBackend::_transmissionsTotal = 0; @@ -100,19 +100,22 @@ bool EspnowMeshBackend::_staticVerboseMode = false; void espnowDelay(uint32_t durationMs) { - uint32_t startingTime = millis(); - - while(millis() - startingTime < durationMs) + ExpiringTimeTracker timeout(durationMs); + + do { + // We want to delay before performEspnowMaintenance() so background tasks can be managed first. + // Initial while combined with YieldAndDelayMs polledTimeout::YieldPolicy is not suitable since the delay then occurs before evaluating the condition (meaning durationMs = 1 never executes the loop interior). delay(1); EspnowMeshBackend::performEspnowMaintenance(); } + while(!timeout); } EspnowMeshBackend::EspnowMeshBackend(requestHandlerType requestHandler, responseHandlerType responseHandler, networkFilterType networkFilter, broadcastFilterType broadcastFilter, const String &meshPassword, const String &ssidPrefix, const String &ssidSuffix, bool verboseMode, uint8 meshWiFiChannel) - : MeshBackendBase(requestHandler, responseHandler, networkFilter, MB_ESP_NOW) + : MeshBackendBase(requestHandler, responseHandler, networkFilter, MeshBackendType::ESP_NOW) { // Reserve the maximum possible usage early on to prevent heap fragmentation later. encryptedConnections.reserve(maxEncryptedConnections); @@ -259,7 +262,7 @@ void EspnowMeshBackend::performEspnowMaintenance(uint32_t estimatedMaxDuration) return; } - if(millis() - _timeOfLastLogClear >= 500) // Clearing too frequently will cause a lot of unnecessary container iterations. + if(_logClearingCooldown) // Clearing too frequently will cause a lot of unnecessary container iterations. { clearOldLogEntries(); } @@ -313,6 +316,21 @@ void EspnowMeshBackend::deleteExpiredLogEntries(std::map, { for(typename std::map, T>::iterator entryIterator = logEntries.begin(); entryIterator != logEntries.end(); ) + { + if(entryIterator->second.getTimeTracker().timeSinceCreation() > maxEntryLifetimeMs) + { + entryIterator = logEntries.erase(entryIterator); + } + else + ++entryIterator; + } +} + +template +void EspnowMeshBackend::deleteExpiredLogEntries(std::map, TimeTracker> &logEntries, uint32_t maxEntryLifetimeMs) +{ + for(typename std::map, TimeTracker>::iterator entryIterator = logEntries.begin(); + entryIterator != logEntries.end(); ) { if(entryIterator->second.timeSinceCreation() > maxEntryLifetimeMs) { @@ -329,7 +347,7 @@ void EspnowMeshBackend::deleteExpiredLogEntries(std::mapfirst.first == uint64BroadcastMac; - uint32_t timeSinceCreation = entryIterator->second.timeSinceCreation(); + uint32_t timeSinceCreation = entryIterator->second.getTimeTracker().timeSinceCreation(); if((!broadcast && timeSinceCreation > requestLifetimeMs) || (broadcast && timeSinceCreation > broadcastLifetimeMs)) @@ -347,7 +365,7 @@ void EspnowMeshBackend::deleteExpiredLogEntries(std::list &logEntries, uint32 for(typename std::list::iterator entryIterator = logEntries.begin(); entryIterator != logEntries.end(); ) { - if(entryIterator->timeSinceCreation() > maxEntryLifetimeMs) + if(entryIterator->getTimeTracker().timeSinceCreation() > maxEntryLifetimeMs) { entryIterator = logEntries.erase(entryIterator); } @@ -363,7 +381,7 @@ void EspnowMeshBackend::deleteExpiredLogEntries(std::listtemporary(); - if(timeTrackerPointer && timeTrackerPointer->timeSinceCreation() > maxEntryLifetimeMs) + if(timeTrackerPointer && timeTrackerPointer->elapsedTime() > maxEntryLifetimeMs) { entryIterator = logEntries.erase(entryIterator); } @@ -379,7 +397,7 @@ void EspnowMeshBackend::deleteExpiredLogEntries(std::list &logEn entryIterator != logEntries.end(); ) { auto timeTrackerPointer = entryIterator->temporary(); - if(timeTrackerPointer && timeTrackerPointer->timeSinceCreation() > maxEntryLifetimeMs) + if(timeTrackerPointer && timeTrackerPointer->elapsedTime() > maxEntryLifetimeMs) { entryIterator = logEntries.erase(entryIterator); } @@ -394,7 +412,7 @@ void EspnowMeshBackend::clearOldLogEntries() // uint32_t startTime = millis(); - _timeOfLastLogClear = millis(); + _logClearingCooldown.reset(); deleteExpiredLogEntries(receivedEspnowTransmissions, logEntryLifetimeMs()); deleteExpiredLogEntries(receivedRequests, logEntryLifetimeMs()); // Just needs to be long enough to not accept repeated transmissions by mistake. @@ -668,7 +686,7 @@ void EspnowMeshBackend::handlePeerRequestConfirmation(uint8_t *macaddr, uint8_t if(!encryptedConnectionEstablished(_ongoingPeerRequestResult) && JsonTranslator::verifyEncryptionRequestHmac(message, macaddr, apMacArray, _ongoingPeerRequester->getEspnowHashKey(), espnowHashKeyLength)) { - _ongoingPeerRequestEncryptionStart = millis(); + _ongoingPeerRequestEncryptionTimeout.reset(); connectionLogIterator existingEncryptedConnection = connectionLogEndIterator(); @@ -680,7 +698,7 @@ void EspnowMeshBackend::handlePeerRequestConfirmation(uint8_t *macaddr, uint8_t else { // Encrypted connection already exists - _ongoingPeerRequestResult = ECS_CONNECTION_ESTABLISHED; + _ongoingPeerRequestResult = EncryptedConnectionStatus::CONNECTION_ESTABLISHED; if(auto timeTrackerPointer = existingEncryptedConnection->temporary()) { @@ -715,15 +733,15 @@ void EspnowMeshBackend::handlePeerRequestConfirmation(uint8_t *macaddr, uint8_t encryptedConnection->setOwnSessionKey(ownSessionKey); if(messageHeader == FPSTR(encryptedConnectionInfoHeader)) - _ongoingPeerRequestResult = ECS_CONNECTION_ESTABLISHED; + _ongoingPeerRequestResult = EncryptedConnectionStatus::CONNECTION_ESTABLISHED; else if(messageHeader == FPSTR(softLimitEncryptedConnectionInfoHeader)) - _ongoingPeerRequestResult = ECS_SOFT_LIMIT_CONNECTION_ESTABLISHED; + _ongoingPeerRequestResult = EncryptedConnectionStatus::SOFT_LIMIT_CONNECTION_ESTABLISHED; else assert(false && String(F("Unknown _ongoingPeerRequestResult!"))); } else { - _ongoingPeerRequestResult = ECS_REQUEST_TRANSMISSION_FAILED; + _ongoingPeerRequestResult = EncryptedConnectionStatus::REQUEST_TRANSMISSION_FAILED; } _ongoingPeerRequestNonce.clear(); @@ -733,7 +751,7 @@ void EspnowMeshBackend::handlePeerRequestConfirmation(uint8_t *macaddr, uint8_t { if(JsonTranslator::verifyEncryptionRequestHmac(message, macaddr, apMacArray, _ongoingPeerRequester->getEspnowHashKey(), espnowHashKeyLength)) { - _ongoingPeerRequestResult = ECS_MAX_CONNECTIONS_REACHED_PEER; + _ongoingPeerRequestResult = EncryptedConnectionStatus::MAX_CONNECTIONS_REACHED_PEER; _ongoingPeerRequestNonce.clear(); } } @@ -815,27 +833,25 @@ void EspnowMeshBackend::espnowReceiveCallback(uint8_t *macaddr, uint8_t *dataArr else { std::map, MessageData>::iterator storedMessageIterator = receivedEspnowTransmissions.find(std::make_pair(macAndType, messageID)); + + if(storedMessageIterator == receivedEspnowTransmissions.end()) // If we have not stored the key already, we missed the first message part. + { + return; + } - if(storedMessageIterator != receivedEspnowTransmissions.end()) // If we have not stored the key already, we missed the first message part. + if(!storedMessageIterator->second.addToMessage(dataArray, len)) { - if(!storedMessageIterator->second.addToMessage(dataArray, len)) + // If we received the wrong message part, remove the whole message if we have missed a part. + // Otherwise just ignore the received part since it has already been stored. + + uint8_t transmissionsRemainingExpected = storedMessageIterator->second.getTransmissionsRemaining() - 1; + + if(transmissionsRemaining < transmissionsRemainingExpected) { - // If we received the wrong message part, remove the whole message if we have missed a part. - // Otherwise just ignore the received part since it has already been stored. - - uint8_t transmissionsRemainingExpected = storedMessageIterator->second.getTransmissionsRemaining() - 1; - - if(transmissionsRemaining < transmissionsRemainingExpected) - { - receivedEspnowTransmissions.erase(storedMessageIterator); - return; - } + receivedEspnowTransmissions.erase(storedMessageIterator); + return; } } - else - { - return; - } } //Serial.println("methodStart storage done " + String(millis() - methodStart)); @@ -913,9 +929,9 @@ bool EspnowMeshBackend::isEspnowRequestManager() return (this == getEspnowRequestManager()); } -bool EspnowMeshBackend::encryptedConnectionEstablished(encrypted_connection_status_t connectionStatus) +bool EspnowMeshBackend::encryptedConnectionEstablished(EncryptedConnectionStatus connectionStatus) { - return connectionStatus > 0; + return static_cast(connectionStatus) > 0; } void EspnowMeshBackend::setLogEntryLifetimeMs(uint32_t logEntryLifetimeMs) @@ -1057,7 +1073,7 @@ bool EspnowMeshBackend::usesConstantSessionKey(char messageType) return messageType == 'A' || messageType == 'C'; } -transmission_status_t EspnowMeshBackend::espnowSendToNode(const String &message, const uint8_t *targetBSSID, char messageType, EspnowMeshBackend *espnowInstance) +TransmissionStatusType EspnowMeshBackend::espnowSendToNode(const String &message, const uint8_t *targetBSSID, char messageType, EspnowMeshBackend *espnowInstance) { using EspnowProtocolInterpreter::synchronizationRequestHeader; @@ -1076,19 +1092,17 @@ transmission_status_t EspnowMeshBackend::espnowSendToNode(const String &message, if(encryptedConnection->desync()) { - return TS_TRANSMISSION_FAILED; + return TransmissionStatusType::TRANSMISSION_FAILED; } } return espnowSendToNodeUnsynchronized(message, encryptedMac, messageType, generateMessageID(encryptedConnection), espnowInstance); } - else - { - return espnowSendToNodeUnsynchronized(message, targetBSSID, messageType, generateMessageID(encryptedConnection), espnowInstance); - } + + return espnowSendToNodeUnsynchronized(message, targetBSSID, messageType, generateMessageID(encryptedConnection), espnowInstance); } -transmission_status_t EspnowMeshBackend::espnowSendToNodeUnsynchronized(const String message, const uint8_t *targetBSSID, char messageType, uint64_t messageID, EspnowMeshBackend *espnowInstance) +TransmissionStatusType EspnowMeshBackend::espnowSendToNodeUnsynchronized(const String message, const uint8_t *targetBSSID, char messageType, uint64_t messageID, EspnowMeshBackend *espnowInstance) { using namespace EspnowProtocolInterpreter; @@ -1096,7 +1110,7 @@ transmission_status_t EspnowMeshBackend::espnowSendToNodeUnsynchronized(const St if(!mutexTracker.mutexCaptured()) { 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 TransmissionStatusType::TRANSMISSION_FAILED; } // We copy the message String and bssid array from the arguments in this method to make sure they are @@ -1197,19 +1211,17 @@ transmission_status_t EspnowMeshBackend::espnowSendToNodeUnsynchronized(const St if(messageType == 'B') retransmissions = espnowInstance->getBroadcastTransmissionRedundancy(); - for(uint32_t i = 0; i <= retransmissions; i++) + for(uint32_t i = 0; i <= retransmissions; ++i) { _espnowSendConfirmed = false; - uint32_t transmissionStartTime = millis(); + ExpiringTimeTracker transmissionTimeout([](){ return getEspnowTransmissionTimeout(); }); - while(!_espnowSendConfirmed && millis() - transmissionStartTime < getEspnowTransmissionTimeout()) + while(!_espnowSendConfirmed && !transmissionTimeout) { if(esp_now_send(_transmissionTargetBSSID, transmission, transmissionSize) == 0) // == 0 => Success { - uint32_t transmissionAttemptStart = millis(); - while(!_espnowSendConfirmed - && (millis() - transmissionAttemptStart < getEspnowRetransmissionInterval()) - && (millis() - transmissionStartTime < getEspnowTransmissionTimeout())) + ExpiringTimeTracker retransmissionTime([](){ return getEspnowRetransmissionInterval(); }); + while(!_espnowSendConfirmed && !retransmissionTime && !transmissionTimeout) { delay(1); // Note that callbacks can be called during delay time, so it is possible to receive a transmission during this delay. } @@ -1235,7 +1247,7 @@ transmission_status_t EspnowMeshBackend::espnowSendToNodeUnsynchronized(const St if(!_espnowSendConfirmed) { - _transmissionsFailed++; + ++_transmissionsFailed; staticVerboseModePrint(String(F("espnowSendToNode failed!"))); staticVerboseModePrint(String(F("Transmission #: ")) + String(transmissionsRequired - transmissionsRemaining) + String('/') + String(transmissionsRequired)); @@ -1244,27 +1256,27 @@ transmission_status_t EspnowMeshBackend::espnowSendToNodeUnsynchronized(const St if(messageStart && encryptedConnection && !usesConstantSessionKey(messageType) && encryptedConnection->getOwnSessionKey() == messageID) encryptedConnection->setDesync(true); - return TS_TRANSMISSION_FAILED; + return TransmissionStatusType::TRANSMISSION_FAILED; } - transmissionsRemaining--; // This is used when transfering multi-transmission messages. + --transmissionsRemaining; // This is used when transfering multi-transmission messages. } while(transmissionsRemaining >= 0); // Useful when debugging the protocol //staticVerboseModePrint("Sent to Mac: " + TypeCast::macToString(_transmissionTargetBSSID) + " ID: " + TypeCast::uint64ToString(messageID)); - return TS_TRANSMISSION_COMPLETE; + return TransmissionStatusType::TRANSMISSION_COMPLETE; } -transmission_status_t EspnowMeshBackend::sendRequest(const String &message, const uint8_t *targetBSSID) +TransmissionStatusType EspnowMeshBackend::sendRequest(const String &message, const uint8_t *targetBSSID) { - transmission_status_t transmissionStatus = espnowSendToNode(message, targetBSSID, 'Q', this); + TransmissionStatusType transmissionStatus = espnowSendToNode(message, targetBSSID, 'Q', this); return transmissionStatus; } -transmission_status_t EspnowMeshBackend::sendResponse(const String &message, uint64_t requestID, const uint8_t *targetBSSID) +TransmissionStatusType EspnowMeshBackend::sendResponse(const String &message, uint64_t requestID, const uint8_t *targetBSSID) { EncryptedConnectionLog *encryptedConnection = getEncryptedConnection(targetBSSID); uint8_t encryptedMac[6] {0}; @@ -1278,7 +1290,7 @@ transmission_status_t EspnowMeshBackend::sendResponse(const String &message, uin return espnowSendToNodeUnsynchronized(message, encryptedConnection ? encryptedMac : targetBSSID, 'A', requestID, this); } -bool EspnowMeshBackend::transmissionInProgress(){return _espnowTransmissionMutex;} +bool EspnowMeshBackend::transmissionInProgress(){return *_espnowTransmissionMutex;} EspnowMeshBackend::macAndType_td EspnowMeshBackend::createMacAndTypeValue(uint64_t uint64Mac, char messageType) { @@ -1294,7 +1306,7 @@ void EspnowMeshBackend::setEspnowEncryptedConnectionKey(const uint8_t espnowEncr { assert(espnowEncryptedConnectionKey != nullptr); - for(int i = 0; i < espnowEncryptedConnectionKeyLength; i++) + for(int i = 0; i < espnowEncryptedConnectionKeyLength; ++i) { _espnowEncryptedConnectionKey[i] = espnowEncryptedConnectionKey[i]; } @@ -1321,7 +1333,7 @@ bool EspnowMeshBackend::setEspnowEncryptionKok(uint8_t espnowEncryptionKok[espno if(espnowEncryptionKok == nullptr || esp_now_set_kok(espnowEncryptionKok, espnowEncryptedConnectionKeyLength)) // esp_now_set_kok failed if not == 0 return false; - for(int i = 0; i < espnowEncryptedConnectionKeyLength; i++) + for(int i = 0; i < espnowEncryptedConnectionKeyLength; ++i) { _espnowEncryptionKok[i] = espnowEncryptionKok[i]; } @@ -1351,7 +1363,7 @@ void EspnowMeshBackend::setEspnowHashKey(const uint8_t espnowHashKey[espnowHashK { assert(espnowHashKey != nullptr); - for(int i = 0; i < espnowHashKeyLength; i++) + for(int i = 0; i < espnowHashKeyLength; ++i) { _espnowHashKey[i] = espnowHashKey[i]; } @@ -1371,7 +1383,7 @@ void EspnowMeshBackend::setEspnowMessageEncryptionKey(uint8_t espnowMessageEncry { assert(espnowMessageEncryptionKey != nullptr); - for(int i = 0; i < CryptoInterface::ENCRYPTION_KEY_LENGTH; i++) + for(int i = 0; i < CryptoInterface::ENCRYPTION_KEY_LENGTH; ++i) { _espnowMessageEncryptionKey[i] = espnowMessageEncryptionKey[i]; } @@ -1461,7 +1473,7 @@ std::list::const_iterator EspnowMeshBackend::getScheduledResponse( while(stepsToTarget > 0) { startFromBeginning ? ++responseIterator : --responseIterator; - stepsToTarget--; + --stepsToTarget; } return responseIterator; @@ -1542,7 +1554,7 @@ bool EspnowMeshBackend::addUnencryptedConnection(const String &serializedConnect return JsonTranslator::getUnsynchronizedMessageID(serializedConnectionState, _unsynchronizedMessageID); } -encrypted_connection_status_t EspnowMeshBackend::addEncryptedConnection(uint8_t *peerStaMac, uint8_t *peerApMac, uint64_t peerSessionKey, uint64_t ownSessionKey) +EncryptedConnectionStatus EspnowMeshBackend::addEncryptedConnection(uint8_t *peerStaMac, uint8_t *peerApMac, uint64_t peerSessionKey, uint64_t ownSessionKey) { assert(encryptedConnections.size() <= maxEncryptedConnections); // If this is not the case, ESP-NOW is no longer in sync with the library @@ -1557,28 +1569,28 @@ encrypted_connection_status_t EspnowMeshBackend::addEncryptedConnection(uint8_t esp_now_set_peer_key(peerStaMac, getEspnowEncryptedConnectionKey(encryptionKeyArray), espnowEncryptedConnectionKeyLength); encryptedConnection->setHashKey(getEspnowHashKey()); - return ECS_CONNECTION_ESTABLISHED; + return EncryptedConnectionStatus::CONNECTION_ESTABLISHED; } if(encryptedConnections.size() == maxEncryptedConnections) { // No capacity for more encrypted connections. - return ECS_MAX_CONNECTIONS_REACHED_SELF; + return EncryptedConnectionStatus::MAX_CONNECTIONS_REACHED_SELF; } // returns 0 on success: int esp_now_add_peer(u8 *mac_addr, u8 role, u8 channel, u8 *key, u8 key_len) // Only MAC, encryption key and key length (16) actually matter. The rest is not used by ESP-NOW. else if(0 == esp_now_add_peer(peerStaMac, ESP_NOW_ROLE_CONTROLLER, getWiFiChannel(), getEspnowEncryptedConnectionKey(encryptionKeyArray), espnowEncryptedConnectionKeyLength)) { encryptedConnections.emplace_back(peerStaMac, peerApMac, peerSessionKey, ownSessionKey, getEspnowHashKey()); - return ECS_CONNECTION_ESTABLISHED; + return EncryptedConnectionStatus::CONNECTION_ESTABLISHED; } else { - return ECS_API_CALL_FAILED; + return EncryptedConnectionStatus::API_CALL_FAILED; } } -encrypted_connection_status_t EspnowMeshBackend::addEncryptedConnection(const String &serializedConnectionState, bool ignoreDuration) +EncryptedConnectionStatus EspnowMeshBackend::addEncryptedConnection(const String &serializedConnectionState, bool ignoreDuration) { uint32_t duration = 0; bool desync = false; @@ -1591,7 +1603,7 @@ encrypted_connection_status_t EspnowMeshBackend::addEncryptedConnection(const St && JsonTranslator::getOwnSessionKey(serializedConnectionState, ownSessionKey) && JsonTranslator::getPeerSessionKey(serializedConnectionState, peerSessionKey) && JsonTranslator::getPeerStaMac(serializedConnectionState, peerStaMac) && JsonTranslator::getPeerApMac(serializedConnectionState, peerApMac)) { - encrypted_connection_status_t result = ECS_API_CALL_FAILED; + EncryptedConnectionStatus result = EncryptedConnectionStatus::API_CALL_FAILED; if(!ignoreDuration && JsonTranslator::getDuration(serializedConnectionState, duration)) { @@ -1602,7 +1614,7 @@ encrypted_connection_status_t EspnowMeshBackend::addEncryptedConnection(const St result = addEncryptedConnection(peerStaMac, peerApMac, peerSessionKey, ownSessionKey); } - if(result == ECS_CONNECTION_ESTABLISHED) + if(result == EncryptedConnectionStatus::CONNECTION_ESTABLISHED) { EncryptedConnectionLog *encryptedConnection = getEncryptedConnection(peerStaMac); encryptedConnection->setDesync(desync); @@ -1610,13 +1622,11 @@ encrypted_connection_status_t EspnowMeshBackend::addEncryptedConnection(const St return result; } - else - { - return ECS_REQUEST_TRANSMISSION_FAILED; - } + + return EncryptedConnectionStatus::REQUEST_TRANSMISSION_FAILED; } -encrypted_connection_status_t EspnowMeshBackend::addTemporaryEncryptedConnection(uint8_t *peerStaMac, uint8_t *peerApMac, uint64_t peerSessionKey, uint64_t ownSessionKey, uint32_t duration) +EncryptedConnectionStatus EspnowMeshBackend::addTemporaryEncryptedConnection(uint8_t *peerStaMac, uint8_t *peerApMac, uint64_t peerSessionKey, uint64_t ownSessionKey, uint32_t duration) { assert(encryptedConnections.size() <= maxEncryptedConnections); // If this is not the case, ESP-NOW is no longer in sync with the library @@ -1637,12 +1647,12 @@ encrypted_connection_status_t EspnowMeshBackend::addTemporaryEncryptedConnection encryptedConnection->setRemainingDuration(duration); } - return ECS_CONNECTION_ESTABLISHED; + return EncryptedConnectionStatus::CONNECTION_ESTABLISHED; } - encrypted_connection_status_t result = addEncryptedConnection(peerStaMac, peerApMac, peerSessionKey, ownSessionKey); + EncryptedConnectionStatus result = addEncryptedConnection(peerStaMac, peerApMac, peerSessionKey, ownSessionKey); - if(result == ECS_CONNECTION_ESTABLISHED) + if(result == EncryptedConnectionStatus::CONNECTION_ESTABLISHED) { if(!getEncryptedConnectionIterator(peerStaMac, encryptedConnection)) assert(false && String(F("No connection found despite being added in addTemporaryEncryptedConnection."))); @@ -1653,7 +1663,7 @@ encrypted_connection_status_t EspnowMeshBackend::addTemporaryEncryptedConnection return result; } -encrypted_connection_status_t EspnowMeshBackend::addTemporaryEncryptedConnection(const String &serializedConnectionState, uint32_t duration) +EncryptedConnectionStatus EspnowMeshBackend::addTemporaryEncryptedConnection(const String &serializedConnectionState, uint32_t duration) { bool desync = false; uint64_t ownSessionKey = 0; @@ -1665,9 +1675,9 @@ encrypted_connection_status_t EspnowMeshBackend::addTemporaryEncryptedConnection && JsonTranslator::getOwnSessionKey(serializedConnectionState, ownSessionKey) && JsonTranslator::getPeerSessionKey(serializedConnectionState, peerSessionKey) && JsonTranslator::getPeerStaMac(serializedConnectionState, peerStaMac) && JsonTranslator::getPeerApMac(serializedConnectionState, peerApMac)) { - encrypted_connection_status_t result = addTemporaryEncryptedConnection(peerStaMac, peerApMac, peerSessionKey, ownSessionKey, duration); + EncryptedConnectionStatus result = addTemporaryEncryptedConnection(peerStaMac, peerApMac, peerSessionKey, ownSessionKey, duration); - if(result == ECS_CONNECTION_ESTABLISHED) + if(result == EncryptedConnectionStatus::CONNECTION_ESTABLISHED) { EncryptedConnectionLog *encryptedConnection = getEncryptedConnection(peerStaMac); encryptedConnection->setDesync(desync); @@ -1675,10 +1685,8 @@ encrypted_connection_status_t EspnowMeshBackend::addTemporaryEncryptedConnection return result; } - else - { - return ECS_REQUEST_TRANSMISSION_FAILED; - } + + return EncryptedConnectionStatus::REQUEST_TRANSMISSION_FAILED; } void EspnowMeshBackend::handlePostponedRemovals() @@ -1696,7 +1704,7 @@ void EspnowMeshBackend::handlePostponedRemovals() } } -encrypted_connection_status_t EspnowMeshBackend::requestEncryptedConnectionKernel(uint8_t *peerMac, const encryptionRequestBuilderType &encryptionRequestBuilder) +EncryptedConnectionStatus EspnowMeshBackend::requestEncryptedConnectionKernel(uint8_t *peerMac, const encryptionRequestBuilderType &encryptionRequestBuilder) { using namespace EspnowProtocolInterpreter; @@ -1706,7 +1714,7 @@ encrypted_connection_status_t EspnowMeshBackend::requestEncryptedConnectionKerne if(!mutexTracker.mutexCaptured()) { 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 EncryptedConnectionStatus::REQUEST_TRANSMISSION_FAILED; } EncryptedConnectionLog *existingEncryptedConnection = getEncryptedConnection(peerMac); @@ -1718,12 +1726,12 @@ encrypted_connection_status_t EspnowMeshBackend::requestEncryptedConnectionKerne assert(encryptedConnections.size() == maxEncryptedConnections); // No capacity for more encrypted connections. - return ECS_MAX_CONNECTIONS_REACHED_SELF; + return EncryptedConnectionStatus::MAX_CONNECTIONS_REACHED_SELF; } String requestNonce = TypeCast::macToString(peerMac) + TypeCast::uint64ToString(MeshUtilityFunctions::randomUint64()) + TypeCast::uint64ToString(MeshUtilityFunctions::randomUint64()); - _ongoingPeerRequestResult = ECS_REQUEST_TRANSMISSION_FAILED; + _ongoingPeerRequestResult = EncryptedConnectionStatus::REQUEST_TRANSMISSION_FAILED; _ongoingPeerRequestNonce = requestNonce; _ongoingPeerRequester = this; _reciprocalPeerRequestConfirmation = false; @@ -1732,12 +1740,11 @@ encrypted_connection_status_t EspnowMeshBackend::requestEncryptedConnectionKerne verboseModePrint(String(F("Sending encrypted connection request to: ")) + TypeCast::macToString(peerMac)); - if(espnowSendToNode(requestMessage, peerMac, 'P') == TS_TRANSMISSION_COMPLETE) + if(espnowSendToNode(requestMessage, peerMac, 'P') == TransmissionStatusType::TRANSMISSION_COMPLETE) { - uint32_t startTime = millis(); - + ExpiringTimeTracker requestTimeout([](){ return getEncryptionRequestTimeout(); }); // _ongoingPeerRequestNonce is set to "" when a peer confirmation response from the mac is received - while(millis() - startTime < getEncryptionRequestTimeout() && !_ongoingPeerRequestNonce.isEmpty()) + while(!requestTimeout && !_ongoingPeerRequestNonce.isEmpty()) { // For obvious reasons dividing by exactly 10 is a good choice. ExpiringTimeTracker maxDurationTracker = ExpiringTimeTracker(getEncryptionRequestTimeout()/10); @@ -1749,15 +1756,15 @@ encrypted_connection_status_t EspnowMeshBackend::requestEncryptedConnectionKerne if(!_ongoingPeerRequestNonce.isEmpty()) { // If nonce != "" we only received the basic connection info, so the pairing process is incomplete - _ongoingPeerRequestResult = ECS_REQUEST_TRANSMISSION_FAILED; + _ongoingPeerRequestResult = EncryptedConnectionStatus::REQUEST_TRANSMISSION_FAILED; _ongoingPeerRequestNonce.clear(); } else if(encryptedConnectionEstablished(_ongoingPeerRequestResult)) { - if(_ongoingPeerRequestResult == ECS_CONNECTION_ESTABLISHED) + if(_ongoingPeerRequestResult == EncryptedConnectionStatus::CONNECTION_ESTABLISHED) // Give the builder a chance to update the message requestMessage = encryptionRequestBuilder(requestNonce, existingTimeTracker); - else if(_ongoingPeerRequestResult == ECS_SOFT_LIMIT_CONNECTION_ESTABLISHED) + else if(_ongoingPeerRequestResult == EncryptedConnectionStatus::SOFT_LIMIT_CONNECTION_ESTABLISHED) // We will only get a soft limit connection. Adjust future actions based on this. requestMessage = JsonTranslator::createEncryptionRequestHmacMessage(FPSTR(temporaryEncryptionRequestHeader), requestNonce, getEspnowHashKey(), espnowHashKeyLength, getAutoEncryptionDuration()); @@ -1769,20 +1776,20 @@ encrypted_connection_status_t EspnowMeshBackend::requestEncryptedConnectionKerne String messageBody = requestMessage.substring(messageHeaderEndIndex + 1); // If we do not get an ack within getEncryptionRequestTimeout() the peer has probably had the time to delete the temporary encrypted connection. - if(espnowSendToNode(String(FPSTR(encryptedConnectionVerificationHeader)) + requestMessage, peerMac, 'P') == TS_TRANSMISSION_COMPLETE - && millis() - _ongoingPeerRequestEncryptionStart < getEncryptionRequestTimeout()) + if(espnowSendToNode(String(FPSTR(encryptedConnectionVerificationHeader)) + requestMessage, peerMac, 'P') == TransmissionStatusType::TRANSMISSION_COMPLETE + && !_ongoingPeerRequestEncryptionTimeout) { EncryptedConnectionLog *encryptedConnection = getEncryptedConnection(peerMac); if(!encryptedConnection) { assert(encryptedConnection && String(F("requestEncryptedConnectionKernel cannot find an encrypted connection!"))); // requestEncryptedConnectionRemoval received. - _ongoingPeerRequestResult = ECS_REQUEST_TRANSMISSION_FAILED; + _ongoingPeerRequestResult = EncryptedConnectionStatus::REQUEST_TRANSMISSION_FAILED; } else if(encryptedConnection->removalScheduled() || (encryptedConnection->temporary() && encryptedConnection->temporary()->expired())) { // Could possibly be caused by a simultaneous temporary peer request from the peer. - _ongoingPeerRequestResult = ECS_REQUEST_TRANSMISSION_FAILED; + _ongoingPeerRequestResult = EncryptedConnectionStatus::REQUEST_TRANSMISSION_FAILED; } else { @@ -1805,13 +1812,13 @@ encrypted_connection_status_t EspnowMeshBackend::requestEncryptedConnectionKerne else { assert(false && String(F("Unknown messageHeader during encrypted connection finalization!"))); - _ongoingPeerRequestResult = ECS_API_CALL_FAILED; + _ongoingPeerRequestResult = EncryptedConnectionStatus::API_CALL_FAILED; } } } else { - _ongoingPeerRequestResult = ECS_REQUEST_TRANSMISSION_FAILED; + _ongoingPeerRequestResult = EncryptedConnectionStatus::REQUEST_TRANSMISSION_FAILED; } } @@ -1849,20 +1856,20 @@ String EspnowMeshBackend::flexibleEncryptionRequestBuilder(const uint32_t minDur return createEncryptionRequestHmacMessage(FPSTR(temporaryEncryptionRequestHeader), requestNonce, hashKey, espnowHashKeyLength, connectionDuration); } -encrypted_connection_status_t EspnowMeshBackend::requestEncryptedConnection(uint8_t *peerMac) +EncryptedConnectionStatus EspnowMeshBackend::requestEncryptedConnection(uint8_t *peerMac) { using namespace std::placeholders; 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) +EncryptedConnectionStatus EspnowMeshBackend::requestTemporaryEncryptedConnection(uint8_t *peerMac, uint32_t durationMs) { using namespace std::placeholders; return requestEncryptedConnectionKernel(peerMac, std::bind(defaultEncryptionRequestBuilder, FPSTR(EspnowProtocolInterpreter::temporaryEncryptionRequestHeader), durationMs, getEspnowHashKey(), _1, _2)); } -encrypted_connection_status_t EspnowMeshBackend::requestFlexibleTemporaryEncryptedConnection(uint8_t *peerMac, uint32_t minDurationMs) +EncryptedConnectionStatus EspnowMeshBackend::requestFlexibleTemporaryEncryptedConnection(uint8_t *peerMac, uint32_t minDurationMs) { using namespace std::placeholders; return requestEncryptedConnectionKernel(peerMac, std::bind(flexibleEncryptionRequestBuilder, minDurationMs, getEspnowHashKey(), _1, _2)); @@ -1875,13 +1882,11 @@ bool EspnowMeshBackend::temporaryEncryptedConnectionToPermanent(uint8_t *peerMac temporaryConnection->removeDuration(); return true; } - else - { - return false; - } + + return false; } -encrypted_connection_removal_outcome_t EspnowMeshBackend::removeEncryptedConnection(uint8_t *peerMac) +EncryptedConnectionRemovalOutcome EspnowMeshBackend::removeEncryptedConnection(uint8_t *peerMac) { auto connectionIterator = getEncryptedConnectionIterator(peerMac, encryptedConnections); if(connectionIterator != encryptedConnections.end()) @@ -1892,27 +1897,25 @@ encrypted_connection_removal_outcome_t EspnowMeshBackend::removeEncryptedConnect // We should not remove an encrypted connection while there is a transmission in progress, since that may cause encrypted data to be sent unencrypted. // Thus when a transmission is in progress we just schedule the encrypted connection for removal, so it will be removed during the next updateTemporaryEncryptedConnections() call. connectionIterator->scheduleForRemoval(); - return ECRO_REMOVAL_SCHEDULED; + return EncryptedConnectionRemovalOutcome::REMOVAL_SCHEDULED; } else { return removeEncryptedConnectionUnprotected(peerMac); } } - else - { - // peerMac is already removed - return ECRO_REMOVAL_SUCCEEDED; - } + + // peerMac is already removed + return EncryptedConnectionRemovalOutcome::REMOVAL_SUCCEEDED; } -encrypted_connection_removal_outcome_t EspnowMeshBackend::removeEncryptedConnectionUnprotected(const uint8_t *peerMac, std::vector::iterator *resultingIterator) +EncryptedConnectionRemovalOutcome EspnowMeshBackend::removeEncryptedConnectionUnprotected(const uint8_t *peerMac, std::vector::iterator *resultingIterator) { connectionLogIterator connectionIterator = getEncryptedConnectionIterator(peerMac, encryptedConnections); return removeEncryptedConnectionUnprotected(connectionIterator, resultingIterator); } -encrypted_connection_removal_outcome_t EspnowMeshBackend::removeEncryptedConnectionUnprotected(connectionLogIterator &connectionIterator, std::vector::iterator *resultingIterator) +EncryptedConnectionRemovalOutcome EspnowMeshBackend::removeEncryptedConnectionUnprotected(connectionLogIterator &connectionIterator, std::vector::iterator *resultingIterator) { assert(encryptedConnections.size() <= maxEncryptedConnections); // If this is not the case, ESP-NOW is no longer in sync with the library @@ -1945,19 +1948,17 @@ encrypted_connection_removal_outcome_t EspnowMeshBackend::removeEncryptedConnect deleteEntriesByMac(sentRequests, encryptedMac, true); deleteEntriesByMac(receivedRequests, encryptedMac, true); - return ECRO_REMOVAL_SUCCEEDED; + return EncryptedConnectionRemovalOutcome::REMOVAL_SUCCEEDED; } else { staticVerboseModePrint(String(F("Removal failed"))); - return ECRO_REMOVAL_FAILED; + return EncryptedConnectionRemovalOutcome::REMOVAL_FAILED; } } - else - { - // connection is already removed - return ECRO_REMOVAL_SUCCEEDED; - } + + // connection is already removed + return EncryptedConnectionRemovalOutcome::REMOVAL_SUCCEEDED; } template @@ -2016,7 +2017,7 @@ void EspnowMeshBackend::deleteEntriesByMac(std::mapremovalScheduled()) - return ECRO_REMOVAL_SUCCEEDED; // Removal will be completed by mutex destructorHook. + return EncryptedConnectionRemovalOutcome::REMOVAL_SUCCEEDED; // Removal will be completed by mutex destructorHook. else - return ECRO_REMOVAL_REQUEST_FAILED; + return EncryptedConnectionRemovalOutcome::REMOVAL_REQUEST_FAILED; } } - else - { - // peerMac is already removed - return ECRO_REMOVAL_SUCCEEDED; - } + + // peerMac is already removed + return EncryptedConnectionRemovalOutcome::REMOVAL_SUCCEEDED; } void EspnowMeshBackend::setAcceptsUnverifiedRequests(bool acceptsUnverifiedRequests) { _acceptsUnverifiedRequests = acceptsUnverifiedRequests; } @@ -2062,11 +2061,11 @@ void EspnowMeshBackend::setEncryptedConnectionsSoftLimit(uint8_t softLimit) uint8_t EspnowMeshBackend::encryptedConnectionsSoftLimit() { return _encryptedConnectionsSoftLimit; } template -typename std::vector::iterator EspnowMeshBackend::getEncryptedConnectionIterator(const uint8_t *peerMac, typename std::vector &connectionVector) +typename T::iterator EspnowMeshBackend::getEncryptedConnectionIterator(const uint8_t *peerMac, T &connectionContainer) { - typename std::vector::iterator connectionIterator = connectionVector.begin(); + typename T::iterator connectionIterator = connectionContainer.begin(); - while(connectionIterator != connectionVector.end()) + while(connectionIterator != connectionContainer.end()) { if(connectionIterator->connectedTo(peerMac)) break; @@ -2091,10 +2090,8 @@ bool EspnowMeshBackend::getEncryptedConnectionIterator(const uint8_t *peerMac, c iterator = result; return true; } - else - { - return false; - } + + return false; } bool EspnowMeshBackend::getTemporaryEncryptedConnectionIterator(const uint8_t *peerMac, connectionLogIterator &iterator) @@ -2106,10 +2103,8 @@ bool EspnowMeshBackend::getTemporaryEncryptedConnectionIterator(const uint8_t *p iterator = result; return true; } - else - { - return false; - } + + return false; } EncryptedConnectionLog *EspnowMeshBackend::getEncryptedConnection(const uint8_t *peerMac) @@ -2119,10 +2114,8 @@ EncryptedConnectionLog *EspnowMeshBackend::getEncryptedConnection(const uint8_t { return &(*connectionIterator); } - else - { - return nullptr; - } + + return nullptr; } EncryptedConnectionLog *EspnowMeshBackend::getTemporaryEncryptedConnection(const uint8_t *peerMac) @@ -2132,10 +2125,8 @@ EncryptedConnectionLog *EspnowMeshBackend::getTemporaryEncryptedConnection(const { return &(*connectionIterator); } - else - { - return nullptr; - } + + return nullptr; } @@ -2145,10 +2136,8 @@ uint8_t *EspnowMeshBackend::getEncryptedMac(const uint8_t *peerMac, uint8_t *res { return encryptedConnection->getEncryptedPeerMac(resultArray); } - else - { - return nullptr; - } + + return nullptr; } void EspnowMeshBackend::prepareForTransmission(const String &message, bool scan, bool scanAllWiFiChannels) @@ -2164,7 +2153,7 @@ void EspnowMeshBackend::prepareForTransmission(const String &message, bool scan, } } -transmission_status_t EspnowMeshBackend::initiateTransmission(const String &message, const EspnowNetworkInfo &recipientInfo) +TransmissionStatusType EspnowMeshBackend::initiateTransmission(const String &message, const EspnowNetworkInfo &recipientInfo) { uint8_t targetBSSID[6] {0}; @@ -2180,17 +2169,17 @@ transmission_status_t EspnowMeshBackend::initiateTransmission(const String &mess return initiateTransmissionKernel(message, targetBSSID); } -transmission_status_t EspnowMeshBackend::initiateTransmissionKernel(const String &message, const uint8_t *targetBSSID) +TransmissionStatusType EspnowMeshBackend::initiateTransmissionKernel(const String &message, const uint8_t *targetBSSID) { uint32_t transmissionStartTime = millis(); - transmission_status_t transmissionResult = sendRequest(message, targetBSSID); + TransmissionStatusType transmissionResult = sendRequest(message, targetBSSID); uint32_t transmissionDuration = millis() - transmissionStartTime; - if(verboseMode() && transmissionResult == TS_TRANSMISSION_COMPLETE) // Avoid calculations if not required + if(verboseMode() && transmissionResult == TransmissionStatusType::TRANSMISSION_COMPLETE) // Avoid calculations if not required { totalDurationWhenSuccessful_AT += transmissionDuration; - successfulTransmissions_AT++; + ++successfulTransmissions_AT; if(transmissionDuration > maxTransmissionDuration_AT) { maxTransmissionDuration_AT = transmissionDuration; @@ -2233,7 +2222,7 @@ void EspnowMeshBackend::attemptTransmission(const String &message, bool scan, bo { for(const EspnowNetworkInfo ¤tNetwork : constConnectionQueue()) { - transmission_status_t transmissionResult = initiateTransmission(getMessage(), currentNetwork); + TransmissionStatusType transmissionResult = initiateTransmission(getMessage(), currentNetwork); latestTransmissionOutcomes().push_back(TransmissionOutcome{.origin = currentNetwork, .transmissionStatus = transmissionResult}); @@ -2245,19 +2234,19 @@ void EspnowMeshBackend::attemptTransmission(const String &message, bool scan, bo printTransmissionStatistics(); } -transmission_status_t EspnowMeshBackend::attemptTransmission(const String &message, const EspnowNetworkInfo &recipientInfo) +TransmissionStatusType EspnowMeshBackend::attemptTransmission(const String &message, const EspnowNetworkInfo &recipientInfo) { MutexTracker mutexTracker(_espnowTransmissionMutex, handlePostponedRemovals); if(!mutexTracker.mutexCaptured()) { 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 TransmissionStatusType::CONNECTION_FAILED; } return initiateTransmission(message, recipientInfo); } -encrypted_connection_status_t EspnowMeshBackend::initiateAutoEncryptingConnection(const EspnowNetworkInfo &recipientInfo, bool requestPermanentConnection, uint8_t *targetBSSID, EncryptedConnectionLog **existingEncryptedConnection) +EncryptedConnectionStatus EspnowMeshBackend::initiateAutoEncryptingConnection(const EspnowNetworkInfo &recipientInfo, bool requestPermanentConnection, uint8_t *targetBSSID, EncryptedConnectionLog **existingEncryptedConnection) { assert(recipientInfo.BSSID() != nullptr); // We need at least the BSSID to connect recipientInfo.getBSSID(targetBSSID); @@ -2269,7 +2258,7 @@ encrypted_connection_status_t EspnowMeshBackend::initiateAutoEncryptingConnectio } *existingEncryptedConnection = getEncryptedConnection(targetBSSID); - encrypted_connection_status_t connectionStatus = ECS_MAX_CONNECTIONS_REACHED_SELF; + EncryptedConnectionStatus connectionStatus = EncryptedConnectionStatus::MAX_CONNECTIONS_REACHED_SELF; if(requestPermanentConnection) connectionStatus = requestEncryptedConnection(targetBSSID); @@ -2279,9 +2268,9 @@ encrypted_connection_status_t EspnowMeshBackend::initiateAutoEncryptingConnectio return connectionStatus; } -transmission_status_t EspnowMeshBackend::initiateAutoEncryptingTransmission(const String &message, const uint8_t *targetBSSID, encrypted_connection_status_t connectionStatus) +TransmissionStatusType EspnowMeshBackend::initiateAutoEncryptingTransmission(const String &message, const uint8_t *targetBSSID, EncryptedConnectionStatus connectionStatus) { - transmission_status_t transmissionResult = TS_CONNECTION_FAILED; + TransmissionStatusType transmissionResult = TransmissionStatusType::CONNECTION_FAILED; if(encryptedConnectionEstablished(connectionStatus)) { @@ -2326,7 +2315,7 @@ void EspnowMeshBackend::attemptAutoEncryptingTransmission(const String &message, { uint8_t currentBSSID[6] {0}; EncryptedConnectionLog *existingEncryptedConnection = nullptr; - encrypted_connection_status_t connectionStatus = initiateAutoEncryptingConnection(currentNetwork, requestPermanentConnections, currentBSSID, &existingEncryptedConnection); + EncryptedConnectionStatus connectionStatus = initiateAutoEncryptingConnection(currentNetwork, requestPermanentConnections, currentBSSID, &existingEncryptedConnection); MutexTracker innerMutexTracker = MutexTracker(_espnowTransmissionMutex); if(!innerMutexTracker.mutexCaptured()) @@ -2335,7 +2324,7 @@ void EspnowMeshBackend::attemptAutoEncryptingTransmission(const String &message, return; } - transmission_status_t transmissionResult = initiateAutoEncryptingTransmission(getMessage(), currentBSSID, connectionStatus); + TransmissionStatusType transmissionResult = initiateAutoEncryptingTransmission(getMessage(), currentBSSID, connectionStatus); latestTransmissionOutcomes().push_back(TransmissionOutcome{.origin = currentNetwork, .transmissionStatus = transmissionResult}); @@ -2349,20 +2338,20 @@ void EspnowMeshBackend::attemptAutoEncryptingTransmission(const String &message, printTransmissionStatistics(); } -transmission_status_t EspnowMeshBackend::attemptAutoEncryptingTransmission(const String &message, const EspnowNetworkInfo &recipientInfo, bool requestPermanentConnection) +TransmissionStatusType EspnowMeshBackend::attemptAutoEncryptingTransmission(const String &message, const EspnowNetworkInfo &recipientInfo, bool requestPermanentConnection) { uint8_t targetBSSID[6] {0}; EncryptedConnectionLog *existingEncryptedConnection = nullptr; - encrypted_connection_status_t connectionStatus = initiateAutoEncryptingConnection(recipientInfo, requestPermanentConnection, targetBSSID, &existingEncryptedConnection); + EncryptedConnectionStatus connectionStatus = initiateAutoEncryptingConnection(recipientInfo, requestPermanentConnection, targetBSSID, &existingEncryptedConnection); MutexTracker mutexTracker(_espnowTransmissionMutex, handlePostponedRemovals); if(!mutexTracker.mutexCaptured()) { 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 TransmissionStatusType::CONNECTION_FAILED; } - transmission_status_t transmissionResult = initiateAutoEncryptingTransmission(message, targetBSSID, connectionStatus); + TransmissionStatusType transmissionResult = initiateAutoEncryptingTransmission(message, targetBSSID, connectionStatus); finalizeAutoEncryptingConnection(targetBSSID, existingEncryptedConnection, requestPermanentConnection); @@ -2412,7 +2401,7 @@ void EspnowMeshBackend::sendPeerRequestConfirmations(const ExpiringTimeTracker * auto timeTrackerPointer = confirmationsIterator->temporary(); assert(timeTrackerPointer); // peerRequestConfirmations should always expire and so should always have a timeTracker - if(timeTrackerPointer->timeSinceCreation() > getEncryptionRequestTimeout() + if(timeTrackerPointer->elapsedTime() > getEncryptionRequestTimeout() || (reciprocalPeerRequest && confirmationsIterator->getPeerRequestNonce() <= initialOngoingPeerRequestNonce)) { // The peer request has expired, @@ -2450,7 +2439,7 @@ void EspnowMeshBackend::sendPeerRequestConfirmations(const ExpiringTimeTracker * else if(espnowSendToNodeUnsynchronized(JsonTranslator::createEncryptionRequestHmacMessage(FPSTR(basicConnectionInfoHeader), confirmationsIterator->getPeerRequestNonce(), hashKey, espnowHashKeyLength), sendToDefaultBSSID ? defaultBSSID : unencryptedBSSID, 'C', generateMessageID(nullptr)) // Generates a new message ID to avoid sending encrypted sessionKeys over unencrypted connections. - == TS_TRANSMISSION_COMPLETE) + == TransmissionStatusType::TRANSMISSION_COMPLETE) { // Try to add encrypted connection. If connection added send confirmation with encryptedConnection->getOwnSessionKey() as session key and C type message (won't increment key). Then proceed with next request (no need to wait for answer). if(existingEncryptedConnection) @@ -2547,7 +2536,7 @@ void EspnowMeshBackend::sendEspnowResponses(const ExpiringTimeTracker *estimated uint32_t responseIndex = 0; for(std::list::iterator responseIterator = responsesToSend.begin(); responseIterator != responsesToSend.end(); ++responseIndex) { - if(responseIterator->timeSinceCreation() > logEntryLifetimeMs()) + if(responseIterator->getTimeTracker().timeSinceCreation() > logEntryLifetimeMs()) { // If the response is older than logEntryLifetimeMs(), the corresponding request log entry has been deleted at the request sender, // so the request sender will not accept our response any more. @@ -2560,7 +2549,7 @@ void EspnowMeshBackend::sendEspnowResponses(const ExpiringTimeTracker *estimated // Note that callbacks can be called during delay time, so it is possible to receive a transmission during espnowSendToNode // (which may add an element to the responsesToSend list). if(espnowSendToNodeUnsynchronized(responseIterator->getMessage(), responseIterator->getRecipientMac(), 'A', responseIterator->getRequestID()) - == TS_TRANSMISSION_COMPLETE) + == TransmissionStatusType::TRANSMISSION_COMPLETE) { if(EspnowMeshBackend *currentEspnowRequestManager = getEspnowRequestManager()) hookOutcome = currentEspnowRequestManager->getResponseTransmittedHook()(responseIterator->getMessage(), responseIterator->getRecipientMac(), responseIndex, *currentEspnowRequestManager); @@ -2626,32 +2615,28 @@ uint8_t EspnowMeshBackend::reservedEncryptedConnections() return encryptedConnections.size(); } -espnow_connection_type_t EspnowMeshBackend::getConnectionInfoHelper(const EncryptedConnectionLog *encryptedConnection, uint32_t *remainingDuration, uint8_t *peerMac) +ConnectionType EspnowMeshBackend::getConnectionInfoHelper(const EncryptedConnectionLog *encryptedConnection, uint32_t *remainingDuration, uint8_t *peerMac) { if(!encryptedConnection) { - return ECT_NO_CONNECTION; + return ConnectionType::NO_CONNECTION; } - else - { - if(peerMac) - encryptedConnection->getEncryptedPeerMac(peerMac); - - if(const ExpiringTimeTracker *timeTracker = encryptedConnection->temporary()) - { - if(remainingDuration) - *remainingDuration = timeTracker->remainingDuration(); - return ECT_TEMPORARY_CONNECTION; - } - else - { - return ECT_PERMANENT_CONNECTION; - } + if(peerMac) + encryptedConnection->getEncryptedPeerMac(peerMac); + + if(const ExpiringTimeTracker *timeTracker = encryptedConnection->temporary()) + { + if(remainingDuration) + *remainingDuration = timeTracker->remainingDuration(); + + return ConnectionType::TEMPORARY_CONNECTION; } + + return ConnectionType::PERMANENT_CONNECTION; } -espnow_connection_type_t EspnowMeshBackend::getConnectionInfo(uint8_t *peerMac, uint32_t *remainingDuration) +ConnectionType EspnowMeshBackend::getConnectionInfo(uint8_t *peerMac, uint32_t *remainingDuration) { EncryptedConnectionLog *encryptedConnection = nullptr; @@ -2661,7 +2646,7 @@ espnow_connection_type_t EspnowMeshBackend::getConnectionInfo(uint8_t *peerMac, return getConnectionInfoHelper(encryptedConnection, remainingDuration); } -espnow_connection_type_t EspnowMeshBackend::getConnectionInfo(uint32_t connectionIndex, uint32_t *remainingDuration, uint8_t *peerMac) +ConnectionType EspnowMeshBackend::getConnectionInfo(uint32_t connectionIndex, uint32_t *remainingDuration, uint8_t *peerMac) { EncryptedConnectionLog *encryptedConnection = nullptr; @@ -2675,8 +2660,8 @@ double EspnowMeshBackend::getTransmissionFailRate() { if(_transmissionsTotal == 0) return 0; - else - return _transmissionsFailed/_transmissionsTotal; + + return _transmissionsFailed/_transmissionsTotal; } void EspnowMeshBackend::resetTransmissionFailRate() @@ -2696,21 +2681,25 @@ String EspnowMeshBackend::serializeUnencryptedConnection() String EspnowMeshBackend::serializeEncryptedConnection(const uint8_t *peerMac) { + String serializedConnection(emptyString); + EncryptedConnectionLog *encryptedConnection = nullptr; if(peerMac) encryptedConnection = getEncryptedConnection(peerMac); if(encryptedConnection) - return encryptedConnection->serialize(); - else - return emptyString; + serializedConnection = encryptedConnection->serialize(); + + return serializedConnection; } String EspnowMeshBackend::serializeEncryptedConnection(uint32_t connectionIndex) { + String serializedConnection(emptyString); + if(connectionIndex < numberOfEncryptedConnections()) - return encryptedConnections[connectionIndex].serialize(); - else - return emptyString; + serializedConnection = encryptedConnections[connectionIndex].serialize(); + + return serializedConnection; } diff --git a/libraries/ESP8266WiFiMesh/src/EspnowMeshBackend.h b/libraries/ESP8266WiFiMesh/src/EspnowMeshBackend.h index 17ca2d26d3..908cf3620d 100644 --- a/libraries/ESP8266WiFiMesh/src/EspnowMeshBackend.h +++ b/libraries/ESP8266WiFiMesh/src/EspnowMeshBackend.h @@ -90,32 +90,34 @@ #include "EspnowNetworkInfo.h" #include "CryptoInterface.h" -typedef enum +namespace Espnow { - ECT_NO_CONNECTION = 0, - ECT_TEMPORARY_CONNECTION = 1, - ECT_PERMANENT_CONNECTION = 2 -} espnow_connection_type_t; +enum class ConnectionType +{ + NO_CONNECTION = 0, + TEMPORARY_CONNECTION = 1, + PERMANENT_CONNECTION = 2 +}; // A value greater than 0 means that an encrypted connection has been established. -typedef enum +enum class EncryptedConnectionStatus { - ECS_MAX_CONNECTIONS_REACHED_SELF = -3, - ECS_REQUEST_TRANSMISSION_FAILED = -2, - ECS_MAX_CONNECTIONS_REACHED_PEER = -1, - ECS_API_CALL_FAILED = 0, - ECS_CONNECTION_ESTABLISHED = 1, - ECS_SOFT_LIMIT_CONNECTION_ESTABLISHED = 2 // Only used if _encryptedConnectionsSoftLimit is less than 6. -} encrypted_connection_status_t; - -typedef enum -{ - ECRO_REMOVAL_REQUEST_FAILED = -1, - ECRO_REMOVAL_FAILED = 0, - ECRO_REMOVAL_SUCCEEDED = 1, - ECRO_REMOVAL_SCHEDULED = 2 -} encrypted_connection_removal_outcome_t; + MAX_CONNECTIONS_REACHED_SELF = -3, + REQUEST_TRANSMISSION_FAILED = -2, + MAX_CONNECTIONS_REACHED_PEER = -1, + API_CALL_FAILED = 0, + CONNECTION_ESTABLISHED = 1, + SOFT_LIMIT_CONNECTION_ESTABLISHED = 2 // Only used if _encryptedConnectionsSoftLimit is less than 6. +}; +enum class EncryptedConnectionRemovalOutcome +{ + REMOVAL_REQUEST_FAILED = -1, + REMOVAL_FAILED = 0, + REMOVAL_SUCCEEDED = 1, + REMOVAL_SCHEDULED = 2 +}; +} /** * An alternative to standard delay(). Will continuously call performEspnowMaintenance() during the waiting time, so that the ESP-NOW node remains responsive. @@ -130,12 +132,14 @@ void espnowDelay(uint32_t durationMs); class RequestData; +using namespace Espnow; // TODO: Remove + class EspnowMeshBackend : public MeshBackendBase { protected: - typedef std::function broadcastFilterType; - typedef std::function responseTransmittedHookType; + using broadcastFilterType = std::function; + using responseTransmittedHookType = std::function; public: @@ -145,7 +149,7 @@ class EspnowMeshBackend : public MeshBackendBase { * @param requestHandler The callback handler for dealing with received requests. Takes a string as an argument which * is the request string received from another node and returns the string to send back. * @param responseHandler The callback handler for dealing with received responses. Takes a string as an argument which - * is the response string received from another node. Returns a transmission status code as a transmission_status_t. + * is the response string received from another node. Returns a transmission status code as a TransmissionStatusType. * @param networkFilter The callback handler for deciding which WiFi networks to connect to. * @param broadcastFilter The callback handler for deciding which ESP-NOW broadcasts to accept. * @param meshPassword The WiFi password for the mesh network. @@ -155,7 +159,7 @@ class EspnowMeshBackend : public MeshBackendBase { * @param ssidSuffix The suffix (last part) of the node SSID. * @param verboseMode Determines if we should print the events occurring in the library to Serial. Off by default. This setting is shared by all EspnowMeshBackend instances. * @param meshWiFiChannel The WiFi channel used by the mesh network. Valid values are integers from 1 to 13. Defaults to 1. - * WARNING: The ESP8266 has only one WiFi channel, and the the station/client mode is always prioritized for channel selection. + * WARNING: The ESP8266 has only one WiFi channel, and the station/client mode is always prioritized for channel selection. * This can cause problems if several mesh instances exist on the same ESP8266 and use different WiFi channels. * In such a case, whenever the station of one mesh instance connects to an AP, it will silently force the * WiFi channel of any active AP on the ESP8266 to match that of the station. This will cause disconnects and possibly @@ -173,7 +177,7 @@ class EspnowMeshBackend : public MeshBackendBase { * @param requestHandler The callback handler for dealing with received requests. Takes a string as an argument which * is the request string received from another node and returns the string to send back. * @param responseHandler The callback handler for dealing with received responses. Takes a string as an argument which - * is the response string received from another node. Returns a transmission status code as a transmission_status_t. + * is the response string received from another node. Returns a transmission status code as a TransmissionStatusType. * @param networkFilter The callback handler for deciding which WiFi networks to connect to. * @param broadcastFilter The callback handler for deciding which ESP-NOW broadcasts to accept. * @param meshPassword The WiFi password for the mesh network. @@ -183,7 +187,7 @@ class EspnowMeshBackend : public MeshBackendBase { * @param ssidSuffix The suffix (last part) of the node SSID. * @param verboseMode Determines if we should print the events occurring in the library to Serial. Off by default. This setting is shared by all EspnowMeshBackend instances. * @param meshWiFiChannel The WiFi channel used by the mesh network. Valid values are integers from 1 to 13. Defaults to 1. - * WARNING: The ESP8266 has only one WiFi channel, and the the station/client mode is always prioritized for channel selection. + * WARNING: The ESP8266 has only one WiFi channel, and the station/client mode is always prioritized for channel selection. * This can cause problems if several mesh instances exist on the same ESP8266 and use different WiFi channels. * In such a case, whenever the station of one mesh instance connects to an AP, it will silently force the * WiFi channel of any active AP on the ESP8266 to match that of the station. This will cause disconnects and possibly @@ -222,7 +226,7 @@ class EspnowMeshBackend : public MeshBackendBase { static std::vector & latestTransmissionOutcomes(); /** - * @return True if latest transmission was successful (i.e. latestTransmissionOutcomes is not empty and all entries have transmissionStatus TS_TRANSMISSION_COMPLETE). False otherwise. + * @return True if latest transmission was successful (i.e. latestTransmissionOutcomes is not empty and all entries have transmissionStatus TransmissionStatusType::TRANSMISSION_COMPLETE). False otherwise. * The result is unique for each mesh backend. */ static bool latestTransmissionSuccessful(); @@ -285,7 +289,7 @@ class EspnowMeshBackend : public MeshBackendBase { * * @param recipientInfo The recipient information. */ - transmission_status_t attemptTransmission(const String &message, const EspnowNetworkInfo &recipientInfo); + TransmissionStatusType attemptTransmission(const String &message, const EspnowNetworkInfo &recipientInfo); /* * Will ensure that an encrypted connection exists to each target node before sending the message, @@ -317,7 +321,7 @@ class EspnowMeshBackend : public MeshBackendBase { * Transmit message to a single recipient without changing the local transmission state (apart from encrypted connections). * Will not change connectionQueue, latestTransmissionOutcomes or stored message. */ - transmission_status_t attemptAutoEncryptingTransmission(const String &message, const EspnowNetworkInfo &recipientInfo, bool requestPermanentConnection = false); + TransmissionStatusType attemptAutoEncryptingTransmission(const String &message, const EspnowNetworkInfo &recipientInfo, bool requestPermanentConnection = false); /** * Send a message simultaneously to all nearby nodes which have ESP-NOW activated. @@ -736,32 +740,32 @@ class EspnowMeshBackend : public MeshBackendBase { // Updates connection with current stored encrypted connection key. // At least one of the leftmost 32 bits in each of the session keys should be 1, since the key otherwise indicates the connection is unencrypted. - encrypted_connection_status_t addEncryptedConnection(uint8_t *peerStaMac, uint8_t *peerApMac, uint64_t peerSessionKey, uint64_t ownSessionKey); + EncryptedConnectionStatus addEncryptedConnection(uint8_t *peerStaMac, uint8_t *peerApMac, uint64_t peerSessionKey, uint64_t ownSessionKey); // Note that the espnowEncryptedConnectionKey, espnowEncryptionKok, espnowHashKey and espnowMessageEncryptionKey are not serialized. // These will be set to the values of the EspnowMeshBackend instance that is adding the serialized encrypted connection. - // @param ignoreDuration Ignores any stored duration serializedConnectionState, guaranteeing that the created connection will be permanent. Returns: ECS_REQUEST_TRANSMISSION_FAILED indicates malformed serializedConnectionState. - encrypted_connection_status_t addEncryptedConnection(const String &serializedConnectionState, bool ignoreDuration = false); + // @param ignoreDuration Ignores any stored duration serializedConnectionState, guaranteeing that the created connection will be permanent. Returns: EncryptedConnectionStatus::REQUEST_TRANSMISSION_FAILED indicates malformed serializedConnectionState. + EncryptedConnectionStatus addEncryptedConnection(const String &serializedConnectionState, bool ignoreDuration = false); // Adds a new temporary encrypted connection, or changes the duration of an existing temporary connection (only updates keys, not duration, for existing permanent connections). // As with all these methods, changes will only take effect once the requester proves it has the ability to decrypt the session key. // At least one of the leftmost 32 bits in each of the session keys should be 1, since the key otherwise indicates the connection is unencrypted. - encrypted_connection_status_t addTemporaryEncryptedConnection(uint8_t *peerStaMac, uint8_t *peerApMac, uint64_t peerSessionKey, uint64_t ownSessionKey, uint32_t duration); + EncryptedConnectionStatus addTemporaryEncryptedConnection(uint8_t *peerStaMac, uint8_t *peerApMac, uint64_t peerSessionKey, uint64_t ownSessionKey, uint32_t duration); // Note that the espnowEncryptedConnectionKey, espnowEncryptionKok, espnowHashKey and espnowMessageEncryptionKey are not serialized. // These will be set to the values of the EspnowMeshBackend instance that is adding the serialized encrypted connection. - // Uses duration argument instead of any stored duration in serializedConnectionState. Returns: ECS_REQUEST_TRANSMISSION_FAILED indicates malformed serializedConnectionState. - encrypted_connection_status_t addTemporaryEncryptedConnection(const String &serializedConnectionState, uint32_t duration); + // Uses duration argument instead of any stored duration in serializedConnectionState. Returns: EncryptedConnectionStatus::REQUEST_TRANSMISSION_FAILED indicates malformed serializedConnectionState. + EncryptedConnectionStatus addTemporaryEncryptedConnection(const String &serializedConnectionState, uint32_t duration); // If an encrypted connection to peerMac already exists, only connection duration is updated. All other settings are kept as is. Use removeEncryptedConnection/requestEncryptedConnectionRemoval first if encryption keys should be updated. // Makes sure both nodes have an encrypted connection to each other that's permanent. - encrypted_connection_status_t requestEncryptedConnection(uint8_t *peerMac); + EncryptedConnectionStatus requestEncryptedConnection(uint8_t *peerMac); // Makes sure both nodes have an encrypted connection to each other that's either permanent or has the duration specified. - encrypted_connection_status_t requestTemporaryEncryptedConnection(uint8_t *peerMac, uint32_t durationMs); + EncryptedConnectionStatus requestTemporaryEncryptedConnection(uint8_t *peerMac, uint32_t durationMs); // Makes sure both nodes have an encrypted connection to each other that's either permanent or has at least the duration specified. // Note that if a temporary encrypted connection already exists to a target node, this method will slightly extend the connection duration // depending on the time it takes to verify the connection to the node. - encrypted_connection_status_t requestFlexibleTemporaryEncryptedConnection(uint8_t *peerMac, uint32_t minDurationMs); - static encrypted_connection_removal_outcome_t removeEncryptedConnection(uint8_t *peerMac); - encrypted_connection_removal_outcome_t requestEncryptedConnectionRemoval(uint8_t *peerMac); + EncryptedConnectionStatus requestFlexibleTemporaryEncryptedConnection(uint8_t *peerMac, uint32_t minDurationMs); + static EncryptedConnectionRemovalOutcome removeEncryptedConnection(uint8_t *peerMac); + EncryptedConnectionRemovalOutcome requestEncryptedConnectionRemoval(uint8_t *peerMac); /** * Set whether this EspnowMeshBackend instance will accept ESP-NOW requests from unencrypted connections or not, when acting as EspnowRequestManager. @@ -824,24 +828,24 @@ class EspnowMeshBackend : public MeshBackendBase { * @param peerMac The node MAC for which to get information. Both MAC for AP interface and MAC for STA interface can be used (and will yield the same result). * Use the getEncryptedMac method or the indexed based getConnectionInfo if there is a need to find the actual encrypted interface. * @param remainingDuration An optional pointer to a uint32_t variable. - * If supplied and the connection type is ECT_TEMPORARY_CONNECTION the variable will be set to the remaining duration of the connection. + * If supplied and the connection type is ConnectionType::TEMPORARY_CONNECTION the variable will be set to the remaining duration of the connection. * Otherwise the variable value is not modified. - * @return The espnow_connection_type_t of the connection with peerMac. + * @return The ConnectionType of the connection with peerMac. */ - static espnow_connection_type_t getConnectionInfo(uint8_t *peerMac, uint32_t *remainingDuration = nullptr); + static ConnectionType getConnectionInfo(uint8_t *peerMac, uint32_t *remainingDuration = nullptr); /** * Get information about any current ESP-NOW connection with another node. * * @param connectionIndex The connection index of the node for which to get information. Valid values are limited by numberOfEncryptedConnections(). * @param remainingDuration An optional pointer to a uint32_t variable. - * If supplied and the connection type is ECT_TEMPORARY_CONNECTION the variable will be set to the remaining duration of the connection. + * If supplied and the connection type is ConnectionType::TEMPORARY_CONNECTION the variable will be set to the remaining duration of the connection. * Otherwise the variable value is not modified. * @param peerMac An optional pointer to an uint8_t array with at least size 6. It will be filled with the MAC of the encrypted peer interface if an encrypted connection exists. * Otherwise the array is not modified. - * @return The espnow_connection_type_t of the connection given by connectionIndex. + * @return The ConnectionType of the connection given by connectionIndex. */ - static espnow_connection_type_t getConnectionInfo(uint32_t connectionIndex, uint32_t *remainingDuration = nullptr, uint8_t *peerMac = nullptr); + static ConnectionType getConnectionInfo(uint32_t connectionIndex, uint32_t *remainingDuration = nullptr, uint8_t *peerMac = nullptr); /** * @return The proportion of ESP-NOW requests made by this node that have failed, since power on or latest reset. @@ -858,7 +862,7 @@ class EspnowMeshBackend : public MeshBackendBase { static std::vector _connectionQueue; static std::vector _latestTransmissionOutcomes; - typedef std::vector::iterator connectionLogIterator; + using connectionLogIterator = std::vector::iterator; static connectionLogIterator connectionLogEndIterator(); static const uint8_t broadcastMac[6]; @@ -866,7 +870,7 @@ class EspnowMeshBackend : public MeshBackendBase { bool activateEspnow(); - static bool encryptedConnectionEstablished(encrypted_connection_status_t connectionStatus); + static bool encryptedConnectionEstablished(EncryptedConnectionStatus connectionStatus); /* * Note that ESP-NOW is not perfect and in rare cases messages may be dropped. @@ -906,8 +910,8 @@ class EspnowMeshBackend : public MeshBackendBase { // Consider using getScheduledResponseRecipient and similar methods for this preparation. // Should only be used when there is no transmissions in progress. In practice when _espnowTransmissionMutex is free. // @param resultingIterator Will be set to the iterator position after the removed element, if an element to remove was found. Otherwise no change will occur. - static encrypted_connection_removal_outcome_t removeEncryptedConnectionUnprotected(const uint8_t *peerMac, std::vector::iterator *resultingIterator = nullptr); - static encrypted_connection_removal_outcome_t removeEncryptedConnectionUnprotected(connectionLogIterator &connectionIterator, std::vector::iterator *resultingIterator); + static EncryptedConnectionRemovalOutcome removeEncryptedConnectionUnprotected(const uint8_t *peerMac, std::vector::iterator *resultingIterator = nullptr); + static EncryptedConnectionRemovalOutcome removeEncryptedConnectionUnprotected(connectionLogIterator &connectionIterator, std::vector::iterator *resultingIterator); /** * Set the MAC address considered to be the sender of the most recently received ESP-NOW request, response or broadcast. @@ -935,17 +939,17 @@ class EspnowMeshBackend : public MeshBackendBase { /** * Will be true if a transmission initiated by a public method is in progress. */ - static bool _espnowTransmissionMutex; + static std::shared_ptr _espnowTransmissionMutex; /** * Will be true when the connectionQueue should not be modified. */ - static bool _espnowConnectionQueueMutex; + static std::shared_ptr _espnowConnectionQueueMutex; /** * Will be true when no responsesToSend element should be removed. */ - static bool _responsesToSendMutex; + static std::shared_ptr _responsesToSendMutex; /** * Check if there is an ongoing ESP-NOW transmission in the library. Used to avoid interrupting transmissions. @@ -955,8 +959,8 @@ class EspnowMeshBackend : public MeshBackendBase { static bool transmissionInProgress(); enum class macAndType_td : uint64_t {}; - typedef uint64_t messageID_td; - typedef uint64_t peerMac_td; + using messageID_td = uint64_t; + using peerMac_td = uint64_t; static macAndType_td createMacAndTypeValue(uint64_t uint64Mac, char messageType); static uint64_t macAndTypeToUint64Mac(const macAndType_td &macAndTypeValue); @@ -985,19 +989,19 @@ class EspnowMeshBackend : public MeshBackendBase { * @return The transmission status for the transmission. */ // Send a message to the node having targetBSSID as mac, changing targetBSSID to the mac of the encrypted connection if it exists and ensuring such an encrypted connection is synchronized. - static transmission_status_t espnowSendToNode(const String &message, const uint8_t *targetBSSID, char messageType, EspnowMeshBackend *espnowInstance = nullptr); + static TransmissionStatusType espnowSendToNode(const String &message, const uint8_t *targetBSSID, char messageType, EspnowMeshBackend *espnowInstance = nullptr); // Send a message using exactly the arguments given, without consideration for any encrypted connections. - static transmission_status_t espnowSendToNodeUnsynchronized(const String message, const uint8_t *targetBSSID, char messageType, uint64_t messageID, EspnowMeshBackend *espnowInstance = nullptr); + static TransmissionStatusType espnowSendToNodeUnsynchronized(const String message, const uint8_t *targetBSSID, char messageType, uint64_t messageID, EspnowMeshBackend *espnowInstance = nullptr); - transmission_status_t sendRequest(const String &message, const uint8_t *targetBSSID); - transmission_status_t sendResponse(const String &message, uint64_t requestID, const uint8_t *targetBSSID); + TransmissionStatusType sendRequest(const String &message, const uint8_t *targetBSSID); + TransmissionStatusType sendResponse(const String &message, uint64_t requestID, const uint8_t *targetBSSID); private: EspnowMeshBackend(requestHandlerType requestHandler, responseHandlerType responseHandler, networkFilterType networkFilter, broadcastFilterType broadcastFilter, const String &meshPassword, const String &ssidPrefix, const String &ssidSuffix, bool verboseMode, uint8 meshWiFiChannel); - typedef std::function encryptionRequestBuilderType; + using encryptionRequestBuilderType = std::function; static String defaultEncryptionRequestBuilder(const String &requestHeader, const uint32_t durationMs, const uint8_t *hashKey, const String &requestNonce, const ExpiringTimeTracker &existingTimeTracker); static String flexibleEncryptionRequestBuilder(const uint32_t minDurationMs, const uint8_t *hashKey, const String &requestNonce, const ExpiringTimeTracker &existingTimeTracker); @@ -1055,14 +1059,14 @@ class EspnowMeshBackend : public MeshBackendBase { static EncryptedConnectionLog *getEncryptedConnection(const uint8_t *peerMac); static EncryptedConnectionLog *getTemporaryEncryptedConnection(const uint8_t *peerMac); - //@return iterator to connection in connectionVector, or connectionVector.end() if element not found + //@return iterator to connection in connectionContainer, or connectionContainer.end() if element not found template - static typename std::vector::iterator getEncryptedConnectionIterator(const uint8_t *peerMac, typename std::vector &connectionVector); + static typename T::iterator getEncryptedConnectionIterator(const uint8_t *peerMac, T &connectionContainer); static bool getEncryptedConnectionIterator(const uint8_t *peerMac, connectionLogIterator &iterator); // @return true if an encrypted connection to peerMac is found and the found connection is temporary. Only changes iterator if true is returned. static bool getTemporaryEncryptedConnectionIterator(const uint8_t *peerMac, connectionLogIterator &iterator); - static espnow_connection_type_t getConnectionInfoHelper(const EncryptedConnectionLog *encryptedConnection, uint32_t *remainingDuration, uint8_t *peerMac = nullptr); + static ConnectionType getConnectionInfoHelper(const EncryptedConnectionLog *encryptedConnection, uint32_t *remainingDuration, uint8_t *peerMac = nullptr); // Should only be used when there is no transmissions in progress, so it is safe to remove encrypted connections. In practice when _espnowTransmissionMutex is free. // @param scheduledRemovalOnly If true, only deletes encrypted connections where removalScheduled() is true. This means only connections which have been requested for removal will be deleted, @@ -1072,6 +1076,9 @@ class EspnowMeshBackend : public MeshBackendBase { template static void deleteExpiredLogEntries(std::map, T> &logEntries, uint32_t maxEntryLifetimeMs); + template + static void deleteExpiredLogEntries(std::map, TimeTracker> &logEntries, uint32_t maxEntryLifetimeMs); + static void deleteExpiredLogEntries(std::map, RequestData> &logEntries, uint32_t requestLifetimeMs, uint32_t broadcastLifetimeMs); template @@ -1082,7 +1089,6 @@ class EspnowMeshBackend : public MeshBackendBase { static uint32_t _encryptionRequestTimeoutMs; - static uint32_t _timeOfLastLogClear; static uint32_t _criticalHeapLevel; static uint32_t _criticalHeapLevelBuffer; @@ -1096,8 +1102,7 @@ class EspnowMeshBackend : public MeshBackendBase { static String _ongoingPeerRequestNonce; static uint8_t _ongoingPeerRequestMac[6]; static EspnowMeshBackend *_ongoingPeerRequester; - static encrypted_connection_status_t _ongoingPeerRequestResult; - static uint32_t _ongoingPeerRequestEncryptionStart; + static EncryptedConnectionStatus _ongoingPeerRequestResult; static bool _reciprocalPeerRequestConfirmation; template @@ -1119,7 +1124,7 @@ class EspnowMeshBackend : public MeshBackendBase { uint8_t _senderAPMac[6] = {0}; bool _receivedEncryptedTransmission = false; - static bool _espnowSendToNodeMutex; + static std::shared_ptr _espnowSendToNodeMutex; static uint8_t _transmissionTargetBSSID[6]; static void storeSentRequest(const uint64_t targetBSSID, const uint64_t messageID, const RequestData &requestData); @@ -1148,9 +1153,9 @@ class EspnowMeshBackend : public MeshBackendBase { * @param encryptionRequestBuilder A function which is responsible for constructing the request message to send. * Called twice when the request is successful. First to build the initial request message and then to build the connection verification message. * The request message should typically be of the form: JsonTranslator::createEncryptionRequestIntro() + JsonTranslator::createEncryptionRequestEnding(). - * @return The ultimate status of the requested encrypted connection, as encrypted_connection_status_t. + * @return The ultimate status of the requested encrypted connection, as EncryptedConnectionStatus. */ - encrypted_connection_status_t requestEncryptedConnectionKernel(uint8_t *peerMac, const encryptionRequestBuilderType &encryptionRequestBuilder); + EncryptedConnectionStatus requestEncryptedConnectionKernel(uint8_t *peerMac, const encryptionRequestBuilderType &encryptionRequestBuilder); /** * Generate a new message ID to be used when making a data transmission. The generated ID will be different depending on whether an encrypted connection exists or not. @@ -1170,12 +1175,12 @@ class EspnowMeshBackend : public MeshBackendBase { static uint64_t createSessionKey(); void prepareForTransmission(const String &message, bool scan, bool scanAllWiFiChannels); - transmission_status_t initiateTransmission(const String &message, const EspnowNetworkInfo &recipientInfo); - transmission_status_t initiateTransmissionKernel(const String &message, const uint8_t *targetBSSID); + TransmissionStatusType initiateTransmission(const String &message, const EspnowNetworkInfo &recipientInfo); + TransmissionStatusType initiateTransmissionKernel(const String &message, const uint8_t *targetBSSID); void printTransmissionStatistics(); - encrypted_connection_status_t initiateAutoEncryptingConnection(const EspnowNetworkInfo &recipientInfo, bool requestPermanentConnection, uint8_t *targetBSSID, EncryptedConnectionLog **existingEncryptedConnection); - transmission_status_t initiateAutoEncryptingTransmission(const String &message, const uint8_t *targetBSSID, encrypted_connection_status_t connectionStatus); + EncryptedConnectionStatus initiateAutoEncryptingConnection(const EspnowNetworkInfo &recipientInfo, bool requestPermanentConnection, uint8_t *targetBSSID, EncryptedConnectionLog **existingEncryptedConnection); + TransmissionStatusType initiateAutoEncryptingTransmission(const String &message, const uint8_t *targetBSSID, EncryptedConnectionStatus connectionStatus); void finalizeAutoEncryptingConnection(const uint8_t *targetBSSID, const EncryptedConnectionLog *existingEncryptedConnection, bool requestPermanentConnection); // Used for verboseMode printing in attemptTransmission, _AT suffix used to reduce namespace clutter diff --git a/libraries/ESP8266WiFiMesh/src/EspnowProtocolInterpreter.h b/libraries/ESP8266WiFiMesh/src/EspnowProtocolInterpreter.h index 42b56232bf..8a227669c2 100644 --- a/libraries/ESP8266WiFiMesh/src/EspnowProtocolInterpreter.h +++ b/libraries/ESP8266WiFiMesh/src/EspnowProtocolInterpreter.h @@ -45,7 +45,7 @@ namespace EspnowProtocolInterpreter constexpr char basicConnectionInfoHeader[] PROGMEM = "BasicCI:"; // Basic connection info constexpr char encryptedConnectionInfoHeader[] PROGMEM = "EncryptedCI:"; // Encrypted connection info constexpr char softLimitEncryptedConnectionInfoHeader[] PROGMEM = "SLEncryptedCI:"; // Soft limit encrypted connection info - constexpr char maxConnectionsReachedHeader[] PROGMEM = "ECS_MAX_CONNECTIONS_REACHED_PEER:"; + constexpr char maxConnectionsReachedHeader[] PROGMEM = "MAX_CONNECTIONS_REACHED_PEER:"; constexpr char encryptedConnectionVerificationHeader[] PROGMEM = "ECVerified:"; // Encrypted connection verified constexpr char encryptedConnectionRemovalRequestHeader[] PROGMEM = "RemoveEC:"; // Remove encrypted connection diff --git a/libraries/ESP8266WiFiMesh/src/ExpiringTimeTracker.cpp b/libraries/ESP8266WiFiMesh/src/ExpiringTimeTracker.cpp index 73b6d32145..5151c0bc5a 100644 --- a/libraries/ESP8266WiFiMesh/src/ExpiringTimeTracker.cpp +++ b/libraries/ESP8266WiFiMesh/src/ExpiringTimeTracker.cpp @@ -24,36 +24,101 @@ #include "ExpiringTimeTracker.h" -ExpiringTimeTracker::ExpiringTimeTracker(uint32_t duration, uint32_t creationTimeMs) : - TimeTracker(creationTimeMs), _duration(duration) -{ } +ExpiringTimeTracker::ExpiringTimeTracker(const uint32_t duration, const uint32_t creationTimeMs) : + timeoutTemplate(0) +{ + setDuration(duration); + _start = creationTimeMs; +} + +ExpiringTimeTracker::ExpiringTimeTracker(const calculatorType durationCalculator, const uint32_t creationTimeMs) : + timeoutTemplate(0) +{ + setDuration(durationCalculator); + _start = creationTimeMs; +} uint32_t ExpiringTimeTracker::duration() const { - return _duration; + if(useCalculator) + return _durationCalculator(); + + return getTimeout(); } -void ExpiringTimeTracker::setRemainingDuration(uint32_t remainingDuration) +IRAM_ATTR // called from ISR +void ExpiringTimeTracker::setTimeout(const uint32_t newUserTimeout) { - _duration = timeSinceCreation() + remainingDuration; + _timeout = newUserTimeout; + _neverExpires = (newUserTimeout > timeMax()); // newUserTimeout < 0 is always false for uint32_t +} + +void ExpiringTimeTracker::setDuration(const uint32_t duration) +{ + setTimeout(duration); + useCalculator = false; +} + +void ExpiringTimeTracker::setDuration(const calculatorType durationCalculator) +{ + _durationCalculator = durationCalculator; + useCalculator = true; +} + +void ExpiringTimeTracker::setRemainingDuration(const uint32_t remainingDuration) +{ + setDuration(elapsedTime() + remainingDuration); +} + +void ExpiringTimeTracker::setRemainingDuration(const calculatorType remainingDurationCalculator) +{ + uint32_t currentElapsedTime = elapsedTime(); + setDuration([remainingDurationCalculator, currentElapsedTime](){ return currentElapsedTime + remainingDurationCalculator(); }); } uint32_t ExpiringTimeTracker::remainingDuration() const { - uint32_t remainingDuration = duration() - timeSinceCreation(); + uint32_t remainingDuration = 0; - if(expired()) - { - // Overflow probably occured for remainingDuration calculation. - return 0; - } - else + if(!expired()) // If expired, overflow will probably occur for remainingDuration calculation. { - return remainingDuration; + remainingDuration = duration() - elapsedTime(); } + + return remainingDuration; +} + +uint32_t ExpiringTimeTracker::elapsedTime() const +{ + return millis() - _start; } bool ExpiringTimeTracker::expired() const +{ + if(useCalculator) + return elapsedTime() >= duration(); + + return expiredOneShot(); +} + +void ExpiringTimeTracker::reset() +{ + timeoutTemplate::reset(); +} + +void ExpiringTimeTracker::reset(const uint32_t newDuration) +{ + setDuration(newDuration); + ExpiringTimeTracker::reset(); +} + +void ExpiringTimeTracker::reset(const calculatorType newDurationCalculator) +{ + setDuration(newDurationCalculator); + ExpiringTimeTracker::reset(); +} + +ExpiringTimeTracker::operator bool() const { - return timeSinceCreation() >= duration(); + return ExpiringTimeTracker::expired(); } diff --git a/libraries/ESP8266WiFiMesh/src/ExpiringTimeTracker.h b/libraries/ESP8266WiFiMesh/src/ExpiringTimeTracker.h index 32fdd94407..1f9cd636ff 100644 --- a/libraries/ESP8266WiFiMesh/src/ExpiringTimeTracker.h +++ b/libraries/ESP8266WiFiMesh/src/ExpiringTimeTracker.h @@ -25,24 +25,54 @@ #ifndef __EXPIRINGTIMETRACKER_H__ #define __EXPIRINGTIMETRACKER_H__ -#include "TimeTracker.h" #include +#include -class ExpiringTimeTracker : public TimeTracker { +class ExpiringTimeTracker : private esp8266::polledTimeout::oneShotMs { public: - ~ExpiringTimeTracker() override = default; + using calculatorType = std::function; - ExpiringTimeTracker(uint32_t duration, uint32_t creationTimeMs = millis()); + virtual ~ExpiringTimeTracker() = default; + + ExpiringTimeTracker(const uint32_t duration, const uint32_t creationTimeMs = millis()); + ExpiringTimeTracker(const calculatorType durationCalculator, const uint32_t creationTimeMs = millis()); + uint32_t duration() const; - void setRemainingDuration(uint32_t remainingDuration); + void setDuration(const uint32_t duration); + void setDuration(const calculatorType durationCalculator); + uint32_t remainingDuration() const; + + /** + * Sets a new duration which includes the current elapsedTime(). This means elapsedTime() is not reset. + * Note that reset() will use this new duration, including the saved elapsedTime(). + */ + void setRemainingDuration(const uint32_t remainingDuration); + + /** + * Sets a new duration which includes the current elapsedTime(). This means elapsedTime() is not reset. + * Note that reset() will use this new duration, including the saved elapsedTime(). + */ + void setRemainingDuration(const calculatorType remainingDurationCalculator); + + /** + * Get the time since the ExpiringTimeTracker instance creation or the last reset(), whichever is more recent. + */ + uint32_t elapsedTime() const; bool expired() const; + void reset(); + void reset(const uint32_t newDuration); + void reset(const calculatorType newDurationCalculator); + explicit operator bool() const; private: - uint32_t _duration; + bool useCalculator = false; + calculatorType _durationCalculator; + + void setTimeout(const uint32_t newUserTimeout); }; #endif diff --git a/libraries/ESP8266WiFiMesh/src/FloodingMesh.cpp b/libraries/ESP8266WiFiMesh/src/FloodingMesh.cpp index 9b0280938f..1f88dadf50 100644 --- a/libraries/ESP8266WiFiMesh/src/FloodingMesh.cpp +++ b/libraries/ESP8266WiFiMesh/src/FloodingMesh.cpp @@ -40,13 +40,16 @@ char FloodingMesh::_metadataDelimiter = 23; void floodingMeshDelay(uint32_t durationMs) { - uint32_t startingTime = millis(); - - while(millis() - startingTime < durationMs) + ExpiringTimeTracker timeout(durationMs); + + do { + // We want to delay before performMeshMaintenance() so background tasks can be managed first. + // Initial while combined with YieldAndDelayMs polledTimeout::YieldPolicy is not suitable since the delay then occurs before evaluating the condition (meaning durationMs = 1 never executes the loop interior). delay(1); FloodingMesh::performMeshMaintenance(); } + while(!timeout); } FloodingMesh::FloodingMesh(messageHandlerType messageHandler, const String &meshPassword, const uint8_t espnowEncryptedConnectionKey[EspnowProtocolInterpreter::espnowEncryptedConnectionKeyLength], @@ -428,9 +431,9 @@ String FloodingMesh::_defaultRequestHandler(const String &request, MeshBackendBa * @param meshInstance The MeshBackendBase instance that called the function. * @return The status code resulting from the response, as an int */ -transmission_status_t FloodingMesh::_defaultResponseHandler(const String &response, MeshBackendBase &meshInstance) +TransmissionStatusType FloodingMesh::_defaultResponseHandler(const String &response, MeshBackendBase &meshInstance) { - transmission_status_t statusCode = TS_TRANSMISSION_COMPLETE; + TransmissionStatusType statusCode = TransmissionStatusType::TRANSMISSION_COMPLETE; getEspnowMeshBackend().warningPrint(String(F("WARNING! Response to FloodingMesh broadcast received, but none is expected!"))); @@ -503,26 +506,22 @@ bool FloodingMesh::_defaultBroadcastFilter(String &firstTransmission, EspnowMesh { return false; // Broadcast is for another mesh network } - else - { - int32_t messageIDEndIndex = firstTransmission.indexOf(metadataDelimiter(), metadataEndIndex + 1); - - if(messageIDEndIndex == -1) - return false; // metadataDelimiter not found - uint64_t messageID = TypeCast::stringToUint64(firstTransmission.substring(metadataEndIndex + 1, messageIDEndIndex)); + int32_t messageIDEndIndex = firstTransmission.indexOf(metadataDelimiter(), metadataEndIndex + 1); - if(insertPreliminaryMessageID(messageID)) - { - // Add broadcast identifier to stored message and mark as accepted broadcast. - firstTransmission = String(metadataDelimiter()) + firstTransmission; - return true; - } - else - { - return false; // Broadcast has already been received the maximum number of times - } + if(messageIDEndIndex == -1) + return false; // metadataDelimiter not found + + uint64_t messageID = TypeCast::stringToUint64(firstTransmission.substring(metadataEndIndex + 1, messageIDEndIndex)); + + if(insertPreliminaryMessageID(messageID)) + { + // Add broadcast identifier to stored message and mark as accepted broadcast. + firstTransmission = String(metadataDelimiter()) + firstTransmission; + return true; } + + return false; // Broadcast has already been received the maximum number of times } /** diff --git a/libraries/ESP8266WiFiMesh/src/FloodingMesh.h b/libraries/ESP8266WiFiMesh/src/FloodingMesh.h index b35e828b65..71c6621784 100644 --- a/libraries/ESP8266WiFiMesh/src/FloodingMesh.h +++ b/libraries/ESP8266WiFiMesh/src/FloodingMesh.h @@ -27,7 +27,6 @@ #include "EspnowMeshBackend.h" #include -#include #include /** @@ -45,8 +44,8 @@ class FloodingMesh { protected: - typedef std::function messageHandlerType; - typedef std::unordered_map::iterator messageQueueElementType; + using messageHandlerType = std::function; + using messageQueueElementType = std::map::iterator; public: @@ -61,7 +60,7 @@ class FloodingMesh { * @param ssidSuffix The suffix (last part) of the node SSID. * @param verboseMode Determines if we should print the events occurring in the library to Serial. Off by default. This setting is shared by all EspnowMeshBackend instances. * @param meshWiFiChannel The WiFi channel used by the mesh network. Valid values are integers from 1 to 13. Defaults to 1. - * WARNING: The ESP8266 has only one WiFi channel, and the the station/client mode is always prioritized for channel selection. + * WARNING: The ESP8266 has only one WiFi channel, and the station/client mode is always prioritized for channel selection. * This can cause problems if several mesh instances exist on the same ESP8266 and use different WiFi channels. * In such a case, whenever the station of one mesh instance connects to an AP, it will silently force the * WiFi channel of any active AP on the ESP8266 to match that of the station. This will cause disconnects and possibly @@ -83,7 +82,7 @@ class FloodingMesh { * @param ssidSuffix The suffix (last part) of the node SSID. * @param verboseMode Determines if we should print the events occurring in the library to Serial. Off by default. This setting is shared by all EspnowMeshBackend instances. * @param meshWiFiChannel The WiFi channel used by the mesh network. Valid values are integers from 1 to 13. Defaults to 1. - * WARNING: The ESP8266 has only one WiFi channel, and the the station/client mode is always prioritized for channel selection. + * WARNING: The ESP8266 has only one WiFi channel, and the station/client mode is always prioritized for channel selection. * This can cause problems if several mesh instances exist on the same ESP8266 and use different WiFi channels. * In such a case, whenever the station of one mesh instance connects to an AP, it will silently force the * WiFi channel of any active AP on the ESP8266 to match that of the station. This will cause disconnects and possibly @@ -321,14 +320,14 @@ class FloodingMesh { uint8_t _originMac[6] = {0}; - std::unordered_map _messageIDs = {}; + std::map _messageIDs = {}; std::queue _messageIdOrder = {}; std::list> _forwardingBacklog = {}; String _macIgnoreList; String _defaultRequestHandler(const String &request, MeshBackendBase &meshInstance); - transmission_status_t _defaultResponseHandler(const String &response, MeshBackendBase &meshInstance); + TransmissionStatusType _defaultResponseHandler(const String &response, MeshBackendBase &meshInstance); void _defaultNetworkFilter(int numberOfNetworks, MeshBackendBase &meshInstance); bool _defaultBroadcastFilter(String &firstTransmission, EspnowMeshBackend &meshInstance); bool _defaultTransmissionOutcomesUpdateHook(MeshBackendBase &meshInstance); diff --git a/libraries/ESP8266WiFiMesh/src/HeapMonitor.cpp b/libraries/ESP8266WiFiMesh/src/HeapMonitor.cpp new file mode 100644 index 0000000000..8bda148355 --- /dev/null +++ b/libraries/ESP8266WiFiMesh/src/HeapMonitor.cpp @@ -0,0 +1,63 @@ +/* + * Copyright (C) 2019 Anders Löfgren + * + * License (MIT license): + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include "HeapMonitor.h" + +HeapMonitor::HeapMonitor(const uint32_t criticalHeapLevel, const uint32_t criticalHeapLevelBuffer) : + _criticalHeapLevel(criticalHeapLevel), _criticalHeapLevelBuffer(criticalHeapLevelBuffer) +{ } + +void HeapMonitor::setCriticalHeapLevel(const uint32_t freeHeapInBytes) +{ + _criticalHeapLevel = freeHeapInBytes; +} + +uint32_t HeapMonitor::getCriticalHeapLevel() const +{ + return _criticalHeapLevel; +} + +void HeapMonitor::setCriticalHeapLevelBuffer(const uint32_t bufferInBytes) +{ + _criticalHeapLevelBuffer = bufferInBytes; +} + +uint32_t HeapMonitor::getCriticalHeapLevelBuffer() const +{ + return _criticalHeapLevelBuffer; +} + +HeapMonitor::HeapStatus HeapMonitor::getHeapStatus() const +{ + HeapStatus heapStatus = HeapStatus::NOMINAL; + + uint32_t freeHeap = ESP.getFreeHeap(); + + if(freeHeap <= getCriticalHeapLevel()) + heapStatus = HeapStatus::CRITICAL; + else if(freeHeap <= getCriticalHeapLevel() + getCriticalHeapLevelBuffer()) + heapStatus = HeapStatus::LIMITED; + + return heapStatus; +} diff --git a/libraries/ESP8266WiFiMesh/src/HeapMonitor.h b/libraries/ESP8266WiFiMesh/src/HeapMonitor.h new file mode 100644 index 0000000000..44550e30d7 --- /dev/null +++ b/libraries/ESP8266WiFiMesh/src/HeapMonitor.h @@ -0,0 +1,66 @@ +/* + * Copyright (C) 2020 Anders Löfgren + * + * License (MIT license): + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#ifndef __ESPHEAPMONITOR_H__ +#define __ESPHEAPMONITOR_H__ + +#include + +class HeapMonitor { + +public: + + enum class HeapStatus + { + NOMINAL = 0, + LIMITED = 1, + CRITICAL = 2 + }; + + HeapMonitor(const uint32_t criticalHeapLevel, const uint32_t criticalHeapLevelBuffer); + + virtual ~HeapMonitor() = default; + + /** + * Set the maximum free heap level in bytes within which free heap size is considered critical. + */ + void setCriticalHeapLevel(const uint32_t freeHeapInBytes); + uint32_t getCriticalHeapLevel() const; + + /** + * Set the buffer of the critical heap level, within which free heap size is considered limited. + */ + void setCriticalHeapLevelBuffer(const uint32_t bufferInBytes); + uint32_t getCriticalHeapLevelBuffer() const; + + HeapStatus getHeapStatus() const; + +private: + + uint32_t _criticalHeapLevel; + uint32_t _criticalHeapLevelBuffer; + +}; + +#endif diff --git a/libraries/ESP8266WiFiMesh/src/MeshBackendBase.cpp b/libraries/ESP8266WiFiMesh/src/MeshBackendBase.cpp index aafc45ab05..33ed8b5650 100644 --- a/libraries/ESP8266WiFiMesh/src/MeshBackendBase.cpp +++ b/libraries/ESP8266WiFiMesh/src/MeshBackendBase.cpp @@ -26,11 +26,11 @@ namespace TypeCast = MeshTypeConversionFunctions; MeshBackendBase *MeshBackendBase::apController = nullptr; -bool MeshBackendBase::_scanMutex = false; +std::shared_ptr MeshBackendBase::_scanMutex = std::make_shared(false); bool MeshBackendBase::_printWarnings = true; -MeshBackendBase::MeshBackendBase(requestHandlerType requestHandler, responseHandlerType responseHandler, networkFilterType networkFilter, mesh_backend_t classType) +MeshBackendBase::MeshBackendBase(requestHandlerType requestHandler, responseHandlerType responseHandler, networkFilterType networkFilter, MeshBackendType classType) { setRequestHandler(requestHandler); setResponseHandler(responseHandler); @@ -43,12 +43,12 @@ MeshBackendBase::~MeshBackendBase() deactivateControlledAP(); } -void MeshBackendBase::setClassType(mesh_backend_t classType) +void MeshBackendBase::setClassType(MeshBackendType classType) { _classType = classType; } -mesh_backend_t MeshBackendBase::getClassType() {return _classType;} +MeshBackendType MeshBackendBase::getClassType() {return _classType;} void MeshBackendBase::activateAP() { @@ -115,7 +115,9 @@ bool MeshBackendBase::isAPController() void MeshBackendBase::setWiFiChannel(uint8 newWiFiChannel) { - assert(1 <= newWiFiChannel && newWiFiChannel <= 13); + wifi_country_t wifiCountry; + wifi_get_country(&wifiCountry); // Note: Should return 0 on success and -1 on failure, but always seems to return 1. Possibly broken API. Channels 1 to 13 are the default limits. + assert(wifiCountry.schan <= newWiFiChannel && newWiFiChannel <= wifiCountry.schan + wifiCountry.nchan - 1); _meshWiFiChannel = newWiFiChannel; @@ -248,10 +250,10 @@ bool MeshBackendBase::latestTransmissionSuccessfulBase(const std::vector requestHandlerType; - typedef std::function responseHandlerType; - typedef std::function networkFilterType; - typedef std::function transmissionOutcomesUpdateHookType; + using requestHandlerType = std::function ; + using responseHandlerType = std::function; + using networkFilterType = std::function; + using transmissionOutcomesUpdateHookType = std::function; public: - MeshBackendBase(requestHandlerType requestHandler, responseHandlerType responseHandler, networkFilterType networkFilter, mesh_backend_t classType); + MeshBackendBase(requestHandlerType requestHandler, responseHandlerType responseHandler, networkFilterType networkFilter, MeshBackendType classType); virtual ~MeshBackendBase(); @@ -109,13 +109,13 @@ class MeshBackendBase { * Will also change the WiFi channel for the active AP (via an AP restart) * if this MeshBackendBase instance is the current AP controller and it is possible to change channel. * - * WARNING: The ESP8266 has only one WiFi channel, and the the station/client mode is always prioritized for channel selection. + * WARNING: The ESP8266 has only one WiFi channel, and the station/client mode is always prioritized for channel selection. * This can cause problems if several MeshBackendBase instances exist on the same ESP8266 and use different WiFi channels. * In such a case, whenever the station of one MeshBackendBase instance connects to an AP, it will silently force the * WiFi channel of any active AP on the ESP8266 to match that of the station. This will cause disconnects and possibly * make it impossible for other stations to detect the APs whose WiFi channels have changed. * - * @param newWiFiChannel The WiFi channel to change to. Valid values are integers from 1 to 13. + * @param newWiFiChannel The WiFi channel to change to. Valid values are determined by wifi_get_country, usually integers from 1 to 11 or 1 to 13. * */ void setWiFiChannel(uint8 newWiFiChannel); @@ -284,14 +284,14 @@ class MeshBackendBase { */ static void warningPrint(const String &stringToPrint, bool newline = true); - mesh_backend_t getClassType(); + MeshBackendType getClassType(); protected: /** * @param latestTransmissionOutcomes The transmission outcomes vector to check. * - * @return True if latest transmission was successful (i.e. latestTransmissionOutcomes is not empty and all entries have transmissionStatus TS_TRANSMISSION_COMPLETE). False otherwise. + * @return True if latest transmission was successful (i.e. latestTransmissionOutcomes is not empty and all entries have transmissionStatus TransmissionStatusType::TRANSMISSION_COMPLETE). False otherwise. */ static bool latestTransmissionSuccessfulBase(const std::vector &latestTransmissionOutcomes); @@ -310,13 +310,13 @@ class MeshBackendBase { */ virtual void deactivateAPHook(); - void setClassType(mesh_backend_t classType); + void setClassType(MeshBackendType classType); - static bool _scanMutex; + static std::shared_ptr _scanMutex; private: - mesh_backend_t _classType; + MeshBackendType _classType; static MeshBackendBase *apController; diff --git a/libraries/ESP8266WiFiMesh/src/MessageData.cpp b/libraries/ESP8266WiFiMesh/src/MessageData.cpp index 721792433a..6e646f303f 100644 --- a/libraries/ESP8266WiFiMesh/src/MessageData.cpp +++ b/libraries/ESP8266WiFiMesh/src/MessageData.cpp @@ -28,15 +28,15 @@ #include MessageData::MessageData(String &message, uint8_t transmissionsRemaining, uint32_t creationTimeMs) : - TimeTracker(creationTimeMs) + _timeTracker(creationTimeMs) { _transmissionsExpected = transmissionsRemaining + 1; _totalMessage += message; - _transmissionsReceived++; + ++_transmissionsReceived; } MessageData::MessageData(uint8_t *initialTransmission, uint8_t transmissionLength, uint32_t creationTimeMs) : - TimeTracker(creationTimeMs) + _timeTracker(creationTimeMs) { _transmissionsExpected = EspnowProtocolInterpreter::espnowGetTransmissionsRemaining(initialTransmission) + 1; addToMessage(initialTransmission, transmissionLength); @@ -49,7 +49,7 @@ bool MessageData::addToMessage(uint8_t *transmission, uint8_t transmissionLength String message = EspnowProtocolInterpreter::espnowGetMessageContent(transmission, transmissionLength); assert(message.length() <= EspnowMeshBackend::getMaxMessageBytesPerTransmission()); // Should catch some cases where transmission is not null terminated. _totalMessage += message; - _transmissionsReceived++; + ++_transmissionsReceived; return true; } @@ -75,3 +75,5 @@ String MessageData::getTotalMessage() { return _totalMessage; } + +const TimeTracker &MessageData::getTimeTracker() const { return _timeTracker; } diff --git a/libraries/ESP8266WiFiMesh/src/MessageData.h b/libraries/ESP8266WiFiMesh/src/MessageData.h index 7dc5adac2a..4b02fa295b 100644 --- a/libraries/ESP8266WiFiMesh/src/MessageData.h +++ b/libraries/ESP8266WiFiMesh/src/MessageData.h @@ -28,7 +28,7 @@ #include "TimeTracker.h" #include -class MessageData : public TimeTracker { +class MessageData { public: @@ -43,9 +43,11 @@ class MessageData : public TimeTracker { uint8_t getTransmissionsExpected(); uint8_t getTransmissionsRemaining(); String getTotalMessage(); + const TimeTracker &getTimeTracker() const; private: + TimeTracker _timeTracker; uint8_t _transmissionsReceived = 0; uint8_t _transmissionsExpected; String _totalMessage; diff --git a/libraries/ESP8266WiFiMesh/src/MutexTracker.cpp b/libraries/ESP8266WiFiMesh/src/MutexTracker.cpp index c864ea76a9..e6d2a6302c 100644 --- a/libraries/ESP8266WiFiMesh/src/MutexTracker.cpp +++ b/libraries/ESP8266WiFiMesh/src/MutexTracker.cpp @@ -24,19 +24,19 @@ #include "MutexTracker.h" -bool MutexTracker::_captureBan = false; +std::shared_ptr MutexTracker::_captureBan = std::make_shared(false); -bool &MutexTracker::captureBan() +std::shared_ptr MutexTracker::captureBan() { return _captureBan; } -MutexTracker::MutexTracker(bool &mutexToCapture) +MutexTracker::MutexTracker(const std::shared_ptr &mutexToCapture) { attemptMutexCapture(mutexToCapture); } -MutexTracker::MutexTracker(bool &mutexToCapture, std::function destructorHook) : MutexTracker(mutexToCapture) +MutexTracker::MutexTracker(const std::shared_ptr &mutexToCapture, const std::function destructorHook) : MutexTracker(mutexToCapture) { _destructorHook = destructorHook; } @@ -47,12 +47,25 @@ MutexTracker::~MutexTracker() _destructorHook(); } -bool MutexTracker::mutexCaptured() +bool MutexTracker::mutexFree(const std::shared_ptr &mutex) { - if(_capturedMutex) + if(mutex != nullptr && !(*mutex)) return true; - else - return false; + + return false; +} + +bool MutexTracker::mutexCaptured(const std::shared_ptr &mutex) +{ + if(mutex != nullptr && (*mutex)) + return true; + + return false; +} + +bool MutexTracker::mutexCaptured() const +{ + return mutexCaptured(_capturedMutex); } void MutexTracker::releaseMutex() @@ -60,20 +73,18 @@ void MutexTracker::releaseMutex() if(mutexCaptured()) { *_capturedMutex = false; - _capturedMutex = nullptr; + _capturedMutex.reset(); } } -bool MutexTracker::attemptMutexCapture(bool &mutexToCapture) +bool MutexTracker::attemptMutexCapture(const std::shared_ptr &mutexToCapture) { - if(!captureBan() && !mutexToCapture) + if(mutexFree(captureBan()) && mutexFree(mutexToCapture)) { - _capturedMutex = &mutexToCapture; + _capturedMutex = mutexToCapture; *_capturedMutex = true; return true; } - else - { - return false; - } + + return false; } diff --git a/libraries/ESP8266WiFiMesh/src/MutexTracker.h b/libraries/ESP8266WiFiMesh/src/MutexTracker.h index 6e70267719..d28d266952 100644 --- a/libraries/ESP8266WiFiMesh/src/MutexTracker.h +++ b/libraries/ESP8266WiFiMesh/src/MutexTracker.h @@ -26,6 +26,7 @@ #define __MUTEXTRACKER_H__ #include +#include /** * A SLIM (Scope LImited Manager)/Scope-Bound Resource Management/RAII class to manage the state of a mutex. @@ -39,23 +40,23 @@ class MutexTracker * Set to false by default. * captureBan can be managed by MutexTracker like any other mutex. */ - static bool &captureBan(); + static std::shared_ptr captureBan(); /** * Attempts to capture the mutex. Use the mutexCaptured() method to check success. */ - MutexTracker(bool &mutexToCapture); + MutexTracker(const std::shared_ptr &mutexToCapture); /** * Attempts to capture the mutex. Use the mutexCaptured() method to check success. * * @param destructorHook A function to hook into the MutexTracker destructor. Will be called when the MutexTracker instance is being destroyed, after the mutex has been released. */ - MutexTracker(bool &mutexToCapture, std::function destructorHook); + MutexTracker(const std::shared_ptr &mutexToCapture, const std::function destructorHook); ~MutexTracker(); - bool mutexCaptured(); + bool mutexCaptured() const; /** * Set the mutex free to roam the binary plains, giving new MutexTrackers a chance to capture it. @@ -64,17 +65,20 @@ class MutexTracker private: - static bool _captureBan; + static std::shared_ptr _captureBan; - bool *_capturedMutex = nullptr; + std::shared_ptr _capturedMutex; std::function _destructorHook = [](){ }; + static bool mutexFree(const std::shared_ptr &mutex); + static bool mutexCaptured(const std::shared_ptr &mutex); + /** * Attempt to capture the mutex. * - * @return True if mutex was caught (meaning no other instance is holding the mutex). False otherwise. + * @return True if mutex was caught (meaning it exists and no other instance is holding the mutex). False otherwise. */ - bool attemptMutexCapture(bool &mutexToCapture); + bool attemptMutexCapture(const std::shared_ptr &mutexToCapture); }; #endif diff --git a/libraries/ESP8266WiFiMesh/src/NetworkInfoBase.cpp b/libraries/ESP8266WiFiMesh/src/NetworkInfoBase.cpp index 2cfed766e6..16c2855a0a 100644 --- a/libraries/ESP8266WiFiMesh/src/NetworkInfoBase.cpp +++ b/libraries/ESP8266WiFiMesh/src/NetworkInfoBase.cpp @@ -40,7 +40,7 @@ void NetworkInfoBase::storeBSSID(const uint8_t newBSSID[6]) _BSSID = _bssidArray; } - for(int i = 0; i < 6; i++) + for(int i = 0; i < 6; ++i) { _BSSID[i] = newBSSID[i]; } diff --git a/libraries/ESP8266WiFiMesh/src/RequestData.cpp b/libraries/ESP8266WiFiMesh/src/RequestData.cpp index a45b460514..dd9dff2574 100644 --- a/libraries/ESP8266WiFiMesh/src/RequestData.cpp +++ b/libraries/ESP8266WiFiMesh/src/RequestData.cpp @@ -25,8 +25,9 @@ #include "RequestData.h" RequestData::RequestData(EspnowMeshBackend &meshInstance, uint32_t creationTimeMs) : - TimeTracker(creationTimeMs), _meshInstance(meshInstance) + _timeTracker(creationTimeMs), _meshInstance(meshInstance) { } -void RequestData::setMeshInstance(EspnowMeshBackend &meshInstance) { _meshInstance = meshInstance; } -EspnowMeshBackend &RequestData::getMeshInstance() { return _meshInstance; } +void RequestData::setMeshInstance(const EspnowMeshBackend &meshInstance) { _meshInstance = meshInstance; } +EspnowMeshBackend &RequestData::getMeshInstance() const { return _meshInstance; } +const TimeTracker &RequestData::getTimeTracker() const { return _timeTracker; } diff --git a/libraries/ESP8266WiFiMesh/src/RequestData.h b/libraries/ESP8266WiFiMesh/src/RequestData.h index 77f373245d..70b4a1fd14 100644 --- a/libraries/ESP8266WiFiMesh/src/RequestData.h +++ b/libraries/ESP8266WiFiMesh/src/RequestData.h @@ -30,17 +30,19 @@ class EspnowMeshBackend; -class RequestData : public TimeTracker { +class RequestData { public: RequestData(EspnowMeshBackend &meshInstance, uint32_t creationTimeMs = millis()); - void setMeshInstance(EspnowMeshBackend &meshInstance); - EspnowMeshBackend &getMeshInstance(); + void setMeshInstance(const EspnowMeshBackend &meshInstance); + EspnowMeshBackend &getMeshInstance() const; + const TimeTracker &getTimeTracker() const; private: + TimeTracker _timeTracker; EspnowMeshBackend &_meshInstance; }; diff --git a/libraries/ESP8266WiFiMesh/src/ResponseData.cpp b/libraries/ESP8266WiFiMesh/src/ResponseData.cpp index b3678d2466..537056ea25 100644 --- a/libraries/ESP8266WiFiMesh/src/ResponseData.cpp +++ b/libraries/ESP8266WiFiMesh/src/ResponseData.cpp @@ -25,13 +25,13 @@ #include "ResponseData.h" ResponseData::ResponseData(const String &message, const uint8_t recipientMac[6], uint64_t requestID, uint32_t creationTimeMs) : - TimeTracker(creationTimeMs), _message(message), _requestID(requestID) + _timeTracker(creationTimeMs), _message(message), _requestID(requestID) { storeRecipientMac(recipientMac); } ResponseData::ResponseData(const ResponseData &other) - : TimeTracker(other), _message(other.getMessage()), _requestID(other.getRequestID()) + : _timeTracker(other.getTimeTracker()), _message(other.getMessage()), _requestID(other.getRequestID()) { storeRecipientMac(other.getRecipientMac()); } @@ -40,7 +40,7 @@ ResponseData & ResponseData::operator=(const ResponseData &other) { if(this != &other) { - TimeTracker::operator=(other); + _timeTracker = other.getTimeTracker(); _message = other.getMessage(); _requestID = other.getRequestID(); storeRecipientMac(other.getRecipientMac()); @@ -51,21 +51,20 @@ ResponseData & ResponseData::operator=(const ResponseData &other) void ResponseData::storeRecipientMac(const uint8_t newRecipientMac[6]) { - if(newRecipientMac != nullptr) + if(newRecipientMac == nullptr) { - if(_recipientMac == nullptr) - { - _recipientMac = _recipientMacArray; - } - - for(int i = 0; i < 6; i++) - { - _recipientMac[i] = newRecipientMac[i]; - } + _recipientMac = nullptr; + return; } - else + + if(_recipientMac == nullptr) { - _recipientMac = nullptr; + _recipientMac = _recipientMacArray; + } + + for(int i = 0; i < 6; ++i) + { + _recipientMac[i] = newRecipientMac[i]; } } @@ -77,3 +76,5 @@ String ResponseData::getMessage() const { return _message; } void ResponseData::setRequestID(uint64_t requestID) { _requestID = requestID; } uint64_t ResponseData::getRequestID() const { return _requestID; } + +const TimeTracker &ResponseData::getTimeTracker() const { return _timeTracker; } diff --git a/libraries/ESP8266WiFiMesh/src/ResponseData.h b/libraries/ESP8266WiFiMesh/src/ResponseData.h index 17b0bcc7d6..982222d706 100644 --- a/libraries/ESP8266WiFiMesh/src/ResponseData.h +++ b/libraries/ESP8266WiFiMesh/src/ResponseData.h @@ -28,7 +28,7 @@ #include "TimeTracker.h" #include -class ResponseData : public TimeTracker { +class ResponseData { public: @@ -44,11 +44,15 @@ class ResponseData : public TimeTracker { String getMessage() const; void setRequestID(uint64_t requestID); - uint64_t getRequestID() const; + uint64_t getRequestID() const; + + const TimeTracker &getTimeTracker() const; private: void storeRecipientMac(const uint8_t newRecipientMac[6]); + + TimeTracker _timeTracker; uint8_t _recipientMacArray[6] {0}; uint8_t *_recipientMac = nullptr; diff --git a/libraries/ESP8266WiFiMesh/src/TcpIpMeshBackend.cpp b/libraries/ESP8266WiFiMesh/src/TcpIpMeshBackend.cpp index 0b1e6dfa8b..34bb7bc900 100644 --- a/libraries/ESP8266WiFiMesh/src/TcpIpMeshBackend.cpp +++ b/libraries/ESP8266WiFiMesh/src/TcpIpMeshBackend.cpp @@ -32,8 +32,8 @@ namespace const IPAddress TcpIpMeshBackend::emptyIP; -bool TcpIpMeshBackend::_tcpIpTransmissionMutex = false; -bool TcpIpMeshBackend::_tcpIpConnectionQueueMutex = false; +std::shared_ptr TcpIpMeshBackend::_tcpIpTransmissionMutex = std::make_shared(false); +std::shared_ptr TcpIpMeshBackend::_tcpIpConnectionQueueMutex = std::make_shared(false); String TcpIpMeshBackend::lastSSID; bool TcpIpMeshBackend::staticIPActivated = false; @@ -51,7 +51,7 @@ std::vector TcpIpMeshBackend::_latestTransmissionOutcomes = TcpIpMeshBackend::TcpIpMeshBackend(requestHandlerType requestHandler, responseHandlerType responseHandler, networkFilterType networkFilter, const String &meshPassword, const String &ssidPrefix, const String &ssidSuffix, bool verboseMode, uint8 meshWiFiChannel, uint16_t serverPort) - : MeshBackendBase(requestHandler, responseHandler, networkFilter, MB_TCP_IP), _server(serverPort) + : MeshBackendBase(requestHandler, responseHandler, networkFilter, MeshBackendType::TCP_IP), _server(serverPort) { setSSID(ssidPrefix, emptyString, ssidSuffix); setMeshPassword(meshPassword); @@ -111,7 +111,7 @@ void TcpIpMeshBackend::deactivateAPHook() _server.stop(); } -bool TcpIpMeshBackend::transmissionInProgress(){return _tcpIpTransmissionMutex;} +bool TcpIpMeshBackend::transmissionInProgress(){return *_tcpIpTransmissionMutex;} void TcpIpMeshBackend::setTemporaryMessage(const String &newTemporaryMessage) {_temporaryMessage = newTemporaryMessage;} String TcpIpMeshBackend::getTemporaryMessage() {return _temporaryMessage;} @@ -180,12 +180,12 @@ void TcpIpMeshBackend::setMaxAPStations(uint8_t maxAPStations) bool TcpIpMeshBackend::getMaxAPStations() {return _maxAPStations;} -void TcpIpMeshBackend::setConnectionAttemptTimeout(int32_t connectionAttemptTimeoutMs) +void TcpIpMeshBackend::setConnectionAttemptTimeout(uint32_t connectionAttemptTimeoutMs) { _connectionAttemptTimeoutMs = connectionAttemptTimeoutMs; } -int32_t TcpIpMeshBackend::getConnectionAttemptTimeout() {return _connectionAttemptTimeoutMs;} +uint32_t TcpIpMeshBackend::getConnectionAttemptTimeout() {return _connectionAttemptTimeoutMs;} void TcpIpMeshBackend::setStationModeTimeout(int stationModeTimeoutMs) { @@ -220,12 +220,11 @@ void TcpIpMeshBackend::fullStop(WiFiClient &currClient) */ bool TcpIpMeshBackend::waitForClientTransmission(WiFiClient &currClient, uint32_t maxWait) { - uint32_t connectionStartTime = millis(); - uint32_t waitingTime = millis() - connectionStartTime; - while(currClient.connected() && !currClient.available() && waitingTime < maxWait) + ExpiringTimeTracker timeout(maxWait); + + while(currClient.connected() && !currClient.available() && !timeout) { delay(1); - waitingTime = millis() - connectionStartTime; } /* Return false if the client isn't ready to communicate */ @@ -246,7 +245,7 @@ bool TcpIpMeshBackend::waitForClientTransmission(WiFiClient &currClient, uint32_ * @return A status code based on the outcome of the exchange. * */ -transmission_status_t TcpIpMeshBackend::exchangeInfo(WiFiClient &currClient) +TransmissionStatusType TcpIpMeshBackend::exchangeInfo(WiFiClient &currClient) { verboseModePrint(String(F("Transmitting"))); @@ -256,13 +255,13 @@ transmission_status_t TcpIpMeshBackend::exchangeInfo(WiFiClient &currClient) if (!waitForClientTransmission(currClient, _stationModeTimeoutMs)) { fullStop(currClient); - return TS_CONNECTION_FAILED; + return TransmissionStatusType::CONNECTION_FAILED; } if (!currClient.available()) { verboseModePrint(F("No response!")); - return TS_TRANSMISSION_FAILED; // WiFi.status() != WL_DISCONNECTED so we do not want to use fullStop(currClient) here since that would force the node to scan for WiFi networks. + return TransmissionStatusType::TRANSMISSION_FAILED; // WiFi.status() != WL_DISCONNECTED so we do not want to use fullStop(currClient) here since that would force the node to scan for WiFi networks. } String response = currClient.readStringUntil('\r'); @@ -278,7 +277,7 @@ transmission_status_t TcpIpMeshBackend::exchangeInfo(WiFiClient &currClient) * * @return A status code based on the outcome of the data transfer attempt. */ -transmission_status_t TcpIpMeshBackend::attemptDataTransfer() +TransmissionStatusType TcpIpMeshBackend::attemptDataTransfer() { // Unlike WiFi.mode(WIFI_AP);, WiFi.mode(WIFI_AP_STA); allows us to stay connected to the AP we connected to in STA mode, at the same time as we can receive connections from other stations. // We cannot send data to the AP in STA_AP mode though, that requires STA mode. @@ -286,7 +285,7 @@ transmission_status_t TcpIpMeshBackend::attemptDataTransfer() WiFiMode_t storedWiFiMode = WiFi.getMode(); WiFi.mode(WIFI_STA); delay(1); - transmission_status_t transmissionOutcome = attemptDataTransferKernel(); + TransmissionStatusType transmissionOutcome = attemptDataTransferKernel(); WiFi.mode(storedWiFiMode); delay(1); @@ -298,7 +297,7 @@ transmission_status_t TcpIpMeshBackend::attemptDataTransfer() * * @return A status code based on the outcome of the data transfer attempt. */ -transmission_status_t TcpIpMeshBackend::attemptDataTransferKernel() +TransmissionStatusType TcpIpMeshBackend::attemptDataTransferKernel() { WiFiClient currClient; currClient.setTimeout(_stationModeTimeoutMs); @@ -308,11 +307,11 @@ transmission_status_t TcpIpMeshBackend::attemptDataTransferKernel() { fullStop(currClient); verboseModePrint(F("Server unavailable")); - return TS_CONNECTION_FAILED; + return TransmissionStatusType::CONNECTION_FAILED; } - transmission_status_t transmissionOutcome = exchangeInfo(currClient); - if (transmissionOutcome <= 0) + TransmissionStatusType transmissionOutcome = exchangeInfo(currClient); + if (static_cast(transmissionOutcome) <= 0) { verboseModePrint(F("Transmission failed during exchangeInfo.")); return transmissionOutcome; @@ -343,7 +342,7 @@ void TcpIpMeshBackend::initiateConnectionToAP(const String &targetSSID, int targ * @return A status code based on the outcome of the connection and data transfer process. * */ -transmission_status_t TcpIpMeshBackend::connectToNode(const String &targetSSID, int targetChannel, uint8_t *targetBSSID) +TransmissionStatusType TcpIpMeshBackend::connectToNode(const String &targetSSID, int targetChannel, uint8_t *targetBSSID) { if(staticIPActivated && !lastSSID.isEmpty() && lastSSID != targetSSID) // So we only do this once per connection, in case there is a performance impact. { @@ -366,37 +365,36 @@ transmission_status_t TcpIpMeshBackend::connectToNode(const String &targetSSID, verboseModePrint(F("Connecting... "), false); initiateConnectionToAP(targetSSID, targetChannel, targetBSSID); - int connectionStartTime = millis(); int attemptNumber = 1; + ExpiringTimeTracker connectionAttemptTimeout([this](){ return _connectionAttemptTimeoutMs; }); - int waitingTime = millis() - connectionStartTime; - while((WiFi.status() == WL_DISCONNECTED) && waitingTime <= _connectionAttemptTimeoutMs) + while((WiFi.status() == WL_DISCONNECTED) && !connectionAttemptTimeout) { - if(waitingTime > attemptNumber * _connectionAttemptTimeoutMs) // _connectionAttemptTimeoutMs can be replaced (lowered) if you want to limit the time allowed for each connection attempt. + if(connectionAttemptTimeout.elapsedTime() > attemptNumber * _connectionAttemptTimeoutMs) // _connectionAttemptTimeoutMs can be replaced (lowered) if you want to limit the time allowed for each connection attempt. { verboseModePrint(F("... "), false); WiFi.disconnect(); yield(); initiateConnectionToAP(targetSSID, targetChannel, targetBSSID); - attemptNumber++; + ++attemptNumber; } + delay(1); - waitingTime = millis() - connectionStartTime; } - verboseModePrint(String(waitingTime)); + verboseModePrint(String(connectionAttemptTimeout.elapsedTime())); /* If the connection timed out */ if (WiFi.status() != WL_CONNECTED) { verboseModePrint(F("Timeout")); - return TS_CONNECTION_FAILED; + return TransmissionStatusType::CONNECTION_FAILED; } return attemptDataTransfer(); } -transmission_status_t TcpIpMeshBackend::initiateTransmission(const TcpIpNetworkInfo &recipientInfo) +TransmissionStatusType TcpIpMeshBackend::initiateTransmission(const TcpIpNetworkInfo &recipientInfo) { WiFi.disconnect(); yield(); @@ -452,7 +450,7 @@ void TcpIpMeshBackend::attemptTransmission(const String &message, bool scan, boo if(WiFi.status() == WL_CONNECTED) { - transmission_status_t transmissionResult = attemptDataTransfer(); + TransmissionStatusType transmissionResult = attemptDataTransfer(); latestTransmissionOutcomes().push_back(TransmissionOutcome(constConnectionQueue().back(), transmissionResult)); getTransmissionOutcomesUpdateHook()(*this); @@ -474,7 +472,7 @@ void TcpIpMeshBackend::attemptTransmission(const String &message, bool scan, boo { for(const TcpIpNetworkInfo ¤tNetwork : constConnectionQueue()) { - transmission_status_t transmissionResult = initiateTransmission(currentNetwork); + TransmissionStatusType transmissionResult = initiateTransmission(currentNetwork); latestTransmissionOutcomes().push_back(TransmissionOutcome{.origin = currentNetwork, .transmissionStatus = transmissionResult}); @@ -492,16 +490,16 @@ void TcpIpMeshBackend::attemptTransmission(const String &message, bool scan, boo attemptTransmission(message, scan, scanAllWiFiChannels, true, false); } -transmission_status_t TcpIpMeshBackend::attemptTransmission(const String &message, const TcpIpNetworkInfo &recipientInfo, bool concludingDisconnect, bool initialDisconnect) +TransmissionStatusType TcpIpMeshBackend::attemptTransmission(const String &message, const TcpIpNetworkInfo &recipientInfo, bool concludingDisconnect, bool initialDisconnect) { MutexTracker mutexTracker(_tcpIpTransmissionMutex); if(!mutexTracker.mutexCaptured()) { 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 TransmissionStatusType::CONNECTION_FAILED; } - transmission_status_t transmissionResult = TS_CONNECTION_FAILED; + TransmissionStatusType transmissionResult = TransmissionStatusType::CONNECTION_FAILED; setTemporaryMessage(message); if(initialDisconnect) diff --git a/libraries/ESP8266WiFiMesh/src/TcpIpMeshBackend.h b/libraries/ESP8266WiFiMesh/src/TcpIpMeshBackend.h index 632bd075d8..4bc6dfed5e 100644 --- a/libraries/ESP8266WiFiMesh/src/TcpIpMeshBackend.h +++ b/libraries/ESP8266WiFiMesh/src/TcpIpMeshBackend.h @@ -43,14 +43,14 @@ class TcpIpMeshBackend : public MeshBackendBase { * @param requestHandler The callback handler for dealing with received requests. Takes a string as an argument which * is the request string received from another node and returns the string to send back. * @param responseHandler The callback handler for dealing with received responses. Takes a string as an argument which - * is the response string received from another node. Returns a transmission status code as a transmission_status_t. + * is the response string received from another node. Returns a transmission status code as a TransmissionStatusType. * @param networkFilter The callback handler for deciding which WiFi networks to connect to. * @param meshPassword The WiFi password for the mesh network. * @param ssidPrefix The prefix (first part) of the node SSID. * @param ssidSuffix The suffix (last part) of the node SSID. * @param verboseMode Determines if we should print the events occurring in the library to Serial. Off by default. This setting is separate for each TcpIpMeshBackend instance. * @param meshWiFiChannel The WiFi channel used by the mesh network. Valid values are integers from 1 to 13. Defaults to 1. - * WARNING: The ESP8266 has only one WiFi channel, and the the station/client mode is always prioritized for channel selection. + * WARNING: The ESP8266 has only one WiFi channel, and the station/client mode is always prioritized for channel selection. * This can cause problems if several mesh instances exist on the same ESP8266 and use different WiFi channels. * In such a case, whenever the station of one mesh instance connects to an AP, it will silently force the * WiFi channel of any active AP on the ESP8266 to match that of the station. This will cause disconnects and possibly @@ -91,7 +91,7 @@ class TcpIpMeshBackend : public MeshBackendBase { static std::vector & latestTransmissionOutcomes(); /** - * @return True if latest transmission was successful (i.e. latestTransmissionOutcomes is not empty and all entries have transmissionStatus TS_TRANSMISSION_COMPLETE). False otherwise. + * @return True if latest transmission was successful (i.e. latestTransmissionOutcomes is not empty and all entries have transmissionStatus TransmissionStatusType::TRANSMISSION_COMPLETE). False otherwise. * The result is unique for each mesh backend. */ static bool latestTransmissionSuccessful(); @@ -124,7 +124,7 @@ class TcpIpMeshBackend : public MeshBackendBase { * * Note that if wifiChannel and BSSID are missing from recipientInfo, connection time will be longer. */ - transmission_status_t attemptTransmission(const String &message, const TcpIpNetworkInfo &recipientInfo, bool concludingDisconnect = true, bool initialDisconnect = false); + TransmissionStatusType attemptTransmission(const String &message, const TcpIpNetworkInfo &recipientInfo, bool concludingDisconnect = true, bool initialDisconnect = false); /** * If any clients are connected, accept their requests and call the requestHandler function for each one. @@ -183,8 +183,8 @@ class TcpIpMeshBackend : public MeshBackendBase { * * @param connectionAttemptTimeoutMs The timeout for each connection attempt, in milliseconds. */ - void setConnectionAttemptTimeout(int32_t connectionAttemptTimeoutMs); - int32_t getConnectionAttemptTimeout(); + void setConnectionAttemptTimeout(uint32_t connectionAttemptTimeoutMs); + uint32_t getConnectionAttemptTimeout(); /** * Set the timeout to use for transmissions when this TcpIpMeshBackend instance acts as a station (i.e. when connected to another AP). @@ -228,12 +228,12 @@ class TcpIpMeshBackend : public MeshBackendBase { /** * Will be true if a transmission initiated by a public method is in progress. */ - static bool _tcpIpTransmissionMutex; + static std::shared_ptr _tcpIpTransmissionMutex; /** * Will be true when the connectionQueue should not be modified. */ - static bool _tcpIpConnectionQueueMutex; + static std::shared_ptr _tcpIpConnectionQueueMutex; /** * Check if there is an ongoing TCP/IP transmission in the library. Used to avoid interrupting transmissions. @@ -257,7 +257,7 @@ class TcpIpMeshBackend : public MeshBackendBase { uint16_t _serverPort; WiFiServer _server; uint8_t _maxAPStations = 4; // Only affects TCP/IP connections, not ESP-NOW connections - int32_t _connectionAttemptTimeoutMs = 10000; + uint32_t _connectionAttemptTimeoutMs = 10000; int _stationModeTimeoutMs = 5000; // int is the type used in the Arduino core for this particular API, not uint32_t, which is why we use int here. uint32_t _apModeTimeoutMs = 4500; @@ -271,12 +271,12 @@ class TcpIpMeshBackend : public MeshBackendBase { void fullStop(WiFiClient &currClient); void initiateConnectionToAP(const String &targetSSID, int targetChannel = NETWORK_INFO_DEFAULT_INT, uint8_t *targetBSSID = NULL); - transmission_status_t connectToNode(const String &targetSSID, int targetChannel = NETWORK_INFO_DEFAULT_INT, uint8_t *targetBSSID = NULL); - transmission_status_t exchangeInfo(WiFiClient &currClient); + TransmissionStatusType connectToNode(const String &targetSSID, int targetChannel = NETWORK_INFO_DEFAULT_INT, uint8_t *targetBSSID = NULL); + TransmissionStatusType exchangeInfo(WiFiClient &currClient); bool waitForClientTransmission(WiFiClient &currClient, uint32_t maxWait); - transmission_status_t attemptDataTransfer(); - transmission_status_t attemptDataTransferKernel(); - transmission_status_t initiateTransmission(const TcpIpNetworkInfo &recipientInfo); + TransmissionStatusType attemptDataTransfer(); + TransmissionStatusType attemptDataTransferKernel(); + TransmissionStatusType initiateTransmission(const TcpIpNetworkInfo &recipientInfo); void enterPostTransmissionState(bool concludingDisconnect); }; diff --git a/libraries/ESP8266WiFiMesh/src/TimeTracker.h b/libraries/ESP8266WiFiMesh/src/TimeTracker.h index 76e5eafe4f..77068228e0 100644 --- a/libraries/ESP8266WiFiMesh/src/TimeTracker.h +++ b/libraries/ESP8266WiFiMesh/src/TimeTracker.h @@ -27,6 +27,7 @@ #include +// Minimal time tracking class. Used instead of other classes like ExpiringTimeTracker when small memory footprint is important and other functionality not required. class TimeTracker { public: diff --git a/libraries/ESP8266WiFiMesh/src/TransmissionOutcome.cpp b/libraries/ESP8266WiFiMesh/src/TransmissionOutcome.cpp index 24c741bca7..5dec1978a7 100644 --- a/libraries/ESP8266WiFiMesh/src/TransmissionOutcome.cpp +++ b/libraries/ESP8266WiFiMesh/src/TransmissionOutcome.cpp @@ -24,13 +24,13 @@ #include "TransmissionOutcome.h" -TransmissionOutcome::TransmissionOutcome(const NetworkInfoBase &origin, transmission_status_t transmissionStatus) +TransmissionOutcome::TransmissionOutcome(const NetworkInfoBase &origin, TransmissionStatusType transmissionStatus) : NetworkInfoBase(origin), _transmissionStatus(transmissionStatus) { } -TransmissionOutcome::TransmissionOutcome(const String &SSID, int32_t wifiChannel, const uint8_t BSSID[6], uint8_t encryptionType, int32_t RSSI, bool isHidden, transmission_status_t transmissionStatus) +TransmissionOutcome::TransmissionOutcome(const String &SSID, int32_t wifiChannel, const uint8_t BSSID[6], uint8_t encryptionType, int32_t RSSI, bool isHidden, TransmissionStatusType transmissionStatus) : NetworkInfoBase(SSID, wifiChannel, BSSID, encryptionType, RSSI, isHidden), _transmissionStatus(transmissionStatus) { } -void TransmissionOutcome::setTransmissionStatus(transmission_status_t transmissionStatus) { _transmissionStatus = transmissionStatus; } -transmission_status_t TransmissionOutcome::transmissionStatus() const { return _transmissionStatus; } +void TransmissionOutcome::setTransmissionStatus(TransmissionStatusType transmissionStatus) { _transmissionStatus = transmissionStatus; } +TransmissionStatusType TransmissionOutcome::transmissionStatus() const { return _transmissionStatus; } diff --git a/libraries/ESP8266WiFiMesh/src/TransmissionOutcome.h b/libraries/ESP8266WiFiMesh/src/TransmissionOutcome.h index 026556d7b7..91d703a8ba 100644 --- a/libraries/ESP8266WiFiMesh/src/TransmissionOutcome.h +++ b/libraries/ESP8266WiFiMesh/src/TransmissionOutcome.h @@ -28,27 +28,27 @@ #include #include "NetworkInfoBase.h" -typedef enum +enum class TransmissionStatusType { - TS_CONNECTION_FAILED = -1, - TS_TRANSMISSION_FAILED = 0, - TS_TRANSMISSION_COMPLETE = 1 -} transmission_status_t; + CONNECTION_FAILED = -1, + TRANSMISSION_FAILED = 0, + TRANSMISSION_COMPLETE = 1 +}; class TransmissionOutcome : public NetworkInfoBase { public: - TransmissionOutcome(const NetworkInfoBase &origin, transmission_status_t transmissionStatus); + TransmissionOutcome(const NetworkInfoBase &origin, TransmissionStatusType transmissionStatus); - TransmissionOutcome(const String &SSID, int32_t wifiChannel, const uint8_t BSSID[6], uint8_t encryptionType, int32_t RSSI, bool isHidden, transmission_status_t transmissionStatus); + TransmissionOutcome(const String &SSID, int32_t wifiChannel, const uint8_t BSSID[6], uint8_t encryptionType, int32_t RSSI, bool isHidden, TransmissionStatusType transmissionStatus); - void setTransmissionStatus(transmission_status_t transmissionStatus); - transmission_status_t transmissionStatus() const; + void setTransmissionStatus(TransmissionStatusType transmissionStatus); + TransmissionStatusType transmissionStatus() const; private: - transmission_status_t _transmissionStatus; + TransmissionStatusType _transmissionStatus; }; #endif diff --git a/libraries/ESP8266WiFiMesh/src/TransmissionResult.h b/libraries/ESP8266WiFiMesh/src/TransmissionResult.h index 86c9b0b088..709e258f97 100644 --- a/libraries/ESP8266WiFiMesh/src/TransmissionResult.h +++ b/libraries/ESP8266WiFiMesh/src/TransmissionResult.h @@ -54,6 +54,13 @@ #include "NetworkInfo.h" #include "TransmissionOutcome.h" +typedef enum +{ + TS_CONNECTION_FAILED = -1, + TS_TRANSMISSION_FAILED = 0, + TS_TRANSMISSION_COMPLETE = 1 +} transmission_status_t; + class TransmissionResult : public NetworkInfo { public: @@ -63,11 +70,11 @@ class TransmissionResult : public NetworkInfo { /** * @param autofill Automatically fill in the rest of the network info using newNetworkIndex and the WiFi scan results. */ - TransmissionResult(int newNetworkIndex, transmission_status_t newTransmissionStatus, bool autofill = true); + TransmissionResult(int newNetworkIndex, transmission_status_t newTransmissionStatus, bool autofill = true) __attribute__((deprecated)); - TransmissionResult(const String &newSSID, int newWiFiChannel, uint8_t newBSSID[6], transmission_status_t newTransmissionStatus); + TransmissionResult(const String &newSSID, int newWiFiChannel, uint8_t newBSSID[6], transmission_status_t newTransmissionStatus) __attribute__((deprecated)); - TransmissionResult(const String &newSSID, int newWiFiChannel, uint8_t newBSSID[6], int newNetworkIndex, transmission_status_t newTransmissionStatus); + TransmissionResult(const String &newSSID, int newWiFiChannel, uint8_t newBSSID[6], int newNetworkIndex, transmission_status_t newTransmissionStatus) __attribute__((deprecated)); TransmissionResult(const NetworkInfo& origin, transmission_status_t newTransmissionStatus); }; diff --git a/libraries/ESP8266WiFiMesh/src/TypeConversionFunctions.cpp b/libraries/ESP8266WiFiMesh/src/TypeConversionFunctions.cpp index 1faa8d5629..b4073bface 100644 --- a/libraries/ESP8266WiFiMesh/src/TypeConversionFunctions.cpp +++ b/libraries/ESP8266WiFiMesh/src/TypeConversionFunctions.cpp @@ -27,15 +27,15 @@ namespace { - constexpr char chars[36] = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z'}; - constexpr uint8_t charValues[75] {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 0, 0, 0, 0, 0, 0, // 0 to 10 + constexpr char chars[36] PROGMEM = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z'}; + constexpr uint8_t charValues[75] PROGMEM {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 0, 0, 0, 0, 0, 0, // 0 to 9 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, 0, 0, 0, 0, 0, 0, // Upper 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, const byte base) { assert(2 <= base && base <= 36); @@ -44,14 +44,14 @@ namespace MeshTypeConversionFunctions if(base == 16) { do { - result += chars[ number % base ]; + result += (char)pgm_read_byte(chars + number % base); number >>= 4; // We could write number /= 16; and the compiler would optimize it to a shift, but the explicit shift notation makes it clearer where the speed-up comes from. } while ( number ); } else { do { - result += chars[ number % base ]; + result += (char)pgm_read_byte(chars + number % base); number /= base; } while ( number ); } @@ -61,7 +61,7 @@ namespace MeshTypeConversionFunctions return result; } - uint64_t stringToUint64(const String &string, byte base) + uint64_t stringToUint64(const String &string, const byte base) { assert(2 <= base && base <= 36); @@ -72,7 +72,7 @@ namespace MeshTypeConversionFunctions for(uint32_t i = 0; i < string.length(); ++i) { result <<= 4; // We could write result *= 16; and the compiler would optimize it to a shift, but the explicit shift notation makes it clearer where the speed-up comes from. - result += charValues[string.charAt(i) - '0']; + result += pgm_read_byte(charValues + string.charAt(i) - '0'); } } else @@ -80,14 +80,14 @@ namespace MeshTypeConversionFunctions for(uint32_t i = 0; i < string.length(); ++i) { result *= base; - result += charValues[string.charAt(i) - '0']; + result += pgm_read_byte(charValues + string.charAt(i) - '0'); } } return result; } - String uint8ArrayToHexString(const uint8_t *uint8Array, uint32_t arrayLength) + String uint8ArrayToHexString(const uint8_t *uint8Array, const uint32_t arrayLength) { String hexString; if(!hexString.reserve(2*arrayLength)) // Each uint8_t will become two characters (00 to FF) @@ -95,26 +95,26 @@ namespace MeshTypeConversionFunctions for(uint32_t i = 0; i < arrayLength; ++i) { - hexString += chars[ uint8Array[i] >> 4 ]; - hexString += chars[ uint8Array[i] % 16 ]; + hexString += (char)pgm_read_byte(chars + (uint8Array[i] >> 4)); + hexString += (char)pgm_read_byte(chars + uint8Array[i] % 16 ); } return hexString; } - uint8_t *hexStringToUint8Array(const String &hexString, uint8_t *uint8Array, uint32_t arrayLength) + uint8_t *hexStringToUint8Array(const String &hexString, uint8_t *uint8Array, const uint32_t arrayLength) { assert(hexString.length() >= arrayLength*2); // Each array element can hold two hexString characters for(uint32_t i = 0; i < arrayLength; ++i) { - uint8Array[i] = (charValues[hexString.charAt(i*2) - '0'] << 4) + charValues[hexString.charAt(i*2 + 1) - '0']; + uint8Array[i] = (pgm_read_byte(charValues + hexString.charAt(i*2) - '0') << 4) + pgm_read_byte(charValues + hexString.charAt(i*2 + 1) - '0'); } return uint8Array; } - String uint8ArrayToMultiString(uint8_t *uint8Array, uint32_t arrayLength) + String uint8ArrayToMultiString(uint8_t *uint8Array, const uint32_t arrayLength) { String multiString; if(!multiString.reserve(arrayLength)) @@ -137,7 +137,7 @@ namespace MeshTypeConversionFunctions return multiString; } - String bufferedUint8ArrayToMultiString(const uint8_t *uint8Array, uint32_t arrayLength) + String bufferedUint8ArrayToMultiString(const uint8_t *uint8Array, const uint32_t arrayLength) { String multiString; if(!multiString.reserve(arrayLength)) @@ -174,7 +174,7 @@ namespace MeshTypeConversionFunctions return result; } - uint8_t *uint64ToMac(uint64_t macValue, uint8_t *macArray) + uint8_t *uint64ToMac(const uint64_t macValue, uint8_t *macArray) { assert(macValue <= 0xFFFFFFFFFFFF); // Overflow will occur if value can't fit within 6 bytes @@ -188,7 +188,7 @@ namespace MeshTypeConversionFunctions return macArray; } - uint8_t *uint64ToUint8Array(uint64_t value, uint8_t *resultArray) + uint8_t *uint64ToUint8Array(const uint64_t value, uint8_t *resultArray) { resultArray[7] = value; resultArray[6] = value >> 8; @@ -214,27 +214,25 @@ namespace MeshTypeConversionFunctions * Helper function for meshBackendCast. */ template - T attemptPointerCast(MeshBackendBase *meshBackendBaseInstance, mesh_backend_t resultClassType) + T attemptPointerCast(MeshBackendBase *meshBackendBaseInstance, MeshBackendType resultClassType) { if(meshBackendBaseInstance && meshBackendBaseInstance->getClassType() == resultClassType) { return static_cast(meshBackendBaseInstance); } - else - { - return nullptr; - } + + return nullptr; } template <> EspnowMeshBackend *meshBackendCast(MeshBackendBase *meshBackendBaseInstance) { - return attemptPointerCast(meshBackendBaseInstance, MB_ESP_NOW); + return attemptPointerCast(meshBackendBaseInstance, MeshBackendType::ESP_NOW); } template <> TcpIpMeshBackend *meshBackendCast(MeshBackendBase *meshBackendBaseInstance) { - return attemptPointerCast(meshBackendBaseInstance, MB_TCP_IP); + return attemptPointerCast(meshBackendBaseInstance, MeshBackendType::TCP_IP); } } diff --git a/libraries/ESP8266WiFiMesh/src/TypeConversionFunctions.h b/libraries/ESP8266WiFiMesh/src/TypeConversionFunctions.h index 07591a8167..4c678a4ec0 100644 --- a/libraries/ESP8266WiFiMesh/src/TypeConversionFunctions.h +++ b/libraries/ESP8266WiFiMesh/src/TypeConversionFunctions.h @@ -42,7 +42,7 @@ namespace MeshTypeConversionFunctions * @param base The radix to convert "number" into. Must be between 2 and 36. * @return A string of "number" encoded in radix "base". */ - String uint64ToString(uint64_t number, byte base = 16); + String uint64ToString(uint64_t number, const 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. @@ -52,7 +52,7 @@ namespace MeshTypeConversionFunctions * @param base The radix of "string". Must be between 2 and 36. * @return A uint64_t of the string, using radix "base" during decoding. */ - uint64_t stringToUint64(const String &string, byte base = 16); + uint64_t stringToUint64(const String &string, const 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. @@ -62,7 +62,7 @@ namespace MeshTypeConversionFunctions * @param arrayLength The size of uint8Array, in bytes. * @return Normally a String containing the HEX representation of the uint8Array. An empty String if the memory allocation for the String failed. */ - String uint8ArrayToHexString(const uint8_t *uint8Array, uint32_t arrayLength); + String uint8ArrayToHexString(const uint8_t *uint8Array, const 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. @@ -73,7 +73,7 @@ namespace MeshTypeConversionFunctions * @param arrayLength The number of bytes to fill in uint8Array. * @return A pointer to the uint8Array. */ - uint8_t *hexStringToUint8Array(const String &hexString, uint8_t *uint8Array, uint32_t arrayLength); + uint8_t *hexStringToUint8Array(const String &hexString, uint8_t *uint8Array, const uint32_t arrayLength); /** * Stores the exact values of uint8Array in a String, even null values. @@ -86,7 +86,7 @@ namespace MeshTypeConversionFunctions * @param arrayLength The size of uint8Array, in bytes. * @return Normally a String containing the same data as the uint8Array. An empty String if the memory allocation for the String failed. */ - String uint8ArrayToMultiString(uint8_t *uint8Array, uint32_t arrayLength); + String uint8ArrayToMultiString(uint8_t *uint8Array, const uint32_t arrayLength); /** * Stores the exact values of uint8Array in a String, even null values. @@ -99,7 +99,7 @@ namespace MeshTypeConversionFunctions * @param arrayLength The size of uint8Array, in bytes. * @return Normally a String containing the same data as the uint8Array. An empty String if the memory allocation for the String failed. */ - String bufferedUint8ArrayToMultiString(const uint8_t *uint8Array, uint32_t arrayLength); + String bufferedUint8ArrayToMultiString(const uint8_t *uint8Array, const uint32_t arrayLength); /** * Takes a uint8_t array and converts the first 6 bytes to a hexadecimal string. @@ -133,7 +133,7 @@ namespace MeshTypeConversionFunctions * @param macArray A uint8_t array that will hold the mac address once the function returns. Should have a size of at least 6 bytes. * @return The macArray. */ - uint8_t *uint64ToMac(uint64_t macValue, uint8_t *macArray); + uint8_t *uint64ToMac(const 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. @@ -142,7 +142,7 @@ namespace MeshTypeConversionFunctions * @param resultArray A uint8_t array that will hold the result once the function returns. Should have a size of at least 8 bytes. * @return The resultArray. */ - uint8_t *uint64ToUint8Array(uint64_t value, uint8_t *resultArray); + uint8_t *uint64ToUint8Array(const 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. diff --git a/libraries/ESP8266WiFiMesh/src/UtilityFunctions.cpp b/libraries/ESP8266WiFiMesh/src/UtilityFunctions.cpp index 08dfb51111..209bdf80b7 100644 --- a/libraries/ESP8266WiFiMesh/src/UtilityFunctions.cpp +++ b/libraries/ESP8266WiFiMesh/src/UtilityFunctions.cpp @@ -30,7 +30,7 @@ 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]) { From 1dcd4c4a7b98aea498881372efc5faa80103cca3 Mon Sep 17 00:00:00 2001 From: Anders Date: Thu, 5 Mar 2020 15:39:49 +0100 Subject: [PATCH 16/30] - Fix even more merge conflicts. --- libraries/ESP8266WiFiMesh/src/ESP8266WiFiMesh.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/ESP8266WiFiMesh/src/ESP8266WiFiMesh.cpp b/libraries/ESP8266WiFiMesh/src/ESP8266WiFiMesh.cpp index 1678f9d001..5775fa3009 100644 --- a/libraries/ESP8266WiFiMesh/src/ESP8266WiFiMesh.cpp +++ b/libraries/ESP8266WiFiMesh/src/ESP8266WiFiMesh.cpp @@ -76,7 +76,7 @@ ESP8266WiFiMesh::ESP8266WiFiMesh(ESP8266WiFiMesh::requestHandlerType requestHand const String &nodeID, bool verboseMode, uint8 meshWiFiChannel, uint16_t serverPort) : _server(serverPort) { - updateNetworkNames(meshName, (nodeID != "" ? nodeID : TypeCast::uint64ToString(ESP.getChipId()))); + updateNetworkNames(meshName, (!nodeID.isEmpty() ? nodeID : TypeCast::uint64ToString(ESP.getChipId()))); _requestHandler = requestHandler; _responseHandler = responseHandler; setWiFiChannel(meshWiFiChannel); From effcc3a2d04bc936654c3bb0f6616c487fe03078 Mon Sep 17 00:00:00 2001 From: Anders Date: Wed, 29 Apr 2020 22:04:59 +0200 Subject: [PATCH 17/30] - Make everything const by default. - Remove setMeshInstance method from RequestData class. - Remove delay(50) and WiFi.disconnect() from setup() in the examples since those statements do not seem to have an effect any longer. - Improve documentation. --- .../examples/HelloEspnow/HelloEspnow.ino | 7 - .../examples/HelloMesh/HelloMesh.ino | 7 - .../examples/HelloTcpIp/HelloTcpIp.ino | 7 - .../src/EncryptedConnectionData.cpp | 14 +- .../src/EncryptedConnectionData.h | 16 +- .../src/EncryptedConnectionLog.cpp | 14 +- .../src/EncryptedConnectionLog.h | 14 +- .../ESP8266WiFiMesh/src/EspnowMeshBackend.cpp | 198 +++++----- .../ESP8266WiFiMesh/src/EspnowMeshBackend.h | 348 ++++++++++++------ .../ESP8266WiFiMesh/src/EspnowNetworkInfo.cpp | 4 +- .../ESP8266WiFiMesh/src/EspnowNetworkInfo.h | 6 +- .../src/EspnowProtocolInterpreter.cpp | 6 +- .../src/EspnowProtocolInterpreter.h | 6 +- .../ESP8266WiFiMesh/src/FloodingMesh.cpp | 57 +-- libraries/ESP8266WiFiMesh/src/FloodingMesh.h | 47 +-- .../ESP8266WiFiMesh/src/JsonTranslator.cpp | 12 +- .../ESP8266WiFiMesh/src/JsonTranslator.h | 12 +- .../ESP8266WiFiMesh/src/MeshBackendBase.cpp | 36 +- .../ESP8266WiFiMesh/src/MeshBackendBase.h | 48 +-- .../src/MeshCryptoInterface.cpp | 4 +- .../ESP8266WiFiMesh/src/MeshCryptoInterface.h | 4 +- libraries/ESP8266WiFiMesh/src/MessageData.cpp | 14 +- libraries/ESP8266WiFiMesh/src/MessageData.h | 16 +- .../ESP8266WiFiMesh/src/NetworkInfoBase.cpp | 14 +- .../ESP8266WiFiMesh/src/NetworkInfoBase.h | 14 +- .../ESP8266WiFiMesh/src/PeerRequestLog.cpp | 22 +- .../ESP8266WiFiMesh/src/PeerRequestLog.h | 22 +- libraries/ESP8266WiFiMesh/src/RequestData.cpp | 3 +- libraries/ESP8266WiFiMesh/src/RequestData.h | 3 +- .../ESP8266WiFiMesh/src/ResponseData.cpp | 6 +- libraries/ESP8266WiFiMesh/src/ResponseData.h | 6 +- .../ESP8266WiFiMesh/src/TcpIpMeshBackend.cpp | 46 +-- .../ESP8266WiFiMesh/src/TcpIpMeshBackend.h | 46 +-- .../ESP8266WiFiMesh/src/TcpIpNetworkInfo.cpp | 4 +- .../ESP8266WiFiMesh/src/TcpIpNetworkInfo.h | 6 +- libraries/ESP8266WiFiMesh/src/TimeTracker.cpp | 2 +- libraries/ESP8266WiFiMesh/src/TimeTracker.h | 2 +- .../src/TransmissionOutcome.cpp | 7 +- .../ESP8266WiFiMesh/src/TransmissionOutcome.h | 7 +- .../ESP8266WiFiMesh/src/UtilityMethods.cpp | 18 +- 40 files changed, 607 insertions(+), 518 deletions(-) diff --git a/libraries/ESP8266WiFiMesh/examples/HelloEspnow/HelloEspnow.ino b/libraries/ESP8266WiFiMesh/examples/HelloEspnow/HelloEspnow.ino index 3a03b8416c..fcb608c29c 100644 --- a/libraries/ESP8266WiFiMesh/examples/HelloEspnow/HelloEspnow.ino +++ b/libraries/ESP8266WiFiMesh/examples/HelloEspnow/HelloEspnow.ino @@ -233,13 +233,6 @@ void setup() { WiFi.persistent(false); Serial.begin(115200); - delay(50); // Wait for Serial. - - //yield(); // Use this if you don't want to wait for Serial, but not with the ESP-NOW backend (yield() causes crashes with ESP-NOW). - - // The WiFi.disconnect() ensures that the WiFi is working correctly. If this is not done before receiving WiFi connections, - // those WiFi connections will take a long time to make or sometimes will not work at all. - WiFi.disconnect(); Serial.println(); Serial.println(); diff --git a/libraries/ESP8266WiFiMesh/examples/HelloMesh/HelloMesh.ino b/libraries/ESP8266WiFiMesh/examples/HelloMesh/HelloMesh.ino index 5855604b36..fdb129d3a3 100644 --- a/libraries/ESP8266WiFiMesh/examples/HelloMesh/HelloMesh.ino +++ b/libraries/ESP8266WiFiMesh/examples/HelloMesh/HelloMesh.ino @@ -122,13 +122,6 @@ void setup() { WiFi.persistent(false); Serial.begin(115200); - delay(50); // Wait for Serial. - - //yield(); // Use this if you don't want to wait for Serial, but not with the ESP-NOW backend (yield() causes crashes with ESP-NOW). - - // The WiFi.disconnect() ensures that the WiFi is working correctly. If this is not done before receiving WiFi connections, - // those WiFi connections will take a long time to make or sometimes will not work at all. - WiFi.disconnect(); Serial.println(); Serial.println(); diff --git a/libraries/ESP8266WiFiMesh/examples/HelloTcpIp/HelloTcpIp.ino b/libraries/ESP8266WiFiMesh/examples/HelloTcpIp/HelloTcpIp.ino index 3fad54aa11..325450da32 100644 --- a/libraries/ESP8266WiFiMesh/examples/HelloTcpIp/HelloTcpIp.ino +++ b/libraries/ESP8266WiFiMesh/examples/HelloTcpIp/HelloTcpIp.ino @@ -160,13 +160,6 @@ void setup() { WiFi.persistent(false); Serial.begin(115200); - delay(50); // Wait for Serial. - - //yield(); // Use this if you don't want to wait for Serial. - - // The WiFi.disconnect() ensures that the WiFi is working correctly. If this is not done before receiving WiFi connections, - // those WiFi connections will take a long time to make or sometimes will not work at all. - WiFi.disconnect(); Serial.println(); Serial.println(); diff --git a/libraries/ESP8266WiFiMesh/src/EncryptedConnectionData.cpp b/libraries/ESP8266WiFiMesh/src/EncryptedConnectionData.cpp index 9f5cc45d37..c26d1fbe0a 100644 --- a/libraries/ESP8266WiFiMesh/src/EncryptedConnectionData.cpp +++ b/libraries/ESP8266WiFiMesh/src/EncryptedConnectionData.cpp @@ -31,7 +31,7 @@ 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], const uint64_t peerSessionKey, const uint64_t ownSessionKey, const uint8_t hashKey[espnowHashKeyLength]) : _peerSessionKey(peerSessionKey), _ownSessionKey(ownSessionKey) { std::copy_n(peerStaMac, 6, _peerStaMac); @@ -39,7 +39,7 @@ EncryptedConnectionData::EncryptedConnectionData(const uint8_t peerStaMac[6], co std::copy_n(hashKey, espnowHashKeyLength, _hashKey); } -EncryptedConnectionData::EncryptedConnectionData(const uint8_t peerStaMac[6], const uint8_t peerApMac[6], uint64_t peerSessionKey, uint64_t ownSessionKey, uint32_t duration, const uint8_t hashKey[espnowHashKeyLength]) +EncryptedConnectionData::EncryptedConnectionData(const uint8_t peerStaMac[6], const uint8_t peerApMac[6], const uint64_t peerSessionKey, const uint64_t ownSessionKey, const uint32_t duration, const uint8_t hashKey[espnowHashKeyLength]) : EncryptedConnectionData(peerStaMac, peerApMac, peerSessionKey, ownSessionKey, hashKey) { setRemainingDuration(duration); @@ -119,13 +119,13 @@ uint8_t *EncryptedConnectionData::getHashKey(uint8_t *resultArray) const return resultArray; } -void EncryptedConnectionData::setPeerSessionKey(uint64_t sessionKey) { _peerSessionKey = sessionKey; } +void EncryptedConnectionData::setPeerSessionKey(const uint64_t sessionKey) { _peerSessionKey = sessionKey; } uint64_t EncryptedConnectionData::getPeerSessionKey() const { return _peerSessionKey; } -void EncryptedConnectionData::setOwnSessionKey(uint64_t sessionKey) { _ownSessionKey = sessionKey; } +void EncryptedConnectionData::setOwnSessionKey(const uint64_t sessionKey) { _ownSessionKey = sessionKey; } uint64_t EncryptedConnectionData::getOwnSessionKey() const { return _ownSessionKey; } -uint64_t EncryptedConnectionData::incrementSessionKey(uint64_t sessionKey, const uint8_t *hashKey, uint8_t hashKeyLength) +uint64_t EncryptedConnectionData::incrementSessionKey(const uint64_t sessionKey, const uint8_t *hashKey, const uint8_t hashKeyLength) { uint8_t inputArray[8] {0}; uint8_t hmacArray[CryptoInterface::SHA256_NATURAL_LENGTH] {0}; @@ -149,7 +149,7 @@ void EncryptedConnectionData::incrementOwnSessionKey() setOwnSessionKey(incrementSessionKey(getOwnSessionKey(), _hashKey, EspnowProtocolInterpreter::espnowHashKeyLength)); } -void EncryptedConnectionData::setDesync(bool desync) { _desync = desync; } +void EncryptedConnectionData::setDesync(const bool desync) { _desync = desync; } bool EncryptedConnectionData::desync() const { return _desync; } String EncryptedConnectionData::serialize() const @@ -171,7 +171,7 @@ const ExpiringTimeTracker *EncryptedConnectionData::temporary() const return _timeTracker.get(); } -void EncryptedConnectionData::setRemainingDuration(uint32_t remainingDuration) +void EncryptedConnectionData::setRemainingDuration(const uint32_t remainingDuration) { if(!_timeTracker) { diff --git a/libraries/ESP8266WiFiMesh/src/EncryptedConnectionData.h b/libraries/ESP8266WiFiMesh/src/EncryptedConnectionData.h index be0d191710..871f8c10c2 100644 --- a/libraries/ESP8266WiFiMesh/src/EncryptedConnectionData.h +++ b/libraries/ESP8266WiFiMesh/src/EncryptedConnectionData.h @@ -36,10 +36,10 @@ class EncryptedConnectionData { virtual ~EncryptedConnectionData() = default; - EncryptedConnectionData(const uint8_t peerStaMac[6], const uint8_t peerApMac[6], uint64_t peerSessionKey, uint64_t ownSessionKey, + EncryptedConnectionData(const uint8_t peerStaMac[6], const uint8_t peerApMac[6], const uint64_t peerSessionKey, const uint64_t ownSessionKey, const uint8_t hashKey[EspnowProtocolInterpreter::espnowHashKeyLength]); - EncryptedConnectionData(const uint8_t peerStaMac[6], const uint8_t peerApMac[6], uint64_t peerSessionKey, uint64_t ownSessionKey, - uint32_t duration, const uint8_t hashKey[EspnowProtocolInterpreter::espnowHashKeyLength]); + EncryptedConnectionData(const uint8_t peerStaMac[6], const uint8_t peerApMac[6], const uint64_t peerSessionKey, const uint64_t ownSessionKey, + const uint32_t duration, const uint8_t hashKey[EspnowProtocolInterpreter::espnowHashKeyLength]); EncryptedConnectionData(const EncryptedConnectionData &other); @@ -65,15 +65,15 @@ class EncryptedConnectionData { // @param resultArray At least size espnowHashKeyLength. uint8_t *getHashKey(uint8_t *resultArray) const; - void setPeerSessionKey(uint64_t sessionKey); + void setPeerSessionKey(const uint64_t sessionKey); uint64_t getPeerSessionKey() const; - void setOwnSessionKey(uint64_t sessionKey); + void setOwnSessionKey(const uint64_t sessionKey); uint64_t getOwnSessionKey() const; - static uint64_t incrementSessionKey(uint64_t sessionKey, const uint8_t *hashKey, uint8_t hashKeyLength); + static uint64_t incrementSessionKey(const uint64_t sessionKey, const uint8_t *hashKey, const uint8_t hashKeyLength); void incrementOwnSessionKey(); - void setDesync(bool desync); + void setDesync(const bool desync); bool desync() const; // Note that the espnowEncryptedConnectionKey, espnowEncryptionKok, espnowHashKey and espnowMessageEncryptionKey are not serialized. @@ -81,7 +81,7 @@ class EncryptedConnectionData { String serialize() const; const ExpiringTimeTracker *temporary() const; - virtual void setRemainingDuration(uint32_t remainingDuration); + virtual void setRemainingDuration(const uint32_t remainingDuration); virtual void removeDuration(); private: diff --git a/libraries/ESP8266WiFiMesh/src/EncryptedConnectionLog.cpp b/libraries/ESP8266WiFiMesh/src/EncryptedConnectionLog.cpp index 12ce4dd3d6..ae87f188b1 100644 --- a/libraries/ESP8266WiFiMesh/src/EncryptedConnectionLog.cpp +++ b/libraries/ESP8266WiFiMesh/src/EncryptedConnectionLog.cpp @@ -26,11 +26,11 @@ using EspnowProtocolInterpreter::espnowHashKeyLength; -EncryptedConnectionLog::EncryptedConnectionLog(const uint8_t peerStaMac[6], const uint8_t peerApMac[6], uint64_t peerSessionKey, uint64_t ownSessionKey, const uint8_t hashKey[espnowHashKeyLength]) +EncryptedConnectionLog::EncryptedConnectionLog(const uint8_t peerStaMac[6], const uint8_t peerApMac[6], const uint64_t peerSessionKey, const uint64_t ownSessionKey, const uint8_t hashKey[espnowHashKeyLength]) : EncryptedConnectionData(peerStaMac, peerApMac, peerSessionKey, ownSessionKey, hashKey) { } -EncryptedConnectionLog::EncryptedConnectionLog(const uint8_t peerStaMac[6], const uint8_t peerApMac[6], uint64_t peerSessionKey, uint64_t ownSessionKey, uint32_t duration, const uint8_t hashKey[espnowHashKeyLength]) +EncryptedConnectionLog::EncryptedConnectionLog(const uint8_t peerStaMac[6], const uint8_t peerApMac[6], const uint64_t peerSessionKey, const uint64_t ownSessionKey, const uint32_t duration, const uint8_t hashKey[espnowHashKeyLength]) : EncryptedConnectionData(peerStaMac, peerApMac, peerSessionKey, ownSessionKey, duration, hashKey) { } @@ -38,7 +38,7 @@ std::unique_ptr EncryptedConnectionLog::_soonestExpiringCon bool EncryptedConnectionLog::_newRemovalsScheduled = false; -void EncryptedConnectionLog::setRemainingDuration(uint32_t remainingDuration) +void EncryptedConnectionLog::setRemainingDuration(const uint32_t remainingDuration) { EncryptedConnectionData::setRemainingDuration(remainingDuration); @@ -61,7 +61,7 @@ void EncryptedConnectionLog::scheduleForRemoval() setScheduledForRemoval(true); } -void EncryptedConnectionLog::setScheduledForRemoval(bool scheduledForRemoval) +void EncryptedConnectionLog::setScheduledForRemoval(const bool scheduledForRemoval) { _scheduledForRemoval = scheduledForRemoval; @@ -70,15 +70,15 @@ void EncryptedConnectionLog::setScheduledForRemoval(bool scheduledForRemoval) } bool EncryptedConnectionLog::removalScheduled() const { return _scheduledForRemoval; } -void EncryptedConnectionLog::setNewRemovalsScheduled(bool newRemovalsScheduled) { _newRemovalsScheduled = newRemovalsScheduled; } -bool EncryptedConnectionLog::newRemovalsScheduled( ){ return _newRemovalsScheduled; } +void EncryptedConnectionLog::setNewRemovalsScheduled(const bool newRemovalsScheduled) { _newRemovalsScheduled = newRemovalsScheduled; } +bool EncryptedConnectionLog::newRemovalsScheduled( ) { return _newRemovalsScheduled; } const ExpiringTimeTracker *EncryptedConnectionLog::getSoonestExpiringConnectionTracker() { return _soonestExpiringConnectionTracker.get(); } -void EncryptedConnectionLog::updateSoonestExpiringConnectionTracker(uint32_t remainingDuration) +void EncryptedConnectionLog::updateSoonestExpiringConnectionTracker(const uint32_t remainingDuration) { if(!getSoonestExpiringConnectionTracker() || remainingDuration < getSoonestExpiringConnectionTracker()->remainingDuration()) { diff --git a/libraries/ESP8266WiFiMesh/src/EncryptedConnectionLog.h b/libraries/ESP8266WiFiMesh/src/EncryptedConnectionLog.h index 2c1a991549..7909961fae 100644 --- a/libraries/ESP8266WiFiMesh/src/EncryptedConnectionLog.h +++ b/libraries/ESP8266WiFiMesh/src/EncryptedConnectionLog.h @@ -32,10 +32,10 @@ class EncryptedConnectionLog : public EncryptedConnectionData { public: - EncryptedConnectionLog(const uint8_t peerStaMac[6], const uint8_t peerApMac[6], uint64_t peerSessionKey, uint64_t ownSessionKey, + EncryptedConnectionLog(const uint8_t peerStaMac[6], const uint8_t peerApMac[6], const uint64_t peerSessionKey, const uint64_t ownSessionKey, const uint8_t hashKey[EspnowProtocolInterpreter::espnowHashKeyLength]); - EncryptedConnectionLog(const uint8_t peerStaMac[6], const uint8_t peerApMac[6], uint64_t peerSessionKey, uint64_t ownSessionKey, - uint32_t duration, const uint8_t hashKey[EspnowProtocolInterpreter::espnowHashKeyLength]); + EncryptedConnectionLog(const uint8_t peerStaMac[6], const uint8_t peerApMac[6], const uint64_t peerSessionKey, const uint64_t ownSessionKey, + const uint32_t duration, const uint8_t hashKey[EspnowProtocolInterpreter::espnowHashKeyLength]); // Only guaranteed to expire at the latest when the soonestExpiringConnection does. Can expire before the soonestExpiringConnection since it is not updated on connection removal. // Needs to be a copy to avoid invalidation during operations on temporaryEncryptedConnections. @@ -46,23 +46,23 @@ class EncryptedConnectionLog : public EncryptedConnectionData { static bool _newRemovalsScheduled; // Can be used to set a duration both for temporary and permanent encrypted connections (transforming the latter into a temporary connection in the process). - void setRemainingDuration(uint32_t remainingDuration) override; + void setRemainingDuration(const uint32_t remainingDuration) override; void removeDuration() override; void scheduleForRemoval(); bool removalScheduled() const; - static void setNewRemovalsScheduled(bool newRemovalsScheduled); + static void setNewRemovalsScheduled(const bool newRemovalsScheduled); static bool newRemovalsScheduled(); static const ExpiringTimeTracker *getSoonestExpiringConnectionTracker(); - static void updateSoonestExpiringConnectionTracker(uint32_t remainingDuration); + static void updateSoonestExpiringConnectionTracker(const uint32_t remainingDuration); static void clearSoonestExpiringConnectionTracker(); private: bool _scheduledForRemoval = false; - void setScheduledForRemoval(bool scheduledForRemoval); + void setScheduledForRemoval(const bool scheduledForRemoval); }; #endif diff --git a/libraries/ESP8266WiFiMesh/src/EspnowMeshBackend.cpp b/libraries/ESP8266WiFiMesh/src/EspnowMeshBackend.cpp index ceb458cd81..95777f53c2 100644 --- a/libraries/ESP8266WiFiMesh/src/EspnowMeshBackend.cpp +++ b/libraries/ESP8266WiFiMesh/src/EspnowMeshBackend.cpp @@ -112,9 +112,9 @@ void espnowDelay(uint32_t durationMs) while(!timeout); } -EspnowMeshBackend::EspnowMeshBackend(requestHandlerType requestHandler, responseHandlerType responseHandler, networkFilterType networkFilter, - broadcastFilterType broadcastFilter, const String &meshPassword, const String &ssidPrefix, const String &ssidSuffix, bool verboseMode, - uint8 meshWiFiChannel) +EspnowMeshBackend::EspnowMeshBackend(const requestHandlerType requestHandler, const responseHandlerType responseHandler, const networkFilterType networkFilter, + const broadcastFilterType broadcastFilter, const String &meshPassword, const String &ssidPrefix, const String &ssidSuffix, const bool verboseMode, + const uint8 meshWiFiChannel) : MeshBackendBase(requestHandler, responseHandler, networkFilter, MeshBackendType::ESP_NOW) { // Reserve the maximum possible usage early on to prevent heap fragmentation later. @@ -127,20 +127,20 @@ EspnowMeshBackend::EspnowMeshBackend(requestHandlerType requestHandler, response setWiFiChannel(meshWiFiChannel); } -EspnowMeshBackend::EspnowMeshBackend(requestHandlerType requestHandler, responseHandlerType responseHandler, networkFilterType networkFilter, - broadcastFilterType broadcastFilter, const String &meshPassword, const uint8_t espnowEncryptedConnectionKey[espnowEncryptedConnectionKeyLength], - const uint8_t espnowHashKey[espnowHashKeyLength], const String &ssidPrefix, const String &ssidSuffix, bool verboseMode, - uint8 meshWiFiChannel) +EspnowMeshBackend::EspnowMeshBackend(const requestHandlerType requestHandler, const responseHandlerType responseHandler, const networkFilterType networkFilter, + const broadcastFilterType broadcastFilter, const String &meshPassword, const uint8_t espnowEncryptedConnectionKey[espnowEncryptedConnectionKeyLength], + const uint8_t espnowHashKey[espnowHashKeyLength], const String &ssidPrefix, const String &ssidSuffix, const bool verboseMode, + const uint8 meshWiFiChannel) : EspnowMeshBackend(requestHandler, responseHandler, networkFilter, broadcastFilter, meshPassword, ssidPrefix, ssidSuffix, verboseMode, meshWiFiChannel) { setEspnowEncryptedConnectionKey(espnowEncryptedConnectionKey); setEspnowHashKey(espnowHashKey); } -EspnowMeshBackend::EspnowMeshBackend(requestHandlerType requestHandler, responseHandlerType responseHandler, networkFilterType networkFilter, - broadcastFilterType broadcastFilter, const String &meshPassword, const String &espnowEncryptedConnectionKeySeed, - const String &espnowHashKeySeed, const String &ssidPrefix, const String &ssidSuffix, bool verboseMode, - uint8 meshWiFiChannel) +EspnowMeshBackend::EspnowMeshBackend(const requestHandlerType requestHandler, const responseHandlerType responseHandler, const networkFilterType networkFilter, + const broadcastFilterType broadcastFilter, const String &meshPassword, const String &espnowEncryptedConnectionKeySeed, + const String &espnowHashKeySeed, const String &ssidPrefix, const String &ssidSuffix, const bool verboseMode, + const uint8 meshWiFiChannel) : EspnowMeshBackend(requestHandler, responseHandler, networkFilter, broadcastFilter, meshPassword, ssidPrefix, ssidSuffix, verboseMode, meshWiFiChannel) { setEspnowEncryptedConnectionKey(espnowEncryptedConnectionKeySeed); @@ -250,7 +250,7 @@ bool EspnowMeshBackend::latestTransmissionSuccessful() return latestTransmissionSuccessfulBase(latestTransmissionOutcomes()); } -void EspnowMeshBackend::performEspnowMaintenance(uint32_t estimatedMaxDuration) +void EspnowMeshBackend::performEspnowMaintenance(const uint32_t estimatedMaxDuration) { ExpiringTimeTracker estimatedMaxDurationTracker = ExpiringTimeTracker(estimatedMaxDuration); @@ -284,7 +284,7 @@ void EspnowMeshBackend::performEspnowMaintenance(uint32_t estimatedMaxDuration) } } -void EspnowMeshBackend::updateTemporaryEncryptedConnections(bool scheduledRemovalOnly) +void EspnowMeshBackend::updateTemporaryEncryptedConnections(const bool scheduledRemovalOnly) { EncryptedConnectionLog::clearSoonestExpiringConnectionTracker(); @@ -312,7 +312,7 @@ void EspnowMeshBackend::updateTemporaryEncryptedConnections(bool scheduledRemova } template -void EspnowMeshBackend::deleteExpiredLogEntries(std::map, T> &logEntries, uint32_t maxEntryLifetimeMs) +void EspnowMeshBackend::deleteExpiredLogEntries(std::map, T> &logEntries, const uint32_t maxEntryLifetimeMs) { for(typename std::map, T>::iterator entryIterator = logEntries.begin(); entryIterator != logEntries.end(); ) @@ -327,7 +327,7 @@ void EspnowMeshBackend::deleteExpiredLogEntries(std::map, } template -void EspnowMeshBackend::deleteExpiredLogEntries(std::map, TimeTracker> &logEntries, uint32_t maxEntryLifetimeMs) +void EspnowMeshBackend::deleteExpiredLogEntries(std::map, TimeTracker> &logEntries, const uint32_t maxEntryLifetimeMs) { for(typename std::map, TimeTracker>::iterator entryIterator = logEntries.begin(); entryIterator != logEntries.end(); ) @@ -341,7 +341,7 @@ void EspnowMeshBackend::deleteExpiredLogEntries(std::map, } } -void EspnowMeshBackend::deleteExpiredLogEntries(std::map, RequestData> &logEntries, uint32_t requestLifetimeMs, uint32_t broadcastLifetimeMs) +void EspnowMeshBackend::deleteExpiredLogEntries(std::map, RequestData> &logEntries, const uint32_t requestLifetimeMs, const uint32_t broadcastLifetimeMs) { for(typename std::map, RequestData>::iterator entryIterator = logEntries.begin(); entryIterator != logEntries.end(); ) @@ -360,7 +360,7 @@ void EspnowMeshBackend::deleteExpiredLogEntries(std::map -void EspnowMeshBackend::deleteExpiredLogEntries(std::list &logEntries, uint32_t maxEntryLifetimeMs) +void EspnowMeshBackend::deleteExpiredLogEntries(std::list &logEntries, const uint32_t maxEntryLifetimeMs) { for(typename std::list::iterator entryIterator = logEntries.begin(); entryIterator != logEntries.end(); ) @@ -375,7 +375,7 @@ void EspnowMeshBackend::deleteExpiredLogEntries(std::list &logEntries, uint32 } template <> -void EspnowMeshBackend::deleteExpiredLogEntries(std::list &logEntries, uint32_t maxEntryLifetimeMs) +void EspnowMeshBackend::deleteExpiredLogEntries(std::list &logEntries, const uint32_t maxEntryLifetimeMs) { for(typename std::list::iterator entryIterator = logEntries.begin(); entryIterator != logEntries.end(); ) @@ -391,7 +391,7 @@ void EspnowMeshBackend::deleteExpiredLogEntries(std::list -void EspnowMeshBackend::deleteExpiredLogEntries(std::list &logEntries, uint32_t maxEntryLifetimeMs) +void EspnowMeshBackend::deleteExpiredLogEntries(std::list &logEntries, const uint32_t maxEntryLifetimeMs) { for(typename std::list::iterator entryIterator = logEntries.begin(); entryIterator != logEntries.end(); ) @@ -421,7 +421,7 @@ void EspnowMeshBackend::clearOldLogEntries() deleteExpiredLogEntries(peerRequestConfirmationsToSend, getEncryptionRequestTimeout()); } -void EspnowMeshBackend::espnowReceiveCallbackWrapper(uint8_t *macaddr, uint8_t *dataArray, uint8_t len) +void EspnowMeshBackend::espnowReceiveCallbackWrapper(uint8_t *macaddr, uint8_t *dataArray, const uint8_t len) { using namespace EspnowProtocolInterpreter; @@ -557,7 +557,7 @@ void EspnowMeshBackend::espnowReceiveCallbackWrapper(uint8_t *macaddr, uint8_t * } } -void EspnowMeshBackend::handlePeerRequest(uint8_t *macaddr, uint8_t *dataArray, uint8_t len, uint64_t uint64StationMac, uint64_t receivedMessageID) +void EspnowMeshBackend::handlePeerRequest(const uint8_t *macaddr, uint8_t *dataArray, const uint8_t len, const uint64_t uint64StationMac, const uint64_t receivedMessageID) { // Pairing process ends when encryptedConnectionVerificationHeader is received, maxConnectionsReachedHeader is sent or timeout is reached. // Pairing process stages for request receiver: @@ -657,7 +657,7 @@ void EspnowMeshBackend::handlePeerRequest(uint8_t *macaddr, uint8_t *dataArray, } } -void EspnowMeshBackend::handlePeerRequestConfirmation(uint8_t *macaddr, uint8_t *dataArray, uint8_t len) +void EspnowMeshBackend::handlePeerRequestConfirmation(uint8_t *macaddr, uint8_t *dataArray, const uint8_t len) { // Pairing process ends when _ongoingPeerRequestNonce == "" or timeout is reached. // Pairing process stages for request sender: @@ -764,7 +764,7 @@ void EspnowMeshBackend::handlePeerRequestConfirmation(uint8_t *macaddr, uint8_t } } -void EspnowMeshBackend::espnowReceiveCallback(uint8_t *macaddr, uint8_t *dataArray, uint8_t len) +void EspnowMeshBackend::espnowReceiveCallback(const uint8_t *macaddr, uint8_t *dataArray, const uint8_t len) { using namespace EspnowProtocolInterpreter; @@ -924,29 +924,29 @@ void EspnowMeshBackend::setEspnowRequestManager(EspnowMeshBackend *espnowMeshIns EspnowMeshBackend *EspnowMeshBackend::getEspnowRequestManager() {return _espnowRequestManager;} -bool EspnowMeshBackend::isEspnowRequestManager() +bool EspnowMeshBackend::isEspnowRequestManager() const { return (this == getEspnowRequestManager()); } -bool EspnowMeshBackend::encryptedConnectionEstablished(EncryptedConnectionStatus connectionStatus) +bool EspnowMeshBackend::encryptedConnectionEstablished(const EncryptedConnectionStatus connectionStatus) { return static_cast(connectionStatus) > 0; } -void EspnowMeshBackend::setLogEntryLifetimeMs(uint32_t logEntryLifetimeMs) +void EspnowMeshBackend::setLogEntryLifetimeMs(const uint32_t logEntryLifetimeMs) { _logEntryLifetimeMs = logEntryLifetimeMs; } uint32_t EspnowMeshBackend::logEntryLifetimeMs() { return _logEntryLifetimeMs; } -void EspnowMeshBackend::setBroadcastResponseTimeoutMs(uint32_t broadcastResponseTimeoutMs) +void EspnowMeshBackend::setBroadcastResponseTimeoutMs(const uint32_t broadcastResponseTimeoutMs) { _broadcastResponseTimeoutMs = broadcastResponseTimeoutMs; } uint32_t EspnowMeshBackend::broadcastResponseTimeoutMs() { return _broadcastResponseTimeoutMs; } -void EspnowMeshBackend::setCriticalHeapLevelBuffer(uint32_t bufferInBytes) +void EspnowMeshBackend::setCriticalHeapLevelBuffer(const uint32_t bufferInBytes) { _criticalHeapLevelBuffer = bufferInBytes; } @@ -957,7 +957,7 @@ uint32_t EspnowMeshBackend::criticalHeapLevelBuffer() } template -T *EspnowMeshBackend::getMapValue(std::map &mapIn, uint64_t keyIn) +T *EspnowMeshBackend::getMapValue(std::map &mapIn, const uint64_t keyIn) { typename std::map::iterator mapIterator = mapIn.find(keyIn); @@ -979,7 +979,7 @@ void EspnowMeshBackend::storeReceivedRequest(const uint64_t senderBSSID, const u receivedRequests.insert(std::make_pair(std::make_pair(senderBSSID, messageID), timeTracker)); } -EspnowMeshBackend *EspnowMeshBackend::getOwnerOfSentRequest(uint64_t requestMac, uint64_t requestID) +EspnowMeshBackend *EspnowMeshBackend::getOwnerOfSentRequest(const uint64_t requestMac, const uint64_t requestID) { std::map, RequestData>::iterator sentRequest = sentRequests.find(std::make_pair(requestMac, requestID)); @@ -991,12 +991,12 @@ EspnowMeshBackend *EspnowMeshBackend::getOwnerOfSentRequest(uint64_t requestMac, return nullptr; } -size_t EspnowMeshBackend::deleteSentRequest(uint64_t requestMac, uint64_t requestID) +size_t EspnowMeshBackend::deleteSentRequest(const uint64_t requestMac, const uint64_t requestID) { return sentRequests.erase(std::make_pair(requestMac, requestID)); } -size_t EspnowMeshBackend::deleteSentRequestsByOwner(EspnowMeshBackend *instancePointer) +size_t EspnowMeshBackend::deleteSentRequestsByOwner(const EspnowMeshBackend *instancePointer) { size_t numberDeleted = 0; @@ -1015,7 +1015,7 @@ size_t EspnowMeshBackend::deleteSentRequestsByOwner(EspnowMeshBackend *instanceP return numberDeleted; } -bool EspnowMeshBackend::requestReceived(uint64_t requestMac, uint64_t requestID) +bool EspnowMeshBackend::requestReceived(const uint64_t requestMac, const uint64_t requestID) { return receivedRequests.count(std::make_pair(requestMac, requestID)); } @@ -1025,7 +1025,7 @@ uint32_t EspnowMeshBackend::criticalHeapLevel() return _criticalHeapLevel; } -uint64_t EspnowMeshBackend::generateMessageID(EncryptedConnectionLog *encryptedConnection) +uint64_t EspnowMeshBackend::generateMessageID(const EncryptedConnectionLog *encryptedConnection) { if(encryptedConnection) { @@ -1041,39 +1041,39 @@ uint64_t EspnowMeshBackend::createSessionKey() return EspnowProtocolInterpreter::usesEncryption(newSessionKey) ? newSessionKey : (newSessionKey | ((uint64_t)RANDOM_REG32) << 32 | uint64MSB); } -void EspnowMeshBackend::setEspnowTransmissionTimeout(uint32_t timeoutMs) +void EspnowMeshBackend::setEspnowTransmissionTimeout(const uint32_t timeoutMs) { _espnowTransmissionTimeoutMs = timeoutMs; } uint32_t EspnowMeshBackend::getEspnowTransmissionTimeout() {return _espnowTransmissionTimeoutMs;} -void EspnowMeshBackend::setEspnowRetransmissionInterval(uint32_t intervalMs) +void EspnowMeshBackend::setEspnowRetransmissionInterval(const uint32_t intervalMs) { _espnowRetransmissionIntervalMs = intervalMs; } uint32_t EspnowMeshBackend::getEspnowRetransmissionInterval() {return _espnowRetransmissionIntervalMs;} -void EspnowMeshBackend::setEncryptionRequestTimeout(uint32_t timeoutMs) +void EspnowMeshBackend::setEncryptionRequestTimeout(const uint32_t timeoutMs) { _encryptionRequestTimeoutMs = timeoutMs; } uint32_t EspnowMeshBackend::getEncryptionRequestTimeout() {return _encryptionRequestTimeoutMs;} -void EspnowMeshBackend::setAutoEncryptionDuration(uint32_t duration) +void EspnowMeshBackend::setAutoEncryptionDuration(const uint32_t duration) { _autoEncryptionDuration = duration; } -uint32_t EspnowMeshBackend::getAutoEncryptionDuration() {return _autoEncryptionDuration;} +uint32_t EspnowMeshBackend::getAutoEncryptionDuration() const {return _autoEncryptionDuration;} -void EspnowMeshBackend::setBroadcastFilter(broadcastFilterType broadcastFilter) {_broadcastFilter = broadcastFilter;} -EspnowMeshBackend::broadcastFilterType EspnowMeshBackend::getBroadcastFilter() {return _broadcastFilter;} +void EspnowMeshBackend::setBroadcastFilter(const broadcastFilterType broadcastFilter) {_broadcastFilter = broadcastFilter;} +EspnowMeshBackend::broadcastFilterType EspnowMeshBackend::getBroadcastFilter() const {return _broadcastFilter;} -bool EspnowMeshBackend::usesConstantSessionKey(char messageType) +bool EspnowMeshBackend::usesConstantSessionKey(const char messageType) { return messageType == 'A' || messageType == 'C'; } -TransmissionStatusType EspnowMeshBackend::espnowSendToNode(const String &message, const uint8_t *targetBSSID, char messageType, EspnowMeshBackend *espnowInstance) +TransmissionStatusType EspnowMeshBackend::espnowSendToNode(const String &message, const uint8_t *targetBSSID, const char messageType, EspnowMeshBackend *espnowInstance) { using EspnowProtocolInterpreter::synchronizationRequestHeader; @@ -1102,7 +1102,7 @@ TransmissionStatusType EspnowMeshBackend::espnowSendToNode(const String &message return espnowSendToNodeUnsynchronized(message, targetBSSID, messageType, generateMessageID(encryptedConnection), espnowInstance); } -TransmissionStatusType EspnowMeshBackend::espnowSendToNodeUnsynchronized(const String message, const uint8_t *targetBSSID, char messageType, uint64_t messageID, EspnowMeshBackend *espnowInstance) +TransmissionStatusType EspnowMeshBackend::espnowSendToNodeUnsynchronized(const String message, const uint8_t *targetBSSID, const char messageType, const uint64_t messageID, EspnowMeshBackend *espnowInstance) { using namespace EspnowProtocolInterpreter; @@ -1276,7 +1276,7 @@ TransmissionStatusType EspnowMeshBackend::sendRequest(const String &message, con return transmissionStatus; } -TransmissionStatusType EspnowMeshBackend::sendResponse(const String &message, uint64_t requestID, const uint8_t *targetBSSID) +TransmissionStatusType EspnowMeshBackend::sendResponse(const String &message, const uint64_t requestID, const uint8_t *targetBSSID) { EncryptedConnectionLog *encryptedConnection = getEncryptedConnection(targetBSSID); uint8_t encryptedMac[6] {0}; @@ -1292,7 +1292,7 @@ TransmissionStatusType EspnowMeshBackend::sendResponse(const String &message, ui bool EspnowMeshBackend::transmissionInProgress(){return *_espnowTransmissionMutex;} -EspnowMeshBackend::macAndType_td EspnowMeshBackend::createMacAndTypeValue(uint64_t uint64Mac, char messageType) +EspnowMeshBackend::macAndType_td EspnowMeshBackend::createMacAndTypeValue(const uint64_t uint64Mac, const char messageType) { return static_cast(uint64Mac << 8 | (uint64_t)messageType); } @@ -1317,12 +1317,12 @@ void EspnowMeshBackend::setEspnowEncryptedConnectionKey(const String &espnowEncr MeshCryptoInterface::initializeKey(_espnowEncryptedConnectionKey, espnowEncryptedConnectionKeyLength, espnowEncryptedConnectionKeySeed); } -const uint8_t *EspnowMeshBackend::getEspnowEncryptedConnectionKey() +const uint8_t *EspnowMeshBackend::getEspnowEncryptedConnectionKey() const { return _espnowEncryptedConnectionKey; } -uint8_t *EspnowMeshBackend::getEspnowEncryptedConnectionKey(uint8_t resultArray[espnowEncryptedConnectionKeyLength]) +uint8_t *EspnowMeshBackend::getEspnowEncryptedConnectionKey(uint8_t resultArray[espnowEncryptedConnectionKeyLength]) const { std::copy_n(_espnowEncryptedConnectionKey, espnowEncryptedConnectionKeyLength, resultArray); return resultArray; @@ -1374,12 +1374,12 @@ void EspnowMeshBackend::setEspnowHashKey(const String &espnowHashKeySeed) MeshCryptoInterface::initializeKey(_espnowHashKey, espnowHashKeyLength, espnowHashKeySeed); } -const uint8_t *EspnowMeshBackend::getEspnowHashKey() +const uint8_t *EspnowMeshBackend::getEspnowHashKey() const { return _espnowHashKey; } -void EspnowMeshBackend::setEspnowMessageEncryptionKey(uint8_t espnowMessageEncryptionKey[CryptoInterface::ENCRYPTION_KEY_LENGTH]) +void EspnowMeshBackend::setEspnowMessageEncryptionKey(const uint8_t espnowMessageEncryptionKey[CryptoInterface::ENCRYPTION_KEY_LENGTH]) { assert(espnowMessageEncryptionKey != nullptr); @@ -1399,7 +1399,7 @@ const uint8_t *EspnowMeshBackend::getEspnowMessageEncryptionKey() return _espnowMessageEncryptionKey; } -void EspnowMeshBackend::setUseEncryptedMessages(bool useEncryptedMessages) +void EspnowMeshBackend::setUseEncryptedMessages(const bool useEncryptedMessages) { MutexTracker mutexTracker(_espnowSendToNodeMutex); if(!mutexTracker.mutexCaptured()) @@ -1411,7 +1411,7 @@ void EspnowMeshBackend::setUseEncryptedMessages(bool useEncryptedMessages) } bool EspnowMeshBackend::useEncryptedMessages() { return _useEncryptedMessages; } -bool EspnowMeshBackend::verifyPeerSessionKey(uint64_t sessionKey, const uint8_t *peerMac, char messageType) +bool EspnowMeshBackend::verifyPeerSessionKey(const uint64_t sessionKey, const uint8_t *peerMac, const char messageType) { if(EncryptedConnectionLog *encryptedConnection = getEncryptedConnection(peerMac)) { @@ -1421,7 +1421,7 @@ bool EspnowMeshBackend::verifyPeerSessionKey(uint64_t sessionKey, const uint8_t return false; } -bool EspnowMeshBackend::verifyPeerSessionKey(uint64_t sessionKey, EncryptedConnectionLog &encryptedConnection, uint64_t uint64PeerMac, char messageType) +bool EspnowMeshBackend::verifyPeerSessionKey(const uint64_t sessionKey, const EncryptedConnectionLog &encryptedConnection, const uint64_t uint64PeerMac, const char messageType) { if(EspnowProtocolInterpreter::usesEncryption(sessionKey)) { @@ -1437,7 +1437,7 @@ bool EspnowMeshBackend::verifyPeerSessionKey(uint64_t sessionKey, EncryptedConne return false; } -bool EspnowMeshBackend::synchronizePeerSessionKey(uint64_t sessionKey, const uint8_t *peerMac) +bool EspnowMeshBackend::synchronizePeerSessionKey(const uint64_t sessionKey, const uint8_t *peerMac) { if(EncryptedConnectionLog *encryptedConnection = getEncryptedConnection(peerMac)) { @@ -1447,7 +1447,7 @@ bool EspnowMeshBackend::synchronizePeerSessionKey(uint64_t sessionKey, const uin return false; } -bool EspnowMeshBackend::synchronizePeerSessionKey(uint64_t sessionKey, EncryptedConnectionLog &encryptedConnection) +bool EspnowMeshBackend::synchronizePeerSessionKey(const uint64_t sessionKey, EncryptedConnectionLog &encryptedConnection) { if(EspnowProtocolInterpreter::usesEncryption(sessionKey)) { @@ -1462,7 +1462,7 @@ bool EspnowMeshBackend::synchronizePeerSessionKey(uint64_t sessionKey, Encrypted return false; } -std::list::const_iterator EspnowMeshBackend::getScheduledResponse(uint32_t responseIndex) +std::list::const_iterator EspnowMeshBackend::getScheduledResponse(const uint32_t responseIndex) { assert(responseIndex < numberOfScheduledResponses()); @@ -1479,12 +1479,12 @@ std::list::const_iterator EspnowMeshBackend::getScheduledResponse( return responseIterator; } -String EspnowMeshBackend::getScheduledResponseMessage(uint32_t responseIndex) +String EspnowMeshBackend::getScheduledResponseMessage(const uint32_t responseIndex) { return getScheduledResponse(responseIndex)->getMessage(); } -const uint8_t *EspnowMeshBackend::getScheduledResponseRecipient(uint32_t responseIndex) +const uint8_t *EspnowMeshBackend::getScheduledResponseRecipient(const uint32_t responseIndex) { return getScheduledResponse(responseIndex)->getRecipientMac(); } @@ -1502,7 +1502,7 @@ void EspnowMeshBackend::clearAllScheduledResponses() responsesToSend.clear(); } -void EspnowMeshBackend::deleteScheduledResponsesByRecipient(const uint8_t *recipientMac, bool encryptedOnly) +void EspnowMeshBackend::deleteScheduledResponsesByRecipient(const uint8_t *recipientMac, const bool encryptedOnly) { MutexTracker responsesToSendMutexTracker(_responsesToSendMutex); if(!responsesToSendMutexTracker.mutexCaptured()) @@ -1522,39 +1522,39 @@ void EspnowMeshBackend::deleteScheduledResponsesByRecipient(const uint8_t *recip } } -void EspnowMeshBackend::setSenderMac(uint8_t *macArray) +void EspnowMeshBackend::setSenderMac(const uint8_t *macArray) { std::copy_n(macArray, 6, _senderMac); } -String EspnowMeshBackend::getSenderMac() {return TypeCast::macToString(_senderMac);} -uint8_t *EspnowMeshBackend::getSenderMac(uint8_t *macArray) +String EspnowMeshBackend::getSenderMac() const {return TypeCast::macToString(_senderMac);} +uint8_t *EspnowMeshBackend::getSenderMac(uint8_t *macArray) const { std::copy_n(_senderMac, 6, macArray); return macArray; } -void EspnowMeshBackend::setSenderAPMac(uint8_t *macArray) +void EspnowMeshBackend::setSenderAPMac(const uint8_t *macArray) { std::copy_n(macArray, 6, _senderAPMac); } -String EspnowMeshBackend::getSenderAPMac() {return TypeCast::macToString(_senderAPMac);} -uint8_t *EspnowMeshBackend::getSenderAPMac(uint8_t *macArray) +String EspnowMeshBackend::getSenderAPMac() const {return TypeCast::macToString(_senderAPMac);} +uint8_t *EspnowMeshBackend::getSenderAPMac(uint8_t *macArray) const { std::copy_n(_senderAPMac, 6, macArray); return macArray; } -void EspnowMeshBackend::setReceivedEncryptedTransmission(bool receivedEncryptedTransmission) { _receivedEncryptedTransmission = receivedEncryptedTransmission; } -bool EspnowMeshBackend::receivedEncryptedTransmission() {return _receivedEncryptedTransmission;} +void EspnowMeshBackend::setReceivedEncryptedTransmission(const bool receivedEncryptedTransmission) { _receivedEncryptedTransmission = receivedEncryptedTransmission; } +bool EspnowMeshBackend::receivedEncryptedTransmission() const {return _receivedEncryptedTransmission;} bool EspnowMeshBackend::addUnencryptedConnection(const String &serializedConnectionState) { return JsonTranslator::getUnsynchronizedMessageID(serializedConnectionState, _unsynchronizedMessageID); } -EncryptedConnectionStatus EspnowMeshBackend::addEncryptedConnection(uint8_t *peerStaMac, uint8_t *peerApMac, uint64_t peerSessionKey, uint64_t ownSessionKey) +EncryptedConnectionStatus EspnowMeshBackend::addEncryptedConnection(uint8_t *peerStaMac, uint8_t *peerApMac, const uint64_t peerSessionKey, const uint64_t ownSessionKey) { assert(encryptedConnections.size() <= maxEncryptedConnections); // If this is not the case, ESP-NOW is no longer in sync with the library @@ -1590,7 +1590,7 @@ EncryptedConnectionStatus EspnowMeshBackend::addEncryptedConnection(uint8_t *pee } } -EncryptedConnectionStatus EspnowMeshBackend::addEncryptedConnection(const String &serializedConnectionState, bool ignoreDuration) +EncryptedConnectionStatus EspnowMeshBackend::addEncryptedConnection(const String &serializedConnectionState, const bool ignoreDuration) { uint32_t duration = 0; bool desync = false; @@ -1626,7 +1626,7 @@ EncryptedConnectionStatus EspnowMeshBackend::addEncryptedConnection(const String return EncryptedConnectionStatus::REQUEST_TRANSMISSION_FAILED; } -EncryptedConnectionStatus EspnowMeshBackend::addTemporaryEncryptedConnection(uint8_t *peerStaMac, uint8_t *peerApMac, uint64_t peerSessionKey, uint64_t ownSessionKey, uint32_t duration) +EncryptedConnectionStatus EspnowMeshBackend::addTemporaryEncryptedConnection(uint8_t *peerStaMac, uint8_t *peerApMac, const uint64_t peerSessionKey, const uint64_t ownSessionKey, const uint32_t duration) { assert(encryptedConnections.size() <= maxEncryptedConnections); // If this is not the case, ESP-NOW is no longer in sync with the library @@ -1663,7 +1663,7 @@ EncryptedConnectionStatus EspnowMeshBackend::addTemporaryEncryptedConnection(uin return result; } -EncryptedConnectionStatus EspnowMeshBackend::addTemporaryEncryptedConnection(const String &serializedConnectionState, uint32_t duration) +EncryptedConnectionStatus EspnowMeshBackend::addTemporaryEncryptedConnection(const String &serializedConnectionState, const uint32_t duration) { bool desync = false; uint64_t ownSessionKey = 0; @@ -1704,7 +1704,7 @@ void EspnowMeshBackend::handlePostponedRemovals() } } -EncryptedConnectionStatus EspnowMeshBackend::requestEncryptedConnectionKernel(uint8_t *peerMac, const encryptionRequestBuilderType &encryptionRequestBuilder) +EncryptedConnectionStatus EspnowMeshBackend::requestEncryptedConnectionKernel(const uint8_t *peerMac, const encryptionRequestBuilderType &encryptionRequestBuilder) { using namespace EspnowProtocolInterpreter; @@ -1856,26 +1856,26 @@ String EspnowMeshBackend::flexibleEncryptionRequestBuilder(const uint32_t minDur return createEncryptionRequestHmacMessage(FPSTR(temporaryEncryptionRequestHeader), requestNonce, hashKey, espnowHashKeyLength, connectionDuration); } -EncryptedConnectionStatus EspnowMeshBackend::requestEncryptedConnection(uint8_t *peerMac) +EncryptedConnectionStatus EspnowMeshBackend::requestEncryptedConnection(const uint8_t *peerMac) { using namespace std::placeholders; return requestEncryptedConnectionKernel(peerMac, std::bind(defaultEncryptionRequestBuilder, FPSTR(EspnowProtocolInterpreter::encryptionRequestHeader), 0, getEspnowHashKey(), _1, _2)); } -EncryptedConnectionStatus EspnowMeshBackend::requestTemporaryEncryptedConnection(uint8_t *peerMac, uint32_t durationMs) +EncryptedConnectionStatus EspnowMeshBackend::requestTemporaryEncryptedConnection(const uint8_t *peerMac, const uint32_t durationMs) { using namespace std::placeholders; return requestEncryptedConnectionKernel(peerMac, std::bind(defaultEncryptionRequestBuilder, FPSTR(EspnowProtocolInterpreter::temporaryEncryptionRequestHeader), durationMs, getEspnowHashKey(), _1, _2)); } -EncryptedConnectionStatus EspnowMeshBackend::requestFlexibleTemporaryEncryptedConnection(uint8_t *peerMac, uint32_t minDurationMs) +EncryptedConnectionStatus EspnowMeshBackend::requestFlexibleTemporaryEncryptedConnection(const uint8_t *peerMac, const uint32_t minDurationMs) { using namespace std::placeholders; return requestEncryptedConnectionKernel(peerMac, std::bind(flexibleEncryptionRequestBuilder, minDurationMs, getEspnowHashKey(), _1, _2)); } -bool EspnowMeshBackend::temporaryEncryptedConnectionToPermanent(uint8_t *peerMac) +bool EspnowMeshBackend::temporaryEncryptedConnectionToPermanent(const uint8_t *peerMac) { if(EncryptedConnectionLog *temporaryConnection = getTemporaryEncryptedConnection(peerMac)) { @@ -1886,7 +1886,7 @@ bool EspnowMeshBackend::temporaryEncryptedConnectionToPermanent(uint8_t *peerMac return false; } -EncryptedConnectionRemovalOutcome EspnowMeshBackend::removeEncryptedConnection(uint8_t *peerMac) +EncryptedConnectionRemovalOutcome EspnowMeshBackend::removeEncryptedConnection(const uint8_t *peerMac) { auto connectionIterator = getEncryptedConnectionIterator(peerMac, encryptedConnections); if(connectionIterator != encryptedConnections.end()) @@ -1962,7 +1962,7 @@ EncryptedConnectionRemovalOutcome EspnowMeshBackend::removeEncryptedConnectionUn } template -void EspnowMeshBackend::deleteEntriesByMac(std::map, T> &logEntries, const uint8_t *peerMac, bool encryptedOnly) +void EspnowMeshBackend::deleteEntriesByMac(std::map, T> &logEntries, const uint8_t *peerMac, const bool encryptedOnly) { bool macFound = false; @@ -1990,7 +1990,7 @@ void EspnowMeshBackend::deleteEntriesByMac(std::map -void EspnowMeshBackend::deleteEntriesByMac(std::map, T> &logEntries, const uint8_t *peerMac, bool encryptedOnly) +void EspnowMeshBackend::deleteEntriesByMac(std::map, T> &logEntries, const uint8_t *peerMac, const bool encryptedOnly) { bool macFound = false; @@ -2017,7 +2017,7 @@ void EspnowMeshBackend::deleteEntriesByMac(std::map typename T::iterator EspnowMeshBackend::getEncryptedConnectionIterator(const uint8_t *peerMac, T &connectionContainer) @@ -2140,7 +2140,7 @@ uint8_t *EspnowMeshBackend::getEncryptedMac(const uint8_t *peerMac, uint8_t *res return nullptr; } -void EspnowMeshBackend::prepareForTransmission(const String &message, bool scan, bool scanAllWiFiChannels) +void EspnowMeshBackend::prepareForTransmission(const String &message, const bool scan, const bool scanAllWiFiChannels) { setMessage(message); @@ -2189,7 +2189,7 @@ TransmissionStatusType EspnowMeshBackend::initiateTransmissionKernel(const Strin return transmissionResult; } -void EspnowMeshBackend::printTransmissionStatistics() +void EspnowMeshBackend::printTransmissionStatistics() const { if(verboseMode() && successfulTransmissions_AT > 0) // Avoid calculations if not required { @@ -2202,7 +2202,7 @@ void EspnowMeshBackend::printTransmissionStatistics() } } -void EspnowMeshBackend::attemptTransmission(const String &message, bool scan, bool scanAllWiFiChannels) +void EspnowMeshBackend::attemptTransmission(const String &message, const bool scan, const bool scanAllWiFiChannels) { MutexTracker mutexTracker(_espnowTransmissionMutex, handlePostponedRemovals); if(!mutexTracker.mutexCaptured()) @@ -2246,7 +2246,7 @@ TransmissionStatusType EspnowMeshBackend::attemptTransmission(const String &mess return initiateTransmission(message, recipientInfo); } -EncryptedConnectionStatus EspnowMeshBackend::initiateAutoEncryptingConnection(const EspnowNetworkInfo &recipientInfo, bool requestPermanentConnection, uint8_t *targetBSSID, EncryptedConnectionLog **existingEncryptedConnection) +EncryptedConnectionStatus EspnowMeshBackend::initiateAutoEncryptingConnection(const EspnowNetworkInfo &recipientInfo, const bool requestPermanentConnection, uint8_t *targetBSSID, EncryptedConnectionLog **existingEncryptedConnection) { assert(recipientInfo.BSSID() != nullptr); // We need at least the BSSID to connect recipientInfo.getBSSID(targetBSSID); @@ -2268,7 +2268,7 @@ EncryptedConnectionStatus EspnowMeshBackend::initiateAutoEncryptingConnection(co return connectionStatus; } -TransmissionStatusType EspnowMeshBackend::initiateAutoEncryptingTransmission(const String &message, const uint8_t *targetBSSID, EncryptedConnectionStatus connectionStatus) +TransmissionStatusType EspnowMeshBackend::initiateAutoEncryptingTransmission(const String &message, uint8_t *targetBSSID, EncryptedConnectionStatus connectionStatus) { TransmissionStatusType transmissionResult = TransmissionStatusType::CONNECTION_FAILED; @@ -2282,7 +2282,7 @@ TransmissionStatusType EspnowMeshBackend::initiateAutoEncryptingTransmission(con return transmissionResult; } -void EspnowMeshBackend::finalizeAutoEncryptingConnection(const uint8_t *targetBSSID, const EncryptedConnectionLog *existingEncryptedConnection, bool requestPermanentConnection) +void EspnowMeshBackend::finalizeAutoEncryptingConnection(const uint8_t *targetBSSID, const EncryptedConnectionLog *existingEncryptedConnection, const bool requestPermanentConnection) { if(!existingEncryptedConnection && !requestPermanentConnection && !_reciprocalPeerRequestConfirmation) { @@ -2291,7 +2291,7 @@ void EspnowMeshBackend::finalizeAutoEncryptingConnection(const uint8_t *targetBS } } -void EspnowMeshBackend::attemptAutoEncryptingTransmission(const String &message, bool requestPermanentConnections, bool scan, bool scanAllWiFiChannels) +void EspnowMeshBackend::attemptAutoEncryptingTransmission(const String &message, const bool requestPermanentConnections, const bool scan, const bool scanAllWiFiChannels) { MutexTracker outerMutexTracker(_espnowTransmissionMutex, handlePostponedRemovals); if(!outerMutexTracker.mutexCaptured()) @@ -2338,7 +2338,7 @@ void EspnowMeshBackend::attemptAutoEncryptingTransmission(const String &message, printTransmissionStatistics(); } -TransmissionStatusType EspnowMeshBackend::attemptAutoEncryptingTransmission(const String &message, const EspnowNetworkInfo &recipientInfo, bool requestPermanentConnection) +TransmissionStatusType EspnowMeshBackend::attemptAutoEncryptingTransmission(const String &message, const EspnowNetworkInfo &recipientInfo, const bool requestPermanentConnection) { uint8_t targetBSSID[6] {0}; EncryptedConnectionLog *existingEncryptedConnection = nullptr; @@ -2370,11 +2370,11 @@ void EspnowMeshBackend::broadcast(const String &message) espnowSendToNode(message, broadcastMac, 'B', this); } -void EspnowMeshBackend::setBroadcastTransmissionRedundancy(uint8_t redundancy) { _broadcastTransmissionRedundancy = redundancy; } -uint8_t EspnowMeshBackend::getBroadcastTransmissionRedundancy() { return _broadcastTransmissionRedundancy; } +void EspnowMeshBackend::setBroadcastTransmissionRedundancy(const uint8_t redundancy) { _broadcastTransmissionRedundancy = redundancy; } +uint8_t EspnowMeshBackend::getBroadcastTransmissionRedundancy() const { return _broadcastTransmissionRedundancy; } -void EspnowMeshBackend::setResponseTransmittedHook(responseTransmittedHookType responseTransmittedHook) { _responseTransmittedHook = responseTransmittedHook; } -EspnowMeshBackend::responseTransmittedHookType EspnowMeshBackend::getResponseTransmittedHook() { return _responseTransmittedHook; } +void EspnowMeshBackend::setResponseTransmittedHook(const responseTransmittedHookType responseTransmittedHook) { _responseTransmittedHook = responseTransmittedHook; } +EspnowMeshBackend::responseTransmittedHookType EspnowMeshBackend::getResponseTransmittedHook() const { return _responseTransmittedHook; } void EspnowMeshBackend::sendStoredEspnowMessages(const ExpiringTimeTracker *estimatedMaxDurationTracker) { @@ -2587,7 +2587,7 @@ uint32_t EspnowMeshBackend::getMaxMessageBytesPerTransmission() return getMaxBytesPerTransmission() - espnowMetadataSize(); } -void EspnowMeshBackend::setMaxTransmissionsPerMessage(uint8_t maxTransmissionsPerMessage) +void EspnowMeshBackend::setMaxTransmissionsPerMessage(const uint8_t maxTransmissionsPerMessage) { assert(1 <= maxTransmissionsPerMessage && maxTransmissionsPerMessage <= 128); @@ -2646,7 +2646,7 @@ ConnectionType EspnowMeshBackend::getConnectionInfo(uint8_t *peerMac, uint32_t * return getConnectionInfoHelper(encryptedConnection, remainingDuration); } -ConnectionType EspnowMeshBackend::getConnectionInfo(uint32_t connectionIndex, uint32_t *remainingDuration, uint8_t *peerMac) +ConnectionType EspnowMeshBackend::getConnectionInfo(const uint32_t connectionIndex, uint32_t *remainingDuration, uint8_t *peerMac) { EncryptedConnectionLog *encryptedConnection = nullptr; @@ -2694,7 +2694,7 @@ String EspnowMeshBackend::serializeEncryptedConnection(const uint8_t *peerMac) return serializedConnection; } -String EspnowMeshBackend::serializeEncryptedConnection(uint32_t connectionIndex) +String EspnowMeshBackend::serializeEncryptedConnection(const uint32_t connectionIndex) { String serializedConnection(emptyString); diff --git a/libraries/ESP8266WiFiMesh/src/EspnowMeshBackend.h b/libraries/ESP8266WiFiMesh/src/EspnowMeshBackend.h index 908cf3620d..641d5c52e0 100644 --- a/libraries/ESP8266WiFiMesh/src/EspnowMeshBackend.h +++ b/libraries/ESP8266WiFiMesh/src/EspnowMeshBackend.h @@ -107,7 +107,7 @@ enum class EncryptedConnectionStatus MAX_CONNECTIONS_REACHED_PEER = -1, API_CALL_FAILED = 0, CONNECTION_ESTABLISHED = 1, - SOFT_LIMIT_CONNECTION_ESTABLISHED = 2 // Only used if _encryptedConnectionsSoftLimit is less than 6. + SOFT_LIMIT_CONNECTION_ESTABLISHED = 2 // Only used if _encryptedConnectionsSoftLimit is less than 6. See the setEncryptedConnectionsSoftLimit method documentation for details. }; enum class EncryptedConnectionRemovalOutcome @@ -128,7 +128,7 @@ enum class EncryptedConnectionRemovalOutcome * * @param durationMs The shortest allowed delay duration, in milliseconds. */ -void espnowDelay(uint32_t durationMs); +void espnowDelay(const uint32_t durationMs); class RequestData; @@ -166,10 +166,10 @@ class EspnowMeshBackend : public MeshBackendBase { * make it impossible for other stations to detect the APs whose WiFi channels have changed. * */ - EspnowMeshBackend(requestHandlerType requestHandler, responseHandlerType responseHandler, networkFilterType networkFilter, broadcastFilterType broadcastFilter, + EspnowMeshBackend(const requestHandlerType requestHandler, const responseHandlerType responseHandler, const networkFilterType networkFilter, const broadcastFilterType broadcastFilter, const String &meshPassword, const uint8_t espnowEncryptedConnectionKey[EspnowProtocolInterpreter::espnowEncryptedConnectionKeyLength], const uint8_t espnowHashKey[EspnowProtocolInterpreter::espnowHashKeyLength], const String &ssidPrefix, - const String &ssidSuffix, bool verboseMode = false, uint8 meshWiFiChannel = 1); + const String &ssidSuffix, const bool verboseMode = false, const uint8 meshWiFiChannel = 1); /** * ESP-NOW constructor method. Creates an ESP-NOW node, ready to be initialised. @@ -194,9 +194,9 @@ class EspnowMeshBackend : public MeshBackendBase { * make it impossible for other stations to detect the APs whose WiFi channels have changed. * */ - EspnowMeshBackend(requestHandlerType requestHandler, responseHandlerType responseHandler, networkFilterType networkFilter, broadcastFilterType broadcastFilter, + EspnowMeshBackend(const requestHandlerType requestHandler, const responseHandlerType responseHandler, const networkFilterType networkFilter, const broadcastFilterType broadcastFilter, const String &meshPassword, const String &espnowEncryptedConnectionKeySeed, const String &espnowHashKeySeed, const String &ssidPrefix, - const String &ssidSuffix, bool verboseMode = false, uint8 meshWiFiChannel = 1); + const String &ssidSuffix, const bool verboseMode = false, const uint8 meshWiFiChannel = 1); ~EspnowMeshBackend() override; @@ -249,7 +249,7 @@ class EspnowMeshBackend : public MeshBackendBase { * Note that setting the estimatedMaxDuration too low may result in missed ESP-NOW transmissions because of too little time for maintenance. * Also note that although the method will try to respect the max duration limit, there is no guarantee. Overshoots by tens of milliseconds are possible. */ - static void performEspnowMaintenance(uint32_t estimatedMaxDuration = 0); + static void performEspnowMaintenance(const uint32_t estimatedMaxDuration = 0); /** * At critical heap level no more incoming requests are accepted. @@ -271,7 +271,7 @@ class EspnowMeshBackend : public MeshBackendBase { * If the buffer is set to 0 bytes a significant number of incoming requests are likely to be lost during intense transmission activity, * and there is a greater risk of heap space completely running out before log clearing occurs (which may cause crashes or empty transmissions). */ - static void setCriticalHeapLevelBuffer(uint32_t bufferInBytes); + static void setCriticalHeapLevelBuffer(const uint32_t bufferInBytes); static uint32_t criticalHeapLevelBuffer(); /** @@ -281,7 +281,7 @@ class EspnowMeshBackend : public MeshBackendBase { */ static bool deactivateEspnow(); - void attemptTransmission(const String &message, bool scan = true, bool scanAllWiFiChannels = false) override; + void attemptTransmission(const String &message, const bool scan = true, const bool scanAllWiFiChannels = false) override; /** * Transmit message to a single recipient without changing the local transmission state. @@ -315,13 +315,13 @@ class EspnowMeshBackend : public MeshBackendBase { * Note that if the ESP8266 has an active AP, that AP will switch WiFi channel to match that of any other AP the ESP8266 connects to. * This can make it impossible for other nodes to detect the AP if they are scanning the wrong WiFi channel. */ - void attemptAutoEncryptingTransmission(const String &message, bool requestPermanentConnections = false, bool scan = true, bool scanAllWiFiChannels = false); + void attemptAutoEncryptingTransmission(const String &message, const bool requestPermanentConnections = false, const bool scan = true, const bool scanAllWiFiChannels = false); /** * Transmit message to a single recipient without changing the local transmission state (apart from encrypted connections). * Will not change connectionQueue, latestTransmissionOutcomes or stored message. */ - TransmissionStatusType attemptAutoEncryptingTransmission(const String &message, const EspnowNetworkInfo &recipientInfo, bool requestPermanentConnection = false); + TransmissionStatusType attemptAutoEncryptingTransmission(const String &message, const EspnowNetworkInfo &recipientInfo, const bool requestPermanentConnection = false); /** * Send a message simultaneously to all nearby nodes which have ESP-NOW activated. @@ -340,8 +340,8 @@ class EspnowMeshBackend : public MeshBackendBase { * * @param redundancy The number of extra transmissions to make of each broadcast. Defaults to 1. */ - void setBroadcastTransmissionRedundancy(uint8_t redundancy); - uint8_t getBroadcastTransmissionRedundancy(); + void setBroadcastTransmissionRedundancy(const uint8_t redundancy); + uint8_t getBroadcastTransmissionRedundancy() const; /** * Set the EspnowMeshBackend instance responsible for handling incoming requests. The requestHandler of the instance will be called upon receiving ESP-NOW requests. @@ -358,7 +358,7 @@ class EspnowMeshBackend : public MeshBackendBase { * * @return True if this EspnowMeshBackend is the espnowRequestManager. False otherwise. */ - bool isEspnowRequestManager(); + bool isEspnowRequestManager() const; /** * Set the duration of most ESP-NOW log entries. Used for all ESP-NOW communication except for broadcasts and encrypted connection requests. @@ -369,7 +369,7 @@ class EspnowMeshBackend : public MeshBackendBase { * * @param logEntryLifetimeMs The duration to use for most ESP-NOW log entries, in milliseconds. */ - static void setLogEntryLifetimeMs(uint32_t logEntryLifetimeMs); + static void setLogEntryLifetimeMs(const uint32_t logEntryLifetimeMs); static uint32_t logEntryLifetimeMs(); /** @@ -383,7 +383,7 @@ class EspnowMeshBackend : public MeshBackendBase { * * @param broadcastResponseTimeoutMs The duration sent ESP-NOW broadcasts will be stored in the log, in milliseconds. */ - static void setBroadcastResponseTimeoutMs(uint32_t broadcastResponseTimeoutMs); + static void setBroadcastResponseTimeoutMs(const uint32_t broadcastResponseTimeoutMs); static uint32_t broadcastResponseTimeoutMs(); /** @@ -420,8 +420,8 @@ class EspnowMeshBackend : public MeshBackendBase { * * @return The current espnowEncryptedConnectionKey for this EspnowMeshBackend instance. */ - const uint8_t *getEspnowEncryptedConnectionKey(); - uint8_t *getEspnowEncryptedConnectionKey(uint8_t resultArray[EspnowProtocolInterpreter::espnowEncryptedConnectionKeyLength]); + const uint8_t *getEspnowEncryptedConnectionKey() const; + uint8_t *getEspnowEncryptedConnectionKey(uint8_t resultArray[EspnowProtocolInterpreter::espnowEncryptedConnectionKeyLength]) const; /** * Change the key used to encrypt/decrypt the encrypted connection key when creating encrypted ESP-NOW connections. (Kok = key of keys, perhaps) If no Kok is provided by the user, a default Kok is used. @@ -486,7 +486,7 @@ class EspnowMeshBackend : public MeshBackendBase { */ void setEspnowHashKey(const String &espnowHashKeySeed); - const uint8_t *getEspnowHashKey(); + const uint8_t *getEspnowHashKey() const; /** * Change the key used to encrypt/decrypt messages when using AEAD encryption. @@ -496,7 +496,7 @@ class EspnowMeshBackend : public MeshBackendBase { * * @param espnowMessageEncryptionKey An array containing the CryptoInterface::ENCRYPTION_KEY_LENGTH bytes that will be used as the message encryption key. */ - static void setEspnowMessageEncryptionKey(uint8_t espnowMessageEncryptionKey[CryptoInterface::ENCRYPTION_KEY_LENGTH]); + static void setEspnowMessageEncryptionKey(const uint8_t espnowMessageEncryptionKey[CryptoInterface::ENCRYPTION_KEY_LENGTH]); /** * Change the key used to encrypt/decrypt messages when using AEAD encryption. @@ -532,7 +532,7 @@ class EspnowMeshBackend : public MeshBackendBase { * * @param useEncryptedMessages If true, AEAD encryption/decryption is enabled. If false, AEAD encryption/decryption is disabled. */ - static void setUseEncryptedMessages(bool useEncryptedMessages); + static void setUseEncryptedMessages(const bool useEncryptedMessages); static bool useEncryptedMessages(); /** @@ -556,7 +556,7 @@ class EspnowMeshBackend : public MeshBackendBase { * * @param maxTransmissionsPerMessage The maximum acceptable message length, in terms of transmissions, when sending a message from this node. Valid values are 1 to 128. Defaults to 3. */ - static void setMaxTransmissionsPerMessage(uint8_t maxTransmissionsPerMessage); + static void setMaxTransmissionsPerMessage(const uint8_t maxTransmissionsPerMessage); static uint8_t getMaxTransmissionsPerMessage(); /** @@ -574,8 +574,8 @@ class EspnowMeshBackend : public MeshBackendBase { * * @param enabled If true, library Serial prints are activated. */ - void setVerboseModeState(bool enabled) override; - bool verboseMode() override; + void setVerboseModeState(const bool enabled) override; + bool verboseMode() const override; /** * Only print stringToPrint if verboseMode() returns true. @@ -583,7 +583,7 @@ class EspnowMeshBackend : public MeshBackendBase { * @param stringToPrint String to print. * @param newline If true, will end the print with a newline. True by default. */ - void verboseModePrint(const String &stringToPrint, bool newline = true) override; + void verboseModePrint(const String &stringToPrint, const bool newline = true) const override; /** * Same as verboseMode(), but used for printing from static functions. @@ -598,7 +598,7 @@ class EspnowMeshBackend : public MeshBackendBase { * @param stringToPrint String to print. * @param newline If true, will end the print with a newline. True by default. */ - static void staticVerboseModePrint(const String &stringToPrint, bool newline = true); + static void staticVerboseModePrint(const String &stringToPrint, const bool newline = true); /** * Get the message of the response at responseIndex among the responses that are scheduled for transmission from this node. @@ -606,7 +606,7 @@ class EspnowMeshBackend : public MeshBackendBase { * @param responseIndex The index of the response. Must be lower than numberOfScheduledResponses(). * @return A String containing the message of the response at responseIndex. */ - static String getScheduledResponseMessage(uint32_t responseIndex); + static String getScheduledResponseMessage(const uint32_t responseIndex); /** * Get the MAC address for the recipient of the response at responseIndex among the responses that are scheduled for transmission from this node. @@ -614,7 +614,7 @@ class EspnowMeshBackend : public MeshBackendBase { * @param responseIndex The index of the response. Must be lower than numberOfScheduledResponses(). * @return An array with six bytes containing the MAC address for the recipient of the response at responseIndex. */ - static const uint8_t *getScheduledResponseRecipient(uint32_t responseIndex); + static const uint8_t *getScheduledResponseRecipient(const uint32_t responseIndex); /** * Get the number of ESP-NOW responses that are scheduled for transmission from this node. @@ -637,7 +637,7 @@ class EspnowMeshBackend : public MeshBackendBase { * @param recipientMac The MAC address of the response recipient. * @param encryptedOnly If true, only responses to encrypted requests will be deleted. */ - static void deleteScheduledResponsesByRecipient(const uint8_t *recipientMac, bool encryptedOnly); + static void deleteScheduledResponsesByRecipient(const uint8_t *recipientMac, const bool encryptedOnly); /** * Set the timeout to use for each ESP-NOW transmission when transmitting. @@ -646,7 +646,7 @@ class EspnowMeshBackend : public MeshBackendBase { * * @param timeoutMs The timeout that should be used for each ESP-NOW transmission, in milliseconds. Defaults to 40 ms. */ - static void setEspnowTransmissionTimeout(uint32_t timeoutMs); + static void setEspnowTransmissionTimeout(const uint32_t timeoutMs); static uint32_t getEspnowTransmissionTimeout(); /** @@ -656,18 +656,18 @@ class EspnowMeshBackend : public MeshBackendBase { * * @param intervalMs The time to wait for an ack after having made an ESP-NOW transmission, in milliseconds. Defaults to 15 ms. */ - static void setEspnowRetransmissionInterval(uint32_t intervalMs); + static void setEspnowRetransmissionInterval(const uint32_t intervalMs); static uint32_t getEspnowRetransmissionInterval(); // The maximum amount of time each of the two stages in an encrypted connection request may take. - static void setEncryptionRequestTimeout(uint32_t timeoutMs); + static void setEncryptionRequestTimeout(const uint32_t timeoutMs); static uint32_t getEncryptionRequestTimeout(); - void setAutoEncryptionDuration(uint32_t duration); - uint32_t getAutoEncryptionDuration(); + void setAutoEncryptionDuration(const uint32_t duration); + uint32_t getAutoEncryptionDuration() const; - void setBroadcastFilter(broadcastFilterType broadcastFilter); - broadcastFilterType getBroadcastFilter(); + void setBroadcastFilter(const broadcastFilterType broadcastFilter); + broadcastFilterType getBroadcastFilter() const; /** * Set a function that should be called after each successful ESP-NOW response transmission, just before the response is removed from the waiting list. @@ -679,8 +679,8 @@ class EspnowMeshBackend : public MeshBackendBase { * If it is false, the response transmission process will stop after removing the just sent response from the waiting list. * The default responseTransmittedHook always returns true. */ - void setResponseTransmittedHook(responseTransmittedHookType responseTransmittedHook); - responseTransmittedHookType getResponseTransmittedHook(); + void setResponseTransmittedHook(const responseTransmittedHookType responseTransmittedHook); + responseTransmittedHookType getResponseTransmittedHook() const; /** * Get the MAC address of the sender of the most recently received ESP-NOW request, response or broadcast to this EspnowMeshBackend instance. @@ -690,7 +690,7 @@ class EspnowMeshBackend : public MeshBackendBase { * * @return A String filled with a hexadecimal representation of the MAC, without delimiters. */ - String getSenderMac(); + String getSenderMac() const; /** * Get the MAC address of the sender of the most recently received ESP-NOW request, response or broadcast to this EspnowMeshBackend instance. @@ -701,7 +701,7 @@ class EspnowMeshBackend : public MeshBackendBase { * @param macArray The array that should store the MAC address. Must be at least 6 bytes. * @return macArray filled with the sender MAC. */ - uint8_t *getSenderMac(uint8_t *macArray); + uint8_t *getSenderMac(uint8_t *macArray) const; /** * Get the AP MAC address of the sender of the most recently received ESP-NOW request, response or broadcast to this EspnowMeshBackend instance. @@ -709,7 +709,7 @@ class EspnowMeshBackend : public MeshBackendBase { * * @return A String filled with a hexadecimal representation of the AP MAC, without delimiters. */ - String getSenderAPMac(); + String getSenderAPMac() const; /** * Get the AP MAC address of the sender of the most recently received ESP-NOW request, response or broadcast to this EspnowMeshBackend instance. @@ -718,14 +718,14 @@ class EspnowMeshBackend : public MeshBackendBase { * @param macArray The array that should store the MAC address. Must be at least 6 bytes. * @return macArray filled with the sender AP MAC. */ - uint8_t *getSenderAPMac(uint8_t *macArray); + uint8_t *getSenderAPMac(uint8_t *macArray) const; /** * Get whether the ESP-NOW request, response or broadcast which was most recently received by this EspnowMeshBackend instance was sent over an encrypted connection or not. * * @return If true, the request, response or broadcast was sent over an encrypted connection. If false, the connection was unencrypted. */ - bool receivedEncryptedTransmission(); + bool receivedEncryptedTransmission() const; /** * Should be used together with serializeUnencryptedConnection() if the node sends unencrypted transmissions @@ -737,35 +737,135 @@ class EspnowMeshBackend : public MeshBackendBase { * @return True if connection was added. False otherwise (e.g. if there is faulty input). */ static bool addUnencryptedConnection(const String &serializedConnectionState); + + /** + * Adds a new permanent encrypted ESP-NOW connection, or makes the duration of an existing temporary connection permanent. + * Note that this will not add an encrypted ESP-NOW connection automatically to the other node. Thus the same method will need to be called on the other node as well to establish an encrypted connection. + * Methods such as requestEncryptedConnection creates an encrypted connection automatically in both nodes, but requires information exchange between the nodes before the connection is established (and is thus much slower). + * + * When called, the method will update an existing encrypted ESP-NOW connection with the current stored encrypted connection key. (in case it has changed since the connection was established) + * + * @param peerStaMac The station MAC of the other node. + * @param peerApMac The AP MAC of the other node. + * @param peerSessionKey The session key of the other node. At least one of the leftmost 32 bits should be 1, since the key otherwise indicates the connection is unencrypted. + * @param peerSessionKey The session key of this node. At least one of the leftmost 32 bits should be 1, since the key otherwise indicates the connection is unencrypted. + * + * @return EncryptedConnectionStatus::CONNECTION_ESTABLISHED if the connection was created. Otherwise another status code based on the outcome. + */ + EncryptedConnectionStatus addEncryptedConnection(uint8_t *peerStaMac, uint8_t *peerApMac, const uint64_t peerSessionKey, const uint64_t ownSessionKey); + + /** + * Create an encrypted ESP-NOW connection on this node based on the information stored in serializedConnectionState. + * Note that this will not add an encrypted ESP-NOW connection automatically to the other node. Thus the same method will need to be called on the other node as well to establish an encrypted connection. + * Methods such as requestEncryptedConnection creates an encrypted connection automatically in both nodes, but requires information exchange between the nodes before the connection is established (and is thus much slower). + * + * When called, the method will update an existing encrypted ESP-NOW connection with the current stored encrypted connection key. (in case it has changed since the connection was established) + * + * Note that the espnowEncryptedConnectionKey, espnowEncryptionKok, espnowHashKey and espnowMessageEncryptionKey are not serialized. + * These will be set to the values of the EspnowMeshBackend instance that is adding the serialized encrypted connection. + * + * @param serializedConnectionState A String containing the serialized connection state. + * @param ignoreDuration Ignores any stored duration in serializedConnectionState, guaranteeing that the created connection will be permanent. + * + * @return EncryptedConnectionStatus::CONNECTION_ESTABLISHED if the connection was created. Otherwise another status code based on the outcome. EncryptedConnectionStatus::REQUEST_TRANSMISSION_FAILED indicates a malformed serializedConnectionState. + */ + EncryptedConnectionStatus addEncryptedConnection(const String &serializedConnectionState, const bool ignoreDuration = false); + + /** + * Adds a new temporary encrypted ESP-NOW connection, or changes the duration of an existing temporary connection (only updates keys, not duration, for existing permanent connections). + * Note that this will not add an encrypted ESP-NOW connection automatically to the other node. Thus the same method will need to be called on the other node as well to establish an encrypted connection. + * Methods such as requestEncryptedConnection creates an encrypted connection automatically in both nodes, but requires information exchange between the nodes before the connection is established (and is thus much slower). + * + * When called, the method will update an existing encrypted ESP-NOW connection with the current stored encrypted connection key. (in case it has changed since the connection was established) + * + * As with all these methods, changes will only take effect once the requester proves it has the ability to decrypt the session key. + * + * @param peerStaMac The station MAC of the other node. + * @param peerApMac The AP MAC of the other node. + * @param peerSessionKey The session key of the other node. At least one of the leftmost 32 bits should be 1, since the key otherwise indicates the connection is unencrypted. + * @param peerSessionKey The session key of this node. At least one of the leftmost 32 bits should be 1, since the key otherwise indicates the connection is unencrypted. + * @param duration The desired duration of the connection. + * + * @return EncryptedConnectionStatus::CONNECTION_ESTABLISHED if the connection was created. Otherwise another status code based on the outcome. + */ + EncryptedConnectionStatus addTemporaryEncryptedConnection(uint8_t *peerStaMac, uint8_t *peerApMac, const uint64_t peerSessionKey, const uint64_t ownSessionKey, const uint32_t duration); - // Updates connection with current stored encrypted connection key. - // At least one of the leftmost 32 bits in each of the session keys should be 1, since the key otherwise indicates the connection is unencrypted. - EncryptedConnectionStatus addEncryptedConnection(uint8_t *peerStaMac, uint8_t *peerApMac, uint64_t peerSessionKey, uint64_t ownSessionKey); - // Note that the espnowEncryptedConnectionKey, espnowEncryptionKok, espnowHashKey and espnowMessageEncryptionKey are not serialized. - // These will be set to the values of the EspnowMeshBackend instance that is adding the serialized encrypted connection. - // @param ignoreDuration Ignores any stored duration serializedConnectionState, guaranteeing that the created connection will be permanent. Returns: EncryptedConnectionStatus::REQUEST_TRANSMISSION_FAILED indicates malformed serializedConnectionState. - EncryptedConnectionStatus addEncryptedConnection(const String &serializedConnectionState, bool ignoreDuration = false); - - // Adds a new temporary encrypted connection, or changes the duration of an existing temporary connection (only updates keys, not duration, for existing permanent connections). - // As with all these methods, changes will only take effect once the requester proves it has the ability to decrypt the session key. - // At least one of the leftmost 32 bits in each of the session keys should be 1, since the key otherwise indicates the connection is unencrypted. - EncryptedConnectionStatus addTemporaryEncryptedConnection(uint8_t *peerStaMac, uint8_t *peerApMac, uint64_t peerSessionKey, uint64_t ownSessionKey, uint32_t duration); - // Note that the espnowEncryptedConnectionKey, espnowEncryptionKok, espnowHashKey and espnowMessageEncryptionKey are not serialized. - // These will be set to the values of the EspnowMeshBackend instance that is adding the serialized encrypted connection. - // Uses duration argument instead of any stored duration in serializedConnectionState. Returns: EncryptedConnectionStatus::REQUEST_TRANSMISSION_FAILED indicates malformed serializedConnectionState. - EncryptedConnectionStatus addTemporaryEncryptedConnection(const String &serializedConnectionState, uint32_t duration); - - // If an encrypted connection to peerMac already exists, only connection duration is updated. All other settings are kept as is. Use removeEncryptedConnection/requestEncryptedConnectionRemoval first if encryption keys should be updated. - // Makes sure both nodes have an encrypted connection to each other that's permanent. - EncryptedConnectionStatus requestEncryptedConnection(uint8_t *peerMac); - // Makes sure both nodes have an encrypted connection to each other that's either permanent or has the duration specified. - EncryptedConnectionStatus requestTemporaryEncryptedConnection(uint8_t *peerMac, uint32_t durationMs); - // Makes sure both nodes have an encrypted connection to each other that's either permanent or has at least the duration specified. - // Note that if a temporary encrypted connection already exists to a target node, this method will slightly extend the connection duration - // depending on the time it takes to verify the connection to the node. - EncryptedConnectionStatus requestFlexibleTemporaryEncryptedConnection(uint8_t *peerMac, uint32_t minDurationMs); - static EncryptedConnectionRemovalOutcome removeEncryptedConnection(uint8_t *peerMac); - EncryptedConnectionRemovalOutcome requestEncryptedConnectionRemoval(uint8_t *peerMac); + /** + * Create a temporary encrypted ESP-NOW connection on this node based on the information stored in serializedConnectionState. + * Note that this will not add an encrypted ESP-NOW connection automatically to the other node. Thus the same method will need to be called on the other node as well to establish an encrypted connection. + * Methods such as requestEncryptedConnection creates an encrypted connection automatically in both nodes, but requires information exchange between the nodes before the connection is established (and is thus much slower). + * + * When called, the method will update an existing encrypted ESP-NOW connection with the current stored encrypted connection key. (in case it has changed since the connection was established) + * + * Note that the espnowEncryptedConnectionKey, espnowEncryptionKok, espnowHashKey and espnowMessageEncryptionKey are not serialized. + * These will be set to the values of the EspnowMeshBackend instance that is adding the serialized encrypted connection. + * + * @param serializedConnectionState A String containing the serialized connection state. + * @param ignoreDuration Ignores any stored duration in serializedConnectionState, guaranteeing that the created connection will be permanent. + * @param duration The desired duration of the connection. Overrides any stored duration in the serializedConnectionState. + * + * @return EncryptedConnectionStatus::CONNECTION_ESTABLISHED if the connection was created. Otherwise another status code based on the outcome. EncryptedConnectionStatus::REQUEST_TRANSMISSION_FAILED indicates a malformed serializedConnectionState. + */ + EncryptedConnectionStatus addTemporaryEncryptedConnection(const String &serializedConnectionState, const uint32_t duration); + + /** + * Request a permanent encrypted ESP-NOW connection with the node that uses peerMac. + * If an encrypted connection to peerMac already exists, only connection duration is updated. All other settings are kept as is. Use removeEncryptedConnection/requestEncryptedConnectionRemoval first if encryption keys should be updated. + * The method makes sure both nodes have an encrypted connection to each other that's permanent. + * + * @param peerMac The MAC of the other node to which the request should be sent. + * + * @return EncryptedConnectionStatus::CONNECTION_ESTABLISHED if the permanent connection was created. EncryptedConnectionStatus::SOFT_LIMIT_CONNECTION_ESTABLISHED if only a temporary soft limit connection could be established (see the setEncryptedConnectionsSoftLimit method documentation for details). Otherwise another status code based on the outcome. + */ + EncryptedConnectionStatus requestEncryptedConnection(const uint8_t *peerMac); + + /** + * Request a temporary encrypted ESP-NOW connection with the node that uses peerMac. + * If a temporary encrypted connection to peerMac already exists, only connection duration is updated. All other settings are kept as is. Permanent connections are not modified. Use removeEncryptedConnection/requestEncryptedConnectionRemoval first if encryption keys should be updated. + * The method makes sure both nodes have an encrypted connection to each other that's either permanent or has exactly the duration specified. + * + * @param peerMac The MAC of the other node to which the request should be sent. + * @param durationMs The desired duration of the connection. + * + * @return EncryptedConnectionStatus::CONNECTION_ESTABLISHED if the request was succesful. EncryptedConnectionStatus::SOFT_LIMIT_CONNECTION_ESTABLISHED if only a temporary soft limit connection could be established (see the setEncryptedConnectionsSoftLimit method documentation for details). Otherwise another status code based on the outcome. + */ + EncryptedConnectionStatus requestTemporaryEncryptedConnection(const uint8_t *peerMac, const uint32_t durationMs); + + /** + * Request a flexible temporary encrypted ESP-NOW connection with the node that uses peerMac. + * If a temporary encrypted connection to peerMac with a shorter duration already exists, connection duration is updated. All other settings are kept as is. Permanent connections are not modified. Use removeEncryptedConnection/requestEncryptedConnectionRemoval first if encryption keys should be updated. + * The method makes sure both nodes have an encrypted connection to each other that's either permanent or has at least the duration specified. + * + * Note that if a temporary encrypted connection already exists to a target node, this method will slightly extend the connection duration + * depending on the time it takes to verify the connection to the node. + * + * @param peerMac The MAC of the other node to which the request should be sent. + * @param minDurationMs The desired minimum duration of the connection. + * + * @return EncryptedConnectionStatus::CONNECTION_ESTABLISHED if the request was succesful. EncryptedConnectionStatus::SOFT_LIMIT_CONNECTION_ESTABLISHED if only a temporary soft limit connection could be established (see the setEncryptedConnectionsSoftLimit method documentation for details). Otherwise another status code based on the outcome. + */ + EncryptedConnectionStatus requestFlexibleTemporaryEncryptedConnection(const uint8_t *peerMac, const uint32_t minDurationMs); + + /** + * Remove the encrypted ESP-NOW connection to peerMac from this node. + * Note that this will not remove the encrypted ESP-NOW connection automatically from the other node. Thus the same method will need to be called on the other node as well to complete the encrypted connection removal. + * The method requestEncryptedConnectionRemoval removes the encrypted connection automatically in both nodes, but requires extra information exchange between the nodes (and is thus much slower). + * + * @param peerMac The MAC of the other node. + * + * @return EncryptedConnectionRemovalOutcome::REMOVAL_SUCCEEDED if the removal succeeded. EncryptedConnectionRemovalOutcome::REMOVAL_SCHEDULED if the removal is scheduled to occur as soon as it is safe to do so (generally as soon as an ongoing transmission is complete, or at the latest during the next performEspnowMaintenance call). Otherwise another status code based on the outcome. + */ + static EncryptedConnectionRemovalOutcome removeEncryptedConnection(const uint8_t *peerMac); + + /** + * Request the removal of the encrypted ESP-NOW connection between this node and the node that uses peerMac. + * The method makes sure both nodes remove the encrypted connection to each other. + * + * @param peerMac The MAC of the other node to which the request should be sent. + * + * @return EncryptedConnectionRemovalOutcome::REMOVAL_SUCCEEDED if the removal succeeded. Otherwise another status code based on the outcome (never REMOVAL_SCHEDULED). + */ + EncryptedConnectionRemovalOutcome requestEncryptedConnectionRemoval(const uint8_t *peerMac); /** * Set whether this EspnowMeshBackend instance will accept ESP-NOW requests from unencrypted connections or not, when acting as EspnowRequestManager. @@ -777,8 +877,8 @@ class EspnowMeshBackend : public MeshBackendBase { * * @param acceptsUnverifiedRequests If and only if true, requests from unencrypted connections will be processed when this EspnowMeshBackend instance is acting as EspnowRequestManager. */ - void setAcceptsUnverifiedRequests(bool acceptsUnverifiedRequests); - bool acceptsUnverifiedRequests(); + void setAcceptsUnverifiedRequests(const bool acceptsUnverifiedRequests); + bool acceptsUnverifiedRequests() const; /** * Set a soft upper limit on the number of encrypted connections this node can have when receiving encrypted connection requests. @@ -795,15 +895,17 @@ class EspnowMeshBackend : public MeshBackendBase { * * @param softLimit The new soft limit. Valid values are 0 to 6. */ - void setEncryptedConnectionsSoftLimit(uint8_t softLimit); - uint8_t encryptedConnectionsSoftLimit(); + void setEncryptedConnectionsSoftLimit(const uint8_t softLimit); + uint8_t encryptedConnectionsSoftLimit() const; /** * @return The current number of encrypted ESP-NOW connections. */ static uint8_t numberOfEncryptedConnections(); - // @return resultArray filled with the MAC to the encrypted interface of the node, if an encrypted connection exists. nulltpr otherwise. + /** + * @return resultArray filled with the MAC to the encrypted interface of the node, if an encrypted connection exists. nulltpr otherwise. + */ static uint8_t *getEncryptedMac(const uint8_t *peerMac, uint8_t *resultArray); /** @@ -815,12 +917,14 @@ class EspnowMeshBackend : public MeshBackendBase { */ static String serializeUnencryptedConnection(); - // Create a string containing the current state of the encrypted connection for this node. The result can be used as input to addEncryptedConnection. - // Note that transferring the serialized state over an unencrypted connection will compromise the security of the stored connection. - // Also note that this saves the current state only, so if encrypted communication between the nodes happen after this, the stored state is invalid. - // @return A String containing the serialized encrypted connection, or an empty String if there is no matching encrypted connection. + /** + * Create a string containing the current state of the encrypted connection for this node. The result can be used as input to addEncryptedConnection. + * Note that transferring the serialized state over an unencrypted connection will compromise the security of the stored connection. + * Also note that this saves the current state only, so if encrypted communication between the nodes happen after this, the stored state is invalid. + * @return A String containing the serialized encrypted connection, or an empty String if there is no matching encrypted connection. + */ static String serializeEncryptedConnection(const uint8_t *peerMac); - static String serializeEncryptedConnection(uint32_t connectionIndex); + static String serializeEncryptedConnection(const uint32_t connectionIndex); /** * Get information about any current ESP-NOW connection with another node. @@ -845,7 +949,7 @@ class EspnowMeshBackend : public MeshBackendBase { * Otherwise the array is not modified. * @return The ConnectionType of the connection given by connectionIndex. */ - static ConnectionType getConnectionInfo(uint32_t connectionIndex, uint32_t *remainingDuration = nullptr, uint8_t *peerMac = nullptr); + static ConnectionType getConnectionInfo(const uint32_t connectionIndex, uint32_t *remainingDuration = nullptr, uint8_t *peerMac = nullptr); /** * @return The proportion of ESP-NOW requests made by this node that have failed, since power on or latest reset. @@ -870,7 +974,7 @@ class EspnowMeshBackend : public MeshBackendBase { bool activateEspnow(); - static bool encryptedConnectionEstablished(EncryptedConnectionStatus connectionStatus); + static bool encryptedConnectionEstablished(const EncryptedConnectionStatus connectionStatus); /* * Note that ESP-NOW is not perfect and in rare cases messages may be dropped. @@ -902,7 +1006,7 @@ class EspnowMeshBackend : public MeshBackendBase { static uint32_t getMaxBytesPerTransmission(); - static std::list::const_iterator getScheduledResponse(uint32_t responseIndex); + static std::list::const_iterator getScheduledResponse(const uint32_t responseIndex); // Note that removing an encrypted connection while there are encrypted responses scheduled for transmission to the encrypted peer will cause these encrypted responses to be removed without being sent. // Also note that removing an encrypted connection while there is encrypted data to be received will make the node unable to decrypt that data (although an ack will still be sent to confirm data reception). @@ -918,23 +1022,23 @@ class EspnowMeshBackend : public MeshBackendBase { * * @param macArray An uint8_t array which contains the MAC address to store. The method will store the first 6 bytes of the array. */ - void setSenderMac(uint8_t *macArray); + void setSenderMac(const uint8_t *macArray); /** * Set the MAC address considered to be the AP MAC of the sender of the most recently received ESP-NOW request, response or broadcast. * * @param macArray An uint8_t array which contains the MAC address to store. The method will store the first 6 bytes of the array. */ - void setSenderAPMac(uint8_t *macArray); + void setSenderAPMac(const uint8_t *macArray); /** * Set whether the most recently received ESP-NOW request, response or broadcast is presented as having been sent over an encrypted connection or not * * @param receivedEncryptedTransmission If true, the request, response or broadcast is presented as having been sent over an encrypted connection. */ - void setReceivedEncryptedTransmission(bool receivedEncryptedTransmission); + void setReceivedEncryptedTransmission(const bool receivedEncryptedTransmission); - static bool temporaryEncryptedConnectionToPermanent(uint8_t *peerMac); + static bool temporaryEncryptedConnectionToPermanent(const uint8_t *peerMac); /** * Will be true if a transmission initiated by a public method is in progress. @@ -962,7 +1066,7 @@ class EspnowMeshBackend : public MeshBackendBase { using messageID_td = uint64_t; using peerMac_td = uint64_t; - static macAndType_td createMacAndTypeValue(uint64_t uint64Mac, char messageType); + static macAndType_td createMacAndTypeValue(const uint64_t uint64Mac, const char messageType); static uint64_t macAndTypeToUint64Mac(const macAndType_td &macAndTypeValue); /** @@ -974,12 +1078,12 @@ class EspnowMeshBackend : public MeshBackendBase { * @param encryptedOnly If true, only entries sent/received by encrypted transmissions will be deleted. */ template - static void deleteEntriesByMac(std::map, T> &logEntries, const uint8_t *peerMac, bool encryptedOnly); + static void deleteEntriesByMac(std::map, T> &logEntries, const uint8_t *peerMac, const bool encryptedOnly); template - static void deleteEntriesByMac(std::map, T> &logEntries, const uint8_t *peerMac, bool encryptedOnly); + static void deleteEntriesByMac(std::map, T> &logEntries, const uint8_t *peerMac, const bool encryptedOnly); - static bool requestReceived(uint64_t requestMac, uint64_t requestID); + static bool requestReceived(const uint64_t requestMac, const uint64_t requestID); /** * Send an ESP-NOW message to the ESP8266 that has the MAC address specified in targetBSSID. @@ -989,17 +1093,17 @@ class EspnowMeshBackend : public MeshBackendBase { * @return The transmission status for the transmission. */ // Send a message to the node having targetBSSID as mac, changing targetBSSID to the mac of the encrypted connection if it exists and ensuring such an encrypted connection is synchronized. - static TransmissionStatusType espnowSendToNode(const String &message, const uint8_t *targetBSSID, char messageType, EspnowMeshBackend *espnowInstance = nullptr); + static TransmissionStatusType espnowSendToNode(const String &message, const uint8_t *targetBSSID, const char messageType, EspnowMeshBackend *espnowInstance = nullptr); // Send a message using exactly the arguments given, without consideration for any encrypted connections. - static TransmissionStatusType espnowSendToNodeUnsynchronized(const String message, const uint8_t *targetBSSID, char messageType, uint64_t messageID, EspnowMeshBackend *espnowInstance = nullptr); + static TransmissionStatusType espnowSendToNodeUnsynchronized(const String message, const uint8_t *targetBSSID, const char messageType, const uint64_t messageID, EspnowMeshBackend *espnowInstance = nullptr); TransmissionStatusType sendRequest(const String &message, const uint8_t *targetBSSID); - TransmissionStatusType sendResponse(const String &message, uint64_t requestID, const uint8_t *targetBSSID); + TransmissionStatusType sendResponse(const String &message, const uint64_t requestID, const uint8_t *targetBSSID); private: - EspnowMeshBackend(requestHandlerType requestHandler, responseHandlerType responseHandler, networkFilterType networkFilter, broadcastFilterType broadcastFilter, - const String &meshPassword, const String &ssidPrefix, const String &ssidSuffix, bool verboseMode, uint8 meshWiFiChannel); + EspnowMeshBackend(const requestHandlerType requestHandler, const responseHandlerType responseHandler, const networkFilterType networkFilter, const broadcastFilterType broadcastFilter, + const String &meshPassword, const String &ssidPrefix, const String &ssidSuffix, const bool verboseMode, const uint8 meshWiFiChannel); using encryptionRequestBuilderType = std::function; static String defaultEncryptionRequestBuilder(const String &requestHeader, const uint32_t durationMs, const uint8_t *hashKey, const String &requestNonce, const ExpiringTimeTracker &existingTimeTracker); @@ -1011,19 +1115,19 @@ class EspnowMeshBackend : public MeshBackendBase { * This method is very time critical so avoid Serial printing in it and in methods called from it (such as espnowReceiveCallback) as much as possible. * Otherwise transmission fail rate is likely to skyrocket. */ - static void espnowReceiveCallbackWrapper(uint8_t *macaddr, uint8_t *dataArray, uint8_t len); - void espnowReceiveCallback(uint8_t *macaddr, uint8_t *data, uint8_t len); + static void espnowReceiveCallbackWrapper(uint8_t *macaddr, uint8_t *dataArray, const uint8_t len); + void espnowReceiveCallback(const uint8_t *macaddr, uint8_t *data, const uint8_t len); - static void handlePeerRequest(uint8_t *macaddr, uint8_t *dataArray, uint8_t len, uint64_t uint64StationMac, uint64_t receivedMessageID); - static void handlePeerRequestConfirmation(uint8_t *macaddr, uint8_t *dataArray, uint8_t len); + static void handlePeerRequest(const uint8_t *macaddr, uint8_t *dataArray, const uint8_t len, const uint64_t uint64StationMac, const uint64_t receivedMessageID); + static void handlePeerRequestConfirmation(uint8_t *macaddr, uint8_t *dataArray, const uint8_t len); static void handlePostponedRemovals(); - static bool verifyPeerSessionKey(uint64_t sessionKey, const uint8_t *peerMac, char messageType); - static bool verifyPeerSessionKey(uint64_t sessionKey, EncryptedConnectionLog &encryptedConnection, uint64_t uint64PeerMac, char messageType); + static bool verifyPeerSessionKey(const uint64_t sessionKey, const uint8_t *peerMac, const char messageType); + static bool verifyPeerSessionKey(const uint64_t sessionKey, const EncryptedConnectionLog &encryptedConnection, const uint64_t uint64PeerMac, const char messageType); - static bool synchronizePeerSessionKey(uint64_t sessionKey, const uint8_t *peerMac); - static bool synchronizePeerSessionKey(uint64_t sessionKey, EncryptedConnectionLog &encryptedConnection); + static bool synchronizePeerSessionKey(const uint64_t sessionKey, const uint8_t *peerMac); + static bool synchronizePeerSessionKey(const uint64_t sessionKey, EncryptedConnectionLog &encryptedConnection); static const uint32_t _maxBytesPerTransmission = 250; static uint8_t _maxTransmissionsPerMessage; @@ -1071,18 +1175,18 @@ class EspnowMeshBackend : public MeshBackendBase { // Should only be used when there is no transmissions in progress, so it is safe to remove encrypted connections. In practice when _espnowTransmissionMutex is free. // @param scheduledRemovalOnly If true, only deletes encrypted connections where removalScheduled() is true. This means only connections which have been requested for removal will be deleted, // not other connections which have expired. - static void updateTemporaryEncryptedConnections(bool scheduledRemovalOnly = false); + static void updateTemporaryEncryptedConnections(const bool scheduledRemovalOnly = false); template - static void deleteExpiredLogEntries(std::map, T> &logEntries, uint32_t maxEntryLifetimeMs); + static void deleteExpiredLogEntries(std::map, T> &logEntries, const uint32_t maxEntryLifetimeMs); template - static void deleteExpiredLogEntries(std::map, TimeTracker> &logEntries, uint32_t maxEntryLifetimeMs); + static void deleteExpiredLogEntries(std::map, TimeTracker> &logEntries, const uint32_t maxEntryLifetimeMs); - static void deleteExpiredLogEntries(std::map, RequestData> &logEntries, uint32_t requestLifetimeMs, uint32_t broadcastLifetimeMs); + static void deleteExpiredLogEntries(std::map, RequestData> &logEntries, const uint32_t requestLifetimeMs, const uint32_t broadcastLifetimeMs); template - static void deleteExpiredLogEntries(std::list &logEntries, uint32_t maxEntryLifetimeMs); + static void deleteExpiredLogEntries(std::list &logEntries, const uint32_t maxEntryLifetimeMs); static uint32_t _logEntryLifetimeMs; static uint32_t _broadcastResponseTimeoutMs; @@ -1106,9 +1210,9 @@ class EspnowMeshBackend : public MeshBackendBase { static bool _reciprocalPeerRequestConfirmation; template - static T *getMapValue(std::map &mapIn, uint64_t keyIn); + static T *getMapValue(std::map &mapIn, const uint64_t keyIn); - static bool usesConstantSessionKey(char messageType); + static bool usesConstantSessionKey(const char messageType); bool _acceptsUnverifiedRequests = true; @@ -1135,16 +1239,16 @@ class EspnowMeshBackend : public MeshBackendBase { * * @return A valid EspnowMeshBackend pointer if a matching entry is found in the EspnowMeshBackend sentRequests container. nullptr otherwise. */ - static EspnowMeshBackend *getOwnerOfSentRequest(uint64_t requestMac, uint64_t requestID); + static EspnowMeshBackend *getOwnerOfSentRequest(const uint64_t requestMac, const uint64_t requestID); /** * Delete all entries in the sentRequests container where requestMac is noted as having received requestID. * * @return The number of entries deleted. */ - static size_t deleteSentRequest(uint64_t requestMac, uint64_t requestID); + static size_t deleteSentRequest(const uint64_t requestMac, const uint64_t requestID); - static size_t deleteSentRequestsByOwner(EspnowMeshBackend *instancePointer); + static size_t deleteSentRequestsByOwner(const EspnowMeshBackend *instancePointer); /** * Contains the core logic used for requesting an encrypted connection to a peerMac. @@ -1155,7 +1259,7 @@ class EspnowMeshBackend : public MeshBackendBase { * The request message should typically be of the form: JsonTranslator::createEncryptionRequestIntro() + JsonTranslator::createEncryptionRequestEnding(). * @return The ultimate status of the requested encrypted connection, as EncryptedConnectionStatus. */ - EncryptedConnectionStatus requestEncryptedConnectionKernel(uint8_t *peerMac, const encryptionRequestBuilderType &encryptionRequestBuilder); + EncryptedConnectionStatus requestEncryptedConnectionKernel(const uint8_t *peerMac, const encryptionRequestBuilderType &encryptionRequestBuilder); /** * Generate a new message ID to be used when making a data transmission. The generated ID will be different depending on whether an encrypted connection exists or not. @@ -1163,7 +1267,7 @@ class EspnowMeshBackend : public MeshBackendBase { * @param encryptedConnection A pointer to the EncryptedConnectionLog of the encrypted connection. Can be set to nullptr if the connection is unecrypted. * @return The generated message ID. */ - static uint64_t generateMessageID(EncryptedConnectionLog *encryptedConnection); + static uint64_t generateMessageID(const EncryptedConnectionLog *encryptedConnection); /** * Create a new session key for an encrypted connection using the built in RANDOM_REG32 of the ESP8266. @@ -1174,14 +1278,14 @@ class EspnowMeshBackend : public MeshBackendBase { */ static uint64_t createSessionKey(); - void prepareForTransmission(const String &message, bool scan, bool scanAllWiFiChannels); + void prepareForTransmission(const String &message, const bool scan, const bool scanAllWiFiChannels); TransmissionStatusType initiateTransmission(const String &message, const EspnowNetworkInfo &recipientInfo); TransmissionStatusType initiateTransmissionKernel(const String &message, const uint8_t *targetBSSID); - void printTransmissionStatistics(); + void printTransmissionStatistics() const; - EncryptedConnectionStatus initiateAutoEncryptingConnection(const EspnowNetworkInfo &recipientInfo, bool requestPermanentConnection, uint8_t *targetBSSID, EncryptedConnectionLog **existingEncryptedConnection); - TransmissionStatusType initiateAutoEncryptingTransmission(const String &message, const uint8_t *targetBSSID, EncryptedConnectionStatus connectionStatus); - void finalizeAutoEncryptingConnection(const uint8_t *targetBSSID, const EncryptedConnectionLog *existingEncryptedConnection, bool requestPermanentConnection); + EncryptedConnectionStatus initiateAutoEncryptingConnection(const EspnowNetworkInfo &recipientInfo, const bool requestPermanentConnection, uint8_t *targetBSSID, EncryptedConnectionLog **existingEncryptedConnection); + TransmissionStatusType initiateAutoEncryptingTransmission(const String &message, uint8_t *targetBSSID, const EncryptedConnectionStatus connectionStatus); + void finalizeAutoEncryptingConnection(const uint8_t *targetBSSID, const EncryptedConnectionLog *existingEncryptedConnection, const bool requestPermanentConnection); // Used for verboseMode printing in attemptTransmission, _AT suffix used to reduce namespace clutter uint32_t totalDurationWhenSuccessful_AT = 0; diff --git a/libraries/ESP8266WiFiMesh/src/EspnowNetworkInfo.cpp b/libraries/ESP8266WiFiMesh/src/EspnowNetworkInfo.cpp index d68f241566..6e52925e39 100644 --- a/libraries/ESP8266WiFiMesh/src/EspnowNetworkInfo.cpp +++ b/libraries/ESP8266WiFiMesh/src/EspnowNetworkInfo.cpp @@ -25,14 +25,14 @@ #include "EspnowNetworkInfo.h" #include -EspnowNetworkInfo::EspnowNetworkInfo(int networkIndex) : NetworkInfoBase(networkIndex) { }; +EspnowNetworkInfo::EspnowNetworkInfo(const int networkIndex) : NetworkInfoBase(networkIndex) { }; EspnowNetworkInfo::EspnowNetworkInfo(const NetworkInfoBase &originalNetworkInfo) : NetworkInfoBase(originalNetworkInfo) { assert(BSSID() != defaultBSSID); // We need at least BSSID to be able to connect. }; -EspnowNetworkInfo::EspnowNetworkInfo(const uint8_t BSSID[6], const String &SSID, int32_t wifiChannel, uint8_t encryptionType, int32_t RSSI , bool isHidden) +EspnowNetworkInfo::EspnowNetworkInfo(const uint8_t BSSID[6], const String &SSID, const int32_t wifiChannel, const uint8_t encryptionType, const int32_t RSSI , const bool isHidden) : NetworkInfoBase(SSID, wifiChannel, BSSID, encryptionType, RSSI, isHidden) { } diff --git a/libraries/ESP8266WiFiMesh/src/EspnowNetworkInfo.h b/libraries/ESP8266WiFiMesh/src/EspnowNetworkInfo.h index fecb7c5b3a..06b39b987c 100644 --- a/libraries/ESP8266WiFiMesh/src/EspnowNetworkInfo.h +++ b/libraries/ESP8266WiFiMesh/src/EspnowNetworkInfo.h @@ -34,12 +34,12 @@ class EspnowNetworkInfo : public NetworkInfoBase { /** * Automatically fill in the rest of the network info using networkIndex and the WiFi scan results. */ - EspnowNetworkInfo(int networkIndex); + EspnowNetworkInfo(const int networkIndex); EspnowNetworkInfo(const NetworkInfoBase &originalNetworkInfo); - EspnowNetworkInfo(const uint8_t BSSID[6], const String &SSID = defaultSSID, int32_t wifiChannel = defaultWifiChannel, uint8_t encryptionType = defaultEncryptionType, - int32_t RSSI = defaultRSSI, bool isHidden = defaultIsHidden); + EspnowNetworkInfo(const uint8_t BSSID[6], const String &SSID = defaultSSID, const int32_t wifiChannel = defaultWifiChannel, const uint8_t encryptionType = defaultEncryptionType, + const int32_t RSSI = defaultRSSI, const bool isHidden = defaultIsHidden); }; #endif diff --git a/libraries/ESP8266WiFiMesh/src/EspnowProtocolInterpreter.cpp b/libraries/ESP8266WiFiMesh/src/EspnowProtocolInterpreter.cpp index cbbacd538b..d50a6f33e1 100644 --- a/libraries/ESP8266WiFiMesh/src/EspnowProtocolInterpreter.cpp +++ b/libraries/ESP8266WiFiMesh/src/EspnowProtocolInterpreter.cpp @@ -36,7 +36,7 @@ namespace EspnowProtocolInterpreter return espnowProtocolBytesSize + (EspnowMeshBackend::useEncryptedMessages() ? aeadMetadataSize : 0); } - String espnowGetMessageContent(uint8_t *transmissionDataArray, uint8_t transmissionLength) + String espnowGetMessageContent(uint8_t *transmissionDataArray, const uint8_t transmissionLength) { String messageContent = emptyString; @@ -81,12 +81,12 @@ namespace EspnowProtocolInterpreter return TypeCast::uint8ArrayToUint64(transmissionDataArray + espnowMessageIDIndex); } - uint8_t *espnowSetMessageID(uint8_t *transmissionDataArray, uint64_t messageID) + uint8_t *espnowSetMessageID(uint8_t *transmissionDataArray, const uint64_t messageID) { return TypeCast::uint64ToUint8Array(messageID, transmissionDataArray + espnowMessageIDIndex); } - bool usesEncryption(uint64_t messageID) + bool usesEncryption(const uint64_t messageID) { // At least one of the leftmost half of bits in messageID is 1 if the transmission is encrypted. return messageID & uint64LeftmostBits; diff --git a/libraries/ESP8266WiFiMesh/src/EspnowProtocolInterpreter.h b/libraries/ESP8266WiFiMesh/src/EspnowProtocolInterpreter.h index 8a227669c2..20617193c4 100644 --- a/libraries/ESP8266WiFiMesh/src/EspnowProtocolInterpreter.h +++ b/libraries/ESP8266WiFiMesh/src/EspnowProtocolInterpreter.h @@ -63,7 +63,7 @@ namespace EspnowProtocolInterpreter constexpr uint64_t uint64LeftmostBits = 0xFFFFFFFF00000000; - String espnowGetMessageContent(uint8_t *transmissionDataArray, uint8_t transmissionLength); + String espnowGetMessageContent(uint8_t *transmissionDataArray, const uint8_t transmissionLength); char espnowGetMessageType(const uint8_t *transmissionDataArray); uint8_t espnowGetTransmissionsRemaining(const uint8_t *transmissionDataArray); bool espnowIsMessageStart(const uint8_t *transmissionDataArray); @@ -71,9 +71,9 @@ namespace EspnowProtocolInterpreter uint8_t *espnowGetTransmissionMac(const uint8_t *transmissionDataArray, uint8_t *resultArray); uint64_t espnowGetMessageID(const uint8_t *transmissionDataArray); // @return a pointer to transmissionDataArray - uint8_t *espnowSetMessageID(uint8_t *transmissionDataArray, uint64_t messageID); + uint8_t *espnowSetMessageID(uint8_t *transmissionDataArray, const uint64_t messageID); - bool usesEncryption(uint64_t messageID); + bool usesEncryption(const uint64_t messageID); } #endif diff --git a/libraries/ESP8266WiFiMesh/src/FloodingMesh.cpp b/libraries/ESP8266WiFiMesh/src/FloodingMesh.cpp index 1f88dadf50..bea1e2f287 100644 --- a/libraries/ESP8266WiFiMesh/src/FloodingMesh.cpp +++ b/libraries/ESP8266WiFiMesh/src/FloodingMesh.cpp @@ -38,7 +38,7 @@ std::set FloodingMesh::availableFloodingMeshes = {}; char FloodingMesh::_metadataDelimiter = 23; -void floodingMeshDelay(uint32_t durationMs) +void floodingMeshDelay(const uint32_t durationMs) { ExpiringTimeTracker timeout(durationMs); @@ -54,7 +54,7 @@ void floodingMeshDelay(uint32_t durationMs) FloodingMesh::FloodingMesh(messageHandlerType messageHandler, const String &meshPassword, const uint8_t espnowEncryptedConnectionKey[EspnowProtocolInterpreter::espnowEncryptedConnectionKeyLength], const uint8_t espnowHashKey[EspnowProtocolInterpreter::espnowHashKeyLength], const String &ssidPrefix, - const String &ssidSuffix, bool verboseMode, uint8 meshWiFiChannel) + const String &ssidSuffix, const bool verboseMode, const uint8 meshWiFiChannel) : _espnowBackend( [this](const String &request, MeshBackendBase &meshInstance){ return _defaultRequestHandler(request, meshInstance); }, [this](const String &response, MeshBackendBase &meshInstance){ return _defaultResponseHandler(response, meshInstance); }, @@ -68,7 +68,7 @@ FloodingMesh::FloodingMesh(messageHandlerType messageHandler, const String &mesh } FloodingMesh::FloodingMesh(messageHandlerType messageHandler, const String &meshPassword, const String &espnowEncryptedConnectionKeySeed, const String &espnowHashKeySeed, - const String &ssidPrefix, const String &ssidSuffix, bool verboseMode, uint8 meshWiFiChannel) + const String &ssidPrefix, const String &ssidSuffix, const bool verboseMode, const uint8 meshWiFiChannel) : FloodingMesh(messageHandler, meshPassword, (const uint8_t[EspnowProtocolInterpreter::espnowEncryptedConnectionKeyLength]){0}, (const uint8_t[EspnowProtocolInterpreter::espnowHashKeyLength]){0}, ssidPrefix, ssidSuffix, verboseMode, meshWiFiChannel) { @@ -79,7 +79,7 @@ FloodingMesh::FloodingMesh(messageHandlerType messageHandler, const String &mesh FloodingMesh::FloodingMesh(const String &serializedMeshState, messageHandlerType messageHandler, const String &meshPassword, const uint8_t espnowEncryptedConnectionKey[EspnowProtocolInterpreter::espnowEncryptedConnectionKeyLength], const uint8_t espnowHashKey[EspnowProtocolInterpreter::espnowHashKeyLength], const String &ssidPrefix, - const String &ssidSuffix, bool verboseMode, uint8 meshWiFiChannel) + const String &ssidSuffix, const bool verboseMode, const uint8 meshWiFiChannel) : FloodingMesh(messageHandler, meshPassword, espnowEncryptedConnectionKey, espnowHashKey, ssidPrefix, ssidSuffix, verboseMode, meshWiFiChannel) { loadMeshState(serializedMeshState); @@ -87,7 +87,7 @@ FloodingMesh::FloodingMesh(const String &serializedMeshState, messageHandlerType FloodingMesh::FloodingMesh(const String &serializedMeshState, messageHandlerType messageHandler, const String &meshPassword, const String &espnowEncryptedConnectionKeySeed, const String &espnowHashKeySeed, const String &ssidPrefix, - const String &ssidSuffix, bool verboseMode, uint8 meshWiFiChannel) + const String &ssidSuffix, const bool verboseMode, const uint8 meshWiFiChannel) : FloodingMesh(messageHandler, meshPassword, espnowEncryptedConnectionKeySeed, espnowHashKeySeed, ssidPrefix, ssidSuffix, verboseMode, meshWiFiChannel) { loadMeshState(serializedMeshState); @@ -151,13 +151,13 @@ void FloodingMesh::performMeshInstanceMaintenance() } } -String FloodingMesh::serializeMeshState() +String FloodingMesh::serializeMeshState() const { using namespace JsonTranslator; // Returns: {"meshState":{"connectionState":{"unsyncMsgID":"123"},"meshMsgCount":"123"}} - String connectionState = getEspnowMeshBackend().serializeUnencryptedConnection(); + String connectionState = getEspnowMeshBackendConst().serializeUnencryptedConnection(); return String(F("{\"meshState\":{")) @@ -204,12 +204,12 @@ void FloodingMesh::broadcastKernel(const String &message) getEspnowMeshBackend().broadcast(message); } -void FloodingMesh::setBroadcastReceptionRedundancy(uint8_t redundancy) +void FloodingMesh::setBroadcastReceptionRedundancy(const uint8_t redundancy) { assert(redundancy < 255); _broadcastReceptionRedundancy = redundancy; } -uint8_t FloodingMesh::getBroadcastReceptionRedundancy() { return _broadcastReceptionRedundancy; } +uint8_t FloodingMesh::getBroadcastReceptionRedundancy() const { return _broadcastReceptionRedundancy; } void FloodingMesh::encryptedBroadcast(const String &message) { @@ -236,40 +236,40 @@ void FloodingMesh::clearForwardingBacklog() _forwardingBacklog.clear(); } -void FloodingMesh::setMessageHandler(messageHandlerType messageHandler) { _messageHandler = messageHandler; } -FloodingMesh::messageHandlerType FloodingMesh::getMessageHandler() { return _messageHandler; } +void FloodingMesh::setMessageHandler(const messageHandlerType messageHandler) { _messageHandler = messageHandler; } +FloodingMesh::messageHandlerType FloodingMesh::getMessageHandler() const { return _messageHandler; } -void FloodingMesh::setOriginMac(uint8_t *macArray) +void FloodingMesh::setOriginMac(const uint8_t *macArray) { std::copy_n(macArray, 6, _originMac); } -String FloodingMesh::getOriginMac() { return TypeCast::macToString(_originMac); } -uint8_t *FloodingMesh::getOriginMac(uint8_t *macArray) +String FloodingMesh::getOriginMac() const { return TypeCast::macToString(_originMac); } +uint8_t *FloodingMesh::getOriginMac(uint8_t *macArray) const { std::copy_n(_originMac, 6, macArray); return macArray; } -uint32_t FloodingMesh::maxUnencryptedMessageLength() +uint32_t FloodingMesh::maxUnencryptedMessageLength() const { - return getEspnowMeshBackend().getMaxMessageLength() - MESSAGE_ID_LENGTH - (getEspnowMeshBackend().getMeshName().length() + 1); // Need room for mesh name + delimiter + return getEspnowMeshBackendConst().getMaxMessageLength() - MESSAGE_ID_LENGTH - (getEspnowMeshBackendConst().getMeshName().length() + 1); // Need room for mesh name + delimiter } -uint32_t FloodingMesh::maxEncryptedMessageLength() +uint32_t FloodingMesh::maxEncryptedMessageLength() const { // Need 1 extra delimiter character for maximum metadata efficiency (makes it possible to store exactly 18 MACs in metadata by adding an extra transmission) - return getEspnowMeshBackend().getMaxMessageLength() - MESSAGE_ID_LENGTH - 1; + return getEspnowMeshBackendConst().getMaxMessageLength() - MESSAGE_ID_LENGTH - 1; } -void FloodingMesh::setMessageLogSize(uint16_t messageLogSize) +void FloodingMesh::setMessageLogSize(const uint16_t messageLogSize) { assert(messageLogSize >= 1); _messageLogSize = messageLogSize; } -uint16_t FloodingMesh::messageLogSize() { return _messageLogSize; } +uint16_t FloodingMesh::messageLogSize() const { return _messageLogSize; } -void FloodingMesh::setMetadataDelimiter(char metadataDelimiter) +void FloodingMesh::setMetadataDelimiter(const char metadataDelimiter) { // Using HEX number characters as a delimiter is a bad idea regardless of broadcast type, since they are always in the broadcast metadata. // We therefore check for those characters below. @@ -286,7 +286,12 @@ EspnowMeshBackend &FloodingMesh::getEspnowMeshBackend() return _espnowBackend; } -bool FloodingMesh::insertPreliminaryMessageID(uint64_t messageID) +const EspnowMeshBackend &FloodingMesh::getEspnowMeshBackendConst() const +{ + return _espnowBackend; +} + +bool FloodingMesh::insertPreliminaryMessageID(const uint64_t messageID) { uint8_t apMacArray[6] = { 0 }; if(messageID >> 16 == TypeCast::macToUint64(WiFi.softAPmacAddress(apMacArray))) @@ -304,7 +309,7 @@ bool FloodingMesh::insertPreliminaryMessageID(uint64_t messageID) return true; } -bool FloodingMesh::insertCompletedMessageID(uint64_t messageID) +bool FloodingMesh::insertCompletedMessageID(const uint64_t messageID) { uint8_t apMacArray[6] = { 0 }; if(messageID >> 16 == TypeCast::macToUint64(WiFi.softAPmacAddress(apMacArray))) @@ -322,7 +327,7 @@ bool FloodingMesh::insertCompletedMessageID(uint64_t messageID) return true; } -void FloodingMesh::updateMessageQueue(messageQueueElementType messageIterator) +void FloodingMesh::updateMessageQueue(const messageQueueElementType messageIterator) { _messageIdOrder.emplace(messageIterator); @@ -449,7 +454,7 @@ TransmissionStatusType FloodingMesh::_defaultResponseHandler(const String &respo * @param numberOfNetworks The number of networks found in the WiFi scan. * @param meshInstance The MeshBackendBase instance that called the function. */ -void FloodingMesh::_defaultNetworkFilter(int numberOfNetworks, MeshBackendBase &meshInstance) +void FloodingMesh::_defaultNetworkFilter(const int numberOfNetworks, MeshBackendBase &meshInstance) { // Note that the network index of a given node may change whenever a new scan is done. for (int networkIndex = 0; networkIndex < numberOfNetworks; ++networkIndex) @@ -554,7 +559,7 @@ bool FloodingMesh::_defaultTransmissionOutcomesUpdateHook(MeshBackendBase &meshI * @return True if the response transmission process should continue with the next response in the waiting list. * False if the response transmission process should stop after removing the just sent response from the waiting list. */ -bool FloodingMesh::_defaultResponseTransmittedHook(const String &response, const uint8_t *recipientMac, uint32_t responseIndex, EspnowMeshBackend &meshInstance) +bool FloodingMesh::_defaultResponseTransmittedHook(const String &response, const uint8_t *recipientMac, const uint32_t responseIndex, EspnowMeshBackend &meshInstance) { (void)response; // This is useful to remove a "unused parameter" compiler warning. Does nothing else. (void)recipientMac; diff --git a/libraries/ESP8266WiFiMesh/src/FloodingMesh.h b/libraries/ESP8266WiFiMesh/src/FloodingMesh.h index 71c6621784..fc69f3f9ad 100644 --- a/libraries/ESP8266WiFiMesh/src/FloodingMesh.h +++ b/libraries/ESP8266WiFiMesh/src/FloodingMesh.h @@ -38,7 +38,7 @@ * * @param durationMs The shortest allowed delay duration, in milliseconds. */ -void floodingMeshDelay(uint32_t durationMs); +void floodingMeshDelay(const uint32_t durationMs); class FloodingMesh { @@ -69,7 +69,7 @@ class FloodingMesh { */ FloodingMesh(messageHandlerType messageHandler, const String &meshPassword, const uint8_t espnowEncryptedConnectionKey[EspnowProtocolInterpreter::espnowEncryptedConnectionKeyLength], const uint8_t espnowHashKey[EspnowProtocolInterpreter::espnowHashKeyLength], const String &ssidPrefix, - const String &ssidSuffix, bool verboseMode = false, uint8 meshWiFiChannel = 1); + const String &ssidSuffix, const bool verboseMode = false, const uint8 meshWiFiChannel = 1); /** * FloodingMesh constructor method. Creates a FloodingMesh node, ready to be initialised. @@ -90,7 +90,7 @@ class FloodingMesh { * */ FloodingMesh(messageHandlerType messageHandler, const String &meshPassword, const String &espnowEncryptedConnectionKeySeed, const String &espnowHashKeySeed, - const String &ssidPrefix, const String &ssidSuffix, bool verboseMode = false, uint8 meshWiFiChannel = 1); + const String &ssidPrefix, const String &ssidSuffix, const bool verboseMode = false, const uint8 meshWiFiChannel = 1); /** * This constructor should be used in combination with serializeMeshState() when the node has gone to sleep while other nodes stayed awake. @@ -101,7 +101,7 @@ class FloodingMesh { FloodingMesh(const String &serializedMeshState, messageHandlerType messageHandler, const String &meshPassword, const uint8_t espnowEncryptedConnectionKey[EspnowProtocolInterpreter::espnowEncryptedConnectionKeyLength], const uint8_t espnowHashKey[EspnowProtocolInterpreter::espnowHashKeyLength], const String &ssidPrefix, - const String &ssidSuffix, bool verboseMode = false, uint8 meshWiFiChannel = 1); + const String &ssidSuffix, const bool verboseMode = false, const uint8 meshWiFiChannel = 1); /** * This constructor should be used in combination with serializeMeshState() when the node has gone to sleep while other nodes stayed awake. @@ -110,7 +110,7 @@ class FloodingMesh { * @param serializedMeshState A String with a serialized mesh node state that the node should use. */ FloodingMesh(const String &serializedMeshState, messageHandlerType messageHandler, const String &meshPassword, const String &espnowEncryptedConnectionKeySeed, - const String &espnowHashKeySeed, const String &ssidPrefix, const String &ssidSuffix, bool verboseMode = false, uint8 meshWiFiChannel = 1); + const String &espnowHashKeySeed, const String &ssidPrefix, const String &ssidSuffix, const bool verboseMode = false, const uint8 meshWiFiChannel = 1); virtual ~FloodingMesh(); @@ -155,7 +155,7 @@ class FloodingMesh { * * @return A string with the serialized current mesh node state. */ - String serializeMeshState(); + String serializeMeshState() const; /** * Make an unencrypted broadcast to the entire mesh network. @@ -173,8 +173,8 @@ class FloodingMesh { * * @param redundancy The maximum number of extra copies that will be accepted. Defaults to 2. Valid values are 0 to 254. */ - void setBroadcastReceptionRedundancy(uint8_t redundancy); - uint8_t getBroadcastReceptionRedundancy(); + void setBroadcastReceptionRedundancy(const uint8_t redundancy); + uint8_t getBroadcastReceptionRedundancy() const; /** * Make an encrypted broadcast to the entire mesh network. @@ -205,8 +205,8 @@ class FloodingMesh { * * @param messageHandler The message handler callback function to use. */ - void setMessageHandler(messageHandlerType messageHandler); - messageHandlerType getMessageHandler(); + void setMessageHandler(const messageHandlerType messageHandler); + messageHandlerType getMessageHandler() const; /** * Get the origin AP MAC address of the most recently received mesh message. @@ -214,7 +214,7 @@ class FloodingMesh { * * @return A String filled with a hexadecimal representation of the MAC, without delimiters. */ - String getOriginMac(); + String getOriginMac() const; /** * Get the origin AP MAC address of the most recently received mesh message. @@ -223,7 +223,7 @@ class FloodingMesh { * @param macArray The array that should store the MAC address. Must be at least 6 bytes. * @return macArray filled with the origin MAC. */ - uint8_t *getOriginMac(uint8_t *macArray); + uint8_t *getOriginMac(uint8_t *macArray) const; /** * The number of received messageID:s that will be stored by the node. Used to remember which messages have been received. @@ -237,8 +237,8 @@ class FloodingMesh { * If a value close to the maximum is chosen, there is a high risk the node will ignore transmissions on messageID rollover if they are sent only by one node * (especially if some transmissions are missed), since the messageID also uses uint16_t. */ - void setMessageLogSize(uint16_t messageLogSize); - uint16_t messageLogSize(); + void setMessageLogSize(const uint16_t messageLogSize); + uint16_t messageLogSize() const; /** * Hint: Use String.length() to get the ASCII length of a String. @@ -247,7 +247,7 @@ class FloodingMesh { * Note that non-ASCII characters usually require at least two bytes each. * Also note that for unencrypted messages the maximum size will depend on getEspnowMeshBackend().getMeshName().length() */ - uint32_t maxUnencryptedMessageLength(); + uint32_t maxUnencryptedMessageLength() const; /** * Hint: Use String.length() to get the ASCII length of a String. @@ -255,7 +255,7 @@ class FloodingMesh { * @return The maximum length in bytes an encrypted ASCII message is allowed to be when broadcasted by this node. * Note that non-ASCII characters usually require at least two bytes each. */ - uint32_t maxEncryptedMessageLength(); + uint32_t maxEncryptedMessageLength() const; /** * Set the delimiter character used for metadata by every FloodingMesh instance. @@ -264,7 +264,7 @@ class FloodingMesh { * @param metadataDelimiter The metadata delimiter character to use. * Defaults to 23 = End-of-Transmission-Block (ETB) control character in ASCII */ - static void setMetadataDelimiter(char metadataDelimiter); + static void setMetadataDelimiter(const char metadataDelimiter); static char metadataDelimiter(); /* @@ -274,6 +274,7 @@ class FloodingMesh { * and so are discouraged for those who prefer it when things just work. */ EspnowMeshBackend &getEspnowMeshBackend(); + const EspnowMeshBackend &getEspnowMeshBackendConst() const; void restoreDefaultRequestHandler(); void restoreDefaultResponseHandler(); @@ -292,9 +293,9 @@ class FloodingMesh { void encryptedBroadcastKernel(const String &message); - bool insertPreliminaryMessageID(uint64_t messageID); - bool insertCompletedMessageID(uint64_t messageID); - void updateMessageQueue(messageQueueElementType messageIterator); + bool insertPreliminaryMessageID(const uint64_t messageID); + bool insertCompletedMessageID(const uint64_t messageID); + void updateMessageQueue(const messageQueueElementType messageIterator); void loadMeshState(const String &serializedMeshState); @@ -303,7 +304,7 @@ class FloodingMesh { * * @param macArray An uint8_t array which contains the MAC address to store. The method will store the first 6 bytes of the array. */ - void setOriginMac(uint8_t *macArray); + void setOriginMac(const uint8_t *macArray); private: @@ -328,10 +329,10 @@ class FloodingMesh { String _defaultRequestHandler(const String &request, MeshBackendBase &meshInstance); TransmissionStatusType _defaultResponseHandler(const String &response, MeshBackendBase &meshInstance); - void _defaultNetworkFilter(int numberOfNetworks, MeshBackendBase &meshInstance); + void _defaultNetworkFilter(const int numberOfNetworks, MeshBackendBase &meshInstance); bool _defaultBroadcastFilter(String &firstTransmission, EspnowMeshBackend &meshInstance); bool _defaultTransmissionOutcomesUpdateHook(MeshBackendBase &meshInstance); - bool _defaultResponseTransmittedHook(const String &response, const uint8_t *recipientMac, uint32_t responseIndex, EspnowMeshBackend &meshInstance); + bool _defaultResponseTransmittedHook(const String &response, const uint8_t *recipientMac, const uint32_t responseIndex, EspnowMeshBackend &meshInstance); }; #endif diff --git a/libraries/ESP8266WiFiMesh/src/JsonTranslator.cpp b/libraries/ESP8266WiFiMesh/src/JsonTranslator.cpp index 89531ec5f6..ea314145a1 100644 --- a/libraries/ESP8266WiFiMesh/src/JsonTranslator.cpp +++ b/libraries/ESP8266WiFiMesh/src/JsonTranslator.cpp @@ -41,7 +41,7 @@ namespace JsonTranslator 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, const uint64_t ownSessionKey, const uint64_t peerSessionKey) { // Returns: Encrypted connection info:{"arguments":{"nonce":"1F2","password":"abc","ownSK":"3B4","peerSK":"1A2"}} @@ -53,7 +53,7 @@ namespace JsonTranslator + createJsonEndPair(FPSTR(jsonPeerSessionKey), TypeCast::uint64ToString(ownSessionKey)); } - String createEncryptionRequestIntro(const String &requestHeader, uint32_t duration) + String createEncryptionRequestIntro(const String &requestHeader, const uint32_t duration) { return requestHeader + String(F("{\"arguments\":{")) @@ -65,7 +65,7 @@ namespace JsonTranslator 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, const uint8_t hashKeyLength, const uint32_t duration) { String mainMessage = createEncryptionRequestIntro(requestHeader, duration) + createJsonPair(FPSTR(jsonNonce), requestNonce); uint8_t staMac[6] {0}; @@ -76,7 +76,7 @@ namespace JsonTranslator } bool verifyEncryptionRequestHmac(const String &encryptionRequestHmacMessage, const uint8_t *requesterStaMac, const uint8_t *requesterApMac, - const uint8_t *hashKey, uint8_t hashKeyLength) + const uint8_t *hashKey, const uint8_t hashKeyLength) { using MeshCryptoInterface::verifyMeshHmac; @@ -97,7 +97,7 @@ namespace JsonTranslator return false; } - int32_t getStartIndex(const String &jsonString, const String &valueIdentifier, int32_t searchStartIndex) + int32_t getStartIndex(const String &jsonString, const String &valueIdentifier, const int32_t searchStartIndex) { int32_t startIndex = jsonString.indexOf(valueIdentifier, searchStartIndex); if(startIndex < 0) @@ -107,7 +107,7 @@ namespace JsonTranslator return startIndex; } - int32_t getEndIndex(const String &jsonString, int32_t searchStartIndex) + int32_t getEndIndex(const String &jsonString, const int32_t searchStartIndex) { int32_t endIndex = jsonString.indexOf(',', searchStartIndex); if(endIndex < 0) diff --git a/libraries/ESP8266WiFiMesh/src/JsonTranslator.h b/libraries/ESP8266WiFiMesh/src/JsonTranslator.h index 3070e9a999..7576dcb537 100644 --- a/libraries/ESP8266WiFiMesh/src/JsonTranslator.h +++ b/libraries/ESP8266WiFiMesh/src/JsonTranslator.h @@ -45,12 +45,12 @@ namespace JsonTranslator String createJsonPair(const String &valueIdentifier, const String &value); String createJsonEndPair(const String &valueIdentifier, const String &value); - String createEncryptedConnectionInfo(const String &infoHeader, const String &requestNonce, const String &authenticationPassword, uint64_t ownSessionKey, uint64_t peerSessionKey); - String createEncryptionRequestIntro(const String &requestHeader, uint32_t duration = 0); + String createEncryptedConnectionInfo(const String &infoHeader, const String &requestNonce, const String &authenticationPassword, const uint64_t ownSessionKey, const uint64_t peerSessionKey); + String createEncryptionRequestIntro(const String &requestHeader, const uint32_t duration = 0); String createEncryptionRequestEnding(const String &requestNonce); - String createEncryptionRequestHmacMessage(const String &requestHeader, const String &requestNonce, const uint8_t *hashKey, uint8_t hashKeyLength, uint32_t duration = 0); + String createEncryptionRequestHmacMessage(const String &requestHeader, const String &requestNonce, const uint8_t *hashKey, const uint8_t hashKeyLength, const uint32_t duration = 0); - bool verifyEncryptionRequestHmac(const String &encryptionRequestHmacMessage, const uint8_t *requesterStaMac, const uint8_t *requesterApMac, const uint8_t *hashKey, uint8_t hashKeyLength); + bool verifyEncryptionRequestHmac(const String &encryptionRequestHmacMessage, const uint8_t *requesterStaMac, const uint8_t *requesterApMac, const uint8_t *hashKey, const uint8_t hashKeyLength); /** * Provides the index within jsonString where the value of valueIdentifier starts. @@ -61,7 +61,7 @@ namespace JsonTranslator * * @return An int32_t containing the index within jsonString where the value of valueIdentifier starts, or a negative value if valueIdentifier was not found. */ - int32_t getStartIndex(const String &jsonString, const String &valueIdentifier, int32_t searchStartIndex = 0); + int32_t getStartIndex(const String &jsonString, const String &valueIdentifier, const int32_t searchStartIndex = 0); /** * Provides the index within jsonString where the next JSON termination character (',' or '}') is found, starting from searchStartIndex. @@ -71,7 +71,7 @@ namespace JsonTranslator * * @return An int32_t containing the index within jsonString where the next JSON termination character is found, or a negative value if no such character was found. */ - int32_t getEndIndex(const String &jsonString, int32_t searchStartIndex); + int32_t getEndIndex(const String &jsonString, const int32_t searchStartIndex); bool getConnectionState(const String &jsonString, String &result); /** diff --git a/libraries/ESP8266WiFiMesh/src/MeshBackendBase.cpp b/libraries/ESP8266WiFiMesh/src/MeshBackendBase.cpp index 33ed8b5650..25ae81d4ba 100644 --- a/libraries/ESP8266WiFiMesh/src/MeshBackendBase.cpp +++ b/libraries/ESP8266WiFiMesh/src/MeshBackendBase.cpp @@ -30,7 +30,7 @@ std::shared_ptr MeshBackendBase::_scanMutex = std::make_shared(false bool MeshBackendBase::_printWarnings = true; -MeshBackendBase::MeshBackendBase(requestHandlerType requestHandler, responseHandlerType responseHandler, networkFilterType networkFilter, MeshBackendType classType) +MeshBackendBase::MeshBackendBase(const requestHandlerType requestHandler, const responseHandlerType responseHandler, const networkFilterType networkFilter, const MeshBackendType classType) { setRequestHandler(requestHandler); setResponseHandler(responseHandler); @@ -43,12 +43,12 @@ MeshBackendBase::~MeshBackendBase() deactivateControlledAP(); } -void MeshBackendBase::setClassType(MeshBackendType classType) +void MeshBackendBase::setClassType(const MeshBackendType classType) { _classType = classType; } -MeshBackendType MeshBackendBase::getClassType() {return _classType;} +MeshBackendType MeshBackendBase::getClassType() const {return _classType;} void MeshBackendBase::activateAP() { @@ -108,12 +108,12 @@ MeshBackendBase *MeshBackendBase::getAPController() return apController; } -bool MeshBackendBase::isAPController() +bool MeshBackendBase::isAPController() const { return (this == getAPController()); } -void MeshBackendBase::setWiFiChannel(uint8 newWiFiChannel) +void MeshBackendBase::setWiFiChannel(const uint8 newWiFiChannel) { wifi_country_t wifiCountry; wifi_get_country(&wifiCountry); // Note: Should return 0 on success and -1 on failure, but always seems to return 1. Possibly broken API. Channels 1 to 13 are the default limits. @@ -188,14 +188,14 @@ void MeshBackendBase::setMeshName(const String &newMeshName) setSSIDPrefix(newMeshName); } -String MeshBackendBase::getMeshName() {return getSSIDPrefix();} +String MeshBackendBase::getMeshName() const {return getSSIDPrefix();} void MeshBackendBase::setNodeID(const String &newNodeID) { setSSIDSuffix(newNodeID); } -String MeshBackendBase::getNodeID() {return getSSIDSuffix();} +String MeshBackendBase::getNodeID() const {return getSSIDSuffix();} void MeshBackendBase::setMeshPassword(const String &newMeshPassword) { @@ -213,26 +213,26 @@ String MeshBackendBase::getMeshPassword() const {return _meshPassword;} void MeshBackendBase::setMessage(const String &newMessage) {_message = newMessage;} String MeshBackendBase::getMessage() const {return _message;} -void MeshBackendBase::setRequestHandler(MeshBackendBase::requestHandlerType requestHandler) {_requestHandler = requestHandler;} -MeshBackendBase::requestHandlerType MeshBackendBase::getRequestHandler() {return _requestHandler;} +void MeshBackendBase::setRequestHandler(const MeshBackendBase::requestHandlerType requestHandler) {_requestHandler = requestHandler;} +MeshBackendBase::requestHandlerType MeshBackendBase::getRequestHandler() const {return _requestHandler;} -void MeshBackendBase::setResponseHandler(MeshBackendBase::responseHandlerType responseHandler) {_responseHandler = responseHandler;} -MeshBackendBase::responseHandlerType MeshBackendBase::getResponseHandler() {return _responseHandler;} +void MeshBackendBase::setResponseHandler(const MeshBackendBase::responseHandlerType responseHandler) {_responseHandler = responseHandler;} +MeshBackendBase::responseHandlerType MeshBackendBase::getResponseHandler() const {return _responseHandler;} -void MeshBackendBase::setNetworkFilter(MeshBackendBase::networkFilterType networkFilter) {_networkFilter = networkFilter;} -MeshBackendBase::networkFilterType MeshBackendBase::getNetworkFilter() {return _networkFilter;} +void MeshBackendBase::setNetworkFilter(const MeshBackendBase::networkFilterType networkFilter) {_networkFilter = networkFilter;} +MeshBackendBase::networkFilterType MeshBackendBase::getNetworkFilter() const {return _networkFilter;} -void MeshBackendBase::setTransmissionOutcomesUpdateHook(MeshBackendBase::transmissionOutcomesUpdateHookType transmissionOutcomesUpdateHook) {_transmissionOutcomesUpdateHook = transmissionOutcomesUpdateHook;} -MeshBackendBase::transmissionOutcomesUpdateHookType MeshBackendBase::getTransmissionOutcomesUpdateHook() {return _transmissionOutcomesUpdateHook;} +void MeshBackendBase::setTransmissionOutcomesUpdateHook(const MeshBackendBase::transmissionOutcomesUpdateHookType transmissionOutcomesUpdateHook) {_transmissionOutcomesUpdateHook = transmissionOutcomesUpdateHook;} +MeshBackendBase::transmissionOutcomesUpdateHookType MeshBackendBase::getTransmissionOutcomesUpdateHook() const {return _transmissionOutcomesUpdateHook;} -void MeshBackendBase::setScanHidden(bool scanHidden) +void MeshBackendBase::setScanHidden(const bool scanHidden) { _scanHidden = scanHidden; } bool MeshBackendBase::getScanHidden() const {return _scanHidden;} -void MeshBackendBase::setAPHidden(bool apHidden) +void MeshBackendBase::setAPHidden(const bool apHidden) { if(getAPHidden() != apHidden) { @@ -258,7 +258,7 @@ bool MeshBackendBase::latestTransmissionSuccessfulBase(const std::vector