1
0
mirror of https://github.com/esp8266/Arduino.git synced 2025-06-16 11:21:18 +03:00
Files
esp8266/libraries/ESP8266WiFiMesh/src/EspnowConnectionManager.cpp
Anders 40e1f02ffb - 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.
2020-05-15 20:33:08 +02:00

586 lines
21 KiB
C++

/*
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++;
}