1
0
mirror of https://github.com/esp8266/Arduino.git synced 2025-04-19 23:22:16 +03:00

- Make connectionQueue(), latestTransmissionOutcomes() and latestTransmissionSuccessful() methods static in order to match the underlying data storage.

- Make it possible to transfer elements directly between connectionQueues.

- Add defaultBSSID value.

- Fix bug where encrypted Espnow-connections expired 1 ms too late.

- Add MutexTracker::captureBan() functionality and use it in the espnowReceiveCallbackWrapper method to ensure a consistent mutex environment there.

- Rename acceptRequest to acceptRequests since several requests can be accepted, not just one.

- Reorganize EspnowMeshBackend.cpp.

- Split sendEspnowResponses() method into sendEspnowResponses() and sendPeerRequestConfirmations().

- Add sendStoredEspnowMessages() method to provide the same functionality as the previous version of sendEspnowResponses().

- Add logic for handling peerRequestConfirmations received at the same time as a peer request is being made, to avoid lockups when there are simultaneous cyclic peer requests.

- Add logic for handling simultaneous reciprocal peer requests.

- Include MAC addresses in HMAC calculations for peer requests and use HMAC for all unencrypted peer request messages, to make sure we receive valid MAC combinations.

- Add asserts to ensure ESP-NOW encryption integrity during code changes.

- Add estimatedMaxDuration argument to performEspnowMaintainance and related methods.

- Add methods to EncryptedConnectionData for setting peer MAC.

- Remove createEncryptionRequestMessage function from JsonTranslator since it is not used, to increase clarity.

- Add encryptedConnectionsSoftLimit() and related functionality.

- Add mutex to protect connectionQueue usage during attemptTransmission.

- Add _ongoingPeerRequestMac variable.

- Add reservedEncryptedConnections() method.

- Add TransmissionOutcomesUpdateHook() callback.

- Add constConnectionQueue() method to allow connectionQueue usage while connectionQueue mutex is active.

- Rearrange attemptAutoEncryptingTransmission argument order to increase efficiency.

- Add functionality for serializing the unencrypted ESP-NOW connection.

- Add some constness.

- Improve comments.

- Improve documentation.

- Update keywords.txt.
This commit is contained in:
Anders 2019-10-31 22:25:12 +01:00
parent b0ef9195b5
commit f8ec4f1c72
23 changed files with 981 additions and 533 deletions

View File

@ -12,9 +12,6 @@ ESP8266WiFiMesh KEYWORD3
# Datatypes (KEYWORD1)
#######################################
ESP8266WiFiMesh KEYWORD1
NetworkInfo KEYWORD1
TransmissionResult KEYWORD1
transmission_status_t KEYWORD1
#######################################
@ -41,7 +38,7 @@ getSSID KEYWORD2
setMessage KEYWORD2
getMessage KEYWORD2
attemptTransmission KEYWORD2
acceptRequest KEYWORD2
acceptRequests KEYWORD2
setStaticIP KEYWORD2
getStaticIP KEYWORD2
disableStaticIP->KEYWORD2

View File

