/* * 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 #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 *)¶m->u8)) return false; break; case MESSAGE_PARAM_TYPE_U16: if (!receive(socket, 2, (char *)¶m->u16)) return false; param->u16 = ntohs(param->u16); break; case MESSAGE_PARAM_TYPE_U32: if (!receive(socket, 4, (char *)¶m->u32)) return false; param->u32 = ntohl(param->u32); break; case MESSAGE_PARAM_TYPE_U64: if (!receive(socket, 8, (char *)¶m->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; } }