| 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 | 1069 | IpSocket::IpSocket() : m_timeoutSeconds(0), m_timeoutMicroseconds(0), m_port(0) { | |
| 48 |
1/2✗ Branch 2 not taken.
✓ Branch 3 taken 1069 times.
|
1069 | (void)::memset(this->m_ipv4_address, 0, sizeof(this->m_ipv4_address)); |
| 49 | 1069 | } | |
| 50 | |||
| 51 | 862 | SocketIpStatus IpSocket::configure(const char* const ipv4_address, | |
| 52 | const U16 port, | ||
| 53 | const U32 timeout_seconds, | ||
| 54 | const U32 timeout_microseconds) { | ||
| 55 | 862 | FW_ASSERT(timeout_microseconds < 1000000, static_cast<FwAssertArgType>(timeout_microseconds)); | |
| 56 | 862 | FW_ASSERT(this->isValidPort(port), static_cast<FwAssertArgType>(port)); | |
| 57 | 862 | FW_ASSERT(ipv4_address != nullptr); | |
| 58 | // Defense-in-depth: reject inputs that would have been silently truncated by string_copy. | ||
| 59 | 862 | 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 | 862 | this->m_timeoutSeconds = timeout_seconds; | |
| 62 | 862 | this->m_timeoutMicroseconds = timeout_microseconds; | |
| 63 | 862 | this->m_port = port; | |
| 64 | 862 | (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 | 862 | FW_ASSERT(this->m_ipv4_address[SOCKET_MAX_IPV4_ADDRESS_SIZE - 1] == '\0'); | |
| 68 | 862 | return SOCK_SUCCESS; | |
| 69 | } | ||
| 70 | |||
| 71 | 1994 | bool IpSocket::isValidPort(U16 port) const { | |
| 72 | 1994 | return true; | |
| 73 | } | ||
| 74 | |||
| 75 | 1689 | 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 | 1689 | struct timeval timeout; | |
| 82 | 1689 | timeout.tv_sec = static_cast<time_t>(this->m_timeoutSeconds); | |
| 83 | 1689 | 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 1689 times.
|
1689 | 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 | 1689 | return SOCK_SUCCESS; | |
| 90 | } | ||
| 91 | |||
| 92 | 1899 | SocketIpStatus IpSocket::addressToIp4(const char* const ipv4_address, void* const out) { | |
| 93 | 1899 | FW_ASSERT(ipv4_address != nullptr); | |
| 94 | 1899 | FW_ASSERT(out != nullptr); | |
| 95 | // Pre-zero the destination so that on failure, callers cannot accidentally consume | ||
| 96 | // uninitialized memory. This is a defense-in-depth measure for safety-critical use. | ||
| 97 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 1899 times.
|
1899 | (void)::memset(out, 0, sizeof(struct in_addr)); |
| 98 | // Get the IP address from host | ||
| 99 | #ifdef TGT_OS_TYPE_VXWORKS | ||
| 100 | int ip = inet_addr(ipv4_address); | ||
| 101 | if (ip == ERROR) { | ||
| 102 | return SOCK_INVALID_IP_ADDRESS; | ||
| 103 | } | ||
| 104 | // from sin_addr, which has one struct | ||
| 105 | // member s_addr, which is unsigned int | ||
| 106 | *static_cast<unsigned long*>(out) = static_cast<unsigned long>(ip); | ||
| 107 | #else | ||
| 108 | // inet_pton(3) returns 1 on success, 0 if the input is not a valid IPv4 presentation | ||
| 109 | // string, and -1 (with errno=EAFNOSUPPORT) if the family argument is bogus. Only a | ||
| 110 | // strict equality check is safe — a `not` test treats -1 as success and would silently | ||
| 111 | // mask an EAFNOSUPPORT failure. (Power-of-Ten Rule 7: check every return value.) | ||
| 112 | 1899 | const int ptonStatus = ::inet_pton(AF_INET, ipv4_address, out); | |
| 113 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 1899 times.
|
1899 | if (ptonStatus != 1) { |
| 114 | ✗ | return SOCK_INVALID_IP_ADDRESS; | |
| 115 | } | ||
| 116 | #endif | ||
| 117 | 1899 | return SOCK_SUCCESS; | |
| 118 | } | ||
| 119 | |||
| 120 | 1732 | void IpSocket::close(const SocketDescriptor& socketDescriptor) { | |
| 121 |
2/2✓ Branch 1 taken 1690 times.
✓ Branch 2 taken 42 times.
|
1732 | if (socketDescriptor.fd >= 0) { |
| 122 | 1690 | (void)::close(socketDescriptor.fd); | |
| 123 | } | ||
| 124 | 1732 | } | |
| 125 | |||
| 126 | 344 | void IpSocket::shutdown(const SocketDescriptor& socketDescriptor) { | |
| 127 | 344 | errno = 0; | |
| 128 | 344 | int status = ::shutdown(socketDescriptor.fd, SHUT_RDWR); | |
| 129 | // If shutdown fails, go straight to the hard-shutdown | ||
| 130 |
2/2✓ Branch 0 taken 19 times.
✓ Branch 1 taken 325 times.
|
344 | if (status != 0) { |
| 131 | 19 | this->close(socketDescriptor); | |
| 132 | } | ||
| 133 | 344 | } | |
| 134 | |||
| 135 | 1689 | SocketIpStatus IpSocket::open(SocketDescriptor& socketDescriptor) { | |
| 136 | 1689 | SocketIpStatus status = SOCK_SUCCESS; | |
| 137 | 1689 | errno = 0; | |
| 138 | // Open a TCP socket for incoming commands, and outgoing data if not using UDP | ||
| 139 | 1689 | status = this->openProtocol(socketDescriptor); | |
| 140 |
2/2✓ Branch 0 taken 1 times.
✓ Branch 1 taken 1688 times.
|
1689 | if (status != SOCK_SUCCESS) { |
| 141 | 1 | socketDescriptor.fd = -1; | |
| 142 | 1 | return status; | |
| 143 | } | ||
| 144 | 1688 | return status; | |
| 145 | } | ||
| 146 | |||
| 147 | 1078 | SocketIpStatus IpSocket::send(const SocketDescriptor& socketDescriptor, const U8* const data, const FwSizeType size) { | |
| 148 | 1078 | FW_ASSERT((size == 0) || (data != nullptr)); | |
| 149 | // Zero-size sends are a no-op | ||
| 150 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 1078 times.
|
1078 | if (size == 0) { |
| 151 | ✗ | return SOCK_SUCCESS; | |
| 152 | } | ||
| 153 | |||
| 154 | 1078 | FwSizeType total = 0; | |
| 155 | 1078 | FwSignedSizeType sent = 0; | |
| 156 | // Attempt to send out data and retry as necessary | ||
| 157 |
3/4✓ Branch 0 taken 2156 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1078 times.
✓ Branch 3 taken 1078 times.
|
2156 | for (FwSizeType i = 0; (i < SOCKET_MAX_ITERATIONS) && (total < size); i++) { |
| 158 | 1078 | errno = 0; | |
| 159 | // Send using my specific protocol | ||
| 160 | 1078 | sent = this->sendProtocol(socketDescriptor, data + total, size - total); | |
| 161 | // Error is EINTR or timeout just try again | ||
| 162 |
2/6✗ Branch 0 not taken.
✓ Branch 1 taken 1078 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
✓ Branch 6 taken 1078 times.
|
1078 | if (((sent == -1) && (errno == EINTR)) || (sent == 0)) { |
| 163 | ✗ | continue; | |
| 164 | } | ||
| 165 | // Error bad file descriptor is a close along with reset | ||
| 166 |
1/6✗ Branch 0 not taken.
✓ Branch 1 taken 1078 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✗ Branch 6 not taken.
✗ Branch 7 not taken.
|
1078 | else if ((sent == -1) && ((errno == EBADF) || (errno == ECONNRESET))) { |
| 167 | ✗ | return SOCK_DISCONNECTED; | |
| 168 | } | ||
| 169 | // Error returned, and it wasn't an interrupt nor a disconnect | ||
| 170 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 1078 times.
|
1078 | else if (sent == -1) { |
| 171 | ✗ | return SOCK_SEND_ERROR; | |
| 172 | } | ||
| 173 | 1078 | FW_ASSERT(sent > 0, static_cast<FwAssertArgType>(sent)); | |
| 174 | 1078 | total += static_cast<FwSizeType>(sent); | |
| 175 | } | ||
| 176 | // Failed to retry enough to send all data | ||
| 177 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 1078 times.
|
1078 | if (total < size) { |
| 178 | ✗ | return SOCK_INTERRUPTED_TRY_AGAIN; | |
| 179 | } | ||
| 180 | // Ensure we sent everything | ||
| 181 | 1078 | FW_ASSERT(total == size, static_cast<FwAssertArgType>(total), static_cast<FwAssertArgType>(size)); | |
| 182 | 1078 | return SOCK_SUCCESS; | |
| 183 | } | ||
| 184 | |||
| 185 | 1527 | SocketIpStatus IpSocket::recv(const SocketDescriptor& socketDescriptor, U8* data, FwSizeType& req_read) { | |
| 186 | // TODO: Uncomment FW_ASSERT for socketDescriptor.fd once we fix TcpClientTester to not pass in uninitialized | ||
| 187 | // socketDescriptor | ||
| 188 | // FW_ASSERT(socketDescriptor.fd != -1, static_cast<FwAssertArgType>(socketDescriptor.fd)); | ||
| 189 | 1527 | FW_ASSERT(data != nullptr); | |
| 190 | |||
| 191 | FwSignedSizeType bytes_received_or_status; // Stores the return value from recvProtocol | ||
| 192 | |||
| 193 | // Loop primarily for EINTR. Other conditions should lead to an earlier exit. | ||
| 194 |
1/2✓ Branch 0 taken 1528 times.
✗ Branch 1 not taken.
|
1528 | for (FwSizeType i = 0; i < SOCKET_MAX_ITERATIONS; i++) { |
| 195 | 1528 | errno = 0; | |
| 196 | // Pass the current value of req_read (max buffer size) to recvProtocol. | ||
| 197 | // recvProtocol returns bytes read or -1 on error. | ||
| 198 | 1528 | bytes_received_or_status = this->recvProtocol(socketDescriptor, data, req_read); | |
| 199 | |||
| 200 |
2/2✓ Branch 0 taken 1079 times.
✓ Branch 1 taken 449 times.
|
1528 | if (bytes_received_or_status > 0) { |
| 201 | // Successfully read data | ||
| 202 | 1079 | req_read = static_cast<FwSizeType>(bytes_received_or_status); | |
| 203 | 1079 | return SOCK_SUCCESS; | |
| 204 |
2/2✓ Branch 0 taken 337 times.
✓ Branch 1 taken 112 times.
|
449 | } else if (bytes_received_or_status == 0) { |
| 205 | // Handle zero return based on protocol-specific behavior | ||
| 206 | 337 | req_read = 0; | |
| 207 | 337 | return this->handleZeroReturn(); | |
| 208 | } else { // bytes_received_or_status == -1, an error occurred | ||
| 209 |
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)) { |
| 210 | // Non-blocking socket would block, or SO_RCVTIMEO timeout occurred. | ||
| 211 | 109 | req_read = 0; | |
| 212 | 109 | return SOCK_NO_DATA_AVAILABLE; | |
| 213 |
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)) { |
| 214 | // Connection reset or bad file descriptor. | ||
| 215 | 2 | req_read = 0; | |
| 216 | 2 | return SOCK_DISCONNECTED; // Or a more specific error like SOCK_READ_ERROR | |
| 217 |
1/2✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
|
1 | } else if (errno == EINTR) { |
| 218 | 1 | continue; | |
| 219 | } else { | ||
| 220 | // Other socket read error. | ||
| 221 | ✗ | req_read = 0; | |
| 222 | ✗ | return SOCK_READ_ERROR; | |
| 223 | } | ||
| 224 | } | ||
| 225 | } | ||
| 226 | // If the loop completes, it means SOCKET_MAX_ITERATIONS of EINTR occurred. | ||
| 227 | ✗ | req_read = 0; | |
| 228 | ✗ | return SOCK_INTERRUPTED_TRY_AGAIN; | |
| 229 | } | ||
| 230 | |||
| 231 | 334 | SocketIpStatus IpSocket::handleZeroReturn() { | |
| 232 | // For TCP (which IpSocket primarily serves as a base for, or when not overridden), | ||
| 233 | // a return of 0 from ::recv means the peer has performed an orderly shutdown. | ||
| 234 | 334 | return SOCK_DISCONNECTED; | |
| 235 | } | ||
| 236 | |||
| 237 | 1173 | SocketIpStatus IpSocket::setupSocketOptions(int socketFd) { | |
| 238 | // Iterate over the socket options and set them | ||
| 239 |
2/2✓ Branch 3 taken 1173 times.
✓ Branch 4 taken 1173 times.
|
2346 | for (const auto& options : IP_SOCKET_OPTIONS) { |
| 240 | 1173 | int status = 0; | |
| 241 |
2/4✗ Branch 1 not taken.
✓ Branch 2 taken 1173 times.
✓ Branch 3 taken 1173 times.
✗ Branch 4 not taken.
|
1173 | if (options.type == SOCK_OPT_INT) { |
| 242 | 1173 | status = setsockopt(socketFd, options.level, options.option, &options.value.intVal, | |
| 243 | sizeof(options.value.intVal)); | ||
| 244 | } else { | ||
| 245 | ✗ | status = setsockopt(socketFd, options.level, options.option, &options.value.sizeVal, | |
| 246 | sizeof(options.value.sizeVal)); | ||
| 247 | } | ||
| 248 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 1173 times.
|
1173 | if (status) { |
| 249 | ✗ | return SOCK_FAILED_TO_SET_SOCKET_OPTIONS; | |
| 250 | } | ||
| 251 | } | ||
| 252 | 1173 | return SOCK_SUCCESS; | |
| 253 | } | ||
| 254 | |||
| 255 | } // namespace Drv | ||
| 256 |