@ -89,6 +89,11 @@ uint8_t *EncryptedConnectionData::getPeerApMac(uint8_t *resultArray) const
return resultArray;
}
void EncryptedConnectionData::setPeerApMac(const uint8_t *peerApMac)
{
std::copy_n(peerApMac, 6, _peerApMac);
}
bool EncryptedConnectionData::connectedTo(const uint8_t *peerMac) const
{
if(macEqual(peerMac, _peerStaMac) || macEqual(peerMac, _peerApMac))
@ -146,10 +151,10 @@ bool EncryptedConnectionData::desync() const { return _desync; }
String EncryptedConnectionData::serialize() const
{
// Returns: {"connectionState":{"duration":"123","password":"abc","ownSessionKey":"1A2","peerSessionKey":"3B4","peerStaMac":"F2","peerApMac":"E3"}}
// Returns: {"connectionState":{"duration":"123","password":"abc","ownSK":"1A2","peerSK":"3B4","peerStaMac":"F2","peerApMac":"E3"}}
return
"{\"connectionState\":{"
JsonTranslator::jsonConnectionState
+ (temporary() ? JsonTranslator::jsonDuration + "\"" + String(temporary()->remainingDuration()) + "\"," : "")
+ JsonTranslator::jsonDesync + "\"" + String(desync()) + "\","
+ JsonTranslator::jsonOwnSessionKey + "\"" + uint64ToString(getOwnSessionKey()) + "\","

View File

@ -55,7 +55,9 @@ public:
// @param resultArray At least size 6.
uint8_t *getPeerStaMac(uint8_t *resultArray) const;
void setPeerStaMac(const uint8_t *peerStaMac) = delete; // A method for setPeerStaMac would sometimes require interacting with the ESP-NOW API to change encrypted connections, so it is not implemented.
uint8_t *getPeerApMac(uint8_t *resultArray) const;
void setPeerApMac(const uint8_t *peerApMac);
bool connectedTo(const uint8_t *peerMac) const;

File diff suppressed because it is too large Load Diff

View File

@ -24,22 +24,48 @@
// but this backend has a much higher data transfer speed than ESP-NOW once connected (100x faster or so).
/**
* This ESP-NOW framework uses a few different message types to enable easier interpretation of transmissions.
* The message type is stored in the first transmission byte, see EspnowProtocolInterpreter.h for more detailed information on the protocol.
* Available message types are 'Q' for question (request), 'A' for answer (response),
* 'B' for broadcast, 'S' for synchronization request, 'P' for peer request and 'C' for peer request confirmation.
*
* 'B', 'Q' and 'A' are the message types that are assigned to data transmitted by the user.
* 'S', 'P' and 'C' are used only for internal framework transmissions.
*
* Messages with type 'B' are only used for broadcasts. They cannot be encrypted.
*
* Messages with type 'Q' are used for requests sent by the user. They can be encrypted.
*
* Messages with type 'A' are used for responses given by the user when 'B' or 'Q' messages have been received. They can be encrypted.
*
* Messages with type 'P' and 'C' are used exclusively for automatically pairing two ESP-NOW nodes to each other.
* This enables flexible easy-to-use encrypted ESP-NOW communication. 'P' and 'C' messages can be encrypted.
* The encryption pairing process works as follows (from top to bottom):
*
* Encryption pairing process, schematic overview:
*
* Connection | Peer sends: | Peer requester sends: | Connection
* encrypted: | | | encrypted:
* | | Peer request + Nonce |
* | StaMac + Nonce + HMAC | |
* | | Ack |
* X | SessionKeys + Nonce + Password | | X
* X | | Ack | X
* X | | SessionKey | X
* X | Ack | | X
* | | |
* Connection | Peer sends ('C'): | Peer requester sends ('P'): | Connection
* encrypted: | | | encrypted:
* | | Peer request + Nonce + HMAC |
* | StaMac + Nonce + HMAC | |
* | | Ack |
* X | SessionKeys + Nonce + Password | | X
* X | | Ack | X
* X | | SessionKey | X
* X | Ack | | X
* | | |
*
*
* The ESP-NOW CCMP encryption should have replay attack protection built in,
* but since there is no official documentation from Espressif about this a 128 bit random nonce is included in encrypted connection requests.
*
* Messages with type 'S' are used exclusively when we try to send an encrypted 'R' or 'P' transmission and the last such transmission we tried failed to receive an ack.
* Since we then do not know if the receiving node has incremented its corresponding session key or not, we first send an 'S' request to make sure the key is incremented.
* Once we get an ack for our 'S' request we send the new encrypted 'R' or 'P' transmission. 'S' messages are always encrypted.
*
* Messages of type 'A' and 'C' are response types, and thus use the same session key as the corresponding 'R' and 'P' message they are responding to.
* This means they can never cause a desynchronization to occur, and therefore they do not trigger 'S' messages.
*
*/
#ifndef __ESPNOWMESHBACKEND_H__
@ -64,13 +90,15 @@ typedef enum
ECT_PERMANENT_CONNECTION = 2
} espnow_connection_type_t;
// A value greater than 0 means that an encrypted connection has been established.
typedef enum
{
ECS_MAX_CONNECTIONS_REACHED_SELF = -3,
ECS_REQUEST_TRANSMISSION_FAILED = -2,
ECS_MAX_CONNECTIONS_REACHED_PEER = -1,
ECS_API_CALL_FAILED = 0,
ECS_CONNECTION_ESTABLISHED = 1
ECS_CONNECTION_ESTABLISHED = 1,
ECS_SOFT_LIMIT_CONNECTION_ESTABLISHED = 2 // Only used if _encryptedConnectionsSoftLimit is less than 6.
} encrypted_connection_status_t;
typedef enum
@ -87,7 +115,7 @@ typedef enum
* Note that if there is a lot of ESP-NOW transmission activity to the node during the espnowDelay, the desired duration may be overshot by several ms.
* Thus, if precise timing is required, use standard delay() instead.
*
* Should not be used inside responseHandler, requestHandler or networkFilter callbacks since performEspnowMaintainance() can alter the ESP-NOW state.
* Should not be used inside responseHandler, requestHandler, networkFilter or broadcastFilter callbacks since performEspnowMaintainance() can alter the ESP-NOW state.
*
* @param durationMs The shortest allowed delay duration, in milliseconds.
*/
@ -133,12 +161,19 @@ public:
/**
* Returns a vector that contains the NetworkInfo for each WiFi network to connect to.
* This vector is unique for each mesh backend.
* This vector is unique for each mesh backend, but NetworkInfo elements can be directly transferred between the vectors as long as both SSID and BSSID are present.
* The connectionQueue vector is cleared before each new scan and filled via the networkFilter callback function once the scan completes.
* WiFi connections will start with connectionQueue[0] and then incrementally proceed to higher vector positions.
* Note that old network indicies often are invalidated whenever a new WiFi network scan occurs.
*
* Since the connectionQueue() is iterated over during transmissions, always use constConnectionQueue() from callbacks other than NetworkFilter.
*/
std::vector<EspnowNetworkInfo> & connectionQueue();
static std::vector<EspnowNetworkInfo> & connectionQueue();
/**
* Same as connectionQueue(), but can be called from all callbacks since the returned reference is const.
*/
static const std::vector<EspnowNetworkInfo> & constConnectionQueue();
/**
* Returns a vector with the TransmissionOutcome for each AP to which a transmission was attempted during the latest attemptTransmission call.
@ -147,7 +182,13 @@ public:
* Connection attempts are indexed in the same order they were attempted.
* Note that old network indicies often are invalidated whenever a new WiFi network scan occurs.
*/
std::vector<TransmissionOutcome> & latestTransmissionOutcomes() override;
static std::vector<TransmissionOutcome> & latestTransmissionOutcomes();
/**
* @return True if latest transmission was successful (i.e. latestTransmissionOutcomes is not empty and all entries have transmissionStatus TS_TRANSMISSION_COMPLETE). False otherwise.
* The result is unique for each mesh backend.
*/
static bool latestTransmissionSuccessful();
/**
* Initialises the node.
@ -161,9 +202,13 @@ public:
* Note that depending on the amount of responses to send and their length, this method can take tens or even hundreds of milliseconds to complete.
* More intense transmission activity and less frequent calls to performEspnowMaintainance will likely cause the method to take longer to complete, so plan accordingly.
*
* Should not be used inside responseHandler, requestHandler or networkFilter callbacks since performEspnowMaintainance() can alter the ESP-NOW state.
* Should not be used inside responseHandler, requestHandler, networkFilter or broadcastFilter callbacks since performEspnowMaintainance() can alter the ESP-NOW state.
*
* @param estimatedMaxDuration The desired max duration for the method. If set to 0 there is no duration limit.
* Note that setting the estimatedMaxDuration too low may result in missed ESP-NOW transmissions because of too little time for maintainance.
* Also note that although the method will try to respect the max duration limit, there is no guarantee. Overshoots by tens of milliseconds are possible.
*/
static void performEspnowMaintainance();
static void performEspnowMaintainance(uint32_t estimatedMaxDuration = 0);
/**
* At critical heap level no more incoming requests are accepted.
@ -200,6 +245,8 @@ public:
/**
* Transmit message to a single recipient without changing the local transmission state.
* Will not change connectionQueue, latestTransmissionOutcomes or stored message.
*
* @param recipientInfo The recipient information.
*/
transmission_status_t attemptTransmission(const String &message, const EspnowNetworkInfo &recipientInfo);
@ -208,29 +255,32 @@ public:
* establishing a temporary encrypted connection with duration getAutoEncryptionDuration() first if neccessary.
* If an encrypted connection cannot be established to a target node, no message will be sent to that node.
* Note that if an encrypted connection to a target node is not present before this method is called, the response from said node will likely not be received
* since it will be encrypted and the auto encrypted connection to the node is immediately removed after transmission (unless the createPermanentConnections argument is set to true).
* since it will be encrypted and the auto encrypted connection to the node is immediately removed after transmission (unless the requestPermanentConnections argument is set to true).
* Also note that if a temporary encrypted connection already exists to a target node, this method will slightly extend the connection duration
* depending on the time it takes to verify the connection to the node. This can substantially increase the connection duration if many auto encrypting
* transmissions occurs.
*
* @param message The message to send to other nodes. It will be stored in the class instance until replaced via attemptTransmission or setMessage.
* @param requestPermanentConnections If true, the method will request that encrypted connections used for this transmission become permanent so they are not removed once the transmission is complete.
* This means that encrypted responses to the transmission are received, as long as the encrypted connection is not removed by other means.
* The receiving node has no obligation to obey the request, although it normally will.
* If encryptedConnectionsSoftLimit() is set to less than 6 for the transmission receiver,
* it is possible that a short lived autoEncryptionConnection is created instead of a permanent encrypted connection.
* Note that a maximum of 6 encrypted ESP-NOW connections can be maintained at the same time by the node.
* Defaults to false.
* @param scan Scan for new networks and call the networkFilter function with the scan results. When set to false, only the data already in connectionQueue will be used for the transmission.
* @param scanAllWiFiChannels Scan all WiFi channels during a WiFi scan, instead of just the channel the MeshBackendBase instance is using.
* Scanning all WiFi channels takes about 2100 ms, compared to just 60 ms if only channel 1 (standard) is scanned.
* Note that if the ESP8266 has an active AP, that AP will switch WiFi channel to match that of any other AP the ESP8266 connects to.
* This can make it impossible for other nodes to detect the AP if they are scanning the wrong WiFi channel.
* @param createPermanentConnections Ensures encrypted connections used for this transmission are permanent and not removed once the transmission is complete.
* This guarantees that encrypted responses to the transmission is received, as long as the encrypted connection is not removed by other means.
* Note that a maximum of 6 encrypted ESP-NOW connections can be maintained at the same time by the node.
* Defaults to false.
*/
void attemptAutoEncryptingTransmission(const String &message, bool scan = true, bool scanAllWiFiChannels = false, bool createPermanentConnections = false);
void attemptAutoEncryptingTransmission(const String &message, bool requestPermanentConnections = false, bool scan = true, bool scanAllWiFiChannels = false);
/**
* Transmit message to a single recipient without changing the local transmission state (apart from encrypted connections).
* Will not change connectionQueue, latestTransmissionOutcomes or stored message.
*/
transmission_status_t attemptAutoEncryptingTransmission(const String &message, const EspnowNetworkInfo &recipientInfo, bool createPermanentConnection = false);
transmission_status_t attemptAutoEncryptingTransmission(const String &message, const EspnowNetworkInfo &recipientInfo, bool requestPermanentConnection = false);
/**
* Send a message simultaneously to all nearby nodes which have ESP-NOW activated.
@ -475,6 +525,17 @@ public:
*/
bool receivedEncryptedMessage();
/**
* Should be used together with serializeUnencryptedConnection() if the node sends unencrypted transmissions
* and will go to sleep for less than logEntryLifetimeMs() while other nodes stay awake.
* Otherwise the message ID will be reset after sleep, which means that the nodes that stayed awake may ignore new unencrypted transmissions until logEntryLifetimeMs() ms has passed.
*
* @param serializedConnectionState A serialized state of an unencrypted ESP-NOW connection.
*
* @return True if connection was added. False otherwise (e.g. if there is faulty input).
*/
static bool addUnencryptedConnection(const String &serializedConnectionState);
// Updates connection with current stored encryption key.
// At least one of the leftmost 32 bits in each of the session keys should be 1, since the key otherwise indicates the connection is unencrypted.
encrypted_connection_status_t addEncryptedConnection(uint8_t *peerStaMac, uint8_t *peerApMac, uint64_t peerSessionKey, uint64_t ownSessionKey);
@ -516,16 +577,42 @@ public:
bool acceptsUnencryptedRequests();
/**
* @ returns The current number of encrypted ESP-NOW connections.
* Set a soft upper limit on the number of encrypted connections this node can have when receiving encrypted connection requests.
* The soft limit can be used to ensure there is normally a pool of free encrypted connection slots that can be used if required.
* Each EspnowMeshBackend instance can have a separate value. The value used is that of the current EspnowRequestManager.
* The hard upper limit is 6 encrypted connections, mandated by the ESP-NOW API.
*
* When a request for encrypted connection is received from a node to which there is no existing permanent encrypted connection,
* and the number of encrypted connections exceeds the soft limit,
* this request will automatically be converted to an autoEncryptionRequest.
* This means it will be a temporary connection with very short duration (with default framework settings).
*
* @param softLimit The new soft limit. Valid values are 0 to 6. Default is 6.
*/
void setEncryptedConnectionsSoftLimit(uint8_t softLimit);
uint8_t encryptedConnectionsSoftLimit();
/**
* @return The current number of encrypted ESP-NOW connections.
*/
static uint8_t numberOfEncryptedConnections();
// @return resultArray filled with the MAC to the encrypted interface of the node, if an encrypted connection exists. nulltpr otherwise.
static uint8_t *getEncryptedMac(const uint8_t *peerMac, uint8_t *resultArray);
/**
* Should be used together with addUnencryptedConnection if the node sends unencrypted transmissions
* and will go to sleep for less than logEntryLifetimeMs() while other nodes stay awake.
* Otherwise the message ID will be reset after sleep, which means that the nodes that stayed awake may ignore new unencrypted transmissions until logEntryLifetimeMs() ms has passed.
*
* @return The serialized state of the unencrypted ESP-NOW connection.
*/
static String serializeUnencryptedConnection();
// Create a string containing the current state of the encrypted connection for this node. The result can be used as input to addEncryptedConnection.
// Note that transferring the serialized state over an unencrypted connection will compromise the security of the stored connection.
// @ returns A String containing the serialized encrypted connection, or an empty String if there is no matching encrypted connection.
// Also note that this saves the current state only, so if encrypted communication between the nodes happen after this, the stored state is invalid.
// @return A String containing the serialized encrypted connection, or an empty String if there is no matching encrypted connection.
static String serializeEncryptedConnection(const uint8_t *peerMac);
static String serializeEncryptedConnection(uint32_t connectionIndex);
@ -537,7 +624,7 @@ public:
* @param remainingDuration An optional pointer to a uint32_t variable.
* If supplied and the connection type is ECT_TEMPORARY_CONNECTION the variable will be set to the remaining duration of the connection.
* Otherwise the variable value is not modified.
* @ returns The espnow_connection_type_t of the connection with peerMac.
* @return The espnow_connection_type_t of the connection with peerMac.
*/
static espnow_connection_type_t getConnectionInfo(uint8_t *peerMac, uint32_t *remainingDuration = nullptr);
@ -550,7 +637,7 @@ public:
* Otherwise the variable value is not modified.
* @param peerMac An optional pointer to an uint8_t array with at least size 6. It will be filled with the MAC of the encrypted peer interface if an encrypted connection exists.
* Otherwise the array is not modified.
* @ returns The espnow_connection_type_t of the connection given by connectionIndex.
* @return The espnow_connection_type_t of the connection given by connectionIndex.
*/
static espnow_connection_type_t getConnectionInfo(uint32_t connectionIndex, uint32_t *remainingDuration = nullptr, uint8_t *peerMac = nullptr);
@ -577,6 +664,8 @@ protected:
bool activateEspnow();
static bool encryptedConnectionEstablished(encrypted_connection_status_t connectionStatus);
/*
* Note that ESP-NOW is not perfect and in rare cases messages may be dropped.
* This needs to be compensated for in the application via extra verification
@ -584,8 +673,25 @@ protected:
*
* Note that although responses will generally be sent in the order they were created, this is not guaranteed to be the case.
* For example, response order will be mixed up if some responses fail to transmit while others transmit successfully.
*
* @param estimatedMaxDurationTracker A pointer to an ExpiringTimeTracker initialized with the desired max duration for the method. If set to nullptr there is no duration limit.
* Note that setting the estimatedMaxDuration too low may result in missed ESP-NOW transmissions because of too little time for maintainance.
* Also note that although the method will try to respect the max duration limit, there is no guarantee. Overshoots by tens of milliseconds are possible.
*/
static void sendEspnowResponses();
static void sendStoredEspnowMessages(const ExpiringTimeTracker *estimatedMaxDurationTracker = nullptr);
/*
* @param estimatedMaxDurationTracker A pointer to an ExpiringTimeTracker initialized with the desired max duration for the method. If set to nullptr there is no duration limit.
* Note that setting the estimatedMaxDuration too low may result in missed ESP-NOW transmissions because of too little time for maintainance.
* Also note that although the method will try to respect the max duration limit, there is no guarantee. Overshoots by tens of milliseconds are possible.
*/
static void sendPeerRequestConfirmations(const ExpiringTimeTracker *estimatedMaxDurationTracker = nullptr);
/*
* @param estimatedMaxDurationTracker A pointer to an ExpiringTimeTracker initialized with the desired max duration for the method. If set to nullptr there is no duration limit.
* Note that setting the estimatedMaxDuration too low may result in missed ESP-NOW transmissions because of too little time for maintainance.
* Also note that although the method will try to respect the max duration limit, there is no guarantee. Overshoots by tens of milliseconds are possible.
*/
static void sendEspnowResponses(const ExpiringTimeTracker *estimatedMaxDurationTracker = nullptr);
static void clearOldLogEntries();
static uint32_t getMaxBytesPerTransmission();
@ -622,6 +728,11 @@ protected:
*/
static bool _espnowTransmissionMutex;
/**
* Will be true when the connectionQueue should not be modified.
*/
static bool _espnowConnectionQueueMutex;
/**
* Check if there is an ongoing ESP-NOW transmission in the library. Used to avoid interrupting transmissions.
*
@ -670,8 +781,8 @@ protected:
private:
typedef std::function<String(const String &, const ExpiringTimeTracker &)> encryptionRequestBuilderType;
static String defaultEncryptionRequestBuilder(const String &requestHeader, const uint32_t durationMs, const String &requestNonce, const ExpiringTimeTracker &existingTimeTracker);
static String flexibleEncryptionRequestBuilder(const uint32_t minDurationMs, const String &requestNonce, const ExpiringTimeTracker &existingTimeTracker);
static String defaultEncryptionRequestBuilder(const String &requestHeader, const uint32_t durationMs, const uint8_t *hashKey, const String &requestNonce, const ExpiringTimeTracker &existingTimeTracker);
static String flexibleEncryptionRequestBuilder(const uint32_t minDurationMs, const uint8_t *hashKey, const String &requestNonce, const ExpiringTimeTracker &existingTimeTracker);
/**
* We can't feed esp_now_register_recv_cb our EspnowMeshBackend instance's espnowReceiveCallback method directly, so this callback wrapper is a workaround.
@ -701,6 +812,8 @@ private:
uint32_t _autoEncryptionDuration = 50;
uint8_t _encryptedConnectionsSoftLimit = 6;
static bool _staticVerboseMode;
static EspnowMeshBackend *_espnowRequestManager;
@ -709,6 +822,14 @@ private:
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 std::list<ResponseData> responsesToSend;
static std::list<PeerRequestLog> peerRequestConfirmationsToSend;
@ -755,9 +876,11 @@ private:
broadcastFilterType _broadcastFilter;
static String _ongoingPeerRequestNonce;
static uint8_t _ongoingPeerRequestMac[6];
static EspnowMeshBackend *_ongoingPeerRequester;
static encrypted_connection_status_t _ongoingPeerRequestResult;
static uint32_t _ongoingPeerRequestEncryptionStart;
static bool _reciprocalPeerRequestConfirmation;
template <typename T>
static T *getMapValue(std::map<uint64_t, T> &mapIn, uint64_t keyIn);
@ -830,9 +953,9 @@ private:
transmission_status_t initiateTransmissionKernel(const String &message, const uint8_t *targetBSSID);
void printTransmissionStatistics();
encrypted_connection_status_t initiateAutoEncryptingConnection(const EspnowNetworkInfo &recipientInfo, bool createPermanentConnection, uint8_t *targetBSSID, EncryptedConnectionLog **encryptedConnection);
encrypted_connection_status_t initiateAutoEncryptingConnection(const EspnowNetworkInfo &recipientInfo, bool requestPermanentConnection, uint8_t *targetBSSID, EncryptedConnectionLog **existingEncryptedConnection);
transmission_status_t initiateAutoEncryptingTransmission(const String &message, const uint8_t *targetBSSID, encrypted_connection_status_t connectionStatus);
void finalizeAutoEncryptingConnection(const uint8_t *targetBSSID, const EncryptedConnectionLog *encryptedConnection, bool createPermanentConnection);
void finalizeAutoEncryptingConnection(const uint8_t *targetBSSID, const EncryptedConnectionLog *existingEncryptedConnection, bool requestPermanentConnection);
// Used for verboseMode printing in attemptTransmission, _AT suffix used to reduce namespace clutter
uint32_t totalDurationWhenSuccessful_AT = 0;

View File

@ -23,9 +23,15 @@
*/
#include "EspnowNetworkInfo.h"
#include <assert.h>
EspnowNetworkInfo::EspnowNetworkInfo(int networkIndex) : NetworkInfoBase(networkIndex) { };
EspnowNetworkInfo::EspnowNetworkInfo(const NetworkInfoBase &originalNetworkInfo) : NetworkInfoBase(originalNetworkInfo)
{
assert(BSSID() != defaultBSSID); // We need at least BSSID to be able to connect.
};
EspnowNetworkInfo::EspnowNetworkInfo(const uint8_t BSSID[6], const String &SSID, int32_t wifiChannel, uint8_t encryptionType, int32_t RSSI , bool isHidden)
: NetworkInfoBase(SSID, wifiChannel, BSSID, encryptionType, RSSI, isHidden)
{ }

View File

@ -36,6 +36,8 @@ public:
*/
EspnowNetworkInfo(int networkIndex);
EspnowNetworkInfo(const NetworkInfoBase &originalNetworkInfo);
EspnowNetworkInfo(const uint8_t BSSID[6], const String &SSID = defaultSSID, int32_t wifiChannel = defaultWifiChannel, uint8_t encryptionType = defaultEncryptionType,
int32_t RSSI = defaultRSSI, bool isHidden = defaultIsHidden);
};

