| // Copyright 2018 The Fuchsia Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| #include <assert.h> |
| #include <fcntl.h> |
| #include <sys/socket.h> |
| #include <unistd.h> |
| |
| #include <lib/fdio/util.h> |
| #include <unittest/unittest.h> |
| #include <zircon/processargs.h> |
| #include <zircon/syscalls.h> |
| #include <zircon/types.h> |
| |
| static bool create_socket_fdio_pair(zx_handle_t* socket_out, int* fd_out) { |
| // Create new socket pair. |
| zx_handle_t s1, s2; |
| ASSERT_EQ(ZX_OK, zx_socket_create(ZX_SOCKET_STREAM | ZX_SOCKET_HAS_CONTROL, &s1, &s2), "Socket create failed"); |
| |
| // Convert one socket to FDIO |
| uint32_t type = PA_FDIO_SOCKET; |
| int fd; |
| ASSERT_EQ(ZX_OK, fdio_create_fd(&s2, &type, 1, &fd), "Socket from handle failed"); |
| |
| *fd_out = fd; |
| *socket_out = s1; |
| |
| return true; |
| } |
| |
| static bool set_nonblocking_io(int fd) { |
| int flags = fcntl(fd, F_GETFL); |
| EXPECT_NE(-1, flags, "fcntl failed"); |
| EXPECT_NE(-1, fcntl(fd, F_SETFL, flags | O_NONBLOCK), "Set NONBLOCK failed"); |
| return true; |
| } |
| |
| // Verify scenario, where multi-segment recvmsg is requested, but the socket has |
| // just enough data to *completely* fill one segment. |
| // In this scenario, an attempt to read data for the next segment immediately |
| // fails with ZX_ERR_SHOULD_WAIT, and this may lead to bogus EAGAIN even if some |
| // data has actually been read. |
| bool socket_recvmsg_nonblock_boundary_test(void) { |
| BEGIN_TEST; |
| |
| zx_handle_t s; |
| int fd; |
| |
| if (!create_socket_fdio_pair(&s, &fd) || !set_nonblocking_io(fd)) { |
| return false; |
| } |
| |
| // Write 4 bytes of data to socket. |
| size_t actual; |
| const uint32_t data_out = 0x12345678; |
| EXPECT_EQ(ZX_OK, zx_socket_write(s, 0, &data_out, sizeof(data_out), &actual), "Socket write failed"); |
| EXPECT_EQ(sizeof(data_out), actual, "Socket write length mismatch"); |
| |
| uint32_t data_in1, data_in2; |
| // Fail at compilation stage if anyone changes types. |
| // This is mandatory here: we need the first chunk to be exactly the same |
| // length as total size of data we just wrote. |
| assert(sizeof(data_in1) == sizeof(data_out)); |
| |
| struct iovec iov[2]; |
| iov[0].iov_base = &data_in1; |
| iov[0].iov_len = sizeof(data_in1); |
| iov[1].iov_base = &data_in2; |
| iov[1].iov_len = sizeof(data_in2); |
| |
| struct msghdr msg; |
| msg.msg_name = NULL; |
| msg.msg_namelen = 0; |
| msg.msg_iov = iov; |
| msg.msg_iovlen = sizeof(iov) / sizeof(*iov); |
| msg.msg_control = NULL; |
| msg.msg_controllen = 0; |
| msg.msg_flags = 0; |
| |
| actual = recvmsg(fd, &msg, 0); |
| EXPECT_EQ(4u, actual, "Socket read failed"); |
| |
| zx_handle_close(s); |
| close(fd); |
| END_TEST; |
| } |
| |
| // Verify scenario, where multi-segment sendmsg is requested, but the socket has |
| // just enough spare buffer to *completely* read one segment. |
| // In this scenario, an attempt to send second segment should immediately fail |
| // with ZX_ERR_SHOULD_WAIT, but the sendmsg should report first segment length |
| // rather than failing with EAGAIN. |
| bool socket_sendmsg_nonblock_boundary_test(void) { |
| BEGIN_TEST; |
| |
| const size_t memlength = 65536; |
| void* memchunk = malloc(memlength); |
| |
| struct iovec iov[2]; |
| iov[0].iov_base = memchunk; |
| iov[0].iov_len = memlength; |
| iov[1].iov_base = memchunk; |
| iov[1].iov_len = memlength; |
| |
| zx_handle_t s; |
| int fd; |
| |
| if (!create_socket_fdio_pair(&s, &fd) || !set_nonblocking_io(fd)) { |
| return false; |
| } |
| |
| struct msghdr msg; |
| msg.msg_name = NULL; |
| msg.msg_namelen = 0; |
| msg.msg_iov = iov; |
| msg.msg_iovlen = sizeof(iov) / sizeof(*iov); |
| msg.msg_control = NULL; |
| msg.msg_controllen = 0; |
| msg.msg_flags = 0; |
| |
| // 1. Keep sending data until socket can take no more. |
| while (sendmsg(fd, &msg, 0) > 0) |
| ; |
| |
| // 2. Consume one segment of the data |
| size_t actual = 0; |
| zx_socket_read(s, 0, memchunk, memlength, &actual); |
| EXPECT_EQ(memlength, actual, "Failed to read from a full socket"); |
| |
| // 3. Push again 2 packets of <memlength> bytes, observe only one sent. |
| EXPECT_EQ((ssize_t)memlength, sendmsg(fd, &msg, 0), |
| "Partial sendmsg failed; is the socket buffer varying?"); |
| |
| zx_handle_close(s); |
| close(fd); |
| free(memchunk); |
| END_TEST; |
| } |
| |
| BEGIN_TEST_CASE(newsocket_tests) |
| RUN_TEST(socket_recvmsg_nonblock_boundary_test) |
| RUN_TEST(socket_sendmsg_nonblock_boundary_test) |
| END_TEST_CASE(newsocket_tests) |