summaryrefslogtreecommitdiffstats
path: root/libc-bottom-half/sources/send.c
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-06-26 16:08:04 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-06-26 16:08:04 +0000
commit41927c28dd5030318be5cf71f515449d55b2802b (patch)
tree41f7cef3a417fee1ee171cdb4f2709f4372ae4f3 /libc-bottom-half/sources/send.c
parentReleasing progress-linux version 0.0~git20230821.ec4566b-2~progress7.99u1. (diff)
downloadwasi-libc-41927c28dd5030318be5cf71f515449d55b2802b.tar.xz
wasi-libc-41927c28dd5030318be5cf71f515449d55b2802b.zip
Merging upstream version 0.0~git20240411.9e8c542.
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'libc-bottom-half/sources/send.c')
-rw-r--r--libc-bottom-half/sources/send.c249
1 files changed, 249 insertions, 0 deletions
diff --git a/libc-bottom-half/sources/send.c b/libc-bottom-half/sources/send.c
new file mode 100644
index 0000000..42f653b
--- /dev/null
+++ b/libc-bottom-half/sources/send.c
@@ -0,0 +1,249 @@
+#include <sys/socket.h>
+
+#include <errno.h>
+
+#include <wasi/api.h>
+#include <wasi/descriptor_table.h>
+#include <wasi/sockets_utils.h>
+
+static ssize_t tcp_sendto(tcp_socket_t *socket, const uint8_t *buffer,
+ size_t length, int flags, const struct sockaddr *addr,
+ socklen_t addrlen)
+{
+ const int supported_flags = MSG_DONTWAIT | MSG_NOSIGNAL;
+ if ((flags & supported_flags) != flags) {
+ errno = EOPNOTSUPP;
+ return -1;
+ }
+
+ if (addr != NULL || addrlen != 0) {
+ errno = EISCONN;
+ return -1;
+ }
+
+ tcp_socket_state_connected_t connection;
+ if (socket->state.tag == TCP_SOCKET_STATE_CONNECTED) {
+ connection = socket->state.connected;
+ } else {
+ errno = ENOTCONN;
+ return -1;
+ }
+
+ bool should_block = socket->blocking;
+ if ((flags & MSG_DONTWAIT) != 0) {
+ should_block = false;
+ }
+
+ if ((flags & MSG_NOSIGNAL) != 0) {
+ // Ignore it. WASI has no Unix-style signals. So effectively,
+ // MSG_NOSIGNAL is always the case, whether it was explicitly
+ // requested or not.
+ }
+
+ streams_borrow_output_stream_t tx_borrow =
+ streams_borrow_output_stream(connection.output);
+ while (true) {
+ streams_stream_error_t error;
+ uint64_t count;
+ if (!streams_method_output_stream_check_write(tx_borrow, &count,
+ &error)) {
+ // TODO wasi-sockets: wasi-sockets has no way to recover stream errors yet.
+ errno = EPIPE;
+ return -1;
+ }
+
+ if (count) {
+ count = count < length ? count : length;
+ wasip2_list_u8_t list = { .ptr = (uint8_t *)buffer,
+ .len = count };
+ if (!streams_method_output_stream_write(
+ tx_borrow, &list, &error)) {
+ // TODO wasi-sockets: wasi-sockets has no way to recover TCP stream errors yet.
+ errno = EPIPE;
+ return -1;
+ } else {
+ return count;
+ }
+ } else if (should_block) {
+ poll_borrow_pollable_t pollable_borrow =
+ poll_borrow_pollable(
+ connection.output_pollable);
+ poll_method_pollable_block(pollable_borrow);
+ } else {
+ errno = EWOULDBLOCK;
+ return -1;
+ }
+ }
+}
+
+static ssize_t udp_sendto(udp_socket_t *socket, const uint8_t *buffer,
+ size_t length, int flags, const struct sockaddr *addr,
+ socklen_t addrlen)
+{
+ const int supported_flags = MSG_DONTWAIT;
+ if ((flags & supported_flags) != flags) {
+ errno = EOPNOTSUPP;
+ return -1;
+ }
+
+ network_ip_socket_address_t remote_address;
+ bool has_remote_address = (addr != NULL);
+
+ if (has_remote_address) {
+ if (socket->state.tag == UDP_SOCKET_STATE_CONNECTED) {
+ errno = EISCONN;
+ return -1;
+ }
+
+ int parse_err;
+ if (!__wasi_sockets_utils__parse_address(
+ socket->family, addr, addrlen, &remote_address,
+ &parse_err)) {
+ errno = parse_err;
+ return -1;
+ }
+ } else {
+ if (addrlen != 0) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ if (socket->state.tag != UDP_SOCKET_STATE_CONNECTED) {
+ errno = EDESTADDRREQ;
+ return -1;
+ }
+ }
+
+ network_error_code_t error;
+ udp_borrow_udp_socket_t socket_borrow =
+ udp_borrow_udp_socket(socket->socket);
+
+ udp_socket_streams_t streams;
+ switch (socket->state.tag) {
+ case UDP_SOCKET_STATE_UNBOUND: {
+ // Socket is not explicitly bound by the user. We'll do it for them:
+
+ network_ip_socket_address_t any =
+ __wasi_sockets_utils__any_addr(socket->family);
+ int result = __wasi_sockets_utils__udp_bind(socket, &any);
+ if (result != 0) {
+ return result;
+ }
+
+ if (!__wasi_sockets_utils__stream(socket, NULL, &streams,
+ &error)) {
+ errno = __wasi_sockets_utils__map_error(error);
+ return -1;
+ }
+ break;
+ }
+ case UDP_SOCKET_STATE_BOUND_NOSTREAMS: {
+ if (!__wasi_sockets_utils__stream(socket, NULL, &streams,
+ &error)) {
+ errno = __wasi_sockets_utils__map_error(error);
+ return -1;
+ }
+ break;
+ }
+ case UDP_SOCKET_STATE_BOUND_STREAMING:
+ streams = socket->state.bound_streaming.streams;
+ break;
+
+ case UDP_SOCKET_STATE_CONNECTED:
+ streams = socket->state.connected.streams;
+ break;
+
+ default: /* unreachable */
+ abort();
+ }
+
+ bool should_block = socket->blocking;
+ if ((flags & MSG_DONTWAIT) != 0) {
+ should_block = false;
+ }
+
+ udp_outgoing_datagram_t datagrams[1] = {{
+ .remote_address = {
+ .is_some = has_remote_address,
+ .val = remote_address,
+ },
+ .data = {
+ .len = length,
+ .ptr = (uint8_t*)buffer,
+ },
+ }};
+ udp_list_outgoing_datagram_t list = {
+ .len = 1,
+ .ptr = datagrams,
+ };
+
+ udp_borrow_outgoing_datagram_stream_t outgoing_borrow =
+ udp_borrow_outgoing_datagram_stream(streams.outgoing);
+ while (true) {
+ uint64_t allowed;
+ if (!udp_method_outgoing_datagram_stream_check_send(
+ outgoing_borrow, &allowed, &error)) {
+ errno = __wasi_sockets_utils__map_error(error);
+ return -1;
+ }
+
+ if (allowed) {
+ uint64_t datagrams_sent;
+ if (!udp_method_outgoing_datagram_stream_send(
+ outgoing_borrow, &list, &datagrams_sent,
+ &error)) {
+ errno = __wasi_sockets_utils__map_error(error);
+ return -1;
+ }
+
+ if (datagrams_sent != 0 && datagrams_sent != 1) {
+ abort();
+ }
+
+ if (datagrams_sent == 1) {
+ return length;
+ }
+ }
+
+ if (should_block) {
+ poll_borrow_pollable_t pollable_borrow =
+ poll_borrow_pollable(streams.outgoing_pollable);
+ poll_method_pollable_block(pollable_borrow);
+ } else {
+ errno = EWOULDBLOCK;
+ return -1;
+ }
+ }
+}
+
+ssize_t send(int socket, const void *buffer, size_t length, int flags)
+{
+ return sendto(socket, buffer, length, flags, NULL, 0);
+}
+
+ssize_t sendto(int socket, const void *buffer, size_t length, int flags,
+ const struct sockaddr *addr, socklen_t addrlen)
+{
+ descriptor_table_entry_t *entry;
+ if (!descriptor_table_get_ref(socket, &entry)) {
+ errno = EBADF;
+ return -1;
+ }
+
+ if (buffer == NULL) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ switch (entry->tag) {
+ case DESCRIPTOR_TABLE_ENTRY_TCP_SOCKET:
+ return tcp_sendto(&entry->tcp_socket, buffer, length, flags,
+ addr, addrlen);
+ case DESCRIPTOR_TABLE_ENTRY_UDP_SOCKET:
+ return udp_sendto(&entry->udp_socket, buffer, length, flags,
+ addr, addrlen);
+ default:
+ errno = EOPNOTSUPP;
+ return -1;
+ }
+}