| Line | Branch | Exec | Source |
|---|---|---|---|
| 1 | // ====================================================================== | ||
| 2 | // \title IpSocket.cpp | ||
| 3 | // \author mstarch, crsmith | ||
| 4 | // \brief cpp file for IpSocket core implementation classes | ||
| 5 | // | ||
| 6 | // \copyright | ||
| 7 | // Copyright 2009-2020, by the California Institute of Technology. | ||
| 8 | // ALL RIGHTS RESERVED. United States Government Sponsorship | ||
| 9 | // acknowledged. | ||
| 10 | // | ||
| 11 | // ====================================================================== | ||
| 12 | #include <sys/time.h> | ||
| 13 | #include <Drv/Ip/IpSocket.hpp> | ||
| 14 | #include <Fw/FPrimeBasicTypes.hpp> | ||
| 15 | #include <Fw/Types/Assert.hpp> | ||
| 16 | #include <Fw/Types/StringUtils.hpp> | ||
| 17 | #include <cstring> | ||
| 18 | |||
| 19 | // This implementation has primarily implemented to isolate | ||
| 20 | // the socket interface from the F' Fw::Buffer class. | ||
| 21 | // There is a macro in VxWorks (m_data) that collides with | ||
| 22 | // the m_data member in Fw::Buffer. | ||
| 23 | |||
| 24 | #ifdef TGT_OS_TYPE_VXWORKS | ||
| 25 | #include <errnoLib.h> | ||
| 26 | #include <fioLib.h> | ||
| 27 | #include <hostLib.h> | ||
| 28 | #include <inetLib.h> | ||
| 29 | #include <ioLib.h> | ||
| 30 | #include <sockLib.h> | ||
| 31 | #include <socket.h> | ||
| 32 | #include <sysLib.h> | ||
| 33 | #include <taskLib.h> | ||
| 34 | #include <vxWorks.h> | ||
| 35 | #include <cstring> | ||
| 36 | #elif defined TGT_OS_TYPE_LINUX || TGT_OS_TYPE_DARWIN | ||
| 37 | #include <arpa/inet.h> | ||
| 38 | #include <sys/socket.h> | ||
| 39 | #include <unistd.h> | ||
| 40 | #include <cerrno> | ||
| 41 | #else | ||
| 42 | #error OS not supported for IP Socket Communications | ||
| 43 | #endif | ||
| 44 | |||
| 45 | namespace Drv { | ||
| 46 | |||
| 47 | 1078 | IpSocket::IpSocket() : m_timeoutSeconds(0), m_timeoutMicroseconds(0), m_port(0) { | |
| 48 |
1/2✗ Branch 2 not taken.
✓ Branch 3 taken 1078 times.
|
1078 | (void)::memset(this->m_ipv4_address, 0, sizeof(this->m_ipv4_address)); |
| 49 | 1078 | } | |
| 50 | |||
| 51 | 866 | SocketIpStatus IpSocket::configure(const char* const ipv4_address, | |
| 52 | const U16 port, | ||
| 53 | const U32 timeout_seconds, | ||
| 54 | const U32 timeout_microseconds) { | ||
| 55 | 866 | FW_ASSERT(timeout_microseconds < 1000000, static_cast<FwAssertArgType>(timeout_microseconds)); | |
| 56 | 866 | FW_ASSERT(this->isValidPort(port), static_cast<FwAssertArgType>(port)); | |
| 57 | 866 | FW_ASSERT(ipv4_address != nullptr); | |
| 58 | // Defense-in-depth: reject inputs that would have been silently truncated by string_copy. | ||
| 59 | 866 | FW_ASSERT(Fw::StringUtils::string_length(ipv4_address, static_cast<FwSizeType>(SOCKET_MAX_IPV4_ADDRESS_SIZE)) < | |
| 60 | static_cast<FwSizeType>(SOCKET_MAX_IPV4_ADDRESS_SIZE)); | ||
| 61 | 866 | this->m_timeoutSeconds = timeout_seconds; | |
| 62 | 866 | this->m_timeoutMicroseconds = timeout_microseconds; | |
| 63 | 866 | this->m_port = port; | |
| 64 | 866 | (void)Fw::StringUtils::string_copy(this->m_ipv4_address, ipv4_address, | |
| 65 | static_cast<FwSizeType>(SOCKET_MAX_IPV4_ADDRESS_SIZE)); | ||
| 66 | // Post-condition: NUL termination guaranteed by Fw::StringUtils::string_copy contract. | ||
| 67 | 866 | FW_ASSERT(this->m_ipv4_address[SOCKET_MAX_IPV4_ADDRESS_SIZE - 1] == '\0'); | |
| 68 | 866 | return SOCK_SUCCESS; | |
| 69 | } | ||
| 70 | |||
| 71 | 1996 | bool IpSocket::isValidPort(U16 port) const { | |
| 72 | 1996 | return true; | |
| 73 | } | ||
| 74 | |||
| 75 | 1693 | SocketIpStatus IpSocket::setupTimeouts(int socketFd) { | |
| 76 | // Get the IP address from host | ||
| 77 | #ifdef TGT_OS_TYPE_VXWORKS | ||
| 78 | // No timeouts set on Vxworks | ||
| 79 | #else | ||
| 80 | // Set timeout socket option | ||
| 81 | 1693 | struct timeval timeout; | |
| 82 | 1693 | timeout.tv_sec = static_cast<time_t>(this->m_timeoutSeconds); | |
| 83 | 1693 | timeout.tv_usec = static_cast<suseconds_t>(this->m_timeoutMicroseconds); | |
| 84 | // set socket write to timeout after 1 sec | ||
| 85 |
1/2✗ Branch 1 not taken.
✓ Branch 2 taken 1693 times.
|
1693 | if (setsockopt(socketFd, SOL_SOCKET, SO_SNDTIMEO, reinterpret_cast<char*>(&timeout), sizeof(timeout)) < 0) { |
| 86 | ✗ | return SOCK_FAILED_TO_SET_SOCKET_OPTIONS; | |
| 87 | } | ||
| 88 | #endif | ||
| 89 | 1693 | return SOCK_SUCCESS; | |
| 90 | } | ||
| 91 | |||
| 92 | 656 | SocketIpStatus IpSocket::setupNoSigPipe(int socketFd) { | |
| 93 | #ifdef SO_NOSIGPIPE | ||
| 94 | constexpr int enable = 1; | ||
| 95 | if (setsockopt(socketFd, SOL_SOCKET, SO_NOSIGPIPE, &enable, sizeof(enable)) < 0) { | ||
| 96 | return SOCK_FAILED_TO_SET_SOCKET_OPTIONS; | ||
| 97 | } | ||
| 98 | #else | ||
| 99 | (void)socketFd; | ||
| 100 | #endif | ||
| 101 | 656 | return SOCK_SUCCESS; | |
| 102 | } | ||
| 103 | |||
| 104 | 1903 | SocketIpStatus IpSocket::addressToIp4(const char* const ipv4_address, void* const out) { | |
| 105 | 1903 | FW_ASSERT(ipv4_address != nullptr); | |
| 106 | 1903 | FW_ASSERT(out != nullptr); | |
| 107 | // Pre-zero the destination so that on failure, callers cannot accidentally consume | ||
| 108 | // uninitialized memory. This is a defense-in-depth measure for safety-critical use. | ||
| 109 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 1903 times.
|
1903 | (void)::memset(out, 0, sizeof(struct in_addr)); |
| 110 | // Get the IP address from host | ||
| 111 | #ifdef TGT_OS_TYPE_VXWORKS | ||
| 112 | int ip = inet_addr(ipv4_address); | ||
| 113 | if (ip == ERROR) { | ||
| 114 | return SOCK_INVALID_IP_ADDRESS; | ||
| 115 | } | ||
| 116 | // from sin_addr, which has one struct | ||
| 117 | // member s_addr, which is unsigned int | ||
| 118 | *static_cast<unsigned long*>(out) = static_cast<unsigned long>(ip); | ||
| 119 | #else | ||
| 120 | // inet_pton(3) returns 1 on success, 0 if the input is not a valid IPv4 presentation | ||
| 121 | // string, and -1 (with errno=EAFNOSUPPORT) if the family argument is bogus. Only a | ||
| 122 | // strict equality check is safe — a `not` test treats -1 as success and would silently | ||
| 123 | // mask an EAFNOSUPPORT failure. (Power-of-Ten Rule 7: check every return value.) | ||
| 124 | 1903 | const int ptonStatus = ::inet_pton(AF_INET, ipv4_address, out); | |
| 125 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 1903 times.
|
1903 | if (ptonStatus != 1) { |
| 126 | ✗ | return SOCK_INVALID_IP_ADDRESS; | |
| 127 | } | ||
| 128 | #endif | ||
| 129 | 1903 | return SOCK_SUCCESS; | |
| 130 | } | ||
| 131 | |||
| 132 | 1736 | void IpSocket::close(const SocketDescriptor& socketDescriptor) { | |
| 133 |
2/2✓ Branch 1 taken 1694 times.
✓ Branch 2 taken 42 times.
|
1736 | if (socketDescriptor.fd >= 0) { |
| 134 | 1694 | (void)::close(socketDescriptor.fd); | |
| 135 | } | ||
| 136 | 1736 | } | |
| 137 | |||
| 138 | 344 | void IpSocket::shutdown(const SocketDescriptor& socketDescriptor) { | |
| 139 | 344 | errno = 0; | |
| 140 | 344 | int status = ::shutdown(socketDescriptor.fd, SHUT_RDWR); | |
| 141 | // If shutdown fails, go straight to the hard-shutdown | ||
| 142 |
2/2✓ Branch 0 taken 19 times.
✓ Branch 1 taken 325 times.
|
344 | if (status != 0) { |
| 143 | 19 | this->close(socketDescriptor); | |
| 144 | } | ||
| 145 | 344 | } | |
| 146 | |||
| 147 | 1693 | SocketIpStatus IpSocket::open(SocketDescriptor& socketDescriptor) { | |
| 148 | 1693 | SocketIpStatus status = SOCK_SUCCESS; | |
| 149 | 1693 | errno = 0; | |
| 150 | // Open a TCP socket for incoming commands, and outgoing data if not using UDP | ||
| 151 | 1693 | status = this->openProtocol(socketDescriptor); | |
| 152 |
2/2✓ Branch 0 taken 1 times.
✓ Branch 1 taken 1692 times.
|
1693 | if (status != SOCK_SUCCESS) { |
| 153 | 1 | socketDescriptor.fd = -1; | |
| 154 | 1 | return status; | |
| 155 | } | ||
| 156 | 1692 | return status; | |
| 157 | } | ||
| 158 | |||
| 159 | 1086 | SocketIpStatus IpSocket::send(const SocketDescriptor& socketDescriptor, const U8* const data, const FwSizeType size) { | |
| 160 | 1086 | FW_ASSERT((size == 0) || (data != nullptr)); | |
| 161 | // Zero-size sends are a no-op | ||
| 162 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 1086 times.
|
1086 | if (size == 0) { |
| 163 | ✗ | return SOCK_SUCCESS; | |
| 164 | } | ||
| 165 | |||
| 166 | 1086 | FwSizeType total = 0; | |
| 167 | 1086 | FwSignedSizeType sent = 0; | |
| 168 | // Attempt to send out data and retry as necessary | ||
| 169 |
3/4✓ Branch 0 taken 2167 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1087 times.
✓ Branch 3 taken 1080 times.
|
2167 | for (FwSizeType i = 0; (i < SOCKET_MAX_ITERATIONS) && (total < size); i++) { |
| 170 | 1087 | errno = 0; | |
| 171 | // Send using my specific protocol | ||
| 172 | 1087 | sent = this->sendProtocol(socketDescriptor, data + total, size - total); | |
| 173 | // Error is EINTR just try again | ||
| 174 |
4/6✓ Branch 0 taken 6 times.
✓ Branch 1 taken 1081 times.
✓ Branch 3 taken 6 times.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
✓ Branch 6 taken 1087 times.
|
1087 | if (((sent == -1) && (errno == EINTR)) || (sent == 0)) { |
| 175 | ✗ | continue; | |
| 176 | } | ||
| 177 | // A bad file descriptor, a reset, or a broken pipe (the peer closed the connection) is a disconnect | ||
| 178 |
6/8✓ Branch 0 taken 6 times.
✓ Branch 1 taken 1081 times.
✓ Branch 3 taken 6 times.
✗ Branch 4 not taken.
✓ Branch 6 taken 6 times.
✗ Branch 7 not taken.
✓ Branch 9 taken 3 times.
✓ Branch 10 taken 3 times.
|
1087 | else if ((sent == -1) && ((errno == EBADF) || (errno == ECONNRESET) || (errno == EPIPE))) { |
| 179 | 3 | return SOCK_DISCONNECTED; | |
| 180 | } | ||
| 181 | // Error is a send timeout (SO_SNDTIMEO) or would-block, recoverable by a caller retry | ||
| 182 |
5/6✓ Branch 0 taken 3 times.
✓ Branch 1 taken 1081 times.
✓ Branch 3 taken 1 times.
✓ Branch 4 taken 2 times.
✗ Branch 6 not taken.
✓ Branch 7 taken 1 times.
|
1084 | else if ((sent == -1) && ((errno == EAGAIN) || (errno == EWOULDBLOCK))) { |
| 183 | 2 | return SOCK_INTERRUPTED_TRY_AGAIN; | |
| 184 | } | ||
| 185 | // Error returned, and it wasn't an interrupt, a timeout, nor a disconnect | ||
| 186 |
2/2✓ Branch 0 taken 1 times.
✓ Branch 1 taken 1081 times.
|
1082 | else if (sent == -1) { |
| 187 | 1 | return SOCK_SEND_ERROR; | |
| 188 | } | ||
| 189 | 1081 | FW_ASSERT(sent > 0, static_cast<FwAssertArgType>(sent)); | |
| 190 | 1081 | total += static_cast<FwSizeType>(sent); | |
| 191 | } | ||
| 192 | // Failed to retry enough to send all data | ||
| 193 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 1080 times.
|
1080 | if (total < size) { |
| 194 | ✗ | return SOCK_INTERRUPTED_TRY_AGAIN; | |
| 195 | } | ||
| 196 | // Ensure we sent everything | ||
| 197 | 1080 | FW_ASSERT(total == size, static_cast<FwAssertArgType>(total), static_cast<FwAssertArgType>(size)); | |
| 198 | 1080 | return SOCK_SUCCESS; | |
| 199 | } | ||
| 200 | |||
| 201 | 1527 | SocketIpStatus IpSocket::recv(const SocketDescriptor& socketDescriptor, U8* data, FwSizeType& req_read) { | |
| 202 | // TODO: Uncomment FW_ASSERT for socketDescriptor.fd once we fix TcpClientTester to not pass in uninitialized | ||
| 203 | // socketDescriptor | ||
| 204 | // FW_ASSERT(socketDescriptor.fd != -1, static_cast<FwAssertArgType>(socketDescriptor.fd)); | ||
| 205 | 1527 | FW_ASSERT(data != nullptr); | |
| 206 | |||
| 207 | FwSignedSizeType bytes_received_or_status; // Stores the return value from recvProtocol | ||
| 208 | |||
| 209 | // Loop primarily for EINTR. Other conditions should lead to an earlier exit. | ||
| 210 |
1/2✓ Branch 0 taken 1528 times.
✗ Branch 1 not taken.
|
1528 | for (FwSizeType i = 0; i < SOCKET_MAX_ITERATIONS; i++) { |
| 211 | 1528 | errno = 0; | |
| 212 | // Pass the current value of req_read (max buffer size) to recvProtocol. | ||
| 213 | // recvProtocol returns bytes read or -1 on error. | ||
| 214 | 1528 | bytes_received_or_status = this->recvProtocol(socketDescriptor, data, req_read); | |
| 215 | |||
| 216 |
2/2✓ Branch 0 taken 1079 times.
✓ Branch 1 taken 449 times.
|
1528 | if (bytes_received_or_status > 0) { |
| 217 | // Successfully read data | ||
| 218 | 1079 | req_read = static_cast<FwSizeType>(bytes_received_or_status); | |
| 219 | 1079 | return SOCK_SUCCESS; | |
| 220 |
2/2✓ Branch 0 taken 337 times.
✓ Branch 1 taken 112 times.
|
449 | } else if (bytes_received_or_status == 0) { |
| 221 | // Handle zero return based on protocol-specific behavior | ||
| 222 | 337 | req_read = 0; | |
| 223 | 337 | return this->handleZeroReturn(); | |
| 224 | } else { // bytes_received_or_status == -1, an error occurred | ||
| 225 |
3/4✓ Branch 1 taken 3 times.
✓ Branch 2 taken 109 times.
✗ Branch 4 not taken.
✓ Branch 5 taken 3 times.
|
112 | if ((errno == EAGAIN) || (errno == EWOULDBLOCK)) { |
| 226 | // Non-blocking socket would block, or SO_RCVTIMEO timeout occurred. | ||
| 227 | 109 | req_read = 0; | |
| 228 | 109 | return SOCK_NO_DATA_AVAILABLE; | |
| 229 |
3/4✓ Branch 1 taken 3 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 2 times.
✓ Branch 5 taken 1 times.
|
3 | } else if ((errno == ECONNRESET) || (errno == EBADF)) { |
| 230 | // Connection reset or bad file descriptor. | ||
| 231 | 2 | req_read = 0; | |
| 232 | 2 | return SOCK_DISCONNECTED; // Or a more specific error like SOCK_READ_ERROR | |
| 233 |
1/2✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
|
1 | } else if (errno == EINTR) { |
| 234 | 1 | continue; | |
| 235 | } else { | ||
| 236 | // Other socket read error. | ||
| 237 | ✗ | req_read = 0; | |
| 238 | ✗ | return SOCK_READ_ERROR; | |
| 239 | } | ||
| 240 | } | ||
| 241 | } | ||
| 242 | // If the loop completes, it means SOCKET_MAX_ITERATIONS of EINTR occurred. | ||
| 243 | ✗ | req_read = 0; | |
| 244 | ✗ | return SOCK_INTERRUPTED_TRY_AGAIN; | |
| 245 | } | ||
| 246 | |||
| 247 | 334 | SocketIpStatus IpSocket::handleZeroReturn() { | |
| 248 | // For TCP (which IpSocket primarily serves as a base for, or when not overridden), | ||
| 249 | // a return of 0 from ::recv means the peer has performed an orderly shutdown. | ||
| 250 | 334 | return SOCK_DISCONNECTED; | |
| 251 | } | ||
| 252 | |||
| 253 | 1177 | SocketIpStatus IpSocket::setupSocketOptions(int socketFd) { | |
| 254 | // Iterate over the socket options and set them | ||
| 255 |
2/2✓ Branch 3 taken 1177 times.
✓ Branch 4 taken 1177 times.
|
2354 | for (const auto& options : IP_SOCKET_OPTIONS) { |
| 256 | 1177 | int status = 0; | |
| 257 |
2/4✗ Branch 1 not taken.
✓ Branch 2 taken 1177 times.
✓ Branch 3 taken 1177 times.
✗ Branch 4 not taken.
|
1177 | if (options.type == SOCK_OPT_INT) { |
| 258 | 1177 | status = setsockopt(socketFd, options.level, options.option, &options.value.intVal, | |
| 259 | sizeof(options.value.intVal)); | ||
| 260 | } else { | ||
| 261 | ✗ | status = setsockopt(socketFd, options.level, options.option, &options.value.sizeVal, | |
| 262 | sizeof(options.value.sizeVal)); | ||
| 263 | } | ||
| 264 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 1177 times.
|
1177 | if (status) { |
| 265 | ✗ | return SOCK_FAILED_TO_SET_SOCKET_OPTIONS; | |
| 266 | } | ||
| 267 | } | ||
| 268 | 1177 | return SOCK_SUCCESS; | |
| 269 | } | ||
| 270 | |||
| 271 | } // namespace Drv | ||
| 272 |