mirror of
https://github.com/esp8266/Arduino.git
synced 2025-04-19 23:22:16 +03:00
- Move all serialization code to separate Serializer namespace and files.
- Generalize and improve JSON processing code. - Prevent mesh passwords from containing " characters to avoid messing up the JSON processing. - Improve documentation.
This commit is contained in:
parent
e64125a53c
commit
2ec2679d6e
@ -19,7 +19,7 @@ namespace TypeCast = MeshTypeConversionFunctions;
|
|||||||
https://arduino-esp8266.readthedocs.io/en/latest/PROGMEM.html
|
https://arduino-esp8266.readthedocs.io/en/latest/PROGMEM.html
|
||||||
*/
|
*/
|
||||||
constexpr char exampleMeshName[] PROGMEM = "MeshNode_"; // The name of the mesh network. Used as prefix for the node SSID and to find other network nodes in the example networkFilter and broadcastFilter functions below.
|
constexpr char exampleMeshName[] PROGMEM = "MeshNode_"; // The name of the mesh network. Used as prefix for the node SSID and to find other network nodes in the example networkFilter and broadcastFilter functions below.
|
||||||
constexpr char exampleWiFiPassword[] PROGMEM = "ChangeThisWiFiPassword_TODO"; // The password has to be min 8 and max 64 characters long, otherwise an AP which uses it will not be found during scans.
|
constexpr char exampleWiFiPassword[] PROGMEM = "ChangeThisWiFiPassword_TODO"; // Note: " is an illegal character. The password has to be min 8 and max 64 characters long, otherwise an AP which uses it will not be found during scans.
|
||||||
|
|
||||||
// A custom encryption key is required when using encrypted ESP-NOW transmissions. There is always a default Kok set, but it can be replaced if desired.
|
// A custom encryption key is required when using encrypted ESP-NOW transmissions. There is always a default Kok set, but it can be replaced if desired.
|
||||||
// All ESP-NOW keys below must match in an encrypted connection pair for encrypted communication to be possible.
|
// All ESP-NOW keys below must match in an encrypted connection pair for encrypted communication to be possible.
|
||||||
|
@ -26,7 +26,7 @@ namespace TypeCast = MeshTypeConversionFunctions;
|
|||||||
https://arduino-esp8266.readthedocs.io/en/latest/PROGMEM.html
|
https://arduino-esp8266.readthedocs.io/en/latest/PROGMEM.html
|
||||||
*/
|
*/
|
||||||
constexpr char exampleMeshName[] PROGMEM = "MeshNode_"; // The name of the mesh network. Used as prefix for the node SSID and to find other network nodes in the example networkFilter and broadcastFilter functions below.
|
constexpr char exampleMeshName[] PROGMEM = "MeshNode_"; // The name of the mesh network. Used as prefix for the node SSID and to find other network nodes in the example networkFilter and broadcastFilter functions below.
|
||||||
constexpr char exampleWiFiPassword[] PROGMEM = "ChangeThisWiFiPassword_TODO"; // The password has to be min 8 and max 64 characters long, otherwise an AP which uses it will not be found during scans.
|
constexpr char exampleWiFiPassword[] PROGMEM = "ChangeThisWiFiPassword_TODO"; // Note: " is an illegal character. The password has to be min 8 and max 64 characters long, otherwise an AP which uses it will not be found during scans.
|
||||||
|
|
||||||
// A custom encryption key is required when using encrypted ESP-NOW transmissions. There is always a default Kok set, but it can be replaced if desired.
|
// A custom encryption key is required when using encrypted ESP-NOW transmissions. There is always a default Kok set, but it can be replaced if desired.
|
||||||
// All ESP-NOW keys below must match in an encrypted connection pair for encrypted communication to be possible.
|
// All ESP-NOW keys below must match in an encrypted connection pair for encrypted communication to be possible.
|
||||||
|
@ -19,7 +19,7 @@ namespace TypeCast = MeshTypeConversionFunctions;
|
|||||||
https://arduino-esp8266.readthedocs.io/en/latest/PROGMEM.html
|
https://arduino-esp8266.readthedocs.io/en/latest/PROGMEM.html
|
||||||
*/
|
*/
|
||||||
constexpr char exampleMeshName[] PROGMEM = "MeshNode_";
|
constexpr char exampleMeshName[] PROGMEM = "MeshNode_";
|
||||||
constexpr char exampleWiFiPassword[] PROGMEM = "ChangeThisWiFiPassword_TODO";
|
constexpr char exampleWiFiPassword[] PROGMEM = "ChangeThisWiFiPassword_TODO"; // Note: " is an illegal character. The password has to be min 8 and max 64 characters long, otherwise an AP which uses it will not be found during scans.
|
||||||
|
|
||||||
unsigned int requestNumber = 0;
|
unsigned int requestNumber = 0;
|
||||||
unsigned int responseNumber = 0;
|
unsigned int responseNumber = 0;
|
||||||
|
@ -27,6 +27,7 @@
|
|||||||
#include "TypeConversionFunctions.h"
|
#include "TypeConversionFunctions.h"
|
||||||
#include "JsonTranslator.h"
|
#include "JsonTranslator.h"
|
||||||
#include "MeshCryptoInterface.h"
|
#include "MeshCryptoInterface.h"
|
||||||
|
#include "Serializer.h"
|
||||||
|
|
||||||
namespace
|
namespace
|
||||||
{
|
{
|
||||||
@ -157,16 +158,8 @@ bool EncryptedConnectionData::desync() const { return _desync; }
|
|||||||
|
|
||||||
String EncryptedConnectionData::serialize() const
|
String EncryptedConnectionData::serialize() const
|
||||||
{
|
{
|
||||||
// Returns: {"connectionState":{"duration":"123","password":"abc","ownSK":"1A2","peerSK":"3B4","peerStaMac":"F2","peerApMac":"E3"}}
|
return Serializer:: serializeEncryptedConnection((temporary() ? String(temporary()->remainingDuration()) : emptyString), String(desync()), TypeCast::uint64ToString(getOwnSessionKey()),
|
||||||
|
TypeCast::uint64ToString(getPeerSessionKey()), TypeCast::macToString(_peerStaMac), TypeCast::macToString(_peerApMac));
|
||||||
return
|
|
||||||
String(FPSTR(JsonTranslator::jsonConnectionState))
|
|
||||||
+ (temporary() ? String(FPSTR(JsonTranslator::jsonDuration)) + '\"' + String(temporary()->remainingDuration()) + F("\",") : emptyString)
|
|
||||||
+ FPSTR(JsonTranslator::jsonDesync) + '\"' + String(desync()) + F("\",")
|
|
||||||
+ FPSTR(JsonTranslator::jsonOwnSessionKey) + '\"' + TypeCast::uint64ToString(getOwnSessionKey()) + F("\",")
|
|
||||||
+ FPSTR(JsonTranslator::jsonPeerSessionKey) + '\"' + TypeCast::uint64ToString(getPeerSessionKey()) + F("\",")
|
|
||||||
+ FPSTR(JsonTranslator::jsonPeerStaMac) + '\"' + TypeCast::macToString(_peerStaMac) + F("\",")
|
|
||||||
+ FPSTR(JsonTranslator::jsonPeerApMac) + '\"' + TypeCast::macToString(_peerApMac) + F("\"}}");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const ExpiringTimeTracker *EncryptedConnectionData::temporary() const
|
const ExpiringTimeTracker *EncryptedConnectionData::temporary() const
|
||||||
|
@ -27,6 +27,8 @@ extern "C" {
|
|||||||
#include "MutexTracker.h"
|
#include "MutexTracker.h"
|
||||||
#include "JsonTranslator.h"
|
#include "JsonTranslator.h"
|
||||||
#include "MeshCryptoInterface.h"
|
#include "MeshCryptoInterface.h"
|
||||||
|
#include "EspnowUtility.h"
|
||||||
|
#include "Serializer.h"
|
||||||
|
|
||||||
namespace
|
namespace
|
||||||
{
|
{
|
||||||
@ -1773,7 +1775,7 @@ EncryptedConnectionStatus EspnowMeshBackend::requestEncryptedConnectionKernel(co
|
|||||||
requestMessage = encryptionRequestBuilder(requestNonce, existingTimeTracker);
|
requestMessage = encryptionRequestBuilder(requestNonce, existingTimeTracker);
|
||||||
else if(_ongoingPeerRequestResult == EncryptedConnectionStatus::SOFT_LIMIT_CONNECTION_ESTABLISHED)
|
else if(_ongoingPeerRequestResult == EncryptedConnectionStatus::SOFT_LIMIT_CONNECTION_ESTABLISHED)
|
||||||
// We will only get a soft limit connection. Adjust future actions based on this.
|
// We will only get a soft limit connection. Adjust future actions based on this.
|
||||||
requestMessage = JsonTranslator::createEncryptionRequestHmacMessage(FPSTR(temporaryEncryptionRequestHeader), requestNonce, getEspnowHashKey(),
|
requestMessage = Serializer::createEncryptionRequestHmacMessage(FPSTR(temporaryEncryptionRequestHeader), requestNonce, getEspnowHashKey(),
|
||||||
hashKeyLength, getAutoEncryptionDuration());
|
hashKeyLength, getAutoEncryptionDuration());
|
||||||
else
|
else
|
||||||
assert(false && String(F("Unknown _ongoingPeerRequestResult during encrypted connection finalization!")));
|
assert(false && String(F("Unknown _ongoingPeerRequestResult during encrypted connection finalization!")));
|
||||||
@ -1848,7 +1850,7 @@ String EspnowMeshBackend::defaultEncryptionRequestBuilder(const String &requestH
|
|||||||
{
|
{
|
||||||
(void)existingTimeTracker; // This removes a "unused parameter" compiler warning. Does nothing else.
|
(void)existingTimeTracker; // This removes a "unused parameter" compiler warning. Does nothing else.
|
||||||
|
|
||||||
return JsonTranslator::createEncryptionRequestHmacMessage(requestHeader, requestNonce, hashKey, hashKeyLength, durationMs);
|
return Serializer::createEncryptionRequestHmacMessage(requestHeader, requestNonce, hashKey, hashKeyLength, durationMs);
|
||||||
}
|
}
|
||||||
|
|
||||||
String EspnowMeshBackend::flexibleEncryptionRequestBuilder(const uint32_t minDurationMs, const uint8_t *hashKey,
|
String EspnowMeshBackend::flexibleEncryptionRequestBuilder(const uint32_t minDurationMs, const uint8_t *hashKey,
|
||||||
@ -1860,7 +1862,7 @@ String EspnowMeshBackend::flexibleEncryptionRequestBuilder(const uint32_t minDur
|
|||||||
uint32_t connectionDuration = minDurationMs >= existingTimeTracker.remainingDuration() ?
|
uint32_t connectionDuration = minDurationMs >= existingTimeTracker.remainingDuration() ?
|
||||||
minDurationMs : existingTimeTracker.remainingDuration();
|
minDurationMs : existingTimeTracker.remainingDuration();
|
||||||
|
|
||||||
return createEncryptionRequestHmacMessage(FPSTR(temporaryEncryptionRequestHeader), requestNonce, hashKey, hashKeyLength, connectionDuration);
|
return Serializer::createEncryptionRequestHmacMessage(FPSTR(temporaryEncryptionRequestHeader), requestNonce, hashKey, hashKeyLength, connectionDuration);
|
||||||
}
|
}
|
||||||
|
|
||||||
EncryptedConnectionStatus EspnowMeshBackend::requestEncryptedConnection(const uint8_t *peerMac)
|
EncryptedConnectionStatus EspnowMeshBackend::requestEncryptedConnection(const uint8_t *peerMac)
|
||||||
@ -2437,13 +2439,13 @@ void EspnowMeshBackend::sendPeerRequestConfirmations(const ExpiringTimeTracker *
|
|||||||
if(!existingEncryptedConnection &&
|
if(!existingEncryptedConnection &&
|
||||||
((reciprocalPeerRequest && encryptedConnections.size() >= maxEncryptedConnections) || (!reciprocalPeerRequest && reservedEncryptedConnections() >= maxEncryptedConnections)))
|
((reciprocalPeerRequest && encryptedConnections.size() >= maxEncryptedConnections) || (!reciprocalPeerRequest && reservedEncryptedConnections() >= maxEncryptedConnections)))
|
||||||
{
|
{
|
||||||
espnowSendToNodeUnsynchronized(JsonTranslator::createEncryptionRequestHmacMessage(FPSTR(maxConnectionsReachedHeader),
|
espnowSendToNodeUnsynchronized(Serializer::createEncryptionRequestHmacMessage(FPSTR(maxConnectionsReachedHeader),
|
||||||
confirmationsIterator->getPeerRequestNonce(), hashKey, hashKeyLength),
|
confirmationsIterator->getPeerRequestNonce(), hashKey, hashKeyLength),
|
||||||
defaultBSSID, 'C', generateMessageID(nullptr)); // Generates a new message ID to avoid sending encrypted sessionKeys over unencrypted connections.
|
defaultBSSID, 'C', generateMessageID(nullptr)); // Generates a new message ID to avoid sending encrypted sessionKeys over unencrypted connections.
|
||||||
|
|
||||||
confirmationsIterator = peerRequestConfirmationsToSend.erase(confirmationsIterator);
|
confirmationsIterator = peerRequestConfirmationsToSend.erase(confirmationsIterator);
|
||||||
}
|
}
|
||||||
else if(espnowSendToNodeUnsynchronized(JsonTranslator::createEncryptionRequestHmacMessage(FPSTR(basicConnectionInfoHeader),
|
else if(espnowSendToNodeUnsynchronized(Serializer::createEncryptionRequestHmacMessage(FPSTR(basicConnectionInfoHeader),
|
||||||
confirmationsIterator->getPeerRequestNonce(), hashKey, hashKeyLength),
|
confirmationsIterator->getPeerRequestNonce(), hashKey, hashKeyLength),
|
||||||
sendToDefaultBSSID ? defaultBSSID : unencryptedBSSID, 'C', generateMessageID(nullptr)) // Generates a new message ID to avoid sending encrypted sessionKeys over unencrypted connections.
|
sendToDefaultBSSID ? defaultBSSID : unencryptedBSSID, 'C', generateMessageID(nullptr)) // Generates a new message ID to avoid sending encrypted sessionKeys over unencrypted connections.
|
||||||
== TransmissionStatusType::TRANSMISSION_COMPLETE)
|
== TransmissionStatusType::TRANSMISSION_COMPLETE)
|
||||||
@ -2475,7 +2477,7 @@ void EspnowMeshBackend::sendPeerRequestConfirmations(const ExpiringTimeTracker *
|
|||||||
if(!existingEncryptedConnection)
|
if(!existingEncryptedConnection)
|
||||||
{
|
{
|
||||||
// Send "node full" message
|
// Send "node full" message
|
||||||
espnowSendToNodeUnsynchronized(JsonTranslator::createEncryptionRequestHmacMessage(FPSTR(maxConnectionsReachedHeader),
|
espnowSendToNodeUnsynchronized(Serializer::createEncryptionRequestHmacMessage(FPSTR(maxConnectionsReachedHeader),
|
||||||
confirmationsIterator->getPeerRequestNonce(), hashKey, hashKeyLength),
|
confirmationsIterator->getPeerRequestNonce(), hashKey, hashKeyLength),
|
||||||
defaultBSSID, 'C', generateMessageID(nullptr)); // Generates a new message ID to avoid sending encrypted sessionKeys over unencrypted connections.
|
defaultBSSID, 'C', generateMessageID(nullptr)); // Generates a new message ID to avoid sending encrypted sessionKeys over unencrypted connections.
|
||||||
}
|
}
|
||||||
@ -2503,7 +2505,7 @@ void EspnowMeshBackend::sendPeerRequestConfirmations(const ExpiringTimeTracker *
|
|||||||
|
|
||||||
// Send password and keys.
|
// 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.
|
// Probably no need to know which connection type to use, that is stored in request node and will be sent over for finalization.
|
||||||
espnowSendToNodeUnsynchronized(JsonTranslator::createEncryptedConnectionInfo(messageHeader,
|
espnowSendToNodeUnsynchronized(Serializer::createEncryptedConnectionInfo(messageHeader,
|
||||||
confirmationsIterator->getPeerRequestNonce(), confirmationsIterator->getAuthenticationPassword(),
|
confirmationsIterator->getPeerRequestNonce(), confirmationsIterator->getAuthenticationPassword(),
|
||||||
existingEncryptedConnection->getOwnSessionKey(), existingEncryptedConnection->getPeerSessionKey()),
|
existingEncryptedConnection->getOwnSessionKey(), existingEncryptedConnection->getPeerSessionKey()),
|
||||||
defaultBSSID, 'C', generateMessageID(nullptr)); // Generates a new message ID to avoid sending encrypted sessionKeys over unencrypted connections.
|
defaultBSSID, 'C', generateMessageID(nullptr)); // Generates a new message ID to avoid sending encrypted sessionKeys over unencrypted connections.
|
||||||
@ -2679,11 +2681,7 @@ void EspnowMeshBackend::resetTransmissionFailRate()
|
|||||||
|
|
||||||
String EspnowMeshBackend::serializeUnencryptedConnection()
|
String EspnowMeshBackend::serializeUnencryptedConnection()
|
||||||
{
|
{
|
||||||
using namespace JsonTranslator;
|
return Serializer::serializeUnencryptedConnection(String(_unsynchronizedMessageID));
|
||||||
|
|
||||||
// Returns: {"connectionState":{"unsyncMsgID":"123"}}
|
|
||||||
|
|
||||||
return String(FPSTR(jsonConnectionState)) + createJsonEndPair(FPSTR(jsonUnsynchronizedMessageID), String(_unsynchronizedMessageID));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
String EspnowMeshBackend::serializeEncryptedConnection(const uint8_t *peerMac)
|
String EspnowMeshBackend::serializeEncryptedConnection(const uint8_t *peerMac)
|
||||||
|
@ -1217,7 +1217,7 @@ private:
|
|||||||
* @param peerMac The MAC of the node with which an encrypted connection should be established.
|
* @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.
|
* @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.
|
* Called twice when the request is successful. First to build the initial request message and then to build the connection verification message.
|
||||||
* The request message should typically be of the form: JsonTranslator::createEncryptionRequestIntro() + JsonTranslator::createEncryptionRequestEnding().
|
* The request message should typically be of the form found in Serializer::createEncryptionRequestHmacMessage.
|
||||||
* @return The ultimate status of the requested encrypted connection, as EncryptedConnectionStatus.
|
* @return The ultimate status of the requested encrypted connection, as EncryptedConnectionStatus.
|
||||||
*/
|
*/
|
||||||
EncryptedConnectionStatus requestEncryptedConnectionKernel(const uint8_t *peerMac, const encryptionRequestBuilderType &encryptionRequestBuilder);
|
EncryptedConnectionStatus requestEncryptedConnectionKernel(const uint8_t *peerMac, const encryptionRequestBuilderType &encryptionRequestBuilder);
|
||||||
|
@ -25,6 +25,7 @@
|
|||||||
#include "FloodingMesh.h"
|
#include "FloodingMesh.h"
|
||||||
#include "TypeConversionFunctions.h"
|
#include "TypeConversionFunctions.h"
|
||||||
#include "JsonTranslator.h"
|
#include "JsonTranslator.h"
|
||||||
|
#include "Serializer.h"
|
||||||
|
|
||||||
namespace
|
namespace
|
||||||
{
|
{
|
||||||
@ -153,16 +154,11 @@ void FloodingMesh::performMeshInstanceMaintenance()
|
|||||||
|
|
||||||
String FloodingMesh::serializeMeshState() const
|
String FloodingMesh::serializeMeshState() const
|
||||||
{
|
{
|
||||||
using namespace JsonTranslator;
|
|
||||||
|
|
||||||
// Returns: {"meshState":{"connectionState":{"unsyncMsgID":"123"},"meshMsgCount":"123"}}
|
|
||||||
|
|
||||||
String connectionState = getEspnowMeshBackendConst().serializeUnencryptedConnection();
|
String connectionState = getEspnowMeshBackendConst().serializeUnencryptedConnection();
|
||||||
|
uint32_t unsyncMsgID = 0;
|
||||||
|
JsonTranslator::getUnsynchronizedMessageID(connectionState, unsyncMsgID);
|
||||||
|
|
||||||
return
|
return Serializer::serializeMeshState(String(unsyncMsgID), String(_messageCount));
|
||||||
String(F("{\"meshState\":{"))
|
|
||||||
+ connectionState.substring(1, connectionState.length() - 1) + String(',')
|
|
||||||
+ createJsonEndPair(FPSTR(jsonMeshMessageCount), String(_messageCount));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void FloodingMesh::loadMeshState(const String &serializedMeshState)
|
void FloodingMesh::loadMeshState(const String &serializedMeshState)
|
||||||
|
@ -25,59 +25,171 @@
|
|||||||
#include "JsonTranslator.h"
|
#include "JsonTranslator.h"
|
||||||
#include "EspnowProtocolInterpreter.h"
|
#include "EspnowProtocolInterpreter.h"
|
||||||
#include "TypeConversionFunctions.h"
|
#include "TypeConversionFunctions.h"
|
||||||
#include "MeshCryptoInterface.h"
|
#include "MeshCryptoInterface.h" // TODO: Remove?
|
||||||
|
|
||||||
namespace
|
namespace
|
||||||
{
|
{
|
||||||
namespace TypeCast = MeshTypeConversionFunctions;
|
namespace TypeCast = MeshTypeConversionFunctions;
|
||||||
|
|
||||||
|
bool getMac(const String &jsonString, const String &valueIdentifier, uint8_t *resultArray)
|
||||||
|
{
|
||||||
|
String jsonValue;
|
||||||
|
bool decoded = JsonTranslator::decode(jsonString, valueIdentifier, jsonValue);
|
||||||
|
|
||||||
|
if(jsonValue.length() != 12)
|
||||||
|
decoded = false; // Mac String is always 12 characters long
|
||||||
|
|
||||||
|
if(decoded)
|
||||||
|
TypeCast::stringToMac(jsonValue, resultArray);
|
||||||
|
|
||||||
|
return decoded;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
namespace JsonTranslator
|
namespace JsonTranslator
|
||||||
{
|
{
|
||||||
String createJsonPair(const String &valueIdentifier, const String &value)
|
int32_t getStartIndex(const String &jsonString, const String &valueIdentifier, const int32_t searchStartIndex)
|
||||||
{
|
{
|
||||||
return valueIdentifier + '\"' + value + F("\",");
|
int32_t startIndex = jsonString.indexOf(String('"') + valueIdentifier + F("\":"), searchStartIndex);
|
||||||
|
if(startIndex < 0)
|
||||||
|
return startIndex;
|
||||||
|
|
||||||
|
startIndex += valueIdentifier.length() + 3; // Do not include valueIdentifier and associated characters
|
||||||
|
return startIndex;
|
||||||
}
|
}
|
||||||
|
|
||||||
String createJsonEndPair(const String &valueIdentifier, const String &value)
|
int32_t getEndIndex(const String &jsonString, const int32_t searchStartIndex)
|
||||||
{
|
{
|
||||||
return valueIdentifier + '\"' + value + F("\"}}");
|
int32_t endIndex = -1;
|
||||||
|
|
||||||
|
if(jsonString[searchStartIndex] == '"')
|
||||||
|
{
|
||||||
|
endIndex = jsonString.indexOf('"', searchStartIndex + 1);
|
||||||
|
}
|
||||||
|
else if(jsonString[searchStartIndex] == '{')
|
||||||
|
{
|
||||||
|
uint32_t depth = 1;
|
||||||
|
bool withinString = false;
|
||||||
|
|
||||||
|
for(uint32_t index = searchStartIndex + 1; depth != 0 && index < jsonString.length(); ++index)
|
||||||
|
{
|
||||||
|
if(jsonString[index] == '"')
|
||||||
|
withinString = !withinString;
|
||||||
|
else if(!withinString)
|
||||||
|
{
|
||||||
|
if(jsonString[index] == '{')
|
||||||
|
++depth;
|
||||||
|
else if(jsonString[index] == '}')
|
||||||
|
--depth;
|
||||||
}
|
}
|
||||||
|
|
||||||
String createEncryptedConnectionInfo(const String &infoHeader, const String &requestNonce, const String &authenticationPassword, const uint64_t ownSessionKey, const uint64_t peerSessionKey)
|
if(depth == 0)
|
||||||
{
|
{
|
||||||
// Returns: Encrypted connection info:{"arguments":{"nonce":"1F2","password":"abc","ownSK":"3B4","peerSK":"1A2"}}
|
assert(index < 0x80000000); // Must avoid int32_t overflow
|
||||||
|
endIndex = index;
|
||||||
return
|
}
|
||||||
infoHeader + String(F("{\"arguments\":{"))
|
}
|
||||||
+ createJsonPair(FPSTR(jsonNonce), requestNonce)
|
|
||||||
+ createJsonPair(FPSTR(jsonPassword), authenticationPassword)
|
|
||||||
+ createJsonPair(FPSTR(jsonOwnSessionKey), TypeCast::uint64ToString(peerSessionKey)) // Exchanges session keys since it should be valid for the receiver.
|
|
||||||
+ createJsonEndPair(FPSTR(jsonPeerSessionKey), TypeCast::uint64ToString(ownSessionKey));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
String createEncryptionRequestIntro(const String &requestHeader, const uint32_t duration)
|
return endIndex;
|
||||||
{
|
|
||||||
return
|
|
||||||
requestHeader + String(F("{\"arguments\":{"))
|
|
||||||
+ (requestHeader == FPSTR(EspnowProtocolInterpreter::temporaryEncryptionRequestHeader) ? createJsonPair(FPSTR(jsonDuration), String(duration)) : emptyString);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
String createEncryptionRequestEnding(const String &requestNonce)
|
String encode(std::initializer_list<String> identifiersAndValues)
|
||||||
{
|
{
|
||||||
return createJsonEndPair(FPSTR(jsonNonce), requestNonce);
|
assert(identifiersAndValues.size() % 2 == 0); // List must consist of identifer-value pairs.
|
||||||
|
|
||||||
|
String result = String('{');
|
||||||
|
|
||||||
|
bool isIdentifier = true;
|
||||||
|
for(String element : identifiersAndValues)
|
||||||
|
{
|
||||||
|
bool isObject = !isIdentifier && element[0] == '{';
|
||||||
|
if(isObject)
|
||||||
|
result += element;
|
||||||
|
else
|
||||||
|
result += String('"') + element + String('"');
|
||||||
|
|
||||||
|
if(isIdentifier)
|
||||||
|
result += ':';
|
||||||
|
else
|
||||||
|
result += ',';
|
||||||
|
|
||||||
|
isIdentifier = !isIdentifier;
|
||||||
}
|
}
|
||||||
|
|
||||||
String createEncryptionRequestHmacMessage(const String &requestHeader, const String &requestNonce, const uint8_t *hashKey, const uint8_t hashKeyLength, const uint32_t duration)
|
result[result.length() - 1] = '}';
|
||||||
{
|
|
||||||
String mainMessage = createEncryptionRequestIntro(requestHeader, duration) + createJsonPair(FPSTR(jsonNonce), requestNonce);
|
return result;
|
||||||
uint8_t staMac[6] {0};
|
|
||||||
uint8_t apMac[6] {0};
|
|
||||||
String requesterStaApMac = TypeCast::macToString(WiFi.macAddress(staMac)) + TypeCast::macToString(WiFi.softAPmacAddress(apMac));
|
|
||||||
String hmac = MeshCryptoInterface::createMeshHmac(requesterStaApMac + mainMessage, hashKey, hashKeyLength);
|
|
||||||
return mainMessage + createJsonEndPair(FPSTR(jsonHmac), hmac);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
String encodeLiterally(std::initializer_list<String> identifiersAndValues)
|
||||||
|
{
|
||||||
|
assert(identifiersAndValues.size() % 2 == 0); // List must consist of identifer-value pairs.
|
||||||
|
|
||||||
|
String result = String('{');
|
||||||
|
|
||||||
|
bool isIdentifier = true;
|
||||||
|
for(String element : identifiersAndValues)
|
||||||
|
{
|
||||||
|
if(isIdentifier)
|
||||||
|
result += String('"') + element + String('"') + ':';
|
||||||
|
else
|
||||||
|
result += element + ',';
|
||||||
|
|
||||||
|
isIdentifier = !isIdentifier;
|
||||||
|
}
|
||||||
|
|
||||||
|
result[result.length() - 1] = '}';
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool decode(const String &jsonString, const String &valueIdentifier, String &value)
|
||||||
|
{
|
||||||
|
int32_t startIndex = getStartIndex(jsonString, valueIdentifier);
|
||||||
|
if(startIndex < 0)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
int32_t endIndex = getEndIndex(jsonString, startIndex);
|
||||||
|
if(endIndex < 0)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if(jsonString[startIndex] == '"')
|
||||||
|
++startIndex; // Should not include starting "
|
||||||
|
else if(jsonString[startIndex] == '{')
|
||||||
|
++endIndex; // Should include ending }
|
||||||
|
else
|
||||||
|
assert(false && F("Illegal JSON starting character!"));
|
||||||
|
|
||||||
|
value = jsonString.substring(startIndex, endIndex);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool decode(const String &jsonString, const String &valueIdentifier, uint32_t &value)
|
||||||
|
{
|
||||||
|
String jsonValue;
|
||||||
|
bool decoded = decode(jsonString, valueIdentifier, jsonValue);
|
||||||
|
|
||||||
|
if(decoded)
|
||||||
|
value = strtoul(jsonValue.c_str(), nullptr, 0); // strtoul stops reading input when an invalid character is discovered.
|
||||||
|
|
||||||
|
return decoded;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool decodeRadix(const String &jsonString, const String &valueIdentifier, uint64_t &value, const uint8_t radix)
|
||||||
|
{
|
||||||
|
String jsonValue;
|
||||||
|
bool decoded = decode(jsonString, valueIdentifier, jsonValue);
|
||||||
|
|
||||||
|
if(decoded)
|
||||||
|
value = TypeCast::stringToUint64(jsonValue, radix);
|
||||||
|
|
||||||
|
return decoded;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// TODO: Move to encryptedEspnow class?
|
||||||
bool verifyEncryptionRequestHmac(const String &encryptionRequestHmacMessage, const uint8_t *requesterStaMac, const uint8_t *requesterApMac,
|
bool verifyEncryptionRequestHmac(const String &encryptionRequestHmacMessage, const uint8_t *requesterStaMac, const uint8_t *requesterApMac,
|
||||||
const uint8_t *hashKey, const uint8_t hashKeyLength)
|
const uint8_t *hashKey, const uint8_t hashKeyLength)
|
||||||
{
|
{
|
||||||
@ -86,7 +198,7 @@ namespace JsonTranslator
|
|||||||
String hmac;
|
String hmac;
|
||||||
if(getHmac(encryptionRequestHmacMessage, hmac))
|
if(getHmac(encryptionRequestHmacMessage, hmac))
|
||||||
{
|
{
|
||||||
int32_t hmacStartIndex = encryptionRequestHmacMessage.indexOf(FPSTR(jsonHmac));
|
int32_t hmacStartIndex = encryptionRequestHmacMessage.indexOf(String('"') + FPSTR(jsonHmac) + F("\":"));
|
||||||
if(hmacStartIndex < 0)
|
if(hmacStartIndex < 0)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
@ -100,179 +212,78 @@ namespace JsonTranslator
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
int32_t getStartIndex(const String &jsonString, const String &valueIdentifier, const int32_t searchStartIndex)
|
|
||||||
{
|
|
||||||
int32_t startIndex = jsonString.indexOf(valueIdentifier, searchStartIndex);
|
|
||||||
if(startIndex < 0)
|
|
||||||
return startIndex;
|
|
||||||
|
|
||||||
startIndex += valueIdentifier.length() + 1; // Do not include valueIdentifier and initial quotation mark
|
|
||||||
return startIndex;
|
|
||||||
}
|
|
||||||
|
|
||||||
int32_t getEndIndex(const String &jsonString, const int32_t searchStartIndex)
|
|
||||||
{
|
|
||||||
int32_t endIndex = jsonString.indexOf(',', searchStartIndex);
|
|
||||||
if(endIndex < 0)
|
|
||||||
endIndex = jsonString.indexOf('}', searchStartIndex);
|
|
||||||
|
|
||||||
endIndex -= 1; // End index will be at the character after the closing quotation mark, so need to subtract 1.
|
|
||||||
|
|
||||||
return endIndex;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool getConnectionState(const String &jsonString, String &result)
|
bool getConnectionState(const String &jsonString, String &result)
|
||||||
{
|
{
|
||||||
int32_t startIndex = jsonString.indexOf(FPSTR(jsonConnectionState));
|
return decode(jsonString, FPSTR(jsonConnectionState), result);
|
||||||
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)
|
bool getPassword(const String &jsonString, String &result)
|
||||||
{
|
{
|
||||||
int32_t startIndex = getStartIndex(jsonString, FPSTR(jsonPassword));
|
return decode(jsonString, FPSTR(jsonPassword), result);
|
||||||
if(startIndex < 0)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
int32_t endIndex = getEndIndex(jsonString, startIndex);
|
|
||||||
if(endIndex < 0)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
result = jsonString.substring(startIndex, endIndex);
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool getOwnSessionKey(const String &jsonString, uint64_t &result)
|
bool getOwnSessionKey(const String &jsonString, uint64_t &result)
|
||||||
{
|
{
|
||||||
int32_t startIndex = getStartIndex(jsonString, FPSTR(jsonOwnSessionKey));
|
return decodeRadix(jsonString, FPSTR(jsonOwnSessionKey), result);
|
||||||
if(startIndex < 0)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
int32_t endIndex = getEndIndex(jsonString, startIndex);
|
|
||||||
if(endIndex < 0)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
result = TypeCast::stringToUint64(jsonString.substring(startIndex, endIndex));
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool getPeerSessionKey(const String &jsonString, uint64_t &result)
|
bool getPeerSessionKey(const String &jsonString, uint64_t &result)
|
||||||
{
|
{
|
||||||
int32_t startIndex = getStartIndex(jsonString, FPSTR(jsonPeerSessionKey));
|
return decodeRadix(jsonString, FPSTR(jsonPeerSessionKey), result);
|
||||||
if(startIndex < 0)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
int32_t endIndex = getEndIndex(jsonString, startIndex);
|
|
||||||
if(endIndex < 0)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
result = TypeCast::stringToUint64(jsonString.substring(startIndex, endIndex));
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool getPeerStaMac(const String &jsonString, uint8_t *resultArray)
|
bool getPeerStaMac(const String &jsonString, uint8_t *resultArray)
|
||||||
{
|
{
|
||||||
int32_t startIndex = getStartIndex(jsonString, FPSTR(jsonPeerStaMac));
|
return getMac(jsonString, FPSTR(jsonPeerStaMac), resultArray);
|
||||||
if(startIndex < 0)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
int32_t endIndex = getEndIndex(jsonString, startIndex);
|
|
||||||
if(endIndex < 0 || endIndex - startIndex != 12) // Mac String is always 12 characters long
|
|
||||||
return false;
|
|
||||||
|
|
||||||
TypeCast::stringToMac(jsonString.substring(startIndex, endIndex), resultArray);
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool getPeerApMac(const String &jsonString, uint8_t *resultArray)
|
bool getPeerApMac(const String &jsonString, uint8_t *resultArray)
|
||||||
{
|
{
|
||||||
int32_t startIndex = getStartIndex(jsonString, FPSTR(jsonPeerApMac));
|
return getMac(jsonString, FPSTR(jsonPeerApMac), resultArray);
|
||||||
if(startIndex < 0)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
int32_t endIndex = getEndIndex(jsonString, startIndex);
|
|
||||||
if(endIndex < 0 || endIndex - startIndex != 12) // Mac String is always 12 characters long
|
|
||||||
return false;
|
|
||||||
|
|
||||||
TypeCast::stringToMac(jsonString.substring(startIndex, endIndex), resultArray);
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool getDuration(const String &jsonString, uint32_t &result)
|
bool getDuration(const String &jsonString, uint32_t &result)
|
||||||
{
|
{
|
||||||
int32_t startIndex = getStartIndex(jsonString, FPSTR(jsonDuration));
|
return decode(jsonString, FPSTR(jsonDuration), result);
|
||||||
if(startIndex < 0)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
result = strtoul(jsonString.substring(startIndex).c_str(), nullptr, 0); // strtoul stops reading input when an invalid character is discovered.
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool getNonce(const String &jsonString, String &result)
|
bool getNonce(const String &jsonString, String &result)
|
||||||
{
|
{
|
||||||
int32_t startIndex = getStartIndex(jsonString, FPSTR(jsonNonce));
|
return decode(jsonString, FPSTR(jsonNonce), result);
|
||||||
if(startIndex < 0)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
int32_t endIndex = getEndIndex(jsonString, startIndex);
|
|
||||||
if(endIndex < 0)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
result = jsonString.substring(startIndex, endIndex);
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool getHmac(const String &jsonString, String &result)
|
bool getHmac(const String &jsonString, String &result)
|
||||||
{
|
{
|
||||||
int32_t startIndex = getStartIndex(jsonString, FPSTR(jsonHmac));
|
return decode(jsonString, FPSTR(jsonHmac), result);
|
||||||
if(startIndex < 0)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
int32_t endIndex = getEndIndex(jsonString, startIndex);
|
|
||||||
if(endIndex < 0)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
result = jsonString.substring(startIndex, endIndex);
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool getDesync(const String &jsonString, bool &result)
|
bool getDesync(const String &jsonString, bool &result)
|
||||||
{
|
{
|
||||||
int32_t startIndex = getStartIndex(jsonString, FPSTR(jsonDesync));
|
String jsonValue;
|
||||||
if(startIndex < 0)
|
bool decoded = decode(jsonString, FPSTR(jsonDesync), jsonValue);
|
||||||
return false;
|
|
||||||
|
|
||||||
result = bool(strtoul(jsonString.substring(startIndex).c_str(), nullptr, 0)); // strtoul stops reading input when an invalid character is discovered.
|
if(decoded)
|
||||||
return true;
|
result = bool(strtoul(jsonValue.c_str(), nullptr, 0)); // strtoul stops reading input when an invalid character is discovered.
|
||||||
|
|
||||||
|
return decoded;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool getUnsynchronizedMessageID(const String &jsonString, uint32_t &result)
|
bool getUnsynchronizedMessageID(const String &jsonString, uint32_t &result)
|
||||||
{
|
{
|
||||||
int32_t startIndex = getStartIndex(jsonString, FPSTR(jsonUnsynchronizedMessageID));
|
return decode(jsonString, FPSTR(jsonUnsynchronizedMessageID), result);
|
||||||
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)
|
bool getMeshMessageCount(const String &jsonString, uint16_t &result)
|
||||||
{
|
{
|
||||||
int32_t startIndex = getStartIndex(jsonString, FPSTR(jsonMeshMessageCount));
|
uint32_t longResult = 0;
|
||||||
if(startIndex < 0)
|
bool decoded = decode(jsonString, FPSTR(jsonMeshMessageCount), longResult);
|
||||||
return false;
|
|
||||||
|
|
||||||
uint32_t longResult = strtoul(jsonString.substring(startIndex).c_str(), nullptr, 0); // strtoul stops reading input when an invalid character is discovered.
|
if(longResult > 65535) // Must fit within uint16_t
|
||||||
assert(longResult <= 65535); // Must fit within uint16_t
|
decoded = false;
|
||||||
|
|
||||||
|
if(decoded)
|
||||||
result = longResult;
|
result = longResult;
|
||||||
return true;
|
|
||||||
|
return decoded;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -26,34 +26,29 @@
|
|||||||
#define __ESPNOWJSONTRANSLATOR_H__
|
#define __ESPNOWJSONTRANSLATOR_H__
|
||||||
|
|
||||||
#include <WString.h>
|
#include <WString.h>
|
||||||
|
#include <initializer_list>
|
||||||
|
|
||||||
namespace JsonTranslator
|
namespace JsonTranslator
|
||||||
{
|
{
|
||||||
constexpr char jsonConnectionState[] PROGMEM = "{\"connectionState\":{";
|
constexpr char jsonConnectionState[] PROGMEM = "connectionState";
|
||||||
constexpr char jsonPassword[] PROGMEM = "\"password\":";
|
constexpr char jsonMeshState[] PROGMEM = "meshState";
|
||||||
constexpr char jsonOwnSessionKey[] PROGMEM = "\"ownSK\":";
|
constexpr char jsonPassword[] PROGMEM = "password";
|
||||||
constexpr char jsonPeerSessionKey[] PROGMEM = "\"peerSK\":";
|
constexpr char jsonOwnSessionKey[] PROGMEM = "ownSK";
|
||||||
constexpr char jsonPeerStaMac[] PROGMEM = "\"peerStaMac\":";
|
constexpr char jsonPeerSessionKey[] PROGMEM = "peerSK";
|
||||||
constexpr char jsonPeerApMac[] PROGMEM = "\"peerApMac\":";
|
constexpr char jsonPeerStaMac[] PROGMEM = "peerStaMac";
|
||||||
constexpr char jsonDuration[] PROGMEM = "\"duration\":";
|
constexpr char jsonPeerApMac[] PROGMEM = "peerApMac";
|
||||||
constexpr char jsonNonce[] PROGMEM = "\"nonce\":";
|
constexpr char jsonDuration[] PROGMEM = "duration";
|
||||||
constexpr char jsonHmac[] PROGMEM = "\"hmac\":";
|
constexpr char jsonNonce[] PROGMEM = "nonce";
|
||||||
constexpr char jsonDesync[] PROGMEM = "\"desync\":";
|
constexpr char jsonHmac[] PROGMEM = "hmac";
|
||||||
constexpr char jsonUnsynchronizedMessageID[] PROGMEM = "\"unsyncMsgID\":";
|
constexpr char jsonDesync[] PROGMEM = "desync";
|
||||||
constexpr char jsonMeshMessageCount[] PROGMEM = "\"meshMsgCount\":";
|
constexpr char jsonUnsynchronizedMessageID[] PROGMEM = "unsyncMsgID";
|
||||||
|
constexpr char jsonMeshMessageCount[] PROGMEM = "meshMsgCount";
|
||||||
|
constexpr char jsonArguments[] PROGMEM = "arguments";
|
||||||
|
|
||||||
String createJsonPair(const String &valueIdentifier, const String &value);
|
|
||||||
String createJsonEndPair(const String &valueIdentifier, const String &value);
|
|
||||||
|
|
||||||
String createEncryptedConnectionInfo(const String &infoHeader, const String &requestNonce, const String &authenticationPassword, const uint64_t ownSessionKey, const uint64_t peerSessionKey);
|
|
||||||
String createEncryptionRequestIntro(const String &requestHeader, const uint32_t duration = 0);
|
|
||||||
String createEncryptionRequestEnding(const String &requestNonce);
|
|
||||||
String createEncryptionRequestHmacMessage(const String &requestHeader, const String &requestNonce, const uint8_t *hashKey, const uint8_t hashKeyLength, const uint32_t duration = 0);
|
|
||||||
|
|
||||||
bool verifyEncryptionRequestHmac(const String &encryptionRequestHmacMessage, const uint8_t *requesterStaMac, const uint8_t *requesterApMac, const uint8_t *hashKey, const uint8_t hashKeyLength);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Provides the index within jsonString where the value of valueIdentifier starts.
|
* Provides the index within jsonString where the value of valueIdentifier starts.
|
||||||
|
* Note that including " within a JSON string value will result in errors.
|
||||||
*
|
*
|
||||||
* @param jsonString The String to search within.
|
* @param jsonString The String to search within.
|
||||||
* @param valueIdentifier The identifier to search for.
|
* @param valueIdentifier The identifier to search for.
|
||||||
@ -64,15 +59,87 @@ namespace JsonTranslator
|
|||||||
int32_t getStartIndex(const String &jsonString, const String &valueIdentifier, const int32_t searchStartIndex = 0);
|
int32_t getStartIndex(const String &jsonString, const String &valueIdentifier, const int32_t searchStartIndex = 0);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Provides the index within jsonString where the next JSON termination character (',' or '}') is found, starting from searchStartIndex.
|
* Provides the index within jsonString where the JSON object or JSON string value ends, starting the search from searchStartIndex.
|
||||||
|
* Note that including " within a JSON string value will result in errors.
|
||||||
|
*
|
||||||
|
* The character at searchStartIndex must be either " (for a string) or { (for an object), otherwise the search fails.
|
||||||
*
|
*
|
||||||
* @param jsonString The String to search within.
|
* @param jsonString The String to search within.
|
||||||
* @param searchStartIndex The index of jsonString where the search will start.
|
* @param searchStartIndex The index of jsonString where the search will start. The index position should contain either " or {.
|
||||||
*
|
*
|
||||||
* @return An int32_t containing the index within jsonString where the next JSON termination character is found, or a negative value if no such character was found.
|
* @return An int32_t containing the index within jsonString where the JSON string/object ends, or a negative value if no such character was found.
|
||||||
*/
|
*/
|
||||||
int32_t getEndIndex(const String &jsonString, const int32_t searchStartIndex);
|
int32_t getEndIndex(const String &jsonString, const int32_t searchStartIndex);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Create a JSON String based on the identifiers and values given.
|
||||||
|
*
|
||||||
|
* Assumes all values are either strings or JSON objects. A value is interpreted as a JSON object if it starts with {
|
||||||
|
* Assumes all identifiers are strings.
|
||||||
|
*
|
||||||
|
* @param identifiersAndValues Any even number of String arguments. It is assumed that the identifiers and values are given in an alternating manner, as in encode({Identifier1, Value1, Identifier2, Value2, ...})
|
||||||
|
*/
|
||||||
|
String encode(std::initializer_list<String> identifiersAndValues);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Create a JSON String based on the identifiers and values given.
|
||||||
|
*
|
||||||
|
* Does not make any assumptions regarding value types. " must be added manually around string values.
|
||||||
|
* Useful for example if your JSON values can contain starting { characters, since the regular encode() will then interpret them as JSON objects.
|
||||||
|
* Assumes all identifiers are strings.
|
||||||
|
*
|
||||||
|
* @param identifiersAndValues Any even number of String arguments. It is assumed that the identifiers and values are given in an alternating manner, as in encodeLiterally({Identifier1, Value1, Identifier2, Value2, ...})
|
||||||
|
*/
|
||||||
|
String encodeLiterally(std::initializer_list<String> identifiersAndValues);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Get a value from a JSON String.
|
||||||
|
* Assumes all values are either JSON strings ( starting with " ) or JSON objects ( starting with { ).
|
||||||
|
*
|
||||||
|
* Note that including " within a JSON string value will result in errors.
|
||||||
|
* Escape characters are not supported at this moment, since we do not want string length modification to occur during ESP-NOW protocol transmissions.
|
||||||
|
*
|
||||||
|
* @param jsonString The String to search within.
|
||||||
|
* @param valueIdentifier The identifier to search for.
|
||||||
|
* @param value The String variable to put the result in.
|
||||||
|
*
|
||||||
|
* @return True if a value was found. False otherwise. The value argument is not modified if false is returned.
|
||||||
|
*/
|
||||||
|
bool decode(const String &jsonString, const String &valueIdentifier, String &value);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Get a value from a JSON String.
|
||||||
|
* Assumes all values are stored as strings in standard C-format (i.e. decimal by default).
|
||||||
|
*
|
||||||
|
* Note that including " within a JSON string value will result in errors.
|
||||||
|
* Escape characters are not supported at this moment, since we do not want string length modification to occur during ESP-NOW protocol transmissions.
|
||||||
|
*
|
||||||
|
* @param jsonString The String to search within.
|
||||||
|
* @param valueIdentifier The identifier to search for.
|
||||||
|
* @param value The uint32_t variable to put the result in.
|
||||||
|
*
|
||||||
|
* @return True if a value was found. False otherwise. The value argument is not modified if false is returned.
|
||||||
|
*/
|
||||||
|
bool decode(const String &jsonString, const String &valueIdentifier, uint32_t &value);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Get a value from a JSON String.
|
||||||
|
* Assumes all values are stored as strings encoded in the specified radix. Hexadecimal encoding is the default.
|
||||||
|
*
|
||||||
|
* Note that including " within a JSON string value will result in errors.
|
||||||
|
* Escape characters are not supported at this moment, since we do not want string length modification to occur during ESP-NOW protocol transmissions.
|
||||||
|
*
|
||||||
|
* @param jsonString The String to search within.
|
||||||
|
* @param valueIdentifier The identifier to search for.
|
||||||
|
* @param value The uint64_t variable to put the result in.
|
||||||
|
* @param radix The base to use when converting the string value to uint64_t. Must be between 2 and 36.
|
||||||
|
*
|
||||||
|
* @return True if a value was found. False otherwise. The value argument is not modified if false is returned.
|
||||||
|
*/
|
||||||
|
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);
|
bool getConnectionState(const String &jsonString, String &result);
|
||||||
/**
|
/**
|
||||||
* Stores the value of the password field within jsonString into the result variable.
|
* Stores the value of the password field within jsonString into the result variable.
|
||||||
|
@ -203,6 +203,7 @@ String MeshBackendBase::getNodeID() const {return getSSIDSuffix();}
|
|||||||
void MeshBackendBase::setMeshPassword(const String &newMeshPassword)
|
void MeshBackendBase::setMeshPassword(const String &newMeshPassword)
|
||||||
{
|
{
|
||||||
assert(8 <= newMeshPassword.length() && newMeshPassword.length() <= 64); // Limited by the ESP8266 API.
|
assert(8 <= newMeshPassword.length() && newMeshPassword.length() <= 64); // Limited by the ESP8266 API.
|
||||||
|
assert(newMeshPassword.indexOf('"') == -1); // " is not allowed in passwords to allow for easier JSON parsing and predictable password length (no need for extra escape characters).
|
||||||
|
|
||||||
_meshPassword = newMeshPassword;
|
_meshPassword = newMeshPassword;
|
||||||
|
|
||||||
|
@ -194,7 +194,7 @@ public:
|
|||||||
* Will also change the setting for the active AP (via an AP restart)
|
* Will also change the setting for the active AP (via an AP restart)
|
||||||
* if this MeshBackendBase instance is the current AP controller.
|
* if this MeshBackendBase instance is the current AP controller.
|
||||||
*
|
*
|
||||||
* @param newMeshPassword The password to use.
|
* @param newMeshPassword The password to use. Must be between 8 and 64 characters long. " is an illegal character because of JSON parsing requirements.
|
||||||
*/
|
*/
|
||||||
void setMeshPassword(const String &newMeshPassword);
|
void setMeshPassword(const String &newMeshPassword);
|
||||||
String getMeshPassword() const;
|
String getMeshPassword() const;
|
||||||
|
122
libraries/ESP8266WiFiMesh/src/Serializer.cpp
Normal file
122
libraries/ESP8266WiFiMesh/src/Serializer.cpp
Normal file
@ -0,0 +1,122 @@
|
|||||||
|
/*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "Serializer.h"
|
||||||
|
#include "JsonTranslator.h"
|
||||||
|
#include "TypeConversionFunctions.h"
|
||||||
|
#include "MeshCryptoInterface.h"
|
||||||
|
|
||||||
|
namespace
|
||||||
|
{
|
||||||
|
namespace TypeCast = MeshTypeConversionFunctions;
|
||||||
|
|
||||||
|
String createJsonEndPair(const String &valueIdentifier, const String &value)
|
||||||
|
{
|
||||||
|
const String q = String('"');
|
||||||
|
return q + valueIdentifier + q + ':' + q + value + F("\"}}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace Serializer
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
* NOTE: The internal states may be changed in future updates, so the function signatures here are not guaranteed to be stable.
|
||||||
|
*/
|
||||||
|
|
||||||
|
String serializeMeshState(const String &unsyncMsgID, const String &meshMsgCount)
|
||||||
|
{
|
||||||
|
using namespace JsonTranslator;
|
||||||
|
|
||||||
|
// Returns: {"meshState":{"connectionState":{"unsyncMsgID":"123"},"meshMsgCount":"123"}}
|
||||||
|
return encode({FPSTR(jsonMeshState), encode({FPSTR(jsonConnectionState), encode({FPSTR(jsonUnsynchronizedMessageID), unsyncMsgID}), FPSTR(jsonMeshMessageCount), meshMsgCount})});
|
||||||
|
}
|
||||||
|
|
||||||
|
String serializeUnencryptedConnection(const String &unsyncMsgID)
|
||||||
|
{
|
||||||
|
using namespace JsonTranslator;
|
||||||
|
|
||||||
|
// Returns: {"connectionState":{"unsyncMsgID":"123"}}
|
||||||
|
return encode({FPSTR(jsonConnectionState), encode({FPSTR(jsonUnsynchronizedMessageID), unsyncMsgID})});
|
||||||
|
}
|
||||||
|
|
||||||
|
String serializeEncryptedConnection(const String &duration, const String &desync, const String &ownSK, const String &peerSK, const String &peerStaMac, const String &peerApMac)
|
||||||
|
{
|
||||||
|
using namespace JsonTranslator;
|
||||||
|
|
||||||
|
if(duration.isEmpty())
|
||||||
|
{
|
||||||
|
// Returns: {"connectionState":{"desync":"0","ownSK":"1A2","peerSK":"3B4","peerStaMac":"F2","peerApMac":"E3"}}
|
||||||
|
return encode({FPSTR(jsonConnectionState), encode({FPSTR(jsonDesync), desync, FPSTR(jsonOwnSessionKey), ownSK, FPSTR(jsonPeerSessionKey), peerSK,
|
||||||
|
FPSTR(jsonPeerStaMac), peerStaMac, FPSTR(jsonPeerApMac), peerApMac})});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns: {"connectionState":{"duration":"123","desync":"0","ownSK":"1A2","peerSK":"3B4","peerStaMac":"F2","peerApMac":"E3"}}
|
||||||
|
return encode({FPSTR(jsonConnectionState), encode({FPSTR(jsonDuration), duration, FPSTR(jsonDesync), desync, FPSTR(jsonOwnSessionKey), ownSK, FPSTR(jsonPeerSessionKey), peerSK,
|
||||||
|
FPSTR(jsonPeerStaMac), peerStaMac, FPSTR(jsonPeerApMac), peerApMac})});
|
||||||
|
}
|
||||||
|
|
||||||
|
String createEncryptedConnectionInfo(const String &infoHeader, const String &requestNonce, const String &authenticationPassword, const uint64_t ownSessionKey, const uint64_t peerSessionKey)
|
||||||
|
{
|
||||||
|
using namespace JsonTranslator;
|
||||||
|
|
||||||
|
const String q = String('"');
|
||||||
|
|
||||||
|
// Returns: infoHeader{"arguments":{"nonce":"1F2","password":"abc","ownSK":"3B4","peerSK":"1A2"}}
|
||||||
|
return
|
||||||
|
infoHeader +
|
||||||
|
encode({FPSTR(jsonArguments),
|
||||||
|
encodeLiterally({FPSTR(jsonNonce), q + requestNonce + q,
|
||||||
|
FPSTR(jsonPassword), q + authenticationPassword + q,
|
||||||
|
FPSTR(jsonOwnSessionKey), q + TypeCast::uint64ToString(peerSessionKey) + q, // Exchanges session keys since it should be valid for the receiver.
|
||||||
|
FPSTR(jsonPeerSessionKey), q + TypeCast::uint64ToString(ownSessionKey) + q})});
|
||||||
|
}
|
||||||
|
|
||||||
|
String createEncryptionRequestHmacMessage(const String &requestHeader, const String &requestNonce, const uint8_t *hashKey, const uint8_t hashKeyLength, const uint32_t duration)
|
||||||
|
{
|
||||||
|
using namespace JsonTranslator;
|
||||||
|
|
||||||
|
String mainMessage = requestHeader;
|
||||||
|
|
||||||
|
if(requestHeader == FPSTR(EspnowProtocolInterpreter::temporaryEncryptionRequestHeader))
|
||||||
|
{
|
||||||
|
mainMessage += encode({FPSTR(jsonArguments), encode({FPSTR(jsonDuration), String(duration), FPSTR(jsonNonce), requestNonce})});
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
mainMessage += encode({FPSTR(jsonArguments), encode({FPSTR(jsonNonce), requestNonce})});
|
||||||
|
}
|
||||||
|
|
||||||
|
// We need to have an open JSON object so we can add the HMAC later.
|
||||||
|
mainMessage.remove(mainMessage.length() - 2);
|
||||||
|
mainMessage += ',';
|
||||||
|
|
||||||
|
uint8_t staMac[6] {0};
|
||||||
|
uint8_t apMac[6] {0};
|
||||||
|
String requesterStaApMac = TypeCast::macToString(WiFi.macAddress(staMac)) + TypeCast::macToString(WiFi.softAPmacAddress(apMac));
|
||||||
|
String hmac = MeshCryptoInterface::createMeshHmac(requesterStaApMac + mainMessage, hashKey, hashKeyLength);
|
||||||
|
|
||||||
|
// Returns: requestHeader{"arguments":{"duration":"123","nonce":"1F2","hmac":"3B4"}}
|
||||||
|
return mainMessage + createJsonEndPair(FPSTR(jsonHmac), hmac);
|
||||||
|
}
|
||||||
|
}
|
44
libraries/ESP8266WiFiMesh/src/Serializer.h
Normal file
44
libraries/ESP8266WiFiMesh/src/Serializer.h
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
/*
|
||||||
|
* 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 __ESP8266MESHSERIALIZER_H__
|
||||||
|
#define __ESP8266MESHSERIALIZER_H__
|
||||||
|
|
||||||
|
#include <WString.h>
|
||||||
|
|
||||||
|
namespace Serializer
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
* NOTE: The internal states may be changed in future updates, so the function signatures here are not guaranteed to be stable.
|
||||||
|
*/
|
||||||
|
|
||||||
|
String serializeMeshState(const String &unsyncMsgID, const String &meshMsgCount);
|
||||||
|
String serializeUnencryptedConnection(const String &unsyncMsgID);
|
||||||
|
String serializeEncryptedConnection(const String &duration, const String &desync, const String &ownSK, const String &peerSK, const String &peerStaMac, const String &peerApMac);
|
||||||
|
|
||||||
|
String createEncryptedConnectionInfo(const String &infoHeader, const String &requestNonce, const String &authenticationPassword, const uint64_t ownSessionKey, const uint64_t peerSessionKey);
|
||||||
|
String createEncryptionRequestHmacMessage(const String &requestHeader, const String &requestNonce, const uint8_t *hashKey, const uint8_t hashKeyLength, const uint32_t duration = 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
@ -42,7 +42,7 @@ namespace MeshTypeConversionFunctions
|
|||||||
* @param base The radix to convert "number" into. Must be between 2 and 36.
|
* @param base The radix to convert "number" into. Must be between 2 and 36.
|
||||||
* @return A string of "number" encoded in radix "base".
|
* @return A string of "number" encoded in radix "base".
|
||||||
*/
|
*/
|
||||||
String uint64ToString(uint64_t number, const byte base = 16);
|
String uint64ToString(uint64_t number, const uint8_t base = 16);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Note that using base 10 instead of 16 increases conversion time by roughly a factor of 2, due to unfavourable 64-bit arithmetic.
|
* Note that using base 10 instead of 16 increases conversion time by roughly a factor of 2, due to unfavourable 64-bit arithmetic.
|
||||||
@ -52,7 +52,7 @@ namespace MeshTypeConversionFunctions
|
|||||||
* @param base The radix of "string". Must be between 2 and 36.
|
* @param base The radix of "string". Must be between 2 and 36.
|
||||||
* @return A uint64_t of the string, using radix "base" during decoding.
|
* @return A uint64_t of the string, using radix "base" during decoding.
|
||||||
*/
|
*/
|
||||||
uint64_t stringToUint64(const String &string, const byte base = 16);
|
uint64_t stringToUint64(const String &string, const uint8_t base = 16);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Convert the contents of a uint8_t array to a String in HEX format. The resulting String starts from index 0 of the array.
|
* Convert the contents of a uint8_t array to a String in HEX format. The resulting String starts from index 0 of the array.
|
||||||
|
Loading…
x
Reference in New Issue
Block a user