GCC Code Coverage Report


Directory: Drv/Ip/
File: IpSocket.cpp
Date: 2026-10-14 21:15:48
Exec Total Coverage
Lines: 90 108 83.3%
Functions: 13 13 100.0%
Branches: 44 68 64.7%

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 724 IpSocket::IpSocket() : m_timeoutSeconds(0), m_timeoutMicroseconds(0), m_port(0) {
48
1/2
✗ Branch 2 not taken.
✓ Branch 3 taken 724 times.
724 (void)::memset(this->m_ipv4_address, 0, sizeof(this->m_ipv4_address));
49 724 }
50
51 616 SocketIpStatus IpSocket::configure(const char* const ipv4_address,
52 const U16 port,
53 const U32 timeout_seconds,
54 const U32 timeout_microseconds) {
55 616 FW_ASSERT(timeout_microseconds < 1000000, static_cast<FwAssertArgType>(timeout_microseconds));
56 616 FW_ASSERT(this->isValidPort(port), static_cast<FwAssertArgType>(port));
57 616 FW_ASSERT(ipv4_address != nullptr);
58 // Defense-in-depth: reject inputs that would have been silently truncated by string_copy.
59 616 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 616 this->m_timeoutSeconds = timeout_seconds;
62 616 this->m_timeoutMicroseconds = timeout_microseconds;
63 616 this->m_port = port;
64 616 (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 616 FW_ASSERT(this->m_ipv4_address[SOCKET_MAX_IPV4_ADDRESS_SIZE - 1] == '\0');
68 616 return SOCK_SUCCESS;
69 }
70
71 1531 bool IpSocket::isValidPort(U16 port) const {
72 1531 return true;
73 }
74
75 817 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 817 struct timeval timeout;
82 817 timeout.tv_sec = static_cast<time_t>(this->m_timeoutSeconds);
83 817 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 817 times.
817 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 817 return SOCK_SUCCESS;
90 }
91
92 206 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 206 return SOCK_SUCCESS;
102 }
103
104 1123 SocketIpStatus IpSocket::addressToIp4(const char* const ipv4_address, void* const out) {
105 1123 FW_ASSERT(ipv4_address != nullptr);
106 1123 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 1123 times.
1123 (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 1123 const int ptonStatus = ::inet_pton(AF_INET, ipv4_address, out);
125
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1123 times.
1123 if (ptonStatus != 1) {
126 ✗ return SOCK_INVALID_IP_ADDRESS;
127 }
128 #endif
129 1123 return SOCK_SUCCESS;
130 }
131
132 817 void IpSocket::close(const SocketDescriptor& socketDescriptor) {
133
1/2
✓ Branch 1 taken 817 times.
✗ Branch 2 not taken.
817 if (socketDescriptor.fd >= 0) {
134 817 (void)::close(socketDescriptor.fd);
135 }
136 817 }
137
138 101 void IpSocket::shutdown(const SocketDescriptor& socketDescriptor) {
139 101 errno = 0;
140 101 int status = ::shutdown(socketDescriptor.fd, SHUT_RDWR);
141 // If shutdown fails, go straight to the hard-shutdown
142
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 101 times.
101 if (status != 0) {
143 ✗ this->close(socketDescriptor);
144 }
145 101 }
146
147 817 SocketIpStatus IpSocket::open(SocketDescriptor& socketDescriptor) {
148 817 SocketIpStatus status = SOCK_SUCCESS;
149 817 errno = 0;
150 // Open a TCP socket for incoming commands, and outgoing data if not using UDP
151 817 status = this->openProtocol(socketDescriptor);
152
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 817 times.
817 if (status != SOCK_SUCCESS) {
153 ✗ socketDescriptor.fd = -1;
154 ✗ return status;
155 }
156 817 return status;
157 }
158
159 616 SocketIpStatus IpSocket::send(const SocketDescriptor& socketDescriptor, const U8* const data, const FwSizeType size) {
160 616 FW_ASSERT((size == 0) || (data != nullptr));
161 // Zero-size sends are a no-op
162
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 616 times.
616 if (size == 0) {
163 ✗ return SOCK_SUCCESS;
164 }
165
166 616 FwSizeType total = 0;
167 616 FwSignedSizeType sent = 0;
168 // Attempt to send out data and retry as necessary
169
3/4
✓ Branch 0 taken 1227 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 617 times.
✓ Branch 3 taken 610 times.
1227 for (FwSizeType i = 0; (i < SOCKET_MAX_ITERATIONS) && (total < size); i++) {
170 617 errno = 0;
171 // Send using my specific protocol
172 617 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 611 times.
✓ Branch 3 taken 6 times.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
✓ Branch 6 taken 617 times.
617 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 611 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.
617 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 611 times.
✓ Branch 3 taken 1 times.
✓ Branch 4 taken 2 times.
✗ Branch 6 not taken.
✓ Branch 7 taken 1 times.
614 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 611 times.
612 else if (sent == -1) {
187 1 return SOCK_SEND_ERROR;
188 }
189 611 FW_ASSERT(sent > 0, static_cast<FwAssertArgType>(sent));
190 611 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 610 times.
610 if (total < size) {
194 ✗ return SOCK_INTERRUPTED_TRY_AGAIN;
195 }
196 // Ensure we sent everything
197 610 FW_ASSERT(total == size, static_cast<FwAssertArgType>(total), static_cast<FwAssertArgType>(size));
198 610 return SOCK_SUCCESS;
199 }
200
201 711 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 711 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 712 times.
✗ Branch 1 not taken.
712 for (FwSizeType i = 0; i < SOCKET_MAX_ITERATIONS; i++) {
211 712 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 712 bytes_received_or_status = this->recvProtocol(socketDescriptor, data, req_read);
215
216
2/2
✓ Branch 0 taken 609 times.
✓ Branch 1 taken 103 times.
712 if (bytes_received_or_status > 0) {
217 // Successfully read data
218 609 req_read = static_cast<FwSizeType>(bytes_received_or_status);
219 609 return SOCK_SUCCESS;
220
2/2
✓ Branch 0 taken 102 times.
✓ Branch 1 taken 1 times.
103 } else if (bytes_received_or_status == 0) {
221 // Handle zero return based on protocol-specific behavior
222 102 req_read = 0;
223 102 return this->handleZeroReturn();
224 } else { // bytes_received_or_status == -1, an error occurred
225
2/4
✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 1 times.
1 if ((errno == EAGAIN) || (errno == EWOULDBLOCK)) {
226 // Non-blocking socket would block, or SO_RCVTIMEO timeout occurred.
227 ✗ req_read = 0;
228 ✗ return SOCK_NO_DATA_AVAILABLE;
229
2/4
✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 1 times.
1 } else if ((errno == ECONNRESET) || (errno == EBADF)) {
230 // Connection reset or bad file descriptor.
231 ✗ req_read = 0;
232 ✗ 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 101 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 101 return SOCK_DISCONNECTED;
251 }
252
253 614 SocketIpStatus IpSocket::setupSocketOptions(int socketFd) {
254 // Iterate over the socket options and set them
255
2/2
✓ Branch 3 taken 614 times.
✓ Branch 4 taken 614 times.
1228 for (const auto& options : IP_SOCKET_OPTIONS) {
256 614 int status = 0;
257
2/4
✗ Branch 1 not taken.
✓ Branch 2 taken 614 times.
✓ Branch 3 taken 614 times.
✗ Branch 4 not taken.
614 if (options.type == SOCK_OPT_INT) {
258 614 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 614 times.
614 if (status) {
265 ✗ return SOCK_FAILED_TO_SET_SOCKET_OPTIONS;
266 }
267 }
268 614 return SOCK_SUCCESS;
269 }
270
271 } // namespace Drv
272