From 61d0f7947b7175104f1294140c3c9be4287e02d1 Mon Sep 17 00:00:00 2001 From: Scott Shawcroft Date: Tue, 14 Jun 2022 17:02:34 -0700 Subject: [PATCH] WIP doesn't flush response before closing --- ports/espressif/background.c | 7 + .../espressif/common-hal/socketpool/Socket.c | 78 ++++++++-- .../common-hal/socketpool/SocketPool.c | 50 +++--- shared-bindings/socketpool/Socket.c | 16 +- shared-bindings/socketpool/Socket.h | 7 + shared-bindings/socketpool/SocketPool.h | 6 + supervisor/shared/web_workflow/web_workflow.c | 146 +++++++++++++++++- supervisor/shared/web_workflow/web_workflow.h | 5 +- 8 files changed, 263 insertions(+), 52 deletions(-) diff --git a/ports/espressif/background.c b/ports/espressif/background.c index 37fe3d5be0..1bf44700be 100644 --- a/ports/espressif/background.c +++ b/ports/espressif/background.c @@ -40,6 +40,9 @@ #include "common-hal/pulseio/PulseIn.h" #endif +#if CIRCUITPY_WEB_WORKFLOW +#include "supervisor/shared/web_workflow/web_workflow.h" +#endif void port_background_task(void) { // Zero delay in case FreeRTOS wants to switch to something else. @@ -47,6 +50,10 @@ void port_background_task(void) { #if CIRCUITPY_PULSEIO pulsein_background(); #endif + + #if CIRCUITPY_WEB_WORKFLOW + supervisor_web_workflow_background(); + #endif } void port_start_background_task(void) { diff --git a/ports/espressif/common-hal/socketpool/Socket.c b/ports/espressif/common-hal/socketpool/Socket.c index 856400a4e7..86944f8b00 100644 --- a/ports/espressif/common-hal/socketpool/Socket.c +++ b/ports/espressif/common-hal/socketpool/Socket.c @@ -37,6 +37,10 @@ #include "components/lwip/lwip/src/include/lwip/sys.h" #include "components/lwip/lwip/src/include/lwip/netdb.h" +#include "esp_log.h" + +static const char *TAG = "socket"; + STATIC socketpool_socket_obj_t *open_socket_handles[CONFIG_LWIP_MAX_SOCKETS]; void socket_user_reset(void) { @@ -62,8 +66,7 @@ bool register_open_socket(socketpool_socket_obj_t *self) { return false; } -socketpool_socket_obj_t *common_hal_socketpool_socket_accept(socketpool_socket_obj_t *self, - uint8_t *ip, uint32_t *port) { +int socketpool_socket_accept(socketpool_socket_obj_t *self, uint8_t *ip, uint32_t *port) { struct sockaddr_in accept_addr; socklen_t socklen = sizeof(accept_addr); int newsoc = -1; @@ -81,7 +84,10 @@ socketpool_socket_obj_t *common_hal_socketpool_socket_accept(socketpool_socket_o newsoc = lwip_accept(self->num, (struct sockaddr *)&accept_addr, &socklen); // In non-blocking mode, fail instead of timing out if (newsoc == -1 && self->timeout_ms == 0) { - mp_raise_OSError(MP_EAGAIN); + if (errno != EAGAIN) { + ESP_LOGE(TAG, "accept failed %d", errno); + } + return -MP_EAGAIN; } } @@ -90,8 +96,17 @@ socketpool_socket_obj_t *common_hal_socketpool_socket_accept(socketpool_socket_o memcpy((void *)ip, (void *)&accept_addr.sin_addr.s_addr, sizeof(accept_addr.sin_addr.s_addr)); *port = accept_addr.sin_port; } else { - mp_raise_OSError(ETIMEDOUT); + return -ETIMEDOUT; } + if (newsoc < 0) { + return -MP_EBADF; + } + return newsoc; +} + +socketpool_socket_obj_t *common_hal_socketpool_socket_accept(socketpool_socket_obj_t *self, + uint8_t *ip, uint32_t *port) { + int newsoc = socketpool_socket_accept(self, ip, port); if (newsoc > 0) { // Create the socket @@ -108,7 +123,7 @@ socketpool_socket_obj_t *common_hal_socketpool_socket_accept(socketpool_socket_o lwip_fcntl(newsoc, F_SETFL, O_NONBLOCK); return sock; } else { - mp_raise_OSError(MP_EBADF); + mp_raise_OSError(-newsoc); return NULL; } } @@ -125,17 +140,22 @@ bool common_hal_socketpool_socket_bind(socketpool_socket_obj_t *self, if (err != 0) { mp_raise_RuntimeError(translate("Cannot set socket options")); } - int result = lwip_bind(self->num, (struct sockaddr *)&bind_addr, sizeof(bind_addr)) == 0; - return result; + int result = lwip_bind(self->num, (struct sockaddr *)&bind_addr, sizeof(bind_addr)); + ESP_LOGE(TAG, "bind result %d", result); + return result == 0; } -void common_hal_socketpool_socket_close(socketpool_socket_obj_t *self) { +void socketpool_socket_close(socketpool_socket_obj_t *self) { self->connected = false; if (self->num >= 0) { - lwip_shutdown(self->num, 0); + lwip_shutdown(self->num, SHUT_RDWR); lwip_close(self->num); self->num = -1; } +} + +void common_hal_socketpool_socket_close(socketpool_socket_obj_t *self) { + socketpool_socket_close(self); // Remove socket record for (size_t i = 0; i < MP_ARRAY_SIZE(open_socket_handles); i++) { if (open_socket_handles[i] == self) { @@ -199,7 +219,9 @@ bool common_hal_socketpool_socket_get_connected(socketpool_socket_obj_t *self) { } bool common_hal_socketpool_socket_listen(socketpool_socket_obj_t *self, int backlog) { - return lwip_listen(self->num, backlog) == 0; + int result = lwip_listen(self->num, backlog); + ESP_LOGE(TAG, "listen result %d", result); + return result == 0; } mp_uint_t common_hal_socketpool_socket_recvfrom_into(socketpool_socket_obj_t *self, @@ -242,7 +264,8 @@ mp_uint_t common_hal_socketpool_socket_recvfrom_into(socketpool_socket_obj_t *se return received; } -mp_uint_t common_hal_socketpool_socket_recv_into(socketpool_socket_obj_t *self, const uint8_t *buf, uint32_t len) { +int socketpool_socket_recv_into(socketpool_socket_obj_t *self, + const uint8_t *buf, uint32_t len) { int received = 0; bool timed_out = false; @@ -261,31 +284,52 @@ mp_uint_t common_hal_socketpool_socket_recv_into(socketpool_socket_obj_t *self, // In non-blocking mode, fail instead of looping if (received == -1 && self->timeout_ms == 0) { - mp_raise_OSError(MP_EAGAIN); + if (errno != EAGAIN) { + ESP_LOGE(TAG, "recv %d", errno); + } + return -MP_EAGAIN; } } } else { - mp_raise_OSError(MP_EBADF); + return -MP_EBADF; } if (timed_out) { - mp_raise_OSError(ETIMEDOUT); + return -ETIMEDOUT; } return received; } -mp_uint_t common_hal_socketpool_socket_send(socketpool_socket_obj_t *self, const uint8_t *buf, uint32_t len) { +mp_uint_t common_hal_socketpool_socket_recv_into(socketpool_socket_obj_t *self, const uint8_t *buf, uint32_t len) { + int received = socketpool_socket_recv_into(self, buf, len); + if (received < 0) { + mp_raise_OSError(received); + } + return received; +} + +int socketpool_socket_send(socketpool_socket_obj_t *self, const uint8_t *buf, uint32_t len) { int sent = -1; if (self->num != -1) { // LWIP Socket // TODO: deal with potential failure/add timeout? sent = lwip_send(self->num, buf, len, 0); } else { - mp_raise_OSError(MP_EBADF); + sent = -MP_EBADF; } if (sent < 0) { - mp_raise_OSError(errno); + return -errno; + } + + return sent; +} + +mp_uint_t common_hal_socketpool_socket_send(socketpool_socket_obj_t *self, const uint8_t *buf, uint32_t len) { + int sent = socketpool_socket_send(self, buf, len); + + if (sent < 0) { + mp_raise_OSError(-sent); } return sent; } diff --git a/ports/espressif/common-hal/socketpool/SocketPool.c b/ports/espressif/common-hal/socketpool/SocketPool.c index 8373410d8a..f9bf62d253 100644 --- a/ports/espressif/common-hal/socketpool/SocketPool.c +++ b/ports/espressif/common-hal/socketpool/SocketPool.c @@ -40,29 +40,9 @@ void common_hal_socketpool_socketpool_construct(socketpool_socketpool_obj_t *sel } } -void socketpool_socket(socketpool_socketpool_obj_t *self, - socketpool_socketpool_addressfamily_t family, socketpool_socketpool_sock_t type +bool socketpool_socket(socketpool_socketpool_obj_t *self, + socketpool_socketpool_addressfamily_t family, socketpool_socketpool_sock_t type, socketpool_socket_obj_t *sock) { - sock->type = socket_type; - sock->family = addr_family; - sock->ipproto = ipproto; - sock->pool = self; - sock->timeout_ms = (uint)-1; - - // Create LWIP socket - int socknum = -1; - socknum = lwip_socket(sock->family, sock->type, sock->ipproto); - if (socknum < 0 || !register_open_socket(sock)) { - mp_raise_RuntimeError(translate("Out of sockets")); - } - sock->num = socknum; - // Sockets should be nonblocking in most cases - lwip_fcntl(socknum, F_SETFL, O_NONBLOCK); -} - -socketpool_socket_obj_t *common_hal_socketpool_socket(socketpool_socketpool_obj_t *self, - socketpool_socketpool_addressfamily_t family, socketpool_socketpool_sock_t type) { - int addr_family; int ipproto; if (family == SOCKETPOOL_AF_INET) { @@ -81,15 +61,37 @@ socketpool_socket_obj_t *common_hal_socketpool_socket(socketpool_socketpool_obj_ } else { // SOCKETPOOL_SOCK_RAW socket_type = SOCK_RAW; } + sock->type = socket_type; + sock->family = addr_family; + sock->ipproto = ipproto; + sock->pool = self; + sock->timeout_ms = (uint)-1; - if (addr_family == AF_INET6 || ipproto == IPPROTO_IPV6) { + // Create LWIP socket + int socknum = -1; + socknum = lwip_socket(sock->family, sock->type, sock->ipproto); + if (socknum < 0) { + return false; + } + sock->num = socknum; + // Sockets should be nonblocking in most cases + lwip_fcntl(socknum, F_SETFL, O_NONBLOCK); + return true; +} + +socketpool_socket_obj_t *common_hal_socketpool_socket(socketpool_socketpool_obj_t *self, + socketpool_socketpool_addressfamily_t family, socketpool_socketpool_sock_t type) { + if (family != SOCKETPOOL_AF_INET) { mp_raise_NotImplementedError(translate("Only IPv4 sockets supported")); } socketpool_socket_obj_t *sock = m_new_obj_with_finaliser(socketpool_socket_obj_t); sock->base.type = &socketpool_socket_type; - socketpool_socket(self, family, type, sock); + if (!socketpool_socket(self, family, type, sock) || + !register_open_socket(sock)) { + mp_raise_RuntimeError(translate("Out of sockets")); + } return sock; } diff --git a/shared-bindings/socketpool/Socket.c b/shared-bindings/socketpool/Socket.c index 873fe5b3d2..716ff673ee 100644 --- a/shared-bindings/socketpool/Socket.c +++ b/shared-bindings/socketpool/Socket.c @@ -75,7 +75,7 @@ STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(socketpool_socket___exit___obj, 4, 4, //| creating a new socket of type SOCK_STREAM. //| Returns a tuple of (new_socket, remote_address)""" //| -STATIC mp_obj_t socketpool_socket_accept(mp_obj_t self_in) { +STATIC mp_obj_t _socketpool_socket_accept(mp_obj_t self_in) { socketpool_socket_obj_t *self = MP_OBJ_TO_PTR(self_in); uint8_t ip[4]; uint32_t port; @@ -87,7 +87,7 @@ STATIC mp_obj_t socketpool_socket_accept(mp_obj_t self_in) { tuple_contents[1] = netutils_format_inet_addr(ip, port, NETUTILS_BIG); return mp_obj_new_tuple(2, tuple_contents); } -STATIC MP_DEFINE_CONST_FUN_OBJ_1(socketpool_socket_accept_obj, socketpool_socket_accept); +STATIC MP_DEFINE_CONST_FUN_OBJ_1(socketpool_socket_accept_obj, _socketpool_socket_accept); //| def bind(self, address: Tuple[str, int]) -> None: //| """Bind a socket to an address @@ -120,12 +120,12 @@ STATIC MP_DEFINE_CONST_FUN_OBJ_2(socketpool_socket_bind_obj, socketpool_socket_b //| def close(self) -> None: //| """Closes this Socket and makes its resources available to its SocketPool.""" //| -STATIC mp_obj_t socketpool_socket_close(mp_obj_t self_in) { +STATIC mp_obj_t _socketpool_socket_close(mp_obj_t self_in) { socketpool_socket_obj_t *self = MP_OBJ_TO_PTR(self_in); common_hal_socketpool_socket_close(self); return mp_const_none; } -STATIC MP_DEFINE_CONST_FUN_OBJ_1(socketpool_socket_close_obj, socketpool_socket_close); +STATIC MP_DEFINE_CONST_FUN_OBJ_1(socketpool_socket_close_obj, _socketpool_socket_close); //| def connect(self, address: Tuple[str, int]) -> None: //| """Connect a socket to a remote address @@ -208,7 +208,7 @@ STATIC MP_DEFINE_CONST_FUN_OBJ_2(socketpool_socket_recvfrom_into_obj, socketpool //| :param int bufsize: optionally, a maximum number of bytes to read.""" //| ... //| -STATIC mp_obj_t socketpool_socket_recv_into(size_t n_args, const mp_obj_t *args) { +STATIC mp_obj_t _socketpool_socket_recv_into(size_t n_args, const mp_obj_t *args) { socketpool_socket_obj_t *self = MP_OBJ_TO_PTR(args[0]); if (common_hal_socketpool_socket_get_closed(self)) { // Bad file number. @@ -238,7 +238,7 @@ STATIC mp_obj_t socketpool_socket_recv_into(size_t n_args, const mp_obj_t *args) mp_int_t ret = common_hal_socketpool_socket_recv_into(self, (byte *)bufinfo.buf, len); return mp_obj_new_int_from_uint(ret); } -STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(socketpool_socket_recv_into_obj, 2, 3, socketpool_socket_recv_into); +STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(socketpool_socket_recv_into_obj, 2, 3, _socketpool_socket_recv_into); //| def send(self, bytes: ReadableBuffer) -> int: //| """Send some bytes to the connected remote address. @@ -247,7 +247,7 @@ STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(socketpool_socket_recv_into_obj, 2, 3 //| :param ~bytes bytes: some bytes to send""" //| ... //| -STATIC mp_obj_t socketpool_socket_send(mp_obj_t self_in, mp_obj_t buf_in) { +STATIC mp_obj_t _socketpool_socket_send(mp_obj_t self_in, mp_obj_t buf_in) { socketpool_socket_obj_t *self = MP_OBJ_TO_PTR(self_in); if (common_hal_socketpool_socket_get_closed(self)) { // Bad file number. @@ -264,7 +264,7 @@ STATIC mp_obj_t socketpool_socket_send(mp_obj_t self_in, mp_obj_t buf_in) { } return mp_obj_new_int_from_uint(ret); } -STATIC MP_DEFINE_CONST_FUN_OBJ_2(socketpool_socket_send_obj, socketpool_socket_send); +STATIC MP_DEFINE_CONST_FUN_OBJ_2(socketpool_socket_send_obj, _socketpool_socket_send); //| def sendto(self, bytes: ReadableBuffer, address: Tuple[str, int]) -> int: //| """Send some bytes to a specific address. diff --git a/shared-bindings/socketpool/Socket.h b/shared-bindings/socketpool/Socket.h index 1f4ab6fc35..c6c2a66630 100644 --- a/shared-bindings/socketpool/Socket.h +++ b/shared-bindings/socketpool/Socket.h @@ -47,4 +47,11 @@ mp_uint_t common_hal_socketpool_socket_sendto(socketpool_socket_obj_t *self, const char *host, size_t hostlen, uint32_t port, const uint8_t *buf, uint32_t len); void common_hal_socketpool_socket_settimeout(socketpool_socket_obj_t *self, uint32_t timeout_ms); +// Non-allocating versions for internal use. +int socketpool_socket_accept(socketpool_socket_obj_t *self, uint8_t *ip, uint32_t *port); +void socketpool_socket_close(socketpool_socket_obj_t *self); +int socketpool_socket_send(socketpool_socket_obj_t *self, const uint8_t *buf, uint32_t len); +int socketpool_socket_recv_into(socketpool_socket_obj_t *self, + const uint8_t *buf, uint32_t len); + #endif // MICROPY_INCLUDED_SHARED_BINDINGS_SOCKETPOOL_SOCKET_H diff --git a/shared-bindings/socketpool/SocketPool.h b/shared-bindings/socketpool/SocketPool.h index 10a943d38b..92382078e1 100644 --- a/shared-bindings/socketpool/SocketPool.h +++ b/shared-bindings/socketpool/SocketPool.h @@ -52,4 +52,10 @@ socketpool_socket_obj_t *common_hal_socketpool_socket(socketpool_socketpool_obj_ mp_obj_t common_hal_socketpool_socketpool_gethostbyname(socketpool_socketpool_obj_t *self, const char *host); +// Non-allocating version for internal use. These sockets are not registered and, therefore, not +// closed automatically. +bool socketpool_socket(socketpool_socketpool_obj_t *self, + socketpool_socketpool_addressfamily_t family, socketpool_socketpool_sock_t type, + socketpool_socket_obj_t *sock); + #endif // MICROPY_INCLUDED_SHARED_BINDINGS_SOCKETPOOL_SOCKETPOOL_H diff --git a/supervisor/shared/web_workflow/web_workflow.c b/supervisor/shared/web_workflow/web_workflow.c index c8f563b640..2ca25c6b72 100644 --- a/supervisor/shared/web_workflow/web_workflow.c +++ b/supervisor/shared/web_workflow/web_workflow.c @@ -30,6 +30,10 @@ #include "supervisor/shared/translate/translate.h" #include "supervisor/shared/web_workflow/web_workflow.h" +#include "shared-bindings/socketpool/__init__.h" +#include "shared-bindings/socketpool/Socket.h" +#include "shared-bindings/socketpool/SocketPool.h" + #if CIRCUITPY_WIFI #include "shared-bindings/wifi/__init__.h" #endif @@ -38,10 +42,35 @@ #include "shared-module/dotenv/__init__.h" #endif +#include "esp_log.h" + +static const char *TAG = "CP webserver"; + +enum request_state { + STATE_METHOD, + STATE_PATH, + STATE_VERSION, + STATE_HEADER_KEY, + STATE_HEADER_VALUE, + STATE_BODY +}; + +typedef struct { + enum request_state state; + char method[6]; + char path[256]; + uint32_t content_length; + size_t offset; +} _request; + static wifi_radio_error_t wifi_status = WIFI_RADIO_ERROR_NONE; static socketpool_socketpool_obj_t pool; static socketpool_socket_obj_t listening; +static socketpool_socket_obj_t active; + +static _request active_request; + void supervisor_web_workflow_status(void) { serial_write_compressed(translate("Wi-Fi: ")); @@ -66,6 +95,13 @@ void supervisor_web_workflow_status(void) { void supervisor_start_web_workflow(void) { #if CIRCUITPY_WEB_WORKFLOW && CIRCUITPY_WIFI + + if (common_hal_wifi_radio_get_enabled(&common_hal_wifi_radio_obj) && + wifi_radio_get_ipv4_address(&common_hal_wifi_radio_obj) != 0) { + // Already started. + return; + } + char ssid[33]; char password[64]; mp_int_t ssid_len = 0; @@ -102,11 +138,19 @@ void supervisor_start_web_workflow(void) { listening.base.type = &socketpool_socket_type; socketpool_socket(&pool, SOCKETPOOL_AF_INET, SOCKETPOOL_SOCK_STREAM, &listening); + common_hal_socketpool_socket_settimeout(&listening, 0); // Bind to any ip. // TODO: Make this port .env configurable. - common_hal_socketpool_socket_bind(&listening, "", 0, 80); + const char *ip = "192.168.1.94"; + common_hal_socketpool_socket_bind(&listening, ip, strlen(ip), 80); common_hal_socketpool_socket_listen(&listening, 1); + ESP_LOGW(TAG, "listening on socket %d", listening.num); + + active.base.type = &socketpool_socket_type; + active.num = -1; + active.connected = false; + // Accept a connection and start parsing: // * HTTP method // * HTTP path @@ -135,5 +179,105 @@ void supervisor_start_web_workflow(void) { #endif } +static void _process_request(socketpool_socket_obj_t *socket, _request *request) { + bool more = true; + bool error = false; + uint8_t c; + while (more && !error) { + int len = socketpool_socket_recv_into(socket, &c, 1); + if (len != 1) { + if (len != -EAGAIN && len != -EBADF) { + ESP_LOGW(TAG, "received %d", len); + } + more = false; + break; + } + ESP_LOGI(TAG, "%c", c); + switch (request->state) { + case STATE_METHOD: { + if (c == ' ') { + request->method[request->offset] = '\0'; + request->offset = 0; + request->state = STATE_PATH; + } else if (request->offset > sizeof(request->method) - 1) { + // Skip methods that are too long. + } else { + request->method[request->offset] = c; + request->offset++; + } + break; + } + case STATE_PATH: { + if (c == ' ') { + request->path[request->offset] = '\0'; + request->offset = 0; + ESP_LOGW(TAG, "Request %s %s", request->method, request->path); + request->state = STATE_VERSION; + error = true; + } else if (request->offset > sizeof(request->path) - 1) { + // Skip methods that are too long. + } else { + request->path[request->offset] = c; + request->offset++; + } + break; + } + case STATE_VERSION: + break; + case STATE_HEADER_KEY: + break; + case STATE_HEADER_VALUE: + break; + case STATE_BODY: + break; + } + } + if (error) { + const char *error_response = "HTTP/1.1 501 Not Implemented"; + int nodelay = 1; + int err = lwip_setsockopt(socket->num, IPPROTO_TCP, TCP_NODELAY, &nodelay, sizeof(nodelay)); + int sent = socketpool_socket_send(socket, (const uint8_t *)error_response, strlen(error_response)); + ESP_LOGW(TAG, "sent %d %d", sent, err); + vTaskDelay(0); + socketpool_socket_close(socket); + } +} + + +void supervisor_web_workflow_background(void) { + // Otherwise, see if we have another socket to accept. + if (!common_hal_socketpool_socket_get_connected(&active) && + !common_hal_socketpool_socket_get_closed(&listening) && + listening.num > 0) { + uint32_t ip; + uint32_t port; + int newsoc = socketpool_socket_accept(&listening, (uint8_t *)&ip, &port); + if (newsoc != -11) { + ESP_LOGI(TAG, "newsoc %d", newsoc); + } + if (newsoc > 0) { + ESP_LOGE(TAG, "Accepted socket %d", newsoc); + // TODO: Don't do this because it uses the private struct layout. + // Create the socket + active.num = newsoc; + active.pool = &pool; + active.connected = true; + + + common_hal_socketpool_socket_settimeout(&active, 0); + + active_request.state = STATE_METHOD; + active_request.offset = 0; + + lwip_fcntl(newsoc, F_SETFL, O_NONBLOCK); + } + } + + // If we have a request in progress, continue working on it. + if (common_hal_socketpool_socket_get_connected(&active)) { + _process_request(&active, &active_request); + } +} + void supervisor_stop_web_workflow(void) { } diff --git a/supervisor/shared/web_workflow/web_workflow.h b/supervisor/shared/web_workflow/web_workflow.h index 6e550aaa9d..f233c06e48 100644 --- a/supervisor/shared/web_workflow/web_workflow.h +++ b/supervisor/shared/web_workflow/web_workflow.h @@ -28,8 +28,9 @@ #include -void supervisor_wifi_background(void); -void supervisor_wifi_init(void); +// This background function should be called repeatedly. It cannot be done based +// on events. +void supervisor_web_workflow_background(void); void supervisor_web_workflow_status(void); void supervisor_start_web_workflow(void); void supervisor_stop_web_workflow(void);