/********************************************************************* PicoTCP. Copyright (c) 2013 TASS Belgium NV. Some rights reserved. See LICENSE and COPYING for usage. Do not redistribute without a written permission by the Copyright holders. Author: Maxime Vincent, Daniele Lacamera *********************************************************************/ #include "pico_defines.h" #include "pico_config.h" /* for zalloc and free */ #include "pico_bsd_sockets.h" #include "pico_osal.h" #ifdef PICO_SUPPORT_SNTP_CLIENT #include "pico_sntp_client.h" #endif #include /* should be there in C99 */ #define SOCK_OPEN 0 #define SOCK_BOUND 1 #define SOCK_LISTEN 2 #define SOCK_CONNECTED 3 #define SOCK_ERROR 4 #define SOCK_RESET_BY_PEER 5 #define SOCK_CLOSED 100 #define bsd_dbg(...) do {} while(0) #define bsd_dbg_select(...) do {} while(0) /* Global signal sent on any event (for select) */ void * picoLock = NULL; /* pico stack lock */ void * pico_signal_select = NULL; /* pico global signal for select */ void * pico_signal_tick = NULL; /* pico tick signal, e.g. coming from a driver ISR */ struct pico_bsd_endpoint { struct pico_socket *s; int socket_fd; int posix_fd; /* TODO: ifdef... */ uint8_t in_use; uint8_t state; /* for pico_state */ uint8_t nonblocking; /* The non-blocking flag, for non-blocking socket operations */ uint16_t events; /* events that we filter for */ uint16_t revents; /* received events */ uint16_t proto; void * mutex_lock; /* mutex for clearing revents */ void * signal; /* signals new events */ uint32_t timeout; /* this is used for timeout sockets */ int error; /* used for SO_ERROR sockopt after connect() */ }; /* MACRO's */ #define VALIDATE_NULL(param) \ if(!param) \ { \ return -1; \ } #define VALIDATE_ONE(param,value) \ if(param != value) { \ pico_err = PICO_ERR_EINVAL; \ errno = pico_err; \ return -1; \ } #define VALIDATE_TWO(param,value1,value2) \ if(param != value1 && param != value2) { \ pico_err = PICO_ERR_EINVAL; \ errno = pico_err; \ return -1; \ } /* Private function prototypes */ static void pico_event_clear(struct pico_bsd_endpoint *ep, uint16_t events); static uint16_t pico_bsd_wait(struct pico_bsd_endpoint * ep, int read, int write, int close); static void pico_socket_event(uint16_t ev, struct pico_socket *s); /************************/ /* Public API functions */ /************************/ void pico_bsd_init(void) { pico_signal_select = pico_signal_init(); pico_signal_tick = pico_signal_init(); picoLock = pico_mutex_init(); } void pico_bsd_deinit(void) { pico_mutex_deinit(picoLock); } /* just ticks the stack twice */ void pico_bsd_stack_tick(void) { pico_mutex_lock(picoLock); pico_stack_tick(); pico_stack_tick(); pico_mutex_unlock(picoLock); } /* ticks the stack, but wait for a signal with a timeout (e.g. from the driver interrupt) */ void pico_bsd_stack_tick_timeout(int timeout_ms) { pico_signal_wait_timeout(pico_signal_tick, timeout_ms); pico_bsd_stack_tick(); } /** Declarations of helper functions **/ static struct pico_bsd_endpoint *pico_bsd_create_socket(void); static int get_free_sd(struct pico_bsd_endpoint *ep); static int new_sd(struct pico_bsd_endpoint *ep); static void free_up_ep(struct pico_bsd_endpoint *ep); static struct pico_bsd_endpoint *get_endpoint(int sd, int set_err); static int bsd_to_pico_addr(union pico_address *addr, struct sockaddr *_saddr, socklen_t socklen); static uint16_t bsd_to_pico_port(struct sockaddr *_saddr, socklen_t socklen); static int pico_addr_to_bsd(struct sockaddr *_saddr, socklen_t socklen, union pico_address *addr, uint16_t net); static int pico_port_to_bsd(struct sockaddr *_saddr, socklen_t socklen, uint16_t port); /** Global Sockets descriptors array **/ static struct pico_bsd_endpoint **PicoSockets = NULL; static int PicoSocket_max = 0; /*** Public socket functions ***/ /* Socket interface. */ int pico_newsocket(int domain, int type, int proto) { struct pico_bsd_endpoint * ep = NULL; (void)proto; #ifdef PICO_SUPPORT_IPV6 VALIDATE_TWO(domain,AF_INET, AF_INET6); #else VALIDATE_ONE(domain, AF_INET); #endif VALIDATE_TWO(type,SOCK_STREAM,SOCK_DGRAM); if (AF_INET6 != PICO_PROTO_IPV6) { if (domain == AF_INET6) domain = PICO_PROTO_IPV6; else domain = PICO_PROTO_IPV4; } if (SOCK_STREAM != PICO_PROTO_TCP) { if (type == SOCK_STREAM) type = PICO_PROTO_TCP; else type = PICO_PROTO_UDP; } pico_mutex_lock(picoLock); ep = pico_bsd_create_socket(); VALIDATE_NULL(ep); ep->error = PICO_ERR_NOERR; ep->proto = type; ep->s = pico_socket_open(domain, type,&pico_socket_event); if (!ep->s) { PICO_FREE(ep); pico_mutex_unlock(picoLock); return -1; } ep->s->priv = ep; /* let priv point to the endpoint struct */ /* open picotcp endpoint */ ep->state = SOCK_OPEN; ep->mutex_lock = pico_mutex_init(); ep->signal = pico_signal_init(); ep->error = pico_err; pico_mutex_unlock(picoLock); return ep->socket_fd; } int pico_bind(int sd, struct sockaddr * local_addr, socklen_t socklen) { union pico_address addr = { .ip4 = { 0 } }; uint16_t port; struct pico_bsd_endpoint *ep = get_endpoint(sd, 1); VALIDATE_NULL(ep); ep->error = PICO_ERR_NOERR; VALIDATE_NULL(local_addr); VALIDATE_TWO(socklen, SOCKSIZE, SOCKSIZE6); if (bsd_to_pico_addr(&addr, local_addr, socklen) < 0) { ep->error = PICO_ERR_EINVAL; errno = pico_err; return -1; } port = bsd_to_pico_port(local_addr, socklen); pico_mutex_lock(picoLock); if(pico_socket_bind(ep->s, &addr, &port) < 0) { ep->error = pico_err; errno = pico_err; pico_mutex_unlock(picoLock); return -1; } ep->state = SOCK_BOUND; pico_mutex_unlock(picoLock); return 0; } int pico_getsockname(int sd, struct sockaddr * local_addr, socklen_t *socklen) { union pico_address addr; uint16_t port, proto; struct pico_bsd_endpoint *ep = get_endpoint(sd, 1); VALIDATE_NULL(ep); ep->error = PICO_ERR_NOERR; VALIDATE_NULL(local_addr); VALIDATE_NULL(socklen); pico_mutex_lock(picoLock); if(pico_socket_getname(ep->s, &addr, &port, &proto) < 0) { ep->error = pico_err; errno = pico_err; pico_mutex_unlock(picoLock); return -1; } if (proto == PICO_PROTO_IPV6) *socklen = SOCKSIZE6; else *socklen = SOCKSIZE; if (pico_addr_to_bsd(local_addr, *socklen, &addr, proto) < 0) { ep->error = pico_err; errno = pico_err; pico_mutex_unlock(picoLock); return -1; } pico_mutex_unlock(picoLock); pico_port_to_bsd(local_addr, *socklen, port); ep->error = pico_err; return 0; } int pico_getpeername(int sd, struct sockaddr * remote_addr, socklen_t *socklen) { union pico_address addr; uint16_t port, proto; struct pico_bsd_endpoint *ep = get_endpoint(sd, 1); VALIDATE_NULL(ep); ep->error = PICO_ERR_NOERR; VALIDATE_NULL(remote_addr); VALIDATE_NULL(socklen); pico_mutex_lock(picoLock); if(pico_socket_getpeername(ep->s, &addr, &port, &proto) < 0) { pico_mutex_unlock(picoLock); return -1; } if (proto == PICO_PROTO_IPV6) *socklen = SOCKSIZE6; else *socklen = SOCKSIZE; if (pico_addr_to_bsd(remote_addr, *socklen, &addr, proto) < 0) { pico_mutex_unlock(picoLock); return -1; } pico_mutex_unlock(picoLock); pico_port_to_bsd(remote_addr, *socklen, port); return 0; } int pico_listen(int sd, int backlog) { struct pico_bsd_endpoint *ep = get_endpoint(sd, 1); VALIDATE_NULL(ep); ep->error = PICO_ERR_NOERR; VALIDATE_NULL(ep->s); VALIDATE_ONE(ep->state, SOCK_BOUND); pico_mutex_lock(picoLock); if(pico_socket_listen(ep->s, backlog) < 0) { ep->error = pico_err; errno = pico_err; pico_mutex_unlock(picoLock); return -1; } ep->state = SOCK_LISTEN; ep->error = pico_err; pico_mutex_unlock(picoLock); return 0; } int pico_connect(int sd, struct sockaddr *_saddr, socklen_t socklen) { struct pico_bsd_endpoint *ep = get_endpoint(sd, 1); union pico_address addr; uint16_t port; uint16_t ev = 0; int ret; VALIDATE_NULL(ep); ep->error = PICO_ERR_NOERR; VALIDATE_NULL(_saddr); if (bsd_to_pico_addr(&addr, _saddr, socklen) < 0) { ep->error = PICO_ERR_EINVAL; errno = pico_err; return -1; } port = bsd_to_pico_port(_saddr, socklen); pico_mutex_lock(picoLock); ret = pico_socket_connect(ep->s, &addr, port); pico_mutex_unlock(picoLock); if (ret < 0) { ep->error = pico_err; return -1; } if (ep->nonblocking) { pico_err = PICO_ERR_EAGAIN; ep->error = pico_err; } else { /* wait for event */ ev = pico_bsd_wait(ep, 0, 0, 0); /* wait for ERR, FIN and CONN */ } if(ev & PICO_SOCK_EV_CONN) { /* clear the EV_CONN event */ pico_event_clear(ep, PICO_SOCK_EV_CONN); ep->error = pico_err; return 0; } else { if (!(ep->nonblocking)) pico_socket_close(ep->s); } ep->error = pico_err; errno = pico_err; return -1; } int pico_isconnected(int sd) { struct pico_bsd_endpoint *ep = NULL; int state = 0; ep = get_endpoint(sd, 1); VALIDATE_NULL(ep); ep->error = PICO_ERR_NOERR; pico_mutex_lock(picoLock); if(ep->state == SOCK_CONNECTED) { state = 1; } pico_mutex_unlock(picoLock); return state; } int pico_accept(int sd, struct sockaddr *_orig, socklen_t *socklen) { struct pico_bsd_endpoint *ep, * client_ep = NULL; uint16_t events; union pico_address picoaddr; uint16_t port; ep = get_endpoint(sd, 1); VALIDATE_NULL(ep); ep->error = PICO_ERR_NOERR; VALIDATE_ONE(ep->state, SOCK_LISTEN); if (ep->nonblocking) events = PICO_SOCK_EV_CONN; else events = pico_bsd_wait(ep, 0, 0, 0); /* Wait for CONN, FIN and ERR */ if(events & PICO_SOCK_EV_CONN) { struct pico_socket *s; pico_mutex_lock(picoLock); s = pico_socket_accept(ep->s,&picoaddr,&port); if (!s) { ep->error = pico_err; errno = pico_err; pico_mutex_unlock(picoLock); return -1; } /* Create a new client EP, only after the accept returned succesfully */ client_ep = pico_bsd_create_socket(); if (!client_ep) { ep->error = pico_err; errno = pico_err; pico_mutex_unlock(picoLock); return -1; } client_ep->s = s; client_ep->state = SOCK_OPEN; client_ep->mutex_lock = pico_mutex_init(); client_ep->signal = pico_signal_init(); client_ep->s->priv = client_ep; pico_event_clear(ep, PICO_SOCK_EV_CONN); /* clear the CONN event the listening socket */ if (client_ep->s->net->proto_number == PICO_PROTO_IPV4) *socklen = SOCKSIZE; else *socklen = SOCKSIZE6; client_ep->state = SOCK_CONNECTED; if (pico_addr_to_bsd(_orig, *socklen, &picoaddr, client_ep->s->net->proto_number) < 0) { client_ep->in_use = 0; pico_mutex_unlock(picoLock); return -1; } pico_port_to_bsd(_orig, *socklen, port); client_ep->in_use = 1; pico_mutex_unlock(picoLock); ep->error = pico_err; return client_ep->socket_fd; } client_ep->in_use = 0; ep->error = pico_err; errno = pico_err; return -1; } int pico_sendto(int sd, void * buf, int len, int flags, struct sockaddr *_dst, socklen_t socklen) { int retval = 0; int tot_len = 0; uint16_t port; union pico_address picoaddr; struct pico_bsd_endpoint *ep = get_endpoint(sd, 1); VALIDATE_NULL(ep); ep->error = PICO_ERR_NOERR; if (!buf || (len <= 0)) { pico_err = PICO_ERR_EINVAL; errno = pico_err; ep->error = pico_err; return -1; } while (tot_len < len) { /* Write to the pico socket */ pico_mutex_lock(picoLock); if (_dst == NULL) { retval = pico_socket_send(ep->s, ((uint8_t *)buf) + tot_len, len - tot_len); } else { if (bsd_to_pico_addr(&picoaddr, _dst, socklen) < 0) { ep->error = PICO_ERR_EINVAL; errno = pico_err; pico_mutex_unlock(picoLock); return -1; } port = bsd_to_pico_port(_dst, socklen); retval = pico_socket_sendto(ep->s, ((uint8_t *)buf) + tot_len, len - tot_len, &picoaddr, port); } pico_event_clear(ep, PICO_SOCK_EV_WR); pico_mutex_unlock(picoLock); /* If sending failed, return an error */ if (retval < 0) { ep->error = pico_err; errno = pico_err; pico_event_clear(ep, PICO_SOCK_EV_WR); return -1; } if (retval > 0) { tot_len += retval; break; } if (ep->nonblocking) break; /* If sent bytes (retval) < len-tot_len: socket full, we need to wait for a new WR event */ if (retval < (len - tot_len)) { uint16_t ev = 0; /* wait for a new WR or CLOSE event */ ev = pico_bsd_wait(ep, 0, 1, 1); if (ev & (PICO_SOCK_EV_ERR | PICO_SOCK_EV_FIN | PICO_SOCK_EV_CLOSE)) { ep->error = pico_err; errno = pico_err; pico_event_clear(ep, PICO_SOCK_EV_WR); /* closing and freeing the socket is done in the event handler */ return -1; } } tot_len += retval; } ep->error = pico_err; return tot_len; } int pico_fcntl(int sd, int cmd, int arg) { struct pico_bsd_endpoint *ep = get_endpoint(sd, 1); if (!ep) { pico_err = PICO_ERR_EINVAL; errno = pico_err; return -1; } if (cmd == F_SETFL) { if ((arg & O_NONBLOCK) != 0) { ep->nonblocking = 1; } else { ep->nonblocking = 0; } ep->error = PICO_ERR_NOERR; return 0; } if (cmd == F_GETFL) { (void)arg; /* F_GETFL: arg is ignored */ ep->error = PICO_ERR_NOERR; if (ep->nonblocking) return O_NONBLOCK; else return 0; } if (cmd == F_SETFD) { (void)arg; ep->error = PICO_ERR_NOERR; return 0; } pico_err = PICO_ERR_EINVAL; errno = pico_err; ep->error = pico_err; return -1; } /* * RETURN VALUE * Upon successful completion, recv_from() shall return the length of the * message in bytes. If no messages are available to be received and the * peer has performed an orderly shutdown, recv() shall return 0. Otherwise, * −1 shall be returned and errno set to indicate the error. */ int pico_recvfrom(int sd, void * _buf, int len, int flags, struct sockaddr *_addr, socklen_t *socklen) { int retval = 0; int tot_len = 0; struct pico_bsd_endpoint *ep = get_endpoint(sd, 1); union pico_address picoaddr; uint16_t port; unsigned char *buf = (unsigned char *)_buf; bsd_dbg("Recvfrom called \n"); VALIDATE_NULL(ep); ep->error = PICO_ERR_NOERR; if (ep->state == SOCK_RESET_BY_PEER) { /* not much to do here. Peer has nothing to say. */ return 0; } if (!buf || (len <= 0)) { pico_err = PICO_ERR_EINVAL; errno = pico_err; ep->error = pico_err; return -1; } while (tot_len < len) { pico_mutex_lock(picoLock); retval = pico_socket_recvfrom(ep->s, buf + tot_len , len - tot_len, &picoaddr, &port); pico_mutex_unlock(picoLock); bsd_dbg("pico_socket_recvfrom returns %d, first bytes are %c-%c-%c-%c\n", retval, buf[0], buf[1], buf[2], buf[3]); /* pico_socket_recvfrom failed */ if (retval < 0) { /* data was received */ if (tot_len > 0) { bsd_dbg("Recvfrom returning %d\n", tot_len); ep->error = pico_err; return tot_len; } /* no data was received yet */ ep->error = pico_err; if (pico_err == PICO_ERR_ESHUTDOWN) /* If no messages are available to be received and the peer has performed an orderly shutdown, recvfrom() shall return 0. */ { return 0; } else /* Otherwise, the function shall return −1 and set errno to indicate the error. */ { return -1; } } /* If received 0 bytes, return -1 or amount of bytes received */ if (retval == 0) { pico_event_clear(ep, PICO_SOCK_EV_RD); if (tot_len > 0) { bsd_dbg("Recvfrom returning %d\n", tot_len); ep->error = pico_err; return tot_len; } } if (retval > 0) { if (ep->proto == PICO_PROTO_UDP) { if (_addr && socklen > 0) { if (pico_addr_to_bsd(_addr, *socklen, &picoaddr, ep->s->net->proto_number) < 0) { pico_err = PICO_ERR_EINVAL; errno = pico_err; ep->error = pico_err; return -1; } pico_port_to_bsd(_addr, *socklen, port); } /* If in a recvfrom call, for UDP we should return immediately after the first dgram */ ep->error = pico_err; return retval + tot_len; } else { /* TCP: continue until recvfrom = 0, socket buffer empty */ tot_len += retval; continue; } } if (ep->nonblocking) { if (retval == 0) { pico_err = PICO_ERR_EAGAIN; /* or EWOULDBLOCK */ errno = pico_err; retval = -1; /* BSD-speak: -1 == 0 bytes received */ } break; } /* If recv bytes (retval) < len-tot_len: socket empty, we need to wait for a new RD event */ if (retval < (len - tot_len)) { uint16_t ev = 0; /* wait for a new RD event -- also wait for CLOSE event */ ev = pico_bsd_wait(ep, 1, 0, 1); if (ev & (PICO_SOCK_EV_ERR | PICO_SOCK_EV_FIN | PICO_SOCK_EV_CLOSE)) { /* closing and freeing the socket is done in the event handler */ pico_event_clear(ep, PICO_SOCK_EV_RD); ep->error = pico_err; return 0; /* return 0 on a properly closed socket */ } } tot_len += retval; } pico_event_clear(ep, PICO_SOCK_EV_RD); // What if still space available and we clear it here?? bsd_dbg("Recvfrom returning %d (full block)\n", tot_len); ep->error = pico_err; if (tot_len == 0) { errno = pico_err; tot_len = -1; /* BSD-speak: -1 == 0 bytes received */ } return tot_len; } int pico_write(int sd, void * buf, int len) { return pico_sendto(sd, buf, len, 0, NULL, 0); } int pico_send(int sd, void * buf, int len, int flags) { return pico_sendto(sd, buf, len, flags, NULL, 0); } int pico_read(int sd, void * buf, int len) { return pico_recvfrom(sd, buf, len, 0, NULL, 0); } int pico_recv(int sd, void * buf, int len, int flags) { return pico_recvfrom(sd, buf, len, flags, NULL, 0); } int pico_close(int sd) { struct pico_bsd_endpoint *ep = get_endpoint(sd, 1); VALIDATE_NULL(ep); ep->error = PICO_ERR_NOERR; if (ep->s && ep->in_use) /* valid socket, try to close it */ { pico_mutex_lock(picoLock); pico_socket_close(ep->s); ep->s->priv = NULL; pico_mutex_unlock(picoLock); } ep->in_use = 0; ep->error = pico_err; return 0; } int pico_shutdown(int sd, int how) { struct pico_bsd_endpoint *ep = get_endpoint(sd, 1); VALIDATE_NULL(ep); ep->error = PICO_ERR_NOERR; if(ep->s) /* valid socket, try to close it */ { pico_mutex_lock(picoLock); pico_socket_shutdown(ep->s, how); ep->error = pico_err; pico_mutex_unlock(picoLock); } else { ep->error = PICO_ERR_EINVAL; errno = pico_err; } return 0; } int pico_join_multicast_group(int sd, const char *address, const char *local) { int ret; struct pico_ip_mreq mreq={}; pico_string_to_ipv4(address, &mreq.mcast_group_addr.ip4.addr); pico_string_to_ipv4(local, &mreq.mcast_link_addr.ip4.addr); ret = pico_setsockopt(sd, SOL_SOCKET, PICO_IP_ADD_MEMBERSHIP, &mreq, sizeof(struct pico_ip_mreq)); return ret; } /*** Helper functions ***/ static int bsd_to_pico_addr(union pico_address *addr, struct sockaddr *_saddr, socklen_t socklen) { VALIDATE_TWO(socklen, SOCKSIZE, SOCKSIZE6); if (socklen == SOCKSIZE6) { struct sockaddr_in6 *saddr = (struct sockaddr_in6 *)_saddr; memcpy(&addr->ip6.addr, &saddr->sin6_addr.s6_addr, 16); saddr->sin6_family = AF_INET6; } else { struct sockaddr_in *saddr = (struct sockaddr_in *)_saddr; addr->ip4.addr = saddr->sin_addr.s_addr; saddr->sin_family = AF_INET; } return 0; } static uint16_t bsd_to_pico_port(struct sockaddr *_saddr, socklen_t socklen) { VALIDATE_TWO(socklen, SOCKSIZE, SOCKSIZE6); if (socklen == SOCKSIZE6) { struct sockaddr_in6 *saddr = (struct sockaddr_in6 *)_saddr; return saddr->sin6_port; } else { struct sockaddr_in *saddr = (struct sockaddr_in *)_saddr; return saddr->sin_port; } } static int pico_port_to_bsd(struct sockaddr *_saddr, socklen_t socklen, uint16_t port) { if (socklen == SOCKSIZE6) { struct sockaddr_in6 *saddr = (struct sockaddr_in6 *)_saddr; saddr->sin6_port = port; return 0; } else { struct sockaddr_in *saddr = (struct sockaddr_in *)_saddr; saddr->sin_port = port; return 0; } pico_err = PICO_ERR_EINVAL; errno = pico_err; return -1; } static int pico_addr_to_bsd(struct sockaddr *_saddr, socklen_t socklen, union pico_address *addr, uint16_t net) { VALIDATE_TWO(socklen, SOCKSIZE, SOCKSIZE6); if ((socklen == SOCKSIZE6) && (net == PICO_PROTO_IPV6)) { struct sockaddr_in6 *saddr = (struct sockaddr_in6 *)_saddr; memcpy(&saddr->sin6_addr.s6_addr, &addr->ip6.addr, 16); saddr->sin6_family = AF_INET6; } else if ((socklen == SOCKSIZE) && (net == PICO_PROTO_IPV4)) { struct sockaddr_in *saddr = (struct sockaddr_in *)_saddr; saddr->sin_addr.s_addr = addr->ip4.addr; saddr->sin_family = AF_INET; } return 0; } static void free_up_ep(struct pico_bsd_endpoint *ep) { if (ep->signal) pico_signal_deinit(ep->signal); if (ep->mutex_lock) pico_mutex_deinit(ep->mutex_lock); PICO_FREE(ep); } static int get_free_sd(struct pico_bsd_endpoint *ep) { int i; for (i = 0; i < PicoSocket_max; i++) { if (!PicoSockets[i]->in_use) { free_up_ep(PicoSockets[i]); PicoSockets[i] = ep; return i; } } return -1; } /* DLA TODO: make a GC for freeing up the last socket descriptor periodically if not in use */ static int new_sd(struct pico_bsd_endpoint *ep) { int sd = PicoSocket_max; struct pico_bsd_endpoint **new; new = PICO_ZALLOC(sizeof(void *) * ++PicoSocket_max); if (!new) { PicoSocket_max--; pico_err = PICO_ERR_ENOMEM; errno = pico_err; return -1; } if (sd > 0) { memcpy(new, PicoSockets, sd * sizeof(void *)); PICO_FREE(PicoSockets); } PicoSockets = new; new[sd] = ep; return sd; } /* picoLock must be taken already ! */ static struct pico_bsd_endpoint *pico_bsd_create_socket(void) { struct pico_bsd_endpoint *ep = PICO_ZALLOC(sizeof(struct pico_bsd_endpoint)); if (!ep) { pico_err = PICO_ERR_ENOMEM; errno = pico_err; } ep->in_use = 1; ep->socket_fd = get_free_sd(ep); if (ep->socket_fd < 0) { ep->socket_fd = new_sd(ep); } return ep; } #ifndef PICO_EBADFD # define PICO_EBADFD 77 /* File descriptor in bad state */ #endif static struct pico_bsd_endpoint *get_endpoint(int sd, int set_err) { if ((sd > PicoSocket_max) || (sd < 0) || (PicoSockets[sd]->in_use == 0)) { if (set_err) { pico_err = PICO_EBADFD; errno = pico_err; } return NULL; } return PicoSockets[sd]; } /* wait for one of the selected events, return any of those that occurred */ uint16_t pico_bsd_select(struct pico_bsd_endpoint *ep) { uint16_t events = ep->events & ep->revents; /* maybe an event we are waiting for, was already queued ? */ /* wait for one of the selected events... */ while (!events) { pico_signal_wait(ep->signal); events = (ep->revents & ep->events); /* filter for the events we were waiting for */ } /* the event we were waiting for happened, now report it */ return events; /* return any event(s) that occurred, that we were waiting for */ } /****************************/ /* Private helper functions */ /****************************/ static uint16_t pico_bsd_wait(struct pico_bsd_endpoint * ep, int read, int write, int close) { pico_mutex_lock(ep->mutex_lock); ep->events = PICO_SOCK_EV_ERR; ep->events |= PICO_SOCK_EV_FIN; ep->events |= PICO_SOCK_EV_CONN; if (close) ep->events |= PICO_SOCK_EV_CLOSE; if (read) ep->events |= PICO_SOCK_EV_RD; if (write) ep->events |= PICO_SOCK_EV_WR; pico_mutex_unlock(ep->mutex_lock); return pico_bsd_select(ep); } static void pico_event_clear(struct pico_bsd_endpoint *ep, uint16_t events) { pico_mutex_lock(ep->mutex_lock); ep->revents &= ~events; /* clear those events */ pico_mutex_unlock(ep->mutex_lock); } /* NOTE: __NO__ picoLock'ing here !! */ /* this is called from pico_stack_tick, so picoLock is already locked */ static void pico_socket_event(uint16_t ev, struct pico_socket *s) { struct pico_bsd_endpoint * ep = (struct pico_bsd_endpoint *)(s->priv); if (!s) return; if(!ep || !ep->s || !ep->mutex_lock || !ep->signal ) { /* DLA: do not call close upon SOCK_CLOSE, we might still write. */ if(ev & (PICO_SOCK_EV_FIN | PICO_SOCK_EV_ERR) ) { pico_signal_send(pico_signal_select); /* Signal this event globally (e.g. for select()) */ pico_socket_close(s); } if (ev & PICO_SOCK_EV_CLOSE) pico_signal_send(pico_signal_select); /* endpoint not initialized yet! */ return; } if(ep->in_use != 1) return; pico_mutex_lock(ep->mutex_lock); /* lock over the complete body is needed, as the event might get cleared in another process.. */ ep->revents |= ev; /* set those events */ if(ev & PICO_SOCK_EV_CONN) { if(ep->state != SOCK_LISTEN) { ep->state = SOCK_CONNECTED; } } if(ev & PICO_SOCK_EV_ERR) { ep->state = SOCK_RESET_BY_PEER; } if (ev & PICO_SOCK_EV_CLOSE) { ep->state = SOCK_RESET_BY_PEER; /* DO NOT close: we might still write! */ } if (ev & PICO_SOCK_EV_FIN) { /* DO NOT set ep->s = NULL, we might still be transmitting stuff! */ ep->state = SOCK_CLOSED; } pico_signal_send(pico_signal_select); /* Signal this event globally (e.g. for select()) */ pico_signal_send(ep->signal); /* Signal the endpoint that was blocking on this event */ pico_mutex_unlock(ep->mutex_lock); } #define DNSQUERY_OK 1 #define DNSQUERY_FAIL 0xFF struct dnsquery_cookie { struct addrinfo **res; void *signal; uint8_t block; uint8_t revents; }; static struct dnsquery_cookie *dnsquery_cookie_create(struct addrinfo **res, uint8_t block) { struct dnsquery_cookie *ck = PICO_ZALLOC(sizeof(struct dnsquery_cookie)); if (!ck) { pico_err = PICO_ERR_ENOMEM; errno = pico_err; return NULL; } ck->signal = pico_signal_init(); ck->res = res; ck->block = block; return ck; } static int dnsquery_cookie_delete(struct dnsquery_cookie *ck) { if (!ck) { pico_err = PICO_ERR_EINVAL; errno = pico_err; return -1; } if (ck->signal) { pico_signal_deinit(ck->signal); ck->signal = NULL; } PICO_FREE(ck); return 0; } #ifdef PICO_SUPPORT_IPV6 static void dns_ip6_cb(char *ip, void *arg) { struct dnsquery_cookie *ck = (struct dnsquery_cookie *)arg; struct addrinfo *new; if (ip) { new = PICO_ZALLOC(sizeof(struct addrinfo)); if (!new) { ck->revents = DNSQUERY_FAIL; if (ck->block) pico_signal_send(ck->signal); return; } new->ai_family = AF_INET6; new->ai_addr = PICO_ZALLOC(sizeof(struct sockaddr_in6)); if (!new->ai_addr) { PICO_FREE(new); ck->revents = DNSQUERY_FAIL; if (ck->block) pico_signal_send(ck->signal); return; } new->ai_addrlen = sizeof(struct sockaddr_in6); pico_string_to_ipv6(ip, (((struct sockaddr_in6*)(new->ai_addr))->sin6_addr.s6_addr)); ((struct sockaddr_in6*)(new->ai_addr))->sin6_family = AF_INET6; new->ai_next = *ck->res; *ck->res = new; ck->revents = DNSQUERY_OK; } else { /* No ip given, but still callback was called: timeout! */ ck->revents = DNSQUERY_FAIL; } if (ck->block) pico_signal_send(ck->signal); } #endif static void dns_ip4_cb(char *ip, void *arg) { struct dnsquery_cookie *ck = (struct dnsquery_cookie *)arg; struct addrinfo *new; if (ip) { new = PICO_ZALLOC(sizeof(struct addrinfo)); if (!new) { ck->revents = DNSQUERY_FAIL; if (ck->block) pico_signal_send(ck->signal); return; } new->ai_family = AF_INET; new->ai_addr = PICO_ZALLOC(sizeof(struct sockaddr_in)); if (!new->ai_addr) { PICO_FREE(new); ck->revents = DNSQUERY_FAIL; if (ck->block) pico_signal_send(ck->signal); return; } new->ai_addrlen = sizeof(struct sockaddr_in); pico_string_to_ipv4(ip, &(((struct sockaddr_in*)new->ai_addr)->sin_addr.s_addr)); ((struct sockaddr_in*)(new->ai_addr))->sin_family = AF_INET; new->ai_next = *ck->res; *ck->res = new; ck->revents = DNSQUERY_OK; } else { /* No ip given, but still callback was called: timeout! */ ck->revents = DNSQUERY_FAIL; } if (ck->block) pico_signal_send(ck->signal); } #ifdef PICO_SUPPORT_DNS_CLIENT int pico_getaddrinfo(const char *node, const char *service, const struct addrinfo *hints, struct addrinfo **res) { struct dnsquery_cookie *ck4 = NULL; struct dnsquery_cookie *ck6 = NULL; struct sockaddr_in sa4; *res = NULL; (void)service; bsd_dbg("Called pico_getaddrinfo, looking for %s\n", node); #ifdef PICO_SUPPORT_IPV6 struct sockaddr_in6 sa6; if (pico_string_to_ipv6(node, sa6.sin6_addr.s6_addr) == 0) { ck6 = dnsquery_cookie_create(res, 0); dns_ip6_cb((char *)node, ck6); dnsquery_cookie_delete(ck6); return 0; } #endif if (pico_string_to_ipv4(node, &sa4.sin_addr.s_addr) == 0) { ck4 = dnsquery_cookie_create(res, 0); dns_ip4_cb((char*)node, ck4); dnsquery_cookie_delete(ck4); return 0; } #ifdef PICO_SUPPORT_IPV6 { if (!hints || (hints->ai_family == AF_INET6)) { ck6 = dnsquery_cookie_create(res, 1); if (!ck6) return -1; pico_mutex_lock(picoLock); if (pico_dns_client_getaddr6(node, dns_ip6_cb, ck6) < 0) { bsd_dbg("Error resolving AAAA record %s\n", node); dnsquery_cookie_delete(ck6); pico_mutex_unlock(picoLock); return -1; } bsd_dbg("Resolving AAAA record %s\n", node); pico_mutex_unlock(picoLock); } } #endif /* PICO_SUPPORT_IPV6 */ if (!hints || (hints->ai_family == AF_INET)) { ck4 = dnsquery_cookie_create(res, 1); pico_mutex_lock(picoLock); if (pico_dns_client_getaddr(node, dns_ip4_cb, ck4) < 0) { bsd_dbg("Error resolving A record %s\n", node); dnsquery_cookie_delete(ck4); pico_mutex_unlock(picoLock); return -1; } bsd_dbg("Resolving A record %s\n", node); pico_mutex_unlock(picoLock); } #ifdef PICO_SUPPORT_IPV6 if (ck6) { /* Signal is always sent; either dns resolved, or timeout/failure */ pico_signal_wait(ck6->signal); dnsquery_cookie_delete(ck6); } #endif /* PICO_SUPPORT_IPV6 */ if (ck4) { /* Signal is always sent; either dns resolved, or timeout/failure */ pico_signal_wait(ck4->signal); dnsquery_cookie_delete(ck4); } if (*res) return 0; return -1; } void pico_freeaddrinfo(struct addrinfo *res) { struct addrinfo *cur = res; struct addrinfo *nxt; while(cur) { if (cur->ai_addr) PICO_FREE(cur->ai_addr); nxt = cur->ai_next; PICO_FREE(cur); cur = nxt; } } /* Legacy gethostbyname call implementation */ static struct hostent PRIV_HOSTENT = { }; struct hostent *pico_gethostbyname(const char *name) { struct addrinfo *res; struct addrinfo hint = {.ai_family = AF_INET}; int ret; if (!PRIV_HOSTENT.h_addr_list) { /* Done only once: reserve space for 2 entries */ PRIV_HOSTENT.h_addr_list = PICO_ZALLOC(2 * sizeof(void*)); PRIV_HOSTENT.h_addr_list[1] = NULL; } ret = pico_getaddrinfo(name, NULL, &hint, &res); if (ret == 0) { if (PRIV_HOSTENT.h_name != NULL) { PICO_FREE(PRIV_HOSTENT.h_name); PRIV_HOSTENT.h_name = NULL; } if (PRIV_HOSTENT.h_addr_list[0] != NULL) { PICO_FREE(PRIV_HOSTENT.h_addr_list[0]); PRIV_HOSTENT.h_addr_list[0] = NULL; } PRIV_HOSTENT.h_name = PICO_ZALLOC(strlen(name)); if (!PRIV_HOSTENT.h_name) { pico_freeaddrinfo(res); return NULL; } strcpy(PRIV_HOSTENT.h_name, name); PRIV_HOSTENT.h_addrtype = res->ai_addr->sa_family; if (PRIV_HOSTENT.h_addrtype == AF_INET) { PRIV_HOSTENT.h_length = 4; PRIV_HOSTENT.h_addr_list[0] = PICO_ZALLOC(4); if (!PRIV_HOSTENT.h_addr_list[0]) { pico_freeaddrinfo(res); return NULL; } memcpy (PRIV_HOSTENT.h_addr_list[0], &(((struct sockaddr_in *)res->ai_addr)->sin_addr.s_addr), 4); } else { /* Only IPv4 supported by this ancient call. */ pico_freeaddrinfo(res); return NULL; } pico_freeaddrinfo(res); return &PRIV_HOSTENT; } return NULL; } #endif int pico_getsockopt(int sockfd, int level, int optname, void *optval, socklen_t *optlen) { struct pico_bsd_endpoint *ep = get_endpoint(sockfd, 1); int ret; bsd_dbg("called getsockopt\n"); VALIDATE_NULL(ep); if (level != SOL_SOCKET) { pico_err = PICO_ERR_EPROTONOSUPPORT; errno = pico_err; return -1; } if (!ep) { pico_err = PICO_ERR_EINVAL; errno = pico_err; return -1; } if (!optval) { pico_err = PICO_ERR_EFAULT; errno = pico_err; return -1; } if (optname == SO_ERROR) { *((int*)optval) = ep->error; ep->error = 0; return 0; } pico_mutex_lock(ep->mutex_lock); ret = pico_socket_getoption(ep->s, sockopt_get_name(optname), optval); pico_mutex_unlock(ep->mutex_lock); return ret; } int pico_setsockopt(int sockfd, int level, int optname, const void *optval, socklen_t optlen) { struct pico_bsd_endpoint *ep = get_endpoint(sockfd, 1); int ret; VALIDATE_NULL(ep); ep->error = PICO_ERR_NOERR; bsd_dbg("called setsockopt\n"); if (level != SOL_SOCKET) { pico_err = PICO_ERR_EPROTONOSUPPORT; errno = pico_err; return -1; } if (!ep) { pico_err = PICO_ERR_EINVAL; errno = pico_err; return -1; } if (!optval) { pico_err = PICO_ERR_EFAULT; errno = pico_err; return -1; } if ((optname == SO_REUSEADDR) || (optname == SO_REUSEPORT)) return 0; /* Pretend it was OK. */ if (optname == SO_ERROR) return PICO_ERR_ENOPROTOOPT; pico_mutex_lock(ep->mutex_lock); ret = pico_socket_setoption(ep->s, sockopt_get_name(optname), (void *)optval); pico_mutex_unlock(ep->mutex_lock); return ret; } #ifdef PICO_SUPPORT_SNTP_CLIENT int pico_gettimeofday(struct timeval *tv, struct timezone *tz) { int ret; (void)tz; struct pico_timeval ptv; ret= pico_sntp_gettimeofday(&ptv); tv->tv_sec = ptv.tv_sec; tv->tv_usec= ptv.tv_msec * 1000; /* pico_timeval uses milliseconds instead of microseconds */ return ret; } /* dummy function */ int pico_settimeofday(struct timeval *tv, struct timezone *tz) { (void)tz; (void)tv; return 0; } #else static struct pico_timeval ptv = {0u,0u}; int pico_gettimeofday(struct timeval *tv, struct timezone *tz) { int ret; (void)tz; tv->tv_sec = ptv.tv_sec; tv->tv_usec= ptv.tv_msec * 1000; /* pico_timeval uses milliseconds instead of microseconds */ return 0; } int pico_settimeofday(struct timeval *tv, struct timezone *tz) { int ret; (void)tz; ptv.tv_sec = tv->tv_sec; ptv.tv_msec= tv->tv_usec / 1000; /* pico_timeval uses milliseconds instead of microseconds */ return 0; } #endif long XTIME(void) { struct timeval t; pico_gettimeofday(&t, NULL); return (long)t.tv_sec; } const char *pico_inet_ntop(int af, const void *src, char *dst, socklen_t size) { if ((!dst) || (!src)) return NULL; switch (af) { case AF_INET: if (size < INET_ADDRSTRLEN) return NULL; pico_ipv4_to_string(dst, *((const uint32_t *)src)); break; #ifdef PICO_SUPPORT_IPV6 case AF_INET6: if (size < INET6_ADDRSTRLEN) return NULL; pico_ipv6_to_string(dst, ((struct in6_addr *)src)->s6_addr); break; #endif default: dst = NULL; break; } return dst; } char *pico_inet_ntoa(struct in_addr in) { static char ipbuf[INET_ADDRSTRLEN]; pico_ipv4_to_string(ipbuf, (uint32_t)in.s_addr); return ipbuf; } int pico_pselect(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, const struct timespec *timeout, const sigset_t *sigmask) { /* * EV_READ: sets the readfds * EV_WRITE: sets the writefds * EV_CONN: sets the readfds (a.k.a. someone connects to your listening socket) * EV_CLOSE: sets the readfds, then next recv() returns 0; * EV_FIN: sets the readfds, then next recv() returns 0; * EV_ERR: sets the exceptfds */ int i = 0; /* socket fds */ int nfds_out = 0; /* amount of changed sockets */ (void) sigmask; bsd_dbg_select("=== IN: PICO SELECT === readfds[0]: 0x%x -- writefds[0]: 0x%x\n", readfds?(*(uint8_t *)readfds):0, writefds?(*(uint8_t *)writefds):0); pico_fd_set readfds_out = {}; pico_fd_set writefds_out = {}; pico_fd_set exceptfds_out = {}; /* First, loop over all possible file descriptors, check if one has an event pending that we're waiting for */ while (nfds_out == 0) { for (i = 0; i < nfds; i++) { struct pico_bsd_endpoint *ep = get_endpoint(i, 0); bsd_dbg_select("\t~~~ SELECT: fds %d - ep:%p ", i, ep); if (ep) { /* Is this endpoint still valid? */ if (!ep->in_use) { bsd_dbg_select(" ep->in_use = 0\n"); break; } /* READ event needed and available? */ if (readfds && PICO_FD_ISSET(i,readfds) && (ep->revents & (PICO_SOCK_EV_CONN | PICO_SOCK_EV_CLOSE | PICO_SOCK_EV_RD))) { bsd_dbg_select("- READ_EV - "); nfds_out++; PICO_FD_SET(i, &readfds_out); } /* Force write events on empty udp sockets */ if ((ep->proto == PICO_PROTO_UDP) && (ep->s->q_out.size < ep->s->q_out.max_size)) ep->revents |= PICO_SOCK_EV_WR; /* WRITE event needed? and available? */ if (writefds && PICO_FD_ISSET(i,writefds) && (ep->revents & (PICO_SOCK_EV_WR))) { bsd_dbg_select("- WRITE_EV - "); nfds_out++; PICO_FD_SET(i, &writefds_out); } /* EXCEPTION event needed and available? */ if (exceptfds && PICO_FD_ISSET(i,exceptfds) && (ep->revents & (PICO_SOCK_EV_ERR))) { bsd_dbg_select("- EXCEPT_EV - "); nfds_out++; PICO_FD_SET(i, &exceptfds_out); } } if (ep) bsd_dbg_select("- s:%p - ev:%x", ep->s, ep->revents); bsd_dbg_select("\n"); } /* If there was a hit, break out of the loop */ if (nfds_out) break; /* If not, wait for a semaphore signaling an event from the stack */ if (pico_signal_wait_timeout(pico_signal_select, (timeout->tv_sec * 1000) + ((timeout->tv_nsec) / 1000000)) == -1) { /* On timeout, break out of the loop */ bsd_dbg_select("\t~~~ SELECT: TIMEOUT\n"); break; } else { /* Process the received event -> re-iterate */ bsd_dbg_select("\t~~~ SELECT: Socket event, re-iterating fds\n"); } } /* Copy back result only if descriptor was valid */ if (readfds) memcpy(readfds, &readfds_out, sizeof(pico_fd_set)); if (writefds) memcpy(writefds, &writefds_out, sizeof(pico_fd_set)); if (exceptfds) memcpy(exceptfds, &exceptfds_out, sizeof(pico_fd_set)); bsd_dbg_select("=== OUT: PICO SELECT === fds changed: %d\n", nfds_out); return nfds_out; } int pico_select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout) { struct timespec ts; if (timeout) { ts.tv_sec = timeout->tv_sec; ts.tv_nsec = timeout->tv_usec * 1000; return pico_pselect(nfds, readfds, writefds, exceptfds, &ts, NULL); } else return pico_pselect(nfds, readfds, writefds, exceptfds, NULL, NULL); } int pico_ppoll(struct pollfd *pfd, nfds_t npfd, const struct timespec *timeout, const sigset_t *sigmask) { int i; int ret = 0; (void) sigmask; while (ret == 0) { for (i = 0; i < npfd; i++) { struct pico_bsd_endpoint *ep = get_endpoint(pfd[i].fd, 0); pfd[i].revents = 0u; /* Always polled events */ if (!ep) { pfd[i].revents |= POLLNVAL; } if (!ep->in_use) { pfd[i].revents |= POLLNVAL; } if (ep->revents & (PICO_SOCK_EV_FIN | PICO_SOCK_EV_ERR)) { pfd[i].revents |= POLLERR; ret++; } if (ep->revents & PICO_SOCK_EV_CLOSE) pfd[i].revents |= POLLHUP; /* XXX: I am sure we mean POLLRDHUP ! see man 2 poll */ /* Checking POLLIN */ if ((pfd[i].events & POLLIN) && (ep->revents & (PICO_SOCK_EV_RD | PICO_SOCK_EV_CONN))) { pfd[i].revents |= POLLIN; if (pfd[i].events & POLLRDNORM) pfd[i].revents |= POLLRDNORM; } /* Checking POLLOUT */ if ((pfd[i].events & POLLOUT) && (ep->revents & (PICO_SOCK_EV_WR))) { pfd[i].revents |= POLLOUT; if (pfd[i].events & POLLWRNORM) pfd[i].revents |= POLLWRNORM; } if (pfd[i].revents != 0) ret++; } /* End for loop */ if ((ret == 0) && timeout && (pico_signal_wait_timeout(pico_signal_select, (timeout->tv_sec * 1000) + ((timeout->tv_nsec) / 1000000)) == -1)) return 0; /* Timeout */ } /* End while loop */ return ret; } int pico_poll(struct pollfd *pfd, nfds_t npfd, int timeout) { struct timespec ts = {0U, 0U}; if (timeout >= 0) { ts.tv_sec = timeout / 1000; ts.tv_nsec = (timeout % 1000) * 1000000; return pico_ppoll(pfd, npfd, &ts, NULL); } else { return pico_ppoll(pfd, npfd, NULL, NULL); } }