View File

@ -44,6 +44,7 @@ namespace EspnowProtocolInterpreter
const String temporaryEncryptionRequestHeader = "AddTEC:"; // Add temporary encrypted connection
const String basicConnectionInfoHeader = "BasicCI:"; // Basic connection info
const String encryptedConnectionInfoHeader = "EncryptedCI:"; // Encrypted connection info
const String softLimitEncryptedConnectionInfoHeader = "SLEncryptedCI:"; // Soft limit encrypted connection info
const String maxConnectionsReachedHeader = "ECS_MAX_CONNECTIONS_REACHED_PEER:";
const String encryptedConnectionVerificationHeader = "ECVerified:"; // Encrypted connection verified
const String encryptedConnectionRemovalRequestHeader = "RemoveEC:"; // Remove encrypted connection

View File

@ -55,5 +55,5 @@ uint32_t ExpiringTimeTracker::remainingDuration() const
bool ExpiringTimeTracker::expired() const
{
return timeSinceCreation() > duration();
return timeSinceCreation() >= duration();
}

View File

@ -73,7 +73,8 @@ namespace JsonTranslator
return false;
}
bool verifyHmac(const String &encryptionRequestHmacMessage, const uint8_t *hashKey, uint8_t hashKeyLength)
bool verifyEncryptionRequestHmac(const String &encryptionRequestHmacMessage, const uint8_t *requesterStaMac, const uint8_t *requesterApMac,
const uint8_t *hashKey, uint8_t hashKeyLength)
{
String hmac = "";
if(getHmac(encryptionRequestHmacMessage, hmac))
@ -82,7 +83,7 @@ namespace JsonTranslator
if(hmacStartIndex < 0)
return false;
if(verifyHmac(encryptionRequestHmacMessage.substring(0, hmacStartIndex), hmac, hashKey, hashKeyLength))
if(verifyHmac(macToString(requesterStaMac) + macToString(requesterApMac) + encryptionRequestHmacMessage.substring(0, hmacStartIndex), hmac, hashKey, hashKeyLength))
{
return true;
}
@ -91,13 +92,12 @@ namespace JsonTranslator
return false;
}
String createEncryptedConnectionInfo(const String &requestNonce, const String &authenticationPassword, uint64_t ownSessionKey, uint64_t peerSessionKey)
String createEncryptedConnectionInfo(const String &infoHeader, const String &requestNonce, const String &authenticationPassword, uint64_t ownSessionKey, uint64_t peerSessionKey)
{
// Returns: Encrypted connection info:{"arguments":{"nonce":"1F2","password":"abc","ownSessionKey":"3B4","peerSessionKey":"1A2"}}
// Returns: Encrypted connection info:{"arguments":{"nonce":"1F2","password":"abc","ownSK":"3B4","peerSK":"1A2"}}
return
EspnowProtocolInterpreter::encryptedConnectionInfoHeader + "{\"arguments\":{"
infoHeader + "{\"arguments\":{"
+ createJsonPair(jsonNonce, requestNonce)
+ createJsonPair(jsonPassword, authenticationPassword)
+ createJsonPair(jsonOwnSessionKey, uint64ToString(peerSessionKey)) // Exchanges session keys since it should be valid for the receiver.
@ -116,15 +116,13 @@ namespace JsonTranslator
return createJsonEndPair(jsonNonce, requestNonce);
}
String createEncryptionRequestMessage(const String &requestHeader, const String &requestNonce, uint32_t duration)
{
return createEncryptionRequestIntro(requestHeader, duration) + createEncryptionRequestEnding(requestNonce);
}
String createEncryptionRequestHmacMessage(const String &requestHeader, const String &requestNonce, const uint8_t *hashKey, uint8_t hashKeyLength, uint32_t duration)
{
String mainMessage = createEncryptionRequestIntro(requestHeader, duration) + createJsonPair(jsonNonce, requestNonce);
String hmac = createHmac(mainMessage, hashKey, hashKeyLength);
uint8_t staMac[6] {0};
uint8_t apMac[6] {0};
String requesterStaApMac = macToString(WiFi.macAddress(staMac)) + macToString(WiFi.softAPmacAddress(apMac));
String hmac = createHmac(requesterStaApMac + mainMessage, hashKey, hashKeyLength);
return mainMessage + createJsonEndPair(jsonHmac, hmac);
}
@ -149,6 +147,20 @@ namespace JsonTranslator
return endIndex;
}
bool getConnectionState(const String &jsonString, String &result)
{
int32_t startIndex = jsonString.indexOf(jsonConnectionState);
if(startIndex < 0)
return false;
int32_t endIndex = jsonString.indexOf("}");
if(endIndex < 0)
return false;
result = jsonString.substring(startIndex, endIndex + 1);
return true;
}
bool getPassword(const String &jsonString, String &result)
{
int32_t startIndex = getStartIndex(jsonString, jsonPassword);
@ -266,4 +278,27 @@ namespace JsonTranslator
result = bool(strtoul(jsonString.substring(startIndex).c_str(), nullptr, 0)); // strtoul stops reading input when an invalid character is discovered.
return true;
}
bool getUnencryptedMessageID(const String &jsonString, uint32_t &result)
{
int32_t startIndex = getStartIndex(jsonString, jsonUnencryptedMessageID);
if(startIndex < 0)
return false;
result = strtoul(jsonString.substring(startIndex).c_str(), nullptr, 0); // strtoul stops reading input when an invalid character is discovered.
return true;
}
bool getMeshMessageCount(const String &jsonString, uint16_t &result)
{
int32_t startIndex = getStartIndex(jsonString, jsonMeshMessageCount);
if(startIndex < 0)
return false;
uint32_t longResult = strtoul(jsonString.substring(startIndex).c_str(), nullptr, 0); // strtoul stops reading input when an invalid character is discovered.
assert(longResult <= 65535); // Must fit within uint16_t
result = longResult;
return true;
}
}

