fprime/Drv/Ip/SocketComponentHelper.hpp
Ian Brault 2b65cc83cf
Add new Fw::ConstStringBase type for strings backed by immutable string literals (#4269)
* Add new Fw::StringBase type StaticString for strings backed my immutable literals

* Spellcheck fix

* Add disclaimer comment about use of StaticString

* Refactor the StringBase interface into an immutable ConstStringBase abstract base class and the now mutable StringBase class

* Rename StaticString to ConstExternalString and inherit from ConstStringBase

* Fix typo

* Change references from StringBase to ConstStringBase where applicable

* Updates following review meeting: add missing deserialize function and add new error status, move length function implementation into ConstStringBase so it is not pure virtual

* Clang format fix

* Additional clang-format fixes

* Fix the copy-assignment operator for StringBase not being correctly evaluated

* Clang format fix

* Explicitly delete the Serializable assignment operator and provide a skeleton implementation for RawTimeInterface to appease the compiler

* Revert "Explicitly delete the Serializable assignment operator and provide a skeleton implementation for RawTimeInterface to appease the compiler"

This reverts commit 086d7bcd3ca9c4f6e553d7fc34d0d126a69a165b.

* Move ConstStringBase to separate hpp/cpp files, plus other pull request feedback

* Clang format fix

* Update length implementation for ConstStringBase and ConstExternalString

* Improved asserts in ConstExternalString constructor

Co-authored-by: Rob Bocchino <bocchino@icloud.com>

* Fixed ConstStringBase length implementation

Co-authored-by: Rob Bocchino <bocchino@icloud.com>

* Clang format fix

* Add some UTs for ConstExternalString, fix non-overridden interfaces, and fix ConstStringBase::maxLength asserting for zero capacity strings

* Spell-check fix for ConstExternalString UTs

* Revise length implementation in ConstStringBase

If the capacity is zero, return zero

* Format

---------

Co-authored-by: Ian Brault <ian.r.brault@jpl.nasa.gov>
Co-authored-by: Rob Bocchino <bocchino@icloud.com>
Co-authored-by: Rob Bocchino <bocchino@jpl.nasa.gov>
Co-authored-by: M Starch <LeStarch@googlemail.com>
2025-11-07 09:50:05 -08:00

300 lines
11 KiB
C++

// ======================================================================
// \title SocketComponentHelper.hpp
// \author mstarch
// \brief hpp file for SocketComponentHelper implementation class
//
// \copyright
// Copyright 2009-2020, by the California Institute of Technology.
// ALL RIGHTS RESERVED. United States Government Sponsorship
// acknowledged.
//
// ======================================================================
#ifndef DRV_SocketComponentHelper_HPP
#define DRV_SocketComponentHelper_HPP
#include <Drv/Ip/IpSocket.hpp>
#include <Fw/Buffer/Buffer.hpp>
#include <Os/Condition.hpp>
#include <Os/Mutex.hpp>
#include <Os/Task.hpp>
namespace Drv {
/**
* \brief supports a task to read a given socket adaptation
*
* Defines an Os::Task task to read a socket and send out the data. This represents the task itself, which is capable of
* reading the data from the socket, sending the data out, and reopening the connection should a non-retry error occur.
*
*/
class SocketComponentHelper {
public:
enum OpenState { NOT_OPEN, OPENING, OPEN, SKIP };
enum ReconnectState { NOT_RECONNECTING, REQUEST_RECONNECT, RECONNECT_IN_PROGRESS };
/**
* \brief constructs the socket read task
*/
SocketComponentHelper();
/**
* \brief destructor of the socket read task
*/
virtual ~SocketComponentHelper();
/**
* \brief start the socket read task to start producing data
*
* Starts up the socket reading task and when reopen was configured, will open up the socket.
*
* \note: users must now use `setAutomaticOpen` to configure the socket to automatically open connections. The
* default behavior is to automatically open connections.
*
* \param name: name of the task
* \param priority: priority of the started read task. See: Os::Task::start. Default: TASK_PRIORITY_DEFAULT, not
* prioritized
* \param stack: stack size provided to the read task. See: Os::Task::start. Default: TASK_DEFAULT, posix threads
* default
* \param cpuAffinity: cpu affinity provided to read task. See: Os::Task::start. Default: TASK_DEFAULT, don't care
* \param priorityReconnect: priority of the started reconnect task. See: Os::Task::start. Default:
* TASK_PRIORITY_DEFAULT, not prioritized
* \param stackReconnect: stack size provided to the reconnect task. See: Os::Task::start. Default: TASK_DEFAULT,
* posix threads default
* \param cpuAffinityReconnect: cpu affinity provided to reconnect task. See: Os::Task::start. Default:
* TASK_DEFAULT, don't care
*/
void start(const Fw::ConstStringBase& name,
const FwTaskPriorityType priority = Os::Task::TASK_PRIORITY_DEFAULT,
const Os::Task::ParamType stack = Os::Task::TASK_DEFAULT,
const Os::Task::ParamType cpuAffinity = Os::Task::TASK_DEFAULT,
const FwTaskPriorityType priorityReconnect = Os::Task::TASK_PRIORITY_DEFAULT,
const Os::Task::ParamType stackReconnect = Os::Task::TASK_DEFAULT,
const Os::Task::ParamType cpuAffinityReconnect = Os::Task::TASK_DEFAULT);
/**
* \brief open the socket for communications
*
* Typically the socket read task will open the connection and keep it open. However, in cases where the socket is
* not automatically opening, this call will open the socket. This will block until the socket is opened.
*
* Note: this just delegates to the handler
*
* \return status of open, SOCK_SUCCESS for success, something else on error
*/
SocketIpStatus open();
/**
* \brief check if IP socket has previously been opened
*
* Check if this IpSocket has previously been opened. In the case of Udp this will check for outgoing transmissions
* and (if configured) incoming transmissions as well. This does not guarantee errors will not occur when using this
* socket as the remote component may have disconnected.
*
* \return true if socket is open, false otherwise
*/
bool isOpened();
/**
* \brief set socket to automatically open connections when true, or not when false
*
* When passed `true`, this instructs the socket to automatically open a socket and reopen socket failed
* connections. When passed `false` the user must explicitly call the `open` method to open the socket initially and
* when a socket fails.
*
* \param auto_open: true to automatically open and reopen sockets, false otherwise
*/
void setAutomaticOpen(bool auto_open);
/**
* \brief get socket automatically open connections status
*
* \return status of auto_open
*/
bool getAutomaticOpen();
/**
* \brief send data to the IP socket from the given buffer
*
*
* \param data: pointer to data to send
* \param size: size of data to send
* \return status of send, SOCK_SUCCESS for success, something else on error
*/
SocketIpStatus send(const U8* const data, const FwSizeType size);
/**
* \brief receive data from the IP socket from the given buffer
*
*
* \param data: pointer to data to fill with received data
* \param size: maximum size of data buffer to fill
* \return status of the send, SOCK_DISCONNECTED to reopen, SOCK_SUCCESS on success, something else on error
*/
SocketIpStatus recv(U8* data, FwSizeType& size);
/**
* \brief close the socket communications
*
* Close the client connection. This will ensure that the resources used are cleaned-up.
*
* Note: this just delegates to the handler
*/
void close();
/**
* \brief shutdown the socket communications
*
* Shutdown communication. This will begin the process of cleanly closing communications. This process will be
* finished with a receive of 0 size and should be followed by a close.
*
* Note: this just delegates to the handler
*/
void shutdown();
/**
* \brief is the read loop running
*/
bool running();
bool runningReconnect();
/**
* \brief stop the socket read task and close the associated socket.
*
* Called to stop the socket read task. It is an error to call this before the thread has been started using the
* startSocketTask call. This will stop the read task and close the client socket.
*/
void stop();
void stopReconnect();
/**
* \brief joins to the stopping read task to wait for it to close
*
* Called to join with the read socket task. This will block and return after the task has been stopped with a call
* to the stopSocketTask method.
* \param value_ptr: a pointer to fill with data. Passed to the Os::Task::join call. NULL to ignore.
* \return: Os::Task::Status passed back from the Os::Task::join call.
*/
Os::Task::Status join();
Os::Task::Status joinReconnect();
protected:
/**
* \brief receive off the TCP socket
*/
virtual void readLoop();
/**
* \brief reconnect TCP socket
*/
virtual void reconnectLoop();
/**
* \brief returns a reference to the socket handler
*
* Gets a reference to the current socket handler in order to operate generically on the IpSocket instance. Used for
* receive, and open calls.
*
* Note: this must be implemented by the inheritor
*
* \return IpSocket reference
*/
virtual IpSocket& getSocketHandler() = 0;
/**
* \brief returns a buffer to fill with data
*
* Gets a reference to a buffer to fill with data. This allows the component to determine how to provide a
* buffer and the socket read task just fills said buffer.
*
* Note: this must be implemented by the inheritor
*
* \return Fw::Buffer to fill with data
*/
virtual Fw::Buffer getBuffer() = 0;
/**
* \brief sends a buffer to be filled with data
*
* Sends the buffer gotten by getBuffer that has now been filled with data. This is used to delegate to the
* component how to send back the buffer.
*
* Note: this must be implemented by the inheritor
*
* \return Fw::Buffer filled with data to send out
*/
virtual void sendBuffer(Fw::Buffer buffer, SocketIpStatus status) = 0;
/**
* \brief called when the IPv4 system has been connected
*/
virtual void connected() = 0;
/**
* \brief a task designed to read from the socket and output incoming data
*
* \param pointer: pointer to "this" component
*/
static void readTask(void* pointer);
/**
* \brief a task designed for socket reconnection
*
* \param pointer: pointer to "this" component
*/
static void reconnectTask(void* pointer);
/**
* \brief signal to reconnect task that a reconnect is needed
*
*/
void requestReconnect();
/**
* \brief wait method for a task to wait for a reconnect request to complete
*
* After requesting a reconnect, tasks should call this method
* to wait for the reconnect thread to complete
*
*
* \param timeout: timeout so that the wait doesn't hang indefinitely
*
* \return status of the reconnect request, SOCK_DISCONNECTED for
* reopen again, or SOCK_SUCCESS on success, something else on error
*/
SocketIpStatus waitForReconnect(Fw::TimeInterval timeout = Fw::TimeInterval(1, 0));
private:
/**
* \brief Re-open port if it has been disconnected
*
* This function is a helper to handle the situations where this code needs to safely reopen a socket. User code
* should connect using the `open` call. This is for opening/reopening in situations where automatic open is
* performed within this socket helper.
*
* \return status of reconnect, SOCK_SUCCESS for success, something else on error
*/
SocketIpStatus reopen();
protected:
bool m_reopen = true; //!< Force reopen on disconnect
SocketDescriptor m_descriptor;
// Read/recv
Os::Task m_task;
Os::Mutex m_lock;
bool m_stop = true; //!< Stops the task when set to true
OpenState m_open = OpenState::NOT_OPEN; //!< Have we successfully opened
// Reconnect
Os::Task m_reconnectTask;
Os::Mutex m_reconnectLock;
bool m_reconnectStop = true;
ReconnectState m_reconnectState = ReconnectState::NOT_RECONNECTING;
Fw::TimeInterval m_reconnectCheckInterval =
Fw::TimeInterval(0, 50000); // 50 ms, Interval at which reconnect task loop checks for requests
Fw::TimeInterval m_reconnectWaitInterval =
Fw::TimeInterval(0, 10000); // 10 ms, Interval at which reconnect requesters wait for response
};
} // namespace Drv
#endif // DRV_SocketComponentHelper_HPP