GCC Code Coverage Report


Directory: ./
File: Drv/Ip/IpSocket.cpp
Date: 2026-11-03 21:15:54
Exec Total Coverage
Lines: 97 108 89.8%
Functions: 13 13 100.0%
Branches: 49 68 72.1%

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