From b244d2a484adce1445ef721329b4d20a89c3b081 Mon Sep 17 00:00:00 2001 From: Pol Henarejos Date: Fri, 17 Apr 2026 16:40:28 +0200 Subject: [PATCH] Add more support for rest emulation. Signed-off-by: Pol Henarejos --- src/usb/lwip/rest_server.c | 253 +++++++++++++++++++++++++++++++++---- src/usb/lwip/rest_server.h | 32 ++++- 2 files changed, 260 insertions(+), 25 deletions(-) diff --git a/src/usb/lwip/rest_server.c b/src/usb/lwip/rest_server.c index d1fa916..709f990 100644 --- a/src/usb/lwip/rest_server.c +++ b/src/usb/lwip/rest_server.c @@ -1,8 +1,5 @@ #include "rest_server.h" -#include "lwip/tcp.h" -#include "lwip/def.h" - #include #include #include @@ -11,20 +8,44 @@ #include #include +#ifdef ENABLE_EMULATION +#ifndef _MSC_VER +#include +#include +#include +#include +#include +#include +#endif +#else +#include "lwip/tcp.h" +#include "lwip/def.h" +#endif + #define REST_PORT 80 #define REST_MAX_CONNS 4 -#define REST_MAX_REQUEST_SIZE 8192 +#define REST_MAX_REQUEST_SIZE 1024 #define REST_MAX_METHOD_SIZE 8 -#define REST_MAX_PATH_SIZE 192 typedef struct { bool in_use; +#ifdef ENABLE_EMULATION + int sock; +#else struct tcp_pcb *pcb; +#endif char request[REST_MAX_REQUEST_SIZE + 1]; size_t request_len; } rest_conn_t; +#ifndef ENABLE_EMULATION static struct tcp_pcb *listener_pcb = NULL; +#else +static int listener_sock = -1; +#ifndef _MSC_VER +static pthread_t rest_thread; +#endif +#endif static rest_conn_t conns[REST_MAX_CONNS]; __attribute__((weak)) const rest_route_t *rest_get_routes(size_t *count) { @@ -34,13 +55,23 @@ __attribute__((weak)) const rest_route_t *rest_get_routes(size_t *count) { return NULL; } -static rest_conn_t *alloc_conn(struct tcp_pcb *pcb) { +static rest_conn_t *alloc_conn( +#ifdef ENABLE_EMULATION + int sock +#else + struct tcp_pcb *pcb +#endif +) { size_t i; for (i = 0; i < REST_MAX_CONNS; i++) { if (!conns[i].in_use) { memset(&conns[i], 0, sizeof(conns[i])); conns[i].in_use = true; +#ifdef ENABLE_EMULATION + conns[i].sock = sock; +#else conns[i].pcb = pcb; +#endif return &conns[i]; } } @@ -55,6 +86,17 @@ static void clear_conn(rest_conn_t *conn) { } static void close_conn(rest_conn_t *conn) { +#ifdef ENABLE_EMULATION + if (conn == NULL) { + return; + } + if (conn->sock >= 0) { +#ifndef _MSC_VER + (void)close(conn->sock); +#endif + } + clear_conn(conn); +#else err_t err; if (conn == NULL || conn->pcb == NULL) { clear_conn(conn); @@ -70,13 +112,25 @@ static void close_conn(rest_conn_t *conn) { tcp_abort(conn->pcb); } clear_conn(conn); +#endif } static void send_response(rest_conn_t *conn, int status_code, const char *status_text, const char *content_type, const char *body, size_t body_len) { char headers[256]; int header_len; +#ifdef ENABLE_EMULATION + size_t sent_total = 0; +#else err_t err; - if (conn == NULL || conn->pcb == NULL) { +#endif + if ( + conn == NULL +#ifdef ENABLE_EMULATION + || conn->sock < 0 +#else + || conn->pcb == NULL +#endif + ) { return; } header_len = snprintf(headers, sizeof(headers), @@ -90,6 +144,33 @@ static void send_response(rest_conn_t *conn, int status_code, const char *status close_conn(conn); return; } +#ifdef ENABLE_EMULATION + while (sent_total < (size_t)header_len) { +#ifndef _MSC_VER + ssize_t n = send(conn->sock, headers + sent_total, (size_t)header_len - sent_total, 0); +#else + int n = -1; +#endif + if (n <= 0) { + close_conn(conn); + return; + } + sent_total += (size_t)n; + } + sent_total = 0; + while (sent_total < body_len) { +#ifndef _MSC_VER + ssize_t n = send(conn->sock, body + sent_total, body_len - sent_total, 0); +#else + int n = -1; +#endif + if (n <= 0) { + close_conn(conn); + return; + } + sent_total += (size_t)n; + } +#else err = tcp_write(conn->pcb, headers, (uint16_t)header_len, TCP_WRITE_FLAG_COPY); if (err == ERR_OK && body_len > 0) { err = tcp_write(conn->pcb, body, (uint16_t)body_len, TCP_WRITE_FLAG_COPY); @@ -97,6 +178,7 @@ static void send_response(rest_conn_t *conn, int status_code, const char *status if (err == ERR_OK) { (void)tcp_output(conn->pcb); } +#endif close_conn(conn); } @@ -125,7 +207,7 @@ static const char *status_text_from_code(uint16_t code) { } } -static int parse_request(rest_conn_t *conn, rest_http_method_t *method, char *path, size_t path_size, rest_request_t *request) { +static int parse_request(rest_conn_t *conn, rest_request_t *request) { char *header_end, *line_end, *cursor; size_t headers_size; unsigned long content_length = 0; @@ -142,20 +224,20 @@ static int parse_request(rest_conn_t *conn, rest_http_method_t *method, char *pa return -1; } *line_end = '\0'; - if (sscanf(conn->request, "%7s %191s", method_str, path) != 2) { + if (sscanf(conn->request, "%7s %191s", method_str, request->path) != 2) { return -1; } if (strcmp(method_str, "GET") == 0) { - *method = REST_HTTP_GET; + request->method = REST_HTTP_GET; } else if (strcmp(method_str, "POST") == 0) { - *method = REST_HTTP_POST; + request->method = REST_HTTP_POST; } else if (strcmp(method_str, "PUT") == 0) { - *method = REST_HTTP_PUT; + request->method = REST_HTTP_PUT; } else if (strcmp(method_str, "DELETE") == 0) { - *method = REST_HTTP_DELETE; + request->method = REST_HTTP_DELETE; } else { return -1; @@ -210,51 +292,73 @@ static int parse_request(rest_conn_t *conn, rest_http_method_t *method, char *pa return 1; } +static int is_json(const char *content_type) { + if (content_type == NULL) { + return 0; + } + return strncasecmp(content_type, "application/json", 16) == 0; +} + static void handle_request(rest_conn_t *conn) { - rest_http_method_t method; - char path[REST_MAX_PATH_SIZE] = {0}; - rest_request_t request; - rest_response_t response; + rest_request_t request = {0}; + rest_response_t response = {0}; const rest_route_t *routes; size_t route_count = 0, i; bool path_exists_for_other_method = false; int parsed; - parsed = parse_request(conn, &method, path, sizeof(path), &request); + parsed = parse_request(conn, &request); if (parsed <= 0) { if (parsed < 0) { send_json(conn, 400, "Bad Request", "{\"error\":\"bad_request\"}"); } return; } - request.method = method; - request.path = path; + if (request.method == REST_HTTP_POST || request.method == REST_HTTP_PUT) { + if (!is_json(request.content_type)) { + send_json(conn, 415, "Unsupported Media Type", "{\"error\":\"content_type_must_be_application_json\"}"); + return; + } + } routes = rest_get_routes(&route_count); for (i = 0; i < route_count; i++) { if (routes[i].path == NULL || routes[i].handler == NULL) { continue; } - if (strcmp(routes[i].path, path) != 0) { + if (strcmp(routes[i].path, request.path) != 0) { continue; } - if (routes[i].method != method) { + if (routes[i].method != request.method) { path_exists_for_other_method = true; continue; } response.status_code = 200; response.content_type = "application/json"; response.body = "{\"ok\":true}"; - response.body_len = strlen(response.body); + response.json = cJSON_CreateObject(); if (routes[i].handler(&request, &response) != 0) { send_json(conn, 500, "Internal Server Error", "{\"error\":\"internal_error\"}"); + cJSON_Delete(response.json); return; } if (response.content_type == NULL || response.body == NULL) { send_json(conn, 500, "Internal Server Error", "{\"error\":\"invalid_response\"}"); + cJSON_Delete(response.json); return; } + response.body = cJSON_PrintUnformatted(response.json); + cJSON_Delete(response.json); + if (response.body == NULL) { + send_json(conn, 500, "Internal Server Error", "{\"error\":\"internal_error\"}"); + return; + } + response.status_code = (response.status_code == 0) ? 200 : response.status_code; + response.body_len = (response.body_len == 0) ? strlen(response.body) : response.body_len; send_response(conn, response.status_code, status_text_from_code(response.status_code), response.content_type, response.body, response.body_len); + if (response.body) { + free(response.body); + } return; } @@ -265,6 +369,7 @@ static void handle_request(rest_conn_t *conn) { } } +#ifndef ENABLE_EMULATION static err_t rest_recv(void *arg, struct tcp_pcb *pcb, struct pbuf *p, err_t err) { rest_conn_t *conn = (rest_conn_t *)arg; @@ -355,3 +460,105 @@ err_t rest_server_init(void) { tcp_accept(listener_pcb, rest_accept); return ERR_OK; } +#else +static int emulation_rest_port(void) { +#ifndef _MSC_VER + const char *port_env = getenv("PICO_REST_PORT"); + long v; + if (port_env == NULL || *port_env == '\0') { + return REST_PORT; + } + errno = 0; + v = strtol(port_env, NULL, 10); + if (errno != 0 || v < 1 || v > 65535) { + return REST_PORT; + } + return (int)v; +#else + return REST_PORT; +#endif +} + +#ifndef _MSC_VER +static void *rest_emulation_thread(void *arg) { + struct sockaddr_in peer; + (void)arg; + + while (true) { + socklen_t peer_len = sizeof(peer); + int accepted = accept(listener_sock, (struct sockaddr *)&peer, &peer_len); + rest_conn_t *conn; + if (accepted < 0) { + continue; + } + conn = alloc_conn(accepted); + if (conn == NULL) { + (void)close(accepted); + continue; + } + while (conn->in_use) { + ssize_t n = recv(conn->sock, conn->request + conn->request_len, REST_MAX_REQUEST_SIZE - conn->request_len, 0); + if (n <= 0) { + close_conn(conn); + break; + } + conn->request_len += (size_t)n; + if (conn->request_len > REST_MAX_REQUEST_SIZE) { + send_json(conn, 413, "Payload Too Large", "{\"error\":\"payload_too_large\"}"); + break; + } + handle_request(conn); + } + } + return NULL; +} +#endif + +err_t rest_server_init(void) { +#ifndef _MSC_VER + struct sockaddr_in addr; + int one = 1; + int port = emulation_rest_port(); + + if (listener_sock >= 0) { + return ERR_OK; + } + listener_sock = socket(AF_INET, SOCK_STREAM, 0); + if (listener_sock < 0) { + return -1; + } + if (setsockopt(listener_sock, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one)) != 0) { + (void)close(listener_sock); + listener_sock = -1; + return -1; + } + memset(&addr, 0, sizeof(addr)); + addr.sin_family = AF_INET; + addr.sin_port = htons((uint16_t)port); + addr.sin_addr.s_addr = htonl(INADDR_ANY); + if (bind(listener_sock, (struct sockaddr *)&addr, sizeof(addr)) != 0) { + (void)close(listener_sock); + listener_sock = -1; + return -1; + } + if (listen(listener_sock, REST_MAX_CONNS) != 0) { + (void)close(listener_sock); + listener_sock = -1; + return -1; + } + if (pthread_create(&rest_thread, NULL, rest_emulation_thread, NULL) != 0) { + (void)close(listener_sock); + listener_sock = -1; + return -1; + } + (void)pthread_detach(rest_thread); + return ERR_OK; +#else + return -1; +#endif +} + +int lwip_itf_init(void) { + return rest_server_init(); +} +#endif diff --git a/src/usb/lwip/rest_server.h b/src/usb/lwip/rest_server.h index c093568..284d692 100644 --- a/src/usb/lwip/rest_server.h +++ b/src/usb/lwip/rest_server.h @@ -1,9 +1,35 @@ +/* + * This file is part of the Pico Keys SDK distribution (https://github.com/polhenarejos/pico-keys-sdk). + * Copyright (c) 2022 Pol Henarejos. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + + #ifndef PICO_KEYS_REST_SERVER_H #define PICO_KEYS_REST_SERVER_H +#ifdef ENABLE_EMULATION +typedef int err_t; +#define ERR_OK 0 +#else #include "lwip/err.h" +#endif #include #include +#include "cJSON.h" + +#define REST_MAX_PATH_SIZE 192 typedef enum { REST_HTTP_GET = 0, @@ -14,7 +40,7 @@ typedef enum { typedef struct { rest_http_method_t method; - const char *path; + char path[REST_MAX_PATH_SIZE]; const char *body; size_t body_len; const char *content_type; @@ -23,8 +49,9 @@ typedef struct { typedef struct { uint16_t status_code; const char *content_type; - const char *body; + char *body; // heap ! size_t body_len; + cJSON *json; } rest_response_t; typedef int (*rest_route_handler_t)(const rest_request_t *request, rest_response_t *response); @@ -38,5 +65,6 @@ typedef struct { const rest_route_t *rest_get_routes(size_t *count); err_t rest_server_init(void); +int lwip_itf_init(void); #endif