mirror of
https://github.com/esp8266/Arduino.git
synced 2025-06-03 07:02:28 +03:00
- Split most of the EspnowMeshBackend code into utility files and the new ConditionalPrinter, EspnowDatabase, EspnowConnectionManager, EspnowTransmitter and EspnowEncryptionBroker classes.
- Improve mutex handling. - Move verifyEncryptionRequestHmac function from JsonTranslator to EspnowEncryptionBroker. - Remove UtilityMethods.cpp.
This commit is contained in:
parent
3f5495bb3d
commit
40e1f02ffb
@ -2,6 +2,7 @@
|
||||
|
||||
#include <ESP8266WiFi.h>
|
||||
#include <EspnowMeshBackend.h>
|
||||
#include <TcpIpMeshBackend.h>
|
||||
#include <TypeConversionFunctions.h>
|
||||
#include <assert.h>
|
||||
|
||||
|
@ -2,6 +2,7 @@
|
||||
|
||||
#include <ESP8266WiFi.h>
|
||||
#include <TcpIpMeshBackend.h>
|
||||
#include <EspnowMeshBackend.h>
|
||||
#include <TypeConversionFunctions.h>
|
||||
#include <assert.h>
|
||||
|
||||
|
@ -1,6 +1,5 @@
|
||||
/*
|
||||
* UtilityMethods
|
||||
* Copyright (C) 2018 Anders Löfgren
|
||||
* Copyright (C) 2020 Anders Löfgren
|
||||
*
|
||||
* License (MIT license):
|
||||
*
|
||||
@ -27,10 +26,16 @@
|
||||
#include "MeshBackendBase.h"
|
||||
#include "EspnowMeshBackend.h"
|
||||
|
||||
void MeshBackendBase::setVerboseModeState(const bool enabled) {_verboseMode = enabled;}
|
||||
bool MeshBackendBase::verboseMode() const {return _verboseMode;}
|
||||
namespace
|
||||
{
|
||||
bool _staticVerboseMode = false;
|
||||
bool _printWarnings = true;
|
||||
}
|
||||
|
||||
void MeshBackendBase::verboseModePrint(const String &stringToPrint, const bool newline) const
|
||||
void ConditionalPrinter::setVerboseModeState(const bool enabled) {_verboseMode = enabled;}
|
||||
bool ConditionalPrinter::verboseMode() const {return _verboseMode;}
|
||||
|
||||
void ConditionalPrinter::verboseModePrint(const String &stringToPrint, const bool newline) const
|
||||
{
|
||||
if(verboseMode())
|
||||
{
|
||||
@ -41,23 +46,10 @@ void MeshBackendBase::verboseModePrint(const String &stringToPrint, const bool n
|
||||
}
|
||||
}
|
||||
|
||||
void EspnowMeshBackend::setVerboseModeState(const bool enabled) {MeshBackendBase::setVerboseModeState(enabled); _staticVerboseMode = enabled;}
|
||||
bool EspnowMeshBackend::verboseMode() const {return staticVerboseMode();}
|
||||
void ConditionalPrinter::setStaticVerboseModeState(const bool enabled) {_staticVerboseMode = enabled;};
|
||||
bool ConditionalPrinter::staticVerboseMode() {return _staticVerboseMode;}
|
||||
|
||||
void EspnowMeshBackend::verboseModePrint(const String &stringToPrint, const bool newline) const
|
||||
{
|
||||
if(verboseMode())
|
||||
{
|
||||
if(newline)
|
||||
Serial.println(stringToPrint);
|
||||
else
|
||||
Serial.print(stringToPrint);
|
||||
}
|
||||
}
|
||||
|
||||
bool EspnowMeshBackend::staticVerboseMode() {return _staticVerboseMode;}
|
||||
|
||||
void EspnowMeshBackend::staticVerboseModePrint(const String &stringToPrint, const bool newline)
|
||||
void ConditionalPrinter::staticVerboseModePrint(const String &stringToPrint, const bool newline)
|
||||
{
|
||||
if(staticVerboseMode())
|
||||
{
|
||||
@ -68,10 +60,10 @@ void EspnowMeshBackend::staticVerboseModePrint(const String &stringToPrint, cons
|
||||
}
|
||||
}
|
||||
|
||||
void MeshBackendBase::setPrintWarnings(const bool printEnabled) {_printWarnings = printEnabled;}
|
||||
bool MeshBackendBase::printWarnings() {return _printWarnings;}
|
||||
void ConditionalPrinter::setPrintWarnings(const bool printEnabled) {_printWarnings = printEnabled;}
|
||||
bool ConditionalPrinter::printWarnings() {return _printWarnings;}
|
||||
|
||||
void MeshBackendBase::warningPrint(const String &stringToPrint, const bool newline)
|
||||
void ConditionalPrinter::warningPrint(const String &stringToPrint, const bool newline)
|
||||
{
|
||||
if(printWarnings())
|
||||
{
|
87
libraries/ESP8266WiFiMesh/src/ConditionalPrinter.h
Normal file
87
libraries/ESP8266WiFiMesh/src/ConditionalPrinter.h
Normal file
@ -0,0 +1,87 @@
|
||||
/*
|
||||
* 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 __CONDITIONALPRINTER_H__
|
||||
#define __CONDITIONALPRINTER_H__
|
||||
|
||||
class ConditionalPrinter
|
||||
{
|
||||
|
||||
public:
|
||||
|
||||
/**
|
||||
* Set whether the normal events occurring in the library will be printed to Serial or not.
|
||||
*
|
||||
* @param enabled If true, library Serial prints are activated.
|
||||
*/
|
||||
void setVerboseModeState(const bool enabled);
|
||||
bool verboseMode() const;
|
||||
|
||||
/**
|
||||
* 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, const bool newline = true) const;
|
||||
|
||||
/**
|
||||
* Same as verboseMode(), but used for printing from static functions.
|
||||
*
|
||||
* @param enabled If true, the normal events occurring in the library will be printed to Serial.
|
||||
*/
|
||||
static void setStaticVerboseModeState(const bool enabled);
|
||||
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, const bool newline = true);
|
||||
|
||||
/**
|
||||
* Set whether the warnings occurring in the library will be printed to Serial or not. On by default.
|
||||
*
|
||||
* @param printEnabled If true, warning Serial prints from the library are activated.
|
||||
*/
|
||||
static void setPrintWarnings(const 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, const bool newline = true);
|
||||
|
||||
private:
|
||||
|
||||
bool _verboseMode = false;
|
||||
|
||||
};
|
||||
|
||||
#endif
|
585
libraries/ESP8266WiFiMesh/src/EspnowConnectionManager.cpp
Normal file
585
libraries/ESP8266WiFiMesh/src/EspnowConnectionManager.cpp
Normal file
@ -0,0 +1,585 @@
|
||||
/*
|
||||
Copyright (C) 2020 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 <ESP8266WiFi.h>
|
||||
extern "C" {
|
||||
#include <espnow.h>
|
||||
}
|
||||
|
||||
#include "EspnowConnectionManager.h"
|
||||
#include "JsonTranslator.h"
|
||||
#include "MeshCryptoInterface.h"
|
||||
#include "Serializer.h"
|
||||
#include "EspnowTransmitter.h"
|
||||
|
||||
namespace
|
||||
{
|
||||
using EspnowProtocolInterpreter::encryptedConnectionKeyLength;
|
||||
using EspnowProtocolInterpreter::hashKeyLength;
|
||||
using EspnowProtocolInterpreter::maxEncryptedConnections;
|
||||
|
||||
namespace TypeCast = MeshTypeConversionFunctions;
|
||||
|
||||
std::vector<EncryptedConnectionLog> _encryptedConnections = {};
|
||||
|
||||
uint32_t _unsynchronizedMessageID = 0;
|
||||
|
||||
uint8_t _espnowEncryptionKok[encryptedConnectionKeyLength] = { 0 };
|
||||
bool _espnowEncryptionKokSet = false;
|
||||
}
|
||||
|
||||
EspnowConnectionManager::EspnowConnectionManager(ConditionalPrinter &conditionalPrinterInstance, EspnowDatabase &databaseInstance)
|
||||
: _conditionalPrinter(conditionalPrinterInstance), _database(databaseInstance)
|
||||
{
|
||||
// Reserve the maximum possible usage early on to prevent heap fragmentation later.
|
||||
encryptedConnections().reserve(maxEncryptedConnections);
|
||||
}
|
||||
|
||||
std::vector<EncryptedConnectionLog> & EspnowConnectionManager::encryptedConnections() { return _encryptedConnections; }
|
||||
|
||||
uint8_t EspnowConnectionManager::numberOfEncryptedConnections()
|
||||
{
|
||||
return encryptedConnections().size();
|
||||
}
|
||||
|
||||
ConnectionType EspnowConnectionManager::getConnectionInfo(uint8_t *peerMac, uint32_t *remainingDuration)
|
||||
{
|
||||
EncryptedConnectionLog *encryptedConnection = nullptr;
|
||||
|
||||
if(peerMac)
|
||||
encryptedConnection = getEncryptedConnection(peerMac);
|
||||
|
||||
return getConnectionInfoHelper(encryptedConnection, remainingDuration);
|
||||
}
|
||||
|
||||
ConnectionType EspnowConnectionManager::getConnectionInfo(const uint32_t connectionIndex, uint32_t *remainingDuration, uint8_t *peerMac)
|
||||
{
|
||||
EncryptedConnectionLog *encryptedConnection = nullptr;
|
||||
|
||||
if(connectionIndex < numberOfEncryptedConnections())
|
||||
encryptedConnection = &encryptedConnections()[connectionIndex];
|
||||
|
||||
return getConnectionInfoHelper(encryptedConnection, remainingDuration, peerMac);
|
||||
}
|
||||
|
||||
EspnowConnectionManager::connectionLogIterator EspnowConnectionManager::connectionLogEndIterator()
|
||||
{
|
||||
return encryptedConnections().end();
|
||||
}
|
||||
|
||||
void EspnowConnectionManager::setEspnowEncryptedConnectionKey(const uint8_t espnowEncryptedConnectionKey[encryptedConnectionKeyLength])
|
||||
{
|
||||
assert(espnowEncryptedConnectionKey != nullptr);
|
||||
|
||||
for(int i = 0; i < encryptedConnectionKeyLength; ++i)
|
||||
{
|
||||
_espnowEncryptedConnectionKey[i] = espnowEncryptedConnectionKey[i];
|
||||
}
|
||||
}
|
||||
|
||||
void EspnowConnectionManager::setEspnowEncryptedConnectionKey(const String &espnowEncryptedConnectionKeySeed)
|
||||
{
|
||||
MeshCryptoInterface::initializeKey(_espnowEncryptedConnectionKey, encryptedConnectionKeyLength, espnowEncryptedConnectionKeySeed);
|
||||
}
|
||||
|
||||
const uint8_t *EspnowConnectionManager::getEspnowEncryptedConnectionKey() const
|
||||
{
|
||||
return _espnowEncryptedConnectionKey;
|
||||
}
|
||||
|
||||
uint8_t *EspnowConnectionManager::getEspnowEncryptedConnectionKey(uint8_t resultArray[encryptedConnectionKeyLength]) const
|
||||
{
|
||||
std::copy_n(_espnowEncryptedConnectionKey, encryptedConnectionKeyLength, resultArray);
|
||||
return resultArray;
|
||||
}
|
||||
|
||||
bool EspnowConnectionManager::initializeEncryptionKok()
|
||||
{
|
||||
// esp_now_set_kok returns 0 on success.
|
||||
return !(_espnowEncryptionKokSet && esp_now_set_kok(_espnowEncryptionKok, encryptedConnectionKeyLength));
|
||||
}
|
||||
|
||||
bool EspnowConnectionManager::setEspnowEncryptionKok(uint8_t espnowEncryptionKok[encryptedConnectionKeyLength])
|
||||
{
|
||||
if(espnowEncryptionKok == nullptr || esp_now_set_kok(espnowEncryptionKok, encryptedConnectionKeyLength)) // esp_now_set_kok failed if not == 0
|
||||
return false;
|
||||
|
||||
for(int i = 0; i < encryptedConnectionKeyLength; ++i)
|
||||
{
|
||||
_espnowEncryptionKok[i] = espnowEncryptionKok[i];
|
||||
}
|
||||
|
||||
_espnowEncryptionKokSet = true;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool EspnowConnectionManager::setEspnowEncryptionKok(const String &espnowEncryptionKokSeed)
|
||||
{
|
||||
uint8_t espnowEncryptionKok[encryptedConnectionKeyLength] {};
|
||||
MeshCryptoInterface::initializeKey(espnowEncryptionKok, encryptedConnectionKeyLength, espnowEncryptionKokSeed);
|
||||
|
||||
return setEspnowEncryptionKok(espnowEncryptionKok);
|
||||
}
|
||||
|
||||
const uint8_t *EspnowConnectionManager::getEspnowEncryptionKok()
|
||||
{
|
||||
if(_espnowEncryptionKokSet)
|
||||
return _espnowEncryptionKok;
|
||||
else
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void EspnowConnectionManager::setEspnowHashKey(const uint8_t espnowHashKey[hashKeyLength])
|
||||
{
|
||||
assert(espnowHashKey != nullptr);
|
||||
|
||||
for(int i = 0; i < hashKeyLength; ++i)
|
||||
{
|
||||
_espnowHashKey[i] = espnowHashKey[i];
|
||||
}
|
||||
}
|
||||
|
||||
void EspnowConnectionManager::setEspnowHashKey(const String &espnowHashKeySeed)
|
||||
{
|
||||
MeshCryptoInterface::initializeKey(_espnowHashKey, hashKeyLength, espnowHashKeySeed);
|
||||
}
|
||||
|
||||
const uint8_t *EspnowConnectionManager::getEspnowHashKey() const
|
||||
{
|
||||
return _espnowHashKey;
|
||||
}
|
||||
|
||||
uint8_t *EspnowConnectionManager::getEncryptedMac(const uint8_t *peerMac, uint8_t *resultArray)
|
||||
{
|
||||
if(EncryptedConnectionLog *encryptedConnection = getEncryptedConnection(peerMac))
|
||||
{
|
||||
return encryptedConnection->getEncryptedPeerMac(resultArray);
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
EncryptedConnectionLog *EspnowConnectionManager::getEncryptedConnection(const uint8_t *peerMac)
|
||||
{
|
||||
auto connectionIterator = getEncryptedConnectionIterator(peerMac, encryptedConnections());
|
||||
if(connectionIterator != encryptedConnections().end())
|
||||
{
|
||||
return &(*connectionIterator);
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
EncryptedConnectionLog *EspnowConnectionManager::getEncryptedConnection(const uint32_t connectionIndex)
|
||||
{
|
||||
if(connectionIndex < numberOfEncryptedConnections())
|
||||
return &encryptedConnections()[connectionIndex];
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
EncryptedConnectionLog *EspnowConnectionManager::getTemporaryEncryptedConnection(const uint8_t *peerMac)
|
||||
{
|
||||
connectionLogIterator connectionIterator = connectionLogEndIterator();
|
||||
if(getTemporaryEncryptedConnectionIterator(peerMac, connectionIterator))
|
||||
{
|
||||
return &(*connectionIterator);
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
typename T::iterator EspnowConnectionManager::getEncryptedConnectionIterator(const uint8_t *peerMac, T &connectionContainer)
|
||||
{
|
||||
typename T::iterator connectionIterator = connectionContainer.begin();
|
||||
|
||||
while(connectionIterator != connectionContainer.end())
|
||||
{
|
||||
if(connectionIterator->connectedTo(peerMac))
|
||||
break;
|
||||
else
|
||||
++connectionIterator;
|
||||
}
|
||||
|
||||
return connectionIterator;
|
||||
}
|
||||
|
||||
bool EspnowConnectionManager::getEncryptedConnectionIterator(const uint8_t *peerMac, connectionLogIterator &iterator)
|
||||
{
|
||||
connectionLogIterator result = getEncryptedConnectionIterator(peerMac, encryptedConnections());
|
||||
|
||||
if(result != connectionLogEndIterator())
|
||||
{
|
||||
iterator = result;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool EspnowConnectionManager::getTemporaryEncryptedConnectionIterator(const uint8_t *peerMac, connectionLogIterator &iterator)
|
||||
{
|
||||
connectionLogIterator result = connectionLogEndIterator();
|
||||
|
||||
if(getEncryptedConnectionIterator(peerMac, result) && result->temporary())
|
||||
{
|
||||
iterator = result;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
ConnectionType EspnowConnectionManager::getConnectionInfoHelper(const EncryptedConnectionLog *encryptedConnection, uint32_t *remainingDuration, uint8_t *peerMac)
|
||||
{
|
||||
if(!encryptedConnection)
|
||||
{
|
||||
return ConnectionType::NO_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;
|
||||
}
|
||||
|
||||
|
||||
void EspnowConnectionManager::setEncryptedConnectionsSoftLimit(const uint8_t softLimit)
|
||||
{
|
||||
assert(softLimit <= 6); // Valid values are 0 to 6, but uint8_t is always at least 0.
|
||||
_encryptedConnectionsSoftLimit = softLimit;
|
||||
}
|
||||
|
||||
uint8_t EspnowConnectionManager::encryptedConnectionsSoftLimit() const { return _encryptedConnectionsSoftLimit; }
|
||||
|
||||
bool EspnowConnectionManager::addUnencryptedConnection(const String &serializedConnectionState)
|
||||
{
|
||||
return JsonTranslator::getUnsynchronizedMessageID(serializedConnectionState, _unsynchronizedMessageID);
|
||||
}
|
||||
|
||||
EncryptedConnectionStatus EspnowConnectionManager::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
|
||||
|
||||
uint8_t encryptionKeyArray[encryptedConnectionKeyLength] = { 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, getEspnowEncryptedConnectionKey(encryptionKeyArray), encryptedConnectionKeyLength);
|
||||
encryptedConnection->setHashKey(getEspnowHashKey());
|
||||
|
||||
return EncryptedConnectionStatus::CONNECTION_ESTABLISHED;
|
||||
}
|
||||
|
||||
if(encryptedConnections().size() == maxEncryptedConnections)
|
||||
{
|
||||
// No capacity for more encrypted connections.
|
||||
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, _database.getWiFiChannel(), getEspnowEncryptedConnectionKey(encryptionKeyArray), encryptedConnectionKeyLength))
|
||||
{
|
||||
encryptedConnections().emplace_back(peerStaMac, peerApMac, peerSessionKey, ownSessionKey, getEspnowHashKey());
|
||||
return EncryptedConnectionStatus::CONNECTION_ESTABLISHED;
|
||||
}
|
||||
else
|
||||
{
|
||||
return EncryptedConnectionStatus::API_CALL_FAILED;
|
||||
}
|
||||
}
|
||||
|
||||
EncryptedConnectionStatus EspnowConnectionManager::addEncryptedConnection(const String &serializedConnectionState, const 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))
|
||||
{
|
||||
EncryptedConnectionStatus result = EncryptedConnectionStatus::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 == EncryptedConnectionStatus::CONNECTION_ESTABLISHED)
|
||||
{
|
||||
EncryptedConnectionLog *encryptedConnection = getEncryptedConnection(peerStaMac);
|
||||
encryptedConnection->setDesync(desync);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
return EncryptedConnectionStatus::REQUEST_TRANSMISSION_FAILED;
|
||||
}
|
||||
|
||||
EncryptedConnectionStatus EspnowConnectionManager::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
|
||||
|
||||
uint8_t encryptionKeyArray[encryptedConnectionKeyLength] = { 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, getEspnowEncryptedConnectionKey(encryptionKeyArray), encryptedConnectionKeyLength);
|
||||
encryptedConnection->setHashKey(getEspnowHashKey());
|
||||
|
||||
if(encryptedConnection->temporary())
|
||||
{
|
||||
encryptedConnection->setRemainingDuration(duration);
|
||||
}
|
||||
|
||||
return EncryptedConnectionStatus::CONNECTION_ESTABLISHED;
|
||||
}
|
||||
|
||||
EncryptedConnectionStatus result = addEncryptedConnection(peerStaMac, peerApMac, peerSessionKey, ownSessionKey);
|
||||
|
||||
if(result == EncryptedConnectionStatus::CONNECTION_ESTABLISHED)
|
||||
{
|
||||
if(!getEncryptedConnectionIterator(peerStaMac, encryptedConnection))
|
||||
assert(false && String(F("No connection found despite being added in addTemporaryEncryptedConnection.")));
|
||||
|
||||
encryptedConnection->setRemainingDuration(duration);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
EncryptedConnectionStatus EspnowConnectionManager::addTemporaryEncryptedConnection(const String &serializedConnectionState, const 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))
|
||||
{
|
||||
EncryptedConnectionStatus result = addTemporaryEncryptedConnection(peerStaMac, peerApMac, peerSessionKey, ownSessionKey, duration);
|
||||
|
||||
if(result == EncryptedConnectionStatus::CONNECTION_ESTABLISHED)
|
||||
{
|
||||
EncryptedConnectionLog *encryptedConnection = getEncryptedConnection(peerStaMac);
|
||||
encryptedConnection->setDesync(desync);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
return EncryptedConnectionStatus::REQUEST_TRANSMISSION_FAILED;
|
||||
}
|
||||
|
||||
EncryptedConnectionRemovalOutcome EspnowConnectionManager::removeEncryptedConnection(const uint8_t *peerMac)
|
||||
{
|
||||
auto connectionIterator = getEncryptedConnectionIterator(peerMac, encryptedConnections());
|
||||
if(connectionIterator != encryptedConnections().end())
|
||||
{
|
||||
MutexTracker mutexTracker(EspnowTransmitter::captureEspnowTransmissionMutex());
|
||||
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 EncryptedConnectionRemovalOutcome::REMOVAL_SCHEDULED;
|
||||
}
|
||||
else
|
||||
{
|
||||
return removeEncryptedConnectionUnprotected(peerMac);
|
||||
}
|
||||
}
|
||||
|
||||
// peerMac is already removed
|
||||
return EncryptedConnectionRemovalOutcome::REMOVAL_SUCCEEDED;
|
||||
}
|
||||
|
||||
|
||||
EncryptedConnectionRemovalOutcome EspnowConnectionManager::removeEncryptedConnectionUnprotected(const uint8_t *peerMac, std::vector<EncryptedConnectionLog>::iterator *resultingIterator)
|
||||
{
|
||||
connectionLogIterator connectionIterator = getEncryptedConnectionIterator(peerMac, encryptedConnections());
|
||||
return removeEncryptedConnectionUnprotected(connectionIterator, resultingIterator);
|
||||
}
|
||||
|
||||
EncryptedConnectionRemovalOutcome EspnowConnectionManager::removeEncryptedConnectionUnprotected(connectionLogIterator &connectionIterator, std::vector<EncryptedConnectionLog>::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);
|
||||
ConditionalPrinter::staticVerboseModePrint(String(F("Removing connection ")) + TypeCast::macToString(encryptedMac) + String(F("... ")), false);
|
||||
bool removalSucceeded = esp_now_del_peer(encryptedMac) == 0;
|
||||
|
||||
if(removalSucceeded)
|
||||
{
|
||||
if(resultingIterator != nullptr)
|
||||
{
|
||||
*resultingIterator = encryptedConnections().erase(connectionIterator);
|
||||
}
|
||||
else
|
||||
{
|
||||
encryptedConnections().erase(connectionIterator);
|
||||
}
|
||||
ConditionalPrinter::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.
|
||||
EspnowDatabase::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).
|
||||
EspnowDatabase::deleteEntriesByMac(EspnowDatabase::receivedEspnowTransmissions(), encryptedMac, true);
|
||||
EspnowDatabase::deleteEntriesByMac(EspnowDatabase::sentRequests(), encryptedMac, true);
|
||||
EspnowDatabase::deleteEntriesByMac(EspnowDatabase::receivedRequests(), encryptedMac, true);
|
||||
|
||||
return EncryptedConnectionRemovalOutcome::REMOVAL_SUCCEEDED;
|
||||
}
|
||||
else
|
||||
{
|
||||
ConditionalPrinter::staticVerboseModePrint(String(F("Removal failed")));
|
||||
return EncryptedConnectionRemovalOutcome::REMOVAL_FAILED;
|
||||
}
|
||||
}
|
||||
|
||||
// connection is already removed
|
||||
return EncryptedConnectionRemovalOutcome::REMOVAL_SUCCEEDED;
|
||||
}
|
||||
|
||||
bool EspnowConnectionManager::temporaryEncryptedConnectionToPermanent(const uint8_t *peerMac)
|
||||
{
|
||||
if(EncryptedConnectionLog *temporaryConnection = getTemporaryEncryptedConnection(peerMac))
|
||||
{
|
||||
temporaryConnection->removeDuration();
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
String EspnowConnectionManager::serializeUnencryptedConnection()
|
||||
{
|
||||
return Serializer::serializeUnencryptedConnection(String(_unsynchronizedMessageID));
|
||||
}
|
||||
|
||||
String EspnowConnectionManager::serializeEncryptedConnection(const uint8_t *peerMac)
|
||||
{
|
||||
String serializedConnection(emptyString);
|
||||
|
||||
EncryptedConnectionLog *encryptedConnection = nullptr;
|
||||
|
||||
if(peerMac)
|
||||
encryptedConnection = getEncryptedConnection(peerMac);
|
||||
|
||||
if(encryptedConnection)
|
||||
serializedConnection = encryptedConnection->serialize();
|
||||
|
||||
return serializedConnection;
|
||||
}
|
||||
|
||||
String EspnowConnectionManager::serializeEncryptedConnection(const uint32_t connectionIndex)
|
||||
{
|
||||
String serializedConnection(emptyString);
|
||||
|
||||
if(EncryptedConnectionLog *encryptedConnection = getEncryptedConnection(connectionIndex))
|
||||
serializedConnection = encryptedConnection->serialize();
|
||||
|
||||
return serializedConnection;
|
||||
}
|
||||
|
||||
void EspnowConnectionManager::handlePostponedRemovals()
|
||||
{
|
||||
MutexTracker mutexTracker(EspnowTransmitter::captureEspnowTransmissionMutex());
|
||||
if(!mutexTracker.mutexCaptured())
|
||||
{
|
||||
assert(false && String(F("ERROR! Transmission in progress. Don't call handlePostponedRemovals from callbacks as this may corrupt program state! Aborting.")));
|
||||
return;
|
||||
}
|
||||
|
||||
if(EncryptedConnectionLog::newRemovalsScheduled())
|
||||
{
|
||||
updateTemporaryEncryptedConnections(true);
|
||||
}
|
||||
}
|
||||
|
||||
void EspnowConnectionManager::updateTemporaryEncryptedConnections(const 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);
|
||||
}
|
||||
|
||||
uint64_t EspnowConnectionManager::generateMessageID(const EncryptedConnectionLog *encryptedConnection)
|
||||
{
|
||||
if(encryptedConnection)
|
||||
{
|
||||
return encryptedConnection->getOwnSessionKey();
|
||||
}
|
||||
|
||||
return _unsynchronizedMessageID++;
|
||||
}
|
152
libraries/ESP8266WiFiMesh/src/EspnowConnectionManager.h
Normal file
152
libraries/ESP8266WiFiMesh/src/EspnowConnectionManager.h
Normal file
@ -0,0 +1,152 @@
|
||||
/*
|
||||
Copyright (C) 2020 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
|
||||
*/
|
||||
|
||||
#ifndef __ESPNOWCONNECTIONMANAGER_H__
|
||||
#define __ESPNOWCONNECTIONMANAGER_H__
|
||||
|
||||
#include <Arduino.h>
|
||||
#include "ConditionalPrinter.h"
|
||||
#include "EspnowDatabase.h"
|
||||
#include "EspnowProtocolInterpreter.h"
|
||||
#include "EncryptedConnectionLog.h"
|
||||
|
||||
class EspnowMeshBackend;
|
||||
|
||||
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.
|
||||
enum class EncryptedConnectionStatus
|
||||
{
|
||||
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. See the setEncryptedConnectionsSoftLimit method documentation for details.
|
||||
};
|
||||
|
||||
enum class EncryptedConnectionRemovalOutcome
|
||||
{
|
||||
REMOVAL_REQUEST_FAILED = -1,
|
||||
REMOVAL_FAILED = 0,
|
||||
REMOVAL_SUCCEEDED = 1,
|
||||
REMOVAL_SCHEDULED = 2
|
||||
};
|
||||
|
||||
class EspnowConnectionManager
|
||||
{
|
||||
|
||||
public:
|
||||
|
||||
using connectionLogIterator = std::vector<EncryptedConnectionLog>::iterator;
|
||||
|
||||
EspnowConnectionManager(ConditionalPrinter &conditionalPrinterInstance, EspnowDatabase &databaseInstance);
|
||||
|
||||
static std::vector<EncryptedConnectionLog> & encryptedConnections();
|
||||
|
||||
static uint8_t numberOfEncryptedConnections();
|
||||
|
||||
static ConnectionType getConnectionInfo(uint8_t *peerMac, uint32_t *remainingDuration = nullptr);
|
||||
static ConnectionType getConnectionInfo(const uint32_t connectionIndex, uint32_t *remainingDuration = nullptr, uint8_t *peerMac = nullptr);
|
||||
|
||||
static connectionLogIterator connectionLogEndIterator();
|
||||
|
||||
void setEspnowEncryptedConnectionKey(const uint8_t espnowEncryptedConnectionKey[EspnowProtocolInterpreter::encryptedConnectionKeyLength]);
|
||||
void setEspnowEncryptedConnectionKey(const String &espnowEncryptedConnectionKeySeed);
|
||||
const uint8_t *getEspnowEncryptedConnectionKey() const;
|
||||
uint8_t *getEspnowEncryptedConnectionKey(uint8_t resultArray[EspnowProtocolInterpreter::encryptedConnectionKeyLength]) const;
|
||||
// Returns false if failed to apply the current KoK (default KoK is used if no KoK provided)
|
||||
static bool initializeEncryptionKok();
|
||||
static bool setEspnowEncryptionKok(uint8_t espnowEncryptionKok[EspnowProtocolInterpreter::encryptedConnectionKeyLength]);
|
||||
static bool setEspnowEncryptionKok(const String &espnowEncryptionKokSeed);
|
||||
static const uint8_t *getEspnowEncryptionKok();
|
||||
void setEspnowHashKey(const uint8_t espnowHashKey[EspnowProtocolInterpreter::hashKeyLength]);
|
||||
void setEspnowHashKey(const String &espnowHashKeySeed);
|
||||
const uint8_t *getEspnowHashKey() const;
|
||||
|
||||
static uint8_t *getEncryptedMac(const uint8_t *peerMac, uint8_t *resultArray);
|
||||
|
||||
static EncryptedConnectionLog *getEncryptedConnection(const uint8_t *peerMac);
|
||||
static EncryptedConnectionLog *getEncryptedConnection(const uint32_t connectionIndex);
|
||||
static EncryptedConnectionLog *getTemporaryEncryptedConnection(const uint8_t *peerMac);
|
||||
|
||||
//@return iterator to connection in connectionContainer, or connectionContainer.end() if element not found
|
||||
template <typename T>
|
||||
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);
|
||||
|
||||
void setEncryptedConnectionsSoftLimit(const uint8_t softLimit);
|
||||
uint8_t encryptedConnectionsSoftLimit() const;
|
||||
|
||||
static bool addUnencryptedConnection(const String &serializedConnectionState);
|
||||
EncryptedConnectionStatus addEncryptedConnection(uint8_t *peerStaMac, uint8_t *peerApMac, const uint64_t peerSessionKey, const uint64_t ownSessionKey);
|
||||
EncryptedConnectionStatus addEncryptedConnection(const String &serializedConnectionState, const bool ignoreDuration = false);
|
||||
EncryptedConnectionStatus addTemporaryEncryptedConnection(uint8_t *peerStaMac, uint8_t *peerApMac, const uint64_t peerSessionKey, const uint64_t ownSessionKey, const uint32_t duration);
|
||||
EncryptedConnectionStatus addTemporaryEncryptedConnection(const String &serializedConnectionState, const uint32_t duration);
|
||||
|
||||
static EncryptedConnectionRemovalOutcome removeEncryptedConnection(const uint8_t *peerMac);
|
||||
|
||||
// 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 EncryptedConnectionRemovalOutcome removeEncryptedConnectionUnprotected(const uint8_t *peerMac, std::vector<EncryptedConnectionLog>::iterator *resultingIterator = nullptr);
|
||||
static EncryptedConnectionRemovalOutcome removeEncryptedConnectionUnprotected(connectionLogIterator &connectionIterator, std::vector<EncryptedConnectionLog>::iterator *resultingIterator);
|
||||
|
||||
static bool temporaryEncryptedConnectionToPermanent(const uint8_t *peerMac);
|
||||
|
||||
static String serializeUnencryptedConnection();
|
||||
static String serializeEncryptedConnection(const uint8_t *peerMac);
|
||||
static String serializeEncryptedConnection(const uint32_t connectionIndex);
|
||||
|
||||
static void handlePostponedRemovals();
|
||||
|
||||
// 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(const bool scheduledRemovalOnly = false);
|
||||
|
||||
/**
|
||||
* 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.
|
||||
* @return The generated message ID.
|
||||
*/
|
||||
static uint64_t generateMessageID(const EncryptedConnectionLog *encryptedConnection);
|
||||
|
||||
private:
|
||||
|
||||
ConditionalPrinter & _conditionalPrinter;
|
||||
EspnowDatabase & _database;
|
||||
|
||||
uint8_t _encryptedConnectionsSoftLimit = 6;
|
||||
|
||||
uint8_t _espnowEncryptedConnectionKey[EspnowProtocolInterpreter::encryptedConnectionKeyLength] {0};
|
||||
uint8_t _espnowHashKey[EspnowProtocolInterpreter::hashKeyLength] {0};
|
||||
|
||||
static ConnectionType getConnectionInfoHelper(const EncryptedConnectionLog *encryptedConnection, uint32_t *remainingDuration, uint8_t *peerMac = nullptr);
|
||||
};
|
||||
|
||||
#endif
|
383
libraries/ESP8266WiFiMesh/src/EspnowDatabase.cpp
Normal file
383
libraries/ESP8266WiFiMesh/src/EspnowDatabase.cpp
Normal file
@ -0,0 +1,383 @@
|
||||
/*
|
||||
Copyright (C) 2020 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 "EspnowDatabase.h"
|
||||
#include "EspnowMeshBackend.h"
|
||||
#include "UtilityFunctions.h"
|
||||
|
||||
namespace
|
||||
{
|
||||
namespace TypeCast = MeshTypeConversionFunctions;
|
||||
|
||||
// _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 _logEntryLifetimeMs = 2500;
|
||||
uint32_t _broadcastResponseTimeoutMs = 1000; // This is shorter than _logEntryLifetimeMs to preserve RAM since broadcasts are not deleted from sentRequests until they expire.
|
||||
ExpiringTimeTracker _logClearingCooldown(500);
|
||||
|
||||
uint32_t _encryptionRequestTimeoutMs = 300;
|
||||
|
||||
uint32_t _criticalHeapLevel = 6000; // In bytes
|
||||
uint32_t _criticalHeapLevelBuffer = 6000; // In bytes
|
||||
|
||||
using EspnowProtocolInterpreter::macAndType_td;
|
||||
using EspnowProtocolInterpreter::messageID_td;
|
||||
using EspnowProtocolInterpreter::peerMac_td;
|
||||
|
||||
std::list<ResponseData> _responsesToSend = {};
|
||||
std::list<PeerRequestLog> _peerRequestConfirmationsToSend = {};
|
||||
|
||||
std::map<std::pair<macAndType_td, messageID_td>, MessageData> _receivedEspnowTransmissions = {};
|
||||
std::map<std::pair<peerMac_td, messageID_td>, RequestData> _sentRequests = {};
|
||||
std::map<std::pair<peerMac_td, messageID_td>, TimeTracker> _receivedRequests = {};
|
||||
|
||||
std::shared_ptr<bool> _espnowConnectionQueueMutex = std::make_shared<bool>(false);
|
||||
std::shared_ptr<bool> _responsesToSendMutex = std::make_shared<bool>(false);
|
||||
}
|
||||
|
||||
std::vector<EspnowNetworkInfo> EspnowDatabase::_connectionQueue = {};
|
||||
std::vector<TransmissionOutcome> EspnowDatabase::_latestTransmissionOutcomes = {};
|
||||
|
||||
EspnowDatabase::EspnowDatabase(ConditionalPrinter &conditionalPrinterInstance, const uint8 espnowWiFiChannel) : _conditionalPrinter(conditionalPrinterInstance), _espnowWiFiChannel(espnowWiFiChannel)
|
||||
{
|
||||
}
|
||||
|
||||
std::vector<EspnowNetworkInfo> & EspnowDatabase::connectionQueue()
|
||||
{
|
||||
MutexTracker connectionQueueMutexTracker(_espnowConnectionQueueMutex);
|
||||
if(!connectionQueueMutexTracker.mutexCaptured())
|
||||
{
|
||||
assert(false && String(F("ERROR! connectionQueue locked. Don't call connectionQueue() from callbacks other than NetworkFilter as this may corrupt program state!")));
|
||||
}
|
||||
|
||||
return _connectionQueue;
|
||||
}
|
||||
|
||||
const std::vector<EspnowNetworkInfo> & EspnowDatabase::constConnectionQueue()
|
||||
{
|
||||
return _connectionQueue;
|
||||
}
|
||||
|
||||
std::vector<TransmissionOutcome> & EspnowDatabase::latestTransmissionOutcomes()
|
||||
{
|
||||
return _latestTransmissionOutcomes;
|
||||
}
|
||||
|
||||
void EspnowDatabase::setCriticalHeapLevelBuffer(const uint32_t bufferInBytes)
|
||||
{
|
||||
_criticalHeapLevelBuffer = bufferInBytes;
|
||||
}
|
||||
|
||||
uint32_t EspnowDatabase::criticalHeapLevelBuffer()
|
||||
{
|
||||
return _criticalHeapLevelBuffer;
|
||||
}
|
||||
|
||||
uint32_t EspnowDatabase::criticalHeapLevel()
|
||||
{
|
||||
return _criticalHeapLevel;
|
||||
}
|
||||
|
||||
template <typename T, typename U>
|
||||
void EspnowDatabase::deleteExpiredLogEntries(std::map<std::pair<U, uint64_t>, T> &logEntries, const uint32_t maxEntryLifetimeMs)
|
||||
{
|
||||
for(typename std::map<std::pair<U, uint64_t>, T>::iterator entryIterator = logEntries.begin();
|
||||
entryIterator != logEntries.end(); )
|
||||
{
|
||||
if(entryIterator->second.getTimeTracker().timeSinceCreation() > maxEntryLifetimeMs)
|
||||
{
|
||||
entryIterator = logEntries.erase(entryIterator);
|
||||
}
|
||||
else
|
||||
++entryIterator;
|
||||
}
|
||||
}
|
||||
|
||||
template <typename U>
|
||||
void EspnowDatabase::deleteExpiredLogEntries(std::map<std::pair<U, uint64_t>, TimeTracker> &logEntries, const uint32_t maxEntryLifetimeMs)
|
||||
{
|
||||
for(typename std::map<std::pair<U, uint64_t>, TimeTracker>::iterator entryIterator = logEntries.begin();
|
||||
entryIterator != logEntries.end(); )
|
||||
{
|
||||
if(entryIterator->second.timeSinceCreation() > maxEntryLifetimeMs)
|
||||
{
|
||||
entryIterator = logEntries.erase(entryIterator);
|
||||
}
|
||||
else
|
||||
++entryIterator;
|
||||
}
|
||||
}
|
||||
|
||||
void EspnowDatabase::deleteExpiredLogEntries(std::map<std::pair<peerMac_td, messageID_td>, RequestData> &logEntries, const uint32_t requestLifetimeMs, const uint32_t broadcastLifetimeMs)
|
||||
{
|
||||
for(typename std::map<std::pair<peerMac_td, messageID_td>, RequestData>::iterator entryIterator = logEntries.begin();
|
||||
entryIterator != logEntries.end(); )
|
||||
{
|
||||
bool broadcast = entryIterator->first.first == EspnowProtocolInterpreter::uint64BroadcastMac;
|
||||
uint32_t timeSinceCreation = entryIterator->second.getTimeTracker().timeSinceCreation();
|
||||
|
||||
if((!broadcast && timeSinceCreation > requestLifetimeMs)
|
||||
|| (broadcast && timeSinceCreation > broadcastLifetimeMs))
|
||||
{
|
||||
entryIterator = logEntries.erase(entryIterator);
|
||||
}
|
||||
else
|
||||
++entryIterator;
|
||||
}
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void EspnowDatabase::deleteExpiredLogEntries(std::list<T> &logEntries, const uint32_t maxEntryLifetimeMs)
|
||||
{
|
||||
for(typename std::list<T>::iterator entryIterator = logEntries.begin();
|
||||
entryIterator != logEntries.end(); )
|
||||
{
|
||||
if(entryIterator->getTimeTracker().timeSinceCreation() > maxEntryLifetimeMs)
|
||||
{
|
||||
entryIterator = logEntries.erase(entryIterator);
|
||||
}
|
||||
else
|
||||
++entryIterator;
|
||||
}
|
||||
}
|
||||
|
||||
template <>
|
||||
void EspnowDatabase::deleteExpiredLogEntries(std::list<PeerRequestLog> &logEntries, const uint32_t maxEntryLifetimeMs)
|
||||
{
|
||||
for(typename std::list<PeerRequestLog>::iterator entryIterator = logEntries.begin();
|
||||
entryIterator != logEntries.end(); )
|
||||
{
|
||||
auto timeTrackerPointer = entryIterator->temporary();
|
||||
if(timeTrackerPointer && timeTrackerPointer->elapsedTime() > maxEntryLifetimeMs)
|
||||
{
|
||||
entryIterator = logEntries.erase(entryIterator);
|
||||
}
|
||||
else
|
||||
++entryIterator;
|
||||
}
|
||||
}
|
||||
|
||||
void EspnowDatabase::setLogEntryLifetimeMs(const uint32_t logEntryLifetimeMs)
|
||||
{
|
||||
_logEntryLifetimeMs = logEntryLifetimeMs;
|
||||
}
|
||||
uint32_t EspnowDatabase::logEntryLifetimeMs() { return _logEntryLifetimeMs; }
|
||||
|
||||
void EspnowDatabase::setBroadcastResponseTimeoutMs(const uint32_t broadcastResponseTimeoutMs)
|
||||
{
|
||||
_broadcastResponseTimeoutMs = broadcastResponseTimeoutMs;
|
||||
}
|
||||
uint32_t EspnowDatabase::broadcastResponseTimeoutMs() { return _broadcastResponseTimeoutMs; }
|
||||
|
||||
String EspnowDatabase::getScheduledResponseMessage(const uint32_t responseIndex)
|
||||
{
|
||||
return getScheduledResponse(responseIndex)->getMessage();
|
||||
}
|
||||
|
||||
const uint8_t *EspnowDatabase::getScheduledResponseRecipient(const uint32_t responseIndex)
|
||||
{
|
||||
return getScheduledResponse(responseIndex)->getRecipientMac();
|
||||
}
|
||||
|
||||
uint32_t EspnowDatabase::numberOfScheduledResponses() {return responsesToSend().size();}
|
||||
|
||||
void EspnowDatabase::clearAllScheduledResponses()
|
||||
{
|
||||
MutexTracker responsesToSendMutexTracker(_responsesToSendMutex);
|
||||
if(!responsesToSendMutexTracker.mutexCaptured())
|
||||
{
|
||||
assert(false && String(F("ERROR! responsesToSend locked. Don't call clearAllScheduledResponses from callbacks as this may corrupt program state! Aborting.")));
|
||||
}
|
||||
|
||||
responsesToSend().clear();
|
||||
}
|
||||
|
||||
void EspnowDatabase::deleteScheduledResponsesByRecipient(const uint8_t *recipientMac, const bool encryptedOnly)
|
||||
{
|
||||
MutexTracker responsesToSendMutexTracker(_responsesToSendMutex);
|
||||
if(!responsesToSendMutexTracker.mutexCaptured())
|
||||
{
|
||||
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(MeshUtilityFunctions::macEqual(responseIterator->getRecipientMac(), recipientMac) &&
|
||||
(!encryptedOnly || EspnowProtocolInterpreter::usesEncryption(responseIterator->getRequestID())))
|
||||
{
|
||||
responseIterator = responsesToSend().erase(responseIterator);
|
||||
}
|
||||
else
|
||||
++responseIterator;
|
||||
}
|
||||
}
|
||||
|
||||
void EspnowDatabase::setEncryptionRequestTimeout(const uint32_t timeoutMs)
|
||||
{
|
||||
_encryptionRequestTimeoutMs = timeoutMs;
|
||||
}
|
||||
uint32_t EspnowDatabase::getEncryptionRequestTimeout() {return _encryptionRequestTimeoutMs;}
|
||||
|
||||
void EspnowDatabase::setAutoEncryptionDuration(const uint32_t duration)
|
||||
{
|
||||
_autoEncryptionDuration = duration;
|
||||
}
|
||||
uint32_t EspnowDatabase::getAutoEncryptionDuration() const {return _autoEncryptionDuration;}
|
||||
|
||||
String EspnowDatabase::getSenderMac() const {return TypeCast::macToString(_senderMac);}
|
||||
uint8_t *EspnowDatabase::getSenderMac(uint8_t *macArray) const
|
||||
{
|
||||
std::copy_n(_senderMac, 6, macArray);
|
||||
return macArray;
|
||||
}
|
||||
|
||||
String EspnowDatabase::getSenderAPMac() const {return TypeCast::macToString(_senderAPMac);}
|
||||
uint8_t *EspnowDatabase::getSenderAPMac(uint8_t *macArray) const
|
||||
{
|
||||
std::copy_n(_senderAPMac, 6, macArray);
|
||||
return macArray;
|
||||
}
|
||||
|
||||
void EspnowDatabase::clearOldLogEntries(bool forced)
|
||||
{
|
||||
// Clearing all old log entries at the same time should help minimize heap fragmentation.
|
||||
|
||||
// uint32_t startTime = millis();
|
||||
|
||||
if(!forced && !_logClearingCooldown) // Clearing too frequently will cause a lot of unnecessary container iterations.
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_logClearingCooldown.reset();
|
||||
|
||||
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());
|
||||
}
|
||||
|
||||
std::list<ResponseData>::const_iterator EspnowDatabase::getScheduledResponse(const 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;
|
||||
}
|
||||
|
||||
void EspnowDatabase::setSenderMac(const uint8_t *macArray)
|
||||
{
|
||||
std::copy_n(macArray, 6, _senderMac);
|
||||
}
|
||||
|
||||
void EspnowDatabase::setSenderAPMac(const uint8_t *macArray)
|
||||
{
|
||||
std::copy_n(macArray, 6, _senderAPMac);
|
||||
}
|
||||
|
||||
void EspnowDatabase::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.
|
||||
assert(wifiCountry.schan <= newWiFiChannel && newWiFiChannel <= wifiCountry.schan + wifiCountry.nchan - 1);
|
||||
|
||||
_espnowWiFiChannel = newWiFiChannel;
|
||||
}
|
||||
|
||||
uint8 EspnowDatabase::getWiFiChannel() const
|
||||
{
|
||||
return _espnowWiFiChannel;
|
||||
}
|
||||
|
||||
bool EspnowDatabase::requestReceived(const uint64_t requestMac, const uint64_t requestID)
|
||||
{
|
||||
return receivedRequests().count(std::make_pair(requestMac, requestID));
|
||||
}
|
||||
|
||||
MutexTracker EspnowDatabase::captureEspnowConnectionQueueMutex()
|
||||
{
|
||||
// Syntax like this will move the resulting value into its new position (similar to NRVO): https://stackoverflow.com/a/11540204
|
||||
return MutexTracker(_espnowConnectionQueueMutex);
|
||||
}
|
||||
|
||||
MutexTracker EspnowDatabase::captureEspnowConnectionQueueMutex(const std::function<void()> destructorHook) { return MutexTracker(_espnowConnectionQueueMutex, destructorHook); }
|
||||
|
||||
MutexTracker EspnowDatabase::captureResponsesToSendMutex(){ return MutexTracker(_responsesToSendMutex); }
|
||||
|
||||
MutexTracker EspnowDatabase::captureResponsesToSendMutex(const std::function<void()> destructorHook) { return MutexTracker(_responsesToSendMutex, destructorHook); }
|
||||
|
||||
void EspnowDatabase::storeSentRequest(const uint64_t targetBSSID, const uint64_t messageID, const RequestData &requestData)
|
||||
{
|
||||
sentRequests().insert(std::make_pair(std::make_pair(targetBSSID, messageID), requestData));
|
||||
}
|
||||
|
||||
void EspnowDatabase::storeReceivedRequest(const uint64_t senderBSSID, const uint64_t messageID, const TimeTracker &timeTracker)
|
||||
{
|
||||
receivedRequests().insert(std::make_pair(std::make_pair(senderBSSID, messageID), timeTracker));
|
||||
}
|
||||
|
||||
EspnowMeshBackend *EspnowDatabase::getOwnerOfSentRequest(const uint64_t requestMac, const uint64_t requestID)
|
||||
{
|
||||
std::map<std::pair<peerMac_td, messageID_td>, RequestData>::iterator sentRequest = sentRequests().find(std::make_pair(requestMac, requestID));
|
||||
|
||||
if(sentRequest != sentRequests().end())
|
||||
{
|
||||
return &sentRequest->second.getMeshInstance();
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
size_t EspnowDatabase::deleteSentRequest(const uint64_t requestMac, const uint64_t requestID)
|
||||
{
|
||||
return sentRequests().erase(std::make_pair(requestMac, requestID));
|
||||
}
|
||||
|
||||
size_t EspnowDatabase::deleteSentRequestsByOwner(const EspnowMeshBackend *instancePointer)
|
||||
{
|
||||
size_t numberDeleted = 0;
|
||||
|
||||
for(std::map<std::pair<peerMac_td, messageID_td>, 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;
|
||||
}
|
||||
|
||||
std::list<ResponseData> & EspnowDatabase::responsesToSend() { return _responsesToSend; }
|
||||
std::list<PeerRequestLog> & EspnowDatabase::peerRequestConfirmationsToSend() { return _peerRequestConfirmationsToSend; }
|
||||
std::map<std::pair<macAndType_td, messageID_td>, MessageData> & EspnowDatabase::receivedEspnowTransmissions() { return _receivedEspnowTransmissions; }
|
||||
std::map<std::pair<peerMac_td, messageID_td>, RequestData> & EspnowDatabase::sentRequests() { return _sentRequests; }
|
||||
std::map<std::pair<peerMac_td, messageID_td>, TimeTracker> & EspnowDatabase::receivedRequests() { return _receivedRequests; }
|
223
libraries/ESP8266WiFiMesh/src/EspnowDatabase.h
Normal file
223
libraries/ESP8266WiFiMesh/src/EspnowDatabase.h
Normal file
@ -0,0 +1,223 @@
|
||||
/*
|
||||
Copyright (C) 2020 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
|
||||
*/
|
||||
|
||||
#ifndef __ESPNOWDATABASE_H__
|
||||
#define __ESPNOWDATABASE_H__
|
||||
|
||||
#include <Arduino.h>
|
||||
#include "EspnowNetworkInfo.h"
|
||||
#include "TransmissionOutcome.h"
|
||||
#include "ResponseData.h"
|
||||
#include "RequestData.h"
|
||||
#include "EspnowProtocolInterpreter.h"
|
||||
#include <list>
|
||||
#include <map>
|
||||
#include "MessageData.h"
|
||||
#include "MutexTracker.h"
|
||||
#include "PeerRequestLog.h"
|
||||
#include "ConditionalPrinter.h"
|
||||
#include "TypeConversionFunctions.h"
|
||||
|
||||
class EspnowMeshBackend;
|
||||
|
||||
class EspnowDatabase
|
||||
{
|
||||
|
||||
public:
|
||||
|
||||
EspnowDatabase(ConditionalPrinter &conditionalPrinterInstance, const uint8 espnowWiFiChannel);
|
||||
|
||||
static std::vector<EspnowNetworkInfo> & connectionQueue();
|
||||
static const std::vector<EspnowNetworkInfo> & constConnectionQueue();
|
||||
static std::vector<TransmissionOutcome> & latestTransmissionOutcomes();
|
||||
static uint32_t criticalHeapLevel();
|
||||
static void setCriticalHeapLevelBuffer(const uint32_t bufferInBytes);
|
||||
static uint32_t criticalHeapLevelBuffer();
|
||||
static void setLogEntryLifetimeMs(const uint32_t logEntryLifetimeMs);
|
||||
static uint32_t logEntryLifetimeMs();
|
||||
static void setBroadcastResponseTimeoutMs(const uint32_t broadcastResponseTimeoutMs);
|
||||
static uint32_t broadcastResponseTimeoutMs();
|
||||
static String getScheduledResponseMessage(const uint32_t responseIndex);
|
||||
static const uint8_t *getScheduledResponseRecipient(const uint32_t responseIndex);
|
||||
static uint32_t numberOfScheduledResponses();
|
||||
static void clearAllScheduledResponses();
|
||||
static void deleteScheduledResponsesByRecipient(const uint8_t *recipientMac, const bool encryptedOnly);
|
||||
static void setEncryptionRequestTimeout(const uint32_t timeoutMs);
|
||||
static uint32_t getEncryptionRequestTimeout();
|
||||
|
||||
void setAutoEncryptionDuration(const uint32_t duration);
|
||||
uint32_t getAutoEncryptionDuration() const;
|
||||
String getSenderMac() const;
|
||||
uint8_t *getSenderMac(uint8_t *macArray) const;
|
||||
String getSenderAPMac() const;
|
||||
uint8_t *getSenderAPMac(uint8_t *macArray) const;
|
||||
|
||||
using macAndType_td = EspnowProtocolInterpreter::macAndType_td;
|
||||
using messageID_td = EspnowProtocolInterpreter::messageID_td;
|
||||
using peerMac_td = EspnowProtocolInterpreter::peerMac_td;
|
||||
|
||||
static size_t deleteSentRequestsByOwner(const EspnowMeshBackend *instancePointer);
|
||||
static std::list<ResponseData> & responsesToSend();
|
||||
static std::list<PeerRequestLog> & peerRequestConfirmationsToSend();
|
||||
static std::map<std::pair<macAndType_td, messageID_td>, MessageData> & receivedEspnowTransmissions();
|
||||
static std::map<std::pair<peerMac_td, messageID_td>, RequestData> & sentRequests();
|
||||
static std::map<std::pair<peerMac_td, messageID_td>, TimeTracker> & receivedRequests();
|
||||
|
||||
static bool requestReceived(const uint64_t requestMac, const uint64_t requestID);
|
||||
|
||||
/**
|
||||
* Will be captured when the connectionQueue should not be modified.
|
||||
*/
|
||||
static MutexTracker captureEspnowConnectionQueueMutex();
|
||||
static MutexTracker captureEspnowConnectionQueueMutex(const std::function<void()> destructorHook);
|
||||
|
||||
/**
|
||||
* Will be captured when no responsesToSend element should be removed.
|
||||
*/
|
||||
static MutexTracker captureResponsesToSendMutex();
|
||||
static MutexTracker captureResponsesToSendMutex(const std::function<void()> destructorHook);
|
||||
|
||||
static void clearOldLogEntries(bool forced);
|
||||
|
||||
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.
|
||||
*
|
||||
* @return A valid EspnowMeshBackend pointer if a matching entry is found in the EspnowMeshBackend sentRequests container. nullptr otherwise.
|
||||
*/
|
||||
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(const uint64_t requestMac, const uint64_t requestID);
|
||||
|
||||
/**
|
||||
* 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(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(const uint8_t *macArray);
|
||||
|
||||
void setWiFiChannel(const uint8 newWiFiChannel);
|
||||
uint8 getWiFiChannel() const;
|
||||
|
||||
/**
|
||||
* 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 <typename T>
|
||||
static void deleteEntriesByMac(std::map<std::pair<macAndType_td, uint64_t>, T> &logEntries, const uint8_t *peerMac, const bool encryptedOnly)
|
||||
{
|
||||
bool macFound = false;
|
||||
|
||||
for(typename std::map<std::pair<macAndType_td, uint64_t>, T>::iterator entryIterator = logEntries.begin();
|
||||
entryIterator != logEntries.end(); )
|
||||
{
|
||||
if(macAndTypeToUint64Mac(entryIterator->first.first) == MeshTypeConversionFunctions::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 <typename T>
|
||||
static void deleteEntriesByMac(std::map<std::pair<uint64_t, uint64_t>, T> &logEntries, const uint8_t *peerMac, const bool encryptedOnly)
|
||||
{
|
||||
bool macFound = false;
|
||||
|
||||
for(typename std::map<std::pair<uint64_t, uint64_t>, T>::iterator entryIterator = logEntries.begin();
|
||||
entryIterator != logEntries.end(); )
|
||||
{
|
||||
if(entryIterator->first.first == MeshTypeConversionFunctions::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;
|
||||
}
|
||||
}
|
||||
|
||||
protected:
|
||||
|
||||
static std::vector<EspnowNetworkInfo> _connectionQueue;
|
||||
static std::vector<TransmissionOutcome> _latestTransmissionOutcomes;
|
||||
|
||||
static std::list<ResponseData>::const_iterator getScheduledResponse(const uint32_t responseIndex);
|
||||
|
||||
private:
|
||||
|
||||
ConditionalPrinter & _conditionalPrinter;
|
||||
|
||||
uint32_t _autoEncryptionDuration = 50;
|
||||
|
||||
uint8_t _senderMac[6] = {0};
|
||||
uint8_t _senderAPMac[6] = {0};
|
||||
|
||||
uint8 _espnowWiFiChannel;
|
||||
|
||||
template <typename T, typename U>
|
||||
static void deleteExpiredLogEntries(std::map<std::pair<U, uint64_t>, T> &logEntries, const uint32_t maxEntryLifetimeMs);
|
||||
|
||||
template <typename U>
|
||||
static void deleteExpiredLogEntries(std::map<std::pair<U, uint64_t>, TimeTracker> &logEntries, const uint32_t maxEntryLifetimeMs);
|
||||
|
||||
static void deleteExpiredLogEntries(std::map<std::pair<peerMac_td, messageID_td>, RequestData> &logEntries, const uint32_t requestLifetimeMs, const uint32_t broadcastLifetimeMs);
|
||||
|
||||
template <typename T>
|
||||
static void deleteExpiredLogEntries(std::list<T> &logEntries, const uint32_t maxEntryLifetimeMs);
|
||||
};
|
||||
|
||||
#endif
|
721
libraries/ESP8266WiFiMesh/src/EspnowEncryptionBroker.cpp
Normal file
721
libraries/ESP8266WiFiMesh/src/EspnowEncryptionBroker.cpp
Normal file
@ -0,0 +1,721 @@
|
||||
/*
|
||||
Copyright (C) 2020 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 <ESP8266WiFi.h>
|
||||
extern "C" {
|
||||
#include <espnow.h>
|
||||
}
|
||||
|
||||
#include "EspnowEncryptionBroker.h"
|
||||
#include "EspnowMeshBackend.h"
|
||||
#include "JsonTranslator.h"
|
||||
#include "UtilityFunctions.h"
|
||||
#include "Serializer.h"
|
||||
#include "MeshCryptoInterface.h"
|
||||
|
||||
namespace
|
||||
{
|
||||
using EspnowProtocolInterpreter::encryptedConnectionKeyLength;
|
||||
using EspnowProtocolInterpreter::hashKeyLength;
|
||||
using EspnowProtocolInterpreter::maxEncryptedConnections;
|
||||
|
||||
using connectionLogIterator = EspnowConnectionManager::connectionLogIterator;
|
||||
|
||||
namespace TypeCast = MeshTypeConversionFunctions;
|
||||
|
||||
String _ongoingPeerRequestNonce;
|
||||
uint8_t _ongoingPeerRequestMac[6] = {0};
|
||||
EspnowMeshBackend *_ongoingPeerRequester = nullptr;
|
||||
EncryptedConnectionStatus _ongoingPeerRequestResult = EncryptedConnectionStatus::MAX_CONNECTIONS_REACHED_SELF;
|
||||
ExpiringTimeTracker _ongoingPeerRequestEncryptionTimeout([](){ return EspnowDatabase::getEncryptionRequestTimeout(); });
|
||||
bool _reciprocalPeerRequestConfirmation = false;
|
||||
}
|
||||
|
||||
EspnowEncryptionBroker::EspnowEncryptionBroker(ConditionalPrinter &conditionalPrinterInstance, EspnowDatabase &databaseInstance, EspnowConnectionManager &connectionManagerInstance, EspnowTransmitter &transmitterInstance)
|
||||
: _conditionalPrinter(conditionalPrinterInstance), _database(databaseInstance), _connectionManager(connectionManagerInstance), _transmitter(transmitterInstance)
|
||||
{
|
||||
}
|
||||
|
||||
void EspnowEncryptionBroker::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:
|
||||
// Receive: encryptionRequestHeader or temporaryEncryptionRequestHeader.
|
||||
// Send: maxConnectionsReachedHeader / basicConnectionInfoHeader -> encryptedConnectionInfoHeader or softLimitEncryptedConnectionInfoHeader or maxConnectionsReachedHeader.
|
||||
// Receive: encryptedConnectionVerificationHeader.
|
||||
|
||||
using namespace EspnowProtocolInterpreter;
|
||||
|
||||
if(!EspnowDatabase::requestReceived(uint64StationMac, receivedMessageID))
|
||||
{
|
||||
EspnowDatabase::storeReceivedRequest(uint64StationMac, receivedMessageID, TimeTracker(millis()));
|
||||
|
||||
bool encryptedCorrectly = synchronizePeerSessionKey(receivedMessageID, macaddr);
|
||||
String message = getHashKeyLength(dataArray, len);
|
||||
int32_t messageHeaderEndIndex = message.indexOf(':');
|
||||
String messageHeader = message.substring(0, messageHeaderEndIndex + 1);
|
||||
|
||||
if(messageHeader == FPSTR(encryptedConnectionVerificationHeader))
|
||||
{
|
||||
if(encryptedCorrectly)
|
||||
{
|
||||
int32_t connectionRequestTypeEndIndex = message.indexOf(':', messageHeaderEndIndex + 1);
|
||||
String connectionRequestType = message.substring(messageHeaderEndIndex + 1, connectionRequestTypeEndIndex + 1);
|
||||
connectionLogIterator encryptedConnection = EspnowConnectionManager::connectionLogEndIterator();
|
||||
if(!EspnowConnectionManager::getEncryptedConnectionIterator(macaddr, encryptedConnection))
|
||||
assert(false && String(F("We must have an encrypted connection if we received an encryptedConnectionVerificationHeader which was encryptedCorrectly.")));
|
||||
|
||||
if(connectionRequestType == FPSTR(encryptionRequestHeader))
|
||||
{
|
||||
EspnowConnectionManager::temporaryEncryptedConnectionToPermanent(macaddr);
|
||||
}
|
||||
else if(connectionRequestType == FPSTR(temporaryEncryptionRequestHeader))
|
||||
{
|
||||
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 && String(F("Unknown P-type verification message received!")));
|
||||
}
|
||||
}
|
||||
}
|
||||
else if(messageHeader == FPSTR(encryptionRequestHeader) || messageHeader == FPSTR(temporaryEncryptionRequestHeader))
|
||||
{
|
||||
// If there is a espnowRequestManager, get it
|
||||
if(EspnowMeshBackend *currentEspnowRequestManager = EspnowMeshBackend::getEspnowRequestManager())
|
||||
{
|
||||
String requestNonce;
|
||||
|
||||
if(JsonTranslator::getNonce(message, requestNonce) && requestNonce.length() >= 12) // The destination MAC address requires 12 characters.
|
||||
{
|
||||
uint8_t destinationMac[6] = {0};
|
||||
TypeCast::stringToMac(requestNonce, destinationMac);
|
||||
|
||||
uint8_t apMac[6] {0};
|
||||
WiFi.softAPmacAddress(apMac);
|
||||
|
||||
bool correctDestination = false;
|
||||
if(MeshUtilityFunctions::macEqual(destinationMac, apMac))
|
||||
{
|
||||
correctDestination = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
uint8_t staMac[6] {0};
|
||||
WiFi.macAddress(staMac);
|
||||
|
||||
if(MeshUtilityFunctions::macEqual(destinationMac, staMac))
|
||||
{
|
||||
correctDestination = true;
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t apMacArray[6] = { 0 };
|
||||
if(correctDestination && verifyEncryptionRequestHmac(message, macaddr, getTransmissionMac(dataArray, apMacArray), currentEspnowRequestManager->getEspnowHashKey(), hashKeyLength))
|
||||
EspnowDatabase::peerRequestConfirmationsToSend().emplace_back(receivedMessageID, encryptedCorrectly, currentEspnowRequestManager->getMeshPassword(), currentEspnowRequestManager->encryptedConnectionsSoftLimit(),
|
||||
requestNonce, macaddr, apMacArray, currentEspnowRequestManager->getEspnowHashKey());
|
||||
}
|
||||
}
|
||||
}
|
||||
else if(messageHeader == FPSTR(encryptedConnectionRemovalRequestHeader))
|
||||
{
|
||||
if(encryptedCorrectly)
|
||||
EspnowConnectionManager::removeEncryptedConnection(macaddr);
|
||||
}
|
||||
else
|
||||
{
|
||||
assert(false && String(F("Unknown P-type message received!")));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void EspnowEncryptionBroker::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:
|
||||
// Send: encryptionRequestHeader or temporaryEncryptionRequestHeader.
|
||||
// Receive: maxConnectionsReachedHeader / basicConnectionInfoHeader -> encryptedConnectionInfoHeader or softLimitEncryptedConnectionInfoHeader or maxConnectionsReachedHeader.
|
||||
// Send: encryptedConnectionVerificationHeader.
|
||||
|
||||
using namespace EspnowProtocolInterpreter;
|
||||
|
||||
if(!_ongoingPeerRequestNonce.isEmpty())
|
||||
{
|
||||
String message = getHashKeyLength(dataArray, len);
|
||||
String requestNonce;
|
||||
|
||||
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 };
|
||||
getTransmissionMac(dataArray, apMacArray);
|
||||
|
||||
if(messageHeader == FPSTR(basicConnectionInfoHeader))
|
||||
{
|
||||
// encryptedConnectionEstablished(_ongoingPeerRequestResult) means we have already received a basicConnectionInfoHeader
|
||||
if(!encryptedConnectionEstablished(_ongoingPeerRequestResult) &&
|
||||
verifyEncryptionRequestHmac(message, macaddr, apMacArray, _ongoingPeerRequester->getEspnowHashKey(), hashKeyLength))
|
||||
{
|
||||
_ongoingPeerRequestEncryptionTimeout.reset();
|
||||
|
||||
connectionLogIterator existingEncryptedConnection = EspnowConnectionManager::connectionLogEndIterator();
|
||||
|
||||
if(!EspnowConnectionManager::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, apMacArray, createSessionKey(), createSessionKey(), EspnowDatabase::getEncryptionRequestTimeout());
|
||||
}
|
||||
else
|
||||
{
|
||||
// Encrypted connection already exists
|
||||
_ongoingPeerRequestResult = EncryptedConnectionStatus::CONNECTION_ESTABLISHED;
|
||||
|
||||
if(auto timeTrackerPointer = existingEncryptedConnection->temporary())
|
||||
{
|
||||
if(timeTrackerPointer->remainingDuration() < EspnowDatabase::getEncryptionRequestTimeout()) // Should only extend duration for existing connections.
|
||||
{
|
||||
existingEncryptedConnection->setRemainingDuration(EspnowDatabase::getEncryptionRequestTimeout());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(!encryptedConnectionEstablished(_ongoingPeerRequestResult))
|
||||
{
|
||||
// Adding connection failed, abort ongoing peer request.
|
||||
_ongoingPeerRequestNonce.clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
else if(messageHeader == FPSTR(encryptedConnectionInfoHeader) || messageHeader == FPSTR(softLimitEncryptedConnectionInfoHeader))
|
||||
{
|
||||
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 = EspnowConnectionManager::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);
|
||||
|
||||
if(messageHeader == FPSTR(encryptedConnectionInfoHeader))
|
||||
_ongoingPeerRequestResult = EncryptedConnectionStatus::CONNECTION_ESTABLISHED;
|
||||
else if(messageHeader == FPSTR(softLimitEncryptedConnectionInfoHeader))
|
||||
_ongoingPeerRequestResult = EncryptedConnectionStatus::SOFT_LIMIT_CONNECTION_ESTABLISHED;
|
||||
else
|
||||
assert(false && String(F("Unknown _ongoingPeerRequestResult!")));
|
||||
}
|
||||
else
|
||||
{
|
||||
_ongoingPeerRequestResult = EncryptedConnectionStatus::REQUEST_TRANSMISSION_FAILED;
|
||||
}
|
||||
|
||||
_ongoingPeerRequestNonce.clear();
|
||||
}
|
||||
}
|
||||
else if(messageHeader == FPSTR(maxConnectionsReachedHeader))
|
||||
{
|
||||
if(verifyEncryptionRequestHmac(message, macaddr, apMacArray, _ongoingPeerRequester->getEspnowHashKey(), hashKeyLength))
|
||||
{
|
||||
_ongoingPeerRequestResult = EncryptedConnectionStatus::MAX_CONNECTIONS_REACHED_PEER;
|
||||
_ongoingPeerRequestNonce.clear();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
assert(messageHeader == FPSTR(basicConnectionInfoHeader) || messageHeader == FPSTR(encryptedConnectionInfoHeader) ||
|
||||
messageHeader == FPSTR(softLimitEncryptedConnectionInfoHeader) || messageHeader == FPSTR(maxConnectionsReachedHeader));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void EspnowEncryptionBroker::sendPeerRequestConfirmations(const ExpiringTimeTracker *estimatedMaxDurationTracker)
|
||||
{
|
||||
uint32_t bufferedCriticalHeapLevel = EspnowDatabase::criticalHeapLevel() + EspnowDatabase::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<PeerRequestLog>::iterator confirmationsIterator = EspnowDatabase::peerRequestConfirmationsToSend().begin(); confirmationsIterator != EspnowDatabase::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.isEmpty() && confirmationsIterator->connectedTo(_ongoingPeerRequestMac);
|
||||
|
||||
auto timeTrackerPointer = confirmationsIterator->temporary();
|
||||
assert(timeTrackerPointer); // peerRequestConfirmations should always expire and so should always have a timeTracker
|
||||
if(timeTrackerPointer->elapsedTime() > EspnowDatabase::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;
|
||||
}
|
||||
|
||||
uint8_t defaultBSSID[6] {0};
|
||||
confirmationsIterator->getEncryptedPeerMac(defaultBSSID);
|
||||
uint8_t unencryptedBSSID[6] {0};
|
||||
confirmationsIterator->getUnencryptedPeerMac(unencryptedBSSID);
|
||||
uint8_t hashKey[hashKeyLength] {0};
|
||||
confirmationsIterator->getHashKey(hashKey);
|
||||
|
||||
EncryptedConnectionLog *existingEncryptedConnection = EspnowConnectionManager::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).
|
||||
|
||||
if(!existingEncryptedConnection &&
|
||||
((reciprocalPeerRequest && EspnowConnectionManager::encryptedConnections().size() >= maxEncryptedConnections) || (!reciprocalPeerRequest && reservedEncryptedConnections() >= maxEncryptedConnections)))
|
||||
{
|
||||
EspnowTransmitter::espnowSendPeerRequestConfirmationsUnsynchronized(Serializer::createEncryptionRequestHmacMessage(FPSTR(maxConnectionsReachedHeader),
|
||||
confirmationsIterator->getPeerRequestNonce(), hashKey, hashKeyLength),
|
||||
defaultBSSID, 'C'); // Generates a new message ID to avoid sending encrypted sessionKeys over unencrypted connections.
|
||||
|
||||
confirmationsIterator = EspnowDatabase::peerRequestConfirmationsToSend().erase(confirmationsIterator);
|
||||
}
|
||||
else if(EspnowTransmitter::espnowSendPeerRequestConfirmationsUnsynchronized(Serializer::createEncryptionRequestHmacMessage(FPSTR(basicConnectionInfoHeader),
|
||||
confirmationsIterator->getPeerRequestNonce(), hashKey, hashKeyLength),
|
||||
sendToDefaultBSSID ? defaultBSSID : unencryptedBSSID, 'C') // Generates a new message ID to avoid sending encrypted sessionKeys over unencrypted connections.
|
||||
== 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)
|
||||
{
|
||||
if(auto timeTrackerPointer = existingEncryptedConnection->temporary())
|
||||
{
|
||||
if(EspnowDatabase::getEncryptionRequestTimeout() > timeTrackerPointer->remainingDuration())
|
||||
{
|
||||
existingEncryptedConnection->setRemainingDuration(EspnowDatabase::getEncryptionRequestTimeout());
|
||||
}
|
||||
}
|
||||
}
|
||||
else if(EspnowMeshBackend *currentEspnowRequestManager = EspnowMeshBackend::getEspnowRequestManager())
|
||||
{
|
||||
uint8_t staMacArray[6] = { 0 };
|
||||
uint8_t apMacArray[6] = { 0 };
|
||||
currentEspnowRequestManager->addTemporaryEncryptedConnection(confirmationsIterator->getPeerStaMac(staMacArray), confirmationsIterator->getPeerApMac(apMacArray),
|
||||
createSessionKey(), createSessionKey(), EspnowDatabase::getEncryptionRequestTimeout());
|
||||
existingEncryptedConnection = EspnowConnectionManager::getEncryptedConnection(defaultBSSID);
|
||||
}
|
||||
else
|
||||
{
|
||||
ConditionalPrinter::warningPrint(String(F("WARNING! Ignoring received encrypted connection request since no EspnowRequestManager is assigned.")));
|
||||
}
|
||||
|
||||
if(!existingEncryptedConnection)
|
||||
{
|
||||
// Send "node full" message
|
||||
EspnowTransmitter::espnowSendPeerRequestConfirmationsUnsynchronized(Serializer::createEncryptionRequestHmacMessage(FPSTR(maxConnectionsReachedHeader),
|
||||
confirmationsIterator->getPeerRequestNonce(), hashKey, hashKeyLength),
|
||||
defaultBSSID, 'C'); // Generates a new message ID to avoid sending encrypted sessionKeys over unencrypted connections.
|
||||
}
|
||||
else
|
||||
{
|
||||
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 && String(F("ERROR! Attempting to send content marked as encrypted via unencrypted connection!")));
|
||||
|
||||
String messageHeader;
|
||||
|
||||
if(existingEncryptedConnection->temporary() && // Should never change permanent connections
|
||||
((reciprocalPeerRequest && EspnowConnectionManager::encryptedConnections().size() > confirmationsIterator->getEncryptedConnectionsSoftLimit())
|
||||
|| (!reciprocalPeerRequest && reservedEncryptedConnections() > confirmationsIterator->getEncryptedConnectionsSoftLimit())))
|
||||
{
|
||||
messageHeader = FPSTR(softLimitEncryptedConnectionInfoHeader);
|
||||
}
|
||||
else
|
||||
{
|
||||
messageHeader = FPSTR(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.
|
||||
EspnowTransmitter::espnowSendPeerRequestConfirmationsUnsynchronized(Serializer::createEncryptedConnectionInfo(messageHeader,
|
||||
confirmationsIterator->getPeerRequestNonce(), confirmationsIterator->getAuthenticationPassword(),
|
||||
existingEncryptedConnection->getOwnSessionKey(), existingEncryptedConnection->getPeerSessionKey()),
|
||||
defaultBSSID, 'C'); // Generates a new message ID to avoid sending encrypted sessionKeys over unencrypted connections.
|
||||
}
|
||||
|
||||
confirmationsIterator = EspnowDatabase::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.
|
||||
ConditionalPrinter::warningPrint("WARNING! Free heap below chosen minimum. Performing emergency log clearing.");
|
||||
EspnowDatabase::clearOldLogEntries(true);
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
EncryptedConnectionStatus EspnowEncryptionBroker::requestEncryptedConnection(const uint8_t *peerMac, EspnowMeshBackend &espnowInstance)
|
||||
{
|
||||
using namespace std::placeholders;
|
||||
return requestEncryptedConnectionKernel(peerMac, std::bind(defaultEncryptionRequestBuilder, FPSTR(EspnowProtocolInterpreter::encryptionRequestHeader), 0, _connectionManager.getEspnowHashKey(), _1, _2), espnowInstance);
|
||||
}
|
||||
|
||||
EncryptedConnectionStatus EspnowEncryptionBroker::requestTemporaryEncryptedConnection(const uint8_t *peerMac, const uint32_t durationMs, EspnowMeshBackend &espnowInstance)
|
||||
{
|
||||
using namespace std::placeholders;
|
||||
return requestEncryptedConnectionKernel(peerMac, std::bind(defaultEncryptionRequestBuilder, FPSTR(EspnowProtocolInterpreter::temporaryEncryptionRequestHeader),
|
||||
durationMs, _connectionManager.getEspnowHashKey(), _1, _2), espnowInstance);
|
||||
}
|
||||
|
||||
EncryptedConnectionStatus EspnowEncryptionBroker::requestFlexibleTemporaryEncryptedConnection(const uint8_t *peerMac, const uint32_t minDurationMs, EspnowMeshBackend &espnowInstance)
|
||||
{
|
||||
using namespace std::placeholders;
|
||||
return requestEncryptedConnectionKernel(peerMac, std::bind(flexibleEncryptionRequestBuilder, minDurationMs, _connectionManager.getEspnowHashKey(), _1, _2), espnowInstance);
|
||||
}
|
||||
|
||||
EncryptedConnectionRemovalOutcome EspnowEncryptionBroker::requestEncryptedConnectionRemoval(const uint8_t *peerMac)
|
||||
{
|
||||
using EspnowProtocolInterpreter::encryptedConnectionRemovalRequestHeader;
|
||||
|
||||
assert(EspnowConnectionManager::encryptedConnections().size() <= maxEncryptedConnections); // If this is not the case, ESP-NOW is no longer in sync with the library
|
||||
|
||||
MutexTracker mutexTracker(EspnowTransmitter::captureEspnowTransmissionMutex(EspnowConnectionManager::handlePostponedRemovals));
|
||||
if(!mutexTracker.mutexCaptured())
|
||||
{
|
||||
assert(false && String(F("ERROR! Transmission in progress. Don't call requestEncryptedConnectionRemoval from callbacks as this may corrupt program state! Aborting.")));
|
||||
return EncryptedConnectionRemovalOutcome::REMOVAL_REQUEST_FAILED;
|
||||
}
|
||||
|
||||
if(EncryptedConnectionLog *encryptedConnection = EspnowConnectionManager::getEncryptedConnection(peerMac))
|
||||
{
|
||||
if(EspnowTransmitter::espnowSendToNode(FPSTR(encryptedConnectionRemovalRequestHeader), peerMac, 'P') == TransmissionStatusType::TRANSMISSION_COMPLETE)
|
||||
{
|
||||
return EspnowConnectionManager::removeEncryptedConnectionUnprotected(peerMac);
|
||||
}
|
||||
else
|
||||
{
|
||||
if(encryptedConnection->removalScheduled())
|
||||
return EncryptedConnectionRemovalOutcome::REMOVAL_SUCCEEDED; // Removal will be completed by mutex destructorHook.
|
||||
else
|
||||
return EncryptedConnectionRemovalOutcome::REMOVAL_REQUEST_FAILED;
|
||||
}
|
||||
}
|
||||
|
||||
// peerMac is already removed
|
||||
return EncryptedConnectionRemovalOutcome::REMOVAL_SUCCEEDED;
|
||||
}
|
||||
|
||||
bool EspnowEncryptionBroker::encryptedConnectionEstablished(const EncryptedConnectionStatus connectionStatus)
|
||||
{
|
||||
return static_cast<int>(connectionStatus) > 0;
|
||||
}
|
||||
|
||||
void EspnowEncryptionBroker::setReceivedEncryptedTransmission(const bool receivedEncryptedTransmission) { _receivedEncryptedTransmission = receivedEncryptedTransmission; }
|
||||
bool EspnowEncryptionBroker::receivedEncryptedTransmission() const {return _receivedEncryptedTransmission;}
|
||||
|
||||
bool EspnowEncryptionBroker::verifyEncryptionRequestHmac(const String &encryptionRequestHmacMessage, const uint8_t *requesterStaMac, const uint8_t *requesterApMac,
|
||||
const uint8_t *hashKey, const uint8_t hashKeyLength)
|
||||
{
|
||||
using MeshCryptoInterface::verifyMeshHmac;
|
||||
using namespace JsonTranslator;
|
||||
|
||||
String hmac;
|
||||
if(getHmac(encryptionRequestHmacMessage, hmac))
|
||||
{
|
||||
int32_t hmacStartIndex = encryptionRequestHmacMessage.indexOf(String('"') + FPSTR(jsonHmac) + F("\":"));
|
||||
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(TypeCast::macToString(requesterStaMac) + TypeCast::macToString(requesterApMac) + encryptionRequestHmacMessage.substring(0, hmacStartIndex), hmac, hashKey, hashKeyLength))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool EspnowEncryptionBroker::verifyPeerSessionKey(const uint64_t sessionKey, const uint8_t *peerMac, const char messageType)
|
||||
{
|
||||
if(EncryptedConnectionLog *encryptedConnection = EspnowConnectionManager::getEncryptedConnection(peerMac))
|
||||
{
|
||||
return verifyPeerSessionKey(sessionKey, *encryptedConnection, TypeCast::macToUint64(peerMac), messageType);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool EspnowEncryptionBroker::verifyPeerSessionKey(const uint64_t sessionKey, const EncryptedConnectionLog &encryptedConnection, const uint64_t uint64PeerMac, const char messageType)
|
||||
{
|
||||
using namespace EspnowProtocolInterpreter;
|
||||
|
||||
if(usesEncryption(sessionKey))
|
||||
{
|
||||
if(sessionKey == encryptedConnection.getPeerSessionKey()
|
||||
|| EspnowDatabase::receivedEspnowTransmissions().find(std::make_pair(createMacAndTypeValue(uint64PeerMac, messageType), sessionKey))
|
||||
!= EspnowDatabase::receivedEspnowTransmissions().end())
|
||||
{
|
||||
// If sessionKey is correct or sessionKey is one part of a multi-part transmission.
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool EspnowEncryptionBroker::synchronizePeerSessionKey(const uint64_t sessionKey, const uint8_t *peerMac)
|
||||
{
|
||||
if(EncryptedConnectionLog *encryptedConnection = EspnowConnectionManager::getEncryptedConnection(peerMac))
|
||||
{
|
||||
return synchronizePeerSessionKey(sessionKey, *encryptedConnection);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool EspnowEncryptionBroker::synchronizePeerSessionKey(const uint64_t sessionKey, EncryptedConnectionLog &encryptedConnection)
|
||||
{
|
||||
if(EspnowProtocolInterpreter::usesEncryption(sessionKey))
|
||||
{
|
||||
if(sessionKey == encryptedConnection.getPeerSessionKey())
|
||||
{
|
||||
uint8_t hashKey[hashKeyLength] {0};
|
||||
encryptedConnection.setPeerSessionKey(EncryptedConnectionLog::incrementSessionKey(sessionKey, encryptedConnection.getHashKey(hashKey), hashKeyLength));
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
uint8_t EspnowEncryptionBroker::reservedEncryptedConnections()
|
||||
{
|
||||
if(!_ongoingPeerRequestNonce.isEmpty())
|
||||
if(!EspnowConnectionManager::getEncryptedConnection(_ongoingPeerRequestMac))
|
||||
return EspnowConnectionManager::encryptedConnections().size() + 1; // Reserve one connection spot if we are currently making a peer request to a new node.
|
||||
|
||||
return EspnowConnectionManager::encryptedConnections().size();
|
||||
}
|
||||
|
||||
EncryptedConnectionStatus EspnowEncryptionBroker::requestEncryptedConnectionKernel(const uint8_t *peerMac, const encryptionRequestBuilderType &encryptionRequestBuilder, EspnowMeshBackend &espnowInstance)
|
||||
{
|
||||
using namespace EspnowProtocolInterpreter;
|
||||
|
||||
assert(EspnowConnectionManager::encryptedConnections().size() <= maxEncryptedConnections); // If this is not the case, ESP-NOW is no longer in sync with the library
|
||||
|
||||
MutexTracker mutexTracker(EspnowTransmitter::captureEspnowTransmissionMutex(EspnowConnectionManager::handlePostponedRemovals));
|
||||
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 EncryptedConnectionStatus::REQUEST_TRANSMISSION_FAILED;
|
||||
}
|
||||
|
||||
EncryptedConnectionLog *existingEncryptedConnection = EspnowConnectionManager::getEncryptedConnection(peerMac);
|
||||
ExpiringTimeTracker existingTimeTracker = existingEncryptedConnection && existingEncryptedConnection->temporary() ?
|
||||
*existingEncryptedConnection->temporary() : ExpiringTimeTracker(0);
|
||||
|
||||
if(!existingEncryptedConnection && EspnowConnectionManager::encryptedConnections().size() >= maxEncryptedConnections)
|
||||
{
|
||||
assert(EspnowConnectionManager::encryptedConnections().size() == maxEncryptedConnections);
|
||||
|
||||
// No capacity for more encrypted connections.
|
||||
return EncryptedConnectionStatus::MAX_CONNECTIONS_REACHED_SELF;
|
||||
}
|
||||
|
||||
String requestNonce = TypeCast::macToString(peerMac) + TypeCast::uint64ToString(MeshUtilityFunctions::randomUint64())
|
||||
+ TypeCast::uint64ToString(MeshUtilityFunctions::randomUint64());
|
||||
_ongoingPeerRequestResult = EncryptedConnectionStatus::REQUEST_TRANSMISSION_FAILED;
|
||||
_ongoingPeerRequestNonce = requestNonce;
|
||||
_ongoingPeerRequester = &espnowInstance;
|
||||
_reciprocalPeerRequestConfirmation = false;
|
||||
std::copy_n(peerMac, 6, _ongoingPeerRequestMac);
|
||||
String requestMessage = encryptionRequestBuilder(requestNonce, existingTimeTracker);
|
||||
|
||||
_conditionalPrinter.verboseModePrint(String(F("Sending encrypted connection request to: ")) + TypeCast::macToString(peerMac));
|
||||
|
||||
if(EspnowTransmitter::espnowSendToNode(requestMessage, peerMac, 'P') == TransmissionStatusType::TRANSMISSION_COMPLETE)
|
||||
{
|
||||
ExpiringTimeTracker requestTimeout([](){ return EspnowDatabase::getEncryptionRequestTimeout(); });
|
||||
// _ongoingPeerRequestNonce is set to "" when a peer confirmation response from the mac is received
|
||||
while(!requestTimeout && !_ongoingPeerRequestNonce.isEmpty())
|
||||
{
|
||||
// For obvious reasons dividing by exactly 10 is a good choice.
|
||||
ExpiringTimeTracker maxDurationTracker = ExpiringTimeTracker(EspnowDatabase::getEncryptionRequestTimeout()/10);
|
||||
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.isEmpty())
|
||||
{
|
||||
// If nonce != "" we only received the basic connection info, so the pairing process is incomplete
|
||||
_ongoingPeerRequestResult = EncryptedConnectionStatus::REQUEST_TRANSMISSION_FAILED;
|
||||
_ongoingPeerRequestNonce.clear();
|
||||
}
|
||||
else if(encryptedConnectionEstablished(_ongoingPeerRequestResult))
|
||||
{
|
||||
if(_ongoingPeerRequestResult == EncryptedConnectionStatus::CONNECTION_ESTABLISHED)
|
||||
// Give the builder a chance to update the message
|
||||
requestMessage = encryptionRequestBuilder(requestNonce, existingTimeTracker);
|
||||
else if(_ongoingPeerRequestResult == EncryptedConnectionStatus::SOFT_LIMIT_CONNECTION_ESTABLISHED)
|
||||
// We will only get a soft limit connection. Adjust future actions based on this.
|
||||
requestMessage = Serializer::createEncryptionRequestHmacMessage(FPSTR(temporaryEncryptionRequestHeader), requestNonce, _connectionManager.getEspnowHashKey(),
|
||||
hashKeyLength, _database.getAutoEncryptionDuration());
|
||||
else
|
||||
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(EspnowTransmitter::espnowSendToNode(String(FPSTR(encryptedConnectionVerificationHeader)) + requestMessage, peerMac, 'P') == TransmissionStatusType::TRANSMISSION_COMPLETE
|
||||
&& !_ongoingPeerRequestEncryptionTimeout)
|
||||
{
|
||||
EncryptedConnectionLog *encryptedConnection = EspnowConnectionManager::getEncryptedConnection(peerMac);
|
||||
if(!encryptedConnection)
|
||||
{
|
||||
assert(encryptedConnection && String(F("requestEncryptedConnectionKernel cannot find an encrypted connection!")));
|
||||
// requestEncryptedConnectionRemoval received.
|
||||
_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 = EncryptedConnectionStatus::REQUEST_TRANSMISSION_FAILED;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Finalize connection
|
||||
if(messageHeader == FPSTR(encryptionRequestHeader))
|
||||
{
|
||||
EspnowConnectionManager::temporaryEncryptedConnectionToPermanent(peerMac);
|
||||
}
|
||||
else if(messageHeader == FPSTR(temporaryEncryptionRequestHeader))
|
||||
{
|
||||
if(encryptedConnection->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 && String(F("Unknown messageHeader during encrypted connection finalization!")));
|
||||
_ongoingPeerRequestResult = EncryptedConnectionStatus::API_CALL_FAILED;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
_ongoingPeerRequestResult = EncryptedConnectionStatus::REQUEST_TRANSMISSION_FAILED;
|
||||
}
|
||||
}
|
||||
|
||||
if(!encryptedConnectionEstablished(_ongoingPeerRequestResult))
|
||||
{
|
||||
if(!existingEncryptedConnection && !_reciprocalPeerRequestConfirmation)
|
||||
{
|
||||
// Remove any connection that was added during the request attempt and is no longer in use.
|
||||
EspnowConnectionManager::removeEncryptedConnectionUnprotected(peerMac);
|
||||
}
|
||||
}
|
||||
|
||||
_ongoingPeerRequester = nullptr;
|
||||
|
||||
return _ongoingPeerRequestResult;
|
||||
}
|
||||
|
||||
EncryptedConnectionStatus EspnowEncryptionBroker::initiateAutoEncryptingConnection(const EspnowNetworkInfo &recipientInfo, const bool requestPermanentConnection, uint8_t *targetBSSID, EncryptedConnectionLog **existingEncryptedConnection, EspnowMeshBackend &espnowInstance)
|
||||
{
|
||||
assert(recipientInfo.BSSID() != nullptr); // We need at least the BSSID to connect
|
||||
recipientInfo.getBSSID(targetBSSID);
|
||||
|
||||
if(_conditionalPrinter.verboseMode()) // Avoid string generation if not required
|
||||
{
|
||||
espnowInstance.printAPInfo(recipientInfo);
|
||||
_conditionalPrinter.verboseModePrint(emptyString);
|
||||
}
|
||||
|
||||
*existingEncryptedConnection = EspnowConnectionManager::getEncryptedConnection(targetBSSID);
|
||||
EncryptedConnectionStatus connectionStatus = EncryptedConnectionStatus::MAX_CONNECTIONS_REACHED_SELF;
|
||||
|
||||
if(requestPermanentConnection)
|
||||
connectionStatus = requestEncryptedConnection(targetBSSID, espnowInstance);
|
||||
else
|
||||
connectionStatus = requestFlexibleTemporaryEncryptedConnection(targetBSSID, _database.getAutoEncryptionDuration(), espnowInstance);
|
||||
|
||||
return connectionStatus;
|
||||
}
|
||||
|
||||
void EspnowEncryptionBroker::finalizeAutoEncryptingConnection(const uint8_t *targetBSSID, const EncryptedConnectionLog *existingEncryptedConnection, const bool requestPermanentConnection)
|
||||
{
|
||||
if(!existingEncryptedConnection && !requestPermanentConnection && !_reciprocalPeerRequestConfirmation)
|
||||
{
|
||||
// Remove any connection that was added during the transmission attempt and is no longer in use.
|
||||
EspnowConnectionManager::removeEncryptedConnectionUnprotected(targetBSSID);
|
||||
}
|
||||
}
|
||||
|
||||
String EspnowEncryptionBroker::defaultEncryptionRequestBuilder(const String &requestHeader, const uint32_t durationMs, const uint8_t *hashKey,
|
||||
const String &requestNonce, const ExpiringTimeTracker &existingTimeTracker)
|
||||
{
|
||||
(void)existingTimeTracker; // This removes a "unused parameter" compiler warning. Does nothing else.
|
||||
|
||||
return Serializer::createEncryptionRequestHmacMessage(requestHeader, requestNonce, hashKey, hashKeyLength, durationMs);
|
||||
}
|
||||
|
||||
String EspnowEncryptionBroker::flexibleEncryptionRequestBuilder(const uint32_t minDurationMs, const uint8_t *hashKey,
|
||||
const String &requestNonce, const ExpiringTimeTracker &existingTimeTracker)
|
||||
{
|
||||
using namespace JsonTranslator;
|
||||
using EspnowProtocolInterpreter::temporaryEncryptionRequestHeader;
|
||||
|
||||
uint32_t connectionDuration = minDurationMs >= existingTimeTracker.remainingDuration() ?
|
||||
minDurationMs : existingTimeTracker.remainingDuration();
|
||||
|
||||
return Serializer::createEncryptionRequestHmacMessage(FPSTR(temporaryEncryptionRequestHeader), requestNonce, hashKey, hashKeyLength, connectionDuration);
|
||||
}
|
105
libraries/ESP8266WiFiMesh/src/EspnowEncryptionBroker.h
Normal file
105
libraries/ESP8266WiFiMesh/src/EspnowEncryptionBroker.h
Normal file
@ -0,0 +1,105 @@
|
||||
/*
|
||||
Copyright (C) 2020 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
|
||||
*/
|
||||
|
||||
#ifndef __ESPNOWENCRYPTIONBROKER_H__
|
||||
#define __ESPNOWENCRYPTIONBROKER_H__
|
||||
|
||||
#include <Arduino.h>
|
||||
#include "ConditionalPrinter.h"
|
||||
#include "EspnowDatabase.h"
|
||||
#include "EspnowConnectionManager.h"
|
||||
#include "EspnowTransmitter.h"
|
||||
|
||||
class EspnowMeshBackend;
|
||||
|
||||
class EspnowEncryptionBroker
|
||||
{
|
||||
|
||||
public:
|
||||
|
||||
EspnowEncryptionBroker(ConditionalPrinter &conditionalPrinterInstance, EspnowDatabase &databaseInstance, EspnowConnectionManager &connectionManagerInstance, EspnowTransmitter &transmitterInstance);
|
||||
|
||||
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);
|
||||
|
||||
/*
|
||||
* @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 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);
|
||||
|
||||
EncryptedConnectionStatus requestEncryptedConnection(const uint8_t *peerMac, EspnowMeshBackend &espnowInstance);
|
||||
EncryptedConnectionStatus requestTemporaryEncryptedConnection(const uint8_t *peerMac, const uint32_t durationMs, EspnowMeshBackend &espnowInstance);
|
||||
EncryptedConnectionStatus requestFlexibleTemporaryEncryptedConnection(const uint8_t *peerMac, const uint32_t minDurationMs, EspnowMeshBackend &espnowInstance);
|
||||
EncryptedConnectionRemovalOutcome requestEncryptedConnectionRemoval(const uint8_t *peerMac);
|
||||
|
||||
static bool encryptedConnectionEstablished(const EncryptedConnectionStatus connectionStatus);
|
||||
|
||||
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(const uint64_t sessionKey, const uint8_t *peerMac);
|
||||
static bool synchronizePeerSessionKey(const uint64_t sessionKey, EncryptedConnectionLog &encryptedConnection);
|
||||
|
||||
/**
|
||||
* 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(const bool receivedEncryptedTransmission);
|
||||
bool receivedEncryptedTransmission() const;
|
||||
|
||||
/**
|
||||
* 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();
|
||||
|
||||
EncryptedConnectionStatus initiateAutoEncryptingConnection(const EspnowNetworkInfo &recipientInfo, const bool requestPermanentConnection, uint8_t *targetBSSID, EncryptedConnectionLog **existingEncryptedConnection, EspnowMeshBackend &espnowInstance);
|
||||
void finalizeAutoEncryptingConnection(const uint8_t *targetBSSID, const EncryptedConnectionLog *existingEncryptedConnection, const bool requestPermanentConnection);
|
||||
|
||||
private:
|
||||
|
||||
ConditionalPrinter & _conditionalPrinter;
|
||||
EspnowDatabase & _database;
|
||||
EspnowConnectionManager & _connectionManager;
|
||||
EspnowTransmitter & _transmitter;
|
||||
|
||||
bool _receivedEncryptedTransmission = false;
|
||||
|
||||
using encryptionRequestBuilderType = std::function<String(const String &, const ExpiringTimeTracker &)>;
|
||||
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);
|
||||
|
||||
/**
|
||||
* 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 found in Serializer::createEncryptionRequestHmacMessage.
|
||||
* @param espnowInstance The EspnowMeshBackend instance that is requesting the encrypted connection.
|
||||
* @return The ultimate status of the requested encrypted connection, as EncryptedConnectionStatus.
|
||||
*/
|
||||
EncryptedConnectionStatus requestEncryptedConnectionKernel(const uint8_t *peerMac, const encryptionRequestBuilderType &encryptionRequestBuilder, EspnowMeshBackend &espnowInstance);
|
||||
|
||||
static bool verifyEncryptionRequestHmac(const String &encryptionRequestHmacMessage, const uint8_t *requesterStaMac, const uint8_t *requesterApMac, const uint8_t *hashKey, const uint8_t hashKeyLength);
|
||||
};
|
||||
|
||||
#endif
|
File diff suppressed because it is too large
Load Diff
@ -78,6 +78,10 @@
|
||||
#ifndef __ESPNOWMESHBACKEND_H__
|
||||
#define __ESPNOWMESHBACKEND_H__
|
||||
|
||||
#include "EspnowDatabase.h"
|
||||
#include "EspnowConnectionManager.h"
|
||||
#include "EspnowTransmitter.h"
|
||||
#include "EspnowEncryptionBroker.h"
|
||||
#include "MeshBackendBase.h"
|
||||
#include "EspnowProtocolInterpreter.h"
|
||||
#include "EncryptedConnectionLog.h"
|
||||
@ -90,35 +94,6 @@
|
||||
#include "EspnowNetworkInfo.h"
|
||||
#include "CryptoInterface.h"
|
||||
|
||||
namespace Espnow
|
||||
{
|
||||
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.
|
||||
enum class EncryptedConnectionStatus
|
||||
{
|
||||
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. See the setEncryptedConnectionsSoftLimit method documentation for details.
|
||||
};
|
||||
|
||||
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.
|
||||
* 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.
|
||||
@ -130,16 +105,11 @@ enum class EncryptedConnectionRemovalOutcome
|
||||
*/
|
||||
void espnowDelay(const uint32_t durationMs);
|
||||
|
||||
class RequestData;
|
||||
|
||||
using namespace Espnow; // TODO: Remove
|
||||
|
||||
class EspnowMeshBackend : public MeshBackendBase {
|
||||
|
||||
protected:
|
||||
|
||||
using broadcastFilterType = std::function<bool(String &, EspnowMeshBackend &)>;
|
||||
using responseTransmittedHookType = std::function<bool(const String &, const uint8_t *, uint32_t, EspnowMeshBackend &)>;
|
||||
|
||||
public:
|
||||
|
||||
@ -488,6 +458,25 @@ public:
|
||||
|
||||
const uint8_t *getEspnowHashKey() const;
|
||||
|
||||
/**
|
||||
* 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(const bool useEncryptedMessages);
|
||||
static bool useEncryptedMessages();
|
||||
|
||||
/**
|
||||
* 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.
|
||||
@ -515,25 +504,6 @@ public:
|
||||
* @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(const bool useEncryptedMessages);
|
||||
static bool useEncryptedMessages();
|
||||
|
||||
/**
|
||||
* Hint: Use String.length() to get the ASCII length of a String.
|
||||
@ -679,8 +649,8 @@ public:
|
||||
* 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(const responseTransmittedHookType responseTransmittedHook);
|
||||
responseTransmittedHookType getResponseTransmittedHook() const;
|
||||
void setResponseTransmittedHook(const EspnowTransmitter::responseTransmittedHookType responseTransmittedHook);
|
||||
EspnowTransmitter::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.
|
||||
@ -960,22 +930,13 @@ public:
|
||||
* Reset TransmissionFailRate back to 0.
|
||||
*/
|
||||
static void resetTransmissionFailRate();
|
||||
|
||||
void setWiFiChannel(const uint8 newWiFiChannel) override;
|
||||
|
||||
protected:
|
||||
|
||||
static std::vector<EspnowNetworkInfo> _connectionQueue;
|
||||
static std::vector<TransmissionOutcome> _latestTransmissionOutcomes;
|
||||
|
||||
using connectionLogIterator = std::vector<EncryptedConnectionLog>::iterator;
|
||||
static connectionLogIterator connectionLogEndIterator();
|
||||
|
||||
static const uint8_t broadcastMac[6];
|
||||
static const uint64_t uint64BroadcastMac;
|
||||
|
||||
bool activateEspnow();
|
||||
|
||||
static bool encryptedConnectionEstablished(const EncryptedConnectionStatus connectionStatus);
|
||||
|
||||
|
||||
/*
|
||||
* 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
|
||||
@ -989,125 +950,31 @@ protected:
|
||||
* 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 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 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);
|
||||
|
||||
static void clearOldLogEntries();
|
||||
|
||||
static uint32_t getMaxBytesPerTransmission();
|
||||
|
||||
static std::list<ResponseData>::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).
|
||||
// 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 EncryptedConnectionRemovalOutcome removeEncryptedConnectionUnprotected(const uint8_t *peerMac, std::vector<EncryptedConnectionLog>::iterator *resultingIterator = nullptr);
|
||||
static EncryptedConnectionRemovalOutcome removeEncryptedConnectionUnprotected(connectionLogIterator &connectionIterator, std::vector<EncryptedConnectionLog>::iterator *resultingIterator);
|
||||
|
||||
/**
|
||||
* 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(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(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(const bool receivedEncryptedTransmission);
|
||||
|
||||
static bool temporaryEncryptedConnectionToPermanent(const uint8_t *peerMac);
|
||||
|
||||
/**
|
||||
* Will be true if a transmission initiated by a public method is in progress.
|
||||
*/
|
||||
static std::shared_ptr<bool> _espnowTransmissionMutex;
|
||||
|
||||
/**
|
||||
* Will be true when the connectionQueue should not be modified.
|
||||
*/
|
||||
static std::shared_ptr<bool> _espnowConnectionQueueMutex;
|
||||
|
||||
/**
|
||||
* Will be true when no responsesToSend element should be removed.
|
||||
*/
|
||||
static std::shared_ptr<bool> _responsesToSendMutex;
|
||||
|
||||
/**
|
||||
* Check if there is an ongoing ESP-NOW transmission in the library. Used to avoid interrupting transmissions.
|
||||
*
|
||||
* @return True if a transmission initiated by a public method is in progress.
|
||||
*/
|
||||
static bool transmissionInProgress();
|
||||
|
||||
enum class macAndType_td : uint64_t {};
|
||||
using messageID_td = uint64_t;
|
||||
using peerMac_td = uint64_t;
|
||||
|
||||
static macAndType_td createMacAndTypeValue(const uint64_t uint64Mac, const 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 <typename T>
|
||||
static void deleteEntriesByMac(std::map<std::pair<uint64_t, uint64_t>, T> &logEntries, const uint8_t *peerMac, const bool encryptedOnly);
|
||||
|
||||
template <typename T>
|
||||
static void deleteEntriesByMac(std::map<std::pair<macAndType_td, uint64_t>, T> &logEntries, const uint8_t *peerMac, const bool encryptedOnly);
|
||||
|
||||
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.
|
||||
*
|
||||
* @param messageType The identifier character for the type of message to send. Choices 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.
|
||||
* @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, 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, const char messageType, const uint64_t messageID, EspnowMeshBackend *espnowInstance = nullptr);
|
||||
|
||||
TransmissionStatusType sendRequest(const String &message, const uint8_t *targetBSSID);
|
||||
TransmissionStatusType sendResponse(const String &message, const uint64_t requestID, const uint8_t *targetBSSID);
|
||||
using macAndType_td = EspnowProtocolInterpreter::macAndType_td;
|
||||
using messageID_td = EspnowProtocolInterpreter::messageID_td;
|
||||
using peerMac_td = EspnowProtocolInterpreter::peerMac_td;
|
||||
|
||||
private:
|
||||
|
||||
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<String(const String &, const ExpiringTimeTracker &)>;
|
||||
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);
|
||||
EspnowDatabase _database;
|
||||
EspnowConnectionManager _connectionManager;
|
||||
EspnowTransmitter _transmitter;
|
||||
EspnowEncryptionBroker _encryptionBroker;
|
||||
|
||||
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);
|
||||
TransmissionStatusType initiateAutoEncryptingTransmission(const String &message, uint8_t *targetBSSID, const EncryptedConnectionStatus connectionStatus);
|
||||
void printTransmissionStatistics() const;
|
||||
|
||||
// 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;
|
||||
|
||||
/**
|
||||
* We can't feed esp_now_register_recv_cb our EspnowMeshBackend instance's espnowReceiveCallback method directly, so this callback wrapper is a workaround.
|
||||
@ -1118,142 +985,9 @@ private:
|
||||
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(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(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(const uint64_t sessionKey, const uint8_t *peerMac);
|
||||
static bool synchronizePeerSessionKey(const uint64_t sessionKey, EncryptedConnectionLog &encryptedConnection);
|
||||
|
||||
uint32_t _autoEncryptionDuration = 50;
|
||||
|
||||
uint8_t _encryptedConnectionsSoftLimit = 6;
|
||||
|
||||
static std::map<std::pair<macAndType_td, messageID_td>, MessageData> receivedEspnowTransmissions;
|
||||
static std::map<std::pair<peerMac_td, messageID_td>, RequestData> sentRequests;
|
||||
static std::map<std::pair<peerMac_td, messageID_td>, 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 EncryptedConnectionLog *getEncryptedConnection(const uint8_t *peerMac);
|
||||
static EncryptedConnectionLog *getTemporaryEncryptedConnection(const uint8_t *peerMac);
|
||||
|
||||
//@return iterator to connection in connectionContainer, or connectionContainer.end() if element not found
|
||||
template <typename T>
|
||||
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 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,
|
||||
// not other connections which have expired.
|
||||
static void updateTemporaryEncryptedConnections(const bool scheduledRemovalOnly = false);
|
||||
|
||||
template <typename T, typename U>
|
||||
static void deleteExpiredLogEntries(std::map<std::pair<U, uint64_t>, T> &logEntries, const uint32_t maxEntryLifetimeMs);
|
||||
|
||||
template <typename U>
|
||||
static void deleteExpiredLogEntries(std::map<std::pair<U, uint64_t>, TimeTracker> &logEntries, const uint32_t maxEntryLifetimeMs);
|
||||
|
||||
static void deleteExpiredLogEntries(std::map<std::pair<peerMac_td, messageID_td>, RequestData> &logEntries, const uint32_t requestLifetimeMs, const uint32_t broadcastLifetimeMs);
|
||||
|
||||
template <typename T>
|
||||
static void deleteExpiredLogEntries(std::list<T> &logEntries, const uint32_t maxEntryLifetimeMs);
|
||||
|
||||
broadcastFilterType _broadcastFilter;
|
||||
responseTransmittedHookType _responseTransmittedHook = [](const String &, const uint8_t *, uint32_t, EspnowMeshBackend &){ return true; };
|
||||
|
||||
uint8_t _broadcastTransmissionRedundancy = 1;
|
||||
|
||||
template <typename T>
|
||||
static T *getMapValue(std::map<uint64_t, T> &mapIn, const uint64_t keyIn);
|
||||
|
||||
static bool usesConstantSessionKey(const char messageType);
|
||||
|
||||
bool _acceptsUnverifiedRequests = true;
|
||||
|
||||
uint8_t _espnowEncryptedConnectionKey[EspnowProtocolInterpreter::encryptedConnectionKeyLength] {0};
|
||||
uint8_t _espnowHashKey[EspnowProtocolInterpreter::hashKeyLength] {0};
|
||||
|
||||
uint8_t _senderMac[6] = {0};
|
||||
uint8_t _senderAPMac[6] = {0};
|
||||
bool _receivedEncryptedTransmission = false;
|
||||
|
||||
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.
|
||||
*
|
||||
* @return A valid EspnowMeshBackend pointer if a matching entry is found in the EspnowMeshBackend sentRequests container. nullptr otherwise.
|
||||
*/
|
||||
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(const uint64_t requestMac, const uint64_t requestID);
|
||||
|
||||
static size_t deleteSentRequestsByOwner(const 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 found in Serializer::createEncryptionRequestHmacMessage.
|
||||
* @return The ultimate status of the requested encrypted connection, as EncryptedConnectionStatus.
|
||||
*/
|
||||
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.
|
||||
*
|
||||
* @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(const 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.
|
||||
*
|
||||
* @return A uint64_t containing a new session key for an encrypted connection.
|
||||
*/
|
||||
static uint64_t createSessionKey();
|
||||
|
||||
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() const;
|
||||
|
||||
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;
|
||||
uint32_t successfulTransmissions_AT = 0;
|
||||
uint32_t maxTransmissionDuration_AT = 0;
|
||||
|
||||
static bool _staticVerboseMode;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
@ -25,7 +25,8 @@
|
||||
#include "EspnowProtocolInterpreter.h"
|
||||
#include "TypeConversionFunctions.h"
|
||||
#include <algorithm>
|
||||
#include "EspnowMeshBackend.h"
|
||||
#include "EspnowTransmitter.h"
|
||||
#include "UtilityFunctions.h"
|
||||
|
||||
namespace
|
||||
{
|
||||
@ -36,7 +37,17 @@ namespace EspnowProtocolInterpreter
|
||||
{
|
||||
uint8_t metadataSize()
|
||||
{
|
||||
return protocolBytesSize + (EspnowMeshBackend::useEncryptedMessages() ? aeadMetadataSize : 0);
|
||||
return protocolBytesSize + (EspnowTransmitter::useEncryptedMessages() ? aeadMetadataSize : 0);
|
||||
}
|
||||
|
||||
uint32_t getMaxBytesPerTransmission()
|
||||
{
|
||||
return 250;
|
||||
}
|
||||
|
||||
uint32_t getMaxMessageBytesPerTransmission()
|
||||
{
|
||||
return getMaxBytesPerTransmission() - metadataSize();
|
||||
}
|
||||
|
||||
String getHashKeyLength(uint8_t *transmissionDataArray, const uint8_t transmissionLength)
|
||||
@ -94,4 +105,25 @@ namespace EspnowProtocolInterpreter
|
||||
// At least one of the leftmost half of bits in messageID is 1 if the transmission is encrypted.
|
||||
return messageID & uint64LeftmostBits;
|
||||
}
|
||||
|
||||
bool usesConstantSessionKey(const char messageType)
|
||||
{
|
||||
return messageType == 'A' || messageType == 'C';
|
||||
}
|
||||
|
||||
uint64_t createSessionKey()
|
||||
{
|
||||
uint64_t newSessionKey = MeshUtilityFunctions::randomUint64();
|
||||
return usesEncryption(newSessionKey) ? newSessionKey : (newSessionKey | ((uint64_t)RANDOM_REG32) << 32 | uint64MSB); // TODO: Replace RANDOM_REG32 use with ESP.random().
|
||||
}
|
||||
|
||||
macAndType_td createMacAndTypeValue(const uint64_t uint64Mac, const char messageType)
|
||||
{
|
||||
return static_cast<macAndType_td>(uint64Mac << 8 | (uint64_t)messageType);
|
||||
}
|
||||
|
||||
uint64_t macAndTypeToUint64Mac(const macAndType_td &macAndTypeValue)
|
||||
{
|
||||
return static_cast<uint64_t>(macAndTypeValue) >> 8;
|
||||
}
|
||||
}
|
||||
|
@ -57,11 +57,19 @@ namespace EspnowProtocolInterpreter
|
||||
constexpr uint8_t protocolBytesSize = 16;
|
||||
constexpr uint8_t aeadMetadataSize = 28;
|
||||
uint8_t metadataSize();
|
||||
uint32_t getMaxBytesPerTransmission();
|
||||
uint32_t getMaxMessageBytesPerTransmission();
|
||||
|
||||
constexpr uint8_t broadcastMac[6] = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF};
|
||||
constexpr uint64_t uint64BroadcastMac = 0xFFFFFFFFFFFF;
|
||||
|
||||
constexpr 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.
|
||||
|
||||
constexpr uint8_t encryptedConnectionKeyLength = 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 hashKeyLength = 16; // This can be changed to any value up to 255. Common values are 16 and 32.
|
||||
|
||||
constexpr uint64_t uint64LeftmostBits = 0xFFFFFFFF00000000;
|
||||
constexpr uint64_t uint64MSB = 0x8000000000000000;
|
||||
|
||||
String getHashKeyLength(uint8_t *transmissionDataArray, const uint8_t transmissionLength);
|
||||
char getMessageType(const uint8_t *transmissionDataArray);
|
||||
@ -74,6 +82,23 @@ namespace EspnowProtocolInterpreter
|
||||
uint8_t *setMessageID(uint8_t *transmissionDataArray, const uint64_t messageID);
|
||||
|
||||
bool usesEncryption(const uint64_t messageID);
|
||||
bool usesConstantSessionKey(const char messageType);
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* @return A uint64_t containing a new session key for an encrypted connection.
|
||||
*/
|
||||
uint64_t createSessionKey();
|
||||
|
||||
enum class macAndType_td : uint64_t {};
|
||||
using messageID_td = uint64_t;
|
||||
using peerMac_td = uint64_t;
|
||||
|
||||
macAndType_td createMacAndTypeValue(const uint64_t uint64Mac, const char messageType);
|
||||
uint64_t macAndTypeToUint64Mac(const macAndType_td &macAndTypeValue);
|
||||
}
|
||||
|
||||
#endif
|
||||
|
426
libraries/ESP8266WiFiMesh/src/EspnowTransmitter.cpp
Normal file
426
libraries/ESP8266WiFiMesh/src/EspnowTransmitter.cpp
Normal file
@ -0,0 +1,426 @@
|
||||
/*
|
||||
Copyright (C) 2020 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 <ESP8266WiFi.h>
|
||||
extern "C" {
|
||||
#include <espnow.h>
|
||||
}
|
||||
|
||||
#include "EspnowTransmitter.h"
|
||||
#include "EspnowMeshBackend.h"
|
||||
#include "TypeConversionFunctions.h"
|
||||
#include "UtilityFunctions.h"
|
||||
#include "MeshCryptoInterface.h"
|
||||
#include "JsonTranslator.h"
|
||||
|
||||
namespace
|
||||
{
|
||||
namespace TypeCast = MeshTypeConversionFunctions;
|
||||
|
||||
double _transmissionsTotal = 0;
|
||||
double _transmissionsFailed = 0;
|
||||
|
||||
std::shared_ptr<bool> _espnowTransmissionMutex = std::make_shared<bool>(false);
|
||||
std::shared_ptr<bool> _espnowSendToNodeMutex = std::make_shared<bool>(false);
|
||||
|
||||
bool _useEncryptedMessages = false;
|
||||
uint8_t _espnowMessageEncryptionKey[CryptoInterface::ENCRYPTION_KEY_LENGTH] = { 0 };
|
||||
|
||||
uint8_t _transmissionTargetBSSID[6] = {0};
|
||||
|
||||
bool _espnowSendConfirmed = false;
|
||||
|
||||
uint8_t _maxTransmissionsPerMessage = 3;
|
||||
|
||||
uint32_t _espnowTransmissionTimeoutMs = 40;
|
||||
uint32_t _espnowRetransmissionIntervalMs = 15;
|
||||
}
|
||||
|
||||
EspnowTransmitter::EspnowTransmitter(ConditionalPrinter &conditionalPrinterInstance, EspnowDatabase &databaseInstance, EspnowConnectionManager &connectionManagerInstance)
|
||||
: _conditionalPrinter(conditionalPrinterInstance), _database(databaseInstance), _connectionManager(connectionManagerInstance)
|
||||
{
|
||||
}
|
||||
|
||||
void EspnowTransmitter::espnowSendCallback(uint8_t* mac, uint8_t sendStatus)
|
||||
{
|
||||
if(_espnowSendConfirmed)
|
||||
return;
|
||||
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.
|
||||
}
|
||||
|
||||
void EspnowTransmitter::setUseEncryptedMessages(const bool useEncryptedMessages)
|
||||
{
|
||||
MutexTracker mutexTracker(_espnowSendToNodeMutex);
|
||||
if(!mutexTracker.mutexCaptured())
|
||||
{
|
||||
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;
|
||||
}
|
||||
bool EspnowTransmitter::useEncryptedMessages() { return _useEncryptedMessages; }
|
||||
|
||||
void EspnowTransmitter::setEspnowMessageEncryptionKey(const 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 EspnowTransmitter::setEspnowMessageEncryptionKey(const String &espnowMessageEncryptionKeySeed)
|
||||
{
|
||||
MeshCryptoInterface::initializeKey(_espnowMessageEncryptionKey, CryptoInterface::ENCRYPTION_KEY_LENGTH, espnowMessageEncryptionKeySeed);
|
||||
}
|
||||
|
||||
const uint8_t *EspnowTransmitter::getEspnowMessageEncryptionKey()
|
||||
{
|
||||
return _espnowMessageEncryptionKey;
|
||||
}
|
||||
|
||||
void EspnowTransmitter::setBroadcastTransmissionRedundancy(const uint8_t redundancy) { _broadcastTransmissionRedundancy = redundancy; }
|
||||
uint8_t EspnowTransmitter::getBroadcastTransmissionRedundancy() const { return _broadcastTransmissionRedundancy; }
|
||||
|
||||
void EspnowTransmitter::setResponseTransmittedHook(const responseTransmittedHookType responseTransmittedHook) { _responseTransmittedHook = responseTransmittedHook; }
|
||||
EspnowTransmitter::responseTransmittedHookType EspnowTransmitter::getResponseTransmittedHook() const { return _responseTransmittedHook; }
|
||||
|
||||
void EspnowTransmitter::setMaxTransmissionsPerMessage(const uint8_t maxTransmissionsPerMessage)
|
||||
{
|
||||
assert(1 <= maxTransmissionsPerMessage && maxTransmissionsPerMessage <= 128);
|
||||
|
||||
_maxTransmissionsPerMessage = maxTransmissionsPerMessage;
|
||||
}
|
||||
|
||||
uint8_t EspnowTransmitter::getMaxTransmissionsPerMessage() {return _maxTransmissionsPerMessage;}
|
||||
|
||||
uint32_t EspnowTransmitter::getMaxMessageLength()
|
||||
{
|
||||
return getMaxTransmissionsPerMessage() * EspnowProtocolInterpreter::getMaxMessageBytesPerTransmission();
|
||||
}
|
||||
|
||||
void EspnowTransmitter::setEspnowTransmissionTimeout(const uint32_t timeoutMs)
|
||||
{
|
||||
_espnowTransmissionTimeoutMs = timeoutMs;
|
||||
}
|
||||
uint32_t EspnowTransmitter::getEspnowTransmissionTimeout() {return _espnowTransmissionTimeoutMs;}
|
||||
|
||||
void EspnowTransmitter::setEspnowRetransmissionInterval(const uint32_t intervalMs)
|
||||
{
|
||||
_espnowRetransmissionIntervalMs = intervalMs;
|
||||
}
|
||||
uint32_t EspnowTransmitter::getEspnowRetransmissionInterval() {return _espnowRetransmissionIntervalMs;}
|
||||
|
||||
double EspnowTransmitter::getTransmissionFailRate()
|
||||
{
|
||||
if(_transmissionsTotal == 0)
|
||||
return 0;
|
||||
|
||||
return _transmissionsFailed/_transmissionsTotal;
|
||||
}
|
||||
|
||||
void EspnowTransmitter::resetTransmissionFailRate()
|
||||
{
|
||||
_transmissionsFailed = 0;
|
||||
_transmissionsTotal = 0;
|
||||
}
|
||||
|
||||
void EspnowTransmitter::sendEspnowResponses(const ExpiringTimeTracker *estimatedMaxDurationTracker)
|
||||
{
|
||||
uint32_t bufferedCriticalHeapLevel = EspnowDatabase::criticalHeapLevel() + EspnowDatabase::criticalHeapLevelBuffer(); // We preferably want to start clearing the logs a bit before things get critical.
|
||||
|
||||
MutexTracker responsesToSendMutexTracker(EspnowDatabase::captureResponsesToSendMutex());
|
||||
if(!responsesToSendMutexTracker.mutexCaptured())
|
||||
{
|
||||
assert(false && String(F("ERROR! responsesToSend locked. Don't call sendEspnowResponses from callbacks as this may corrupt program state! Aborting.")));
|
||||
}
|
||||
|
||||
uint32_t responseIndex = 0;
|
||||
for(std::list<ResponseData>::iterator responseIterator = EspnowDatabase::responsesToSend().begin(); responseIterator != EspnowDatabase::responsesToSend().end(); ++responseIndex)
|
||||
{
|
||||
if(responseIterator->getTimeTracker().timeSinceCreation() > EspnowDatabase::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;
|
||||
}
|
||||
|
||||
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())
|
||||
== TransmissionStatusType::TRANSMISSION_COMPLETE)
|
||||
{
|
||||
if(EspnowMeshBackend *currentEspnowRequestManager = EspnowMeshBackend::getEspnowRequestManager())
|
||||
hookOutcome = currentEspnowRequestManager->getResponseTransmittedHook()(responseIterator->getMessage(), responseIterator->getRecipientMac(), responseIndex, *currentEspnowRequestManager);
|
||||
|
||||
responseIterator = EspnowDatabase::responsesToSend().erase(responseIterator);
|
||||
--responseIndex;
|
||||
}
|
||||
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.
|
||||
ConditionalPrinter::warningPrint("WARNING! Free heap below chosen minimum. Performing emergency log clearing.");
|
||||
EspnowDatabase::clearOldLogEntries(true);
|
||||
return; // responseIterator may be invalid now. Also, we should give the main loop a chance to respond to the situation.
|
||||
}
|
||||
|
||||
if(!hookOutcome || (estimatedMaxDurationTracker && estimatedMaxDurationTracker->expired()))
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
MutexTracker EspnowTransmitter::captureEspnowTransmissionMutex()
|
||||
{
|
||||
// Syntax like this will move the resulting value into its new position (similar to NRVO): https://stackoverflow.com/a/11540204
|
||||
return MutexTracker(_espnowTransmissionMutex);
|
||||
}
|
||||
|
||||
MutexTracker EspnowTransmitter::captureEspnowTransmissionMutex(const std::function<void()> destructorHook) { return MutexTracker(_espnowTransmissionMutex, destructorHook); }
|
||||
|
||||
bool EspnowTransmitter::transmissionInProgress(){return *_espnowTransmissionMutex;}
|
||||
|
||||
TransmissionStatusType EspnowTransmitter::espnowSendToNode(const String &message, const uint8_t *targetBSSID, const char messageType, EspnowMeshBackend *espnowInstance)
|
||||
{
|
||||
using EspnowProtocolInterpreter::synchronizationRequestHeader;
|
||||
|
||||
EncryptedConnectionLog *encryptedConnection = EspnowConnectionManager::getEncryptedConnection(targetBSSID);
|
||||
|
||||
if(encryptedConnection)
|
||||
{
|
||||
uint8_t encryptedMac[6] {0};
|
||||
encryptedConnection->getEncryptedPeerMac(encryptedMac);
|
||||
|
||||
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(FPSTR(synchronizationRequestHeader), encryptedMac, 'S', EspnowConnectionManager::generateMessageID(encryptedConnection), espnowInstance);
|
||||
|
||||
if(encryptedConnection->desync())
|
||||
{
|
||||
return TransmissionStatusType::TRANSMISSION_FAILED;
|
||||
}
|
||||
}
|
||||
|
||||
return espnowSendToNodeUnsynchronized(message, encryptedMac, messageType, EspnowConnectionManager::generateMessageID(encryptedConnection), espnowInstance);
|
||||
}
|
||||
|
||||
return espnowSendToNodeUnsynchronized(message, targetBSSID, messageType, EspnowConnectionManager::generateMessageID(encryptedConnection), espnowInstance);
|
||||
}
|
||||
|
||||
TransmissionStatusType EspnowTransmitter::espnowSendToNodeUnsynchronized(const String message, const uint8_t *targetBSSID, const char messageType, const uint64_t messageID, EspnowMeshBackend *espnowInstance)
|
||||
{
|
||||
using namespace EspnowProtocolInterpreter;
|
||||
|
||||
MutexTracker mutexTracker(_espnowSendToNodeMutex);
|
||||
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 TransmissionStatusType::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 = EspnowConnectionManager::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 == '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.
|
||||
}
|
||||
|
||||
uint8_t transmissionSize = 0;
|
||||
bool messageStart = true;
|
||||
uint8_t espnowMetadataSize = metadataSize();
|
||||
|
||||
do
|
||||
{
|
||||
////// Manage logs //////
|
||||
|
||||
if(transmissionsRemaining == 0 && (messageType == 'Q' || messageType == 'B'))
|
||||
{
|
||||
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.
|
||||
EspnowDatabase::storeSentRequest(TypeCast::macToUint64(_transmissionTargetBSSID), messageID, RequestData(*espnowInstance));
|
||||
}
|
||||
|
||||
////// Create transmission array //////
|
||||
|
||||
if(transmissionsRemaining > 0)
|
||||
{
|
||||
transmissionSize = getMaxBytesPerTransmission();
|
||||
}
|
||||
else
|
||||
{
|
||||
transmissionSize = espnowMetadataSize;
|
||||
|
||||
if(message.length() > 0)
|
||||
{
|
||||
uint32_t remainingLength = message.length() % getMaxMessageBytesPerTransmission();
|
||||
transmissionSize += (remainingLength == 0 ? getMaxMessageBytesPerTransmission() : remainingLength);
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t transmission[transmissionSize];
|
||||
|
||||
////// Fill protocol bytes //////
|
||||
|
||||
transmission[messageTypeIndex] = messageType;
|
||||
|
||||
if(messageStart)
|
||||
{
|
||||
transmission[transmissionsRemainingIndex] = (char)(transmissionsRemaining | 0x80);
|
||||
}
|
||||
else
|
||||
{
|
||||
transmission[transmissionsRemainingIndex] = (char)transmissionsRemaining;
|
||||
}
|
||||
|
||||
// Fills indicies in range [transmissionMacIndex, transmissionMacIndex + 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 + transmissionMacIndex);
|
||||
|
||||
setMessageID(transmission, messageID);
|
||||
|
||||
////// Fill message bytes //////
|
||||
|
||||
int32_t transmissionStartIndex = (transmissionsRequired - transmissionsRemaining - 1) * getMaxMessageBytesPerTransmission();
|
||||
|
||||
std::copy_n(message.begin() + transmissionStartIndex, transmissionSize - espnowMetadataSize, transmission + espnowMetadataSize);
|
||||
|
||||
if(useEncryptedMessages())
|
||||
{
|
||||
// chacha20Poly1305Encrypt encrypts transmission in place.
|
||||
// We are using the protocol bytes as a key salt.
|
||||
CryptoInterface::chacha20Poly1305Encrypt(transmission + espnowMetadataSize, transmissionSize - espnowMetadataSize, getEspnowMessageEncryptionKey(), transmission,
|
||||
protocolBytesSize, transmission + protocolBytesSize, transmission + protocolBytesSize + 12);
|
||||
}
|
||||
|
||||
////// Transmit //////
|
||||
|
||||
uint32_t retransmissions = 0;
|
||||
if(messageType == 'B')
|
||||
retransmissions = espnowInstance->getBroadcastTransmissionRedundancy();
|
||||
|
||||
for(uint32_t i = 0; i <= retransmissions; ++i)
|
||||
{
|
||||
_espnowSendConfirmed = false;
|
||||
ExpiringTimeTracker transmissionTimeout([](){ return getEspnowTransmissionTimeout(); });
|
||||
|
||||
while(!_espnowSendConfirmed && !transmissionTimeout)
|
||||
{
|
||||
if(esp_now_send(_transmissionTargetBSSID, transmission, transmissionSize) == 0) // == 0 => Success
|
||||
{
|
||||
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.
|
||||
}
|
||||
}
|
||||
|
||||
if(_espnowSendConfirmed)
|
||||
{
|
||||
if(messageStart)
|
||||
{
|
||||
if(encryptedConnection && !usesConstantSessionKey(messageType) && encryptedConnection->getOwnSessionKey() == messageID)
|
||||
{
|
||||
encryptedConnection->setDesync(false);
|
||||
encryptedConnection->incrementOwnSessionKey();
|
||||
}
|
||||
|
||||
messageStart = false;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(!_espnowSendConfirmed)
|
||||
{
|
||||
++_transmissionsFailed;
|
||||
|
||||
ConditionalPrinter::staticVerboseModePrint(String(F("espnowSendToNode failed!")));
|
||||
ConditionalPrinter::staticVerboseModePrint(String(F("Transmission #: ")) + String(transmissionsRequired - transmissionsRemaining) + String('/') + String(transmissionsRequired));
|
||||
ConditionalPrinter::staticVerboseModePrint(String(F("Transmission fail rate (up) ")) + String(getTransmissionFailRate()));
|
||||
|
||||
if(messageStart && encryptedConnection && !usesConstantSessionKey(messageType) && encryptedConnection->getOwnSessionKey() == messageID)
|
||||
encryptedConnection->setDesync(true);
|
||||
|
||||
return TransmissionStatusType::TRANSMISSION_FAILED;
|
||||
}
|
||||
|
||||
--transmissionsRemaining; // This is used when transfering multi-transmission messages.
|
||||
|
||||
} while(transmissionsRemaining >= 0);
|
||||
|
||||
// Useful when debugging the protocol
|
||||
//_conditionalPrinter.staticVerboseModePrint("Sent to Mac: " + TypeCast::macToString(_transmissionTargetBSSID) + " ID: " + TypeCast::uint64ToString(messageID));
|
||||
|
||||
return TransmissionStatusType::TRANSMISSION_COMPLETE;
|
||||
}
|
||||
|
||||
TransmissionStatusType EspnowTransmitter::espnowSendPeerRequestConfirmationsUnsynchronized(const String message, const uint8_t *targetBSSID, const char messageType, EspnowMeshBackend *espnowInstance)
|
||||
{
|
||||
return espnowSendToNodeUnsynchronized(message, targetBSSID, messageType, EspnowConnectionManager::generateMessageID(nullptr), espnowInstance);
|
||||
}
|
||||
|
||||
TransmissionStatusType EspnowTransmitter::sendRequest(const String &message, const uint8_t *targetBSSID, EspnowMeshBackend *espnowInstance)
|
||||
{
|
||||
TransmissionStatusType transmissionStatus = espnowSendToNode(message, targetBSSID, 'Q', espnowInstance);
|
||||
|
||||
return transmissionStatus;
|
||||
}
|
||||
|
||||
TransmissionStatusType EspnowTransmitter::sendResponse(const String &message, const uint64_t requestID, const uint8_t *targetBSSID, EspnowMeshBackend *espnowInstance)
|
||||
{
|
||||
EncryptedConnectionLog *encryptedConnection = EspnowConnectionManager::getEncryptedConnection(targetBSSID);
|
||||
uint8_t encryptedMac[6] {0};
|
||||
|
||||
if(encryptedConnection)
|
||||
{
|
||||
encryptedConnection->getEncryptedPeerMac(encryptedMac);
|
||||
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, espnowInstance);
|
||||
}
|
109
libraries/ESP8266WiFiMesh/src/EspnowTransmitter.h
Normal file
109
libraries/ESP8266WiFiMesh/src/EspnowTransmitter.h
Normal file
@ -0,0 +1,109 @@
|
||||
/*
|
||||
Copyright (C) 2020 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
|
||||
*/
|
||||
|
||||
#ifndef __ESPNOWTRANSMITTER_H__
|
||||
#define __ESPNOWTRANSMITTER_H__
|
||||
|
||||
#include <Arduino.h>
|
||||
#include "ExpiringTimeTracker.h"
|
||||
#include "EspnowDatabase.h"
|
||||
#include "EspnowConnectionManager.h"
|
||||
#include "ConditionalPrinter.h"
|
||||
#include "CryptoInterface.h"
|
||||
|
||||
class EspnowMeshBackend;
|
||||
|
||||
class EspnowTransmitter
|
||||
{
|
||||
|
||||
public:
|
||||
|
||||
using responseTransmittedHookType = std::function<bool(const String &, const uint8_t *, uint32_t, EspnowMeshBackend &)>;
|
||||
|
||||
EspnowTransmitter(ConditionalPrinter &conditionalPrinterInstance, EspnowDatabase &databaseInstance, EspnowConnectionManager &connectionManagerInstance);
|
||||
|
||||
static void espnowSendCallback(uint8_t* mac, uint8_t sendStatus);
|
||||
|
||||
/**
|
||||
* 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), '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 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, const char messageType, const uint64_t messageID, EspnowMeshBackend *espnowInstance = nullptr);
|
||||
|
||||
// Send a PeerRequestConfirmation using exactly the arguments given, without consideration for any encrypted connections.
|
||||
static TransmissionStatusType espnowSendPeerRequestConfirmationsUnsynchronized(const String message, const uint8_t *targetBSSID, const char messageType, EspnowMeshBackend *espnowInstance = nullptr);
|
||||
|
||||
TransmissionStatusType sendRequest(const String &message, const uint8_t *targetBSSID, EspnowMeshBackend *espnowInstance);
|
||||
TransmissionStatusType sendResponse(const String &message, const uint64_t requestID, const uint8_t *targetBSSID, EspnowMeshBackend *espnowInstance);
|
||||
|
||||
static void setUseEncryptedMessages(const bool useEncryptedMessages);
|
||||
static bool useEncryptedMessages();
|
||||
static void setEspnowMessageEncryptionKey(const uint8_t espnowMessageEncryptionKey[CryptoInterface::ENCRYPTION_KEY_LENGTH]);
|
||||
static void setEspnowMessageEncryptionKey(const String &espnowMessageEncryptionKeySeed);
|
||||
static const uint8_t *getEspnowMessageEncryptionKey();
|
||||
|
||||
void setBroadcastTransmissionRedundancy(const uint8_t redundancy);
|
||||
uint8_t getBroadcastTransmissionRedundancy() const;
|
||||
void setResponseTransmittedHook(const responseTransmittedHookType responseTransmittedHook);
|
||||
responseTransmittedHookType getResponseTransmittedHook() const;
|
||||
static void setMaxTransmissionsPerMessage(const uint8_t maxTransmissionsPerMessage);
|
||||
static uint8_t getMaxTransmissionsPerMessage();
|
||||
static uint32_t getMaxMessageLength();
|
||||
static void setEspnowTransmissionTimeout(const uint32_t timeoutMs);
|
||||
static uint32_t getEspnowTransmissionTimeout();
|
||||
static void setEspnowRetransmissionInterval(const uint32_t intervalMs);
|
||||
static uint32_t getEspnowRetransmissionInterval();
|
||||
static double getTransmissionFailRate();
|
||||
static void resetTransmissionFailRate();
|
||||
|
||||
/*
|
||||
* @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 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);
|
||||
|
||||
/**
|
||||
* Will be captured if a transmission initiated by a public method is in progress.
|
||||
*/
|
||||
static MutexTracker captureEspnowTransmissionMutex();
|
||||
static MutexTracker captureEspnowTransmissionMutex(const std::function<void()> destructorHook);
|
||||
|
||||
/**
|
||||
* Check if there is an ongoing ESP-NOW transmission in the library. Used to avoid interrupting transmissions.
|
||||
*
|
||||
* @return True if a transmission initiated by a public method is in progress.
|
||||
*/
|
||||
static bool transmissionInProgress();
|
||||
|
||||
private:
|
||||
|
||||
ConditionalPrinter & _conditionalPrinter;
|
||||
EspnowDatabase & _database;
|
||||
EspnowConnectionManager & _connectionManager;
|
||||
|
||||
uint8_t _broadcastTransmissionRedundancy = 1;
|
||||
|
||||
responseTransmittedHookType _responseTransmittedHook = [](const String &, const uint8_t *, uint32_t, EspnowMeshBackend &){ return true; };
|
||||
};
|
||||
|
||||
#endif
|
@ -25,7 +25,6 @@
|
||||
#include "JsonTranslator.h"
|
||||
#include "EspnowProtocolInterpreter.h"
|
||||
#include "TypeConversionFunctions.h"
|
||||
#include "MeshCryptoInterface.h" // TODO: Remove?
|
||||
|
||||
namespace
|
||||
{
|
||||
@ -187,31 +186,6 @@ namespace JsonTranslator
|
||||
return decoded;
|
||||
}
|
||||
|
||||
|
||||
|
||||
// TODO: Move to encryptedEspnow class?
|
||||
bool verifyEncryptionRequestHmac(const String &encryptionRequestHmacMessage, const uint8_t *requesterStaMac, const uint8_t *requesterApMac,
|
||||
const uint8_t *hashKey, const uint8_t hashKeyLength)
|
||||
{
|
||||
using MeshCryptoInterface::verifyMeshHmac;
|
||||
|
||||
String hmac;
|
||||
if(getHmac(encryptionRequestHmacMessage, hmac))
|
||||
{
|
||||
int32_t hmacStartIndex = encryptionRequestHmacMessage.indexOf(String('"') + FPSTR(jsonHmac) + F("\":"));
|
||||
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(TypeCast::macToString(requesterStaMac) + TypeCast::macToString(requesterApMac) + encryptionRequestHmacMessage.substring(0, hmacStartIndex), hmac, hashKey, hashKeyLength))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool getConnectionState(const String &jsonString, String &result)
|
||||
{
|
||||
return decode(jsonString, FPSTR(jsonConnectionState), result);
|
||||
|
@ -138,8 +138,6 @@ namespace JsonTranslator
|
||||
*/
|
||||
bool decodeRadix(const String &jsonString, const String &valueIdentifier, uint64_t &value, const uint8_t radix = 16);
|
||||
|
||||
bool verifyEncryptionRequestHmac(const String &encryptionRequestHmacMessage, const uint8_t *requesterStaMac, const uint8_t *requesterApMac, const uint8_t *hashKey, const uint8_t hashKeyLength);
|
||||
|
||||
bool getConnectionState(const String &jsonString, String &result);
|
||||
/**
|
||||
* Stores the value of the password field within jsonString into the result variable.
|
||||
|
@ -31,8 +31,6 @@ namespace
|
||||
|
||||
std::shared_ptr<bool> MeshBackendBase::_scanMutex = std::make_shared<bool>(false);
|
||||
|
||||
bool MeshBackendBase::_printWarnings = true;
|
||||
|
||||
MeshBackendBase::MeshBackendBase(const requestHandlerType requestHandler, const responseHandlerType responseHandler, const networkFilterType networkFilter, const MeshBackendType classType)
|
||||
{
|
||||
setRequestHandler(requestHandler);
|
||||
@ -53,6 +51,22 @@ void MeshBackendBase::setClassType(const MeshBackendType classType)
|
||||
|
||||
MeshBackendType MeshBackendBase::getClassType() const {return _classType;}
|
||||
|
||||
void MeshBackendBase::setVerboseModeState(const bool enabled) { _conditionalPrinter.setVerboseModeState(enabled); }
|
||||
bool MeshBackendBase::verboseMode() const { return _conditionalPrinter.verboseMode(); }
|
||||
|
||||
void MeshBackendBase::verboseModePrint(const String &stringToPrint, const bool newline) const
|
||||
{
|
||||
_conditionalPrinter.verboseModePrint(stringToPrint, newline);
|
||||
}
|
||||
|
||||
void MeshBackendBase::setPrintWarnings(const bool printEnabled) { ConditionalPrinter::setPrintWarnings(printEnabled); }
|
||||
bool MeshBackendBase::printWarnings() {return ConditionalPrinter::printWarnings();}
|
||||
|
||||
void MeshBackendBase::warningPrint(const String &stringToPrint, const bool newline)
|
||||
{
|
||||
ConditionalPrinter::warningPrint(stringToPrint, newline);
|
||||
}
|
||||
|
||||
void MeshBackendBase::activateAP()
|
||||
{
|
||||
// Deactivate active AP to avoid two servers using the same port, which can lead to crashes.
|
||||
|
@ -22,6 +22,7 @@
|
||||
#include <ESP8266WiFi.h>
|
||||
#include "TransmissionOutcome.h"
|
||||
#include "NetworkInfoBase.h"
|
||||
#include "ConditionalPrinter.h"
|
||||
|
||||
enum class MeshBackendType
|
||||
{
|
||||
@ -118,8 +119,8 @@ public:
|
||||
* @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(const uint8 newWiFiChannel);
|
||||
uint8 getWiFiChannel() const;
|
||||
virtual void setWiFiChannel(const uint8 newWiFiChannel);
|
||||
virtual uint8 getWiFiChannel() const;
|
||||
|
||||
/**
|
||||
* Change the SSID used by this MeshBackendBase instance.
|
||||
@ -285,6 +286,8 @@ public:
|
||||
static void warningPrint(const String &stringToPrint, const bool newline = true);
|
||||
|
||||
MeshBackendType getClassType() const;
|
||||
|
||||
virtual void printAPInfo(const NetworkInfoBase &apNetworkInfo);
|
||||
|
||||
protected:
|
||||
|
||||
@ -296,7 +299,6 @@ protected:
|
||||
static bool latestTransmissionSuccessfulBase(const std::vector<TransmissionOutcome> &latestTransmissionOutcomes);
|
||||
|
||||
virtual void scanForNetworks(const bool scanAllWiFiChannels);
|
||||
virtual void printAPInfo(const NetworkInfoBase &apNetworkInfo);
|
||||
|
||||
/**
|
||||
* Called just before we activate the AP.
|
||||
@ -314,6 +316,8 @@ protected:
|
||||
|
||||
static std::shared_ptr<bool> _scanMutex;
|
||||
|
||||
ConditionalPrinter _conditionalPrinter;
|
||||
|
||||
private:
|
||||
|
||||
MeshBackendType _classType;
|
||||
@ -324,7 +328,6 @@ private:
|
||||
String _SSIDSuffix;
|
||||
String _meshPassword;
|
||||
uint8 _meshWiFiChannel;
|
||||
bool _verboseMode;
|
||||
String _message;
|
||||
bool _scanHidden = false;
|
||||
bool _apHidden = false;
|
||||
@ -333,8 +336,6 @@ private:
|
||||
responseHandlerType _responseHandler;
|
||||
networkFilterType _networkFilter;
|
||||
transmissionOutcomesUpdateHookType _transmissionOutcomesUpdateHook = [](MeshBackendBase &){return true;};
|
||||
|
||||
static bool _printWarnings;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
@ -26,12 +26,7 @@
|
||||
|
||||
namespace
|
||||
{
|
||||
static std::shared_ptr<bool> _captureBan = std::make_shared<bool>(false);
|
||||
}
|
||||
|
||||
std::shared_ptr<bool> MutexTracker::captureBan()
|
||||
{
|
||||
return _captureBan;
|
||||
std::shared_ptr<bool> _captureBan = std::make_shared<bool>(false);
|
||||
}
|
||||
|
||||
MutexTracker::MutexTracker(const std::shared_ptr<bool> &mutexToCapture)
|
||||
@ -50,6 +45,14 @@ MutexTracker::~MutexTracker()
|
||||
_destructorHook();
|
||||
}
|
||||
|
||||
MutexTracker MutexTracker::captureBan()
|
||||
{
|
||||
// Syntax like this will move the resulting value into its new position (similar to NRVO): https://stackoverflow.com/a/11540204
|
||||
return MutexTracker(_captureBan);
|
||||
}
|
||||
|
||||
MutexTracker MutexTracker::captureBan(const std::function<void()> destructorHook) { return MutexTracker(_captureBan, destructorHook); }
|
||||
|
||||
bool MutexTracker::mutexFree(const std::shared_ptr<bool> &mutex)
|
||||
{
|
||||
if(mutex != nullptr && !(*mutex))
|
||||
@ -82,7 +85,7 @@ void MutexTracker::releaseMutex()
|
||||
|
||||
bool MutexTracker::attemptMutexCapture(const std::shared_ptr<bool> &mutexToCapture)
|
||||
{
|
||||
if(mutexFree(captureBan()) && mutexFree(mutexToCapture))
|
||||
if(mutexFree(_captureBan) && mutexFree(mutexToCapture))
|
||||
{
|
||||
_capturedMutex = mutexToCapture;
|
||||
*_capturedMutex = true;
|
||||
|
@ -35,13 +35,6 @@ 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 std::shared_ptr<bool> captureBan();
|
||||
|
||||
/**
|
||||
* Attempts to capture the mutex. Use the mutexCaptured() method to check success.
|
||||
*/
|
||||
@ -56,6 +49,14 @@ class MutexTracker
|
||||
|
||||
~MutexTracker();
|
||||
|
||||
/*
|
||||
* If captureBan is active, trying to capture a mutex will always fail.
|
||||
* Inactive by default.
|
||||
* captureBan can be managed by MutexTracker like any other mutex.
|
||||
*/
|
||||
static MutexTracker captureBan();
|
||||
static MutexTracker captureBan(const std::function<void()> destructorHook);
|
||||
|
||||
bool mutexCaptured() const;
|
||||
|
||||
/**
|
||||
|
@ -23,6 +23,7 @@
|
||||
*/
|
||||
|
||||
#include "RequestData.h"
|
||||
#include "EspnowMeshBackend.h"
|
||||
|
||||
RequestData::RequestData(EspnowMeshBackend &meshInstance, const uint32_t creationTimeMs) :
|
||||
_timeTracker(creationTimeMs), _meshInstance(meshInstance)
|
||||
|
@ -25,8 +25,8 @@
|
||||
#ifndef __ESPNOWREQUESTDATA_H__
|
||||
#define __ESPNOWREQUESTDATA_H__
|
||||
|
||||
#include <Arduino.h>
|
||||
#include "TimeTracker.h"
|
||||
#include "EspnowMeshBackend.h"
|
||||
|
||||
class EspnowMeshBackend;
|
||||
|
||||
|
@ -25,7 +25,9 @@
|
||||
#include "Serializer.h"
|
||||
#include "JsonTranslator.h"
|
||||
#include "TypeConversionFunctions.h"
|
||||
#include "MeshCryptoInterface.h"
|
||||
#include "MeshCryptoInterface.h"
|
||||
#include "EspnowProtocolInterpreter.h"
|
||||
#include <ESP8266WiFi.h>
|
||||
|
||||
namespace
|
||||
{
|
||||
|
@ -24,6 +24,7 @@
|
||||
#include "TcpIpMeshBackend.h"
|
||||
#include "TypeConversionFunctions.h"
|
||||
#include "MutexTracker.h"
|
||||
#include "ExpiringTimeTracker.h"
|
||||
|
||||
namespace
|
||||
{
|
||||
|
@ -24,6 +24,9 @@
|
||||
*/
|
||||
|
||||
#include "TypeConversionFunctions.h"
|
||||
#include "MeshBackendBase.h"
|
||||
#include "TcpIpMeshBackend.h"
|
||||
#include "EspnowMeshBackend.h"
|
||||
|
||||
namespace
|
||||
{
|
||||
|
@ -28,9 +28,10 @@
|
||||
|
||||
#include <Arduino.h>
|
||||
#include <assert.h>
|
||||
#include "MeshBackendBase.h"
|
||||
#include "TcpIpMeshBackend.h"
|
||||
#include "EspnowMeshBackend.h"
|
||||
|
||||
class MeshBackendBase;
|
||||
class TcpIpMeshBackend;
|
||||
class EspnowMeshBackend;
|
||||
|
||||
namespace MeshTypeConversionFunctions
|
||||
{
|
||||
|
@ -45,4 +45,17 @@ namespace MeshUtilityFunctions
|
||||
{
|
||||
return (((uint64_t)RANDOM_REG32 << 32) | (uint64_t)RANDOM_REG32);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
T *getMapValue(std::map<uint64_t, T> &mapIn, const uint64_t keyIn)
|
||||
{
|
||||
typename std::map<uint64_t, T>::iterator mapIterator = mapIn.find(keyIn);
|
||||
|
||||
if(mapIterator != mapIn.end())
|
||||
{
|
||||
return &mapIterator->second;
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
@ -27,12 +27,16 @@
|
||||
#define __UTILITYFUNCTIONS_H__
|
||||
|
||||
#include <inttypes.h>
|
||||
#include <map>
|
||||
|
||||
namespace MeshUtilityFunctions
|
||||
{
|
||||
bool macEqual(const uint8_t *macOne, const uint8_t *macTwo);
|
||||
|
||||
uint64_t randomUint64();
|
||||
|
||||
template <typename T>
|
||||
T *getMapValue(std::map<uint64_t, T> &mapIn, const uint64_t keyIn);
|
||||
}
|
||||
|
||||
#endif
|
||||
|
Loading…
x
Reference in New Issue
Block a user