Sesame-Device/src/controller.cpp
2023-08-09 15:02:24 +02:00

218 lines
7.2 KiB
C++

#include "controller.h"
#include "crypto.h"
#include "config.h"
#include <WiFi.h>
// The url parameter to send the message to the local server
constexpr char messageUrlParameter[] = "m";
SesameController::SesameController(uint16_t localWebServerPort, uint8_t remoteDeviceCount) :
storage(remoteDeviceCount), localWebServer(localWebServerPort) {
// Set up response buffer
responseStatus = (SesameEvent*) responseBuffer;
responseMessage = (AuthenticatedMessage*) (responseBuffer + 1);
}
void SesameController::configure(ServoConfiguration servoConfig, ServerConfiguration serverConfig, TimeConfiguration timeConfig, WifiConfiguration wifiConfig, KeyConfiguration keyConfig) {
this->wifiConfig = wifiConfig;
this->keyConfig = keyConfig;
// Prepare EEPROM for reading and writing
storage.configure();
Serial.println("[INFO] Storage configured");
servo.configure(servoConfig);
Serial.println("[INFO] Servo configured");
// Direct messages and errors over the websocket to the controller
server.configure(serverConfig, this);
Serial.println("[INFO] Server connection configured");
timeCheck.configure(timeConfig);
// Direct messages from the local web server to the controller
localWebServer.on("/message", HTTP_POST, [this] (AsyncWebServerRequest *request) {
this->handleLocalMessage(request);
this->sendPreparedLocalResponse(request);
});
Serial.println("[INFO] Local web server configured");
//storage.resetMessageCounters();
storage.printMessageCounters();
}
void SesameController::loop(uint32_t millis) {
server.loop();
servo.loop(millis);
ensureWiFiConnection(millis);
ensureWebSocketConnection();
}
// MARK: Local
void
SesameController::handleLocalMessage(AsyncWebServerRequest *request) {
if (!request->hasParam(messageUrlParameter)) {
Serial.println("Missing url parameter");
prepareResponseBuffer(SesameEvent::InvalidUrlParameter);
return;
}
String encoded = request->getParam(messageUrlParameter)->value();
if (!convertHexMessageToBinary(encoded.c_str())) {
Serial.println("Invalid hex encoding");
prepareResponseBuffer(SesameEvent::InvalidMessageSize);
return;
}
processMessage((AuthenticatedMessage*) receivedMessageBuffer);
}
void SesameController::sendPreparedLocalResponse(AsyncWebServerRequest *request) {
request->send_P(200, "application/octet-stream", responseBuffer, responseSize);
Serial.printf("[INFO] Local response %u (%u bytes)\n", responseBuffer[0], responseSize);
}
// MARK: Server
void SesameController::sendServerError(SesameEvent event) {
prepareResponseBuffer(event);
sendPreparedServerResponse();
}
void SesameController::handleServerMessage(uint8_t* payload, size_t length) {
if (length != AUTHENTICATED_MESSAGE_SIZE) {
prepareResponseBuffer(SesameEvent::InvalidMessageSize);
return;
}
processMessage((AuthenticatedMessage*) payload);
sendPreparedServerResponse();
}
void SesameController::sendPreparedServerResponse() {
server.sendResponse(responseBuffer, responseSize);
Serial.printf("[INFO] Server response %u (%u bytes)\n", responseBuffer[0], responseSize);
}
// MARK: Message handling
void SesameController::processMessage(AuthenticatedMessage* message) {
SesameEvent event = verifyAndProcessReceivedMessage(message);
prepareResponseBuffer(event, message->message.device);
}
/**
* Process a received message.
*
* Checks whether the received data is a valid,
* and then signals that the motor should move.
*
* @param message The message received from the remote
* @return The response to signal to the server.
*/
SesameEvent SesameController::verifyAndProcessReceivedMessage(AuthenticatedMessage* message) {
if (!isAuthenticMessage(message, keyConfig.remoteKey, keySize)) {
return SesameEvent::MessageAuthenticationFailed;
}
if (!storage.isDeviceIdValid(message->message.device)) {
return SesameEvent::MessageDeviceInvalid;
}
if (!storage.isMessageCounterValid(message->message.id, message->message.device)) {
return SesameEvent::MessageCounterInvalid;
}
if (!timeCheck.isMessageTimeAcceptable(message->message.time)) {
return SesameEvent::MessageTimeMismatch;
}
storage.didUseMessageCounter(message->message.id, message->message.device);
// Move servo
servo.pressButton();
Serial.printf("[Info] Accepted message %d\n", message->message.id);
return SesameEvent::MessageAccepted;
}
bool allowMessageResponse(SesameEvent event) {
switch (event) {
case SesameEvent::MessageTimeMismatch:
case SesameEvent::MessageCounterInvalid:
case SesameEvent::MessageAccepted:
case SesameEvent::MessageDeviceInvalid:
return true;
default:
return false;
}
}
uint16_t SesameController::prepareResponseBuffer(SesameEvent event, uint8_t deviceId) {
*responseStatus = event;
if (!allowMessageResponse(event)) {
return 1;
}
responseMessage->message.time = timeCheck.getEpochTime();
responseMessage->message.id = storage.getNextMessageCounter(deviceId);
responseMessage->message.device = deviceId;
if (!authenticateMessage(responseMessage, keyConfig.localKey, keySize)) {
*responseStatus = SesameEvent::InvalidResponseAuthentication;
return 1;
}
return 1 + AUTHENTICATED_MESSAGE_SIZE;
}
// MARK: Reconnecting
void SesameController::ensureWiFiConnection(uint32_t millis) {
static uint32_t nextWifiReconnect = 0;
// Reconnect to WiFi
if(millis > nextWifiReconnect && WiFi.status() != WL_CONNECTED) {
Serial.println("[INFO] Reconnecting WiFi...");
WiFi.setHostname(wifiConfig.networkName);
WiFi.begin(wifiConfig.ssid, wifiConfig.password);
isReconnecting = true;
nextWifiReconnect = millis + wifiConfig.reconnectInterval;
}
}
void SesameController::ensureWebSocketConnection() {
if (isReconnecting && WiFi.status() == WL_CONNECTED) {
isReconnecting = false;
Serial.print("WiFi IP address: ");
Serial.println(WiFi.localIP());
server.connect();
timeCheck.startNTP();
timeCheck.printLocalTime();
localWebServer.begin();
}
}
// MARK: Helper
// Based on https://stackoverflow.com/a/23898449/266720
bool SesameController::convertHexMessageToBinary(const char* str) {
// TODO: Fail if invalid hex values are used
uint8_t idx0, idx1;
// mapping of ASCII characters to hex values
const uint8_t hashmap[] = {
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, // 01234567
0x08, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 89:;<=>?
0x00, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x00, // @ABCDEFG
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // HIJKLMNO
};
size_t len = strlen(str);
if (len != AUTHENTICATED_MESSAGE_SIZE * 2) {
// Require exact message size
return false;
}
for (size_t pos = 0; pos < len; pos += 2) {
idx0 = ((uint8_t)str[pos+0] & 0x1F) ^ 0x10;
idx1 = ((uint8_t)str[pos+1] & 0x1F) ^ 0x10;
receivedMessageBuffer[pos/2] = (uint8_t)(hashmap[idx0] << 4) | hashmap[idx1];
};
return true;
}