GCC Code Coverage Report


Directory: ./
File: Drv/Ip/IpSocket.cpp
Date: 2026-09-03 22:12:29
Exec Total Coverage
Lines: 79 103 76.7%
Functions: 11 12 91.7%
Branches: 24 54 44.4%

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 1 IpSocket::IpSocket() : m_timeoutSeconds(0), m_timeoutMicroseconds(0), m_port(0) {
48 1 (void)::memset(this->m_ipv4_address, 0, sizeof(this->m_ipv4_address));
49 1 }
50
51 1 SocketIpStatus IpSocket::configure(const char* const ipv4_address,
52 const U16 port,
53 const U32 timeout_seconds,
54 const U32 timeout_microseconds) {
55 1 FW_ASSERT(timeout_microseconds < 1000000, static_cast<FwAssertArgType>(timeout_microseconds));
56 1 FW_ASSERT(this->isValidPort(port), static_cast<FwAssertArgType>(port));
57 1 FW_ASSERT(ipv4_address != nullptr);
58 // Defense-in-depth: reject inputs that would have been silently truncated by string_copy.
59 1 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 1 this->m_timeoutSeconds = timeout_seconds;
62 1 this->m_timeoutMicroseconds = timeout_microseconds;
63 1 this->m_port = port;
64 1 (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 1 FW_ASSERT(this->m_ipv4_address[SOCKET_MAX_IPV4_ADDRESS_SIZE - 1] == '\0');
68 1 return SOCK_SUCCESS;
69 }
70
71 bool IpSocket::isValidPort(U16 port) const {
72 return true;
73 }
74
75 2 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 struct timeval timeout;
82 2 timeout.tv_sec = static_cast<time_t>(this->m_timeoutSeconds);
83 2 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 2 times.
2 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 2 return SOCK_SUCCESS;
90 }
91
92 2 SocketIpStatus IpSocket::addressToIp4(const char* const ipv4_address, void* const out) {
93 2 FW_ASSERT(ipv4_address != nullptr);
94 2 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 2 (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 2 const int ptonStatus = ::inet_pton(AF_INET, ipv4_address, out);
113
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
2 if (ptonStatus != 1) {
114 return SOCK_INVALID_IP_ADDRESS;
115 }
116 #endif
117 2 return SOCK_SUCCESS;
118 }
119
120 3 void IpSocket::close(const SocketDescriptor& socketDescriptor) {
121
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 2 times.
3 if (socketDescriptor.fd >= 0) {
122 1 (void)::close(socketDescriptor.fd);
123 }
124 3 }
125
126 1 void IpSocket::shutdown(const SocketDescriptor& socketDescriptor) {
127 1 errno = 0;
128 1 int status = ::shutdown(socketDescriptor.fd, SHUT_RDWR);
129 // If shutdown fails, go straight to the hard-shutdown
130
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
1 if (status != 0) {
131 this->close(socketDescriptor);
132 }
133 1 }
134
135 2 SocketIpStatus IpSocket::open(SocketDescriptor& socketDescriptor) {
136 2 SocketIpStatus status = SOCK_SUCCESS;
137 2 errno = 0;
138 // Open a TCP socket for incoming commands, and outgoing data if not using UDP
139 2 status = this->openProtocol(socketDescriptor);
140
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 1 times.
2 if (status != SOCK_SUCCESS) {
141 1 socketDescriptor.fd = -1;
142 1 return status;
143 }
144 1 return status;
145 }
146
147 229 SocketIpStatus IpSocket::send(const SocketDescriptor& socketDescriptor, const U8* const data, const FwSizeType size) {
148 229 FW_ASSERT((size == 0) || (data != nullptr));
149 // Zero-size sends are a no-op
150
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 229 times.
229 if (size == 0) {
151 return SOCK_SUCCESS;
152 }
153
154 229 FwSizeType total = 0;
155 229 FwSignedSizeType sent = 0;
156 // Attempt to send out data and retry as necessary
157
3/4
✓ Branch 0 taken 458 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 229 times.
✓ Branch 3 taken 229 times.
458 for (FwSizeType i = 0; (i < SOCKET_MAX_ITERATIONS) && (total < size); i++) {
158 229 errno = 0;
159 // Send using my specific protocol
160 229 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 229 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 229 times.
229 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 229 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
229 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 229 times.
229 else if (sent == -1) {
171 return SOCK_SEND_ERROR;
172 }
173 229 FW_ASSERT(sent > 0, static_cast<FwAssertArgType>(sent));
174 229 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 229 times.
229 if (total < size) {
178 return SOCK_INTERRUPTED_TRY_AGAIN;
179 }
180 // Ensure we sent everything
181 229 FW_ASSERT(total == size, static_cast<FwAssertArgType>(total), static_cast<FwAssertArgType>(size));
182 229 return SOCK_SUCCESS;
183 }
184
185 615 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 615 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 615 times.
✗ Branch 1 not taken.
615 for (FwSizeType i = 0; i < SOCKET_MAX_ITERATIONS; i++) {
195 615 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 615 bytes_received_or_status = this->recvProtocol(socketDescriptor, data, req_read);
199
200
2/2
✓ Branch 0 taken 614 times.
✓ Branch 1 taken 1 times.
615 if (bytes_received_or_status > 0) {
201 // Successfully read data
202 614 req_read = static_cast<FwSizeType>(bytes_received_or_status);
203 614 return SOCK_SUCCESS;
204
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 } else if (bytes_received_or_status == 0) {
205 // Handle zero return based on protocol-specific behavior
206 1 req_read = 0;
207 1 return this->handleZeroReturn();
208 } else { // bytes_received_or_status == -1, an error occurred
209 if ((errno == EAGAIN) || (errno == EWOULDBLOCK)) {
210 // Non-blocking socket would block, or SO_RCVTIMEO timeout occurred.
211 req_read = 0;
212 return SOCK_NO_DATA_AVAILABLE;
213 } else if ((errno == ECONNRESET) || (errno == EBADF)) {
214 // Connection reset or bad file descriptor.
215 req_read = 0;
216 return SOCK_DISCONNECTED; // Or a more specific error like SOCK_READ_ERROR
217 } else if (errno == EINTR) {
218 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 1 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 1 return SOCK_DISCONNECTED;
235 }
236
237 2 SocketIpStatus IpSocket::setupSocketOptions(int socketFd) {
238 // Iterate over the socket options and set them
239
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 2 times.
4 for (const auto& options : IP_SOCKET_OPTIONS) {
240 2 int status = 0;
241
1/2
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
2 if (options.type == SOCK_OPT_INT) {
242 2 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 2 times.
2 if (status) {
249 return SOCK_FAILED_TO_SET_SOCKET_OPTIONS;
250 }
251 }
252 2 return SOCK_SUCCESS;
253 }
254
255 } // namespace Drv
256