bedside-clock/firmware/components/protocol/protocol_interface.c
2023-07-14 15:50:45 +01:00

245 lines
5.6 KiB
C

/*
* SPDX-License-Identifier: AGPL-3.0-only
* Copyright (c) Camden Dixie O'Brien
*/
#include "protocol_interface.h"
#include "esp_log.h"
#include "lwip/sockets.h"
#include <string.h>
#define TAG "Protocol interface"
#define PORT 3498
static CommandHandler handlers[COMMAND_INSTRUCTION_COUNT];
static Message command;
static Message response;
static char string_buffer[CONFIG_MESSAGE_STRING_BUFFER_SIZE];
static char *string_buffer_free_ptr;
static bool receive(int socket, unsigned n, char *p)
{
int len;
do {
len = recv(socket, p, n, 0);
if (len < 0) {
ESP_LOGE(TAG, "Error reading from socket: %d", errno);
return false;
} else if (len == 0) {
ESP_LOGI(TAG, "Connection closed");
return false;
} else {
n -= len;
p += len;
}
} while (n > 0);
return true;
}
static bool receive_param(int socket, unsigned i)
{
MessageParam *param = &command.params[i];
uint8_t type_buf;
if (!receive(socket, 1, &type_buf))
return false;
// TODO handle invalid param type
param->type = (MessageParamType)type_buf;
uint8_t len, alarm_buf[3];
switch (param->type) {
case MESSAGE_PARAM_TYPE_U8:
if (!receive(socket, 1, (char *)&param->u8))
return false;
break;
case MESSAGE_PARAM_TYPE_U16:
if (!receive(socket, 2, (char *)&param->u16))
return false;
param->u16 = ntohs(param->u16);
break;
case MESSAGE_PARAM_TYPE_U32:
if (!receive(socket, 4, (char *)&param->u32))
return false;
param->u32 = ntohl(param->u32);
break;
case MESSAGE_PARAM_TYPE_U64:
if (!receive(socket, 8, (char *)&param->u64))
return false;
param->u64 = ntohll(param->u64);
break;
case MESSAGE_PARAM_TYPE_STRING:
if (!receive(socket, 1, &len))
return false;
char *buf = alloc_message_string(len);
// TODO handle out-of-memory
if (!receive(socket, len, buf))
return false;
param->string = buf;
break;
case MESSAGE_PARAM_TYPE_ALARM:
if (!receive(socket, 3, alarm_buf))
return false;
param->alarm.time.hour = alarm_buf[0];
param->alarm.time.minute = alarm_buf[1];
for (unsigned i = 0; i < WEEK_DAY_COUNT; ++i)
param->alarm.days[i] = (alarm_buf[2] >> i) & 1;
break;
}
}
static bool receive_command(int socket)
{
char header_buf[2];
if (!receive(socket, 2, header_buf))
return false;
// TODO handle invalid instruction
command.instruction = (CommandInstruction)header_buf[0];
command.param_count = (uint8_t)header_buf[1];
for (unsigned i = 0; i < command.param_count; ++i) {
if (!receive_param(socket, i))
return false;
}
return true;
}
static void send_response(int socket)
{
}
static void drop_message_strings(void)
{
string_buffer_free_ptr = string_buffer;
}
static void handle_command(void)
{
const CommandHandler handler = handlers[command.instruction];
if (handler == NULL) {
ESP_LOGW(
TAG, "Unhandled command, instruction: %u", command.instruction);
return;
}
const ResponseStatus status = handler(&command, &response);
if (status != RESPONSE_STATUS_OK) {
response.status = status;
response.param_count = 0;
}
send_response();
drop_message_strings();
}
void server_task(void *arg)
{
int status;
// Create listening socket
const int listen_socket = socket(AF_INET6, SOCK_STREAM, IPPROTO_IPV6);
if (listen_socket < 0) {
ESP_LOGE(TAG, "Error creating socket: %d", errno);
goto error_no_cleanup;
}
// Configure listening socket
int opt = 1;
setsockopt(listen_socket, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
// Bind socket to port (any address)
struct sockaddr_storage server_addr;
struct sockaddr_in6 *server_addr_ip6
= (struct sockaddr_in6 *)&server_addr;
memset(
&server_addr_ip6->sin6_addr.un, 0,
sizeof(server_addr_ip6->sin6_addr.un));
server_addr_ip6->sin6_family = AF_INET6;
server_addr_ip6->sin6_port = htons(PORT);
status = bind(
listen_socket, (struct sockaddr *)&server_addr, sizeof(server_addr));
if (status != 0) {
ESP_LOGE(TAG, "Error binding socket: %d", errno);
goto error_cleanup_socket;
}
// Listen on socket
status = listen(listen_socket, 1);
if (status != 0) {
ESP_LOGE(TAG, "Error listening on socket: %d", errno);
goto error_cleanup_socket;
}
while (1) {
struct sockaddr_storage client_addr;
socklen_t addr_len = sizeof(client_addr);
// Accept incoming connection
int socket = accept(
listen_socket, (struct sockaddr *)&client_addr, &addr_len);
if (socket < 0) {
ESP_LOGE(TAG, "Error accepting connection: %d", errno);
continue;
}
// Log client address, run command-response loop until
// connection closed.
const char *addr_str
= ((struct sockaddr_in6 *)&client_addr)->sin6_addr;
ESP_LOGI(TAG, "Accepted connection from %s", addr_str);
while (1) {
if (!receive_command())
break;
handle_command();
}
// Cleanup connection
shutdown(socket, 0);
close(socket);
}
error_cleanup_socket:
close(listen_socket);
error_no_cleanup:
vTaskDelete(NULL);
}
void protocol_interface_init(void)
{
memset(handlers, 0, sizeof(handlers));
string_buffer_free = string_buffer;
memset(command, 0, sizeof(command));
memset(response, 0, sizeof(response));
}
void set_command_handler(
CommandInstruction instruction, CommandHandler handler)
{
if (instruction < COMMAND_INSTRUCTION_COUNT) {
if (handlers[instruction] != NULL)
ESP_LOGW(TAG, "Handler #%u overwritten", instruction);
handlers[instruction] = handler;
} else {
ESP_LOGE(TAG, "Invalid instruction passed to %s()", __func__);
}
}
char *alloc_message_string(unsigned length)
{
if (string_buffer_free_ptr + length + 1
< string_buffer + CONFIG_MESSAGE_STRING_BUFFER_SIZE) {
char *s = string_buffer_free_ptr;
string_buffer_free_ptr += length + 1;
s[length] = '\0';
return s;
} else {
return NULL;
}
}