diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml index e038fec9..b654404d 100644 --- a/.github/workflows/nightly.yml +++ b/.github/workflows/nightly.yml @@ -303,6 +303,7 @@ jobs: strategy: fail-fast: false matrix: + target: [bsd, bsd_nortos] cc: [gcc, clang, g++, clang++] name: bsd CC=${{ matrix.cc }} steps: @@ -316,7 +317,7 @@ jobs: make -C test/bsd shm_tap_bridge test/bsd/shm_tap_bridge & sleep 2 - make -C test/bsd CC=${{ matrix.cc }} > log + make -C test/bsd ${{ matrix.target }} CC=${{ matrix.cc }} > log - if: success() || failure() run: | cat log @@ -588,10 +589,12 @@ jobs: stm32/nucleo-f746zg/minimal stm32/nucleo-f746zg/minimal-pppoe stm32/nucleo-f756zg/minimal - stm32/nucleo-h563zi/minimal stm32/nucleo-h723zg/minimal + stm32/nucleo-h723zg/minimal-bsd-baremetal + stm32/nucleo-h723zg/minimal-bsd-freertos - group: stm32-2 examples: | + stm32/nucleo-h563zi/minimal stm32/nucleo-h743zi/minimal stm32/nucleo-n657x0-q/minimal stm32/nucleo-u5a5zj-q/minimal diff --git a/.github/workflows/quicktest.yml b/.github/workflows/quicktest.yml index e01bfe97..b0e1e2f2 100644 --- a/.github/workflows/quicktest.yml +++ b/.github/workflows/quicktest.yml @@ -157,7 +157,8 @@ jobs: - path: stm32/nucleo-f746zg/minimal - path: stm32/nucleo-h563zi/minimal - path: stm32/nucleo-h723zg/minimal - - path: stm32/nucleo-h723zg/minimal-bsd + - path: stm32/nucleo-h723zg/minimal-bsd-baremetal + - path: stm32/nucleo-h723zg/minimal-bsd-freertos - path: stm32/nucleo-h743zi/minimal - path: stm32/stm32h573i-dk/minimal - path: ti/ek-tm4c1294xl-make-baremetal-builtin diff --git a/mongoose.c b/mongoose.c index b6d3796a..c92d61a5 100644 --- a/mongoose.c +++ b/mongoose.c @@ -167,735 +167,209 @@ fail: #endif -#if MG_ENABLE_BSD_SOCKETS - -// Queue-based BSD shim is currently TCP/SOCK_STREAM only. UDP/SOCK_DGRAM would -// need datagram boundaries, e.g. datagram queues or SOD/EOD framing. -// Unconnected UDP also needs per-packet peer addresses for sendto()/recvfrom(). -// It is also IPv4-only: transports store sockaddr_in and build IPv4 URLs. - -#ifndef MG_ENABLE_BSD_LOG -#define MG_ENABLE_BSD_LOG 0 -#define bsd_log(type, tag, a, b, c, n) -#else -#define MG_BSD_LOG_SOCK 1 -#define MG_BSD_LOG_ACCEPT 2 -#define MG_BSD_LOG_CLOSE 3 -#define MG_BSD_LOG_TRANSPORT 4 -#define MG_BSD_LOG_RESULT 5 -#define MG_BSD_LOG_CONNECT 6 -// Type is always a constant, so optimised builds fold the switch to one log. -#define bsd_log(type, tag, a, b, c, n) \ - do { \ - switch (type) { \ - case MG_BSD_LOG_SOCK: { \ - struct mg_bsd_sock *log_s = (struct mg_bsd_sock *) (a); \ - MG_DEBUG(("SOCK %s %ld %p", tag, (long) (n), log_s->t)); \ - break; \ - } \ - case MG_BSD_LOG_ACCEPT: { \ - struct mg_connection *log_mc = (struct mg_connection *) (a); \ - if ((n) >= 0) { \ - MG_DEBUG(("ACCEPT %lu %p %u %ld", log_mc->id, c, \ - (unsigned) mg_ntohs(log_mc->rem.port), (long) (n))); \ - } else { \ - MG_DEBUG(("ACCEPT %lu %p %u", log_mc->id, c, \ - (unsigned) mg_ntohs(log_mc->rem.port))); \ - } \ - break; \ - } \ - case MG_BSD_LOG_CLOSE: { \ - struct mg_connection *log_mc = (struct mg_connection *) (a); \ - if ((n) >= 0) MG_INFO(("CLOSE %s %lu %p %ld", tag, log_mc->id, b, \ - (long) (n))); \ - else MG_DEBUG(("CLOSE %s %lu %p", tag, log_mc->id, b)); \ - break; \ - } \ - case MG_BSD_LOG_TRANSPORT: \ - if ((n) >= 0) MG_INFO(("TRANSP %s %p %ld", tag, a, (long) (n))); \ - else MG_DEBUG(("TRANSP %s %p", tag, a)); \ - break; \ - case MG_BSD_LOG_RESULT: \ - MG_DEBUG(("RESULT %s %p %ld", tag, a, (long) (n))); \ - break; \ - case MG_BSD_LOG_CONNECT: \ - if ((c) != NULL) { \ - MG_DEBUG(("CONNECT %s %p %p %s %ld", tag, a, b, \ - (const char *) (c), (long) (n))); \ - } else { \ - MG_DEBUG(("CONNECT %s %p %p %ld", tag, a, b, (long) (n))); \ - } \ - break; \ - } \ - } while (0) -#endif - -struct mg_bsd_sock { - void *t; // opaque transport handle - int fd; - int domain, type, proto; - bool nonblock; - struct sockaddr_in addr; // bind address - struct sockaddr_in peer; // peer address (after accept/connect) - struct mg_bsd_sock *next; -}; - -#define MG_BSD_FD_BASE 17777 - -// static struct mg_mgr *s_mgr; -static struct mg_bsd_sock *s_socks; - -static struct mg_bsd_sock *get(int fd) { - if (fd < MG_BSD_FD_BASE) return NULL; - for (struct mg_bsd_sock *s = s_socks; s; s = s->next) - if (s->fd == fd) return s; - return NULL; -} - -static int alloc_sock(struct mg_bsd_sock *s) { - for (int fd = MG_BSD_FD_BASE; ; fd++) { - if (get(fd) == NULL) { s->fd = fd; break; } - } - s->next = s_socks; - s_socks = s; - return s->fd; -} - -static void release_sock(int fd) { - struct mg_bsd_sock **p = &s_socks; - while (*p && (*p)->fd != fd) p = &(*p)->next; - if (*p) *p = (*p)->next; -} - -int socket(int domain, int type, int proto) { - struct mg_bsd_sock *s = (struct mg_bsd_sock *) calloc(1, sizeof(*s)); - if (!s) { errno = ENOMEM; return -1; } - s->t = mg_bsd_transport_new(domain, type, proto); - if (!s->t) { free(s); return -1; } - if (alloc_sock(s) < 0) { mg_bsd_transport_free(s->t); free(s); errno = ENOMEM; return -1; } - s->domain = domain; s->type = type; s->proto = proto; - return s->fd; -} - -int bind(int fd, const struct sockaddr *addr, socklen_t len) { - struct mg_bsd_sock *s = get(fd); - if (!s) return -1; - memcpy(&s->addr, addr, len < sizeof(s->addr) ? len : sizeof(s->addr)); - return 0; -} - -int listen(int fd, int backlog) { - struct mg_bsd_sock *s = get(fd); - if (!s) return -1; - (void) backlog; - return mg_bsd_transport_listen(s->t, &s->addr); -} - -int accept(int fd, struct sockaddr *addr, socklen_t *addrlen) { - struct mg_bsd_sock *ls = get(fd); - if (!ls) return -1; - struct sockaddr_in peer; - memset(&peer, 0, sizeof(peer)); - void *t = mg_bsd_transport_accept(ls->t, &peer, ls->nonblock); - if (!t) return -1; // errno was set by transport_accept() - struct mg_bsd_sock *ns = (struct mg_bsd_sock *) calloc(1, sizeof(*ns)); - if (!ns || alloc_sock(ns) < 0) { mg_bsd_transport_close(t); free(ns); errno = ENOMEM; return -1; } - ns->t = t; ns->domain = ls->domain; ns->type = ls->type; ns->peer = peer; - if (addr && addrlen) { - size_t sz = sizeof(peer) < *addrlen ? sizeof(peer) : *addrlen; - memcpy(addr, &peer, sz); - *addrlen = (socklen_t) sizeof(peer); - } - return ns->fd; -} - -int connect(int fd, const struct sockaddr *addr, socklen_t len) { - struct mg_bsd_sock *s = get(fd); - if (!s) return -1; - (void) len; - return mg_bsd_transport_connect(s->t, (const struct sockaddr_in *) addr, s->nonblock); -} - -ssize_t send(int fd, const void *buf, size_t len, int flags) { - struct mg_bsd_sock *s = get(fd); - if (!s) return -1; - return mg_bsd_transport_send(s->t, buf, len, s->nonblock || (flags & MSG_DONTWAIT)); -} - -ssize_t recv(int fd, void *buf, size_t len, int flags) { - struct mg_bsd_sock *s = get(fd); - if (!s) return -1; - return mg_bsd_transport_recv(s->t, buf, len, s->nonblock || (flags & MSG_DONTWAIT)); -} - -ssize_t sendto(int fd, const void *buf, size_t len, int flags, - const struct sockaddr *dest, socklen_t addrlen) { - (void) fd; (void) buf; (void) len; (void) flags; (void) dest; (void) addrlen; - errno = EPROTONOSUPPORT; - return -1; -} - -ssize_t recvfrom(int fd, void *buf, size_t len, int flags, - struct sockaddr *src, socklen_t *addrlen) { - (void) fd; (void) buf; (void) len; (void) flags; (void) src; (void) addrlen; - errno = EPROTONOSUPPORT; - return -1; -} - -ssize_t write(int fd, const void *buf, size_t len) { return send(fd, buf, len, 0); } -ssize_t read(int fd, void *buf, size_t len) { return recv(fd, buf, len, 0); } - -int close(int fd) { - struct mg_bsd_sock *s = get(fd); - if (!s) return -1; - bsd_log(MG_BSD_LOG_SOCK, "CLOSE", s, NULL, NULL, (long) fd); - mg_bsd_transport_close(s->t); - bsd_log(MG_BSD_LOG_SOCK, "FREE", s, NULL, NULL, (long) fd); - release_sock(fd); - free(s); - return 0; -} - -int shutdown(int fd, int how) { (void) how; return close(fd); } - -int fcntl(int fd, int cmd, int arg) { - struct mg_bsd_sock *s = get(fd); - if (!s) return -1; - if (cmd == F_GETFL) return s->nonblock ? O_NONBLOCK : 0; - if (cmd == F_SETFL) { s->nonblock = (arg & O_NONBLOCK) != 0; return 0; } - return -1; -} - -int setsockopt(int fd, int level, int optname, const void *optval, socklen_t optlen) { - (void) fd; (void) level; (void) optname; (void) optval; (void) optlen; - return 0; -} - -int getsockopt(int fd, int level, int optname, void *optval, socklen_t *optlen) { - (void) fd; (void) level; (void) optname; - if (optval && optlen && *optlen >= sizeof(int)) { *(int *) optval = 0; *optlen = sizeof(int); } - return 0; -} - -int getsockname(int fd, struct sockaddr *addr, socklen_t *addrlen) { - struct mg_bsd_sock *s = get(fd); - if (!s) return -1; - size_t sz = sizeof(s->addr) < *addrlen ? sizeof(s->addr) : *addrlen; - memcpy(addr, &s->addr, sz); - *addrlen = (socklen_t) sz; - return 0; -} - -int getpeername(int fd, struct sockaddr *addr, socklen_t *addrlen) { - struct mg_bsd_sock *s = get(fd); - if (!s) { errno = ENOTCONN; return -1; } - size_t sz = sizeof(s->peer) < *addrlen ? sizeof(s->peer) : *addrlen; - memcpy(addr, &s->peer, sz); - *addrlen = (socklen_t) sz; - return 0; -} - -// select/poll: not implemented for queue-based backend -int select(int nfds, fd_set *r, fd_set *w, fd_set *e, struct timeval *tv) { - (void) nfds; (void) r; (void) w; (void) e; (void) tv; - return 0; -} - -int poll(struct pollfd *fds, unsigned int nfds, int timeout) { - (void) fds; (void) nfds; (void) timeout; - return 0; -} - -// DNS stubs (overridden in the FreeRTOS backend below) -#if !MG_ENABLE_FREERTOS -struct hostent *gethostbyname(const char *name) { (void) name; return NULL; } -int getaddrinfo(const char *node, const char *service, - const struct addrinfo *hints, struct addrinfo **res) { - (void) node; (void) service; (void) hints; (void) res; - return -1; -} -void freeaddrinfo(struct addrinfo *res) { (void) res; } -#endif - -int inet_pton(int af, const char *src, void *dst) { - struct mg_addr a; - memset(&a, 0, sizeof(a)); - if (af == AF_INET && mg_aton(mg_str_s(src), &a)) { memcpy(dst, &a.addr.ip4, 4); return 1; } - return 0; -} - -const char *inet_ntop(int af, const void *src, char *dst, socklen_t size) { - if (af == AF_INET && size >= 16) { - const uint8_t *ip = (const uint8_t *) src; - snprintf(dst, size, "%d.%d.%d.%d", ip[0], ip[1], ip[2], ip[3]); - return dst; - } - return NULL; -} - -in_addr_t inet_addr(const char *cp) { - struct mg_addr a; - memset(&a, 0, sizeof(a)); - return mg_aton(mg_str_s(cp), &a) ? a.addr.ip4 : (in_addr_t) -1; -} - -static char s_ntoa_buf[16]; -char *inet_ntoa(struct in_addr in) { - const uint8_t *ip = (const uint8_t *) &in.s_addr; - snprintf(s_ntoa_buf, sizeof(s_ntoa_buf), "%d.%d.%d.%d", ip[0], ip[1], ip[2], ip[3]); - return s_ntoa_buf; -} - -#ifdef MG_ENABLE_BSD_PROTOTYPES -uint16_t htons(uint16_t n) { return mg_htons(n); } -uint16_t ntohs(uint16_t n) { return mg_htons(n); } -uint32_t htonl(uint32_t n) { return mg_htonl(n); } -uint32_t ntohl(uint32_t n) { return mg_htonl(n); } -#endif - -// ============================================================ -// FreeRTOS + Mongoose transport backend -// ============================================================ -#if MG_ENABLE_FREERTOS +#include +#if MG_ENABLE_BSD_SOCKETS && MG_ENABLE_FREERTOS #include -#ifndef MG_BSD_CHUNK_SIZE -#define MG_BSD_CHUNK_SIZE 256 +static QueueHandle_t s_cmd_q; +static struct mg_bsd_cmd *s_waiters; + +static bool bsd_qsend(QueueHandle_t q, const void *item, TickType_t ticks) { + if (xQueueSend(q, item, ticks) == pdTRUE) return true; + MG_ERROR(("queue send failed %p", q)); + return false; +} #endif -#ifndef MG_BSD_Q_DEPTH -#define MG_BSD_Q_DEPTH 4 + +#if MG_ENABLE_BSD_SOCKETS +static struct mg_mgr *s_mgr; + +#ifndef MG_BSD_FD_BASE +#define MG_BSD_FD_BASE 11 +#endif +#ifndef MG_BSD_MAX_SOCKETS +#define MG_BSD_MAX_SOCKETS 64 #endif #ifndef MG_BSD_ACCEPT_MS -#define MG_BSD_ACCEPT_MS 3000 // Timeout for accept queue handoff +#define MG_BSD_ACCEPT_MS 3000 #endif -struct mg_bsd_chunk { uint8_t data[MG_BSD_CHUNK_SIZE]; uint16_t len; }; - -struct mg_xport { - struct mg_connection *c; // Mongoose connection, task1-only - QueueHandle_t recv_q; // task1 writes on MG_EV_READ, task2 reads in recv() - QueueHandle_t send_q; // task2 writes in send(), task1 drains on MG_EV_POLL - QueueHandle_t accept_q; // task1 writes on MG_EV_ACCEPT, task2 reads in accept() - struct sockaddr_in peer; - uint16_t rx_off; - uint64_t accept_expire; - int err; - bool closed; - bool orphan; // accepted connection not handed to socket owner - TaskHandle_t connect_waiter; // task blocked in connect(), woken by MG_EV_CONNECT - int *connect_result; // where to store 0/−1 connect outcome +// clang-format off +enum bsd_op { + BSD_OP_NONE, BSD_OP_SOCKET, BSD_OP_BIND, BSD_OP_LISTEN, BSD_OP_FCNTL, + BSD_OP_ACCEPT, BSD_OP_CONNECT, BSD_OP_RECV, BSD_OP_SEND, BSD_OP_SENDTO, + BSD_OP_RECVFROM, BSD_OP_CLOSE, BSD_OP_POLL, BSD_OP_SELECT, + BSD_OP_GETHOSTBYNAME, }; -enum mg_bsd_cmd_op { BSD_CMD_LISTEN, BSD_CMD_CLOSE, BSD_CMD_CONNECT, BSD_CMD_RESOLVE }; +// A BSD API request from a user task. Gets sent via the RTOS queue, and +// executed by the network task (mg_bsd_poll). Baremetal code calls directly struct mg_bsd_cmd { - enum mg_bsd_cmd_op type; - struct mg_xport *x; - char url[64]; - TaskHandle_t caller; - int *result; + int result; // return value + int err; // errno + enum bsd_op op; + bool started; // operation initiated (connect/resolve) + void *caller; // task to notify when done + struct mg_bsd_cmd *next; // waiter list link + union { // op-specific arguments + struct { int domain, type, proto; } op_socket; + struct { int fd, backlog; } op_listen; + struct { int fd, cmd, arg; } op_fcntl; + struct { int fd; } op_close; + struct { int fd; const struct sockaddr *addr; socklen_t len; } op_bind; + struct { int fd; const struct sockaddr *addr; socklen_t len; } op_connect; + struct { int fd; struct sockaddr *addr; socklen_t *addrlen; } op_accept; + struct { int fd; void *buf; size_t len; int flags; } op_recv; + struct { int fd; const void *buf; size_t len; int flags; } op_send; + struct { int fd; const void *buf; size_t len; int flags; const struct sockaddr *addr; socklen_t addrlen; } op_sendto; + struct { int fd; void *buf; size_t len; int flags; struct sockaddr *src; socklen_t *addrlen; } op_recvfrom; + struct { struct pollfd *fds; nfds_t nfds; uint64_t deadline; } op_poll; + struct { int nfds; fd_set *r, *w, *e; uint64_t deadline; } op_select; + struct { const char *name; struct hostent *result; } op_gethostbyname; + }; +}; +#define BSD_CMD_EMPTY {0, 0, BSD_OP_NONE, 0, 0, 0, {{0, 0, 0}}} +// clang-format on + +// The BSD socket outlives its Mongoose connection until the caller closes fd. +struct mg_bsd_sock { + int fd, err, backlog; + uint64_t accept_expire; + struct sockaddr_in addr; + struct mg_connection *c; + struct mg_bsd_sock *listener; + bool used, bound, closed, nonblock, is_udp, orphan; // closed: c was closed }; -static QueueHandle_t s_cmd_q; +static struct mg_bsd_sock s_socks[MG_BSD_MAX_SOCKETS > 0 ? + MG_BSD_MAX_SOCKETS : 1]; -static bool bsd_qsend(QueueHandle_t q, const void *item, TickType_t ticks, - bool reserve) { - BaseType_t ok = pdFALSE; - if (!reserve || uxQueueSpacesAvailable(q) > 1) ok = xQueueSend(q, item, ticks); - if (ok != pdTRUE) MG_ERROR(("%p", q)); - return ok == pdTRUE; +static void bsd_ev(struct mg_connection *, int, void *); + +// BSD-owned fd registry +static struct mg_bsd_sock *bsd_get(int fd) { + int offset; + struct mg_bsd_sock *s; + + if (fd < MG_BSD_FD_BASE) return NULL; + offset = fd - MG_BSD_FD_BASE; + if (offset >= MG_BSD_MAX_SOCKETS) return NULL; + s = &s_socks[offset]; + if (!s->used || s->orphan) return NULL; + return s; } -static bool xport_accept(struct mg_xport *x) { - if (bsd_qsend(x->accept_q, &x, 0, true)) { - x->orphan = false; // not an orphan anymore - x->accept_q = NULL; - return true; +static void bsd_attach(struct mg_bsd_sock *s, struct mg_connection *c) { + s->c = c; + c->fn = bsd_ev; + c->fn_data = s; + c->is_bsd = 1; + c->is_udp = s->is_udp; +} + +static struct mg_connection *bsd_new_conn(struct mg_bsd_sock *s) { + struct mg_connection *c = mg_alloc_conn(s_mgr); + if (c != NULL) { + bsd_attach(s, c); + LIST_ADD_HEAD(struct mg_connection, &s_mgr->conns, c); } - return false; // still an orphan, retry later + return c; } -// static DNS resolve state; not reentrant (see below) -static struct { struct mg_addr addr; bool error; TaskHandle_t caller; } s_resolve; -// gethostbyname statics (official isn't reentrant anyway, and is obsolete) -static struct hostent s_hostent; -static char *s_h_aliases[1]; -static char *s_h_addr_list[2]; -static uint32_t s_h_addr; -static char s_h_name[64]; +#if MG_ENABLE_FREERTOS +// Complete operations blocked on c before Mongoose destroys it. +static void bsd_finish_waiters(struct mg_bsd_sock *s, bool error) { + struct mg_bsd_cmd **p = &s_waiters; -static void resolve_cb(struct mg_connection *c, int ev, void *ev_data) { - bool notify = false; - if (ev == MG_EV_RESOLVE) { - s_resolve.addr = c->rem; - c->is_closing = 1; - MG_DEBUG(("%lu resolved", c->id)); - notify = true; - } else if (ev == MG_EV_ERROR) { - s_resolve.error = true; - MG_DEBUG(("%lu failed", c->id)); - notify = true; - } else if (ev == MG_EV_CLOSE) { - s_resolve.caller = NULL; // The resolver connection has fully unwound. - MG_DEBUG(("%lu done", c->id)); + if (s->orphan) return; // No BSD socket owner yet + + while (*p != NULL) { + struct mg_bsd_cmd *cmd = *p; + int fd = -1; + bool read = cmd->op == BSD_OP_RECV || cmd->op == BSD_OP_RECVFROM; + bool send = cmd->op == BSD_OP_SEND || cmd->op == BSD_OP_SENDTO; + + switch (cmd->op) { // clang-format off + case BSD_OP_ACCEPT: fd = cmd->op_accept.fd; break; + case BSD_OP_CONNECT: fd = cmd->op_connect.fd; break; + case BSD_OP_RECV: fd = cmd->op_recv.fd; break; + case BSD_OP_SEND: fd = cmd->op_send.fd; break; + case BSD_OP_SENDTO: fd = cmd->op_sendto.fd; break; + case BSD_OP_RECVFROM: fd = cmd->op_recvfrom.fd; break; + default: break; + } // clang-format on + if (fd != s->fd) { + p = &cmd->next; + continue; + } + + *p = cmd->next; + if (!error && s->c != NULL && s->c->is_draining && read) { + cmd->result = 0; // Orderly EOF + } else { + cmd->result = -1; + cmd->err = send ? EPIPE : EIO; // Mongoose error / forced close + } + xTaskNotifyGive((TaskHandle_t) cmd->caller); } - if (notify) xTaskNotifyGive(s_resolve.caller); - (void) ev_data; } +#endif -// Allocate transport for an accepted connection (recv+send queues only) -static struct mg_xport *xport_alloc(void) { - struct mg_xport *x = (struct mg_xport *) calloc(1, sizeof(*x)); - if (!x) return NULL; - // +1 keeps a terminal EOF/error slot for MG_EV_CLOSE - x->recv_q = xQueueCreate(MG_BSD_Q_DEPTH + 1, sizeof(struct mg_bsd_chunk)); - x->send_q = xQueueCreate(MG_BSD_Q_DEPTH, sizeof(struct mg_bsd_chunk)); - if (!x->recv_q || !x->send_q) { mg_bsd_transport_free(x); return NULL; } - x->orphan = true; // haven't attached this connection to its socket - return x; -} - -static void xport_ev(struct mg_connection *c, int ev, void *ev_data) { - struct mg_xport *x = (struct mg_xport *) c->fn_data; - if (!x) return; +static void bsd_ev(struct mg_connection *c, int ev, void *ev_data) { + struct mg_bsd_sock *s = (struct mg_bsd_sock *) c->fn_data; + if (s == NULL) return; if (ev == MG_EV_ACCEPT) { - // c is the new accepted connection; x is the listening transport - bool ok; - struct mg_xport *nx = xport_alloc(); - if (!nx) { c->fn_data = NULL; mg_error(c, "accept OOM"); return; } - nx->c = c; - nx->accept_q = x->accept_q; - nx->accept_expire = mg_millis() + MG_BSD_ACCEPT_MS; - nx->peer.sin_family = AF_INET; - nx->peer.sin_port = c->rem.port; - memcpy(&nx->peer.sin_addr, &c->rem.addr.ip4, 4); - c->fn_data = nx; - ok = xport_accept(nx); // leaves orphaned on failure, retry on POLL - bsd_log(MG_BSD_LOG_ACCEPT, NULL, c, x, nx, ok ? 1 : 0); - } else if (ev == MG_EV_POLL && x->orphan && x->accept_q) { - if (uxQueueSpacesAvailable(x->accept_q) > 1 && xport_accept(x)) { - bsd_log(MG_BSD_LOG_ACCEPT, NULL, c, NULL, x, 1); - } else if (mg_millis() > x->accept_expire) { // retried enough, give up - x->err = EIO; - mg_error(c, "accept_q"); + struct mg_bsd_sock *child = NULL, *t; + int count = 0; + for (t = s_socks; t < s_socks + MG_BSD_MAX_SOCKETS; t++) { + if (!t->used && child == NULL) child = t; // Find a free BSD socket slot, + if (t->orphan && t->listener == s) count++; // and count pending accepts } - } else if (ev == MG_EV_READ || ev == MG_EV_POLL) { - if (x->recv_q) { // let POLL resume abandoned READ processing on full queue - // Drain c->recv into recv_q in fixed-size chunks; task1 owns c->recv - size_t off = 0; - while (off < c->recv.len) { - struct mg_bsd_chunk chunk; - size_t n = c->recv.len - off; - if (n > MG_BSD_CHUNK_SIZE) n = MG_BSD_CHUNK_SIZE; - memcpy(chunk.data, c->recv.buf + off, n); - chunk.len = (uint16_t) n; - if (uxQueueSpacesAvailable(x->recv_q) <= 1 || - !bsd_qsend(x->recv_q, &chunk, 0, false)) break; // retry later - off += n; - } - mg_iobuf_del(&c->recv, 0, off); + if (count >= s->backlog) { + c->fn_data = NULL; + mg_error(c, "accept backlog full"); + } else if (child == NULL) { + c->fn_data = NULL; + mg_error(c, "accept rejected"); + } else { + memset(child, 0, sizeof(*child)); // Claim slot + child->used = true; + child->c = c; + child->listener = s; + child->accept_expire = mg_millis() + MG_BSD_ACCEPT_MS; + child->is_udp = s->is_udp; + child->orphan = true; + c->fn_data = child; + c->is_bsd = 1; + c->is_udp = child->is_udp; } - if (ev == MG_EV_POLL && x->send_q) { - // Drain send_q → mg_send(); task1 owns c - struct mg_bsd_chunk chunk; - while (xQueuePeek(x->send_q, &chunk, 0) == pdTRUE) { - if (!mg_send(c, chunk.data, chunk.len)) break; // retry later - xQueueReceive(x->send_q, &chunk, 0); - } - } - } else if (ev == MG_EV_CONNECT) { - // Outgoing connection established: wake the task blocked in connect() - if (x->connect_waiter) { - bsd_log(MG_BSD_LOG_CONNECT, "OK", x, c, NULL, 0); - if (x->connect_result) *x->connect_result = 0; - TaskHandle_t h = x->connect_waiter; - x->connect_waiter = NULL; x->connect_result = NULL; - xTaskNotifyGive(h); - } - } else if (ev == MG_EV_ERROR) { // remember error condition - if (x->err == 0) x->err = EIO; // and let CLOSE handle it - bsd_log(MG_BSD_LOG_CLOSE, "ERR", c, x, NULL, (long) x->err); + } else if (ev == MG_EV_ERROR) { + if (s->err == 0) s->err = EIO; +#if MG_ENABLE_FREERTOS + bsd_finish_waiters(s, true); +#endif } else if (ev == MG_EV_CLOSE) { - bsd_log(MG_BSD_LOG_CLOSE, "IN", c, x, NULL, -1); - x->c = NULL; x->closed = true; c->fn_data = NULL; - // If connect() is still waiting, signal failure - if (x->connect_waiter) { - bsd_log(MG_BSD_LOG_CONNECT, "FAIL", x, c, NULL, -1); - if (x->connect_result) *x->connect_result = -1; - TaskHandle_t h = x->connect_waiter; - x->connect_waiter = NULL; x->connect_result = NULL; - xTaskNotifyGive(h); - // The notified task owns the transport and can close/free it immediately. - return; - } - if (x->orphan) { // connection --> socket attachment failed - x->accept_q = NULL; // borrowed from listener; do not delete it here - bsd_log(MG_BSD_LOG_CLOSE, "FREE", c, x, NULL, -1); - mg_bsd_transport_free(x); // connection closed, release resources - return; - } - if (x->recv_q) { - struct mg_bsd_chunk eof; - bool ok; - memset(&eof, 0, sizeof(eof)); - eof.len = 0; - ok = bsd_qsend(x->recv_q, &eof, 0, false); - bsd_log(MG_BSD_LOG_CLOSE, "EOF>", c, x, NULL, ok ? 1 : 0); - if (!ok) MG_ERROR(("recv_q close notification failed")); - // recv() wakeup transfers control to the transport owner. - return; - } - if (x->accept_q) { - struct mg_xport *nil = NULL; - bool ok; - ok = bsd_qsend(x->accept_q, &nil, 0, false); - bsd_log(MG_BSD_LOG_CLOSE, "ACCEPT>", c, x, NULL, ok ? 1 : 0); - if (!ok) MG_ERROR(("accept_q close notification failed")); - // accept() wakeup transfers control to the transport owner. - return; - } - bsd_log(MG_BSD_LOG_CLOSE, "OUT", c, x, NULL, -1); +#if MG_ENABLE_FREERTOS + bsd_finish_waiters(s, false); +#endif + s->c = NULL; + s->closed = true; + c->fn_data = NULL; + if (s->orphan) memset(s, 0, sizeof(*s)); // release + } else if (ev == MG_EV_POLL && s->orphan && + mg_millis() > s->accept_expire) { + mg_error(c, "accept timeout"); } (void) ev_data; } -void mg_bsd_init(void) { - s_cmd_q = xQueueCreate(8, sizeof(struct mg_bsd_cmd)); -} - -void mg_bsd_poll(struct mg_mgr *mgr) { - struct mg_bsd_cmd cmd; - if (s_cmd_q == NULL) return; - while (xQueueReceive(s_cmd_q, &cmd, 0) == pdTRUE) { - bool notify = true; - if (cmd.type == BSD_CMD_LISTEN) { - struct mg_connection *c = mg_listen(mgr, cmd.url, xport_ev, cmd.x); - cmd.x->c = c; - *cmd.result = c ? 0 : -1; - } else if (cmd.type == BSD_CMD_CLOSE) { - bsd_log(MG_BSD_LOG_TRANSPORT, "CMD", cmd.x, NULL, NULL, -1); - if (cmd.x->c) { - cmd.x->c->fn_data = NULL; - cmd.x->c->is_draining = 1; - cmd.x->c = NULL; - } - cmd.x->closed = true; - *cmd.result = 0; - } else if (cmd.type == BSD_CMD_CONNECT) { - cmd.x->connect_waiter = cmd.caller; - cmd.x->connect_result = cmd.result; - struct mg_connection *c = mg_connect(mgr, cmd.url, xport_ev, cmd.x); - bsd_log(MG_BSD_LOG_CONNECT, "NEW", cmd.x, c, cmd.url, c ? 1 : 0); - cmd.x->c = c; - if (!c) { *cmd.result = -1; cmd.x->connect_waiter = NULL; cmd.x->connect_result = NULL; } - else notify = false; // xport_ev notifies when connected or on error - } else if (cmd.type == BSD_CMD_RESOLVE) { - s_resolve.error = false; - s_resolve.caller = cmd.caller; - struct mg_connection *c; - c = mg_alloc_conn(mgr); - if (c == NULL) { - } else { - c->fn = resolve_cb; - LIST_ADD_HEAD(struct mg_connection, &mgr->conns, c); - mg_call(c, MG_EV_OPEN, NULL); - MG_DEBUG(("%lu resolve %s", c->id, cmd.url)); - mg_resolve(c, cmd.url); - notify = false; // resolve_cb notifies when done - } - } - if (notify) xTaskNotifyGive(cmd.caller); - } -} - -void *mg_bsd_transport_new(int domain, int type, int proto) { - if (domain != AF_INET || type != SOCK_STREAM || - (proto != 0 && proto != IPPROTO_TCP)) { - errno = EPROTONOSUPPORT; - return NULL; - } - // For socket() calls: allocate accept_q only; recv/send added when needed. - // +1 keeps a terminal slot for MG_EV_CLOSE without blocking Mongoose. - struct mg_xport *x = (struct mg_xport *) calloc(1, sizeof(*x)); - if (!x) return NULL; - x->accept_q = xQueueCreate(MG_BSD_BACKLOG + 1, sizeof(struct mg_xport *)); - if (!x->accept_q) { free(x); return NULL; } - return x; -} - -void mg_bsd_transport_free(void *t) { - struct mg_xport *x = (struct mg_xport *) t; - if (!x) return; - bsd_log(MG_BSD_LOG_TRANSPORT, "FREE", x, NULL, NULL, -1); - if (x->recv_q) vQueueDelete(x->recv_q); - if (x->send_q) vQueueDelete(x->send_q); - if (x->accept_q) vQueueDelete(x->accept_q); - free(x); -} - -int mg_bsd_transport_listen(void *t, const struct sockaddr_in *addr) { - struct mg_xport *x = (struct mg_xport *) t; - int result = -1; - struct mg_bsd_cmd cmd = {BSD_CMD_LISTEN, x, {0}, xTaskGetCurrentTaskHandle(), &result}; - snprintf(cmd.url, sizeof(cmd.url), "tcp://0.0.0.0:%d", mg_ntohs(addr->sin_port)); - if (!bsd_qsend(s_cmd_q, &cmd, portMAX_DELAY, false)) return -1; - ulTaskNotifyTake(pdTRUE, portMAX_DELAY); - return result; -} - -void *mg_bsd_transport_accept(void *t, struct sockaddr_in *peer, bool nonblock) { - struct mg_xport *x = (struct mg_xport *) t; - struct mg_xport *nx = NULL; - TickType_t ticks = nonblock ? 0 : portMAX_DELAY; - if (xQueueReceive(x->accept_q, &nx, ticks) != pdTRUE) { - errno = x->closed ? (x->err ? x->err : EIO) : (nonblock ? EAGAIN : EIO); - return NULL; - } - if (!nx) { errno = x->err ? x->err : EIO; return NULL; } - if (peer) *peer = nx->peer; - return nx; -} - -ssize_t mg_bsd_transport_recv(void *t, void *buf, size_t len, bool nonblock) { - struct mg_xport *x = (struct mg_xport *) t; - uint8_t *p = (uint8_t *) buf; - size_t recvd = 0; - TickType_t ticks = nonblock ? 0 : portMAX_DELAY; - while (recvd < len) { - struct mg_bsd_chunk chunk; - if (xQueuePeek(x->recv_q, &chunk, recvd == 0 ? ticks : 0) != pdTRUE) { - if (recvd > 0) break; - errno = x->closed ? (x->err ? x->err : EIO) : (nonblock ? EAGAIN : EIO); - return -1; - } - if (chunk.len == 0) { - if (recvd > 0) break; - xQueueReceive(x->recv_q, &chunk, 0); - if (x->err) { errno = x->err; return -1; } - return 0; // EOF - } else { - size_t n = chunk.len - x->rx_off; - if (n > len - recvd) n = len - recvd; - memcpy(p + recvd, chunk.data + x->rx_off, n); - recvd += n; - x->rx_off = (uint16_t) (x->rx_off + n); - if (x->rx_off >= chunk.len) { - xQueueReceive(x->recv_q, &chunk, 0); - x->rx_off = 0; - } - } - } - return (ssize_t) recvd; -} - -ssize_t mg_bsd_transport_send(void *t, const void *buf, size_t len, bool nonblock) { - struct mg_xport *x = (struct mg_xport *) t; - if (x->closed) { errno = x->err ? x->err : EPIPE; return -1; } - size_t sent = 0; - TickType_t ticks = nonblock ? 0 : portMAX_DELAY; - while (sent < len) { - struct mg_bsd_chunk chunk; - size_t n = len - sent; - if (n > MG_BSD_CHUNK_SIZE) n = MG_BSD_CHUNK_SIZE; - memcpy(chunk.data, (const uint8_t *) buf + sent, n); - chunk.len = (uint16_t) n; - if (!bsd_qsend(x->send_q, &chunk, ticks, false)) { - errno = x->closed ? (x->err ? x->err : EPIPE) : (nonblock ? EAGAIN : EIO); - return sent > 0 ? (ssize_t) sent : -1; - } - sent += n; - } - return sent > 0 ? (ssize_t) sent : (errno = EAGAIN, -1); -} - -int mg_bsd_transport_connect(void *t, const struct sockaddr_in *addr, bool nonblock) { - struct mg_xport *x = (struct mg_xport *) t; - (void) nonblock; - if (!x->recv_q) x->recv_q = xQueueCreate(MG_BSD_Q_DEPTH + 1, sizeof(struct mg_bsd_chunk)); - if (!x->send_q) x->send_q = xQueueCreate(MG_BSD_Q_DEPTH, sizeof(struct mg_bsd_chunk)); - if (!x->recv_q || !x->send_q) { errno = ENOMEM; return -1; } - int result = -1; - struct mg_bsd_cmd cmd = {BSD_CMD_CONNECT, x, {0}, xTaskGetCurrentTaskHandle(), &result}; - uint8_t *ip = (uint8_t *) &addr->sin_addr.s_addr; - snprintf(cmd.url, sizeof(cmd.url), "tcp://%d.%d.%d.%d:%d", - ip[0], ip[1], ip[2], ip[3], mg_ntohs(addr->sin_port)); - bsd_log(MG_BSD_LOG_CONNECT, "REQ", x, NULL, cmd.url, -1); - if (!bsd_qsend(s_cmd_q, &cmd, portMAX_DELAY, false)) return -1; - ulTaskNotifyTake(pdTRUE, portMAX_DELAY); - if (result != 0) errno = x->err ? x->err : EIO; - return result; -} - -// gethostbyname: resolve via Mongoose DNS (not reentrant) -struct hostent *gethostbyname(const char *name) { - struct mg_bsd_cmd cmd = {BSD_CMD_RESOLVE, NULL, {0}, xTaskGetCurrentTaskHandle(), NULL}; - snprintf(cmd.url, sizeof(cmd.url), "%s", name); - if (!bsd_qsend(s_cmd_q, &cmd, portMAX_DELAY, false)) return NULL; - ulTaskNotifyTake(pdTRUE, portMAX_DELAY); - if (s_resolve.error) return NULL; - s_h_addr = s_resolve.addr.addr.ip4; - s_h_addr_list[0] = (char *) &s_h_addr; - s_h_addr_list[1] = NULL; - s_h_aliases[0] = NULL; - snprintf(s_h_name, sizeof(s_h_name), "%s", name); - s_hostent.h_name = s_h_name; - s_hostent.h_aliases = s_h_aliases; - s_hostent.h_addrtype = AF_INET; - s_hostent.h_length = 4; - s_hostent.h_addr_list = s_h_addr_list; - return &s_hostent; -} - -int getaddrinfo(const char *node, const char *service, - const struct addrinfo *hints, struct addrinfo **res) { - struct hostent *h = gethostbyname(node); - if (!h) return -1; - struct addrinfo *ai = (struct addrinfo *) calloc(1, sizeof(*ai)); - struct sockaddr_in *sa = (struct sockaddr_in *) calloc(1, sizeof(*sa)); - if (!ai || !sa) { free(ai); free(sa); return -1; } - sa->sin_family = AF_INET; - memcpy(&sa->sin_addr, h->h_addr, 4); - if (service) sa->sin_port = htons((uint16_t) atoi(service)); - ai->ai_family = AF_INET; - ai->ai_socktype = hints ? hints->ai_socktype : SOCK_STREAM; - ai->ai_addrlen = sizeof(*sa); - ai->ai_addr = (struct sockaddr *) sa; - *res = ai; - return 0; -} - -void freeaddrinfo(struct addrinfo *res) { - while (res) { - struct addrinfo *next = res->ai_next; - free(res->ai_addr); - free(res); - res = next; - } -} - -void mg_bsd_transport_close(void *t) { - struct mg_xport *x = (struct mg_xport *) t; - bsd_log(MG_BSD_LOG_TRANSPORT, "CLOSE", x, NULL, NULL, -1); - if (!x->closed && x->c) { - int result = 0; - struct mg_bsd_cmd cmd = {BSD_CMD_CLOSE, x, {0}, xTaskGetCurrentTaskHandle(), &result}; - bool ok = bsd_qsend(s_cmd_q, &cmd, portMAX_DELAY, false); - bsd_log(MG_BSD_LOG_TRANSPORT, "CMD>", x, NULL, NULL, ok ? 1 : 0); - if (ok) { - ulTaskNotifyTake(pdTRUE, portMAX_DELAY); - bsd_log(MG_BSD_LOG_RESULT, "CMD", x, NULL, NULL, (long) result); - } - } - bsd_log(MG_BSD_LOG_TRANSPORT, "FREE_REQ", x, NULL, NULL, -1); - mg_bsd_transport_free(x); -} - +#if MG_ENABLE_FREERTOS #ifndef MG_WAKEUP_QUEUE_DEPTH #define MG_WAKEUP_QUEUE_DEPTH 4 #endif - struct wumsg { unsigned long id; size_t len; @@ -931,8 +405,8 @@ bool mg_wakeup_init(struct mg_mgr *mgr) { if (mgr->pipe.q != NULL) return true; mgr->pipe.q = xQueueCreate(MG_WAKEUP_QUEUE_DEPTH, sizeof(void *)); if (mgr->pipe.q == NULL) { - MG_ERROR(("Cannot create queue")); - return false; + MG_ERROR(("Cannot create queue")); + return false; } c = mg_alloc_conn(mgr); if (c == NULL) { @@ -960,16 +434,702 @@ bool mg_wakeup(struct mg_mgr *mgr, unsigned long conn_id, const void *buf, m->id = conn_id; m->len = len; memcpy(m->data, buf, len); - if (!bsd_qsend((QueueHandle_t) mgr->pipe.q, &m, 0, false)) { + if (!bsd_qsend((QueueHandle_t) mgr->pipe.q, &m, 0)) { free(m); return false; } return true; } +#endif + +enum { BSD_READ = 1, BSD_WRITE = 2, BSD_ERROR = 4 }; + +static int bsd_ready(struct mg_bsd_sock *s) { + struct mg_bsd_sock *t; + int ready = 0; + + if (s->err != 0) return BSD_READ | BSD_ERROR; + if (s->c == NULL) return s->closed ? BSD_READ | BSD_ERROR : 0; + if (s->c->is_listening) { + for (t = s_socks; t < s_socks + MG_BSD_MAX_SOCKETS; t++) { + if (t->orphan && t->listener == s) return BSD_READ; + } + return 0; + } + if (s->c->recv.len > 0 || s->c->is_draining) ready |= BSD_READ; + if (!s->is_udp && !s->c->is_connecting && !s->c->is_closing && + !s->c->is_draining) + ready |= BSD_WRITE; // Established TCP socket + return ready; +} + +static void bsd_close(struct mg_bsd_sock *s) { + struct mg_bsd_sock *t; + +#if MG_ENABLE_FREERTOS + bsd_finish_waiters(s, true); +#endif + for (t = s_socks; t < s_socks + MG_BSD_MAX_SOCKETS; t++) { + if (t->orphan && t->listener == s) { + if (t->c != NULL) { + t->c->fn_data = NULL; + mg_close_conn(t->c); + t->c = NULL; + } + memset(t, 0, sizeof(*t)); // Release orphan + } + } + if (s->c != NULL) { + s->c->fn_data = NULL; + mg_close_conn(s->c); + s->c = NULL; + } + memset(s, 0, sizeof(*s)); // Release socket +} + +static char s_hostent_name[64]; +static uint32_t s_hostent_addr; +static char *s_hostent_addr_list[2] = {(char *) &s_hostent_addr, NULL}; +static struct hostent s_hostent = {s_hostent_name, NULL, AF_INET, 4, + s_hostent_addr_list}; +static volatile int s_resolve_done; + +static void bsd_resolve_fn(struct mg_connection *c, int ev, void *ev_data) { + (void) ev_data; + if (ev == MG_EV_RESOLVE) { + s_hostent_addr = c->rem.addr.ip4; + s_resolve_done = 1; + c->is_closing = 1; + } else if (ev == MG_EV_ERROR) { + s_resolve_done = -1; + } +} + +// Execute a command. Runs in the network task (mg_bsd_poll) on FreeRTOS, or +// in the caller otherwise. Returns true when done, false when the operation +// must wait for the network task and be re-checked later. +static bool bsd_run(struct mg_bsd_cmd *cmd) { + struct mg_connection *c; + struct mg_bsd_sock *s; + switch (cmd->op) { + case BSD_OP_NONE: + cmd->result = -1; + return true; + case BSD_OP_SOCKET: + if (s_mgr == NULL || cmd->op_socket.domain != AF_INET || + (cmd->op_socket.type != SOCK_STREAM && + cmd->op_socket.type != SOCK_DGRAM)) { + cmd->result = -1, cmd->err = EPERM; + } else { + for (s = s_socks; s < s_socks + MG_BSD_MAX_SOCKETS; s++) { + if (!s->used) break; // Find a free BSD socket registry slot + } + if (s == s_socks + MG_BSD_MAX_SOCKETS) { + cmd->result = -1, cmd->err = EMFILE; + } else { + memset(s, 0, sizeof(*s)); // Claim slot + s->used = true; + s->is_udp = cmd->op_socket.type == SOCK_DGRAM; + s->fd = MG_BSD_FD_BASE + (int) (s - s_socks); + cmd->result = s->fd; + } + } + return true; + case BSD_OP_BIND: + if ((s = bsd_get(cmd->op_bind.fd)) == NULL) { + cmd->result = -1, cmd->err = EBADF; + } else { + const struct sockaddr_in *sa = + (const struct sockaddr_in *) cmd->op_bind.addr; + memcpy(&s->addr, sa, sizeof(s->addr)); + s->bound = true; + if (!s->is_udp) { + cmd->result = 0; + } else if (s->c == NULL && (c = bsd_new_conn(s)) == NULL) { + cmd->result = -1, cmd->err = ENOMEM; + } else { + c = s->c; + c->loc.port = s->addr.sin_port; + c->loc.addr.ip4 = s->addr.sin_addr.s_addr; + c->loc.is_ip6 = false; + cmd->result = 0; + } + } + return true; + case BSD_OP_LISTEN: { + char url[32]; + + s = bsd_get(cmd->op_listen.fd); + if (s == NULL) { + cmd->result = -1, cmd->err = EBADF; + } else if (!s->bound || s->is_udp || cmd->op_listen.backlog < 1) { + cmd->result = -1, cmd->err = EINVAL; + } else if (s->c != NULL && !s->c->is_listening) { + cmd->result = -1, cmd->err = EINVAL; + } else if (s->c == NULL) { + mg_snprintf(url, sizeof(url), "tcp://%M:%hu", mg_print_ip4, + &s->addr.sin_addr.s_addr, mg_ntohs(s->addr.sin_port)); + if ((c = mg_listen(s_mgr, url, bsd_ev, s)) == NULL) { + cmd->result = -1, cmd->err = ENOMEM; + } else { + bsd_attach(s, c); + s->backlog = cmd->op_listen.backlog; + cmd->result = 0; + } + } else { + s->backlog = cmd->op_listen.backlog; + cmd->result = 0; + } + return true; + } + case BSD_OP_FCNTL: + if ((s = bsd_get(cmd->op_fcntl.fd)) == NULL) { + cmd->result = -1, cmd->err = EBADF; + } else if (cmd->op_fcntl.cmd == F_GETFL) { + cmd->result = s->nonblock ? O_NONBLOCK : 0; + } else if (cmd->op_fcntl.cmd == F_SETFL) { + s->nonblock = (cmd->op_fcntl.arg & O_NONBLOCK) != 0; + cmd->result = 0; + } else { + cmd->result = -1, cmd->err = EINVAL; + } + return true; + case BSD_OP_ACCEPT: { + int i; + struct mg_bsd_sock *child; + + s = bsd_get(cmd->op_accept.fd); + if (s == NULL || s->c == NULL || !s->c->is_listening) { + cmd->result = -1, cmd->err = EINVAL; + return true; + } + for (i = 0, child = s_socks; i < MG_BSD_MAX_SOCKETS; i++, child++) { + if (child->orphan && child->listener == s) { + c = child->c; + child->orphan = false; + child->listener = NULL; + child->fd = MG_BSD_FD_BASE + i; + cmd->result = child->fd; + if (cmd->op_accept.addr != NULL && cmd->op_accept.addrlen != NULL) { + struct sockaddr_in *sa = (struct sockaddr_in *) cmd->op_accept.addr; + sa->sin_family = AF_INET; + sa->sin_port = c->rem.port; + sa->sin_addr.s_addr = c->rem.addr.ip4; + *cmd->op_accept.addrlen = sizeof(*sa); + } + return true; + } + } + if (s->nonblock) { + cmd->result = -1, cmd->err = EAGAIN; + return true; + } +#if MG_ENABLE_FREERTOS + return false; // No connection yet, wait +#else + cmd->result = -1, cmd->err = EAGAIN; + return true; +#endif + } + case BSD_OP_CONNECT: + if ((s = bsd_get(cmd->op_connect.fd)) == NULL) { + cmd->result = -1, cmd->err = EBADF; + return true; + } + if (!cmd->started && s->c == NULL) { + if (s->closed || (c = bsd_new_conn(s)) == NULL) { + cmd->result = -1, cmd->err = s->closed ? EIO : ENOMEM; + return true; + } + } + if ((c = s->c) == NULL) { + cmd->result = -1, cmd->err = s->err ? s->err : EIO; + return true; + } + if (!cmd->started) { + const struct sockaddr_in *sa = + (const struct sockaddr_in *) cmd->op_connect.addr; + c->rem.port = sa->sin_port; + c->rem.addr.ip4 = sa->sin_addr.s_addr; + c->rem.is_ip6 = false; + c->is_client = 1; + mg_connect_resolved(c); + cmd->started = true; + } + if (c->is_closing) { + cmd->result = -1, cmd->err = ECONNREFUSED; + return true; + } + if (!c->is_connecting) { + cmd->result = 0; + return true; + } + if (s->nonblock) { + cmd->result = -1, cmd->err = EINPROGRESS; + return true; + } +#if MG_ENABLE_FREERTOS + return false; // Still connecting, wait +#else + cmd->result = -1, cmd->err = EINPROGRESS; + return true; +#endif + case BSD_OP_RECV: + if ((s = bsd_get(cmd->op_recv.fd)) == NULL) { + cmd->result = -1, cmd->err = EBADF; + return true; + } + if ((c = s->c) == NULL) { + cmd->result = s->closed && s->err == 0 ? 0 : -1; + cmd->err = s->closed ? s->err : ENOTCONN; + return true; + } + if (c->recv.len > 0) { + size_t n = cmd->op_recv.len; + if (c->recv.len < n) n = c->recv.len; + memcpy(cmd->op_recv.buf, c->recv.buf, n); + if (!(cmd->op_recv.flags & MSG_PEEK)) mg_iobuf_del(&c->recv, 0, n); + cmd->result = (int) n; + return true; + } + if (s->err != 0) { + cmd->result = -1, cmd->err = s->err; + return true; + } + if (c->is_draining) { // Orderly shutdown by peer + cmd->result = 0; + return true; + } + if (!s->nonblock && !(cmd->op_recv.flags & MSG_DONTWAIT)) { +#if MG_ENABLE_FREERTOS + return false; // No data yet, wait +#endif + } + cmd->result = -1, cmd->err = EAGAIN; + return true; + case BSD_OP_SEND: + if ((s = bsd_get(cmd->op_send.fd)) == NULL) { + cmd->result = -1, cmd->err = EBADF; + } else if ((c = s->c) == NULL || s->err != 0 || c->is_closing || + c->is_draining) { + cmd->result = -1, cmd->err = EPIPE; + } else if (mg_send(c, cmd->op_send.buf, cmd->op_send.len)) { + cmd->result = (int) cmd->op_send.len; + } else if (s->nonblock || (cmd->op_send.flags & MSG_DONTWAIT)) { + cmd->result = -1, cmd->err = EAGAIN; + } else { +#if MG_ENABLE_FREERTOS + return false; // No send buffer space yet, wait +#else + cmd->result = -1, cmd->err = EAGAIN; +#endif + } + return true; + case BSD_OP_SENDTO: + if ((s = bsd_get(cmd->op_sendto.fd)) == NULL) { + cmd->result = -1, cmd->err = EBADF; + } else if ((c = s->c) == NULL || s->err != 0 || c->is_closing || + c->is_draining) { + cmd->result = -1, cmd->err = EPIPE; + } else { + const struct sockaddr_in *sa = + (const struct sockaddr_in *) cmd->op_sendto.addr; + c->rem.port = sa->sin_port; + c->rem.addr.ip4 = sa->sin_addr.s_addr; + c->rem.is_ip6 = false; + if (mg_send(c, cmd->op_sendto.buf, cmd->op_sendto.len)) { + cmd->result = (int) cmd->op_sendto.len; + } else if (s->nonblock || (cmd->op_sendto.flags & MSG_DONTWAIT)) { + cmd->result = -1, cmd->err = EAGAIN; + } else { +#if MG_ENABLE_FREERTOS + return false; // No send buffer space yet, wait +#else + cmd->result = -1, cmd->err = EAGAIN; +#endif + } + } + return true; + case BSD_OP_RECVFROM: + if ((s = bsd_get(cmd->op_recvfrom.fd)) == NULL) { + cmd->result = -1, cmd->err = EBADF; + return true; + } + if ((c = s->c) == NULL) { + cmd->result = s->closed && s->err == 0 ? 0 : -1; + cmd->err = s->closed ? s->err : ENOTCONN; + return true; + } + if (c->recv.len > 0) { + size_t n = cmd->op_recvfrom.len; + if (c->recv.len < n) n = c->recv.len; + memcpy(cmd->op_recvfrom.buf, c->recv.buf, n); + if (!(cmd->op_recvfrom.flags & MSG_PEEK)) mg_iobuf_del(&c->recv, 0, n); + if (cmd->op_recvfrom.src != NULL && cmd->op_recvfrom.addrlen != NULL) { + struct sockaddr_in *sa = (struct sockaddr_in *) cmd->op_recvfrom.src; + sa->sin_family = AF_INET; + sa->sin_port = c->rem.port; + sa->sin_addr.s_addr = c->rem.addr.ip4; + *cmd->op_recvfrom.addrlen = sizeof(*sa); + } + cmd->result = (int) n; + return true; + } + if (s->err != 0) { + cmd->result = -1, cmd->err = s->err; + return true; + } + if (!s->nonblock && !(cmd->op_recvfrom.flags & MSG_DONTWAIT)) { +#if MG_ENABLE_FREERTOS + return false; // No datagram yet, wait +#endif + } + cmd->result = -1, cmd->err = EAGAIN; + return true; + case BSD_OP_CLOSE: + if ((s = bsd_get(cmd->op_close.fd)) == NULL) { + cmd->result = -1, cmd->err = EBADF; + } else { + bsd_close(s); + cmd->result = 0; + } + return true; + case BSD_OP_POLL: { + struct pollfd *p; + int i, n = 0, ready; + + for (i = 0, p = cmd->op_poll.fds; i < (int) cmd->op_poll.nfds; + i++, p++) { + p->revents = 0; + if (p->fd < 0) continue; + if ((s = bsd_get(p->fd)) == NULL) { + p->revents = POLLNVAL; + } else { + ready = bsd_ready(s); + if ((p->events & POLLIN) && (ready & BSD_READ)) + p->revents |= POLLIN; + if ((p->events & POLLOUT) && (ready & BSD_WRITE)) + p->revents |= POLLOUT; + if (s->err != 0) p->revents |= POLLERR; + if (s->closed) p->revents |= POLLHUP; + } + if (p->revents != 0) n++; + } +#if MG_ENABLE_FREERTOS + if (n == 0 && mg_millis() < cmd->op_poll.deadline) + return false; // Keep caller pollfd[] intact while waiting +#endif + cmd->result = n; + return true; + } + case BSD_OP_SELECT: { + fd_set rr, ww, ee; + fd_set *r = cmd->op_select.r; + fd_set *w = cmd->op_select.w; + fd_set *e = cmd->op_select.e; + int fd, n = 0, want_read, want_write, want_error, ready; +#if MG_ENABLE_BSD_PROTOTYPES + int i; + __fd_mask bits; + int bitsize = (int) (sizeof(bits) * 8); +#endif + + if (cmd->op_select.nfds < 0 || cmd->op_select.nfds > FD_SETSIZE) { + cmd->result = -1, cmd->err = EINVAL; + return true; + } + FD_ZERO(&rr); + FD_ZERO(&ww); + FD_ZERO(&ee); +#if MG_ENABLE_BSD_PROTOTYPES + for (i = 0; i < (cmd->op_select.nfds + bitsize - 1) / bitsize; i++) { + bits = 0; + if (r != NULL) bits |= r->__fds_bits[i]; + if (w != NULL) bits |= w->__fds_bits[i]; + if (e != NULL) bits |= e->__fds_bits[i]; + while (bits != 0) { + fd = i * bitsize + (int) __builtin_ctzl(bits); + bits &= bits - 1; + if (fd >= cmd->op_select.nfds) continue; + want_read = r != NULL && FD_ISSET(fd, r); + want_write = w != NULL && FD_ISSET(fd, w); + want_error = e != NULL && FD_ISSET(fd, e); + if ((s = bsd_get(fd)) == NULL) goto err; + ready = bsd_ready(s); + if (want_read && (ready & BSD_READ)) FD_SET(fd, &rr); + if (want_write && (ready & BSD_WRITE)) FD_SET(fd, &ww); + if (want_error && (ready & BSD_ERROR)) FD_SET(fd, &ee); + if ((want_read && (ready & BSD_READ)) || + (want_write && (ready & BSD_WRITE)) || + (want_error && (ready & BSD_ERROR))) + n++; + } + } +#else + for (fd = 0; fd < cmd->op_select.nfds; fd++) { + want_read = r != NULL && FD_ISSET(fd, r); + want_write = w != NULL && FD_ISSET(fd, w); + want_error = e != NULL && FD_ISSET(fd, e); + if (!want_read && !want_write && !want_error) continue; + if ((s = bsd_get(fd)) == NULL) goto err; + ready = bsd_ready(s); + if (want_read && (ready & BSD_READ)) FD_SET(fd, &rr); + if (want_write && (ready & BSD_WRITE)) FD_SET(fd, &ww); + if (want_error && (ready & BSD_ERROR)) FD_SET(fd, &ee); + if ((want_read && (ready & BSD_READ)) || + (want_write && (ready & BSD_WRITE)) || + (want_error && (ready & BSD_ERROR))) + n++; + } +#endif +#if MG_ENABLE_FREERTOS + if (n == 0 && mg_millis() < cmd->op_select.deadline) + return false; // Keep fd sets while waiting +#endif + if (r != NULL) *r = rr; + if (w != NULL) *w = ww; + if (e != NULL) *e = ee; + cmd->result = n; + return true; + +err: + cmd->result = -1, cmd->err = EBADF; + return true; + } + case BSD_OP_GETHOSTBYNAME: + if (!cmd->started) { + struct mg_connection *r = mg_alloc_conn(s_mgr); + if (r == NULL) { + cmd->op_gethostbyname.result = NULL; + return true; + } + r->fn = bsd_resolve_fn; + LIST_ADD_HEAD(struct mg_connection, &s_mgr->conns, r); + mg_call(r, MG_EV_OPEN, NULL); + s_resolve_done = 0; + mg_resolve(r, cmd->op_gethostbyname.name); + cmd->started = true; +#if MG_ENABLE_FREERTOS + return false; // Wait for DNS response +#endif + } + if (s_resolve_done != 0) { + if (s_resolve_done < 0) { + cmd->op_gethostbyname.result = NULL; + } else { + mg_snprintf(s_hostent_name, sizeof(s_hostent_name), "%s", + cmd->op_gethostbyname.name); + cmd->op_gethostbyname.result = &s_hostent; + } + return true; + } +#if MG_ENABLE_FREERTOS + return false; // Still resolving +#else + cmd->op_gethostbyname.result = NULL; + return true; +#endif + } + return true; +} + +static void bsd_dispatch(struct mg_bsd_cmd *cmd) { +#if MG_ENABLE_FREERTOS + if (s_cmd_q == NULL) { + cmd->result = -1, cmd->err = EPERM; + } else { + cmd->caller = xTaskGetCurrentTaskHandle(); + if (!bsd_qsend(s_cmd_q, &cmd, portMAX_DELAY)) { + cmd->result = -1, cmd->err = EIO; + } else { + ulTaskNotifyTake(pdTRUE, portMAX_DELAY); + } + } +#else + bsd_run(cmd); +#endif +} + +int socket(int domain, int type, int proto) { + struct mg_bsd_cmd cmd = BSD_CMD_EMPTY; + cmd.op = BSD_OP_SOCKET, cmd.op_socket.domain = domain, + cmd.op_socket.type = type, cmd.op_socket.proto = proto; + bsd_dispatch(&cmd); + errno = cmd.err; + return cmd.result; +} + +int bind(int fd, const struct sockaddr *addr, socklen_t len) { + struct mg_bsd_cmd cmd = BSD_CMD_EMPTY; + cmd.op = BSD_OP_BIND, cmd.op_bind.fd = fd, cmd.op_bind.addr = addr, + cmd.op_bind.len = len; + bsd_dispatch(&cmd); + errno = cmd.err; + return cmd.result; +} + +int listen(int fd, int backlog) { + struct mg_bsd_cmd cmd = BSD_CMD_EMPTY; + cmd.op = BSD_OP_LISTEN, cmd.op_listen.fd = fd, + cmd.op_listen.backlog = backlog; + bsd_dispatch(&cmd); + errno = cmd.err; + return cmd.result; +} + +int fcntl(int fd, int fcmd, ...) { + struct mg_bsd_cmd cmd = BSD_CMD_EMPTY; + int arg = 0; + + if (fcmd == F_SETFL) { + va_list ap; + + va_start(ap, fcmd); + arg = va_arg(ap, int); + va_end(ap); + } + cmd.op = BSD_OP_FCNTL, cmd.op_fcntl.fd = fd, cmd.op_fcntl.cmd = fcmd, + cmd.op_fcntl.arg = arg; + bsd_dispatch(&cmd); + errno = cmd.err; + return cmd.result; +} + +int accept(int fd, struct sockaddr *addr, socklen_t *addrlen) { + struct mg_bsd_cmd cmd = BSD_CMD_EMPTY; + cmd.op = BSD_OP_ACCEPT, cmd.op_accept.fd = fd, cmd.op_accept.addr = addr, + cmd.op_accept.addrlen = addrlen; + bsd_dispatch(&cmd); + errno = cmd.err; + return cmd.result; +} + +int connect(int fd, const struct sockaddr *addr, socklen_t len) { + struct mg_bsd_cmd cmd = BSD_CMD_EMPTY; + cmd.op = BSD_OP_CONNECT, cmd.op_connect.fd = fd, cmd.op_connect.addr = addr, + cmd.op_connect.len = len; + bsd_dispatch(&cmd); + errno = cmd.err; + return cmd.result; +} + +ssize_t recv(int fd, void *buf, size_t len, int flags) { + struct mg_bsd_cmd cmd = BSD_CMD_EMPTY; + cmd.op = BSD_OP_RECV, cmd.op_recv.fd = fd, cmd.op_recv.buf = buf, + cmd.op_recv.len = len, cmd.op_recv.flags = flags; + bsd_dispatch(&cmd); + errno = cmd.err; + return cmd.result; +} + +ssize_t send(int fd, const void *buf, size_t len, int flags) { + struct mg_bsd_cmd cmd = BSD_CMD_EMPTY; + cmd.op = BSD_OP_SEND, cmd.op_send.fd = fd, cmd.op_send.buf = (void *) buf, + cmd.op_send.len = len, cmd.op_send.flags = flags; + bsd_dispatch(&cmd); + errno = cmd.err; + return cmd.result; +} + +ssize_t sendto(int fd, const void *buf, size_t len, int flags, + const struct sockaddr *dest, socklen_t addrlen) { + struct mg_bsd_cmd cmd = BSD_CMD_EMPTY; + cmd.op = BSD_OP_SENDTO, cmd.op_sendto.fd = fd, + cmd.op_sendto.buf = (void *) buf, cmd.op_sendto.len = len, + cmd.op_sendto.flags = flags, cmd.op_sendto.addr = dest, + cmd.op_sendto.addrlen = addrlen; + bsd_dispatch(&cmd); + errno = cmd.err; + return cmd.result; +} + +ssize_t recvfrom(int fd, void *buf, size_t len, int flags, struct sockaddr *src, + socklen_t *addrlen) { + struct mg_bsd_cmd cmd = BSD_CMD_EMPTY; + cmd.op = BSD_OP_RECVFROM, cmd.op_recvfrom.fd = fd, cmd.op_recvfrom.buf = buf, + cmd.op_recvfrom.len = len, cmd.op_recvfrom.flags = flags, + cmd.op_recvfrom.src = src, cmd.op_recvfrom.addrlen = addrlen; + bsd_dispatch(&cmd); + errno = cmd.err; + return cmd.result; +} + +int close(int fd) { + struct mg_bsd_cmd cmd = BSD_CMD_EMPTY; + cmd.op = BSD_OP_CLOSE, cmd.op_close.fd = fd; + bsd_dispatch(&cmd); + errno = cmd.err; + return cmd.result; +} + +int poll(struct pollfd *fds, nfds_t nfds, int timeout) { + struct mg_bsd_cmd cmd = BSD_CMD_EMPTY; + uint64_t deadline = timeout < 0 ? (uint64_t) ~0 + : mg_millis() + (uint64_t) timeout; + + cmd.op = BSD_OP_POLL, cmd.op_poll.fds = fds, cmd.op_poll.nfds = nfds, + cmd.op_poll.deadline = deadline; + bsd_dispatch(&cmd); + errno = cmd.err; + return cmd.result; +} + +int select(int nfds, fd_set *r, fd_set *w, fd_set *e, struct timeval *tv) { + struct mg_bsd_cmd cmd = BSD_CMD_EMPTY; + uint64_t deadline = tv == NULL ? (uint64_t) ~0 + : mg_millis() + (uint64_t) tv->tv_sec * 1000 + + tv->tv_usec / 1000; + cmd.op = BSD_OP_SELECT, cmd.op_select.nfds = nfds, cmd.op_select.r = r, + cmd.op_select.w = w, cmd.op_select.e = e, cmd.op_select.deadline = deadline; + bsd_dispatch(&cmd); + errno = cmd.err; + return cmd.result; +} + +struct hostent *gethostbyname(const char *name) { + struct mg_bsd_cmd cmd = BSD_CMD_EMPTY; + cmd.op = BSD_OP_GETHOSTBYNAME, cmd.op_gethostbyname.name = name; + bsd_dispatch(&cmd); + return cmd.op_gethostbyname.result; +} -#endif // MG_ENABLE_FREERTOS #endif // MG_ENABLE_BSD_SOCKETS +#if MG_ENABLE_BSD_SOCKETS +void mg_bsd_init(struct mg_mgr *mgr) { + s_mgr = mgr; +#if MG_ENABLE_BSD_SOCKETS && MG_ENABLE_FREERTOS + s_cmd_q = xQueueCreate(16, sizeof(struct mg_bsd_cmd *)); +#endif +} + +void mg_bsd_poll(struct mg_mgr *mgr) { + (void) mgr; +#if MG_ENABLE_BSD_SOCKETS && MG_ENABLE_FREERTOS + struct mg_bsd_cmd **p, *cmd; + while (xQueueReceive(s_cmd_q, &cmd, 0) == pdTRUE) { + if (bsd_run(cmd)) { + xTaskNotifyGive((TaskHandle_t) cmd->caller); + } else { + cmd->next = s_waiters; + s_waiters = cmd; + } + } + p = &s_waiters; + while (*p != NULL) { + struct mg_bsd_cmd *w = *p; + if (bsd_run(w)) { + *p = w->next; + xTaskNotifyGive((TaskHandle_t) w->caller); + } else { + p = &w->next; + } + } + taskYIELD(); +#endif +} +#endif + #ifdef MG_ENABLE_LINES #line 1 "src/dash.c" #endif @@ -8446,6 +8606,7 @@ void mg_multicast_restore(struct mg_connection *c, uint8_t *from) { + #if MG_ENABLE_TCPIP #define MG_EPHEMERAL_PORT_BASE 32768 #define PDIFF(a, b) ((size_t) (((char *) (b)) - ((char *) (a)))) @@ -8476,16 +8637,16 @@ void mg_multicast_restore(struct mg_connection *c, uint8_t *from) { #endif struct connstate { - uint32_t seq, ack; // TCP seq/ack counters - uint64_t timer; // TCP timer (see 'ttype' below) - uint64_t txq_timer; // TCP retransmission timer (RFC-6298, 5) - uint32_t acked; // Last ACK-ed number - size_t unacked; // Not acked bytes - uint32_t maxseq; // Max send seq (ack + window) - uint32_t txq_seq; // Sequence of first txq record (RFC-9293, 3.8) - uint32_t txq_una; // Oldest unacknowledged sequence (RFC-9293, 3.4) - uint16_t win; // destination current window size - uint16_t dmss; // destination MSS (from TCP opts) + uint32_t seq, ack; // TCP seq/ack counters + uint64_t timer; // TCP timer (see 'ttype' below) + uint64_t txq_timer; // TCP retransmission timer (RFC-6298, 5) + uint32_t acked; // Last ACK-ed number + size_t unacked; // Not acked bytes + uint32_t maxseq; // Max send seq (ack + window) + uint32_t txq_seq; // Sequence of first txq record (RFC-9293, 3.8) + uint32_t txq_una; // Oldest unacknowledged sequence (RFC-9293, 3.4) + uint16_t win; // destination current window size + uint16_t dmss; // destination MSS (from TCP opts) uint8_t mac[sizeof(struct mg_l2addr)]; // Peer hw address uint8_t ttype; // Timer type: #define MIP_TTYPE_KEEPALIVE 0 // Connection is idle for long, send keepalive @@ -9254,7 +9415,7 @@ static struct ip6 *tx_ip6(struct mg_tcpip_if *ifp, uint8_t *l2_dst, struct ip6 *ip6 = (struct ip6 *) mg_l2_header(ifp, MG_TCPIP_L2PROTO_IPV6, ifp->mac, l2_dst, l2p); memset(ip6, 0, sizeof(*ip6)); - ip6->ver = (uint8_t) (0x60 | (dscp >> 2)); // Version 6, traffic class + ip6->ver = (uint8_t) (0x60 | (dscp >> 2)); // Version 6, traffic class ip6->label[0] = (uint8_t) (dscp << 6); ip6->plen = mg_htons((uint16_t) plen); ip6->next = next; @@ -9723,7 +9884,7 @@ static size_t tx_tcp(struct mg_tcpip_if *ifp, uint8_t *l2_dst, tcp->off = (uint8_t) (hlen / 4 << 4); #if MG_ENABLE_IPV6 if (ip_dst->is_ip6) { - tcp->csum = p6csum(ip6, tcp, 6, hlen + len); + tcp->csum = p6csum(ip6, tcp, 6, hlen + len); } else #endif { @@ -10711,6 +10872,7 @@ void mg_tcpip_init(struct mg_mgr *mgr, struct mg_tcpip_if *ifp) { // MG_EPHEMERAL_PORT_BASE to 65535 if (ifp->tx.buf == NULL || ifp->recv_queue.buf == NULL) MG_ERROR(("OOM")); } + mg_bsd_init(mgr); } void mg_tcpip_free(struct mg_tcpip_if *ifp) { @@ -10881,6 +11043,7 @@ void mg_mgr_poll(struct mg_mgr *mgr, int ms) { c->is_closing = 1; if (c->is_closing) close_conn(c); } + mg_bsd_poll(mgr); (void) ms; } diff --git a/mongoose.h b/mongoose.h index bce2e8a0..1cfd243c 100644 --- a/mongoose.h +++ b/mongoose.h @@ -2491,6 +2491,7 @@ struct mg_connection { unsigned is_resp : 1; // HTTP: response is still being generated unsigned is_readable : 1; // Socket is ready to read (epoll/select) unsigned is_writable : 1; // Socket is ready to write (epoll/select) + unsigned is_bsd : 1; // Built-in TCP/IP stack BSD socket }; // Runs one iteration of the event loop. @@ -5404,92 +5405,12 @@ struct mg_tcpip_spi { #if MG_ENABLE_BSD_SOCKETS +void mg_bsd_init(struct mg_mgr *); +void mg_bsd_poll(struct mg_mgr *); -#ifndef MG_ENABLE_BSD_PROTOTYPES -#include #include #include -#include -#include -#include -#include -#include -#include -#include -#include -#else -#include -typedef unsigned int socklen_t; -typedef uint32_t in_addr_t; -struct in_addr { in_addr_t s_addr; }; -struct in6_addr { uint8_t s6_addr[16]; }; -struct sockaddr { uint16_t sa_family; char sa_data[14]; }; -struct sockaddr_in { - uint16_t sin_family; - uint16_t sin_port; - struct in_addr sin_addr; - char sin_zero[8]; -}; -struct sockaddr_in6 { - uint16_t sin6_family; - uint16_t sin6_port; - uint32_t sin6_flowinfo; - struct in6_addr sin6_addr; - uint32_t sin6_scope_id; -}; -struct sockaddr_storage { uint16_t ss_family; char __ss_pad[126]; }; -struct hostent { - char *h_name; - char **h_aliases; - int h_addrtype; - int h_length; - char **h_addr_list; -}; -#define h_addr h_addr_list[0] -struct addrinfo { - int ai_flags; - int ai_family; - int ai_socktype; - int ai_protocol; - socklen_t ai_addrlen; - struct sockaddr *ai_addr; - char *ai_canonname; - struct addrinfo *ai_next; -}; -struct pollfd { int fd; short events; short revents; }; -#define AF_INET 2 -#define AF_INET6 10 -#define AF_UNSPEC 0 -#define PF_INET AF_INET -#define PF_INET6 AF_INET6 -#define PF_UNSPEC AF_UNSPEC -#define SOCK_STREAM 1 -#define SOCK_DGRAM 2 -#define IPPROTO_TCP 6 -#define IPPROTO_UDP 17 -#define INADDR_ANY 0 -#define INADDR_LOOPBACK 0x7f000001 -#define SOL_SOCKET 0xffff -#define SO_REUSEADDR 2 -#define SO_REUSEPORT 15 -#define SO_KEEPALIVE 9 -#define SO_ERROR 4 -#define SO_BROADCAST 6 -#define SO_RCVBUF 8 -#define SO_SNDBUF 7 -#define TCP_NODELAY 1 -#define MSG_DONTWAIT 0x40 -#define MSG_NOSIGNAL 0x4000 -#define MSG_PEEK 0x02 -#define F_GETFL 3 -#define F_SETFL 4 -#define O_NONBLOCK 0x0004 -#define SHUT_RD 0 -#define SHUT_WR 1 -#define SHUT_RDWR 2 -#define AI_PASSIVE 0x0001 -#define NI_MAXHOST 1025 -#define NI_MAXSERV 32 + #ifndef EAGAIN #define EAGAIN 11 #endif @@ -5514,68 +5435,57 @@ struct pollfd { int fd; short events; short revents; }; #ifndef ETIMEDOUT #define ETIMEDOUT 110 #endif -#define POLLIN 0x001 -#define POLLOUT 0x004 -#define POLLERR 0x008 -#define POLLHUP 0x010 -#define POLLNVAL 0x020 -uint16_t htons(uint16_t); -uint16_t ntohs(uint16_t); -uint32_t htonl(uint32_t); -uint32_t ntohl(uint32_t); +#ifndef EBADF +#define EBADF 9 +#endif +#ifndef EMFILE +#define EMFILE 24 +#endif +#ifndef ENOMEM +#define ENOMEM 12 +#endif +#ifndef EIO +#define EIO 5 +#endif +#ifndef EINVAL +#define EINVAL 22 +#endif +#ifndef EPERM +#define EPERM 1 +#endif +#ifndef EPIPE +#define EPIPE 32 +#endif +#ifndef EPROTONOSUPPORT +#define EPROTONOSUPPORT 93 +#endif + +#if MG_ENABLE_BSD_PROTOTYPES +#include +#ifndef MG_BSD_SELECT_H +#error "Add the Mongoose BSD compatibility directory to the include path" +#else +#include +#endif +#else +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include #endif // MG_ENABLE_BSD_PROTOTYPES -#define closesocket(a) close(a) - -#ifndef MG_BSD_BACKLOG -#define MG_BSD_BACKLOG 5 -#endif - -#ifndef MG_BSD_CONNECT_TIMEOUT_MS -#define MG_BSD_CONNECT_TIMEOUT_MS 10000 -#endif - -// Mongoose-specific API -void mg_bsd_init(void); // must be called before socket() -void mg_bsd_poll(struct mg_mgr *); // process pending BSD commands - -// Transport backend: implement these OR define MG_ENABLE_FREERTOS for the built-in backend -void *mg_bsd_transport_new(int domain, int type, int proto); -void mg_bsd_transport_free(void *t); -int mg_bsd_transport_listen(void *t, const struct sockaddr_in *addr); -void *mg_bsd_transport_accept(void *t, struct sockaddr_in *peer, bool nonblock); -ssize_t mg_bsd_transport_recv(void *t, void *buf, size_t len, bool nonblock); -ssize_t mg_bsd_transport_send(void *t, const void *buf, size_t len, bool nonblock); -int mg_bsd_transport_connect(void *t, const struct sockaddr_in *addr, bool nonblock); -void mg_bsd_transport_close(void *t); - -// Standard BSD socket API -int socket(int, int, int); -int bind(int, const struct sockaddr *, socklen_t); -int listen(int, int); -int accept(int, struct sockaddr *, socklen_t *); -int connect(int, const struct sockaddr *, socklen_t); -ssize_t send(int, const void *, size_t, int); -ssize_t recv(int, void *, size_t, int); -ssize_t sendto(int, const void *, size_t, int, const struct sockaddr *, socklen_t); -ssize_t recvfrom(int, void *, size_t, int, struct sockaddr *, socklen_t *); -int close(int); -int shutdown(int, int); -int fcntl(int, int, int); -int setsockopt(int, int, int, const void *, socklen_t); -int getsockopt(int, int, int, void *, socklen_t *); -int getsockname(int, struct sockaddr *, socklen_t *); -int getpeername(int, struct sockaddr *, socklen_t *); -int select(int, fd_set *, fd_set *, fd_set *, struct timeval *); -int poll(struct pollfd *, unsigned int, int); -struct hostent *gethostbyname(const char *); -int getaddrinfo(const char *, const char *, const struct addrinfo *, struct addrinfo **); -void freeaddrinfo(struct addrinfo *); -int inet_pton(int, const char *, void *); -const char *inet_ntop(int, const void *, char *, socklen_t); -in_addr_t inet_addr(const char *); -char *inet_ntoa(struct in_addr); +#include +#else +#define mg_bsd_init(mgr) +#define mg_bsd_poll(mgr) #endif // MG_ENABLE_BSD_SOCKETS diff --git a/src/bsd.c b/src/bsd.c index b1f41358..467cbeca 100644 --- a/src/bsd.c +++ b/src/bsd.c @@ -1,734 +1,208 @@ #include "bsd.h" +#include "dns.h" +#include -#if MG_ENABLE_BSD_SOCKETS - -// Queue-based BSD shim is currently TCP/SOCK_STREAM only. UDP/SOCK_DGRAM would -// need datagram boundaries, e.g. datagram queues or SOD/EOD framing. -// Unconnected UDP also needs per-packet peer addresses for sendto()/recvfrom(). -// It is also IPv4-only: transports store sockaddr_in and build IPv4 URLs. - -#ifndef MG_ENABLE_BSD_LOG -#define MG_ENABLE_BSD_LOG 0 -#define bsd_log(type, tag, a, b, c, n) -#else -#define MG_BSD_LOG_SOCK 1 -#define MG_BSD_LOG_ACCEPT 2 -#define MG_BSD_LOG_CLOSE 3 -#define MG_BSD_LOG_TRANSPORT 4 -#define MG_BSD_LOG_RESULT 5 -#define MG_BSD_LOG_CONNECT 6 -// Type is always a constant, so optimised builds fold the switch to one log. -#define bsd_log(type, tag, a, b, c, n) \ - do { \ - switch (type) { \ - case MG_BSD_LOG_SOCK: { \ - struct mg_bsd_sock *log_s = (struct mg_bsd_sock *) (a); \ - MG_DEBUG(("SOCK %s %ld %p", tag, (long) (n), log_s->t)); \ - break; \ - } \ - case MG_BSD_LOG_ACCEPT: { \ - struct mg_connection *log_mc = (struct mg_connection *) (a); \ - if ((n) >= 0) { \ - MG_DEBUG(("ACCEPT %lu %p %u %ld", log_mc->id, c, \ - (unsigned) mg_ntohs(log_mc->rem.port), (long) (n))); \ - } else { \ - MG_DEBUG(("ACCEPT %lu %p %u", log_mc->id, c, \ - (unsigned) mg_ntohs(log_mc->rem.port))); \ - } \ - break; \ - } \ - case MG_BSD_LOG_CLOSE: { \ - struct mg_connection *log_mc = (struct mg_connection *) (a); \ - if ((n) >= 0) MG_INFO(("CLOSE %s %lu %p %ld", tag, log_mc->id, b, \ - (long) (n))); \ - else MG_DEBUG(("CLOSE %s %lu %p", tag, log_mc->id, b)); \ - break; \ - } \ - case MG_BSD_LOG_TRANSPORT: \ - if ((n) >= 0) MG_INFO(("TRANSP %s %p %ld", tag, a, (long) (n))); \ - else MG_DEBUG(("TRANSP %s %p", tag, a)); \ - break; \ - case MG_BSD_LOG_RESULT: \ - MG_DEBUG(("RESULT %s %p %ld", tag, a, (long) (n))); \ - break; \ - case MG_BSD_LOG_CONNECT: \ - if ((c) != NULL) { \ - MG_DEBUG(("CONNECT %s %p %p %s %ld", tag, a, b, \ - (const char *) (c), (long) (n))); \ - } else { \ - MG_DEBUG(("CONNECT %s %p %p %ld", tag, a, b, (long) (n))); \ - } \ - break; \ - } \ - } while (0) -#endif - -struct mg_bsd_sock { - void *t; // opaque transport handle - int fd; - int domain, type, proto; - bool nonblock; - struct sockaddr_in addr; // bind address - struct sockaddr_in peer; // peer address (after accept/connect) - struct mg_bsd_sock *next; -}; - -#define MG_BSD_FD_BASE 17777 - -// static struct mg_mgr *s_mgr; -static struct mg_bsd_sock *s_socks; - -static struct mg_bsd_sock *get(int fd) { - if (fd < MG_BSD_FD_BASE) return NULL; - for (struct mg_bsd_sock *s = s_socks; s; s = s->next) - if (s->fd == fd) return s; - return NULL; -} - -static int alloc_sock(struct mg_bsd_sock *s) { - for (int fd = MG_BSD_FD_BASE; ; fd++) { - if (get(fd) == NULL) { s->fd = fd; break; } - } - s->next = s_socks; - s_socks = s; - return s->fd; -} - -static void release_sock(int fd) { - struct mg_bsd_sock **p = &s_socks; - while (*p && (*p)->fd != fd) p = &(*p)->next; - if (*p) *p = (*p)->next; -} - -int socket(int domain, int type, int proto) { - struct mg_bsd_sock *s = (struct mg_bsd_sock *) calloc(1, sizeof(*s)); - if (!s) { errno = ENOMEM; return -1; } - s->t = mg_bsd_transport_new(domain, type, proto); - if (!s->t) { free(s); return -1; } - if (alloc_sock(s) < 0) { mg_bsd_transport_free(s->t); free(s); errno = ENOMEM; return -1; } - s->domain = domain; s->type = type; s->proto = proto; - return s->fd; -} - -int bind(int fd, const struct sockaddr *addr, socklen_t len) { - struct mg_bsd_sock *s = get(fd); - if (!s) return -1; - memcpy(&s->addr, addr, len < sizeof(s->addr) ? len : sizeof(s->addr)); - return 0; -} - -int listen(int fd, int backlog) { - struct mg_bsd_sock *s = get(fd); - if (!s) return -1; - (void) backlog; - return mg_bsd_transport_listen(s->t, &s->addr); -} - -int accept(int fd, struct sockaddr *addr, socklen_t *addrlen) { - struct mg_bsd_sock *ls = get(fd); - if (!ls) return -1; - struct sockaddr_in peer; - memset(&peer, 0, sizeof(peer)); - void *t = mg_bsd_transport_accept(ls->t, &peer, ls->nonblock); - if (!t) return -1; // errno was set by transport_accept() - struct mg_bsd_sock *ns = (struct mg_bsd_sock *) calloc(1, sizeof(*ns)); - if (!ns || alloc_sock(ns) < 0) { mg_bsd_transport_close(t); free(ns); errno = ENOMEM; return -1; } - ns->t = t; ns->domain = ls->domain; ns->type = ls->type; ns->peer = peer; - if (addr && addrlen) { - size_t sz = sizeof(peer) < *addrlen ? sizeof(peer) : *addrlen; - memcpy(addr, &peer, sz); - *addrlen = (socklen_t) sizeof(peer); - } - return ns->fd; -} - -int connect(int fd, const struct sockaddr *addr, socklen_t len) { - struct mg_bsd_sock *s = get(fd); - if (!s) return -1; - (void) len; - return mg_bsd_transport_connect(s->t, (const struct sockaddr_in *) addr, s->nonblock); -} - -ssize_t send(int fd, const void *buf, size_t len, int flags) { - struct mg_bsd_sock *s = get(fd); - if (!s) return -1; - return mg_bsd_transport_send(s->t, buf, len, s->nonblock || (flags & MSG_DONTWAIT)); -} - -ssize_t recv(int fd, void *buf, size_t len, int flags) { - struct mg_bsd_sock *s = get(fd); - if (!s) return -1; - return mg_bsd_transport_recv(s->t, buf, len, s->nonblock || (flags & MSG_DONTWAIT)); -} - -ssize_t sendto(int fd, const void *buf, size_t len, int flags, - const struct sockaddr *dest, socklen_t addrlen) { - (void) fd; (void) buf; (void) len; (void) flags; (void) dest; (void) addrlen; - errno = EPROTONOSUPPORT; - return -1; -} - -ssize_t recvfrom(int fd, void *buf, size_t len, int flags, - struct sockaddr *src, socklen_t *addrlen) { - (void) fd; (void) buf; (void) len; (void) flags; (void) src; (void) addrlen; - errno = EPROTONOSUPPORT; - return -1; -} - -ssize_t write(int fd, const void *buf, size_t len) { return send(fd, buf, len, 0); } -ssize_t read(int fd, void *buf, size_t len) { return recv(fd, buf, len, 0); } - -int close(int fd) { - struct mg_bsd_sock *s = get(fd); - if (!s) return -1; - bsd_log(MG_BSD_LOG_SOCK, "CLOSE", s, NULL, NULL, (long) fd); - mg_bsd_transport_close(s->t); - bsd_log(MG_BSD_LOG_SOCK, "FREE", s, NULL, NULL, (long) fd); - release_sock(fd); - free(s); - return 0; -} - -int shutdown(int fd, int how) { (void) how; return close(fd); } - -int fcntl(int fd, int cmd, int arg) { - struct mg_bsd_sock *s = get(fd); - if (!s) return -1; - if (cmd == F_GETFL) return s->nonblock ? O_NONBLOCK : 0; - if (cmd == F_SETFL) { s->nonblock = (arg & O_NONBLOCK) != 0; return 0; } - return -1; -} - -int setsockopt(int fd, int level, int optname, const void *optval, socklen_t optlen) { - (void) fd; (void) level; (void) optname; (void) optval; (void) optlen; - return 0; -} - -int getsockopt(int fd, int level, int optname, void *optval, socklen_t *optlen) { - (void) fd; (void) level; (void) optname; - if (optval && optlen && *optlen >= sizeof(int)) { *(int *) optval = 0; *optlen = sizeof(int); } - return 0; -} - -int getsockname(int fd, struct sockaddr *addr, socklen_t *addrlen) { - struct mg_bsd_sock *s = get(fd); - if (!s) return -1; - size_t sz = sizeof(s->addr) < *addrlen ? sizeof(s->addr) : *addrlen; - memcpy(addr, &s->addr, sz); - *addrlen = (socklen_t) sz; - return 0; -} - -int getpeername(int fd, struct sockaddr *addr, socklen_t *addrlen) { - struct mg_bsd_sock *s = get(fd); - if (!s) { errno = ENOTCONN; return -1; } - size_t sz = sizeof(s->peer) < *addrlen ? sizeof(s->peer) : *addrlen; - memcpy(addr, &s->peer, sz); - *addrlen = (socklen_t) sz; - return 0; -} - -// select/poll: not implemented for queue-based backend -int select(int nfds, fd_set *r, fd_set *w, fd_set *e, struct timeval *tv) { - (void) nfds; (void) r; (void) w; (void) e; (void) tv; - return 0; -} - -int poll(struct pollfd *fds, unsigned int nfds, int timeout) { - (void) fds; (void) nfds; (void) timeout; - return 0; -} - -// DNS stubs (overridden in the FreeRTOS backend below) -#if !MG_ENABLE_FREERTOS -struct hostent *gethostbyname(const char *name) { (void) name; return NULL; } -int getaddrinfo(const char *node, const char *service, - const struct addrinfo *hints, struct addrinfo **res) { - (void) node; (void) service; (void) hints; (void) res; - return -1; -} -void freeaddrinfo(struct addrinfo *res) { (void) res; } -#endif - -int inet_pton(int af, const char *src, void *dst) { - struct mg_addr a; - memset(&a, 0, sizeof(a)); - if (af == AF_INET && mg_aton(mg_str_s(src), &a)) { memcpy(dst, &a.addr.ip4, 4); return 1; } - return 0; -} - -const char *inet_ntop(int af, const void *src, char *dst, socklen_t size) { - if (af == AF_INET && size >= 16) { - const uint8_t *ip = (const uint8_t *) src; - snprintf(dst, size, "%d.%d.%d.%d", ip[0], ip[1], ip[2], ip[3]); - return dst; - } - return NULL; -} - -in_addr_t inet_addr(const char *cp) { - struct mg_addr a; - memset(&a, 0, sizeof(a)); - return mg_aton(mg_str_s(cp), &a) ? a.addr.ip4 : (in_addr_t) -1; -} - -static char s_ntoa_buf[16]; -char *inet_ntoa(struct in_addr in) { - const uint8_t *ip = (const uint8_t *) &in.s_addr; - snprintf(s_ntoa_buf, sizeof(s_ntoa_buf), "%d.%d.%d.%d", ip[0], ip[1], ip[2], ip[3]); - return s_ntoa_buf; -} - -#ifdef MG_ENABLE_BSD_PROTOTYPES -uint16_t htons(uint16_t n) { return mg_htons(n); } -uint16_t ntohs(uint16_t n) { return mg_htons(n); } -uint32_t htonl(uint32_t n) { return mg_htonl(n); } -uint32_t ntohl(uint32_t n) { return mg_htonl(n); } -#endif - -// ============================================================ -// FreeRTOS + Mongoose transport backend -// ============================================================ -#if MG_ENABLE_FREERTOS - +#if MG_ENABLE_BSD_SOCKETS && MG_ENABLE_FREERTOS #include -#ifndef MG_BSD_CHUNK_SIZE -#define MG_BSD_CHUNK_SIZE 256 +static QueueHandle_t s_cmd_q; +static struct mg_bsd_cmd *s_waiters; + +static bool bsd_qsend(QueueHandle_t q, const void *item, TickType_t ticks) { + if (xQueueSend(q, item, ticks) == pdTRUE) return true; + MG_ERROR(("queue send failed %p", q)); + return false; +} #endif -#ifndef MG_BSD_Q_DEPTH -#define MG_BSD_Q_DEPTH 4 + +#if MG_ENABLE_BSD_SOCKETS +static struct mg_mgr *s_mgr; + +#ifndef MG_BSD_FD_BASE +#define MG_BSD_FD_BASE 11 +#endif +#ifndef MG_BSD_MAX_SOCKETS +#define MG_BSD_MAX_SOCKETS 64 #endif #ifndef MG_BSD_ACCEPT_MS -#define MG_BSD_ACCEPT_MS 3000 // Timeout for accept queue handoff +#define MG_BSD_ACCEPT_MS 3000 #endif -struct mg_bsd_chunk { uint8_t data[MG_BSD_CHUNK_SIZE]; uint16_t len; }; - -struct mg_xport { - struct mg_connection *c; // Mongoose connection, task1-only - QueueHandle_t recv_q; // task1 writes on MG_EV_READ, task2 reads in recv() - QueueHandle_t send_q; // task2 writes in send(), task1 drains on MG_EV_POLL - QueueHandle_t accept_q; // task1 writes on MG_EV_ACCEPT, task2 reads in accept() - struct sockaddr_in peer; - uint16_t rx_off; - uint64_t accept_expire; - int err; - bool closed; - bool orphan; // accepted connection not handed to socket owner - TaskHandle_t connect_waiter; // task blocked in connect(), woken by MG_EV_CONNECT - int *connect_result; // where to store 0/−1 connect outcome +// clang-format off +enum bsd_op { + BSD_OP_NONE, BSD_OP_SOCKET, BSD_OP_BIND, BSD_OP_LISTEN, BSD_OP_FCNTL, + BSD_OP_ACCEPT, BSD_OP_CONNECT, BSD_OP_RECV, BSD_OP_SEND, BSD_OP_SENDTO, + BSD_OP_RECVFROM, BSD_OP_CLOSE, BSD_OP_POLL, BSD_OP_SELECT, + BSD_OP_GETHOSTBYNAME, }; -enum mg_bsd_cmd_op { BSD_CMD_LISTEN, BSD_CMD_CLOSE, BSD_CMD_CONNECT, BSD_CMD_RESOLVE }; +// A BSD API request from a user task. Gets sent via the RTOS queue, and +// executed by the network task (mg_bsd_poll). Baremetal code calls directly struct mg_bsd_cmd { - enum mg_bsd_cmd_op type; - struct mg_xport *x; - char url[64]; - TaskHandle_t caller; - int *result; + int result; // return value + int err; // errno + enum bsd_op op; + bool started; // operation initiated (connect/resolve) + void *caller; // task to notify when done + struct mg_bsd_cmd *next; // waiter list link + union { // op-specific arguments + struct { int domain, type, proto; } op_socket; + struct { int fd, backlog; } op_listen; + struct { int fd, cmd, arg; } op_fcntl; + struct { int fd; } op_close; + struct { int fd; const struct sockaddr *addr; socklen_t len; } op_bind; + struct { int fd; const struct sockaddr *addr; socklen_t len; } op_connect; + struct { int fd; struct sockaddr *addr; socklen_t *addrlen; } op_accept; + struct { int fd; void *buf; size_t len; int flags; } op_recv; + struct { int fd; const void *buf; size_t len; int flags; } op_send; + struct { int fd; const void *buf; size_t len; int flags; const struct sockaddr *addr; socklen_t addrlen; } op_sendto; + struct { int fd; void *buf; size_t len; int flags; struct sockaddr *src; socklen_t *addrlen; } op_recvfrom; + struct { struct pollfd *fds; nfds_t nfds; uint64_t deadline; } op_poll; + struct { int nfds; fd_set *r, *w, *e; uint64_t deadline; } op_select; + struct { const char *name; struct hostent *result; } op_gethostbyname; + }; +}; +#define BSD_CMD_EMPTY {0, 0, BSD_OP_NONE, 0, 0, 0, {{0, 0, 0}}} +// clang-format on + +// The BSD socket outlives its Mongoose connection until the caller closes fd. +struct mg_bsd_sock { + int fd, err, backlog; + uint64_t accept_expire; + struct sockaddr_in addr; + struct mg_connection *c; + struct mg_bsd_sock *listener; + bool used, bound, closed, nonblock, is_udp, orphan; // closed: c was closed }; -static QueueHandle_t s_cmd_q; +static struct mg_bsd_sock s_socks[MG_BSD_MAX_SOCKETS > 0 ? + MG_BSD_MAX_SOCKETS : 1]; -static bool bsd_qsend(QueueHandle_t q, const void *item, TickType_t ticks, - bool reserve) { - BaseType_t ok = pdFALSE; - if (!reserve || uxQueueSpacesAvailable(q) > 1) ok = xQueueSend(q, item, ticks); - if (ok != pdTRUE) MG_ERROR(("%p", q)); - return ok == pdTRUE; +static void bsd_ev(struct mg_connection *, int, void *); + +// BSD-owned fd registry +static struct mg_bsd_sock *bsd_get(int fd) { + int offset; + struct mg_bsd_sock *s; + + if (fd < MG_BSD_FD_BASE) return NULL; + offset = fd - MG_BSD_FD_BASE; + if (offset >= MG_BSD_MAX_SOCKETS) return NULL; + s = &s_socks[offset]; + if (!s->used || s->orphan) return NULL; + return s; } -static bool xport_accept(struct mg_xport *x) { - if (bsd_qsend(x->accept_q, &x, 0, true)) { - x->orphan = false; // not an orphan anymore - x->accept_q = NULL; - return true; +static void bsd_attach(struct mg_bsd_sock *s, struct mg_connection *c) { + s->c = c; + c->fn = bsd_ev; + c->fn_data = s; + c->is_bsd = 1; + c->is_udp = s->is_udp; +} + +static struct mg_connection *bsd_new_conn(struct mg_bsd_sock *s) { + struct mg_connection *c = mg_alloc_conn(s_mgr); + if (c != NULL) { + bsd_attach(s, c); + LIST_ADD_HEAD(struct mg_connection, &s_mgr->conns, c); } - return false; // still an orphan, retry later + return c; } -// static DNS resolve state; not reentrant (see below) -static struct { struct mg_addr addr; bool error; TaskHandle_t caller; } s_resolve; -// gethostbyname statics (official isn't reentrant anyway, and is obsolete) -static struct hostent s_hostent; -static char *s_h_aliases[1]; -static char *s_h_addr_list[2]; -static uint32_t s_h_addr; -static char s_h_name[64]; +#if MG_ENABLE_FREERTOS +// Complete operations blocked on c before Mongoose destroys it. +static void bsd_finish_waiters(struct mg_bsd_sock *s, bool error) { + struct mg_bsd_cmd **p = &s_waiters; -static void resolve_cb(struct mg_connection *c, int ev, void *ev_data) { - bool notify = false; - if (ev == MG_EV_RESOLVE) { - s_resolve.addr = c->rem; - c->is_closing = 1; - MG_DEBUG(("%lu resolved", c->id)); - notify = true; - } else if (ev == MG_EV_ERROR) { - s_resolve.error = true; - MG_DEBUG(("%lu failed", c->id)); - notify = true; - } else if (ev == MG_EV_CLOSE) { - s_resolve.caller = NULL; // The resolver connection has fully unwound. - MG_DEBUG(("%lu done", c->id)); + if (s->orphan) return; // No BSD socket owner yet + + while (*p != NULL) { + struct mg_bsd_cmd *cmd = *p; + int fd = -1; + bool read = cmd->op == BSD_OP_RECV || cmd->op == BSD_OP_RECVFROM; + bool send = cmd->op == BSD_OP_SEND || cmd->op == BSD_OP_SENDTO; + + switch (cmd->op) { // clang-format off + case BSD_OP_ACCEPT: fd = cmd->op_accept.fd; break; + case BSD_OP_CONNECT: fd = cmd->op_connect.fd; break; + case BSD_OP_RECV: fd = cmd->op_recv.fd; break; + case BSD_OP_SEND: fd = cmd->op_send.fd; break; + case BSD_OP_SENDTO: fd = cmd->op_sendto.fd; break; + case BSD_OP_RECVFROM: fd = cmd->op_recvfrom.fd; break; + default: break; + } // clang-format on + if (fd != s->fd) { + p = &cmd->next; + continue; + } + + *p = cmd->next; + if (!error && s->c != NULL && s->c->is_draining && read) { + cmd->result = 0; // Orderly EOF + } else { + cmd->result = -1; + cmd->err = send ? EPIPE : EIO; // Mongoose error / forced close + } + xTaskNotifyGive((TaskHandle_t) cmd->caller); } - if (notify) xTaskNotifyGive(s_resolve.caller); - (void) ev_data; } +#endif -// Allocate transport for an accepted connection (recv+send queues only) -static struct mg_xport *xport_alloc(void) { - struct mg_xport *x = (struct mg_xport *) calloc(1, sizeof(*x)); - if (!x) return NULL; - // +1 keeps a terminal EOF/error slot for MG_EV_CLOSE - x->recv_q = xQueueCreate(MG_BSD_Q_DEPTH + 1, sizeof(struct mg_bsd_chunk)); - x->send_q = xQueueCreate(MG_BSD_Q_DEPTH, sizeof(struct mg_bsd_chunk)); - if (!x->recv_q || !x->send_q) { mg_bsd_transport_free(x); return NULL; } - x->orphan = true; // haven't attached this connection to its socket - return x; -} - -static void xport_ev(struct mg_connection *c, int ev, void *ev_data) { - struct mg_xport *x = (struct mg_xport *) c->fn_data; - if (!x) return; +static void bsd_ev(struct mg_connection *c, int ev, void *ev_data) { + struct mg_bsd_sock *s = (struct mg_bsd_sock *) c->fn_data; + if (s == NULL) return; if (ev == MG_EV_ACCEPT) { - // c is the new accepted connection; x is the listening transport - bool ok; - struct mg_xport *nx = xport_alloc(); - if (!nx) { c->fn_data = NULL; mg_error(c, "accept OOM"); return; } - nx->c = c; - nx->accept_q = x->accept_q; - nx->accept_expire = mg_millis() + MG_BSD_ACCEPT_MS; - nx->peer.sin_family = AF_INET; - nx->peer.sin_port = c->rem.port; - memcpy(&nx->peer.sin_addr, &c->rem.addr.ip4, 4); - c->fn_data = nx; - ok = xport_accept(nx); // leaves orphaned on failure, retry on POLL - bsd_log(MG_BSD_LOG_ACCEPT, NULL, c, x, nx, ok ? 1 : 0); - } else if (ev == MG_EV_POLL && x->orphan && x->accept_q) { - if (uxQueueSpacesAvailable(x->accept_q) > 1 && xport_accept(x)) { - bsd_log(MG_BSD_LOG_ACCEPT, NULL, c, NULL, x, 1); - } else if (mg_millis() > x->accept_expire) { // retried enough, give up - x->err = EIO; - mg_error(c, "accept_q"); + struct mg_bsd_sock *child = NULL, *t; + int count = 0; + for (t = s_socks; t < s_socks + MG_BSD_MAX_SOCKETS; t++) { + if (!t->used && child == NULL) child = t; // Find a free BSD socket slot, + if (t->orphan && t->listener == s) count++; // and count pending accepts } - } else if (ev == MG_EV_READ || ev == MG_EV_POLL) { - if (x->recv_q) { // let POLL resume abandoned READ processing on full queue - // Drain c->recv into recv_q in fixed-size chunks; task1 owns c->recv - size_t off = 0; - while (off < c->recv.len) { - struct mg_bsd_chunk chunk; - size_t n = c->recv.len - off; - if (n > MG_BSD_CHUNK_SIZE) n = MG_BSD_CHUNK_SIZE; - memcpy(chunk.data, c->recv.buf + off, n); - chunk.len = (uint16_t) n; - if (uxQueueSpacesAvailable(x->recv_q) <= 1 || - !bsd_qsend(x->recv_q, &chunk, 0, false)) break; // retry later - off += n; - } - mg_iobuf_del(&c->recv, 0, off); + if (count >= s->backlog) { + c->fn_data = NULL; + mg_error(c, "accept backlog full"); + } else if (child == NULL) { + c->fn_data = NULL; + mg_error(c, "accept rejected"); + } else { + memset(child, 0, sizeof(*child)); // Claim slot + child->used = true; + child->c = c; + child->listener = s; + child->accept_expire = mg_millis() + MG_BSD_ACCEPT_MS; + child->is_udp = s->is_udp; + child->orphan = true; + c->fn_data = child; + c->is_bsd = 1; + c->is_udp = child->is_udp; } - if (ev == MG_EV_POLL && x->send_q) { - // Drain send_q → mg_send(); task1 owns c - struct mg_bsd_chunk chunk; - while (xQueuePeek(x->send_q, &chunk, 0) == pdTRUE) { - if (!mg_send(c, chunk.data, chunk.len)) break; // retry later - xQueueReceive(x->send_q, &chunk, 0); - } - } - } else if (ev == MG_EV_CONNECT) { - // Outgoing connection established: wake the task blocked in connect() - if (x->connect_waiter) { - bsd_log(MG_BSD_LOG_CONNECT, "OK", x, c, NULL, 0); - if (x->connect_result) *x->connect_result = 0; - TaskHandle_t h = x->connect_waiter; - x->connect_waiter = NULL; x->connect_result = NULL; - xTaskNotifyGive(h); - } - } else if (ev == MG_EV_ERROR) { // remember error condition - if (x->err == 0) x->err = EIO; // and let CLOSE handle it - bsd_log(MG_BSD_LOG_CLOSE, "ERR", c, x, NULL, (long) x->err); + } else if (ev == MG_EV_ERROR) { + if (s->err == 0) s->err = EIO; +#if MG_ENABLE_FREERTOS + bsd_finish_waiters(s, true); +#endif } else if (ev == MG_EV_CLOSE) { - bsd_log(MG_BSD_LOG_CLOSE, "IN", c, x, NULL, -1); - x->c = NULL; x->closed = true; c->fn_data = NULL; - // If connect() is still waiting, signal failure - if (x->connect_waiter) { - bsd_log(MG_BSD_LOG_CONNECT, "FAIL", x, c, NULL, -1); - if (x->connect_result) *x->connect_result = -1; - TaskHandle_t h = x->connect_waiter; - x->connect_waiter = NULL; x->connect_result = NULL; - xTaskNotifyGive(h); - // The notified task owns the transport and can close/free it immediately. - return; - } - if (x->orphan) { // connection --> socket attachment failed - x->accept_q = NULL; // borrowed from listener; do not delete it here - bsd_log(MG_BSD_LOG_CLOSE, "FREE", c, x, NULL, -1); - mg_bsd_transport_free(x); // connection closed, release resources - return; - } - if (x->recv_q) { - struct mg_bsd_chunk eof; - bool ok; - memset(&eof, 0, sizeof(eof)); - eof.len = 0; - ok = bsd_qsend(x->recv_q, &eof, 0, false); - bsd_log(MG_BSD_LOG_CLOSE, "EOF>", c, x, NULL, ok ? 1 : 0); - if (!ok) MG_ERROR(("recv_q close notification failed")); - // recv() wakeup transfers control to the transport owner. - return; - } - if (x->accept_q) { - struct mg_xport *nil = NULL; - bool ok; - ok = bsd_qsend(x->accept_q, &nil, 0, false); - bsd_log(MG_BSD_LOG_CLOSE, "ACCEPT>", c, x, NULL, ok ? 1 : 0); - if (!ok) MG_ERROR(("accept_q close notification failed")); - // accept() wakeup transfers control to the transport owner. - return; - } - bsd_log(MG_BSD_LOG_CLOSE, "OUT", c, x, NULL, -1); +#if MG_ENABLE_FREERTOS + bsd_finish_waiters(s, false); +#endif + s->c = NULL; + s->closed = true; + c->fn_data = NULL; + if (s->orphan) memset(s, 0, sizeof(*s)); // release + } else if (ev == MG_EV_POLL && s->orphan && + mg_millis() > s->accept_expire) { + mg_error(c, "accept timeout"); } (void) ev_data; } -void mg_bsd_init(void) { - s_cmd_q = xQueueCreate(8, sizeof(struct mg_bsd_cmd)); -} - -void mg_bsd_poll(struct mg_mgr *mgr) { - struct mg_bsd_cmd cmd; - if (s_cmd_q == NULL) return; - while (xQueueReceive(s_cmd_q, &cmd, 0) == pdTRUE) { - bool notify = true; - if (cmd.type == BSD_CMD_LISTEN) { - struct mg_connection *c = mg_listen(mgr, cmd.url, xport_ev, cmd.x); - cmd.x->c = c; - *cmd.result = c ? 0 : -1; - } else if (cmd.type == BSD_CMD_CLOSE) { - bsd_log(MG_BSD_LOG_TRANSPORT, "CMD", cmd.x, NULL, NULL, -1); - if (cmd.x->c) { - cmd.x->c->fn_data = NULL; - cmd.x->c->is_draining = 1; - cmd.x->c = NULL; - } - cmd.x->closed = true; - *cmd.result = 0; - } else if (cmd.type == BSD_CMD_CONNECT) { - cmd.x->connect_waiter = cmd.caller; - cmd.x->connect_result = cmd.result; - struct mg_connection *c = mg_connect(mgr, cmd.url, xport_ev, cmd.x); - bsd_log(MG_BSD_LOG_CONNECT, "NEW", cmd.x, c, cmd.url, c ? 1 : 0); - cmd.x->c = c; - if (!c) { *cmd.result = -1; cmd.x->connect_waiter = NULL; cmd.x->connect_result = NULL; } - else notify = false; // xport_ev notifies when connected or on error - } else if (cmd.type == BSD_CMD_RESOLVE) { - s_resolve.error = false; - s_resolve.caller = cmd.caller; - struct mg_connection *c; - c = mg_alloc_conn(mgr); - if (c == NULL) { - } else { - c->fn = resolve_cb; - LIST_ADD_HEAD(struct mg_connection, &mgr->conns, c); - mg_call(c, MG_EV_OPEN, NULL); - MG_DEBUG(("%lu resolve %s", c->id, cmd.url)); - mg_resolve(c, cmd.url); - notify = false; // resolve_cb notifies when done - } - } - if (notify) xTaskNotifyGive(cmd.caller); - } -} - -void *mg_bsd_transport_new(int domain, int type, int proto) { - if (domain != AF_INET || type != SOCK_STREAM || - (proto != 0 && proto != IPPROTO_TCP)) { - errno = EPROTONOSUPPORT; - return NULL; - } - // For socket() calls: allocate accept_q only; recv/send added when needed. - // +1 keeps a terminal slot for MG_EV_CLOSE without blocking Mongoose. - struct mg_xport *x = (struct mg_xport *) calloc(1, sizeof(*x)); - if (!x) return NULL; - x->accept_q = xQueueCreate(MG_BSD_BACKLOG + 1, sizeof(struct mg_xport *)); - if (!x->accept_q) { free(x); return NULL; } - return x; -} - -void mg_bsd_transport_free(void *t) { - struct mg_xport *x = (struct mg_xport *) t; - if (!x) return; - bsd_log(MG_BSD_LOG_TRANSPORT, "FREE", x, NULL, NULL, -1); - if (x->recv_q) vQueueDelete(x->recv_q); - if (x->send_q) vQueueDelete(x->send_q); - if (x->accept_q) vQueueDelete(x->accept_q); - free(x); -} - -int mg_bsd_transport_listen(void *t, const struct sockaddr_in *addr) { - struct mg_xport *x = (struct mg_xport *) t; - int result = -1; - struct mg_bsd_cmd cmd = {BSD_CMD_LISTEN, x, {0}, xTaskGetCurrentTaskHandle(), &result}; - snprintf(cmd.url, sizeof(cmd.url), "tcp://0.0.0.0:%d", mg_ntohs(addr->sin_port)); - if (!bsd_qsend(s_cmd_q, &cmd, portMAX_DELAY, false)) return -1; - ulTaskNotifyTake(pdTRUE, portMAX_DELAY); - return result; -} - -void *mg_bsd_transport_accept(void *t, struct sockaddr_in *peer, bool nonblock) { - struct mg_xport *x = (struct mg_xport *) t; - struct mg_xport *nx = NULL; - TickType_t ticks = nonblock ? 0 : portMAX_DELAY; - if (xQueueReceive(x->accept_q, &nx, ticks) != pdTRUE) { - errno = x->closed ? (x->err ? x->err : EIO) : (nonblock ? EAGAIN : EIO); - return NULL; - } - if (!nx) { errno = x->err ? x->err : EIO; return NULL; } - if (peer) *peer = nx->peer; - return nx; -} - -ssize_t mg_bsd_transport_recv(void *t, void *buf, size_t len, bool nonblock) { - struct mg_xport *x = (struct mg_xport *) t; - uint8_t *p = (uint8_t *) buf; - size_t recvd = 0; - TickType_t ticks = nonblock ? 0 : portMAX_DELAY; - while (recvd < len) { - struct mg_bsd_chunk chunk; - if (xQueuePeek(x->recv_q, &chunk, recvd == 0 ? ticks : 0) != pdTRUE) { - if (recvd > 0) break; - errno = x->closed ? (x->err ? x->err : EIO) : (nonblock ? EAGAIN : EIO); - return -1; - } - if (chunk.len == 0) { - if (recvd > 0) break; - xQueueReceive(x->recv_q, &chunk, 0); - if (x->err) { errno = x->err; return -1; } - return 0; // EOF - } else { - size_t n = chunk.len - x->rx_off; - if (n > len - recvd) n = len - recvd; - memcpy(p + recvd, chunk.data + x->rx_off, n); - recvd += n; - x->rx_off = (uint16_t) (x->rx_off + n); - if (x->rx_off >= chunk.len) { - xQueueReceive(x->recv_q, &chunk, 0); - x->rx_off = 0; - } - } - } - return (ssize_t) recvd; -} - -ssize_t mg_bsd_transport_send(void *t, const void *buf, size_t len, bool nonblock) { - struct mg_xport *x = (struct mg_xport *) t; - if (x->closed) { errno = x->err ? x->err : EPIPE; return -1; } - size_t sent = 0; - TickType_t ticks = nonblock ? 0 : portMAX_DELAY; - while (sent < len) { - struct mg_bsd_chunk chunk; - size_t n = len - sent; - if (n > MG_BSD_CHUNK_SIZE) n = MG_BSD_CHUNK_SIZE; - memcpy(chunk.data, (const uint8_t *) buf + sent, n); - chunk.len = (uint16_t) n; - if (!bsd_qsend(x->send_q, &chunk, ticks, false)) { - errno = x->closed ? (x->err ? x->err : EPIPE) : (nonblock ? EAGAIN : EIO); - return sent > 0 ? (ssize_t) sent : -1; - } - sent += n; - } - return sent > 0 ? (ssize_t) sent : (errno = EAGAIN, -1); -} - -int mg_bsd_transport_connect(void *t, const struct sockaddr_in *addr, bool nonblock) { - struct mg_xport *x = (struct mg_xport *) t; - (void) nonblock; - if (!x->recv_q) x->recv_q = xQueueCreate(MG_BSD_Q_DEPTH + 1, sizeof(struct mg_bsd_chunk)); - if (!x->send_q) x->send_q = xQueueCreate(MG_BSD_Q_DEPTH, sizeof(struct mg_bsd_chunk)); - if (!x->recv_q || !x->send_q) { errno = ENOMEM; return -1; } - int result = -1; - struct mg_bsd_cmd cmd = {BSD_CMD_CONNECT, x, {0}, xTaskGetCurrentTaskHandle(), &result}; - uint8_t *ip = (uint8_t *) &addr->sin_addr.s_addr; - snprintf(cmd.url, sizeof(cmd.url), "tcp://%d.%d.%d.%d:%d", - ip[0], ip[1], ip[2], ip[3], mg_ntohs(addr->sin_port)); - bsd_log(MG_BSD_LOG_CONNECT, "REQ", x, NULL, cmd.url, -1); - if (!bsd_qsend(s_cmd_q, &cmd, portMAX_DELAY, false)) return -1; - ulTaskNotifyTake(pdTRUE, portMAX_DELAY); - if (result != 0) errno = x->err ? x->err : EIO; - return result; -} - -// gethostbyname: resolve via Mongoose DNS (not reentrant) -struct hostent *gethostbyname(const char *name) { - struct mg_bsd_cmd cmd = {BSD_CMD_RESOLVE, NULL, {0}, xTaskGetCurrentTaskHandle(), NULL}; - snprintf(cmd.url, sizeof(cmd.url), "%s", name); - if (!bsd_qsend(s_cmd_q, &cmd, portMAX_DELAY, false)) return NULL; - ulTaskNotifyTake(pdTRUE, portMAX_DELAY); - if (s_resolve.error) return NULL; - s_h_addr = s_resolve.addr.addr.ip4; - s_h_addr_list[0] = (char *) &s_h_addr; - s_h_addr_list[1] = NULL; - s_h_aliases[0] = NULL; - snprintf(s_h_name, sizeof(s_h_name), "%s", name); - s_hostent.h_name = s_h_name; - s_hostent.h_aliases = s_h_aliases; - s_hostent.h_addrtype = AF_INET; - s_hostent.h_length = 4; - s_hostent.h_addr_list = s_h_addr_list; - return &s_hostent; -} - -int getaddrinfo(const char *node, const char *service, - const struct addrinfo *hints, struct addrinfo **res) { - struct hostent *h = gethostbyname(node); - if (!h) return -1; - struct addrinfo *ai = (struct addrinfo *) calloc(1, sizeof(*ai)); - struct sockaddr_in *sa = (struct sockaddr_in *) calloc(1, sizeof(*sa)); - if (!ai || !sa) { free(ai); free(sa); return -1; } - sa->sin_family = AF_INET; - memcpy(&sa->sin_addr, h->h_addr, 4); - if (service) sa->sin_port = htons((uint16_t) atoi(service)); - ai->ai_family = AF_INET; - ai->ai_socktype = hints ? hints->ai_socktype : SOCK_STREAM; - ai->ai_addrlen = sizeof(*sa); - ai->ai_addr = (struct sockaddr *) sa; - *res = ai; - return 0; -} - -void freeaddrinfo(struct addrinfo *res) { - while (res) { - struct addrinfo *next = res->ai_next; - free(res->ai_addr); - free(res); - res = next; - } -} - -void mg_bsd_transport_close(void *t) { - struct mg_xport *x = (struct mg_xport *) t; - bsd_log(MG_BSD_LOG_TRANSPORT, "CLOSE", x, NULL, NULL, -1); - if (!x->closed && x->c) { - int result = 0; - struct mg_bsd_cmd cmd = {BSD_CMD_CLOSE, x, {0}, xTaskGetCurrentTaskHandle(), &result}; - bool ok = bsd_qsend(s_cmd_q, &cmd, portMAX_DELAY, false); - bsd_log(MG_BSD_LOG_TRANSPORT, "CMD>", x, NULL, NULL, ok ? 1 : 0); - if (ok) { - ulTaskNotifyTake(pdTRUE, portMAX_DELAY); - bsd_log(MG_BSD_LOG_RESULT, "CMD", x, NULL, NULL, (long) result); - } - } - bsd_log(MG_BSD_LOG_TRANSPORT, "FREE_REQ", x, NULL, NULL, -1); - mg_bsd_transport_free(x); -} - +#if MG_ENABLE_FREERTOS #ifndef MG_WAKEUP_QUEUE_DEPTH #define MG_WAKEUP_QUEUE_DEPTH 4 #endif - struct wumsg { unsigned long id; size_t len; @@ -764,8 +238,8 @@ bool mg_wakeup_init(struct mg_mgr *mgr) { if (mgr->pipe.q != NULL) return true; mgr->pipe.q = xQueueCreate(MG_WAKEUP_QUEUE_DEPTH, sizeof(void *)); if (mgr->pipe.q == NULL) { - MG_ERROR(("Cannot create queue")); - return false; + MG_ERROR(("Cannot create queue")); + return false; } c = mg_alloc_conn(mgr); if (c == NULL) { @@ -793,12 +267,698 @@ bool mg_wakeup(struct mg_mgr *mgr, unsigned long conn_id, const void *buf, m->id = conn_id; m->len = len; memcpy(m->data, buf, len); - if (!bsd_qsend((QueueHandle_t) mgr->pipe.q, &m, 0, false)) { + if (!bsd_qsend((QueueHandle_t) mgr->pipe.q, &m, 0)) { free(m); return false; } return true; } +#endif + +enum { BSD_READ = 1, BSD_WRITE = 2, BSD_ERROR = 4 }; + +static int bsd_ready(struct mg_bsd_sock *s) { + struct mg_bsd_sock *t; + int ready = 0; + + if (s->err != 0) return BSD_READ | BSD_ERROR; + if (s->c == NULL) return s->closed ? BSD_READ | BSD_ERROR : 0; + if (s->c->is_listening) { + for (t = s_socks; t < s_socks + MG_BSD_MAX_SOCKETS; t++) { + if (t->orphan && t->listener == s) return BSD_READ; + } + return 0; + } + if (s->c->recv.len > 0 || s->c->is_draining) ready |= BSD_READ; + if (!s->is_udp && !s->c->is_connecting && !s->c->is_closing && + !s->c->is_draining) + ready |= BSD_WRITE; // Established TCP socket + return ready; +} + +static void bsd_close(struct mg_bsd_sock *s) { + struct mg_bsd_sock *t; + +#if MG_ENABLE_FREERTOS + bsd_finish_waiters(s, true); +#endif + for (t = s_socks; t < s_socks + MG_BSD_MAX_SOCKETS; t++) { + if (t->orphan && t->listener == s) { + if (t->c != NULL) { + t->c->fn_data = NULL; + mg_close_conn(t->c); + t->c = NULL; + } + memset(t, 0, sizeof(*t)); // Release orphan + } + } + if (s->c != NULL) { + s->c->fn_data = NULL; + mg_close_conn(s->c); + s->c = NULL; + } + memset(s, 0, sizeof(*s)); // Release socket +} + +static char s_hostent_name[64]; +static uint32_t s_hostent_addr; +static char *s_hostent_addr_list[2] = {(char *) &s_hostent_addr, NULL}; +static struct hostent s_hostent = {s_hostent_name, NULL, AF_INET, 4, + s_hostent_addr_list}; +static volatile int s_resolve_done; + +static void bsd_resolve_fn(struct mg_connection *c, int ev, void *ev_data) { + (void) ev_data; + if (ev == MG_EV_RESOLVE) { + s_hostent_addr = c->rem.addr.ip4; + s_resolve_done = 1; + c->is_closing = 1; + } else if (ev == MG_EV_ERROR) { + s_resolve_done = -1; + } +} + +// Execute a command. Runs in the network task (mg_bsd_poll) on FreeRTOS, or +// in the caller otherwise. Returns true when done, false when the operation +// must wait for the network task and be re-checked later. +static bool bsd_run(struct mg_bsd_cmd *cmd) { + struct mg_connection *c; + struct mg_bsd_sock *s; + switch (cmd->op) { + case BSD_OP_NONE: + cmd->result = -1; + return true; + case BSD_OP_SOCKET: + if (s_mgr == NULL || cmd->op_socket.domain != AF_INET || + (cmd->op_socket.type != SOCK_STREAM && + cmd->op_socket.type != SOCK_DGRAM)) { + cmd->result = -1, cmd->err = EPERM; + } else { + for (s = s_socks; s < s_socks + MG_BSD_MAX_SOCKETS; s++) { + if (!s->used) break; // Find a free BSD socket registry slot + } + if (s == s_socks + MG_BSD_MAX_SOCKETS) { + cmd->result = -1, cmd->err = EMFILE; + } else { + memset(s, 0, sizeof(*s)); // Claim slot + s->used = true; + s->is_udp = cmd->op_socket.type == SOCK_DGRAM; + s->fd = MG_BSD_FD_BASE + (int) (s - s_socks); + cmd->result = s->fd; + } + } + return true; + case BSD_OP_BIND: + if ((s = bsd_get(cmd->op_bind.fd)) == NULL) { + cmd->result = -1, cmd->err = EBADF; + } else { + const struct sockaddr_in *sa = + (const struct sockaddr_in *) cmd->op_bind.addr; + memcpy(&s->addr, sa, sizeof(s->addr)); + s->bound = true; + if (!s->is_udp) { + cmd->result = 0; + } else if (s->c == NULL && (c = bsd_new_conn(s)) == NULL) { + cmd->result = -1, cmd->err = ENOMEM; + } else { + c = s->c; + c->loc.port = s->addr.sin_port; + c->loc.addr.ip4 = s->addr.sin_addr.s_addr; + c->loc.is_ip6 = false; + cmd->result = 0; + } + } + return true; + case BSD_OP_LISTEN: { + char url[32]; + + s = bsd_get(cmd->op_listen.fd); + if (s == NULL) { + cmd->result = -1, cmd->err = EBADF; + } else if (!s->bound || s->is_udp || cmd->op_listen.backlog < 1) { + cmd->result = -1, cmd->err = EINVAL; + } else if (s->c != NULL && !s->c->is_listening) { + cmd->result = -1, cmd->err = EINVAL; + } else if (s->c == NULL) { + mg_snprintf(url, sizeof(url), "tcp://%M:%hu", mg_print_ip4, + &s->addr.sin_addr.s_addr, mg_ntohs(s->addr.sin_port)); + if ((c = mg_listen(s_mgr, url, bsd_ev, s)) == NULL) { + cmd->result = -1, cmd->err = ENOMEM; + } else { + bsd_attach(s, c); + s->backlog = cmd->op_listen.backlog; + cmd->result = 0; + } + } else { + s->backlog = cmd->op_listen.backlog; + cmd->result = 0; + } + return true; + } + case BSD_OP_FCNTL: + if ((s = bsd_get(cmd->op_fcntl.fd)) == NULL) { + cmd->result = -1, cmd->err = EBADF; + } else if (cmd->op_fcntl.cmd == F_GETFL) { + cmd->result = s->nonblock ? O_NONBLOCK : 0; + } else if (cmd->op_fcntl.cmd == F_SETFL) { + s->nonblock = (cmd->op_fcntl.arg & O_NONBLOCK) != 0; + cmd->result = 0; + } else { + cmd->result = -1, cmd->err = EINVAL; + } + return true; + case BSD_OP_ACCEPT: { + int i; + struct mg_bsd_sock *child; + + s = bsd_get(cmd->op_accept.fd); + if (s == NULL || s->c == NULL || !s->c->is_listening) { + cmd->result = -1, cmd->err = EINVAL; + return true; + } + for (i = 0, child = s_socks; i < MG_BSD_MAX_SOCKETS; i++, child++) { + if (child->orphan && child->listener == s) { + c = child->c; + child->orphan = false; + child->listener = NULL; + child->fd = MG_BSD_FD_BASE + i; + cmd->result = child->fd; + if (cmd->op_accept.addr != NULL && cmd->op_accept.addrlen != NULL) { + struct sockaddr_in *sa = (struct sockaddr_in *) cmd->op_accept.addr; + sa->sin_family = AF_INET; + sa->sin_port = c->rem.port; + sa->sin_addr.s_addr = c->rem.addr.ip4; + *cmd->op_accept.addrlen = sizeof(*sa); + } + return true; + } + } + if (s->nonblock) { + cmd->result = -1, cmd->err = EAGAIN; + return true; + } +#if MG_ENABLE_FREERTOS + return false; // No connection yet, wait +#else + cmd->result = -1, cmd->err = EAGAIN; + return true; +#endif + } + case BSD_OP_CONNECT: + if ((s = bsd_get(cmd->op_connect.fd)) == NULL) { + cmd->result = -1, cmd->err = EBADF; + return true; + } + if (!cmd->started && s->c == NULL) { + if (s->closed || (c = bsd_new_conn(s)) == NULL) { + cmd->result = -1, cmd->err = s->closed ? EIO : ENOMEM; + return true; + } + } + if ((c = s->c) == NULL) { + cmd->result = -1, cmd->err = s->err ? s->err : EIO; + return true; + } + if (!cmd->started) { + const struct sockaddr_in *sa = + (const struct sockaddr_in *) cmd->op_connect.addr; + c->rem.port = sa->sin_port; + c->rem.addr.ip4 = sa->sin_addr.s_addr; + c->rem.is_ip6 = false; + c->is_client = 1; + mg_connect_resolved(c); + cmd->started = true; + } + if (c->is_closing) { + cmd->result = -1, cmd->err = ECONNREFUSED; + return true; + } + if (!c->is_connecting) { + cmd->result = 0; + return true; + } + if (s->nonblock) { + cmd->result = -1, cmd->err = EINPROGRESS; + return true; + } +#if MG_ENABLE_FREERTOS + return false; // Still connecting, wait +#else + cmd->result = -1, cmd->err = EINPROGRESS; + return true; +#endif + case BSD_OP_RECV: + if ((s = bsd_get(cmd->op_recv.fd)) == NULL) { + cmd->result = -1, cmd->err = EBADF; + return true; + } + if ((c = s->c) == NULL) { + cmd->result = s->closed && s->err == 0 ? 0 : -1; + cmd->err = s->closed ? s->err : ENOTCONN; + return true; + } + if (c->recv.len > 0) { + size_t n = cmd->op_recv.len; + if (c->recv.len < n) n = c->recv.len; + memcpy(cmd->op_recv.buf, c->recv.buf, n); + if (!(cmd->op_recv.flags & MSG_PEEK)) mg_iobuf_del(&c->recv, 0, n); + cmd->result = (int) n; + return true; + } + if (s->err != 0) { + cmd->result = -1, cmd->err = s->err; + return true; + } + if (c->is_draining) { // Orderly shutdown by peer + cmd->result = 0; + return true; + } + if (!s->nonblock && !(cmd->op_recv.flags & MSG_DONTWAIT)) { +#if MG_ENABLE_FREERTOS + return false; // No data yet, wait +#endif + } + cmd->result = -1, cmd->err = EAGAIN; + return true; + case BSD_OP_SEND: + if ((s = bsd_get(cmd->op_send.fd)) == NULL) { + cmd->result = -1, cmd->err = EBADF; + } else if ((c = s->c) == NULL || s->err != 0 || c->is_closing || + c->is_draining) { + cmd->result = -1, cmd->err = EPIPE; + } else if (mg_send(c, cmd->op_send.buf, cmd->op_send.len)) { + cmd->result = (int) cmd->op_send.len; + } else if (s->nonblock || (cmd->op_send.flags & MSG_DONTWAIT)) { + cmd->result = -1, cmd->err = EAGAIN; + } else { +#if MG_ENABLE_FREERTOS + return false; // No send buffer space yet, wait +#else + cmd->result = -1, cmd->err = EAGAIN; +#endif + } + return true; + case BSD_OP_SENDTO: + if ((s = bsd_get(cmd->op_sendto.fd)) == NULL) { + cmd->result = -1, cmd->err = EBADF; + } else if ((c = s->c) == NULL || s->err != 0 || c->is_closing || + c->is_draining) { + cmd->result = -1, cmd->err = EPIPE; + } else { + const struct sockaddr_in *sa = + (const struct sockaddr_in *) cmd->op_sendto.addr; + c->rem.port = sa->sin_port; + c->rem.addr.ip4 = sa->sin_addr.s_addr; + c->rem.is_ip6 = false; + if (mg_send(c, cmd->op_sendto.buf, cmd->op_sendto.len)) { + cmd->result = (int) cmd->op_sendto.len; + } else if (s->nonblock || (cmd->op_sendto.flags & MSG_DONTWAIT)) { + cmd->result = -1, cmd->err = EAGAIN; + } else { +#if MG_ENABLE_FREERTOS + return false; // No send buffer space yet, wait +#else + cmd->result = -1, cmd->err = EAGAIN; +#endif + } + } + return true; + case BSD_OP_RECVFROM: + if ((s = bsd_get(cmd->op_recvfrom.fd)) == NULL) { + cmd->result = -1, cmd->err = EBADF; + return true; + } + if ((c = s->c) == NULL) { + cmd->result = s->closed && s->err == 0 ? 0 : -1; + cmd->err = s->closed ? s->err : ENOTCONN; + return true; + } + if (c->recv.len > 0) { + size_t n = cmd->op_recvfrom.len; + if (c->recv.len < n) n = c->recv.len; + memcpy(cmd->op_recvfrom.buf, c->recv.buf, n); + if (!(cmd->op_recvfrom.flags & MSG_PEEK)) mg_iobuf_del(&c->recv, 0, n); + if (cmd->op_recvfrom.src != NULL && cmd->op_recvfrom.addrlen != NULL) { + struct sockaddr_in *sa = (struct sockaddr_in *) cmd->op_recvfrom.src; + sa->sin_family = AF_INET; + sa->sin_port = c->rem.port; + sa->sin_addr.s_addr = c->rem.addr.ip4; + *cmd->op_recvfrom.addrlen = sizeof(*sa); + } + cmd->result = (int) n; + return true; + } + if (s->err != 0) { + cmd->result = -1, cmd->err = s->err; + return true; + } + if (!s->nonblock && !(cmd->op_recvfrom.flags & MSG_DONTWAIT)) { +#if MG_ENABLE_FREERTOS + return false; // No datagram yet, wait +#endif + } + cmd->result = -1, cmd->err = EAGAIN; + return true; + case BSD_OP_CLOSE: + if ((s = bsd_get(cmd->op_close.fd)) == NULL) { + cmd->result = -1, cmd->err = EBADF; + } else { + bsd_close(s); + cmd->result = 0; + } + return true; + case BSD_OP_POLL: { + struct pollfd *p; + int i, n = 0, ready; + + for (i = 0, p = cmd->op_poll.fds; i < (int) cmd->op_poll.nfds; + i++, p++) { + p->revents = 0; + if (p->fd < 0) continue; + if ((s = bsd_get(p->fd)) == NULL) { + p->revents = POLLNVAL; + } else { + ready = bsd_ready(s); + if ((p->events & POLLIN) && (ready & BSD_READ)) + p->revents |= POLLIN; + if ((p->events & POLLOUT) && (ready & BSD_WRITE)) + p->revents |= POLLOUT; + if (s->err != 0) p->revents |= POLLERR; + if (s->closed) p->revents |= POLLHUP; + } + if (p->revents != 0) n++; + } +#if MG_ENABLE_FREERTOS + if (n == 0 && mg_millis() < cmd->op_poll.deadline) + return false; // Keep caller pollfd[] intact while waiting +#endif + cmd->result = n; + return true; + } + case BSD_OP_SELECT: { + fd_set rr, ww, ee; + fd_set *r = cmd->op_select.r; + fd_set *w = cmd->op_select.w; + fd_set *e = cmd->op_select.e; + int fd, n = 0, want_read, want_write, want_error, ready; +#if MG_ENABLE_BSD_PROTOTYPES + int i; + __fd_mask bits; + int bitsize = (int) (sizeof(bits) * 8); +#endif + + if (cmd->op_select.nfds < 0 || cmd->op_select.nfds > FD_SETSIZE) { + cmd->result = -1, cmd->err = EINVAL; + return true; + } + FD_ZERO(&rr); + FD_ZERO(&ww); + FD_ZERO(&ee); +#if MG_ENABLE_BSD_PROTOTYPES + for (i = 0; i < (cmd->op_select.nfds + bitsize - 1) / bitsize; i++) { + bits = 0; + if (r != NULL) bits |= r->__fds_bits[i]; + if (w != NULL) bits |= w->__fds_bits[i]; + if (e != NULL) bits |= e->__fds_bits[i]; + while (bits != 0) { + fd = i * bitsize + (int) __builtin_ctzl(bits); + bits &= bits - 1; + if (fd >= cmd->op_select.nfds) continue; + want_read = r != NULL && FD_ISSET(fd, r); + want_write = w != NULL && FD_ISSET(fd, w); + want_error = e != NULL && FD_ISSET(fd, e); + if ((s = bsd_get(fd)) == NULL) goto err; + ready = bsd_ready(s); + if (want_read && (ready & BSD_READ)) FD_SET(fd, &rr); + if (want_write && (ready & BSD_WRITE)) FD_SET(fd, &ww); + if (want_error && (ready & BSD_ERROR)) FD_SET(fd, &ee); + if ((want_read && (ready & BSD_READ)) || + (want_write && (ready & BSD_WRITE)) || + (want_error && (ready & BSD_ERROR))) + n++; + } + } +#else + for (fd = 0; fd < cmd->op_select.nfds; fd++) { + want_read = r != NULL && FD_ISSET(fd, r); + want_write = w != NULL && FD_ISSET(fd, w); + want_error = e != NULL && FD_ISSET(fd, e); + if (!want_read && !want_write && !want_error) continue; + if ((s = bsd_get(fd)) == NULL) goto err; + ready = bsd_ready(s); + if (want_read && (ready & BSD_READ)) FD_SET(fd, &rr); + if (want_write && (ready & BSD_WRITE)) FD_SET(fd, &ww); + if (want_error && (ready & BSD_ERROR)) FD_SET(fd, &ee); + if ((want_read && (ready & BSD_READ)) || + (want_write && (ready & BSD_WRITE)) || + (want_error && (ready & BSD_ERROR))) + n++; + } +#endif +#if MG_ENABLE_FREERTOS + if (n == 0 && mg_millis() < cmd->op_select.deadline) + return false; // Keep fd sets while waiting +#endif + if (r != NULL) *r = rr; + if (w != NULL) *w = ww; + if (e != NULL) *e = ee; + cmd->result = n; + return true; + +err: + cmd->result = -1, cmd->err = EBADF; + return true; + } + case BSD_OP_GETHOSTBYNAME: + if (!cmd->started) { + struct mg_connection *r = mg_alloc_conn(s_mgr); + if (r == NULL) { + cmd->op_gethostbyname.result = NULL; + return true; + } + r->fn = bsd_resolve_fn; + LIST_ADD_HEAD(struct mg_connection, &s_mgr->conns, r); + mg_call(r, MG_EV_OPEN, NULL); + s_resolve_done = 0; + mg_resolve(r, cmd->op_gethostbyname.name); + cmd->started = true; +#if MG_ENABLE_FREERTOS + return false; // Wait for DNS response +#endif + } + if (s_resolve_done != 0) { + if (s_resolve_done < 0) { + cmd->op_gethostbyname.result = NULL; + } else { + mg_snprintf(s_hostent_name, sizeof(s_hostent_name), "%s", + cmd->op_gethostbyname.name); + cmd->op_gethostbyname.result = &s_hostent; + } + return true; + } +#if MG_ENABLE_FREERTOS + return false; // Still resolving +#else + cmd->op_gethostbyname.result = NULL; + return true; +#endif + } + return true; +} + +static void bsd_dispatch(struct mg_bsd_cmd *cmd) { +#if MG_ENABLE_FREERTOS + if (s_cmd_q == NULL) { + cmd->result = -1, cmd->err = EPERM; + } else { + cmd->caller = xTaskGetCurrentTaskHandle(); + if (!bsd_qsend(s_cmd_q, &cmd, portMAX_DELAY)) { + cmd->result = -1, cmd->err = EIO; + } else { + ulTaskNotifyTake(pdTRUE, portMAX_DELAY); + } + } +#else + bsd_run(cmd); +#endif +} + +int socket(int domain, int type, int proto) { + struct mg_bsd_cmd cmd = BSD_CMD_EMPTY; + cmd.op = BSD_OP_SOCKET, cmd.op_socket.domain = domain, + cmd.op_socket.type = type, cmd.op_socket.proto = proto; + bsd_dispatch(&cmd); + errno = cmd.err; + return cmd.result; +} + +int bind(int fd, const struct sockaddr *addr, socklen_t len) { + struct mg_bsd_cmd cmd = BSD_CMD_EMPTY; + cmd.op = BSD_OP_BIND, cmd.op_bind.fd = fd, cmd.op_bind.addr = addr, + cmd.op_bind.len = len; + bsd_dispatch(&cmd); + errno = cmd.err; + return cmd.result; +} + +int listen(int fd, int backlog) { + struct mg_bsd_cmd cmd = BSD_CMD_EMPTY; + cmd.op = BSD_OP_LISTEN, cmd.op_listen.fd = fd, + cmd.op_listen.backlog = backlog; + bsd_dispatch(&cmd); + errno = cmd.err; + return cmd.result; +} + +int fcntl(int fd, int fcmd, ...) { + struct mg_bsd_cmd cmd = BSD_CMD_EMPTY; + int arg = 0; + + if (fcmd == F_SETFL) { + va_list ap; + + va_start(ap, fcmd); + arg = va_arg(ap, int); + va_end(ap); + } + cmd.op = BSD_OP_FCNTL, cmd.op_fcntl.fd = fd, cmd.op_fcntl.cmd = fcmd, + cmd.op_fcntl.arg = arg; + bsd_dispatch(&cmd); + errno = cmd.err; + return cmd.result; +} + +int accept(int fd, struct sockaddr *addr, socklen_t *addrlen) { + struct mg_bsd_cmd cmd = BSD_CMD_EMPTY; + cmd.op = BSD_OP_ACCEPT, cmd.op_accept.fd = fd, cmd.op_accept.addr = addr, + cmd.op_accept.addrlen = addrlen; + bsd_dispatch(&cmd); + errno = cmd.err; + return cmd.result; +} + +int connect(int fd, const struct sockaddr *addr, socklen_t len) { + struct mg_bsd_cmd cmd = BSD_CMD_EMPTY; + cmd.op = BSD_OP_CONNECT, cmd.op_connect.fd = fd, cmd.op_connect.addr = addr, + cmd.op_connect.len = len; + bsd_dispatch(&cmd); + errno = cmd.err; + return cmd.result; +} + +ssize_t recv(int fd, void *buf, size_t len, int flags) { + struct mg_bsd_cmd cmd = BSD_CMD_EMPTY; + cmd.op = BSD_OP_RECV, cmd.op_recv.fd = fd, cmd.op_recv.buf = buf, + cmd.op_recv.len = len, cmd.op_recv.flags = flags; + bsd_dispatch(&cmd); + errno = cmd.err; + return cmd.result; +} + +ssize_t send(int fd, const void *buf, size_t len, int flags) { + struct mg_bsd_cmd cmd = BSD_CMD_EMPTY; + cmd.op = BSD_OP_SEND, cmd.op_send.fd = fd, cmd.op_send.buf = (void *) buf, + cmd.op_send.len = len, cmd.op_send.flags = flags; + bsd_dispatch(&cmd); + errno = cmd.err; + return cmd.result; +} + +ssize_t sendto(int fd, const void *buf, size_t len, int flags, + const struct sockaddr *dest, socklen_t addrlen) { + struct mg_bsd_cmd cmd = BSD_CMD_EMPTY; + cmd.op = BSD_OP_SENDTO, cmd.op_sendto.fd = fd, + cmd.op_sendto.buf = (void *) buf, cmd.op_sendto.len = len, + cmd.op_sendto.flags = flags, cmd.op_sendto.addr = dest, + cmd.op_sendto.addrlen = addrlen; + bsd_dispatch(&cmd); + errno = cmd.err; + return cmd.result; +} + +ssize_t recvfrom(int fd, void *buf, size_t len, int flags, struct sockaddr *src, + socklen_t *addrlen) { + struct mg_bsd_cmd cmd = BSD_CMD_EMPTY; + cmd.op = BSD_OP_RECVFROM, cmd.op_recvfrom.fd = fd, cmd.op_recvfrom.buf = buf, + cmd.op_recvfrom.len = len, cmd.op_recvfrom.flags = flags, + cmd.op_recvfrom.src = src, cmd.op_recvfrom.addrlen = addrlen; + bsd_dispatch(&cmd); + errno = cmd.err; + return cmd.result; +} + +int close(int fd) { + struct mg_bsd_cmd cmd = BSD_CMD_EMPTY; + cmd.op = BSD_OP_CLOSE, cmd.op_close.fd = fd; + bsd_dispatch(&cmd); + errno = cmd.err; + return cmd.result; +} + +int poll(struct pollfd *fds, nfds_t nfds, int timeout) { + struct mg_bsd_cmd cmd = BSD_CMD_EMPTY; + uint64_t deadline = timeout < 0 ? (uint64_t) ~0 + : mg_millis() + (uint64_t) timeout; + + cmd.op = BSD_OP_POLL, cmd.op_poll.fds = fds, cmd.op_poll.nfds = nfds, + cmd.op_poll.deadline = deadline; + bsd_dispatch(&cmd); + errno = cmd.err; + return cmd.result; +} + +int select(int nfds, fd_set *r, fd_set *w, fd_set *e, struct timeval *tv) { + struct mg_bsd_cmd cmd = BSD_CMD_EMPTY; + uint64_t deadline = tv == NULL ? (uint64_t) ~0 + : mg_millis() + (uint64_t) tv->tv_sec * 1000 + + tv->tv_usec / 1000; + cmd.op = BSD_OP_SELECT, cmd.op_select.nfds = nfds, cmd.op_select.r = r, + cmd.op_select.w = w, cmd.op_select.e = e, cmd.op_select.deadline = deadline; + bsd_dispatch(&cmd); + errno = cmd.err; + return cmd.result; +} + +struct hostent *gethostbyname(const char *name) { + struct mg_bsd_cmd cmd = BSD_CMD_EMPTY; + cmd.op = BSD_OP_GETHOSTBYNAME, cmd.op_gethostbyname.name = name; + bsd_dispatch(&cmd); + return cmd.op_gethostbyname.result; +} -#endif // MG_ENABLE_FREERTOS #endif // MG_ENABLE_BSD_SOCKETS + +#if MG_ENABLE_BSD_SOCKETS +void mg_bsd_init(struct mg_mgr *mgr) { + s_mgr = mgr; +#if MG_ENABLE_BSD_SOCKETS && MG_ENABLE_FREERTOS + s_cmd_q = xQueueCreate(16, sizeof(struct mg_bsd_cmd *)); +#endif +} + +void mg_bsd_poll(struct mg_mgr *mgr) { + (void) mgr; +#if MG_ENABLE_BSD_SOCKETS && MG_ENABLE_FREERTOS + struct mg_bsd_cmd **p, *cmd; + while (xQueueReceive(s_cmd_q, &cmd, 0) == pdTRUE) { + if (bsd_run(cmd)) { + xTaskNotifyGive((TaskHandle_t) cmd->caller); + } else { + cmd->next = s_waiters; + s_waiters = cmd; + } + } + p = &s_waiters; + while (*p != NULL) { + struct mg_bsd_cmd *w = *p; + if (bsd_run(w)) { + *p = w->next; + xTaskNotifyGive((TaskHandle_t) w->caller); + } else { + p = &w->next; + } + } + taskYIELD(); +#endif +} +#endif diff --git a/src/bsd.h b/src/bsd.h index 88b49210..dab4206d 100644 --- a/src/bsd.h +++ b/src/bsd.h @@ -4,92 +4,12 @@ #include "net_builtin.h" #if MG_ENABLE_BSD_SOCKETS +void mg_bsd_init(struct mg_mgr *); +void mg_bsd_poll(struct mg_mgr *); -#ifndef MG_ENABLE_BSD_PROTOTYPES -#include #include #include -#include -#include -#include -#include -#include -#include -#include -#include -#else -#include -typedef unsigned int socklen_t; -typedef uint32_t in_addr_t; -struct in_addr { in_addr_t s_addr; }; -struct in6_addr { uint8_t s6_addr[16]; }; -struct sockaddr { uint16_t sa_family; char sa_data[14]; }; -struct sockaddr_in { - uint16_t sin_family; - uint16_t sin_port; - struct in_addr sin_addr; - char sin_zero[8]; -}; -struct sockaddr_in6 { - uint16_t sin6_family; - uint16_t sin6_port; - uint32_t sin6_flowinfo; - struct in6_addr sin6_addr; - uint32_t sin6_scope_id; -}; -struct sockaddr_storage { uint16_t ss_family; char __ss_pad[126]; }; -struct hostent { - char *h_name; - char **h_aliases; - int h_addrtype; - int h_length; - char **h_addr_list; -}; -#define h_addr h_addr_list[0] -struct addrinfo { - int ai_flags; - int ai_family; - int ai_socktype; - int ai_protocol; - socklen_t ai_addrlen; - struct sockaddr *ai_addr; - char *ai_canonname; - struct addrinfo *ai_next; -}; -struct pollfd { int fd; short events; short revents; }; -#define AF_INET 2 -#define AF_INET6 10 -#define AF_UNSPEC 0 -#define PF_INET AF_INET -#define PF_INET6 AF_INET6 -#define PF_UNSPEC AF_UNSPEC -#define SOCK_STREAM 1 -#define SOCK_DGRAM 2 -#define IPPROTO_TCP 6 -#define IPPROTO_UDP 17 -#define INADDR_ANY 0 -#define INADDR_LOOPBACK 0x7f000001 -#define SOL_SOCKET 0xffff -#define SO_REUSEADDR 2 -#define SO_REUSEPORT 15 -#define SO_KEEPALIVE 9 -#define SO_ERROR 4 -#define SO_BROADCAST 6 -#define SO_RCVBUF 8 -#define SO_SNDBUF 7 -#define TCP_NODELAY 1 -#define MSG_DONTWAIT 0x40 -#define MSG_NOSIGNAL 0x4000 -#define MSG_PEEK 0x02 -#define F_GETFL 3 -#define F_SETFL 4 -#define O_NONBLOCK 0x0004 -#define SHUT_RD 0 -#define SHUT_WR 1 -#define SHUT_RDWR 2 -#define AI_PASSIVE 0x0001 -#define NI_MAXHOST 1025 -#define NI_MAXSERV 32 + #ifndef EAGAIN #define EAGAIN 11 #endif @@ -114,66 +34,53 @@ struct pollfd { int fd; short events; short revents; }; #ifndef ETIMEDOUT #define ETIMEDOUT 110 #endif -#define POLLIN 0x001 -#define POLLOUT 0x004 -#define POLLERR 0x008 -#define POLLHUP 0x010 -#define POLLNVAL 0x020 -uint16_t htons(uint16_t); -uint16_t ntohs(uint16_t); -uint32_t htonl(uint32_t); -uint32_t ntohl(uint32_t); +#ifndef EBADF +#define EBADF 9 +#endif +#ifndef EMFILE +#define EMFILE 24 +#endif +#ifndef ENOMEM +#define ENOMEM 12 +#endif +#ifndef EIO +#define EIO 5 +#endif +#ifndef EINVAL +#define EINVAL 22 +#endif +#ifndef EPERM +#define EPERM 1 +#endif +#ifndef EPIPE +#define EPIPE 32 +#endif +#ifndef EPROTONOSUPPORT +#define EPROTONOSUPPORT 93 +#endif + +#if MG_ENABLE_BSD_PROTOTYPES +#include +#ifndef MG_BSD_SELECT_H +#error "Add the Mongoose BSD compatibility directory to the include path" +#else +#include +#endif +#else +#include +#include +#include +#include +#include +#include +#include +#include +#include #endif // MG_ENABLE_BSD_PROTOTYPES -#define closesocket(a) close(a) - -#ifndef MG_BSD_BACKLOG -#define MG_BSD_BACKLOG 5 -#endif - -#ifndef MG_BSD_CONNECT_TIMEOUT_MS -#define MG_BSD_CONNECT_TIMEOUT_MS 10000 -#endif - -// Mongoose-specific API -void mg_bsd_init(void); // must be called before socket() -void mg_bsd_poll(struct mg_mgr *); // process pending BSD commands - -// Transport backend: implement these OR define MG_ENABLE_FREERTOS for the built-in backend -void *mg_bsd_transport_new(int domain, int type, int proto); -void mg_bsd_transport_free(void *t); -int mg_bsd_transport_listen(void *t, const struct sockaddr_in *addr); -void *mg_bsd_transport_accept(void *t, struct sockaddr_in *peer, bool nonblock); -ssize_t mg_bsd_transport_recv(void *t, void *buf, size_t len, bool nonblock); -ssize_t mg_bsd_transport_send(void *t, const void *buf, size_t len, bool nonblock); -int mg_bsd_transport_connect(void *t, const struct sockaddr_in *addr, bool nonblock); -void mg_bsd_transport_close(void *t); - -// Standard BSD socket API -int socket(int, int, int); -int bind(int, const struct sockaddr *, socklen_t); -int listen(int, int); -int accept(int, struct sockaddr *, socklen_t *); -int connect(int, const struct sockaddr *, socklen_t); -ssize_t send(int, const void *, size_t, int); -ssize_t recv(int, void *, size_t, int); -ssize_t sendto(int, const void *, size_t, int, const struct sockaddr *, socklen_t); -ssize_t recvfrom(int, void *, size_t, int, struct sockaddr *, socklen_t *); -int close(int); -int shutdown(int, int); -int fcntl(int, int, int); -int setsockopt(int, int, int, const void *, socklen_t); -int getsockopt(int, int, int, void *, socklen_t *); -int getsockname(int, struct sockaddr *, socklen_t *); -int getpeername(int, struct sockaddr *, socklen_t *); -int select(int, fd_set *, fd_set *, fd_set *, struct timeval *); -int poll(struct pollfd *, unsigned int, int); -struct hostent *gethostbyname(const char *); -int getaddrinfo(const char *, const char *, const struct addrinfo *, struct addrinfo **); -void freeaddrinfo(struct addrinfo *); -int inet_pton(int, const char *, void *); -const char *inet_ntop(int, const void *, char *, socklen_t); -in_addr_t inet_addr(const char *); -char *inet_ntoa(struct in_addr); +#include +#else +#define mg_bsd_init(mgr) +#define mg_bsd_poll(mgr) #endif // MG_ENABLE_BSD_SOCKETS diff --git a/src/bsd/arpa/inet.h b/src/bsd/arpa/inet.h new file mode 100644 index 00000000..24d0f219 --- /dev/null +++ b/src/bsd/arpa/inet.h @@ -0,0 +1,2 @@ +#pragma once +#include "../mg_bsd_compat.h" diff --git a/src/bsd/mg_bsd_compat.h b/src/bsd/mg_bsd_compat.h new file mode 100644 index 00000000..c39fd103 --- /dev/null +++ b/src/bsd/mg_bsd_compat.h @@ -0,0 +1,171 @@ +#pragma once + +#ifndef MG_BSD_COMPAT_H +#define MG_BSD_COMPAT_H + +#include + +typedef unsigned int socklen_t; +typedef uint32_t in_addr_t; +struct in_addr { in_addr_t s_addr; }; +struct in6_addr { uint8_t s6_addr[16]; }; +struct sockaddr { uint16_t sa_family; char sa_data[14]; }; +struct sockaddr_in { + uint16_t sin_family; + uint16_t sin_port; + struct in_addr sin_addr; + char sin_zero[8]; +}; +struct sockaddr_in6 { + uint16_t sin6_family; + uint16_t sin6_port; + uint32_t sin6_flowinfo; + struct in6_addr sin6_addr; + uint32_t sin6_scope_id; +}; +struct sockaddr_storage { uint16_t ss_family; char __ss_pad[126]; }; +struct hostent { + char *h_name; + char **h_aliases; + int h_addrtype; + int h_length; + char **h_addr_list; +}; +#define h_addr h_addr_list[0] +struct addrinfo { + int ai_flags; + int ai_family; + int ai_socktype; + int ai_protocol; + socklen_t ai_addrlen; + struct sockaddr *ai_addr; + char *ai_canonname; + struct addrinfo *ai_next; +}; +#define AF_INET 2 +#define AF_INET6 10 +#define AF_UNSPEC 0 +#define PF_INET AF_INET +#define PF_INET6 AF_INET6 +#define PF_UNSPEC AF_UNSPEC +#define SOCK_STREAM 1 +#define SOCK_DGRAM 2 +#define IPPROTO_TCP 6 +#define IPPROTO_UDP 17 +#define INADDR_ANY 0 +#define INADDR_LOOPBACK 0x7f000001 +#define INET_ADDRSTRLEN 16 +#define SOL_SOCKET 0xffff +#define SO_REUSEADDR 2 +#define SO_REUSEPORT 15 +#define SO_KEEPALIVE 9 +#define SO_ERROR 4 +#define SO_BROADCAST 6 +#define SO_RCVBUF 8 +#define SO_SNDBUF 7 +#define SO_NOSIGPIPE 0x1022 +#define TCP_NODELAY 1 +#define MSG_DONTWAIT 0x40 +#define MSG_NOSIGNAL 0x4000 +#define MSG_PEEK 0x02 +#ifndef F_GETFL +#define F_GETFL 3 +#endif +#ifndef F_SETFL +#define F_SETFL 4 +#endif +#ifndef O_NONBLOCK +#define O_NONBLOCK 0x4000 +#endif +#define SHUT_RD 0 +#define SHUT_WR 1 +#define SHUT_RDWR 2 +#define AI_PASSIVE 0x0001 +#define NI_MAXHOST 1025 +#define NI_MAXSERV 32 +#ifndef EAGAIN +#define EAGAIN 11 +#endif +#ifndef EWOULDBLOCK +#define EWOULDBLOCK EAGAIN +#endif +#ifndef EINPROGRESS +#define EINPROGRESS 36 +#endif +#ifndef ENOTCONN +#define ENOTCONN 107 +#endif +#ifndef ECONNREFUSED +#define ECONNREFUSED 111 +#endif +#ifndef ECONNRESET +#define ECONNRESET 104 +#endif +#ifndef EADDRINUSE +#define EADDRINUSE 98 +#endif +#ifndef ETIMEDOUT +#define ETIMEDOUT 110 +#endif +#ifndef EBADF +#define EBADF 9 +#endif +#ifndef EMFILE +#define EMFILE 24 +#endif +#ifndef ENOMEM +#define ENOMEM 12 +#endif +#ifndef EIO +#define EIO 5 +#endif +#ifndef EINVAL +#define EINVAL 22 +#endif +#ifndef EPERM +#define EPERM 1 +#endif +#ifndef EPIPE +#define EPIPE 32 +#endif +#ifndef EPROTONOSUPPORT +#define EPROTONOSUPPORT 93 +#endif + +uint16_t mg_ntohs(uint16_t); +uint32_t mg_ntohl(uint32_t); +#define htons(x) mg_ntohs(x) +#define ntohs(x) mg_ntohs(x) +#define htonl(x) mg_ntohl(x) +#define ntohl(x) mg_ntohl(x) + +int socket(int, int, int); +int bind(int, const struct sockaddr *, socklen_t); +int listen(int, int); +int accept(int, struct sockaddr *, socklen_t *); +int connect(int, const struct sockaddr *, socklen_t); +ssize_t send(int, const void *, size_t, int); +ssize_t recv(int, void *, size_t, int); +ssize_t sendto(int, const void *, size_t, int, const struct sockaddr *, + socklen_t); +ssize_t recvfrom(int, void *, size_t, int, struct sockaddr *, socklen_t *); +int close(int); +int shutdown(int, int); +int setsockopt(int, int, int, const void *, socklen_t); +int getsockopt(int, int, int, void *, socklen_t *); +int getsockname(int, struct sockaddr *, socklen_t *); +int getpeername(int, struct sockaddr *, socklen_t *); +struct hostent *gethostbyname(const char *); +int getaddrinfo(const char *, const char *, const struct addrinfo *, + struct addrinfo **); +void freeaddrinfo(struct addrinfo *); +int inet_pton(int, const char *, void *); +const char *inet_ntop(int, const void *, char *, socklen_t); +in_addr_t inet_addr(const char *); +char *inet_ntoa(struct in_addr); + +#define closesocket(a) close(a) + +#include + +#endif // MG_BSD_COMPAT_H diff --git a/src/bsd/netdb.h b/src/bsd/netdb.h new file mode 100644 index 00000000..09072e02 --- /dev/null +++ b/src/bsd/netdb.h @@ -0,0 +1,2 @@ +#pragma once +#include "mg_bsd_compat.h" diff --git a/src/bsd/netinet/in.h b/src/bsd/netinet/in.h new file mode 100644 index 00000000..24d0f219 --- /dev/null +++ b/src/bsd/netinet/in.h @@ -0,0 +1,2 @@ +#pragma once +#include "../mg_bsd_compat.h" diff --git a/src/bsd/netinet/tcp.h b/src/bsd/netinet/tcp.h new file mode 100644 index 00000000..96fae161 --- /dev/null +++ b/src/bsd/netinet/tcp.h @@ -0,0 +1,2 @@ +#pragma once +#include "../compat.h" diff --git a/src/bsd/poll.h b/src/bsd/poll.h new file mode 100644 index 00000000..055e7ea5 --- /dev/null +++ b/src/bsd/poll.h @@ -0,0 +1,18 @@ +#pragma once + +#include "mg_bsd_compat.h" + +#ifndef POLLIN +#define POLLIN 0x001 +#define POLLOUT 0x004 +#define POLLERR 0x008 +#define POLLHUP 0x010 +#define POLLNVAL 0x020 +typedef unsigned long nfds_t; +struct pollfd { + int fd; + short events; + short revents; +}; +int poll(struct pollfd *, nfds_t, int); +#endif diff --git a/src/bsd/sys/select.h b/src/bsd/sys/select.h new file mode 100644 index 00000000..eaa014af --- /dev/null +++ b/src/bsd/sys/select.h @@ -0,0 +1,32 @@ +#pragma once + +#ifndef MG_BSD_SELECT_H +#define MG_BSD_SELECT_H + +#ifndef FD_SETSIZE +#define FD_SETSIZE 64 +#endif + +typedef unsigned long __fd_mask; +#define _NFDBITS ((int) sizeof(__fd_mask) * 8) +#define _howmany(x, y) (((x) + ((y) - 1)) / (y)) +typedef struct fd_set { + __fd_mask __fds_bits[_howmany(FD_SETSIZE, _NFDBITS)]; +} fd_set; + +#define __fdset_mask(n) ((__fd_mask) 1 << ((n) % _NFDBITS)) +#define FD_CLR(n, p) ((p)->__fds_bits[(n) / _NFDBITS] &= ~__fdset_mask(n)) +#define FD_ISSET(n, p) \ + (((p)->__fds_bits[(n) / _NFDBITS] & __fdset_mask(n)) != 0) +#define FD_SET(n, p) ((p)->__fds_bits[(n) / _NFDBITS] |= __fdset_mask(n)) +#define FD_ZERO(p) \ + do { \ + fd_set *_p = (p); \ + size_t _n = _howmany(FD_SETSIZE, _NFDBITS); \ + while (_n > 0) _p->__fds_bits[--_n] = 0; \ + } while (0) + +struct timeval; +int select(int, fd_set *, fd_set *, fd_set *, struct timeval *); + +#endif // MG_BSD_SELECT_H diff --git a/src/bsd/sys/socket.h b/src/bsd/sys/socket.h new file mode 100644 index 00000000..24d0f219 --- /dev/null +++ b/src/bsd/sys/socket.h @@ -0,0 +1,2 @@ +#pragma once +#include "../mg_bsd_compat.h" diff --git a/src/bsd/sys/un.h b/src/bsd/sys/un.h new file mode 100644 index 00000000..96fae161 --- /dev/null +++ b/src/bsd/sys/un.h @@ -0,0 +1,2 @@ +#pragma once +#include "../compat.h" diff --git a/src/net.h b/src/net.h index 5ba7a71b..81097df9 100644 --- a/src/net.h +++ b/src/net.h @@ -98,6 +98,7 @@ struct mg_connection { unsigned is_resp : 1; // HTTP: response is still being generated unsigned is_readable : 1; // Socket is ready to read (epoll/select) unsigned is_writable : 1; // Socket is ready to write (epoll/select) + unsigned is_bsd : 1; // Built-in TCP/IP stack BSD socket }; // Runs one iteration of the event loop. diff --git a/src/net_builtin.c b/src/net_builtin.c index 5b730320..01c528a8 100644 --- a/src/net_builtin.c +++ b/src/net_builtin.c @@ -1,4 +1,5 @@ #include "net_builtin.h" +#include "bsd.h" #include "profile.h" #if MG_ENABLE_TCPIP @@ -31,16 +32,16 @@ #endif struct connstate { - uint32_t seq, ack; // TCP seq/ack counters - uint64_t timer; // TCP timer (see 'ttype' below) - uint64_t txq_timer; // TCP retransmission timer (RFC-6298, 5) - uint32_t acked; // Last ACK-ed number - size_t unacked; // Not acked bytes - uint32_t maxseq; // Max send seq (ack + window) - uint32_t txq_seq; // Sequence of first txq record (RFC-9293, 3.8) - uint32_t txq_una; // Oldest unacknowledged sequence (RFC-9293, 3.4) - uint16_t win; // destination current window size - uint16_t dmss; // destination MSS (from TCP opts) + uint32_t seq, ack; // TCP seq/ack counters + uint64_t timer; // TCP timer (see 'ttype' below) + uint64_t txq_timer; // TCP retransmission timer (RFC-6298, 5) + uint32_t acked; // Last ACK-ed number + size_t unacked; // Not acked bytes + uint32_t maxseq; // Max send seq (ack + window) + uint32_t txq_seq; // Sequence of first txq record (RFC-9293, 3.8) + uint32_t txq_una; // Oldest unacknowledged sequence (RFC-9293, 3.4) + uint16_t win; // destination current window size + uint16_t dmss; // destination MSS (from TCP opts) uint8_t mac[sizeof(struct mg_l2addr)]; // Peer hw address uint8_t ttype; // Timer type: #define MIP_TTYPE_KEEPALIVE 0 // Connection is idle for long, send keepalive @@ -809,7 +810,7 @@ static struct ip6 *tx_ip6(struct mg_tcpip_if *ifp, uint8_t *l2_dst, struct ip6 *ip6 = (struct ip6 *) mg_l2_header(ifp, MG_TCPIP_L2PROTO_IPV6, ifp->mac, l2_dst, l2p); memset(ip6, 0, sizeof(*ip6)); - ip6->ver = (uint8_t) (0x60 | (dscp >> 2)); // Version 6, traffic class + ip6->ver = (uint8_t) (0x60 | (dscp >> 2)); // Version 6, traffic class ip6->label[0] = (uint8_t) (dscp << 6); ip6->plen = mg_htons((uint16_t) plen); ip6->next = next; @@ -1278,7 +1279,7 @@ static size_t tx_tcp(struct mg_tcpip_if *ifp, uint8_t *l2_dst, tcp->off = (uint8_t) (hlen / 4 << 4); #if MG_ENABLE_IPV6 if (ip_dst->is_ip6) { - tcp->csum = p6csum(ip6, tcp, 6, hlen + len); + tcp->csum = p6csum(ip6, tcp, 6, hlen + len); } else #endif { @@ -2266,6 +2267,7 @@ void mg_tcpip_init(struct mg_mgr *mgr, struct mg_tcpip_if *ifp) { // MG_EPHEMERAL_PORT_BASE to 65535 if (ifp->tx.buf == NULL || ifp->recv_queue.buf == NULL) MG_ERROR(("OOM")); } + mg_bsd_init(mgr); } void mg_tcpip_free(struct mg_tcpip_if *ifp) { @@ -2436,6 +2438,7 @@ void mg_mgr_poll(struct mg_mgr *mgr, int ms) { c->is_closing = 1; if (c->is_closing) close_conn(c); } + mg_bsd_poll(mgr); (void) ms; } diff --git a/test/bsd/Makefile b/test/bsd/Makefile index 062bbe2a..fc7d405c 100644 --- a/test/bsd/Makefile +++ b/test/bsd/Makefile @@ -13,18 +13,21 @@ else C_WARN ?= -Wno-deprecated endif -.PHONY: all bsd_test bsd_client shm_tap_bridge shm_port_bridge bsd_s390 clean +.PHONY: all bsd bsd_test bsd_client bsd_nortos bsd_nortos_test shm_tap_bridge \ + shm_port_bridge bsd_s390 bsd_nortos_s390 clean TEST_SOURCES = bsd_shm_test.c # A C++ application compiles its sources as C++, linking Mongoose and FreeRTOS as C. # FreeRTOS does not build as C++, so C sources are compiled separately. C_SOURCES = ../../mongoose.c -C_SOURCES += FreeRTOS-Kernel/portable/MemMang/heap_4.c -C_SOURCES += FreeRTOS-Kernel/portable/ThirdParty/GCC/Posix/port.c -C_SOURCES += FreeRTOS-Kernel/portable/ThirdParty/GCC/Posix/utils/wait_for_event.c -C_SOURCES += $(wildcard FreeRTOS-Kernel/*.c) +FREERTOS_SOURCES = FreeRTOS-Kernel/portable/MemMang/heap_4.c +FREERTOS_SOURCES += FreeRTOS-Kernel/portable/ThirdParty/GCC/Posix/port.c +FREERTOS_SOURCES += FreeRTOS-Kernel/portable/ThirdParty/GCC/Posix/utils/wait_for_event.c +FREERTOS_SOURCES += $(wildcard FreeRTOS-Kernel/*.c) C_OBJECTS = $(notdir $(C_SOURCES:.c=.o)) +FREERTOS_OBJECTS = $(notdir $(FREERTOS_SOURCES:.c=.o)) C_INPUTS = $(foreach S,$(C_SOURCES),-x c $(S)) +FREERTOS_INPUTS = $(foreach S,$(FREERTOS_SOURCES),-x c $(S)) CFLAGS += -IFreeRTOS-Kernel/include CFLAGS += -IFreeRTOS-Kernel/portable/ThirdParty/GCC/Posix -Wno-conversion -Wno-undef @@ -44,11 +47,24 @@ DOCKER ?= $(DOCKER_BIN) run --platform linux/amd64 --net=host --rm $(ENV) -v $(R CFLAGS_MONGOOSE += -DMG_ARCH=MG_ARCH_CUSTOM +bsd: CFLAGS_MONGOOSE += -I../../src/bsd all bsd: shm_tap_bridge bsd_client FreeRTOS-Kernel bsd_test ASAN_OPTIONS=$(ASAN_OPTIONS) $(RUN) ./bsd_test $(ARGS) +# also +bsd_nortos: CFLAGS_MONGOOSE += -I../../src/bsd +bsd_nortos: shm_tap_bridge bsd_client bsd_nortos_test + ASAN_OPTIONS=$(ASAN_OPTIONS) $(RUN) ./bsd_nortos_test $(ARGS) + # requires shm_tap_bridge to be running +bsd_test: CFLAGS_MONGOOSE += -DMG_ENABLE_FREERTOS=1 bsd_test: FreeRTOS-Kernel + $(CC) $(C_INPUTS) $(FREERTOS_INPUTS) -c $(CFLAGS) $(CFLAGS_MONGOOSE) $(CFLAGS_EXTRA) + $(CC) -x $(TEST_LANGUAGE) -c $(TEST_SOURCES) $(CFLAGS) $(CFLAGS_MONGOOSE) $(CFLAGS_EXTRA) + $(CC) $(C_OBJECTS) $(FREERTOS_OBJECTS) $(TEST_SOURCES:.c=.o) $(CFLAGS) $(CFLAGS_MONGOOSE) $(CFLAGS_EXTRA) -o $@ + +bsd_nortos_test: CFLAGS_MONGOOSE += -DMG_ENABLE_FREERTOS=0 +bsd_nortos_test: $(CC) $(C_INPUTS) -c $(CFLAGS) $(CFLAGS_MONGOOSE) $(CFLAGS_EXTRA) $(CC) -x $(TEST_LANGUAGE) -c $(TEST_SOURCES) $(CFLAGS) $(CFLAGS_MONGOOSE) $(CFLAGS_EXTRA) $(CC) $(C_OBJECTS) $(TEST_SOURCES:.c=.o) $(CFLAGS) $(CFLAGS_MONGOOSE) $(CFLAGS_EXTRA) -o $@ @@ -61,12 +77,21 @@ shm_tap_bridge: shm_tap_bridge.c shm_queue.h $(CC) shm_tap_bridge.c -pthread -o $@ # requires test/port_tap_bridge to be running +bsd_s390: CFLAGS_MONGOOSE += -DMG_ENABLE_BSD_PROTOTYPES=0 bsd_s390: ASAN= bsd_s390: CC = $(DOCKER) mdashnet/s390 cc bsd_s390: RUN = $(DOCKER) --init mdashnet/s390 bsd_s390: shm_port_bridge bsd_client FreeRTOS-Kernel bsd_test $(RUN) sh -c './shm_port_bridge & pid=$$!; sleep 1; ./bsd_test; rc=$$?; kill $$pid >/dev/null 2>&1; wait $$pid >/dev/null 2>&1; exit $$rc' +#also +bsd_nortos_s390: CFLAGS_MONGOOSE += -DMG_ENABLE_BSD_PROTOTYPES=0 +bsd_nortos_s390: ASAN= +bsd_nortos_s390: CC = $(DOCKER) mdashnet/s390 cc +bsd_nortos_s390: RUN = $(DOCKER) --init mdashnet/s390 +bsd_nortos_s390: shm_port_bridge bsd_client bsd_nortos_test + $(RUN) sh -c './shm_port_bridge & pid=$$!; sleep 1; ./bsd_nortos_test; rc=$$?; kill $$pid >/dev/null 2>&1; wait $$pid >/dev/null 2>&1; exit $$rc' + shm_port_bridge: shm_port_bridge.c shm_queue.h $(CC) shm_port_bridge.c $(CFLAGS) -pthread -o $@ diff --git a/test/bsd/bsd_client.c b/test/bsd/bsd_client.c index 8a6b81ef..44ea79e1 100644 --- a/test/bsd/bsd_client.c +++ b/test/bsd/bsd_client.c @@ -1,4 +1,5 @@ #include +#include #include #include #include @@ -91,22 +92,80 @@ fail: exit(1); } +enum wait_mode { WAIT_ABORT, WAIT_DRAIN, WAIT_HOLD, WAIT_CLOSE }; + +// Wait for output, optionally draining through the final marker. +static int wait_data(const char *host, enum wait_mode mode) { + struct sockaddr_in sa; + struct timeval timeout; + char buf[512]; + ssize_t n; + size_t i; + int fd = socket(AF_INET, SOCK_STREAM, 0); + + if (fd < 0) return 1; + memset(&sa, 0, sizeof(sa)); + sa.sin_family = AF_INET; + sa.sin_port = htons(PORT); + if (inet_pton(AF_INET, host, &sa.sin_addr) != 1) goto fail; + timeout.tv_sec = 5, timeout.tv_usec = 0; + if (setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, &timeout, sizeof(timeout)) != 0) + goto fail; + if (connect(fd, (struct sockaddr *) &sa, sizeof(sa)) != 0) goto fail; + if (mode == WAIT_HOLD) { + usleep(100000); // Keep the peer connected while the server fails TX + close(fd); + return 0; + } + if (mode == WAIT_CLOSE) { + n = recv(fd, buf, sizeof(buf), 0); + close(fd); + return n == 0 || (n < 0 && errno == ECONNRESET) ? 0 : 1; + } + for (;;) { + n = recv(fd, buf, sizeof(buf), 0); + if (n <= 0) goto fail; + if (mode == WAIT_ABORT) break; + for (i = 0; i < (size_t) n; i++) { + if (buf[i] == '\1') break; + } + if (i < (size_t) n) break; + } + close(fd); + return 0; + +fail: + close(fd); + return 1; +} + int main(int argc, char *argv[]) { pthread_t threads[NUM_CONNECTIONS]; - int i; + int connections = NUM_CONNECTIONS, i; - if (argc != 2) { - fprintf(stderr, "usage: %s IP\n", argv[0]); + if (argc == 3 && strcmp(argv[2], "waitandabort") == 0) + return wait_data(argv[1], WAIT_ABORT); + if (argc == 3 && strcmp(argv[2], "waitanddrain") == 0) + return wait_data(argv[1], WAIT_DRAIN); + if (argc == 3 && strcmp(argv[2], "hold") == 0) + return wait_data(argv[1], WAIT_HOLD); + if (argc == 3 && strcmp(argv[2], "expectclose") == 0) + return wait_data(argv[1], WAIT_CLOSE); + if (argc == 3) connections = atoi(argv[2]); + if ((argc != 2 && argc != 3) || connections < 1 || + connections > NUM_CONNECTIONS) { + fprintf(stderr, "usage: %s IP [connections|waitandabort|waitanddrain|hold|expectclose]\n", + argv[0]); return 1; } srandom(1); - for (i = 0; i < NUM_CONNECTIONS; i++) { + for (i = 0; i < connections; i++) { if (pthread_create(&threads[i], NULL, worker, argv[1]) != 0) return 1; } - for (i = 0; i < NUM_CONNECTIONS; i++) { + for (i = 0; i < connections; i++) { if (pthread_join(threads[i], NULL) != 0) return 1; } diff --git a/test/bsd/bsd_x_test.c b/test/bsd/bsd_x_test.c index 74395a45..92de1ddc 100644 --- a/test/bsd/bsd_x_test.c +++ b/test/bsd/bsd_x_test.c @@ -1,11 +1,11 @@ #define BSDTEST_USING_DHCP 1 +#include #include #include static int s_num_tests = 0; static bool s_error = false; -static volatile bool s_done = false, s_wait; #ifdef NO_ABORT static int s_abort = 0; @@ -30,15 +30,177 @@ static int s_abort = 0; } \ } while (0) +#define DASHBOARD(x) \ + printf("HEALTH_DASHBOARD\t\"%s\": %s,\n", x, s_error ? "false" : "true") + +static pid_t start_client(const char *ip, const char *connections) { + char *cmd[4]; + pid_t pid; + extern char **environ; + + cmd[0] = (char *) "./bsd_client"; + cmd[1] = (char *) ip; + cmd[2] = (char *) connections; + cmd[3] = NULL; + ASSERT(posix_spawn(&pid, cmd[0], NULL, NULL, cmd, environ) == 0); + return pid; +} + +static bool client_done(pid_t pid, int *status) { + pid_t r = waitpid(pid, status, WNOHANG); + ASSERT(r >= 0); + return r == pid; +} + +static void check_client(int status) { + ASSERT(WIFEXITED(status)); + ASSERT(WEXITSTATUS(status) == 0); +} + +static int open_listener(int backlog, bool nonblock) { + int lfd = socket(AF_INET, SOCK_STREAM, 0); + struct sockaddr_in sa; + + memset(&sa, 0, sizeof(sa)); + sa.sin_family = AF_INET; + sa.sin_port = htons(1234); + sa.sin_addr.s_addr = INADDR_ANY; + ASSERT(lfd >= 0); + ASSERT(bind(lfd, (struct sockaddr *) &sa, sizeof(sa)) == 0); + ASSERT(listen(lfd, backlog) == 0); + if (nonblock) ASSERT(fcntl(lfd, F_SETFL, O_NONBLOCK) == 0); + else ASSERT((fcntl(lfd, F_GETFL, 0) & O_NONBLOCK) == 0); + MG_INFO(("BSD echo server on :1234")); + return lfd; +} + +static void wait_ready(struct mg_mgr *mgr) { + // Stack initialization, Network configuration (DHCP lease, ...) +#if BSDTEST_USING_DHCP == 0 + MG_INFO(("MIF configuration: Static IP")); + ASSERT(mgr->ifp->ip != 0); // Check we have a satic IP assigned + mg_mgr_poll(mgr, 0); // For initialisation +#else + MG_INFO(("MIF configuration: DHCP")); + ASSERT(mgr->ifp->ip == 0); // Check we are set for DHCP + while (mgr->ifp->ip == 0 && mg_millis() < 5000) mg_mgr_poll(mgr, 0); + if (mgr->ifp->ip == 0) MG_ERROR(("No ip assigned (DHCP lease may have failed).\n")); + ASSERT(mgr->ifp->ip != 0); // We have an IP (lease or static) +#endif + while (mgr->ifp->state != MG_TCPIP_STATE_READY) mg_mgr_poll(mgr, 0); + MG_INFO(("Interface started, starting main loop")); +} + +#if !MG_ENABLE_FREERTOS +static bool s_fail_alloc; + +void *mg_calloc(size_t count, size_t size) { + return s_fail_alloc ? NULL : calloc(count, size); +} + +void mg_free(void *ptr) { + free(ptr); +} + +static uint64_t s_bootmillis = 0; + +uint64_t mg_millis(void) { + struct timespec ts = {0, 0}; + // See #1615 - prefer monotonic clock +#if defined(CLOCK_MONOTONIC_RAW) + // Raw hardware-based time that is not subject to NTP adjustment + clock_gettime(CLOCK_MONOTONIC_RAW, &ts); +#elif defined(CLOCK_MONOTONIC) + // Affected by the incremental adjustments performed by adjtime and NTP + clock_gettime(CLOCK_MONOTONIC, &ts); +#else + // Affected by discontinuous jumps in the system time and by the incremental + // adjustments performed by adjtime and NTP + clock_gettime(CLOCK_REALTIME, &ts); +#endif + return ((uint64_t) ts.tv_sec * 1000 + (uint64_t) ts.tv_nsec / 1000000) - s_bootmillis; +} + +#endif + +#if MG_ENABLE_FREERTOS +static volatile bool s_done = false, s_wait, s_accept_waiting; +static volatile bool s_accept_done, s_dontwait_done, s_nonblock_done; +static volatile bool s_select_waiting, s_select_done; +static volatile bool s_poll_waiting, s_poll_done; +static volatile bool s_ready_waiting, s_ready_done; +static volatile bool s_backlog_ready, s_backlog_full, s_backlog_accept; +static volatile bool s_tx_held, s_tx_waits, s_tx_fail, s_tx_error; +static volatile bool s_send_waiting; +static volatile bool s_send_done, s_send_expect_error; +static volatile bool s_recv_start, s_recv_waiting, s_recv_done; +static volatile bool s_recv_expect_error; +static size_t (*s_tx)(const void *, size_t, struct mg_tcpip_if *); + struct client_data { char hostname[64]; uint32_t ip; bool done; }; +static void echo_task(void *args); +static void echo_dontwait_task(void *args); +static void echo_nonblock_task(void *args); +static void send_task(void *args); +static void recv_wait_task(void *args); +static void select_task(void *args); +static void readiness_task(void *args); +static void backlog_task(void *args); +static void tx_gate_task(void *args); + +struct atask_args { + bool listener_nonblock; + bool worker_nonblock; + bool once; + bool receiver; + TaskFunction_t task; + TaskFunction_t gate; +}; + +static const struct atask_args s_echo_server = {true, false, false, + false, echo_task, NULL}; +static const struct atask_args s_accept_server = {false, false, true, + false, echo_task, NULL}; +static const struct atask_args s_nonblock_accept_server = { + true, false, true, false, echo_task, NULL}; +static const struct atask_args s_dontwait_server = { + true, false, false, false, echo_dontwait_task, NULL}; +static const struct atask_args s_nonblock_server = { + true, true, false, false, echo_nonblock_task, NULL}; +static const struct atask_args s_send_server = {true, false, false, + true, send_task, tx_gate_task}; +static const struct atask_args s_send_error_server = { + true, false, false, false, send_task, tx_gate_task}; + +static size_t tx_gate(const void *buf, size_t len, struct mg_tcpip_if *ifp) { + if (s_tx_fail) return (size_t) -1; // Force MG_IO_ERR after send() waits + if (s_tx_held) { + s_tx_waits = true; + return 0; // Gate TX: MG_IO_WAIT leaves c->send queued + } + return s_tx(buf, len, ifp); +} + +static void run_client(const char *ip, const char *connections) { + int status; + pid_t pid = start_client(ip, connections); + + while (!client_done(pid, &status)) { + vTaskDelay(pdMS_TO_TICKS(10)); + } + check_client(status); +} + // client_task: connects to hostname:80, fetches "/", logs response length. static void client_task(void *args) { - int fd, c; + fd_set w, e; + struct pollfd p; + int fd; struct client_data *d = (struct client_data *) args; char req[256]; fd = socket(AF_INET, SOCK_STREAM, 0); @@ -47,14 +209,20 @@ static void client_task(void *args) { sa.sin_family = AF_INET; sa.sin_port = htons(80); memcpy(&sa.sin_addr, &d->ip, 4); - c = connect(fd, (struct sockaddr *) &sa, sizeof(sa)); - ASSERT(c >= 0); - if (c < 0) { - MG_ERROR(("connect failed")); - close(fd); - vTaskDelete(NULL); - return; - } + ASSERT(fcntl(fd, F_SETFL, O_NONBLOCK) == 0); + ASSERT(connect(fd, (struct sockaddr *) &sa, sizeof(sa)) < 0 && + errno == EINPROGRESS); + FD_ZERO(&w); + FD_ZERO(&e); + FD_SET(fd, &w); + FD_SET(fd, &e); + ASSERT(select(fd + 1, NULL, &w, &e, NULL) == 1); + ASSERT(FD_ISSET(fd, &w)); + ASSERT(!FD_ISSET(fd, &e)); + p.fd = fd, p.events = POLLOUT, p.revents = 0; + ASSERT(poll(&p, 1, 0) == 1); + ASSERT(p.revents == POLLOUT); + ASSERT(fcntl(fd, F_SETFL, 0) == 0); snprintf(req, sizeof(req),"GET / HTTP/1.0\r\nHost: %s\r\n\r\n", d->hostname); send(fd, req, strlen(req), 0); size_t total = 0; @@ -69,84 +237,475 @@ static void client_task(void *args) { (void) args; } -// echo_task: one per accepted connection. Echoes data back. -static void echo_task(void *args) { - int fd = (int) (uintptr_t) args; +// Echo data back from one accepted connection. +static void echo_task_common(int fd, int flags, volatile bool *done) { + bool nonblock = (fcntl(fd, F_GETFL, 0) & O_NONBLOCK) != 0; + bool dontwait = (flags & MSG_DONTWAIT) != 0; + bool echoed = false, eagain = false; static const size_t sizes[] = {1, 7, 17, 255, 256, 257, 511, 512}; char buf[512]; size_t i = 0; ssize_t n; + MG_INFO(("Echo task started: %d", fd)); - while ((n = recv(fd, buf, sizes[i++ % (sizeof(sizes) / sizeof(sizes[0]))], 0)) > 0) - send(fd, buf, (size_t) n, 0); + for (;;) { + n = recv(fd, buf, sizes[i++ % (sizeof(sizes) / sizeof(sizes[0]))], flags); + if (n > 0) { + if (!nonblock) { + ssize_t w = send(fd, buf, (size_t) n, 0); + if (done != NULL) ASSERT(w == n); + echoed = w == n; + } else { + size_t sent = 0; + while (sent < (size_t) n) { + ssize_t w = send(fd, buf + sent, (size_t) n - sent, 0); + if (w > 0) sent += (size_t) w; + else if (w < 0 && errno == EAGAIN) vTaskDelay(1); + else { + ASSERT(w > 0); + break; + } + } + if (sent != (size_t) n) break; + echoed = true; + } + } else if ((nonblock || dontwait) && n < 0 && errno == EAGAIN) { + if (echoed) eagain = true; + vTaskDelay(1); + } else { + break; + } + } + if (done != NULL) { + ASSERT(eagain); + *done = true; + } close(fd); vTaskDelete(NULL); } +static void echo_task(void *args) { + echo_task_common((int) (uintptr_t) args, 0, NULL); +} + +static void echo_dontwait_task(void *args) { + echo_task_common((int) (uintptr_t) args, MSG_DONTWAIT, &s_dontwait_done); +} + +static void echo_nonblock_task(void *args) { + echo_task_common((int) (uintptr_t) args, 0, &s_nonblock_done); +} + +static void tx_gate_task(void *args) { + // receiver/sender > network task > gate: final send is waiting + while (!s_send_waiting) vTaskDelay(1); + ASSERT(!s_send_done); + if (s_send_expect_error) s_tx_error = s_tx_fail = true; + else s_tx_held = false; + vTaskDelete(NULL); + (void) args; +} + +static void recv_wait_task(void *args) { + char buf; + int fd = (int) (uintptr_t) args; + ssize_t n; + + while (!s_recv_start) vTaskDelay(1); + s_recv_waiting = true; // send_task starts after recv() is waiting + n = recv(fd, &buf, sizeof(buf), 0); + if (s_recv_expect_error) { + ASSERT(n < 0 && errno == EIO); // bsd_client aborts with unread data + } else { + ASSERT(n == 0); // bsd_client drains, then closes + } + s_recv_done = true; + close(fd); // Receiver owns the shared socket close + vTaskDelete(NULL); +} + +static void send_task(void *args) { + static const char buf[512] = {0}; + static const char marker = '\1'; + int fd = (int) (uintptr_t) args; + ssize_t n; + + ASSERT(fcntl(fd, F_SETFL, O_NONBLOCK) == 0); + s_tx_held = true; // Gate TX until c->send fills + do { + n = send(fd, buf, sizeof(buf), 0); + } while (n == (ssize_t) sizeof(buf)); + ASSERT(n < 0 && errno == EAGAIN); // Full c->send: O_NONBLOCK + while (!s_tx_waits) vTaskDelay(1); // Next write_conn hits the gate + + ASSERT(fcntl(fd, F_SETFL, 0) == 0); + n = send(fd, buf, sizeof(buf), MSG_DONTWAIT); + ASSERT(n < 0 && errno == EAGAIN); // Same full c->send: MSG_DONTWAIT + + if (!s_send_expect_error) { + s_recv_start = true; // O_NONBLOCK is per socket; now start the receiver + while (!s_recv_waiting) vTaskDelay(1); + } + s_send_waiting = true; // Gate task releases TX after this send blocks + n = send(fd, &marker, sizeof(marker), 0); + if (s_send_expect_error) ASSERT(n < 0 && errno == EPIPE); + else ASSERT(n == (ssize_t) sizeof(marker)); + s_send_done = true; + vTaskDelete(NULL); +} + // accept loop. Waits for incoming connections on port 1234 and spawns // an echo_task for each one, allowing concurrent clients. static void atask(void *args) { + const struct atask_args *cfg = (const struct atask_args *) args; BaseType_t result; - mg_bsd_init(); - int lfd = socket(AF_INET, SOCK_STREAM, 0); - struct sockaddr_in sa; - memset(&sa, 0, sizeof(sa)); - sa.sin_family = AF_INET; - sa.sin_port = htons(1234); - sa.sin_addr.s_addr = INADDR_ANY; - bind(lfd, (struct sockaddr *) &sa, sizeof(sa)); - listen(lfd, 5); - fcntl(lfd, F_SETFL, O_NONBLOCK); - MG_INFO(("BSD echo server on :1234")); + int lfd = open_listener(5, cfg->listener_nonblock); + if (cfg->once && cfg->listener_nonblock) { + ASSERT(accept(lfd, NULL, NULL) < 0 && errno == EAGAIN); + s_accept_waiting = true; + } while (!s_wait) { - int fd = accept(lfd, NULL, NULL); - if (fd < 0) { vTaskDelay(pdMS_TO_TICKS(10)); continue; } - // echo_task at higher priority than task1 - result = xTaskCreate(echo_task, "echo", 512, (void *) (uintptr_t) fd, + int fd; + + if (cfg->once && !cfg->listener_nonblock) s_accept_waiting = true; + fd = accept(lfd, NULL, NULL); + if (fd < 0) { + if (!cfg->listener_nonblock) { + ASSERT(fd >= 0); + break; + } + vTaskDelay(pdMS_TO_TICKS(10)); + continue; + } + // Accepted sockets start blocking, regardless of listener flags + ASSERT((fcntl(fd, F_GETFL, 0) & O_NONBLOCK) == 0); + if (cfg->worker_nonblock) ASSERT(fcntl(fd, F_SETFL, O_NONBLOCK) == 0); + // Worker tasks run at higher priority than task1 + if (cfg->receiver) { + result = xTaskCreate(recv_wait_task, "recv", 512, + (void *) (uintptr_t) fd, + configMAX_PRIORITIES - 1, NULL); + ASSERT(result == pdPASS); + } + if (cfg->gate != NULL) { + result = xTaskCreate(cfg->gate, "gate", 256, NULL, + configMAX_PRIORITIES - 3, NULL); + ASSERT(result == pdPASS); + } + result = xTaskCreate(cfg->task, "echo", 512, (void *) (uintptr_t) fd, configMAX_PRIORITIES - 1, NULL); MG_INFO(("Echo task %d: %s", fd, result == pdPASS ? "created" : "FAILED")); ASSERT(result == pdPASS); + if (cfg->once) break; } close(lfd); - s_wait = false; + if (cfg->once) s_accept_done = true; + else s_wait = false; vTaskDelete(NULL); - (void) args; } +static void select_task(void *args) { + struct timeval zero = {0, 0}; + fd_set r, w, e; + int fd, lfd, n; + + (void) args; + lfd = open_listener(5, true); + FD_ZERO(&r); + FD_SET(lfd, &r); + ASSERT(select(lfd, &r, NULL, NULL, &zero) == 0); + ASSERT(!FD_ISSET(lfd, &r)); + FD_ZERO(&r); + FD_SET(lfd + 1, &r); + ASSERT(select(lfd + 2, &r, NULL, NULL, &zero) < 0 && errno == EBADF); + FD_ZERO(&r); + FD_ZERO(&w); + FD_ZERO(&e); + FD_SET(lfd, &r); + FD_SET(lfd, &w); + FD_SET(lfd, &e); + s_select_waiting = true; + n = select(lfd + 1, &r, &w, &e, NULL); + ASSERT(n == 1); + ASSERT(FD_ISSET(lfd, &r)); + ASSERT(!FD_ISSET(lfd, &w)); + ASSERT(!FD_ISSET(lfd, &e)); + fd = accept(lfd, NULL, NULL); + ASSERT(fd >= 0); + ASSERT(close(fd) == 0); + ASSERT(close(lfd) == 0); + s_select_done = true; + vTaskDelete(NULL); +} + +static void poll_task(void *args) { + struct pollfd p; + int fd, lfd; + + (void) args; + lfd = open_listener(5, true); + p.fd = lfd, p.events = POLLIN, p.revents = 0; + ASSERT(poll(&p, 1, 0) == 0); + ASSERT(p.revents == 0); + p.fd = lfd + 1, p.revents = 0; + ASSERT(poll(&p, 1, 0) == 1); + ASSERT(p.revents == POLLNVAL); + p.fd = lfd, p.revents = 0; + s_poll_waiting = true; + ASSERT(poll(&p, 1, -1) == 1); + ASSERT(p.revents == POLLIN); + fd = accept(lfd, NULL, NULL); + ASSERT(fd >= 0); + ASSERT(close(fd) == 0); + ASSERT(close(lfd) == 0); + s_poll_done = true; + vTaskDelete(NULL); +} + +static void readiness_task(void *args) { + struct pollfd p; + fd_set r, w; + int fd, lfd; + + (void) args; + lfd = open_listener(5, true); + s_ready_waiting = true; + while ((fd = accept(lfd, NULL, NULL)) < 0) { + ASSERT(errno == EAGAIN); + vTaskDelay(1); + } + ASSERT(fcntl(fd, F_SETFL, O_NONBLOCK) == 0); + FD_ZERO(&r); + FD_SET(fd, &r); + ASSERT(select(fd + 1, &r, NULL, NULL, NULL) == 1); + ASSERT(FD_ISSET(fd, &r)); + p.fd = fd, p.events = POLLIN, p.revents = 0; + ASSERT(poll(&p, 1, 0) == 1); + ASSERT(p.revents == POLLIN); + FD_ZERO(&w); + FD_SET(fd, &w); + ASSERT(select(fd + 1, NULL, &w, NULL, NULL) == 1); + ASSERT(FD_ISSET(fd, &w)); + p.events = POLLOUT, p.revents = 0; + ASSERT(poll(&p, 1, 0) == 1); + ASSERT(p.revents == POLLOUT); + ASSERT(close(lfd) == 0); + echo_task_common(fd, 0, &s_ready_done); +} + +static void backlog_task(void *args) { + struct pollfd p; + BaseType_t result; + int fd, lfd; + + (void) args; + lfd = open_listener(1, true); + p.fd = lfd, p.events = POLLIN, p.revents = 0; + s_backlog_ready = true; + while (poll(&p, 1, 0) == 0) vTaskDelay(1); + ASSERT(p.revents == POLLIN); + s_backlog_full = true; // One orphan occupies the BSD backlog + while (!s_backlog_accept) vTaskDelay(1); + ASSERT((fd = accept(lfd, NULL, NULL)) >= 0); + result = xTaskCreate(echo_task, "echo", 512, (void *) (uintptr_t) fd, + configMAX_PRIORITIES - 1, NULL); + ASSERT(result == pdPASS); + ASSERT(close(lfd) == 0); + vTaskDelete(NULL); +} // task2: tests owner. static void task2(void *args) { struct mg_mgr *mgr = (struct mg_mgr *)args; // Event manager - char *ip, *cmd[3]; - pid_t pid; + char *ip; int status; - extern char **environ; - -#define DASHBOARD(x) printf("HEALTH_DASHBOARD\t\"%s\": %s,\n", x, s_error ? "false":"true"); + pid_t first, second; vTaskDelay(pdMS_TO_TICKS(500)); // 500 ms s_error = false; s_wait = false; - // atask at higher priority than task1 - xTaskCreate(atask, "atask", 256, NULL, configMAX_PRIORITIES - 1, NULL); ip = mg_mprintf("%M", mg_print_ip4, &mgr->ifp->ip); - cmd[0] = (char *) "./bsd_client"; - cmd[1] = ip; - cmd[2] = NULL; - ASSERT(posix_spawn(&pid, cmd[0], NULL, NULL, cmd, environ) == 0); - for (;;) { - pid_t r = waitpid(pid, &status, WNOHANG); - ASSERT(r >= 0); - if (r == pid) break; - vTaskDelay(pdMS_TO_TICKS(10)); - } - ASSERT(WIFEXITED(status)); - ASSERT(WEXITSTATUS(status) == 0); - mg_free(ip); + s_poll_waiting = s_poll_done = false; + ASSERT(xTaskCreate(poll_task, "poll", 512, NULL, + configMAX_PRIORITIES - 1, NULL) == pdPASS); + while (!s_poll_waiting) vTaskDelay(1); + ASSERT(!s_poll_done); + run_client(ip, "hold"); + while (!s_poll_done) vTaskDelay(1); + DASHBOARD("poll_read"); + + s_select_waiting = s_select_done = false; + ASSERT(xTaskCreate(select_task, "select", 512, NULL, + configMAX_PRIORITIES - 1, NULL) == pdPASS); + while (!s_select_waiting) vTaskDelay(1); + ASSERT(!s_select_done); + run_client(ip, "hold"); + while (!s_select_done) vTaskDelay(1); + DASHBOARD("select_read"); + + s_error = false; + s_ready_waiting = s_ready_done = false; + ASSERT(xTaskCreate(readiness_task, "ready", 512, NULL, + configMAX_PRIORITIES - 1, NULL) == pdPASS); + while (!s_ready_waiting) vTaskDelay(1); + run_client(ip, "1"); + while (!s_ready_done) vTaskDelay(1); + DASHBOARD("select_child_read"); + DASHBOARD("poll_child_read"); + DASHBOARD("select_child_write"); + DASHBOARD("poll_child_write"); + + #if 0 + s_error = false; + s_backlog_ready = s_backlog_full = s_backlog_accept = false; + ASSERT(xTaskCreate(backlog_task, "backlog", 512, NULL, + configMAX_PRIORITIES - 1, NULL) == pdPASS); + while (!s_backlog_ready) vTaskDelay(1); + first = start_client(ip, "1"); // Fills listen(fd, 1)'s pending accept + while (!s_backlog_full) vTaskDelay(1); + second = start_client(ip, "expectclose"); // Rejected over the backlog + while (!client_done(second, &status)) vTaskDelay(1); + check_client(status); + s_backlog_accept = true; + while (!client_done(first, &status)) vTaskDelay(1); + check_client(status); + DASHBOARD("listen_backlog"); +#else + (void) first; + (void) second; + (void) status; + (void) backlog_task; +#endif + + s_error = false; + // atask at higher priority than task1 + s_accept_waiting = s_accept_done = false; + ASSERT(xTaskCreate(atask, "atask", 256, (void *) &s_accept_server, + configMAX_PRIORITIES - 1, NULL) == pdPASS); + while (!s_accept_waiting) vTaskDelay(pdMS_TO_TICKS(10)); + ASSERT(!s_accept_done); + run_client(ip, "1"); + while (!s_accept_done) vTaskDelay(pdMS_TO_TICKS(10)); + DASHBOARD("blocking_accept"); + + s_error = false; + s_wait = false; + s_accept_waiting = s_accept_done = false; + ASSERT(xTaskCreate(atask, "atask", 256, (void *) &s_nonblock_accept_server, + configMAX_PRIORITIES - 1, NULL) == pdPASS); + while (!s_accept_waiting) vTaskDelay(pdMS_TO_TICKS(10)); + run_client(ip, "1"); + while (!s_accept_done) vTaskDelay(pdMS_TO_TICKS(10)); + DASHBOARD("nonblocking_accept"); + + s_error = false; + s_wait = false; + ASSERT(xTaskCreate(atask, "atask", 256, (void *) &s_echo_server, + configMAX_PRIORITIES - 1, NULL) == pdPASS); + run_client(ip, NULL); s_wait = true; while (s_wait) vTaskDelay(pdMS_TO_TICKS(10)); DASHBOARD("accept"); + s_error = false; + s_wait = false; + s_dontwait_done = false; + ASSERT(xTaskCreate(atask, "atask", 256, (void *) &s_dontwait_server, + configMAX_PRIORITIES - 1, NULL) == pdPASS); + run_client(ip, "1"); + while (!s_dontwait_done) vTaskDelay(pdMS_TO_TICKS(10)); + s_wait = true; + while (s_wait) vTaskDelay(pdMS_TO_TICKS(10)); + DASHBOARD("dontwait_recv"); + + s_error = false; + s_wait = false; + s_nonblock_done = false; + ASSERT(xTaskCreate(atask, "atask", 256, (void *) &s_nonblock_server, + configMAX_PRIORITIES - 1, NULL) == pdPASS); + run_client(ip, "1"); + while (!s_nonblock_done) vTaskDelay(pdMS_TO_TICKS(10)); + s_wait = true; + while (s_wait) vTaskDelay(pdMS_TO_TICKS(10)); + DASHBOARD("nonblocking_recv"); + + s_error = false; + s_wait = s_tx_held = s_tx_waits = s_tx_fail = s_tx_error = false; + s_send_waiting = false; + s_send_done = s_send_expect_error = false; + s_recv_start = s_recv_waiting = s_recv_done = s_recv_expect_error = false; + ASSERT(xTaskCreate(atask, "atask", 256, (void *) &s_send_server, + configMAX_PRIORITIES - 1, NULL) == pdPASS); + run_client(ip, "waitanddrain"); // Peer drains through the final marker + ASSERT(s_send_done); + DASHBOARD("nonblocking_send"); + DASHBOARD("blocking_send"); + while (!s_recv_done) vTaskDelay(pdMS_TO_TICKS(10)); + DASHBOARD("blocking_recv"); + DASHBOARD("shared_socket"); + s_wait = true; + while (s_wait) vTaskDelay(pdMS_TO_TICKS(10)); + + s_error = false; + s_wait = s_tx_held = s_tx_waits = s_tx_fail = s_tx_error = false; + s_send_waiting = false; + s_send_done = s_send_expect_error = false; + s_recv_start = s_recv_waiting = s_recv_done = false; + s_recv_expect_error = true; + ASSERT(xTaskCreate(atask, "atask", 256, (void *) &s_send_server, + configMAX_PRIORITIES - 1, NULL) == pdPASS); + run_client(ip, "waitandabort"); // Peer leaves data unread and sends RST + ASSERT(s_send_done); + while (!s_recv_done) vTaskDelay(pdMS_TO_TICKS(10)); + DASHBOARD("error_wakeup"); + s_wait = true; + while (s_wait) vTaskDelay(pdMS_TO_TICKS(10)); + + s_error = false; + s_wait = s_tx_held = s_tx_waits = s_tx_fail = s_tx_error = false; + s_send_waiting = false; + s_send_done = false; + s_send_expect_error = true; + ASSERT(xTaskCreate(atask, "atask", 256, (void *) &s_send_error_server, + configMAX_PRIORITIES - 1, NULL) == pdPASS); + run_client(ip, "hold"); // Peer stays connected while the driver fails TX + while (!s_tx_error) vTaskDelay(pdMS_TO_TICKS(10)); + ASSERT(s_send_done); + DASHBOARD("send_error_wakeup"); + s_wait = true; + while (s_wait) vTaskDelay(pdMS_TO_TICKS(10)); + s_tx_held = s_tx_fail = s_tx_error = s_send_expect_error = false; + + mg_free(ip); + + s_error = false; + { + fd_set e; + struct pollfd p; + int fd = socket(AF_INET, SOCK_STREAM, 0); + struct sockaddr_in sa; + memset(&sa, 0, sizeof(sa)); + sa.sin_family = AF_INET; + sa.sin_port = htons(1234); + ASSERT(fcntl(fd, F_SETFL, O_NONBLOCK) == 0); + ASSERT(connect(fd, (struct sockaddr *) &sa, sizeof(sa)) < 0 && + errno == EINPROGRESS); + FD_ZERO(&e); + FD_SET(fd, &e); + ASSERT(select(fd + 1, NULL, NULL, &e, NULL) == 1); + ASSERT(FD_ISSET(fd, &e)); + p.fd = fd, p.events = 0, p.revents = 0; + ASSERT(poll(&p, 1, -1) == 1); + ASSERT((p.revents & POLLERR) != 0); + p.events = POLLOUT, p.revents = 0; + ASSERT(poll(&p, 1, 0) == 1); + ASSERT((p.revents & POLLERR) != 0); + ASSERT((p.revents & POLLOUT) == 0); + ASSERT(close(fd) == 0); + } + DASHBOARD("select_error"); + DASHBOARD("poll_error"); + vTaskDelay(pdMS_TO_TICKS(500)); // 500 ms s_error = false; { @@ -188,6 +747,7 @@ static void task2(void *args) { while (!cd[0].done || !cd[1].done || !cd[2].done) vTaskDelay(pdMS_TO_TICKS(10)); } DASHBOARD("connect"); + DASHBOARD("select_write"); // End task 1 s_done = true; while (s_done) vTaskDelay(pdMS_TO_TICKS(10)); @@ -206,35 +766,205 @@ static void task2(void *args) { static void task1(void *args) { struct mg_mgr *mgr = (struct mg_mgr *)args; // Event manager - // Stack initialization, Network configuration (DHCP lease, ...) -#if BSDTEST_USING_DHCP == 0 - MG_INFO(("MIF configuration: Static IP")); - ASSERT(mif.ip != 0); // Check we have a satic IP assigned - mg_mgr_poll(&mgr, 0); // For initialisation -#else - MG_INFO(("MIF configuration: DHCP")); - ASSERT(mgr->ifp->ip == 0); // Check we are set for DHCP - while (mgr->ifp->ip == 0 && mg_millis() < 5000) { - mg_mgr_poll(mgr, 0); - } - if (mgr->ifp->ip == 0) MG_ERROR(("No ip assigned (DHCP lease may have failed).\n")); - ASSERT(mgr->ifp->ip != 0); // We have an IP (lease or static) -#endif - while (mgr->ifp->state != MG_TCPIP_STATE_READY) { - mg_mgr_poll(mgr, 0); - } - MG_INFO(("Interface started, starting main loop")); + wait_ready(mgr); xTaskCreate(task2, "task2", 256, mgr, configMAX_PRIORITIES - 2, NULL); while (!s_done) { mg_mgr_poll(mgr, 0); - mg_bsd_poll(mgr); vTaskDelay(1); // Let idle reclaim deleted echo tasks } s_done = false; vTaskDelete(NULL); } +#else + +#define NORTOS_ECHO_CLIENTS 3 + +struct nortos_echo_client { + int fd; + size_t len, sent; + bool eof; + char buf[512]; +}; + +static int nortos_accept(struct mg_mgr *mgr, int lfd) { + int fd; + + while ((fd = accept(lfd, NULL, NULL)) < 0) { + ASSERT(errno == EAGAIN); + mg_mgr_poll(mgr, 0); + } + return fd; +} + +static void nortos_wait_client(struct mg_mgr *mgr, pid_t pid) { + int status; + + while (!client_done(pid, &status)) mg_mgr_poll(mgr, 0); + check_client(status); +} + +static void nortos_echo(struct mg_mgr *mgr, struct nortos_echo_client *clients, + size_t count, pid_t pid) { + struct pollfd p[NORTOS_ECHO_CLIENTS]; + fd_set r; + size_t i, live = count; + int maxfd, result; + ssize_t n; + + while (live > 0) { + for (i = 0; i < count; i++) { + struct nortos_echo_client *c = &clients[i]; + if (!c->eof && c->sent < c->len) { + n = send(c->fd, c->buf + c->sent, c->len - c->sent, 0); + if (n > 0) c->sent += (size_t) n; + else ASSERT(n < 0 && errno == EAGAIN); + } + } + + FD_ZERO(&r); + maxfd = -1; + for (i = 0; i < count; i++) { + struct nortos_echo_client *c = &clients[i]; + p[i].fd = -1, p[i].events = POLLIN, p[i].revents = 0; + if (!c->eof && c->sent == c->len) { + p[i].fd = c->fd; + FD_SET(c->fd, &r); + if (c->fd > maxfd) maxfd = c->fd; + } + } + if (maxfd >= 0) { + result = poll(p, (nfds_t) count, 0); + ASSERT(result >= 0); + if (result > 0) { + ASSERT(select(maxfd + 1, &r, NULL, NULL, NULL) == result); + for (i = 0; i < count; i++) { + struct nortos_echo_client *c = &clients[i]; + bool readable = (p[i].revents & POLLIN) != 0; + + ASSERT(p[i].revents == (readable ? POLLIN : 0)); + ASSERT(FD_ISSET(c->fd, &r) == readable); + if (readable) { + n = recv(c->fd, c->buf, sizeof(c->buf), 0); + ASSERT(n >= 0); + if (n > 0) c->len = (size_t) n, c->sent = 0; + else c->eof = true, live--; + } + } + } + } + mg_mgr_poll(mgr, 0); + } + nortos_wait_client(mgr, pid); +} + +static void nortos_test(struct mg_mgr *mgr) { + static const char bootstrap = '\0'; + static const char grow[512] = {0}; + static const char marker = '\1'; + struct sockaddr_in sa; + struct timeval zero = {0, 0}; + struct pollfd p; + fd_set r, w, e; + char buf; + char *ip; + pid_t pid; + size_t i, j; + int fd, lfd, n; + struct nortos_echo_client clients[NORTOS_ECHO_CLIENTS]; + + ip = mg_mprintf("%M", mg_print_ip4, &mgr->ifp->ip); + ASSERT(ip != NULL); + lfd = open_listener(5, true); + p.fd = lfd, p.events = POLLIN, p.revents = 0; + ASSERT(poll(&p, 1, 10) == 0); + ASSERT(p.revents == 0); + p.fd = lfd + 1, p.revents = 0; + ASSERT(poll(&p, 1, 10) == 1); + ASSERT(p.revents == POLLNVAL); + p.fd = lfd, p.revents = 0; + FD_ZERO(&r); + FD_SET(lfd, &r); + ASSERT(select(lfd, &r, NULL, NULL, &zero) == 0); + ASSERT(!FD_ISSET(lfd, &r)); + FD_ZERO(&r); + FD_SET(lfd + 1, &r); + ASSERT(select(lfd + 2, &r, NULL, NULL, &zero) < 0 && errno == EBADF); + ASSERT(accept(lfd, NULL, NULL) < 0 && errno == EAGAIN); + DASHBOARD("nortos_nonblocking_accept"); + + pid = start_client(ip, "waitanddrain"); + do { + p.revents = 0; + n = poll(&p, 1, 10); + if (n == 0) mg_mgr_poll(mgr, 0); + } while (n == 0); + ASSERT(n == 1); + ASSERT(p.revents == POLLIN); + DASHBOARD("nortos_poll_read"); + do { + FD_ZERO(&r); + FD_ZERO(&w); + FD_ZERO(&e); + FD_SET(lfd, &r); + FD_SET(lfd, &w); + FD_SET(lfd, &e); + n = select(lfd + 1, &r, &w, &e, NULL); + if (n == 0) mg_mgr_poll(mgr, 0); + } while (n == 0); + ASSERT(n == 1); + ASSERT(FD_ISSET(lfd, &r)); + ASSERT(!FD_ISSET(lfd, &w)); + ASSERT(!FD_ISSET(lfd, &e)); + DASHBOARD("nortos_select_read"); + fd = nortos_accept(mgr, lfd); + ASSERT(recv(fd, &buf, sizeof(buf), MSG_DONTWAIT) < 0 && errno == EAGAIN); + DASHBOARD("nortos_dontwait_recv"); + ASSERT(fcntl(fd, F_SETFL, O_NONBLOCK) == 0); + ASSERT(recv(fd, &buf, sizeof(buf), 0) < 0 && errno == EAGAIN); + DASHBOARD("nortos_nonblocking_recv"); + + ASSERT(send(fd, &bootstrap, sizeof(bootstrap), 0) == + (ssize_t) sizeof(bootstrap)); + s_fail_alloc = true; // Fail growth after c->send has been bootstrapped + ASSERT(send(fd, grow, sizeof(grow), 0) < 0 && errno == EAGAIN); + s_fail_alloc = false; + ASSERT(send(fd, &marker, sizeof(marker), 0) == (ssize_t) sizeof(marker)); + nortos_wait_client(mgr, pid); + ASSERT(close(fd) == 0); + DASHBOARD("nortos_nonblocking_send"); + + memset(clients, 0, sizeof(clients)); + pid = start_client(ip, "3"); + for (i = 0; i < NORTOS_ECHO_CLIENTS; i++) { + clients[i].fd = nortos_accept(mgr, lfd); + ASSERT(fcntl(clients[i].fd, F_SETFL, O_NONBLOCK) == 0); + for (j = 0; j < i; j++) ASSERT(clients[i].fd != clients[j].fd); + } + nortos_echo(mgr, clients, NORTOS_ECHO_CLIENTS, pid); + for (i = 0; i < NORTOS_ECHO_CLIENTS; i++) + ASSERT(close(clients[i].fd) == 0); + ASSERT(close(lfd) == 0); + DASHBOARD("nortos_select_children"); + DASHBOARD("nortos_poll_children"); + DASHBOARD("nortos_echo"); + + fd = socket(AF_INET, SOCK_STREAM, 0); + memset(&sa, 0, sizeof(sa)); + sa.sin_family = AF_INET; + sa.sin_port = htons(1234); + ASSERT(fcntl(fd, F_SETFL, O_NONBLOCK) == 0); + ASSERT(connect(fd, (struct sockaddr *) &sa, sizeof(sa)) < 0 && + errno == EINPROGRESS); + ASSERT(close(fd) == 0); + mg_mgr_poll(mgr, 0); + DASHBOARD("nortos_nonblocking_connect"); + mg_free(ip); +} + +#endif // MG_ENABLE_FREERTOS + static bool bsd_x_test(struct mg_tcpip_driver *driver, void *driver_data, const char *mac, const char *debug_level) { if (debug_level == NULL) debug_level = "3"; mg_log_set(atoi(debug_level)); @@ -263,8 +993,21 @@ static bool bsd_x_test(struct mg_tcpip_driver *driver, void *driver_data, const MG_INFO(("Init done, starting interface")); usleep(200000); // 200 ms +#if MG_ENABLE_FREERTOS + s_tx = driver->tx; + driver->tx = tx_gate; xTaskCreate(task1, "task1", 2048, &mgr, configMAX_PRIORITIES - 2, NULL); vTaskStartScheduler(); + driver->tx = s_tx; +#else + s_bootmillis = mg_millis(); + wait_ready(&mgr); + nortos_test(&mgr); + s_error = false; + mg_mgr_free(&mgr); + ASSERT(mgr.conns == NULL); + printf("HEALTH_DASHBOARD\t\"cleanup\": %s\n", s_error ? "false" : "true"); +#endif #ifdef NO_ABORT if (s_abort != 0) return false; diff --git a/test/bsd/mongoose_config.h b/test/bsd/mongoose_config.h index 31cc5bd2..ef156cde 100644 --- a/test/bsd/mongoose_config.h +++ b/test/bsd/mongoose_config.h @@ -1,18 +1,13 @@ #pragma once // See https://mongoose.ws/documentation/#build-options -//#undef MG_ARCH -//#define MG_ARCH MG_ARCH_UNIX -#define MG_ENABLE_FREERTOS 1 #define MG_ENABLE_TCPIP 1 #define MG_ENABLE_TCPIP_DRIVER_INIT 0 #define MG_ENABLE_BSD_SOCKETS 1 -#define MG_ENABLE_BSD_LOG 1 #define MG_ENABLE_LINES 1 #define MG_ENABLE_CUSTOM_RANDOM 1 -//#include #include #include #include @@ -24,7 +19,16 @@ #include #include -#include #include #include #include + +#if MG_ENABLE_FREERTOS + + +#else + +#define MG_ENABLE_CUSTOM_CALLOC 1 +#define MG_ENABLE_CUSTOM_MILLIS 1 + +#endif diff --git a/tutorials/stm32/nucleo-h723zg/minimal-bsd-baremetal/Makefile b/tutorials/stm32/nucleo-h723zg/minimal-bsd-baremetal/Makefile new file mode 100644 index 00000000..2ab24109 --- /dev/null +++ b/tutorials/stm32/nucleo-h723zg/minimal-bsd-baremetal/Makefile @@ -0,0 +1,35 @@ +# Copyright (c) 2026 Cesanta Software Limited +# Environment setup: https://mongoose.ws/docs/getting-started/build-environment/ + +CFLAGS = -W -Wall -Wextra -Wundef -Wshadow -Wdouble-promotion +CFLAGS += -Wformat-truncation -fno-common -Wconversion -Wno-sign-conversion +CFLAGS += -g3 -Os -ffunction-sections -fdata-sections +CFLAGS += -I. -Ibsd -Icmsis_core/CMSIS/Core/Include -Icmsis_h7/Include +CFLAGS += -mcpu=cortex-m7 -mthumb -mfloat-abi=hard -mfpu=fpv5-d16 $(CFLAGS_EXTRA) +LDFLAGS ?= -Tlink.ld -nostdlib -nostartfiles --specs nosys.specs -lc -lgcc -Wl,--gc-sections -Wl,-Map=$@.map + +SOURCES = main.c hal.c echo.c +SOURCES += cmsis_h7/Source/Templates/gcc/startup_stm32h723xx.s +SOURCES += mongoose.c + +all build example: firmware.bin + +firmware.elf: cmsis_core cmsis_h7 hal.h link.ld Makefile $(SOURCES) mongoose.h mongoose_config.h + arm-none-eabi-gcc $(SOURCES) $(CFLAGS) $(CFLAGS_EXTRA) $(LDFLAGS) -o $@ + +firmware.bin: firmware.elf + arm-none-eabi-objcopy -O binary $< $@ + @echo + @echo "To flash, run 'make flash', or use STM32CubeProgrammer" + +flash: firmware.bin + STM32_Programmer_CLI -c port=swd -w firmware.elf -hardRst + +cmsis_core: + git clone -q -c advice.detachedHead=false --depth 1 -b 5.9.0 https://github.com/ARM-software/CMSIS_5 $@ + +cmsis_h7: + git clone -q -c advice.detachedHead=false --depth 1 -b v1.10.6 https://github.com/STMicroelectronics/cmsis_device_h7 $@ + +clean: + rm -rf firmware.* cmsis_* diff --git a/tutorials/stm32/nucleo-h723zg/minimal-bsd-baremetal/bsd b/tutorials/stm32/nucleo-h723zg/minimal-bsd-baremetal/bsd new file mode 120000 index 00000000..6079e81d --- /dev/null +++ b/tutorials/stm32/nucleo-h723zg/minimal-bsd-baremetal/bsd @@ -0,0 +1 @@ +../../../../src/bsd \ No newline at end of file diff --git a/tutorials/stm32/nucleo-h723zg/minimal-bsd-baremetal/echo.c b/tutorials/stm32/nucleo-h723zg/minimal-bsd-baremetal/echo.c new file mode 100644 index 00000000..e4de2268 --- /dev/null +++ b/tutorials/stm32/nucleo-h723zg/minimal-bsd-baremetal/echo.c @@ -0,0 +1,120 @@ +// Copyright (c) 2026 Cesanta Software Limited +// All rights reserved +// +// This is an implementation of the simple TCP echo server which supports +// multiple concurrent connections, using BSD sockets API. + +#include +#include +#include +#include +#include +#include +#include +#include + +struct echo_server { + int sock; + struct echo_client { + struct echo_client *next; + int sock; + size_t len, sent; + char buf[128]; + } *clients; +}; + +static struct echo_server s_server = {-1, NULL}; + +static int echo_nonblock(int fd) { + int flags = fcntl(fd, F_GETFL, 0); + return flags >= 0 && fcntl(fd, F_SETFL, flags | O_NONBLOCK) == 0; +} + +static void echo_remove(struct echo_client **p) { + struct echo_client *c = *p; + *p = c->next; + close(c->sock); + free(c); +} + +int echo_init(uint16_t port) { + struct sockaddr_in sa = { + .sin_family = AF_INET, + .sin_port = htons(port), + .sin_addr = {.s_addr = INADDR_ANY}, + }; + int fd = socket(AF_INET, SOCK_STREAM, 0); + + if (fd < 0 || !echo_nonblock(fd) || + bind(fd, (struct sockaddr *) &sa, sizeof(sa)) < 0 || listen(fd, 3) < 0) { + printf("BSD echo init: %d\n", errno); + if (fd >= 0) close(fd); + return 0; + } + s_server.sock = fd; + printf("BSD echo server on port %hu\n", port); + return 1; +} + +void echo_poll(void) { + struct echo_server *s = &s_server; + struct echo_client *c, **p; + int maxfd, n; + fd_set r, w; + + if (s->sock < 0) return; + maxfd = s->sock; + FD_ZERO(&r); + FD_ZERO(&w); + FD_SET(s->sock, &r); + for (c = s->clients; c != NULL; c = c->next) { + if (c->len == c->sent) + FD_SET(c->sock, &r); + else + FD_SET(c->sock, &w); + if (c->sock > maxfd) maxfd = c->sock; + } + + if (select(maxfd + 1, &r, &w, NULL, NULL) <= 0) return; + + if (FD_ISSET(s->sock, &r)) { + struct sockaddr_in sa; + socklen_t len = sizeof(sa); + int fd = accept(s->sock, (struct sockaddr *) &sa, &len); + if (fd >= 0) { + c = (struct echo_client *) calloc(1, sizeof(*c)); + if (c == NULL || !echo_nonblock(fd)) { + printf("BSD echo accept: %d\n", c == NULL ? ENOMEM : errno); + close(fd); + free(c); + } else { + c->sock = fd; + c->next = s->clients; + s->clients = c; + } + } + } + + p = &s->clients; + while (*p != NULL) { + c = *p; + if (c->sent < c->len && FD_ISSET(c->sock, &w)) { + n = (int) send(c->sock, c->buf + c->sent, c->len - c->sent, 0); + if (n > 0) c->sent += (size_t) n; + if (n < 0 && errno != EAGAIN && errno != EWOULDBLOCK) { + echo_remove(p); + continue; + } + } + if (c->sent == c->len && FD_ISSET(c->sock, &r)) { + n = (int) recv(c->sock, c->buf, sizeof(c->buf), 0); + if (n > 0) c->len = (size_t) n, c->sent = 0; + if (n == 0 || (n < 0 && errno != EAGAIN && errno != EWOULDBLOCK)) { + echo_remove(p); + continue; + } + } + if (c->sent == c->len) c->len = c->sent = 0; + p = &c->next; + } +} diff --git a/tutorials/stm32/nucleo-h723zg/minimal-bsd/hal.c b/tutorials/stm32/nucleo-h723zg/minimal-bsd-baremetal/hal.c similarity index 100% rename from tutorials/stm32/nucleo-h723zg/minimal-bsd/hal.c rename to tutorials/stm32/nucleo-h723zg/minimal-bsd-baremetal/hal.c diff --git a/tutorials/stm32/nucleo-h723zg/minimal-bsd-baremetal/hal.h b/tutorials/stm32/nucleo-h723zg/minimal-bsd-baremetal/hal.h new file mode 100644 index 00000000..4fef7c79 --- /dev/null +++ b/tutorials/stm32/nucleo-h723zg/minimal-bsd-baremetal/hal.h @@ -0,0 +1,225 @@ +// Copyright (c) 2022-2026 Cesanta Software Limited +// Datasheet: RM0468, devboard manual: UM2407 +// https://www.st.com/resource/en/reference_manual/rm0468-stm32h723733-stm32h725735-and-stm32h730-value-line-advanced-armbased-32bit-mcus-stmicroelectronics.pdf +// https://www.st.com/resource/en/user_manual/um2407-stm32h7-nucleo144-boards-mb1364-stmicroelectronics.pdf +// Alternate functions: https://www.st.com/resource/en/datasheet/stm32h723.pdf + +#pragma once + +#include + +#include +#include +#include +#include + +extern bool hal_timer_expired(volatile uint64_t *t, uint64_t prd, uint64_t now); +extern uint64_t hal_get_tick(void); +extern size_t hal_ram_free(void); +extern size_t hal_ram_used(void); +extern uint32_t SystemCoreClock; + +#define BIT(x) (1UL << (x)) +#define CLRSET(reg, clear, set) ((reg) = ((reg) & ~(clear)) | (set)) +#define PIN(bank, num) ((((bank) - 'A') << 8) | (num)) +#define PINNO(pin) (pin & 255) +#define PINBANK(pin) (pin >> 8) + +#define HAL_ETH_PINS PIN('A', 1), PIN('A', 2), PIN('A', 7), \ + PIN('B', 13), PIN('C', 1), PIN('C', 4), \ + PIN('C', 5), PIN('G', 11), PIN('G', 13) + +// System clock (2.1, Figure 1; 8.5, Figure 45; 8.5.5, Figure 47; 8.5.6, Figure +// 49; 8.5.8 Table 56; datasheet) CPU_FREQUENCY <= 550 MHz; hclk = CPU_FREQUENCY +// / HAL_HPRE ; hclk <= 275 MHz; APB clocks <= 137.5 MHz. D1 domain bus matrix (and +// so flash) runs at hclk frequency. Configure flash latency (WS) in accordance +// to hclk freq (4.3.8, Table 16) The Ethernet controller is in D2 domain and +// runs at hclk frequency +enum { + HAL_D1CPRE = 1, // actual divisor value + HAL_HPRE = 2, // actual divisor value + HAL_D1PPRE = 4, // register values, divisor value = BIT(value - 3) = / 2 + HAL_D2PPRE1 = 4, + HAL_D2PPRE2 = 4, + HAL_D3PPRE = 4 +}; +// PLL1_P: odd division factors are not allowed (8.7.12) (except for '1') +enum { PLL1_HSI = 64, PLL1_M = 32, PLL1_N = 225, PLL1_P = 1 }; +#define HAL_FLASH_LATENCY 0x33 // WRHIGHFREQ LATENCY +#define SYS_FREQUENCY ((PLL1_HSI * PLL1_N / PLL1_M / PLL1_P / HAL_D1CPRE) * 1000000) +#define AHB_FREQUENCY (SYS_FREQUENCY / HAL_HPRE) +#define APB2_FREQUENCY (AHB_FREQUENCY / (BIT(HAL_D2PPRE2 - 3))) +#define APB1_FREQUENCY (AHB_FREQUENCY / (BIT(HAL_D2PPRE1 - 3))) + + +enum { HAL_GPIO_MODE_INPUT, HAL_GPIO_MODE_OUTPUT, HAL_GPIO_MODE_AF, HAL_GPIO_MODE_ANALOG }; +enum { HAL_GPIO_OTYPE_PUSH_PULL, HAL_GPIO_OTYPE_OPEN_DRAIN }; +enum { HAL_GPIO_SPEED_LOW, HAL_GPIO_SPEED_MEDIUM, HAL_GPIO_SPEED_HIGH, HAL_GPIO_SPEED_INSANE }; +enum { HAL_GPIO_PULL_NONE, HAL_GPIO_PULL_UP, HAL_GPIO_PULL_DOWN }; + +#define HAL_GPIO(N) ((GPIO_TypeDef *) (0x40000000 + 0x18020000UL + 0x400 * (N))) + +static GPIO_TypeDef *hal_gpio_bank(uint16_t pin) { + return HAL_GPIO(PINBANK(pin)); +} +static inline void hal_gpio_toggle(uint16_t pin) { + GPIO_TypeDef *gpio = hal_gpio_bank(pin); + uint32_t mask = BIT(PINNO(pin)); + gpio->BSRR = mask << (gpio->ODR & mask ? 16 : 0); +} +static inline int hal_gpio_read(uint16_t pin) { + return hal_gpio_bank(pin)->IDR & BIT(PINNO(pin)) ? 1 : 0; +} +static inline void hal_gpio_write(uint16_t pin, bool val) { + GPIO_TypeDef *gpio = hal_gpio_bank(pin); + gpio->BSRR = BIT(PINNO(pin)) << (val ? 0 : 16); +} +static inline void hal_gpio_init(uint16_t pin, uint8_t mode, uint8_t type, + uint8_t speed, uint8_t pull, uint8_t af) { + GPIO_TypeDef *gpio = hal_gpio_bank(pin); + uint8_t n = (uint8_t) (PINNO(pin)); + RCC->AHB4ENR |= BIT(PINBANK(pin)); // Enable GPIO clock + CLRSET(gpio->OTYPER, 1UL << n, ((uint32_t) type) << n); + CLRSET(gpio->OSPEEDR, 3UL << (n * 2), ((uint32_t) speed) << (n * 2)); + CLRSET(gpio->PUPDR, 3UL << (n * 2), ((uint32_t) pull) << (n * 2)); + CLRSET(gpio->AFR[n >> 3], 15UL << ((n & 7) * 4), + ((uint32_t) af) << ((n & 7) * 4)); + CLRSET(gpio->MODER, 3UL << (n * 2), ((uint32_t) mode) << (n * 2)); +} +static inline void hal_gpio_input(uint16_t pin) { + hal_gpio_init(pin, HAL_GPIO_MODE_INPUT, HAL_GPIO_OTYPE_PUSH_PULL, HAL_GPIO_SPEED_HIGH, + HAL_GPIO_PULL_NONE, 0); +} +static inline void hal_gpio_output(uint16_t pin) { + hal_gpio_init(pin, HAL_GPIO_MODE_OUTPUT, HAL_GPIO_OTYPE_PUSH_PULL, HAL_GPIO_SPEED_HIGH, + HAL_GPIO_PULL_NONE, 0); +} + +// D2 Kernel clock (8.7.21) USART1 defaults to pclk2 (APB2), while USART2,3 +// default to pclk1 (APB1). Even if using other kernel clocks, the APBx clocks +// must be enabled for CPU access, as the kernel clock drives the BRR, not the +// APB bus interface +static inline void hal_uart_init(USART_TypeDef *uart, uint16_t tx, + uint16_t rx, unsigned long baud) { + uint8_t af = 7; // Alternate function + uint32_t freq = 0; // Bus frequency. UART1 is on APB2, rest on APB1 + + if (uart == USART1) freq = APB2_FREQUENCY, RCC->APB2ENR |= BIT(4); + if (uart == USART2) freq = APB1_FREQUENCY, RCC->APB1LENR |= BIT(17); + if (uart == USART3) freq = APB1_FREQUENCY, RCC->APB1LENR |= BIT(18); + +#if 0 // CONSTANT BAUD RATE FOR REMOTE DEBUGGING WHILE SETTING THE PLL + CLRSET(RCC->D2CCIP2R, 7 << 3, 3 << 3); // use HSI for UART1 + freq = 64000000; +#endif + + hal_gpio_init(tx, HAL_GPIO_MODE_AF, HAL_GPIO_OTYPE_PUSH_PULL, HAL_GPIO_SPEED_HIGH, 0, af); + hal_gpio_init(rx, HAL_GPIO_MODE_AF, HAL_GPIO_OTYPE_PUSH_PULL, HAL_GPIO_SPEED_HIGH, 0, af); + uart->CR1 = 0; // Disable this UART + uart->BRR = freq / baud; // Set baud rate + uart->CR1 = BIT(0) | BIT(2) | BIT(3); // Set UE, RE, TE +} +static inline void hal_uart_write_byte(USART_TypeDef *uart, uint8_t byte) { + uart->TDR = byte; + while ((uart->ISR & USART_ISR_TXE_TXFNF) == 0) (void) 0; +} +static inline void hal_uart_write_buf(USART_TypeDef *uart, char *buf, size_t len) { + while (len-- > 0) hal_uart_write_byte(uart, *(uint8_t *) buf++); +} +static inline int hal_uart_read_ready(USART_TypeDef *uart) { + return uart->ISR & BIT(5); // If RXNE bit is set, data is ready +} +static inline uint8_t hal_uart_read_byte(USART_TypeDef *uart) { + return (uint8_t) (uart->RDR & 255); +} + +// RNG clock (rng_clk) defaults to HSI48 and is characterized at this frequency. +// Enable at SystemInit. (STM32CubeMX) It won't work with a 275MHz (AHB) +static inline void hal_rng_init(void) { + RCC->AHB2ENR |= RCC_AHB2ENR_RNGEN; // Enable RNG AHB clock (hal_rng_hclk) + // 39.6.2 Table 320 init + RNG->CR = RNG_CR_CONDRST | (0x0F << RNG_CR_RNG_CONFIG1_Pos) | + (0 << RNG_CR_RNG_CONFIG2_Pos) | (0xD << RNG_CR_RNG_CONFIG3_Pos); + RNG->HTCR = 0x17590abc; + RNG->HTCR = 0xaa74; + RNG->CR &= ~RNG_CR_CONDRST ; + while (RNG->CR & RNG_CR_CONDRST) (void) 0; // 39.7.1 + RNG->CR |= RNG_CR_RNGEN; // Enable RNG +} + +static inline uint32_t hal_rng_read(void) { + while ((RNG->SR & RNG_SR_DRDY) == 0) (void) 0; + return RNG->DR; +} + +// Hw pull-ups on PHY RXD0,1,DV to enable autonegotiation +static inline void hal_ethernet_init(void) { + // Initialise Ethernet. Enable MAC GPIO pins, see + // https://www.st.com/resource/en/user_manual/um2407-stm32h7-nucleo144-boards-mb1364-stmicroelectronics.pdf + uint16_t pins[] = {HAL_ETH_PINS}; + for (size_t i = 0; i < sizeof(pins) / sizeof(pins[0]); i++) { + hal_gpio_init(pins[i], HAL_GPIO_MODE_AF, HAL_GPIO_OTYPE_PUSH_PULL, HAL_GPIO_SPEED_INSANE, + HAL_GPIO_PULL_NONE, 11); // 11 is the Ethernet function + } + NVIC_EnableIRQ(ETH_IRQn); // Setup Ethernet IRQ handler + CLRSET(SYSCFG->PMCR, 7 << 21, 4 << 21); // Use RMII (12.3.1) + RCC->AHB1ENR |= BIT(15) | BIT(16) | BIT(17); // Enable Ethernet clocks +} + +static inline unsigned int hal_div2prescval(unsigned int div) { + // 0 --> /1; 8 --> /2 ... 11 --> /16; 12 --> /64 ... 15 --> /512 + if (div == 1) return 0; + if (div > 16) div /= 2; + unsigned int val = 7; + while (div >>= 1) ++val; + return val; +} + +static inline unsigned int hal_pllrge(unsigned int f) { + unsigned int val = 0; + while (f >>= 1) ++val; + return val - 1; +} + +// Enable RTC/BKP APB clock and backup domain write access. +// Must be called before accessing RTC backup registers or IWDG. +static inline void hal_backup_domain_init(void) { + RCC->APB4ENR |= RCC_APB4ENR_RTCAPBEN; // enable RTC/BKP APB clock + PWR->CR1 |= PWR_CR1_DBP; // enable backup domain write access +} + +static inline void hal_clock_init(void) { + hal_backup_domain_init(); + PWR->CR3 |= PWR_CR3_LDOEN; // select LDO (reset value) + while ((PWR->CSR1 & PWR_CSR1_ACTVOSRDY) == 0) (void) 0; // ACTVOSRDY + PWR->D3CR &= ~PWR_D3CR_VOS_Msk; // Select VOS0 + while ((PWR->D3CR & PWR_D3CR_VOSRDY) == 0) (void) 0; // VOSRDY + CLRSET( + RCC->D1CFGR, (0x0F << 8) | (7 << 4) | (0x0F << 0), + (hal_div2prescval(HAL_D1CPRE) << 8) | (HAL_D1PPRE << 4) | (hal_div2prescval(HAL_HPRE) << 0)); + RCC->D2CFGR = (HAL_D2PPRE2 << 8) | (HAL_D2PPRE1 << 4); + RCC->D3CFGR = (HAL_D3PPRE << 4); + CLRSET(RCC->PLLCFGR, 3 << 2, + hal_pllrge(PLL1_HSI / PLL1_M) + << 2); // keep reset config (DIVP1EN, !PLL1VCOSEL), PLL1RGE + CLRSET(RCC->PLL1DIVR, (0x7F << 9) | (0x1FF << 0), + ((PLL1_P - 1) << 9) | ((PLL1_N - 1) << 0)); // Set PLL1_P PLL1_N + CLRSET(RCC->PLLCKSELR, 0x3F << 4, + PLL1_M << 4); // Set PLL1_M (source defaults to HSI) + RCC->CR |= RCC_CR_PLL1ON | RCC_CR_HSI48ON; // Enable PLL1 and HSI48 (for RNG) + while ((RCC->CR & RCC_CR_PLL1RDY) == 0) (void) 0; // Wait until done + RCC->CFGR |= (3 << 0); // Set clock source to PLL1 + while ((RCC->CFGR & (7 << 3)) != (3 << 3)) (void) 0; // Wait until done + FLASH->ACR = HAL_FLASH_LATENCY; // default is larger + RCC->APB4ENR |= RCC_APB4ENR_SYSCFGEN; // Enable SYSCFG + while ((RCC->CR & RCC_CR_HSI48RDY) == 0) (void) 0; // Make sure HSI48 is ready + SystemCoreClock = SYS_FREQUENCY; // Update SystemCoreClock global var + SysTick_Config(SystemCoreClock / 1000); // Sys tick every 1ms +} + + +static inline void hal_system_init(void) { + SCB->CPACR |= ((3UL << 10 * 2) | (3UL << 11 * 2)); // Enable FPU + __DSB(); + __ISB(); +} diff --git a/tutorials/stm32/nucleo-h723zg/minimal-bsd-baremetal/link.ld b/tutorials/stm32/nucleo-h723zg/minimal-bsd-baremetal/link.ld new file mode 100644 index 00000000..cacf44e7 --- /dev/null +++ b/tutorials/stm32/nucleo-h723zg/minimal-bsd-baremetal/link.ld @@ -0,0 +1,32 @@ +ENTRY(Reset_Handler); +MEMORY { + flash(rx) : ORIGIN = 0x08000000, LENGTH = 1024k + + dtcmram (rwx) : ORIGIN = 0x20000000, LENGTH = 128K + ram_d1 (rwx) : ORIGIN = 0x24000000, LENGTH = 320K + ram_d2 (rwx) : ORIGIN = 0x30000000, LENGTH = 32K + ram_d3 (rwx) : ORIGIN = 0x38000000, LENGTH = 16K + itcmram (rwx) : ORIGIN = 0x00000000, LENGTH = 64K +} +_estack = ORIGIN(ram_d1) + LENGTH(ram_d1); + +SECTIONS { + .vectors : { KEEP(*(.isr_vector)) } > flash + .text : { *(.text* .text.*) } > flash + .rodata : { *(.rodata*) } > flash + + /* Note the .iram section, for functions copied to RAM. Required for MG_IRAM OTA support */ + .data : { _sdata = .; *(.first_data) *(.iram .iram* .iram.*) *(.data SORT(.data.*)) _edata = .; } > ram_d1 AT > flash + _sidata = LOADADDR(.data); + + .bss : { _sbss = .; *(.bss SORT(.bss.*) COMMON) _ebss = .; } > ram_d1 + + .eth_ram : { *(.eth_ram .eth_ram*) } > ram_d1 AT > flash + + /* Health record. NOLOAD and in unused DTCM, so startup code neither copies + nor zeroes it and the contents survive a warm reset */ + .mg_health (NOLOAD) : { KEEP(*(.mg_health .mg_health*)) } > dtcmram + + . = ALIGN(8); + _end = .; +} diff --git a/tutorials/stm32/nucleo-h723zg/minimal-bsd-baremetal/main.c b/tutorials/stm32/nucleo-h723zg/minimal-bsd-baremetal/main.c new file mode 100644 index 00000000..466726ef --- /dev/null +++ b/tutorials/stm32/nucleo-h723zg/minimal-bsd-baremetal/main.c @@ -0,0 +1,89 @@ +// Copyright (c) 2026 Cesanta Software Limited +// All rights reserved + +#include "hal.h" +#include "mongoose.h" + +int echo_init(uint16_t port); +void echo_poll(void); + +#ifndef UART_DEBUG +#define UART_DEBUG USART3 +#define UART_DEBUG_TX_PIN PIN('D', 8) +#define UART_DEBUG_RX_PIN PIN('D', 9) +#else +#define UART_DEBUG_TX_PIN PIN('A', 9) +#define UART_DEBUG_RX_PIN PIN('A', 10) +#endif + +#define LED1 PIN('B', 0) +#define LED2 PIN('E', 1) +#define LED3 PIN('B', 14) + +static void log_fn(char ch, void *param) { + hal_uart_write_buf(param, &ch, 1); +} + +static void blink_task(void) { + static uint64_t blink_timer = 0; + if (hal_timer_expired(&blink_timer, 500, hal_get_tick())) { + hal_gpio_toggle(LED2); + } +} + +uint64_t mg_millis(void) { + return hal_get_tick(); +} + +bool mg_random(void *buf, size_t len) { + for (size_t n = 0; n < len; n += sizeof(uint32_t)) { + uint32_t r = hal_rng_read(); + memcpy((char *) buf + n, &r, n + sizeof(r) > len ? len - n : sizeof(r)); + } + return true; +} + +static void http_ev_handler(struct mg_connection *c, int ev, void *ev_data) { + if (ev == MG_EV_HTTP_MSG) { + struct mg_http_message *hm = (struct mg_http_message *) ev_data; + if (mg_match(hm->uri, mg_str("/api/tick"), NULL)) { + mg_http_reply(c, 200, "", "{%m:%llu}\n", MG_ESC("tick"), hal_get_tick()); + } else { + mg_http_reply(c, 200, "", "Hi from Mongoose, tick %llu\n", + hal_get_tick()); + } + } +} + +int main(void) { + hal_clock_init(); + hal_uart_init(UART_DEBUG, UART_DEBUG_TX_PIN, UART_DEBUG_RX_PIN, 115200); + mg_log_set_fn(log_fn, UART_DEBUG); + hal_rng_init(); + hal_ethernet_init(); + hal_gpio_output(LED1); + hal_gpio_output(LED2); + hal_gpio_output(LED3); + + MG_INFO(("Initialised. CPU clock: %lu MHz", SystemCoreClock / 1000000)); + + // Mongoose event manager processes both Mongoose API and BSD sockets API + struct mg_mgr mgr; + mg_mgr_init(&mgr); + + // This is Mongoose-API HTTP server + mg_http_listen(&mgr, "http://0.0.0.0", http_ev_handler, NULL); + + // This is BSD sockets API tcp echo server that handles multiple concurrent + // connections. Note - baremetal implementation is non-blocking only. + echo_init(1234); + + // Baremetal superloop. Each function in the loop must not block + for (;;) { + mg_mgr_poll(&mgr, 0); + echo_poll(); + blink_task(); + } + + return 0; +} diff --git a/tutorials/stm32/nucleo-h723zg/minimal-bsd-baremetal/mongoose.c b/tutorials/stm32/nucleo-h723zg/minimal-bsd-baremetal/mongoose.c new file mode 120000 index 00000000..7a2752cb --- /dev/null +++ b/tutorials/stm32/nucleo-h723zg/minimal-bsd-baremetal/mongoose.c @@ -0,0 +1 @@ +../../../../mongoose.c \ No newline at end of file diff --git a/tutorials/stm32/nucleo-h723zg/minimal-bsd-baremetal/mongoose.h b/tutorials/stm32/nucleo-h723zg/minimal-bsd-baremetal/mongoose.h new file mode 120000 index 00000000..daff1633 --- /dev/null +++ b/tutorials/stm32/nucleo-h723zg/minimal-bsd-baremetal/mongoose.h @@ -0,0 +1 @@ +../../../../mongoose.h \ No newline at end of file diff --git a/tutorials/stm32/nucleo-h723zg/minimal-bsd-baremetal/mongoose_config.h b/tutorials/stm32/nucleo-h723zg/minimal-bsd-baremetal/mongoose_config.h new file mode 100644 index 00000000..a8672ed2 --- /dev/null +++ b/tutorials/stm32/nucleo-h723zg/minimal-bsd-baremetal/mongoose_config.h @@ -0,0 +1,37 @@ +#pragma once +#include "hal.h" + +// See https://mongoose.ws/docs/getting-started/build-options/ +#define MG_ARCH MG_ARCH_ARMGCC +#define MG_TLS MG_TLS_BUILTIN +#define MG_OTA MG_OTA_STM32H7 + +#define MG_ENABLE_TCPIP 1 +#define MG_ENABLE_BSD_SOCKETS 1 +#define MG_ENABLE_CUSTOM_MILLIS 1 +#define MG_ENABLE_CUSTOM_RANDOM 1 +#define MG_ENABLE_DRIVER_STM32H 1 +#define MG_ENABLE_PACKED_FS 1 +#define MG_ETH_RAM __attribute__((section(".eth_ram"))) + +// #define MG_DRIVER_MDC_CR 4 // RMII MDC clock divider, from 0 to 5 +// #define MG_TCPIP_PHY_ADDR 0 // PHY address + +// For static IP configuration, define MG_TCPIP_{IP,MASK,GW} +// By default, those are set to zero, meaning that DHCP is used +// +// #define MG_TCPIP_IP MG_IPV4(192, 168, 0, 10) // IP +// #define MG_TCPIP_GW MG_IPV4(192, 168, 0, 1) // Gateway +// #define MG_TCPIP_MASK MG_IPV4(255, 255, 255, 0) // Netmask + +// Construct MAC address from the MCU unique ID +#define MGUID ((uint32_t *) UID_BASE) +#define MG_SET_MAC_ADDRESS(mac) \ + do { \ + mac[0] = 2; \ + mac[1] = MGUID[0] & 255; \ + mac[2] = (MGUID[0] >> 10) & 255; \ + mac[3] = (MGUID[0] >> 19) & 255; \ + mac[4] = MGUID[1] & 255; \ + mac[5] = MGUID[2] & 255; \ + } while (0) diff --git a/tutorials/stm32/nucleo-h723zg/minimal-bsd/FreeRTOSConfig.h b/tutorials/stm32/nucleo-h723zg/minimal-bsd-freertos/FreeRTOSConfig.h similarity index 100% rename from tutorials/stm32/nucleo-h723zg/minimal-bsd/FreeRTOSConfig.h rename to tutorials/stm32/nucleo-h723zg/minimal-bsd-freertos/FreeRTOSConfig.h diff --git a/tutorials/stm32/nucleo-h723zg/minimal-bsd/Makefile b/tutorials/stm32/nucleo-h723zg/minimal-bsd-freertos/Makefile similarity index 68% rename from tutorials/stm32/nucleo-h723zg/minimal-bsd/Makefile rename to tutorials/stm32/nucleo-h723zg/minimal-bsd-freertos/Makefile index df1d01f2..fd08d47e 100644 --- a/tutorials/stm32/nucleo-h723zg/minimal-bsd/Makefile +++ b/tutorials/stm32/nucleo-h723zg/minimal-bsd-freertos/Makefile @@ -4,13 +4,13 @@ CFLAGS = -W -Wall -Wextra -Wundef -Wshadow -Wdouble-promotion CFLAGS += -Wformat-truncation -fno-common -Wconversion -Wno-sign-conversion CFLAGS += -g3 -Os -ffunction-sections -fdata-sections -CFLAGS += -I. -Icmsis_core/CMSIS/Core/Include -Icmsis_h7/Include -Imongoose +CFLAGS += -I. -Ibsd -Icmsis_core/CMSIS/Core/Include -Icmsis_h7/Include CFLAGS += -mcpu=cortex-m7 -mthumb -mfloat-abi=hard -mfpu=fpv5-d16 $(CFLAGS_EXTRA) LDFLAGS ?= -Tlink.ld -nostdlib -nostartfiles --specs nosys.specs -lc -lgcc -Wl,--gc-sections -Wl,-Map=$@.map -SOURCES = main.c hal.c +SOURCES = main.c hal.c echo.c SOURCES += cmsis_h7/Source/Templates/gcc/startup_stm32h723xx.s -SOURCES += mongoose/mongoose.c +SOURCES += mongoose.c # FreeRTOS. H7 has a Cortex-M7 r1p0 core, FreeRTOS recommends using CM4F port for non-r0p1 CM7 micros SOURCES += FreeRTOS-Kernel/tasks.c FreeRTOS-Kernel/list.c FreeRTOS-Kernel/queue.c @@ -21,10 +21,7 @@ CFLAGS += -IFreeRTOS-Kernel/portable/GCC/ARM_CM4F -Wno-conversion all build example: firmware.bin -mongoose/mongoose.c mongoose/mongoose.h: - cp ../../../../mongoose.[ch] mongoose/ - -firmware.elf: cmsis_core cmsis_h7 FreeRTOS-Kernel hal.h link.ld Makefile $(SOURCES) mongoose/mongoose.h +firmware.elf: cmsis_core cmsis_h7 FreeRTOS-Kernel hal.h link.ld Makefile $(SOURCES) mongoose.h mongoose_config.h arm-none-eabi-gcc $(SOURCES) $(CFLAGS) $(CFLAGS_EXTRA) $(LDFLAGS) -o $@ firmware.bin: firmware.elf @@ -45,15 +42,4 @@ FreeRTOS-Kernel: git clone --depth 1 -b V11.3.0 https://github.com/FreeRTOS/FreeRTOS-Kernel $@ clean: - rm -rf firmware.* cmsis_* mongoose/mongoose.* - - -# Automated remote test. Requires env variable VCON_API_KEY set. See https://vcon.io/automated-firmware-tests/ -DEVICE_URL ?= https://dash.vcon.io/api/v3/devices/10 -update: firmware.bin - curl --fail-with-body -su :$(VCON_API_KEY) $(DEVICE_URL)/ota --data-binary @$< - -test update: CFLAGS_EXTRA = -DUART_DEBUG=USART1 -test: update - curl --fail-with-body -su :$(VCON_API_KEY) $(DEVICE_URL)/tx?t=15 | tee /tmp/output.txt - grep 'READY, IP:' /tmp/output.txt # Check for network init + rm -rf firmware.* cmsis_* FreeRTOS-Kernel diff --git a/tutorials/stm32/nucleo-h723zg/minimal-bsd-freertos/bsd b/tutorials/stm32/nucleo-h723zg/minimal-bsd-freertos/bsd new file mode 120000 index 00000000..6079e81d --- /dev/null +++ b/tutorials/stm32/nucleo-h723zg/minimal-bsd-freertos/bsd @@ -0,0 +1 @@ +../../../../src/bsd \ No newline at end of file diff --git a/tutorials/stm32/nucleo-h723zg/minimal-bsd-freertos/echo.c b/tutorials/stm32/nucleo-h723zg/minimal-bsd-freertos/echo.c new file mode 100644 index 00000000..57a4af5a --- /dev/null +++ b/tutorials/stm32/nucleo-h723zg/minimal-bsd-freertos/echo.c @@ -0,0 +1,141 @@ +// Copyright (c) 2026 Cesanta Software Limited +// All rights reserved +// +// BSD socket tasks: TCP echo, UDP echo, and an HTTP client. + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#define PRIO (configMAX_PRIORITIES - 1) + +// Connect to mongoose.ws:80, fetch "/", log response length. +static void web_client_task(void *args) { + taskYIELD(); + struct hostent *h = gethostbyname("mongoose.ws"); + if (!h) { + printf("DNS failed\n"); + vTaskDelete(NULL); + return; + } + int fd = socket(AF_INET, SOCK_STREAM, 0); + struct sockaddr_in sa = {.sin_family = AF_INET, .sin_port = htons(80)}; + memcpy(&sa.sin_addr, h->h_addr, 4); + if (connect(fd, (struct sockaddr *) &sa, sizeof(sa)) < 0) { + printf("connect failed\n"); + close(fd); + vTaskDelete(NULL); + return; + } + const char *req = "GET / HTTP/1.0\r\nHost: mongoose.ws\r\n\r\n"; + send(fd, req, strlen(req), 0); + size_t total = 0; + char buf[512]; + ssize_t n; + while ((n = recv(fd, buf, sizeof(buf), 0)) > 0) total += (size_t) n; + printf("mongoose.ws response: %lu bytes\n", (unsigned long) total); + close(fd); + vTaskDelete(NULL); + (void) args; +} + +// Client connection: socket fd + linked-list pointer +struct peer { + int fd; + struct peer *next; +}; + +// TCP echo: multiplex clients with select() over a linked list of sockets +static void tcp_echo_task(void *args) { + int lfd = socket(AF_INET, SOCK_STREAM, 0); + struct sockaddr_in sa = { + .sin_family = AF_INET, + .sin_port = htons(1234), + .sin_addr = {.s_addr = INADDR_ANY}, + }; + struct peer *head = NULL; + bind(lfd, (struct sockaddr *) &sa, sizeof(sa)); + listen(lfd, 5); + printf("BSD TCP echo server on :1234\n"); + for (;;) { + fd_set rfds; + int maxfd = lfd; + struct peer *c, **link; + FD_ZERO(&rfds); + FD_SET(lfd, &rfds); + for (c = head; c != NULL; c = c->next) { + FD_SET(c->fd, &rfds); + if (c->fd > maxfd) maxfd = c->fd; + } + if (select(maxfd + 1, &rfds, NULL, NULL, NULL) <= 0) continue; + if (FD_ISSET(lfd, &rfds)) { + int fd = accept(lfd, NULL, NULL); + if (fd >= 0) { + struct peer *nc = (struct peer *) calloc(1, sizeof(*nc)); + if (nc != NULL) { + nc->fd = fd; + nc->next = head; + head = nc; + } else { + close(fd); + } + } + } + for (link = &head; (c = *link) != NULL;) { + if (FD_ISSET(c->fd, &rfds)) { + char buf[512]; + ssize_t n = recv(c->fd, buf, sizeof(buf), 0); + if (n > 0) { + send(c->fd, buf, (size_t) n, 0); + link = &c->next; + } else { + *link = c->next; // EOF or error: drop the client + close(c->fd); + free(c); + } + } else { + link = &c->next; + } + } + } + (void) args; +} + +// Echo UDP datagrams back to the sender +static void udp_echo_task(void *args) { + int fd = socket(AF_INET, SOCK_DGRAM, 0); + struct sockaddr_in sa = { + .sin_family = AF_INET, + .sin_port = htons(1235), + .sin_addr = {.s_addr = INADDR_ANY}, + }; + char buf[512]; + if (fd < 0 || bind(fd, (struct sockaddr *) &sa, sizeof(sa)) < 0) { + printf("udp bind failed\n"); + vTaskDelete(NULL); + return; + } + printf("BSD UDP echo server on :1235\n"); + for (;;) { + struct sockaddr_in peer; + socklen_t peerlen = sizeof(peer); + ssize_t n = recvfrom(fd, buf, sizeof(buf), 0, (struct sockaddr *) &peer, + &peerlen); + if (n > 0) sendto(fd, buf, (size_t) n, 0, (struct sockaddr *) &peer, peerlen); + } + (void) args; +} + +void echo_start(void) { + xTaskCreate(tcp_echo_task, "tcp_echo", 1024, NULL, PRIO, 0); + xTaskCreate(udp_echo_task, "udp_echo", 256, NULL, PRIO, 0); + xTaskCreate(web_client_task, "web_client", 256, NULL, PRIO, 0); +} diff --git a/tutorials/stm32/nucleo-h723zg/minimal-bsd-freertos/hal.c b/tutorials/stm32/nucleo-h723zg/minimal-bsd-freertos/hal.c new file mode 100644 index 00000000..e519bff3 --- /dev/null +++ b/tutorials/stm32/nucleo-h723zg/minimal-bsd-freertos/hal.c @@ -0,0 +1,125 @@ +// Copyright (c) 2026 Cesanta Software Limited +// All rights reserved + +#include "hal.h" + +bool hal_timer_expired(volatile uint64_t *t, uint64_t period, uint64_t now) { + uint64_t diff = now - *t; // Wrap-safe elapsed time since last expiry + if (period == 0) return false; // Avoid division by zero + if (diff < period) return false; // Period has not elapsed yet + *t += (diff / period) * period; // Preserve cadence, skip missed periods + return true; +} + +static volatile uint64_t s_ticks; // Milliseconds since boot +void SysTick_Handler(void) { // SyStick IRQ handler, triggered every 1ms + s_ticks++; +} + +uint64_t hal_get_tick(void) { + return s_ticks; +}; + +uint32_t SystemCoreClock = 160000000; +void SystemInit(void) { // Called automatically by startup code + hal_system_init(); // Enable FPU + hal_clock_init(); + SysTick_Config(SystemCoreClock / 1000); // Sys tick every 1ms +} + +void ExitRun0Mode(void) { +} + +struct stat; +__attribute__((weak)) int _fstat(int fd, struct stat *st) { + (void) fd, (void) st; + return -1; +} + +extern unsigned char _end[]; // End of data section, start of heap. See link.ld +static unsigned char *s_current_heap_end = _end; + +size_t hal_ram_used(void) { + return (size_t) (s_current_heap_end - _end); +} + +size_t hal_ram_free(void) { + unsigned char endofstack; + return (size_t) (&endofstack - s_current_heap_end); +} + +void *_sbrk(int incr) { + unsigned char *prev_heap; + unsigned char *heap_end = (unsigned char *) ((size_t) &heap_end - 256); + prev_heap = s_current_heap_end; + // Check how much space we got from the heap end to the stack end + if (s_current_heap_end + incr > heap_end) return (void *) -1; + s_current_heap_end += incr; + return prev_heap; +} + +__attribute__((weak)) int _open(const char *path) { + (void) path; + return -1; +} + +__attribute__((weak)) int _close(int fd) { + (void) fd; + return -1; +} + +__attribute__((weak)) int _isatty(int fd) { + (void) fd; + return 1; +} + +__attribute__((weak)) int _lseek(int fd, int ptr, int dir) { + (void) fd, (void) ptr, (void) dir; + return 0; +} + +__attribute__((weak)) void _exit(int status) { + (void) status; + for (;;) asm volatile("BKPT #0"); +} + +__attribute__((weak)) void _kill(int pid, int sig) { + (void) pid, (void) sig; +} + +__attribute__((weak)) int _getpid(void) { + return -1; +} + +__attribute__((weak)) int _write(int fd, char *ptr, int len) { + (void) fd, (void) ptr, (void) len; + return -1; +} + +__attribute__((weak)) int _read(int fd, char *ptr, int len) { + (void) fd, (void) ptr, (void) len; + return -1; +} + +__attribute__((weak)) int _link(const char *a, const char *b) { + (void) a, (void) b; + return -1; +} + +__attribute__((weak)) int _unlink(const char *a) { + (void) a; + return -1; +} + +__attribute__((weak)) int _stat(const char *path, struct stat *st) { + (void) path, (void) st; + return -1; +} + +__attribute__((weak)) int mkdir(const char *path, int mode) { + (void) path, (void) mode; + return -1; +} + +__attribute__((weak)) void _init(void) { +} diff --git a/tutorials/stm32/nucleo-h723zg/minimal-bsd/hal.h b/tutorials/stm32/nucleo-h723zg/minimal-bsd-freertos/hal.h similarity index 100% rename from tutorials/stm32/nucleo-h723zg/minimal-bsd/hal.h rename to tutorials/stm32/nucleo-h723zg/minimal-bsd-freertos/hal.h diff --git a/tutorials/stm32/nucleo-h723zg/minimal-bsd/link.ld b/tutorials/stm32/nucleo-h723zg/minimal-bsd-freertos/link.ld similarity index 100% rename from tutorials/stm32/nucleo-h723zg/minimal-bsd/link.ld rename to tutorials/stm32/nucleo-h723zg/minimal-bsd-freertos/link.ld diff --git a/tutorials/stm32/nucleo-h723zg/minimal-bsd-freertos/main.c b/tutorials/stm32/nucleo-h723zg/minimal-bsd-freertos/main.c new file mode 100644 index 00000000..d0796e70 --- /dev/null +++ b/tutorials/stm32/nucleo-h723zg/minimal-bsd-freertos/main.c @@ -0,0 +1,88 @@ +// Copyright (c) 2026 Cesanta Software Limited +// All rights reserved +// +// Four FreeRTOS tasks: network owner, TCP echo, UDP echo, and web client. + +#include "hal.h" +#include "mongoose.h" + +void echo_start(void); + +#ifndef UART_DEBUG +#define UART_DEBUG USART3 +#define UART_DEBUG_TX_PIN PIN('D', 8) +#define UART_DEBUG_RX_PIN PIN('D', 9) +#else +#define UART_DEBUG_TX_PIN PIN('A', 9) +#define UART_DEBUG_RX_PIN PIN('A', 10) +#endif + +#define LED1 PIN('B', 0) +#define LED2 PIN('E', 1) +#define LED3 PIN('B', 14) + +int _write(int fd, char *ptr, int len) { + if (fd == 1) hal_uart_write_buf(UART_DEBUG, ptr, (size_t) len); + return len; +} + +static void blink_task(void) { + static uint64_t t = 0; + if (hal_timer_expired(&t, 500, hal_get_tick())) hal_gpio_toggle(LED1); +} + +uint64_t mg_millis(void) { + return hal_get_tick(); +} + +bool mg_random(void *buf, size_t len) { + for (size_t n = 0; n < len; n += sizeof(uint32_t)) { + uint32_t r = hal_rng_read(); + memcpy((char *) buf + n, &r, n + sizeof(r) > len ? len - n : sizeof(r)); + } + return true; +} + +static void http_ev_handler(struct mg_connection *c, int ev, void *ev_data) { + if (ev == MG_EV_HTTP_MSG) { + struct mg_http_message *hm = (struct mg_http_message *) ev_data; + if (mg_match(hm->uri, mg_str("/api/tick"), NULL)) { + mg_http_reply(c, 200, "", "{%m:%llu}\n", MG_ESC("tick"), hal_get_tick()); + } else { + mg_http_reply(c, 200, "", "Hi from Mongoose, tick %llu\n", + hal_get_tick()); + } + } +} + +// Network owner. Runs the Mongoose event loop +static void net_core_task(void *arg) { + MG_INFO(("Stating Mongoose event loop...")); + for (;;) { + mg_mgr_poll((struct mg_mgr *) arg, 0); + blink_task(); + } +} + +#define PRIO (configMAX_PRIORITIES - 1) + +int main(void) { + hal_clock_init(); + hal_uart_init(UART_DEBUG, UART_DEBUG_TX_PIN, UART_DEBUG_RX_PIN, 115200); + hal_rng_init(); + hal_ethernet_init(); + hal_gpio_output(LED1); + hal_gpio_output(LED2); + hal_gpio_output(LED3); + MG_INFO(("CPU clock: %lu MHz", SystemCoreClock / 1000000)); + + static struct mg_mgr mgr; + mg_mgr_init(&mgr); + mg_http_listen(&mgr, "http://0.0.0.0:80", http_ev_handler, NULL); + mg_http_listen(&mgr, "udp://0.0.0.0:1236", NULL, NULL); + + xTaskCreate(net_core_task, "net_core", 2048, &mgr, PRIO, 0); + echo_start(); + vTaskStartScheduler(); + return 0; +} diff --git a/tutorials/stm32/nucleo-h723zg/minimal-bsd-freertos/mongoose.c b/tutorials/stm32/nucleo-h723zg/minimal-bsd-freertos/mongoose.c new file mode 120000 index 00000000..7a2752cb --- /dev/null +++ b/tutorials/stm32/nucleo-h723zg/minimal-bsd-freertos/mongoose.c @@ -0,0 +1 @@ +../../../../mongoose.c \ No newline at end of file diff --git a/tutorials/stm32/nucleo-h723zg/minimal-bsd-freertos/mongoose.h b/tutorials/stm32/nucleo-h723zg/minimal-bsd-freertos/mongoose.h new file mode 120000 index 00000000..daff1633 --- /dev/null +++ b/tutorials/stm32/nucleo-h723zg/minimal-bsd-freertos/mongoose.h @@ -0,0 +1 @@ +../../../../mongoose.h \ No newline at end of file diff --git a/tutorials/stm32/nucleo-h723zg/minimal-bsd/mongoose/mongoose_config.h b/tutorials/stm32/nucleo-h723zg/minimal-bsd-freertos/mongoose_config.h similarity index 97% rename from tutorials/stm32/nucleo-h723zg/minimal-bsd/mongoose/mongoose_config.h rename to tutorials/stm32/nucleo-h723zg/minimal-bsd-freertos/mongoose_config.h index 91896432..8453f885 100644 --- a/tutorials/stm32/nucleo-h723zg/minimal-bsd/mongoose/mongoose_config.h +++ b/tutorials/stm32/nucleo-h723zg/minimal-bsd-freertos/mongoose_config.h @@ -11,7 +11,6 @@ #define MG_ENABLE_CUSTOM_MILLIS 1 #define MG_ENABLE_CUSTOM_RANDOM 1 #define MG_ENABLE_DRIVER_STM32H 1 -#define MG_ENABLE_PACKED_FS 1 #define MG_ETH_RAM __attribute__((section(".eth_ram"))) // #define MG_DRIVER_MDC_CR 4 // RMII MDC clock divider, from 0 to 5 diff --git a/tutorials/stm32/nucleo-h723zg/minimal-bsd/main.c b/tutorials/stm32/nucleo-h723zg/minimal-bsd/main.c deleted file mode 100644 index 3d834258..00000000 --- a/tutorials/stm32/nucleo-h723zg/minimal-bsd/main.c +++ /dev/null @@ -1,142 +0,0 @@ -// Copyright (c) 2026 Cesanta Software Limited -// All rights reserved -// -// Two FreeRTOS tasks sharing one Mongoose event manager via the BSD socket -// shim (MG_ENABLE_BSD_SOCKETS). task1 owns the manager and drives the -// network; task2 accepts connections and spawns per-connection echo_tasks -// that speak standard BSD sockets. - -#include "hal.h" -#include "mongoose.h" - -#ifndef UART_DEBUG -#define UART_DEBUG USART3 -#define UART_DEBUG_TX_PIN PIN('D', 8) -#define UART_DEBUG_RX_PIN PIN('D', 9) -#else -#define UART_DEBUG_TX_PIN PIN('A', 9) -#define UART_DEBUG_RX_PIN PIN('A', 10) -#endif - -#define LED1 PIN('B', 0) -#define LED2 PIN('E', 1) -#define LED3 PIN('B', 14) - -int _write(int fd, char *ptr, int len) { - if (fd == 1) hal_uart_write_buf(UART_DEBUG, ptr, (size_t) len); - return len; -} - -static void blink_task(void) { - static uint64_t t = 0; - if (hal_timer_expired(&t, 500, hal_get_tick())) hal_gpio_toggle(LED1); -} - -uint64_t mg_millis(void) { return hal_get_tick(); } - -bool mg_random(void *buf, size_t len) { - for (size_t n = 0; n < len; n += sizeof(uint32_t)) { - uint32_t r = hal_rng_read(); - memcpy((char *) buf + n, &r, n + sizeof(r) > len ? len - n : sizeof(r)); - } - return true; -} - -static void http_ev_handler(struct mg_connection *c, int ev, void *ev_data) { - if (ev == MG_EV_HTTP_MSG) { - struct mg_http_message *hm = (struct mg_http_message *) ev_data; - if (mg_match(hm->uri, mg_str("/api/tick"), NULL)) { - mg_http_reply(c, 200, "", "{%m:%llu}\n", MG_ESC("tick"), hal_get_tick()); - } else { - mg_http_reply(c, 200, "", "Hi from Mongoose, tick %llu\n", hal_get_tick()); - } - } -} - -// task1: network owner. Runs the Mongoose event loop (mg_bsd_poll drives both -// the TCP/IP stack and the BSD command queue) and blinks the LED. -static void task1(void *args) { - static struct mg_mgr mgr; - mg_mgr_init(&mgr); - mg_http_listen(&mgr, "http://0.0.0.0", http_ev_handler, NULL); - for (;;) { - mg_mgr_poll(&mgr, 0); - mg_bsd_poll(&mgr); - blink_task(); - } - (void) args; -} - -// client_task: connects to mongoose.ws:80, fetches "/", logs response length. -// Spawned when the echo server receives a message starting with "42". -static void client_task(void *args) { - struct hostent *h = gethostbyname("mongoose.ws"); - if (!h) { MG_ERROR(("DNS failed")); vTaskDelete(NULL); return; } - int fd = socket(AF_INET, SOCK_STREAM, 0); - struct sockaddr_in sa = {.sin_family = AF_INET, .sin_port = htons(80)}; - memcpy(&sa.sin_addr, h->h_addr, 4); - if (connect(fd, (struct sockaddr *) &sa, sizeof(sa)) < 0) { - MG_ERROR(("connect failed")); close(fd); vTaskDelete(NULL); return; - } - const char *req = "GET / HTTP/1.0\r\nHost: mongoose.ws\r\n\r\n"; - send(fd, req, strlen(req), 0); - size_t total = 0; - char buf[512]; - ssize_t n; - while ((n = recv(fd, buf, sizeof(buf), 0)) > 0) total += (size_t) n; - MG_INFO(("mongoose.ws response: %lu bytes", (unsigned long) total)); - close(fd); - vTaskDelete(NULL); - (void) args; -} - -// echo_task: one per accepted connection. Echoes data back. -// If a message starts with "42", also spawns a client_task to fetch mongoose.ws. -static void echo_task(void *args) { - int fd = (int) (uintptr_t) args; - char buf[512]; - ssize_t n; - while ((n = recv(fd, buf, sizeof(buf), 0)) > 0) { - if (n >= 2 && buf[0] == '4' && buf[1] == '2') - xTaskCreate(client_task, "client", 1024, NULL, configMAX_PRIORITIES - 1, NULL); - send(fd, buf, (size_t) n, 0); - } - close(fd); - vTaskDelete(NULL); -} - -// task2: accept loop. Waits for incoming connections on port 1234 and spawns -// an echo_task for each one, allowing concurrent clients. -static void task2(void *args) { - mg_bsd_init(); - int lfd = socket(AF_INET, SOCK_STREAM, 0); - struct sockaddr_in sa = {.sin_family = AF_INET, .sin_port = htons(1234), .sin_addr = {INADDR_ANY}}; - bind(lfd, (struct sockaddr *) &sa, sizeof(sa)); - listen(lfd, 5); - MG_INFO(("BSD echo server on :1234")); - for (;;) { - int fd = accept(lfd, NULL, NULL); - if (fd < 0) continue; - xTaskCreate(echo_task, "echo", 512, (void *) (uintptr_t) fd, - configMAX_PRIORITIES - 1, NULL); - } - (void) args; -} - -int main(void) { - hal_clock_init(); - hal_uart_init(UART_DEBUG, UART_DEBUG_TX_PIN, UART_DEBUG_RX_PIN, 115200); - hal_rng_init(); - hal_ethernet_init(); - hal_gpio_output(LED1); - hal_gpio_output(LED2); - hal_gpio_output(LED3); - MG_INFO(("CPU clock: %lu MHz", SystemCoreClock / 1000000)); - - // task2 at higher priority than task1 so that when task1 pushes data into - // recv_q, task2 preempts immediately, echoes, and yields before MG_EV_CLOSE. - xTaskCreate(task1, "task1", 2048, NULL, configMAX_PRIORITIES - 2, NULL); - xTaskCreate(task2, "task2", 256, NULL, configMAX_PRIORITIES - 1, NULL); - vTaskStartScheduler(); - return 0; -}