diff --git a/erpc_c/setup/erpc_setup_lwip_tcp.cpp b/erpc_c/setup/erpc_setup_lwip_tcp.cpp new file mode 100644 index 000000000..b61079c82 --- /dev/null +++ b/erpc_c/setup/erpc_setup_lwip_tcp.cpp @@ -0,0 +1,63 @@ +/** + * @file erpc_setup_lwip_tcp.cpp + * @brief TCP Transport setup code. + * + * + * @author Andrej Hýroš, xhyros00@stud.fit.vut.cz + * @date 7th of May, 2025 + * + * SPDX-License-Identifier: BSD-3-Clause + */ + + +#include "erpc_transport_setup.h" +#include "erpc_lwip_tcp_transport.hpp" +#include "ethernetif.h" + + +using namespace erpc; + + +// TODO Missing support for static allocation policy +erpc_transport_t erpc_transport_lwip_tcp_init(const char *host, uint16_t port, bool isServer) { + erpc_transport_t transport; + LwipTCPTransport *lwipTCPTransport; + +#if ERPC_ALLOCATION_POLICY == ERPC_ALLOCATION_POLICY_DYNAMIC + lwipTCPTransport = new LwipTCPTransport(host, port, isServer); +#else +#error "Only dynamic allocation policy supported for lwipTCPTransport!" +#endif + + transport = reinterpret_cast(lwipTCPTransport); + + if(lwipTCPTransport != NULL) { + if(lwipTCPTransport->openTransport() != kErpcStatus_Success) { + LWIPTCP_DEBUG_PRINT("[erpc_transport_lwip_tcp_init()] Setup failed."); + transport = NULL; + } + } + + return transport; +} + + +void erpc_transport_lwip_tcp_close(erpc_transport_t transport) { + erpc_assert(transport != NULL); + + LwipTCPTransport *lwipTcpTransport = reinterpret_cast(transport); + + lwipTcpTransport->closeTransport(); +} + +void erpc_transport_lwip_tcp_deinit(erpc_transport_t transport) { +#if ERPC_ALLOCATION_POLICY == ERPC_ALLOCATION_POLICY_DYNAMIC + erpc_assert(transport != NULL); + + LwipTCPTransport *lwipTcpTransport = reinterpret_cast(transport); + + delete lwipTcpTransport; +#else +#error "Only dynamic allocation policy supported for lwipTCPTransport!" +#endif +} diff --git a/erpc_c/setup/erpc_transport_setup.h b/erpc_c/setup/erpc_transport_setup.h index 65c3d4be0..1ca60dc58 100644 --- a/erpc_c/setup/erpc_transport_setup.h +++ b/erpc_c/setup/erpc_transport_setup.h @@ -486,6 +486,14 @@ void erpc_transport_tcp_deinit(erpc_transport_t transport); //@} + +erpc_transport_t erpc_transport_lwip_tcp_init(const char *host, uint16_t port, bool isServer); + +void erpc_transport_lwip_tcp_deinit(); + + + + //! @name CMSIS UART transport setup //@{ diff --git a/erpc_c/transports/erpc_lwip_tcp_transport.cpp b/erpc_c/transports/erpc_lwip_tcp_transport.cpp new file mode 100644 index 000000000..fc6957a90 --- /dev/null +++ b/erpc_c/transports/erpc_lwip_tcp_transport.cpp @@ -0,0 +1,297 @@ +/** + * @file erpc_lwip_tcp_transport.cpp + * @brief TCP Transport implementation on LwIP TCP/IP stack. + * + * + * @author Andrej Hýroš, xhyros00@stud.fit.vut.cz + * @date 7th of May, 2025 + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#include "erpc_lwip_tcp_transport.hpp" + +using namespace erpc; + + +LwipTCPTransport::LwipTCPTransport(const char *host, uint16_t port, bool isServer) : + m_host(host), + m_isServer(isServer), + m_port(port), + m_runServer(false), + m_clientSock(-1), + m_serverThread(serverThreadStub, DEFAULT_THREAD_PRIO, DEFAULT_THREAD_STACKSIZE) +{ +} + +LwipTCPTransport::~LwipTCPTransport() { + closeTransport(); +} + + + +void LwipTCPTransport::configure(const char* host, uint16_t port, bool isServer) { + m_host = host; + m_isServer = isServer; + m_port = port; +} + + +erpc_status_t LwipTCPTransport::openTransport() { + erpc_status_t status = kErpcStatus_Success; + /* + * Open transport as a server or as a client. + */ + if(m_isServer) { + LWIPTCP_DEBUG_PRINT("[openTransport()] Starting server..."); + m_runServer = true; + m_serverThread.start(this); + } else { + status = connectClient(); + } + return status; +} + + + +erpc_status_t LwipTCPTransport::closeTransport() { + /* + * End server loop and close connection to the client. + */ + if (m_isServer) { + m_runServer = false; + } + lwip_close(m_clientSock); + + + return kErpcStatus_Success; +} + + + + + +erpc_status_t LwipTCPTransport::connectClient() { + + + if(m_clientSock != -1) { + LWIPTCP_DEBUG_PRINT("[connectClient()] Client is already connected!"); + return kErpcStatus_Success; + } + + + struct sockaddr_in srv; + + /* + * Configure and create socket. + */ + memset(&srv, 0, sizeof(srv)); + srv.sin_family = AF_INET; + srv.sin_addr.s_addr = inet_addr(m_host); + srv.sin_port = htons(m_port); + + m_clientSock = lwip_socket(AF_INET, SOCK_STREAM, 0); + if(m_clientSock < 0) { + LWIPTCP_DEBUG_PRINT("[connectClient()] Error creating socket..."); + return kErpcStatus_Fail; + } + + /* + * TCP_NODELAY disables Nagle's algorithm, which should reduce latency for small packets. + * Without this setting, TCP would wait for additional data, so it can send multiple packets + * together. This helps with congestion controll, but in eRPC case, we often want to send + * small messages. + */ + int yes = 1; + lwip_setsockopt(m_clientSock, IPPROTO_TCP, TCP_NODELAY, &yes, sizeof(yes)); + + + /* + * Connect to server. + */ + if (lwip_connect(m_clientSock, (struct sockaddr *)&srv, sizeof(srv)) < 0) { + LWIPTCP_DEBUG_PRINT("[connectClient()] Connection failed..."); + lwip_close(m_clientSock); + return kErpcStatus_ConnectionFailure; + } + return kErpcStatus_Success; +} + + +erpc_status_t LwipTCPTransport::disconnect() { + /* + * If socket is valid, close it and mark it as invalid. + */ + if (m_clientSock >= 0) { + lwip_close(m_clientSock); + m_clientSock = -1; + } + return kErpcStatus_Success; +} + + +erpc_status_t LwipTCPTransport::startServer() +{ + struct sockaddr_in srv; + struct sockaddr_in cli; + socklen_t cliInfoLen = sizeof(struct sockaddr_in); + memset(&srv, 0, sizeof(srv)); + memset(&cli, 0, sizeof(cli)); + int incomingSocket; + + /* + * Define address. + */ + srv.sin_family = AF_INET; + srv.sin_port = htons(m_port); + srv.sin_addr.s_addr = INADDR_ANY; + + /* + * Create listening socket. + */ + if((m_serverSock = lwip_socket(AF_INET, SOCK_STREAM, 0)) < 0) { + LWIPTCP_DEBUG_PRINT("[startServer()] Error creating socket..."); + return kErpcStatus_Fail; + } + + /* + * SO_REUSEADDR makes the port available right after closing. + */ + int yes = 1; + lwip_setsockopt(m_serverSock, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(yes)); + + + /* + * Bind socket to address. + */ + if(lwip_bind(m_serverSock, (struct sockaddr *)&srv, sizeof(srv)) == -1) { + LWIPTCP_DEBUG_PRINT("[startServer()] Failed on bind()..."); + lwip_close(m_serverSock); + return kErpcStatus_Fail; + } + + /* + * Starting listening. + */ + if(lwip_listen(m_serverSock, 1) == -1) { + LWIPTCP_DEBUG_PRINT("[startServer()] Failed on listen()..."); + lwip_close(m_serverSock); + return kErpcStatus_Fail; + } + + + while(m_runServer) { + incomingSocket = lwip_accept(m_serverSock, (struct sockaddr *)&cli, &cliInfoLen); + + /* + * In case of accept failure, wait some time and try again. + */ + if(incomingSocket < 0) { + LWIPTCP_DEBUG_PRINT("[startServer()] Failed on accept()..."); + Thread::sleep(1000); // 1ms + continue; + } else { + m_clientSock = incomingSocket; + } + + } + + lwip_close(m_serverSock); + m_serverSock = -1; + + return kErpcStatus_Success; +} + + +erpc_status_t LwipTCPTransport::underlyingReceive(uint8_t *data, uint32_t size) { + ssize_t read; + + /* + * Repeat reading from socket until we read all that we want. + */ + while(size > 0) { + read = lwip_read(m_clientSock, data, size); + if(read > 0) { + size -= read; + data += read; + } + /* + * Connection was closed by peer. + */ + if(read == 0) { + lwip_close(m_clientSock); + return kErpcStatus_ConnectionClosed; + } + + /* + * Read error. + */ + if(read < 0) { + return kErpcStatus_ReceiveFailed; + } + } + return kErpcStatus_Success; +} + +erpc_status_t LwipTCPTransport::underlyingSend(const uint8_t *data, uint32_t size) { + ssize_t toSend = size; + ssize_t sent; + + + /* + * Socket is invalid. + */ + if(m_clientSock < 0) { + return kErpcStatus_ConnectionFailure; + } + + /* + * Write into socket until we sent all that we wanted. + */ + while(toSend > 0) { + sent = lwip_write(m_clientSock, data, toSend); + + if(sent > 0) { + toSend -= sent; + data += sent; + } else { + return kErpcStatus_SendFailed; + } + } + return kErpcStatus_Success; +} + +bool LwipTCPTransport::hasMessage(void) { + /* + * Return true if server is connected to client. + */ + return (m_clientSock >= 0); +} + +void LwipTCPTransport::serverThreadStub(void *arg) { + /* + * Start server as a separate task/thread. + */ + LwipTCPTransport *instance = reinterpret_cast(arg); + if (instance != NULL) + { + instance->startServer(); + } else { + LWIPTCP_DEBUG_PRINT("[serverThreadStub()] Failed on thread creation..."); + } +} + + + + + + + + + + + + + + + diff --git a/erpc_c/transports/erpc_lwip_tcp_transport.hpp b/erpc_c/transports/erpc_lwip_tcp_transport.hpp new file mode 100644 index 000000000..4786bcd8b --- /dev/null +++ b/erpc_c/transports/erpc_lwip_tcp_transport.hpp @@ -0,0 +1,177 @@ +/** + * @file erpc_lwip_tcp_transport.hpp + * @brief TCP Transport implementation on LwIP TCP/IP stack. + * + * + * @author Andrej Hýroš, xhyros00@stud.fit.vut.cz + * @date 7th of May, 2025 + * + * SPDX-License-Identifier: BSD-3-Clause + */ + + +#ifndef EVKMIMXRT1060_ERPC_LWIP_EXAMPLE_LWIPTCPTRANSPORT_HPP +#define EVKMIMXRT1060_ERPC_LWIP_EXAMPLE_LWIPTCPTRANSPORT_HPP + + +#include "erpc_framed_transport.hpp" +#include "erpc_threading.h" + +#include +#include + +// Set this macro to 0 if you do not want debug prints. +#define LWIPTCP_TRANSPORT_DEBUG_LOG 1 + +#if LWIPTCP_TRANSPORT_DEBUG_LOG +#define LWIPTCP_DEBUG_PRINT(_fmt_, ...) PRINTF(_fmt_ "\n\r", ##__VA_ARGS__) +#endif + + +extern "C" { +#include "lwip/altcp.h" +#include "lwip/altcp_tcp.h" +#include "lwip/sockets.h" +} + + +namespace erpc +{ + + +/** + * @class LwipTCPTransport + * @brief Transport layer implementation using LwIP over TCP for eRPC. + * + * This class provides a framed transport interface over a TCP connection + * using the Lightweight IP (LwIP) stack. It can operate in both client and + * server modes depending on configuration. + */ +class LwipTCPTransport : public FramedTransport +{ +public: + /** + * @brief Constructor for LwipTCPTransport. + * + * @param host IP address or hostname to connect to or bind to. + * @param port Port number for connection. + * @param isServer Set to true if the instance should act as a server. + */ + LwipTCPTransport(const char* host, uint16_t port, bool isServer); + + + virtual ~LwipTCPTransport(); + + + /** + * @brief Configures the transport with new host, port, and mode. + * + * @param host IP address or hostname. + * @param port Port number to use. + * @param isServer If true, transport acts as a server. + */ + virtual void configure(const char* host, uint16_t port, bool isServer); + + + /** + * @brief Opens the transport (e.g., starts server thread or connects to the server). + * + * @retval kErpcStatus_Success If transport is successfully opened. + * @retval kErpcStatus_Fail If opening transport fails. + */ + virtual erpc_status_t openTransport(); + + + /** + * @brief Closes the transport (e.g., releases sockets and resources). + * + * @retval kErpcStatus_Success If transport is successfully closed. + * @retval kErpcStatus_Fail If closing transport fails. + */ + virtual erpc_status_t closeTransport(); + + + /*! + * @brief Connects client to server. + * + * @retval kErpcStatus_Success Successful connection to the server + * @retval kErpcStatus_Fail Failed to create socket + * @retval kErpcStatus_ConnectionFailure Connection attempt failed + */ + virtual erpc_status_t connectClient(); + + // TODO unused. + virtual erpc_status_t disconnect(); + + + /*! + * @brief Starts server. + * + * @retval kErpcStatus_Success If server has shutdown correctly + * @retval kErpcStatus_Fail If initialisation of server failed + * @retval kErpcStatus_ConnectionFailure accept() call failed + */ + virtual erpc_status_t startServer(); + + + /*! + * @brief Receive data from lwIP TCP/IP stack using socket API. + * + * + * @param[inout] data Preallocated buffer for receiving data. + * @param[in] size Size of data to read. + * + * @retval kErpcStatus_Connection Closed Connection was closed by peer. + * @retval kErpcStatus_Success Successfully received all data. + * @retval kErpcStatus_ReceiveFailed read() call failed. + */ + virtual erpc_status_t underlyingReceive(uint8_t *data, uint32_t size) override; + + + /*! + * @brief Write data to lwIP socket. + * + * @param[in] data Buffer to send. + * @param[in] size Size of data to send. + * + * @retval kErpcStatus_Success Data was sent successfully. + * @retval kErpcStatus_SendFailed Failed to send data. + */ + virtual erpc_status_t underlyingSend(const uint8_t *data, uint32_t size) override; + + + /*! + * @brief Returns true if connection is established. + * This prevents blocking of the task that is polling the eRPC server + * when there is no connection established. However, if connection is + * established, then said task will be blocked on underlyingReceive's + * read() call until requested amount of data came through the socket. + * Name of this method is therefore not accurate. + */ + virtual bool hasMessage(void); + + + /*! + * @brief Stub for server thread (only static methods can be made tasks) + * + * @param[in] arg Instance of this class is passed through this argument to call server method. + */ + static void serverThreadStub(void *arg); + + +protected: + const char *m_host; /*!< String specifying host. Can only be IP address at the moment. + Irrelevant if m_isServer is true */ + bool m_isServer; /*!< Instance's role. */ + uint16_t m_port; /*!< Specify the listening port number. */ + bool m_runServer; /*!< Indicates server state. */ + + int m_clientSock; /*!< Socket connected to the peer */ + int m_serverSock; /*!< Server's listening socket. Unused in client mode */ + + Thread m_serverThread; /*!< Server thread. */ +}; + +} // erpc + +#endif //EVKMIMXRT1060_ERPC_LWIP_EXAMPLE_LWIPTCPTRANSPORT_HPP