mirror of
https://github.com/cesanta/mongoose.git
synced 2026-10-25 12:04:47 +00:00
965 lines
30 KiB
C
965 lines
30 KiB
C
#include "bsd.h"
|
|
#include "dns.h"
|
|
#include <stdarg.h>
|
|
|
|
#if MG_ENABLE_BSD_SOCKETS && MG_ENABLE_FREERTOS
|
|
#include <queue.h>
|
|
|
|
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
|
|
|
|
#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
|
|
#endif
|
|
|
|
// 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,
|
|
};
|
|
|
|
// 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 {
|
|
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 struct mg_bsd_sock s_socks[MG_BSD_MAX_SOCKETS > 0 ?
|
|
MG_BSD_MAX_SOCKETS : 1];
|
|
|
|
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 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 c;
|
|
}
|
|
|
|
#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;
|
|
|
|
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);
|
|
}
|
|
}
|
|
#endif
|
|
|
|
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) {
|
|
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
|
|
}
|
|
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;
|
|
}
|
|
} 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) {
|
|
#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;
|
|
}
|
|
|
|
#if MG_ENABLE_FREERTOS
|
|
#ifndef MG_WAKEUP_QUEUE_DEPTH
|
|
#define MG_WAKEUP_QUEUE_DEPTH 4
|
|
#endif
|
|
|
|
struct wumsg {
|
|
unsigned long id;
|
|
size_t len;
|
|
uint8_t data[];
|
|
};
|
|
|
|
static void wufn(struct mg_connection *c, int ev, void *ev_data) {
|
|
QueueHandle_t q = (QueueHandle_t) c->mgr->pipe.q;
|
|
if (ev == MG_EV_POLL) {
|
|
struct wumsg *m;
|
|
if (xQueueReceive(q, &m, 0) == pdTRUE) {
|
|
struct mg_connection *t;
|
|
for (t = c->mgr->conns; t != NULL; t = t->next) {
|
|
if (t->id == m->id) {
|
|
struct mg_str data = mg_str_n((char *) m->data, m->len);
|
|
mg_call(t, MG_EV_WAKEUP, &data);
|
|
break;
|
|
}
|
|
}
|
|
free(m);
|
|
}
|
|
} else if (ev == MG_EV_CLOSE) {
|
|
struct wumsg *m;
|
|
while (xQueueReceive(q, &m, 0) == pdTRUE) free(m);
|
|
vQueueDelete(q);
|
|
c->mgr->pipe.q = NULL;
|
|
}
|
|
(void) ev_data;
|
|
}
|
|
|
|
bool mg_wakeup_init(struct mg_mgr *mgr) {
|
|
struct mg_connection *c;
|
|
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;
|
|
}
|
|
c = mg_alloc_conn(mgr);
|
|
if (c == NULL) {
|
|
vQueueDelete((QueueHandle_t) mgr->pipe.q);
|
|
mgr->pipe.q = NULL;
|
|
return false;
|
|
}
|
|
c->fd = (void *) (size_t) MG_INVALID_SOCKET;
|
|
c->fn = wufn;
|
|
LIST_ADD_HEAD(struct mg_connection, &mgr->conns, c);
|
|
MG_DEBUG(("%lu queue %p", c->id, mgr->pipe.q));
|
|
mg_call(c, MG_EV_OPEN, NULL);
|
|
return true;
|
|
}
|
|
|
|
bool mg_wakeup(struct mg_mgr *mgr, unsigned long conn_id, const void *buf,
|
|
size_t len) {
|
|
struct wumsg *m;
|
|
if (mgr->pipe.q == NULL || conn_id == 0) return false;
|
|
m = (struct wumsg *) calloc(1, sizeof(*m) + len);
|
|
if (m == NULL) {
|
|
MG_ERROR(("OOM"));
|
|
return false;
|
|
}
|
|
m->id = conn_id;
|
|
m->len = len;
|
|
memcpy(m->data, buf, len);
|
|
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 defined(MG_BSD_SELECT_H)
|
|
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 defined(MG_BSD_SELECT_H)
|
|
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_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
|