View File

@ -30,6 +30,7 @@
namespace JsonTranslator
{
const String jsonConnectionState = "{\"connectionState\":{";
const String jsonPassword = "\"password\":";
const String jsonOwnSessionKey = "\"ownSK\":";
const String jsonPeerSessionKey = "\"peerSK\":";
@ -39,6 +40,8 @@ namespace JsonTranslator
const String jsonNonce = "\"nonce\":";
const String jsonHmac = "\"hmac\":";
const String jsonDesync = "\"desync\":";
const String jsonUnencryptedMessageID = "\"uMessageID\":";
const String jsonMeshMessageCount = "\"mMessageCount\":";
String createJsonPair(const String &valueIdentifier, const String &value);
String createJsonEndPair(const String &valueIdentifier, const String &value);
@ -47,12 +50,11 @@ namespace JsonTranslator
String createHmac(const String &message, const uint8_t *hashKey, uint8_t hashKeyLength);
bool verifyHmac(const String &message, const String &messageHmac, const uint8_t *hashKey, uint8_t hashKeyLength);
bool verifyHmac(const String &encryptionRequestHmacMessage, const uint8_t *hashKey, uint8_t hashKeyLength);
bool verifyEncryptionRequestHmac(const String &encryptionRequestHmacMessage, const uint8_t *requesterStaMac, const uint8_t *requesterApMac, const uint8_t *hashKey, uint8_t hashKeyLength);
String createEncryptedConnectionInfo(const String &requestNonce, const String &authenticationPassword, uint64_t ownSessionKey, uint64_t peerSessionKey);
String createEncryptedConnectionInfo(const String &infoHeader, const String &requestNonce, const String &authenticationPassword, uint64_t ownSessionKey, uint64_t peerSessionKey);
String createEncryptionRequestIntro(const String &requestHeader, uint32_t duration = 0);
String createEncryptionRequestEnding(const String &requestNonce);
String createEncryptionRequestMessage(const String &requestHeader, const String &requestNonce, uint32_t duration = 0);
String createEncryptionRequestHmacMessage(const String &requestHeader, const String &requestNonce, const uint8_t *hashKey, uint8_t hashKeyLength, uint32_t duration = 0);
/**
@ -76,6 +78,7 @@ namespace JsonTranslator
*/
int32_t getEndIndex(const String &jsonString, int32_t searchStartIndex);
bool getConnectionState(const String &jsonString, String &result);
/**
* Stores the value of the password field within jsonString into the result variable.
* No changes to the result variable are made if jsonString does not contain a password.
@ -104,6 +107,8 @@ namespace JsonTranslator
bool getNonce(const String &jsonString, String &result);
bool getHmac(const String &jsonString, String &result);
bool getDesync(const String &jsonString, bool &result);
bool getUnencryptedMessageID(const String &jsonString, uint32_t &result);
bool getMeshMessageCount(const String &jsonString, uint16_t &result);
}
#endif

View File

@ -119,7 +119,7 @@ void MeshBackendBase::setWiFiChannel(uint8 newWiFiChannel)
}
}
uint8 MeshBackendBase::getWiFiChannel()
uint8 MeshBackendBase::getWiFiChannel() const
{
return _meshWiFiChannel;
}
@ -147,28 +147,28 @@ void MeshBackendBase::setSSID(const String &newSSIDPrefix, const String &newSSID
}
}
String MeshBackendBase::getSSID() {return _SSID;}
String MeshBackendBase::getSSID() const {return _SSID;}
void MeshBackendBase::setSSIDPrefix(const String &newSSIDPrefix)
{
setSSID(newSSIDPrefix);
}
String MeshBackendBase::getSSIDPrefix() {return _SSIDPrefix;}
String MeshBackendBase::getSSIDPrefix() const {return _SSIDPrefix;}
void MeshBackendBase::setSSIDRoot(const String &newSSIDRoot)
{
setSSID("", newSSIDRoot);
}
String MeshBackendBase::getSSIDRoot() {return _SSIDRoot;}
String MeshBackendBase::getSSIDRoot() const {return _SSIDRoot;}
void MeshBackendBase::setSSIDSuffix(const String &newSSIDSuffix)
{
setSSID("", "", newSSIDSuffix);
}
String MeshBackendBase::getSSIDSuffix() {return _SSIDSuffix;}
String MeshBackendBase::getSSIDSuffix() const {return _SSIDSuffix;}
void MeshBackendBase::setMeshName(const String &newMeshName)
{
@ -195,10 +195,10 @@ void MeshBackendBase::setMeshPassword(const String &newMeshPassword)
restartAP();
}
String MeshBackendBase::getMeshPassword() {return _meshPassword;}
String MeshBackendBase::getMeshPassword() const {return _meshPassword;}
void MeshBackendBase::setMessage(const String &newMessage) {_message = newMessage;}
String MeshBackendBase::getMessage() {return _message;}
String MeshBackendBase::getMessage() const {return _message;}
void MeshBackendBase::setRequestHandler(MeshBackendBase::requestHandlerType requestHandler) {_requestHandler = requestHandler;}
MeshBackendBase::requestHandlerType MeshBackendBase::getRequestHandler() {return _requestHandler;}
@ -209,12 +209,15 @@ MeshBackendBase::responseHandlerType MeshBackendBase::getResponseHandler() {retu
void MeshBackendBase::setNetworkFilter(MeshBackendBase::networkFilterType networkFilter) {_networkFilter = networkFilter;}
MeshBackendBase::networkFilterType MeshBackendBase::getNetworkFilter() {return _networkFilter;}
void MeshBackendBase::setTransmissionOutcomesUpdateHook(MeshBackendBase::transmissionOutcomesUpdateHookType transmissionOutcomesUpdateHook) {_transmissionOutcomesUpdateHook = transmissionOutcomesUpdateHook;}
MeshBackendBase::transmissionOutcomesUpdateHookType MeshBackendBase::getTransmissionOutcomesUpdateHook() {return _transmissionOutcomesUpdateHook;}
void MeshBackendBase::setScanHidden(bool scanHidden)
{
_scanHidden = scanHidden;
}
bool MeshBackendBase::getScanHidden() {return _scanHidden;}
bool MeshBackendBase::getScanHidden() const {return _scanHidden;}
void MeshBackendBase::setAPHidden(bool apHidden)
{
@ -228,14 +231,14 @@ void MeshBackendBase::setAPHidden(bool apHidden)
}
}
bool MeshBackendBase::getAPHidden() {return _apHidden;}
bool MeshBackendBase::getAPHidden() const {return _apHidden;}
bool MeshBackendBase::latestTransmissionSuccessful()
bool MeshBackendBase::latestTransmissionSuccessfulBase(const std::vector<TransmissionOutcome> &latestTransmissionOutcomes)
{
if(latestTransmissionOutcomes().empty())
if(latestTransmissionOutcomes.empty())
return false;
else
for(TransmissionOutcome &transmissionOutcome : latestTransmissionOutcomes())
for(const TransmissionOutcome &transmissionOutcome : latestTransmissionOutcomes)
if(transmissionOutcome.transmissionStatus() != TS_TRANSMISSION_COMPLETE)
return false;

View File

@ -38,6 +38,7 @@ protected:
typedef std::function<String(const String &, MeshBackendBase &)> requestHandlerType;
typedef std::function<transmission_status_t(const String &, MeshBackendBase &)> responseHandlerType;
typedef std::function<void(int, MeshBackendBase &)> networkFilterType;
typedef std::function<bool(MeshBackendBase &)> transmissionOutcomesUpdateHookType;
public:
@ -45,20 +46,6 @@ public:
virtual ~MeshBackendBase();
/**
* Returns a vector with the TransmissionOutcome for each AP to which a transmission was attempted during the latest attemptTransmission call.
* This vector is unique for each mesh backend.
* The latestTransmissionOutcomes vector is cleared before each new transmission attempt.
* Connection attempts are indexed in the same order they were attempted.
* Note that old network indicies often are invalidated whenever a new WiFi network scan occurs.
*/
virtual std::vector<TransmissionOutcome> & latestTransmissionOutcomes() = 0;
/**
* @return True if latest transmission was successful (i.e. latestTransmissionOutcomes is not empty and all entries have transmissionStatus TS_TRANSMISSION_COMPLETE). False otherwise.
*/
bool latestTransmissionSuccessful();
/**
* Initialises the node.
*/
@ -105,7 +92,7 @@ public:
*
*/
void setWiFiChannel(uint8 newWiFiChannel);
uint8 getWiFiChannel();
uint8 getWiFiChannel() const;
/**
* Change the SSID used by this MeshBackendBase instance.
@ -119,7 +106,7 @@ public:
*/
void setSSID(const String &newSSIDPrefix = ESP8266_MESH_EMPTY_STRING, const String &newSSIDRoot = ESP8266_MESH_EMPTY_STRING,
const String &newSSIDSuffix = ESP8266_MESH_EMPTY_STRING);
String getSSID();
String getSSID() const;
/**
* Change the first part of the SSID used by this MeshBackendBase instance.
@ -129,7 +116,7 @@ public:
* @param newSSIDPrefix The new first part of the SSID.
*/
void setSSIDPrefix(const String &newSSIDPrefix);
String getSSIDPrefix();
String getSSIDPrefix() const;
/**
* Change the middle part of the SSID used by this MeshBackendBase instance.
@ -139,7 +126,7 @@ public:
* @param newSSIDPrefix The new middle part of the SSID.
*/
void setSSIDRoot(const String &newSSIDRoot);
String getSSIDRoot();
String getSSIDRoot() const;
/**
* Change the last part of the SSID used by this MeshBackendBase instance.
@ -149,7 +136,7 @@ public:
* @param newSSIDSuffix The new last part of the SSID.
*/
void setSSIDSuffix(const String &newSSIDSuffix);
String getSSIDSuffix();
String getSSIDSuffix() const;
/**
* Change the mesh name used by this MeshBackendBase instance.
@ -183,7 +170,7 @@ public:
* @param newMeshPassword The password to use.
*/
void setMeshPassword(const String &newMeshPassword);
String getMeshPassword();
String getMeshPassword() const;
/**
* Set the message that will be sent to other nodes when calling attemptTransmission.
@ -191,7 +178,7 @@ public:
* @param newMessage The message to send.
*/
void setMessage(const String &newMessage);
String getMessage();
String getMessage() const;
virtual void attemptTransmission(const String &message, bool scan = true, bool scanAllWiFiChannels = false) = 0;
@ -204,6 +191,16 @@ public:
void setNetworkFilter(networkFilterType networkFilter);
networkFilterType getNetworkFilter();
/**
* Set a function that should be called after each update of the latestTransmissionOutcomes vector during attemptTransmission. (which happens after each individual transmission has finished)
* The function should return a bool. If this return value is true, attemptTransmission will continue with the next entry in the connectionQueue. If it is false, attemptTransmission will stop.
* The default transmissionOutcomesUpdateHook always returns true.
*
* Example use cases is modifying getMessage() between transmissions, or aborting attemptTransmission before all nodes in the connectionQueue have been contacted.
*/
void setTransmissionOutcomesUpdateHook(transmissionOutcomesUpdateHookType transmissionOutcomesUpdateHook);
transmissionOutcomesUpdateHookType getTransmissionOutcomesUpdateHook();
/**
* Set whether scan results from this MeshBackendBase instance will include WiFi networks with hidden SSIDs.
* This is false by default.
@ -213,7 +210,7 @@ public:
* @param scanHidden If true, WiFi networks with hidden SSIDs will be included in scan results.
*/
void setScanHidden(bool scanHidden);
bool getScanHidden();
bool getScanHidden() const;
/**
* Set whether the AP controlled by this MeshBackendBase instance will have a WiFi network with hidden SSID.
@ -224,7 +221,7 @@ public:
* @param apHidden If true, the WiFi network created will have a hidden SSID.
*/
void setAPHidden(bool apHidden);
bool getAPHidden();
bool getAPHidden() const;
/**
* Set whether the normal events occurring in the library will be printed to Serial or not. Off by default.
@ -264,6 +261,13 @@ public:
protected:
/**
* @param latestTransmissionOutcomes The transmission outcomes vector to check.
*
* @return True if latest transmission was successful (i.e. latestTransmissionOutcomes is not empty and all entries have transmissionStatus TS_TRANSMISSION_COMPLETE). False otherwise.
*/
static bool latestTransmissionSuccessfulBase(const std::vector<TransmissionOutcome> &latestTransmissionOutcomes);
virtual void scanForNetworks(bool scanAllWiFiChannels);
virtual void printAPInfo(const NetworkInfoBase &apNetworkInfo);
@ -303,6 +307,7 @@ private:
requestHandlerType _requestHandler;
responseHandlerType _responseHandler;
networkFilterType _networkFilter;
transmissionOutcomesUpdateHookType _transmissionOutcomesUpdateHook = [](MeshBackendBase &){return true;};
static bool _printWarnings;
};

