GCC Code Coverage Report


Directory: ./
File: Drv/Ip/IpSocket.cpp
Date: 2026-11-03 22:10:14
Exec Total Coverage
Lines: 82 107 76.6%
Functions: 12 13 92.3%
Branches: 25 62 40.3%

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 1 IpSocket::IpSocket() : m_timeoutSeconds(0), m_timeoutMicroseconds(0), m_port(0) {
27 1 (void)::memset(this->m_ipv4_address, 0, sizeof(this->m_ipv4_address));
28 1 }
29
30 1 SocketIpStatus IpSocket::configure(const char* const ipv4_address,
31 const U16 port,
32 const U32 timeout_seconds,
33 const U32 timeout_microseconds) {
34 1 FW_ASSERT(timeout_microseconds < 1000000, static_cast<FwAssertArgType>(timeout_microseconds));
35 1 FW_ASSERT(this->isValidPort(port), static_cast<FwAssertArgType>(port));
36 1 FW_ASSERT(ipv4_address != nullptr);
37 // Defense-in-depth: reject inputs that would have been silently truncated by string_copy.
38 1 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 1 this->m_timeoutSeconds = timeout_seconds;
41 1 this->m_timeoutMicroseconds = timeout_microseconds;
42 1 this->m_port = port;
43 1 (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 1 FW_ASSERT(this->m_ipv4_address[SOCKET_MAX_IPV4_ADDRESS_SIZE - 1] == '\0');
47 1 return SOCK_SUCCESS;
48 }
49
50 ✗ bool IpSocket::isValidPort(U16 port) const {
51 ✗ return true;
52 }
53
54 2 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 struct timeval timeout;
61 2 timeout.tv_sec = static_cast<time_t>(this->m_timeoutSeconds);
62 2 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 2 times.
2 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 2 return SOCK_SUCCESS;
69 }
70
71 2 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 2 return SOCK_SUCCESS;
81 }
82
83 2 SocketIpStatus IpSocket::addressToIp4(const char* const ipv4_address, void* const out) {
84 2 FW_ASSERT(ipv4_address != nullptr);
85 2 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 2 (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 2 const int ptonStatus = ::inet_pton(AF_INET, ipv4_address, out);
104
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
2 if (ptonStatus != 1) {
105 ✗ return SOCK_INVALID_IP_ADDRESS;
106 }
107 #endif
108 2 return SOCK_SUCCESS;
109 }
110
111 3 void IpSocket::close(const SocketDescriptor& socketDescriptor) {
112
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 2 times.
3 if (socketDescriptor.fd >= 0) {
113 1 (void)::close(socketDescriptor.fd);
114 }
115 3 }
116
117 1 void IpSocket::shutdown(const SocketDescriptor& socketDescriptor) {
118 1 errno = 0;
119 1 int status = ::shutdown(socketDescriptor.fd, SHUT_RDWR);
120 // If shutdown fails, go straight to the hard-shutdown
121
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
1 if (status != 0) {
122 ✗ this->close(socketDescriptor);
123 }
124 1 }
125
126 2 SocketIpStatus IpSocket::open(SocketDescriptor& socketDescriptor) {
127 2 SocketIpStatus status = SOCK_SUCCESS;
128 2 errno = 0;
129 // Open a TCP socket for incoming commands, and outgoing data if not using UDP
130 2 status = this->openProtocol(socketDescriptor);
131
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 1 times.
2 if (status != SOCK_SUCCESS) {
132 1 socketDescriptor.fd = -1;
133 1 return status;
134 }
135 1 return status;
136 }
137
138 279 SocketIpStatus IpSocket::send(const SocketDescriptor& socketDescriptor, const U8* const data, const FwSizeType size) {
139 279 FW_ASSERT((size == 0) || (data != nullptr));
140 // Zero-size sends are a no-op
141
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 279 times.
279 if (size == 0) {
142 ✗ return SOCK_SUCCESS;
143 }
144
145 279 FwSizeType total = 0;
146 279 FwSignedSizeType sent = 0;
147 // Attempt to send out data and retry as necessary
148
3/4
✓ Branch 0 taken 558 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 279 times.
✓ Branch 3 taken 279 times.
558 for (FwSizeType i = 0; (i < SOCKET_MAX_ITERATIONS) && (total < size); i++) {
149 279 errno = 0;
150 // Send using my specific protocol
151 279 sent = this->sendProtocol(socketDescriptor, data + total, size - total);
152 // Error is EINTR just try again
153
2/6
✗ Branch 0 not taken.
✓ Branch 1 taken 279 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 279 times.
279 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
1/8
✗ Branch 0 not taken.
✓ Branch 1 taken 279 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
✗ Branch 7 not taken.
279 else if ((sent == -1) && ((errno == EBADF) || (errno == ECONNRESET) || (errno == EPIPE))) {
158 ✗ return SOCK_DISCONNECTED;
159 }
160 // Error is a send timeout (SO_SNDTIMEO) or would-block, recoverable by a caller retry
161
1/6
✗ Branch 0 not taken.
✓ Branch 1 taken 279 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
279 else if ((sent == -1) && ((errno == EAGAIN) || (errno == EWOULDBLOCK))) {
162 ✗ return SOCK_INTERRUPTED_TRY_AGAIN;
163 }
164 // Error returned, and it wasn't an interrupt, a timeout, nor a disconnect
165
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 279 times.
279 else if (sent == -1) {
166 ✗ return SOCK_SEND_ERROR;
167 }
168 279 FW_ASSERT(sent > 0, static_cast<FwAssertArgType>(sent));
169 279 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 279 times.
279 if (total < size) {
173 ✗ return SOCK_INTERRUPTED_TRY_AGAIN;
174 }
175 // Ensure we sent everything
176 279 FW_ASSERT(total == size, static_cast<FwAssertArgType>(total), static_cast<FwAssertArgType>(size));
177 279 return SOCK_SUCCESS;
178 }
179
180 680 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 680 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 680 times.
✗ Branch 1 not taken.
680 for (FwSizeType i = 0; i < SOCKET_MAX_ITERATIONS; i++) {
190 680 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 680 bytes_received_or_status = this->recvProtocol(socketDescriptor, data, req_read);
194
195
2/2
✓ Branch 0 taken 679 times.
✓ Branch 1 taken 1 times.
680 if (bytes_received_or_status > 0) {
196 // Successfully read data
197 679 req_read = static_cast<FwSizeType>(bytes_received_or_status);
198 679 return SOCK_SUCCESS;
199
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 } else if (bytes_received_or_status == 0) {
200 // Handle zero return based on protocol-specific behavior
201 1 req_read = 0;
202 1 return this->handleZeroReturn();
203 } else { // bytes_received_or_status == -1, an error occurred
204 ✗ if ((errno == EAGAIN) || (errno == EWOULDBLOCK)) {
205 // Non-blocking socket would block, or SO_RCVTIMEO timeout occurred.
206 ✗ req_read = 0;
207 ✗ return SOCK_NO_DATA_AVAILABLE;
208 ✗ } else if ((errno == ECONNRESET) || (errno == EBADF)) {
209 // Connection reset or bad file descriptor.
210 ✗ req_read = 0;
211 ✗ return SOCK_DISCONNECTED; // Or a more specific error like SOCK_READ_ERROR
212 ✗ } else if (errno == EINTR) {
213 ✗ 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 1 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 1 return SOCK_DISCONNECTED;
230 }
231
232 2 SocketIpStatus IpSocket::setupSocketOptions(int socketFd) {
233 // Iterate over the socket options and set them
234
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 2 times.
4 for (const auto& options : IP_SOCKET_OPTIONS) {
235 2 int status = 0;
236
1/2
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
2 if (options.type == SOCK_OPT_INT) {
237 2 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 2 times.
2 if (status) {
244 ✗ return SOCK_FAILED_TO_SET_SOCKET_OPTIONS;
245 }
246 }
247 2 return SOCK_SUCCESS;
248 }
249
250 } // namespace Drv
251