F´ Flight Software - C/C++ Documentation
A framework for building embedded system applications to NASA flight quality standards.
IpSocket.cpp
Go to the documentation of this file.
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 IpSocket::IpSocket() : m_timeoutSeconds(0), m_timeoutMicroseconds(0), m_port(0) {
48  (void)::memset(this->m_ipv4_address, 0, sizeof(this->m_ipv4_address));
49 }
50 
51 SocketIpStatus IpSocket::configure(const char* const ipv4_address,
52  const U16 port,
53  const U32 timeout_seconds,
54  const U32 timeout_microseconds) {
55  FW_ASSERT(timeout_microseconds < 1000000, static_cast<FwAssertArgType>(timeout_microseconds));
56  FW_ASSERT(this->isValidPort(port), static_cast<FwAssertArgType>(port));
57  FW_ASSERT(ipv4_address != nullptr);
58  // Defense-in-depth: reject inputs that would have been silently truncated by string_copy.
59  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  this->m_timeoutSeconds = timeout_seconds;
62  this->m_timeoutMicroseconds = timeout_microseconds;
63  this->m_port = port;
64  (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.
68  return SOCK_SUCCESS;
69 }
70 
71 bool IpSocket::isValidPort(U16 port) const {
72  return true;
73 }
74 
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  timeout.tv_sec = static_cast<time_t>(this->m_timeoutSeconds);
83  timeout.tv_usec = static_cast<suseconds_t>(this->m_timeoutMicroseconds);
84  // set socket write to timeout after 1 sec
85  if (setsockopt(socketFd, SOL_SOCKET, SO_SNDTIMEO, reinterpret_cast<char*>(&timeout), sizeof(timeout)) < 0) {
87  }
88 #endif
89  return SOCK_SUCCESS;
90 }
91 
93 #ifdef SO_NOSIGPIPE
94  constexpr int enable = 1;
95  if (setsockopt(socketFd, SOL_SOCKET, SO_NOSIGPIPE, &enable, sizeof(enable)) < 0) {
97  }
98 #else
99  (void)socketFd;
100 #endif
101  return SOCK_SUCCESS;
102 }
103 
104 SocketIpStatus IpSocket::addressToIp4(const char* const ipv4_address, void* const out) {
105  FW_ASSERT(ipv4_address != nullptr);
106  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  (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) {
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  const int ptonStatus = ::inet_pton(AF_INET, ipv4_address, out);
125  if (ptonStatus != 1) {
127  }
128 #endif
129  return SOCK_SUCCESS;
130 }
131 
132 void IpSocket::close(const SocketDescriptor& socketDescriptor) {
133  if (socketDescriptor.fd >= 0) {
134  (void)::close(socketDescriptor.fd);
135  }
136 }
137 
138 void IpSocket::shutdown(const SocketDescriptor& socketDescriptor) {
139  errno = 0;
140  int status = ::shutdown(socketDescriptor.fd, SHUT_RDWR);
141  // If shutdown fails, go straight to the hard-shutdown
142  if (status != 0) {
143  this->close(socketDescriptor);
144  }
145 }
146 
148  SocketIpStatus status = SOCK_SUCCESS;
149  errno = 0;
150  // Open a TCP socket for incoming commands, and outgoing data if not using UDP
151  status = this->openProtocol(socketDescriptor);
152  if (status != SOCK_SUCCESS) {
153  socketDescriptor.fd = -1;
154  return status;
155  }
156  return status;
157 }
158 
159 SocketIpStatus IpSocket::send(const SocketDescriptor& socketDescriptor, const U8* const data, const FwSizeType size) {
160  FW_ASSERT((size == 0) || (data != nullptr));
161  // Zero-size sends are a no-op
162  if (size == 0) {
163  return SOCK_SUCCESS;
164  }
165 
166  FwSizeType total = 0;
167  FwSignedSizeType sent = 0;
168  // Attempt to send out data and retry as necessary
169  for (FwSizeType i = 0; (i < SOCKET_MAX_ITERATIONS) && (total < size); i++) {
170  errno = 0;
171  // Send using my specific protocol
172  sent = this->sendProtocol(socketDescriptor, data + total, size - total);
173  // Error is EINTR just try again
174  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  else if ((sent == -1) && ((errno == EBADF) || (errno == ECONNRESET) || (errno == EPIPE))) {
179  return SOCK_DISCONNECTED;
180  }
181  // Error is a send timeout (SO_SNDTIMEO) or would-block, recoverable by a caller retry
182  else if ((sent == -1) && ((errno == EAGAIN) || (errno == EWOULDBLOCK))) {
184  }
185  // Error returned, and it wasn't an interrupt, a timeout, nor a disconnect
186  else if (sent == -1) {
187  return SOCK_SEND_ERROR;
188  }
189  FW_ASSERT(sent > 0, static_cast<FwAssertArgType>(sent));
190  total += static_cast<FwSizeType>(sent);
191  }
192  // Failed to retry enough to send all data
193  if (total < size) {
195  }
196  // Ensure we sent everything
197  FW_ASSERT(total == size, static_cast<FwAssertArgType>(total), static_cast<FwAssertArgType>(size));
198  return SOCK_SUCCESS;
199 }
200 
201 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  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  for (FwSizeType i = 0; i < SOCKET_MAX_ITERATIONS; i++) {
211  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  bytes_received_or_status = this->recvProtocol(socketDescriptor, data, req_read);
215 
216  if (bytes_received_or_status > 0) {
217  // Successfully read data
218  req_read = static_cast<FwSizeType>(bytes_received_or_status);
219  return SOCK_SUCCESS;
220  } else if (bytes_received_or_status == 0) {
221  // Handle zero return based on protocol-specific behavior
222  req_read = 0;
223  return this->handleZeroReturn();
224  } else { // bytes_received_or_status == -1, an error occurred
225  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  } 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  } else if (errno == EINTR) {
234  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;
245 }
246 
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  return SOCK_DISCONNECTED;
251 }
252 
254  // Iterate over the socket options and set them
255  for (const auto& options : IP_SOCKET_OPTIONS) {
256  int status = 0;
257  if (options.type == SOCK_OPT_INT) {
258  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  if (status) {
266  }
267  }
268  return SOCK_SUCCESS;
269 }
270 
271 } // namespace Drv
Failed to send after configured retries.
Definition: IpSocket.hpp:50
Socket disconnected during read or send.
Definition: IpSocket.hpp:46
Interrupted status for retries.
Definition: IpSocket.hpp:44
Failed to configure socket.
Definition: IpSocket.hpp:43
PlatformSizeType FwSizeType
SocketIpStatus recv(const SocketDescriptor &fd, U8 *const data, FwSizeType &size)
receive data from the IP socket from the given buffer
Definition: IpSocket.cpp:201
Operation failed.
Definition: Os.hpp:28
SocketIpStatus setupTimeouts(int socketFd)
setup the socket timeout properties of the opened outgoing socket
Definition: IpSocket.cpp:75
virtual SocketIpStatus configure(const char *const ipv4_address, const U16 port, const U32 send_timeout_seconds, const U32 send_timeout_microseconds)
configure the ip socket with an IPv4 address and transmission timeouts
Definition: IpSocket.cpp:51
static SocketIpStatus addressToIp4(const char *const ipv4_address, void *const out)
converts a given IPv4 address in dotted-quad form "x.x.x.x" to a network-order in_addr structure...
Definition: IpSocket.cpp:104
int fd
Used for all sockets to track the communication file descriptor.
Definition: IpSocket.hpp:30
PlatformSignedSizeType FwSignedSizeType
SocketIpStatus setupNoSigPipe(int socketFd)
prevent sends on a stream socket from raising SIGPIPE on platforms without MSG_NOSIGNAL ...
Definition: IpSocket.cpp:92
virtual SocketIpStatus send(const SocketDescriptor &socketDescriptor, const U8 *const data, const FwSizeType size)
send data out the IP socket from the given buffer
Definition: IpSocket.cpp:159
Bad IP address supplied.
Definition: IpSocket.hpp:41
virtual SocketIpStatus openProtocol(SocketDescriptor &fd)=0
Protocol specific open implementation, called from open.
void shutdown(const SocketDescriptor &socketDescriptor)
shutdown the socket
Definition: IpSocket.cpp:138
virtual FwSignedSizeType sendProtocol(const SocketDescriptor &socketDescriptor, const U8 *const data, const FwSizeType size)=0
Protocol specific implementation of send. Called directly with retry from send.
U16 m_port
IP address port used.
Definition: IpSocket.hpp:281
Socket operation successful.
Definition: IpSocket.hpp:38
char * string_copy(char *destination, const char *source, FwSizeType num)
copy string with null-termination guaranteed
Definition: StringUtils.cpp:7
Failed to read socket.
Definition: IpSocket.hpp:45
SocketIpStatus setupSocketOptions(int socketFd)
setup the socket options of the input socket as defined in IpCfg.hpp
Definition: IpSocket.cpp:253
virtual bool isValidPort(U16 port) const
Check if the given port is valid for the socket.
Definition: IpSocket.cpp:71
uint8_t U8
8-bit unsigned integer
Definition: BasicTypes.h:54
static const IpSocketOptions IP_SOCKET_OPTIONS[]
Definition: IpCfg.hpp:88
virtual SocketIpStatus handleZeroReturn()
Handle zero return from recvProtocol.
Definition: IpSocket.cpp:247
char m_ipv4_address[SOCKET_MAX_IPV4_ADDRESS_SIZE]
IPv4 address (dotted-quad "x.x.x.x")
Definition: IpSocket.hpp:282
void close(const SocketDescriptor &socketDescriptor)
closes the socket
Definition: IpSocket.cpp:132
virtual FwSignedSizeType recvProtocol(const SocketDescriptor &socketDescriptor, U8 *const data, const FwSizeType size)=0
Protocol specific implementation of recv. Called directly with error handling from recv...
SocketIpStatus open(SocketDescriptor &socketDescriptor)
open the IP socket for communications
Definition: IpSocket.cpp:147
SocketIpStatus
Status enumeration for socket return values.
Definition: IpSocket.hpp:37
U32 m_timeoutMicroseconds
Definition: IpSocket.hpp:280
No data available or read operation would block.
Definition: IpSocket.hpp:53
#define FW_ASSERT(...)
Definition: Assert.hpp:14
U32 m_timeoutSeconds
Definition: IpSocket.hpp:279
FwSizeType string_length(const CHAR *source, FwSizeType buffer_size)
get the length of the source string
Definition: StringUtils.cpp:32