View File

@ -24,6 +24,13 @@
#include "MutexTracker.h"
bool MutexTracker::_captureBan = false;
bool &MutexTracker::captureBan()
{
return _captureBan;
}
MutexTracker::MutexTracker(bool &mutexToCapture)
{
attemptMutexCapture(mutexToCapture);
@ -59,7 +66,7 @@ void MutexTracker::releaseMutex()
bool MutexTracker::attemptMutexCapture(bool &mutexToCapture)
{
if(!mutexToCapture)
if(!captureBan() && !mutexToCapture)
{
_capturedMutex = &mutexToCapture;
*_capturedMutex = true;

View File

@ -34,6 +34,13 @@ class MutexTracker
{
public:
/*
* If captureBan is true, trying to capture a mutex will always fail.
* Set to false by default.
* captureBan can be managed by MutexTracker like any other mutex.
*/
static bool &captureBan();
/**
* Attempts to capture the mutex. Use the mutexCaptured() method to check success.
*/
@ -57,6 +64,8 @@ class MutexTracker
private:
static bool _captureBan;
bool *_capturedMutex = nullptr;
std::function<void()> _destructorHook = [](){ };

View File

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

View File

@ -69,6 +69,7 @@ public:
void setIsHidden(bool isHidden);
bool isHidden() const;
static uint8_t * const defaultBSSID;
static const String defaultSSID;
static const int32_t defaultWifiChannel;
static const uint8_t defaultEncryptionType;
@ -90,7 +91,7 @@ protected:
private:
uint8_t _bssidArray[6] {0};
uint8_t *_BSSID = nullptr;
uint8_t *_BSSID = defaultBSSID;
String _SSID = defaultSSID;
int32_t _wifiChannel = defaultWifiChannel;
uint8_t _encryptionType = defaultEncryptionType; // see enum wl_enc_type for values

View File

@ -27,14 +27,18 @@
using EspnowProtocolInterpreter::espnowHashKeyLength;
PeerRequestLog::PeerRequestLog(uint64_t requestID, bool requestEncrypted, const String &authenticationPassword, const String &peerRequestNonce, const uint8_t peerStaMac[6], const uint8_t peerApMac[6], const uint8_t hashKey[espnowHashKeyLength])
PeerRequestLog::PeerRequestLog(uint64_t requestID, bool requestEncrypted, const String &authenticationPassword, uint8_t encryptedConnectionsSoftLimit,
const String &peerRequestNonce, const uint8_t peerStaMac[6], const uint8_t peerApMac[6], const uint8_t hashKey[espnowHashKeyLength])
: EncryptedConnectionData(peerStaMac, peerApMac, 0, 0, EspnowMeshBackend::getEncryptionRequestTimeout(), hashKey),
_requestID(requestID), _requestEncrypted(requestEncrypted), _authenticationPassword(authenticationPassword), _peerRequestNonce(peerRequestNonce)
_requestID(requestID), _requestEncrypted(requestEncrypted), _authenticationPassword(authenticationPassword),
_encryptedConnectionsSoftLimit(encryptedConnectionsSoftLimit), _peerRequestNonce(peerRequestNonce)
{ }
PeerRequestLog::PeerRequestLog(uint64_t requestID, bool requestEncrypted, const String &authenticationPassword, const String &peerRequestNonce, const uint8_t peerStaMac[6], const uint8_t peerApMac[6], uint64_t peerSessionKey, uint64_t ownSessionKey, const uint8_t hashKey[espnowHashKeyLength])
PeerRequestLog::PeerRequestLog(uint64_t requestID, bool requestEncrypted, const String &authenticationPassword, uint8_t encryptedConnectionsSoftLimit, const String &peerRequestNonce,
const uint8_t peerStaMac[6], const uint8_t peerApMac[6], uint64_t peerSessionKey, uint64_t ownSessionKey, const uint8_t hashKey[espnowHashKeyLength])
: EncryptedConnectionData(peerStaMac, peerApMac, peerSessionKey, ownSessionKey, EspnowMeshBackend::getEncryptionRequestTimeout(), hashKey),
_requestID(requestID), _requestEncrypted(requestEncrypted), _authenticationPassword(authenticationPassword), _peerRequestNonce(peerRequestNonce)
_requestID(requestID), _requestEncrypted(requestEncrypted), _authenticationPassword(authenticationPassword),
_encryptedConnectionsSoftLimit(encryptedConnectionsSoftLimit), _peerRequestNonce(peerRequestNonce)
{ }
void PeerRequestLog::setRequestID(uint64_t requestID) { _requestID = requestID; }
@ -46,5 +50,8 @@ bool PeerRequestLog::requestEncrypted() { return _requestEncrypted; }
void PeerRequestLog::setAuthenticationPassword(const String &password) { _authenticationPassword = password; }
String PeerRequestLog::getAuthenticationPassword() { return _authenticationPassword; }
void PeerRequestLog::setEncryptedConnectionsSoftLimit(uint8_t softLimit) { _encryptedConnectionsSoftLimit = softLimit; }
uint8_t PeerRequestLog::getEncryptedConnectionsSoftLimit() { return _encryptedConnectionsSoftLimit; }
void PeerRequestLog::setPeerRequestNonce(const String &nonce) { _peerRequestNonce = nonce; }
String PeerRequestLog::getPeerRequestNonce() { return _peerRequestNonce; }

View File

@ -32,10 +32,11 @@ class PeerRequestLog : public EncryptedConnectionData {
public:
PeerRequestLog(uint64_t requestID, bool requestEncrypted, const String &authenticationPassword, const String &peerRequestNonce, const uint8_t peerStaMac[6],
const uint8_t peerApMac[6], const uint8_t hashKey[EspnowProtocolInterpreter::espnowHashKeyLength]);
PeerRequestLog(uint64_t requestID, bool requestEncrypted, const String &authenticationPassword, const String &peerRequestNonce, const uint8_t peerStaMac[6],
const uint8_t peerApMac[6], uint64_t peerSessionKey, uint64_t ownSessionKey, const uint8_t hashKey[EspnowProtocolInterpreter::espnowHashKeyLength]);
PeerRequestLog(uint64_t requestID, bool requestEncrypted, const String &authenticationPassword, uint8_t encryptedConnectionsSoftLimit, const String &peerRequestNonce,
const uint8_t peerStaMac[6], const uint8_t peerApMac[6], const uint8_t hashKey[EspnowProtocolInterpreter::espnowHashKeyLength]);
PeerRequestLog(uint64_t requestID, bool requestEncrypted, const String &authenticationPassword, uint8_t encryptedConnectionsSoftLimit, const String &peerRequestNonce,
const uint8_t peerStaMac[6], const uint8_t peerApMac[6], uint64_t peerSessionKey, uint64_t ownSessionKey,
const uint8_t hashKey[EspnowProtocolInterpreter::espnowHashKeyLength]);
void setRequestID(uint64_t requestID);
uint64_t getRequestID();
@ -46,6 +47,9 @@ public:
void setAuthenticationPassword(const String &password);
String getAuthenticationPassword();
void setEncryptedConnectionsSoftLimit(uint8_t softLimit);
uint8_t getEncryptedConnectionsSoftLimit();
void setPeerRequestNonce(const String &nonce);
String getPeerRequestNonce();
@ -54,6 +58,7 @@ private:
uint64_t _requestID;
bool _requestEncrypted;
String _authenticationPassword = "";
uint8_t _encryptedConnectionsSoftLimit;
String _peerRequestNonce = "";
};

View File

@ -30,6 +30,7 @@
const IPAddress TcpIpMeshBackend::emptyIP = IPAddress();
bool TcpIpMeshBackend::_tcpIpTransmissionMutex = false;
bool TcpIpMeshBackend::_tcpIpConnectionQueueMutex = false;
String TcpIpMeshBackend::lastSSID = "";
bool TcpIpMeshBackend::staticIPActivated = false;
@ -57,6 +58,17 @@ TcpIpMeshBackend::TcpIpMeshBackend(requestHandlerType requestHandler, responseHa
}
std::vector<TcpIpNetworkInfo> & TcpIpMeshBackend::connectionQueue()
{
MutexTracker connectionQueueMutexTracker(_tcpIpConnectionQueueMutex);
if(!connectionQueueMutexTracker.mutexCaptured())
{
assert(false && "ERROR! connectionQueue locked. Don't call connectionQueue() from callbacks other than NetworkFilter as this may corrupt program state!");
}
return _connectionQueue;
}
const std::vector<TcpIpNetworkInfo> & TcpIpMeshBackend::constConnectionQueue()
{
return _connectionQueue;
}
@ -66,6 +78,11 @@ std::vector<TransmissionOutcome> & TcpIpMeshBackend::latestTransmissionOutcomes(
return _latestTransmissionOutcomes;
}
bool TcpIpMeshBackend::latestTransmissionSuccessful()
{
return latestTransmissionSuccessfulBase(latestTransmissionOutcomes());
}
void TcpIpMeshBackend::begin()
{
if(!TcpIpMeshBackend::getAPController()) // If there is no active AP controller
@ -433,7 +450,7 @@ void TcpIpMeshBackend::attemptTransmission(const String &message, bool scan, boo
if(WiFi.status() == WL_CONNECTED)
{
transmission_status_t transmissionResult = attemptDataTransfer();
latestTransmissionOutcomes().push_back(TransmissionOutcome(connectionQueue().back(), transmissionResult));
latestTransmissionOutcomes().push_back(TransmissionOutcome(constConnectionQueue().back(), transmissionResult));
}
else
{
@ -443,11 +460,22 @@ void TcpIpMeshBackend::attemptTransmission(const String &message, bool scan, boo
scanForNetworks(scanAllWiFiChannels);
}
for(TcpIpNetworkInfo &currentNetwork : connectionQueue())
MutexTracker connectionQueueMutexTracker(_tcpIpConnectionQueueMutex);
if(!connectionQueueMutexTracker.mutexCaptured())
{
transmission_status_t transmissionResult = initiateTransmission(currentNetwork);
assert(false && "ERROR! connectionQueue locked. Don't call attemptTransmission from callbacks as this may corrupt program state! Aborting.");
}
else
{
for(const TcpIpNetworkInfo &currentNetwork : constConnectionQueue())
{
transmission_status_t transmissionResult = initiateTransmission(currentNetwork);
latestTransmissionOutcomes().push_back(TransmissionOutcome{.origin = currentNetwork, .transmissionStatus = transmissionResult});
latestTransmissionOutcomes().push_back(TransmissionOutcome{.origin = currentNetwork, .transmissionStatus = transmissionResult});
if(!getTransmissionOutcomesUpdateHook()(*this))
break;
}
}
}
@ -492,12 +520,12 @@ transmission_status_t TcpIpMeshBackend::attemptTransmission(const String &messag
return transmissionResult;
}
void TcpIpMeshBackend::acceptRequest()
void TcpIpMeshBackend::acceptRequests()
{
MutexTracker mutexTracker(_tcpIpTransmissionMutex);
if(!mutexTracker.mutexCaptured())
{
assert(false && "ERROR! TCP/IP transmission in progress. Don't call acceptRequest from TCP/IP callbacks as this may corrupt program state! Aborting.");
assert(false && "ERROR! TCP/IP transmission in progress. Don't call acceptRequests from callbacks as this may corrupt program state! Aborting.");
return;
}

View File

@ -67,12 +67,19 @@ public:
/**
* Returns a vector that contains the NetworkInfo for each WiFi network to connect to.
* This vector is unique for each mesh backend.
* This vector is unique for each mesh backend, but NetworkInfo elements can be directly transferred between the vectors as long as both SSID and BSSID are present.
* The connectionQueue vector is cleared before each new scan and filled via the networkFilter callback function once the scan completes.
* WiFi connections will start with connectionQueue[0] and then incrementally proceed to higher vector positions.
* Note that old network indicies often are invalidated whenever a new WiFi network scan occurs.
*
* Since the connectionQueue() is iterated over during transmissions, always use constConnectionQueue() from callbacks other than NetworkFilter.
*/
std::vector<TcpIpNetworkInfo> & connectionQueue();
static std::vector<TcpIpNetworkInfo> & connectionQueue();
/**
* Same as connectionQueue(), but can be called from all callbacks since the returned reference is const.
*/
static const std::vector<TcpIpNetworkInfo> & constConnectionQueue();
/**
* Returns a vector with the TransmissionOutcome for each AP to which a transmission was attempted during the latest attemptTransmission call.
@ -81,7 +88,13 @@ public:
* Connection attempts are indexed in the same order they were attempted.
* Note that old network indicies often are invalidated whenever a new WiFi network scan occurs.
*/
std::vector<TransmissionOutcome> & latestTransmissionOutcomes() override;
static std::vector<TransmissionOutcome> & latestTransmissionOutcomes();
/**
* @return True if latest transmission was successful (i.e. latestTransmissionOutcomes is not empty and all entries have transmissionStatus TS_TRANSMISSION_COMPLETE). False otherwise.
* The result is unique for each mesh backend.
*/
static bool latestTransmissionSuccessful();
/**
* Initialises the node.
@ -116,7 +129,7 @@ public:
/**
* If any clients are connected, accept their requests and call the requestHandler function for each one.
*/
void acceptRequest();
void acceptRequests();
/**
* Get the TCP/IP message that is currently scheduled for transmission.
@ -185,7 +198,7 @@ public:
/**
* Set the timeout to use for transmissions when this TcpIpMeshBackend instance acts as an AP (i.e. when receiving connections from other stations).
* This will affect the timeout of the acceptRequest method.
* This will affect the timeout of the acceptRequests method.
* The timeout is 4 500 ms by default.
* Will also change the setting for the active AP (without an AP restart)
* if this TcpIpMeshBackend instance is the current AP controller.
@ -217,6 +230,11 @@ protected:
*/
static bool _tcpIpTransmissionMutex;
/**
* Will be true when the connectionQueue should not be modified.
*/
static bool _tcpIpConnectionQueueMutex;
/**
* Check if there is an ongoing TCP/IP transmission in the library. Used to avoid interrupting transmissions.
*

View File

@ -23,9 +23,16 @@
*/
#include "TcpIpNetworkInfo.h"
#include <assert.h>
TcpIpNetworkInfo::TcpIpNetworkInfo(int networkIndex) : NetworkInfoBase(networkIndex) { };
TcpIpNetworkInfo::TcpIpNetworkInfo(const NetworkInfoBase &originalNetworkInfo) : NetworkInfoBase(originalNetworkInfo)
{
assert(SSID() != defaultSSID); // We need at least SSID to be able to connect.
};
TcpIpNetworkInfo::TcpIpNetworkInfo(const String &SSID, int32_t wifiChannel, const uint8_t BSSID[6], uint8_t encryptionType, int32_t RSSI , bool isHidden)
: NetworkInfoBase(SSID, wifiChannel, BSSID, encryptionType, RSSI, isHidden)
{ }

View File

@ -36,10 +36,13 @@ public:
*/
TcpIpNetworkInfo(int networkIndex);
TcpIpNetworkInfo(const NetworkInfoBase &originalNetworkInfo);
/**
* Without giving wifiChannel and BSSID, connection time is longer.
*/
TcpIpNetworkInfo(const String &SSID, int32_t wifiChannel = defaultWifiChannel, const uint8_t BSSID[6] = nullptr, uint8_t encryptionType = defaultEncryptionType,
TcpIpNetworkInfo(const String &SSID, int32_t wifiChannel = defaultWifiChannel, const uint8_t BSSID[6] = defaultBSSID, uint8_t encryptionType = defaultEncryptionType,
int32_t RSSI = defaultRSSI, bool isHidden = defaultIsHidden);
};