| /* |
| * Copyright (c) 2019 Linumiz |
| * |
| * SPDX-License-Identifier: Apache-2.0 |
| */ |
| |
| #include "eswifi_log.h" |
| LOG_MODULE_DECLARE(LOG_MODULE_NAME); |
| |
| #include <zephyr/kernel.h> |
| #include <zephyr/device.h> |
| #include <string.h> |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <errno.h> |
| |
| #include <zephyr/net/socket_offload.h> |
| #include <zephyr/net/tls_credentials.h> |
| |
| #include "sockets_internal.h" |
| #if defined(CONFIG_NET_SOCKETS_SOCKOPT_TLS) |
| #include "tls_internal.h" |
| #endif |
| #include "eswifi.h" |
| #include <zephyr/net/net_pkt.h> |
| |
| /* Increment by 1 to make sure we do not store the value of 0, which has |
| * a special meaning in the fdtable subsys. |
| */ |
| #define SD_TO_OBJ(sd) ((void *)(sd + 1)) |
| #define OBJ_TO_SD(obj) (((intptr_t)obj) - 1) |
| /* Default socket context (50CE) */ |
| #define ESWIFI_INIT_CONTEXT INT_TO_POINTER(0x50CE) |
| |
| static struct eswifi_dev *eswifi; |
| static const struct socket_op_vtable eswifi_socket_fd_op_vtable; |
| |
| static void __process_received(struct net_context *context, |
| struct net_pkt *pkt, |
| union net_ip_header *ip_hdr, |
| union net_proto_header *proto_hdr, |
| int status, |
| void *user_data) |
| { |
| struct eswifi_off_socket *socket = user_data; |
| |
| if (!pkt) { |
| k_fifo_cancel_wait(&socket->fifo); |
| return; |
| } |
| |
| k_fifo_put(&socket->fifo, pkt); |
| } |
| |
| static int eswifi_socket_connect(void *obj, const struct sockaddr *addr, |
| socklen_t addrlen) |
| { |
| intptr_t sock = OBJ_TO_SD(obj); |
| struct eswifi_off_socket *socket; |
| int ret; |
| |
| if ((addrlen == 0) || (addr == NULL) || |
| (sock >= ESWIFI_OFFLOAD_MAX_SOCKETS)) { |
| return -EINVAL; |
| } |
| |
| if (addr->sa_family != AF_INET) { |
| LOG_ERR("Only AF_INET is supported!"); |
| return -EPFNOSUPPORT; |
| } |
| |
| eswifi_lock(eswifi); |
| socket = &eswifi->socket[sock]; |
| |
| if (socket->state != ESWIFI_SOCKET_STATE_NONE) { |
| eswifi_unlock(eswifi); |
| return -EBUSY; |
| } |
| |
| socket->peer_addr = *addr; |
| socket->state = ESWIFI_SOCKET_STATE_CONNECTING; |
| |
| ret = __eswifi_off_start_client(eswifi, socket); |
| if (!ret) { |
| socket->state = ESWIFI_SOCKET_STATE_CONNECTED; |
| } else { |
| socket->state = ESWIFI_SOCKET_STATE_NONE; |
| } |
| |
| eswifi_unlock(eswifi); |
| return ret; |
| } |
| |
| static int eswifi_socket_listen(void *obj, int backlog) |
| { |
| struct eswifi_off_socket *socket; |
| intptr_t sock = OBJ_TO_SD(obj); |
| int ret; |
| |
| eswifi_lock(eswifi); |
| socket = &eswifi->socket[sock]; |
| |
| ret = __eswifi_listen(eswifi, socket, backlog); |
| eswifi_unlock(eswifi); |
| |
| return ret; |
| } |
| |
| void __eswifi_socket_accept_cb(struct net_context *context, struct sockaddr *addr, |
| size_t len, int val, void *data) |
| { |
| struct sockaddr *addr_target = data; |
| |
| memcpy(addr_target, addr, len); |
| } |
| |
| static int __eswifi_socket_accept(void *obj, struct sockaddr *addr, |
| socklen_t *addrlen) |
| { |
| intptr_t sock = OBJ_TO_SD(obj); |
| struct eswifi_off_socket *socket; |
| int ret; |
| |
| if ((addrlen == NULL) || (addr == NULL) || |
| (sock >= ESWIFI_OFFLOAD_MAX_SOCKETS)) { |
| return -EINVAL; |
| } |
| |
| eswifi_lock(eswifi); |
| socket = &eswifi->socket[sock]; |
| |
| ret = __eswifi_accept(eswifi, socket); |
| socket->accept_cb = __eswifi_socket_accept_cb; |
| socket->accept_data = addr; |
| k_sem_reset(&socket->accept_sem); |
| eswifi_unlock(eswifi); |
| |
| *addrlen = sizeof(struct sockaddr_in); |
| |
| k_sem_take(&socket->accept_sem, K_FOREVER); |
| |
| return 0; |
| } |
| |
| static int eswifi_socket_accept(void *obj, struct sockaddr *addr, |
| socklen_t *addrlen) |
| { |
| int fd = zvfs_reserve_fd(); |
| intptr_t sock; |
| |
| if (fd < 0) { |
| return -1; |
| } |
| |
| sock = __eswifi_socket_accept(obj, addr, addrlen); |
| if (sock < 0) { |
| zvfs_free_fd(fd); |
| return -1; |
| } |
| |
| zvfs_finalize_typed_fd(fd, SD_TO_OBJ(sock), |
| (const struct fd_op_vtable *)&eswifi_socket_fd_op_vtable, |
| ZVFS_MODE_IFSOCK); |
| |
| return fd; |
| } |
| |
| #if defined(CONFIG_NET_SOCKETS_SOCKOPT_TLS) |
| static int map_credentials(int sd, const void *optval, socklen_t optlen) |
| { |
| sec_tag_t *sec_tags = (sec_tag_t *)optval; |
| int ret = 0; |
| int tags_len; |
| sec_tag_t tag; |
| int id; |
| int i, bytes; |
| struct tls_credential *cert; |
| |
| if ((optlen % sizeof(sec_tag_t)) != 0 || (optlen == 0)) { |
| return -EINVAL; |
| } |
| |
| tags_len = optlen / sizeof(sec_tag_t); |
| /* For each tag, retrieve the credentials value and type: */ |
| for (i = 0; i < tags_len; i++) { |
| tag = sec_tags[i]; |
| cert = credential_next_get(tag, NULL); |
| while (cert != NULL) { |
| /* Map Zephyr cert types to Simplelink cert options: */ |
| switch (cert->type) { |
| case TLS_CREDENTIAL_CA_CERTIFICATE: |
| id = 0; |
| break; |
| case TLS_CREDENTIAL_SERVER_CERTIFICATE: |
| id = 1; |
| break; |
| case TLS_CREDENTIAL_PRIVATE_KEY: |
| id = 2; |
| break; |
| case TLS_CREDENTIAL_NONE: |
| case TLS_CREDENTIAL_PSK: |
| case TLS_CREDENTIAL_PSK_ID: |
| default: |
| /* Not handled */ |
| return -EINVAL; |
| } |
| |
| snprintk(eswifi->buf, sizeof(eswifi->buf), |
| "PG=%d,%d,%d\r", 0, id, cert->len); |
| bytes = strlen(eswifi->buf); |
| memcpy(&eswifi->buf[bytes], cert->buf, cert->len); |
| bytes += cert->len; |
| LOG_DBG("cert write len %d\n", cert->len); |
| ret = eswifi_request(eswifi, eswifi->buf, bytes + 1, |
| eswifi->buf, sizeof(eswifi->buf)); |
| LOG_DBG("cert write err %d\n", ret); |
| if (ret < 0) { |
| return ret; |
| } |
| |
| snprintk(eswifi->buf, sizeof(eswifi->buf), "PF=0,0\r"); |
| ret = eswifi_at_cmd(eswifi, eswifi->buf); |
| if (ret < 0) { |
| return ret; |
| } |
| |
| cert = credential_next_get(tag, cert); |
| } |
| } |
| |
| return 0; |
| } |
| #else |
| static int map_credentials(int sd, const void *optval, socklen_t optlen) |
| { |
| return 0; |
| } |
| #endif |
| |
| static int eswifi_socket_setsockopt(void *obj, int level, int optname, |
| const void *optval, socklen_t optlen) |
| { |
| intptr_t sd = OBJ_TO_SD(obj); |
| int ret; |
| |
| if (IS_ENABLED(CONFIG_NET_SOCKETS_SOCKOPT_TLS) && level == SOL_TLS) { |
| switch (optname) { |
| case TLS_SEC_TAG_LIST: |
| ret = map_credentials(sd, optval, optlen); |
| break; |
| case TLS_HOSTNAME: |
| case TLS_PEER_VERIFY: |
| ret = 0; |
| break; |
| default: |
| return -EINVAL; |
| } |
| } else { |
| return -EINVAL; |
| } |
| |
| return ret; |
| } |
| |
| static ssize_t eswifi_socket_send(void *obj, const void *buf, size_t len, |
| int flags) |
| { |
| intptr_t sock = OBJ_TO_SD(obj); |
| struct eswifi_off_socket *socket; |
| int ret; |
| int offset; |
| |
| if (!buf) { |
| return -EINVAL; |
| } |
| |
| eswifi_lock(eswifi); |
| socket = &eswifi->socket[sock]; |
| |
| if (socket->state != ESWIFI_SOCKET_STATE_CONNECTED) { |
| eswifi_unlock(eswifi); |
| return -ENOTCONN; |
| } |
| |
| __select_socket(eswifi, socket->index); |
| |
| /* header */ |
| snprintk(eswifi->buf, sizeof(eswifi->buf), "S3=%u\r", len); |
| offset = strlen(eswifi->buf); |
| |
| /* copy payload */ |
| memcpy(&eswifi->buf[offset], buf, len); |
| offset += len; |
| |
| ret = eswifi_request(eswifi, eswifi->buf, offset + 1, eswifi->buf, |
| sizeof(eswifi->buf)); |
| if (ret < 0) { |
| LOG_DBG("Unable to send data"); |
| ret = -EIO; |
| } else { |
| ret = len; |
| } |
| |
| eswifi_unlock(eswifi); |
| return ret; |
| } |
| |
| static ssize_t eswifi_socket_sendto(void *obj, const void *buf, size_t len, |
| int flags, const struct sockaddr *to, |
| socklen_t tolen) |
| { |
| if (to != NULL) { |
| errno = EOPNOTSUPP; |
| return -1; |
| } |
| |
| return eswifi_socket_send(obj, buf, len, flags); |
| } |
| |
| static ssize_t eswifi_socket_recv(void *obj, void *buf, size_t max_len, |
| int flags) |
| { |
| intptr_t sock = OBJ_TO_SD(obj); |
| struct eswifi_off_socket *socket; |
| int len = 0, ret = 0; |
| struct net_pkt *pkt; |
| |
| if ((max_len == 0) || (buf == NULL) || |
| (sock >= ESWIFI_OFFLOAD_MAX_SOCKETS)) { |
| return -EINVAL; |
| } |
| |
| eswifi_lock(eswifi); |
| socket = &eswifi->socket[sock]; |
| |
| if (socket->prev_pkt_rem) { |
| pkt = socket->prev_pkt_rem; |
| goto skip_wait; |
| } |
| |
| ret = k_work_reschedule_for_queue(&eswifi->work_q, &socket->read_work, K_NO_WAIT); |
| if (ret < 0) { |
| LOG_ERR("Rescheduling socket read error"); |
| errno = -ret; |
| len = -1; |
| } |
| |
| if (flags & ZSOCK_MSG_DONTWAIT) { |
| pkt = k_fifo_get(&socket->fifo, K_NO_WAIT); |
| if (!pkt) { |
| errno = EAGAIN; |
| len = -1; |
| goto done; |
| } |
| } else { |
| eswifi_unlock(eswifi); |
| pkt = k_fifo_get(&socket->fifo, K_FOREVER); |
| if (!pkt) { |
| return 0; /* EOF */ |
| } |
| eswifi_lock(eswifi); |
| } |
| |
| skip_wait: |
| len = net_pkt_remaining_data(pkt); |
| if (len > max_len) { |
| len = max_len; |
| socket->prev_pkt_rem = pkt; |
| } else { |
| socket->prev_pkt_rem = NULL; |
| } |
| |
| ret = net_pkt_read(pkt, buf, len); |
| |
| if (!socket->prev_pkt_rem) { |
| net_pkt_unref(pkt); |
| } |
| |
| done: |
| LOG_DBG("read %d %d %p", len, ret, pkt); |
| eswifi_unlock(eswifi); |
| if (ret) { |
| len = 0; |
| } |
| |
| return len; |
| } |
| |
| static ssize_t eswifi_socket_recvfrom(void *obj, void *buf, size_t len, |
| int flags, struct sockaddr *from, |
| socklen_t *fromlen) |
| { |
| if (fromlen != NULL) { |
| errno = EOPNOTSUPP; |
| return -1; |
| } |
| |
| return eswifi_socket_recv(obj, buf, len, flags); |
| } |
| |
| static int eswifi_socket_close(void *obj) |
| { |
| intptr_t sock = OBJ_TO_SD(obj); |
| struct eswifi_off_socket *socket; |
| struct net_pkt *pkt; |
| int ret; |
| |
| if (sock >= ESWIFI_OFFLOAD_MAX_SOCKETS) { |
| return -EINVAL; |
| } |
| |
| eswifi_lock(eswifi); |
| |
| socket = &eswifi->socket[sock]; |
| ret = __eswifi_socket_free(eswifi, socket); |
| if (ret) { |
| goto done; |
| } |
| |
| /* consume all net pkt */ |
| while (1) { |
| pkt = k_fifo_get(&socket->fifo, K_NO_WAIT); |
| if (!pkt) { |
| break; |
| } |
| net_pkt_unref(pkt); |
| } |
| |
| if (--socket->usage <= 0) { |
| socket->context = NULL; |
| } |
| |
| done: |
| eswifi_unlock(eswifi); |
| return ret; |
| } |
| |
| static int eswifi_socket_open(int family, int type, int proto) |
| { |
| struct eswifi_off_socket *socket = NULL; |
| int idx; |
| |
| eswifi_lock(eswifi); |
| |
| idx = __eswifi_socket_new(eswifi, family, type, proto, ESWIFI_INIT_CONTEXT); |
| if (idx < 0) { |
| goto unlock; |
| } |
| |
| socket = &eswifi->socket[idx]; |
| k_fifo_init(&socket->fifo); |
| k_sem_init(&socket->read_sem, 0, 200); |
| k_sem_init(&socket->accept_sem, 1, 1); |
| socket->prev_pkt_rem = NULL; |
| socket->recv_cb = __process_received; |
| socket->recv_data = socket; |
| |
| k_work_reschedule_for_queue(&eswifi->work_q, &socket->read_work, |
| K_MSEC(500)); |
| |
| unlock: |
| eswifi_unlock(eswifi); |
| return idx; |
| } |
| |
| static int eswifi_socket_poll(struct zsock_pollfd *fds, int nfds, int msecs) |
| { |
| struct eswifi_off_socket *socket; |
| k_timeout_t timeout; |
| intptr_t sock; |
| int ret; |
| void *obj; |
| |
| if (nfds != 1) { |
| errno = EINVAL; |
| return -1; |
| } |
| |
| obj = zvfs_get_fd_obj(fds[0].fd, |
| (const struct fd_op_vtable *) |
| &eswifi_socket_fd_op_vtable, |
| 0); |
| if (obj != NULL) { |
| sock = OBJ_TO_SD(obj); |
| } else { |
| errno = EINVAL; |
| return -1; |
| } |
| |
| if (sock >= ESWIFI_OFFLOAD_MAX_SOCKETS) { |
| errno = EINVAL; |
| return -1; |
| } |
| |
| if (!(fds[0].events & ZSOCK_POLLIN)) { |
| errno = ENOTSUP; |
| return -1; |
| } |
| |
| eswifi_lock(eswifi); |
| socket = &eswifi->socket[sock]; |
| |
| ret = k_work_reschedule_for_queue(&eswifi->work_q, &socket->read_work, K_NO_WAIT); |
| if (ret < 0) { |
| LOG_ERR("Rescheduling socket read error"); |
| errno = -ret; |
| eswifi_unlock(eswifi); |
| return -1; |
| } |
| |
| eswifi_unlock(eswifi); |
| |
| if (socket->state != ESWIFI_SOCKET_STATE_CONNECTED) { |
| errno = EINVAL; |
| return -1; |
| } |
| |
| if (!k_fifo_is_empty(&socket->fifo)) { |
| goto done; |
| } |
| |
| if (msecs == SYS_FOREVER_MS) { |
| timeout = K_FOREVER; |
| } else { |
| timeout = K_MSEC(msecs); |
| } |
| |
| ret = k_sem_take(&socket->read_sem, timeout); |
| if (ret) { |
| errno = ETIMEDOUT; |
| return -1; |
| } |
| |
| done: |
| fds[0].revents = ZSOCK_POLLIN; |
| |
| /* Report one event */ |
| return 1; |
| } |
| |
| static int eswifi_socket_bind(void *obj, const struct sockaddr *addr, |
| socklen_t addrlen) |
| { |
| intptr_t sock = OBJ_TO_SD(obj); |
| struct eswifi_off_socket *socket; |
| int ret; |
| |
| if ((addrlen == 0) || (addr == NULL) || |
| (sock >= ESWIFI_OFFLOAD_MAX_SOCKETS)) { |
| return -EINVAL; |
| } |
| |
| eswifi_lock(eswifi); |
| socket = &eswifi->socket[sock]; |
| ret = __eswifi_bind(eswifi, socket, addr, addrlen); |
| eswifi_unlock(eswifi); |
| |
| return ret; |
| } |
| |
| static bool eswifi_socket_is_supported(int family, int type, int proto) |
| { |
| enum eswifi_transport_type eswifi_socket_type; |
| int err; |
| |
| if (family != AF_INET) { |
| return false; |
| } |
| |
| if (type != SOCK_DGRAM && |
| type != SOCK_STREAM) { |
| return false; |
| } |
| |
| err = eswifi_socket_type_from_zephyr(proto, &eswifi_socket_type); |
| if (err) { |
| return false; |
| } |
| |
| return true; |
| } |
| |
| int eswifi_socket_create(int family, int type, int proto) |
| { |
| int fd = zvfs_reserve_fd(); |
| intptr_t sock; |
| |
| if (fd < 0) { |
| return -1; |
| } |
| |
| sock = eswifi_socket_open(family, type, proto); |
| if (sock < 0) { |
| zvfs_free_fd(fd); |
| return -1; |
| } |
| |
| zvfs_finalize_typed_fd(fd, SD_TO_OBJ(sock), |
| (const struct fd_op_vtable *)&eswifi_socket_fd_op_vtable, |
| ZVFS_MODE_IFSOCK); |
| |
| return fd; |
| } |
| |
| static int eswifi_socket_ioctl(void *obj, unsigned int request, va_list args) |
| { |
| switch (request) { |
| case ZFD_IOCTL_POLL_PREPARE: |
| return -EXDEV; |
| |
| case ZFD_IOCTL_POLL_UPDATE: |
| return -EOPNOTSUPP; |
| |
| case ZFD_IOCTL_POLL_OFFLOAD: { |
| struct zsock_pollfd *fds; |
| int nfds; |
| int timeout; |
| |
| fds = va_arg(args, struct zsock_pollfd *); |
| nfds = va_arg(args, int); |
| timeout = va_arg(args, int); |
| |
| return eswifi_socket_poll(fds, nfds, timeout); |
| } |
| |
| default: |
| errno = EINVAL; |
| return -1; |
| } |
| } |
| |
| static ssize_t eswifi_socket_read(void *obj, void *buffer, size_t count) |
| { |
| return eswifi_socket_recvfrom(obj, buffer, count, 0, NULL, 0); |
| } |
| |
| static ssize_t eswifi_socket_write(void *obj, const void *buffer, |
| size_t count) |
| { |
| return eswifi_socket_sendto(obj, buffer, count, 0, NULL, 0); |
| } |
| |
| static const struct socket_op_vtable eswifi_socket_fd_op_vtable = { |
| .fd_vtable = { |
| .read = eswifi_socket_read, |
| .write = eswifi_socket_write, |
| .close = eswifi_socket_close, |
| .ioctl = eswifi_socket_ioctl, |
| }, |
| .bind = eswifi_socket_bind, |
| .connect = eswifi_socket_connect, |
| .listen = eswifi_socket_listen, |
| .accept = eswifi_socket_accept, |
| .sendto = eswifi_socket_sendto, |
| .recvfrom = eswifi_socket_recvfrom, |
| .setsockopt = eswifi_socket_setsockopt, |
| }; |
| |
| #ifdef CONFIG_NET_SOCKETS_OFFLOAD |
| NET_SOCKET_OFFLOAD_REGISTER(eswifi, CONFIG_NET_SOCKETS_OFFLOAD_PRIORITY, AF_UNSPEC, |
| eswifi_socket_is_supported, eswifi_socket_create); |
| #endif |
| |
| static int eswifi_off_getaddrinfo(const char *node, const char *service, |
| const struct zsock_addrinfo *hints, |
| struct zsock_addrinfo **res) |
| { |
| struct sockaddr_in *ai_addr; |
| struct zsock_addrinfo *ai; |
| unsigned long port = 0; |
| char *rsp; |
| int err; |
| |
| if (!node) { |
| return DNS_EAI_NONAME; |
| } |
| |
| if (service) { |
| port = strtol(service, NULL, 10); |
| if (port < 1 || port > USHRT_MAX) { |
| return DNS_EAI_SERVICE; |
| } |
| } |
| |
| if (!res) { |
| return DNS_EAI_NONAME; |
| } |
| |
| if (hints && hints->ai_family != AF_INET) { |
| return DNS_EAI_FAIL; |
| } |
| |
| eswifi_lock(eswifi); |
| |
| /* DNS lookup */ |
| snprintk(eswifi->buf, sizeof(eswifi->buf), "D0=%s\r", node); |
| err = eswifi_at_cmd_rsp(eswifi, eswifi->buf, &rsp); |
| if (err < 0) { |
| err = DNS_EAI_FAIL; |
| goto done_unlock; |
| } |
| |
| /* Allocate out res (addrinfo) struct. Just one. */ |
| *res = calloc(1, sizeof(struct zsock_addrinfo)); |
| ai = *res; |
| if (!ai) { |
| err = DNS_EAI_MEMORY; |
| goto done_unlock; |
| } |
| |
| /* Now, alloc the embedded sockaddr struct: */ |
| ai_addr = calloc(1, sizeof(*ai_addr)); |
| if (!ai_addr) { |
| free(*res); |
| err = DNS_EAI_MEMORY; |
| goto done_unlock; |
| } |
| |
| ai->ai_family = AF_INET; |
| ai->ai_socktype = hints ? hints->ai_socktype : SOCK_STREAM; |
| ai->ai_protocol = ai->ai_socktype == SOCK_STREAM ? IPPROTO_TCP : IPPROTO_UDP; |
| |
| ai_addr->sin_family = ai->ai_family; |
| ai_addr->sin_port = htons(port); |
| |
| if (!net_ipaddr_parse(rsp, strlen(rsp), (struct sockaddr *)ai_addr)) { |
| free(ai_addr); |
| free(*res); |
| err = DNS_EAI_FAIL; |
| goto done_unlock; |
| } |
| |
| ai->ai_addrlen = sizeof(*ai_addr); |
| ai->ai_addr = (struct sockaddr *)ai_addr; |
| err = 0; |
| |
| done_unlock: |
| eswifi_unlock(eswifi); |
| return err; |
| } |
| |
| static void eswifi_off_freeaddrinfo(struct zsock_addrinfo *res) |
| { |
| __ASSERT_NO_MSG(res); |
| |
| free(res->ai_addr); |
| free(res); |
| } |
| |
| const struct socket_dns_offload eswifi_dns_ops = { |
| .getaddrinfo = eswifi_off_getaddrinfo, |
| .freeaddrinfo = eswifi_off_freeaddrinfo, |
| }; |
| |
| int eswifi_socket_offload_init(struct eswifi_dev *leswifi) |
| { |
| eswifi = leswifi; |
| |
| socket_offload_dns_register(&eswifi_dns_ops); |
| |
| return 0; |
| } |