From 8213b6836812ab8db180dc29fa134ecc6ade10db Mon Sep 17 00:00:00 2001 From: Lars Toenning Date: Mon, 18 Sep 2023 10:34:39 +0200 Subject: [PATCH] feat(daisi): Add TCP network interface --- .github/workflows/build-sola.yml | 1 + cppcheck.conf | 1 + daisi/src/CMakeLists.txt | 1 + daisi/src/network_tcp/CMakeLists.txt | 53 +++++++ daisi/src/network_tcp/client.cpp | 131 +++++++++++++++++ daisi/src/network_tcp/client.h | 87 +++++++++++ daisi/src/network_tcp/definitions.h | 35 +++++ daisi/src/network_tcp/framing_manager.cpp | 105 ++++++++++++++ daisi/src/network_tcp/framing_manager.h | 59 ++++++++ daisi/src/network_tcp/server.cpp | 136 ++++++++++++++++++ daisi/src/network_tcp/server.h | 96 +++++++++++++ daisi/tests/unittests/CMakeLists.txt | 2 + .../unittests/network_tcp/CMakeLists.txt | 10 ++ .../network_tcp/framing_manager_test.cpp | 128 +++++++++++++++++ 14 files changed, 845 insertions(+) create mode 100644 daisi/src/network_tcp/CMakeLists.txt create mode 100644 daisi/src/network_tcp/client.cpp create mode 100644 daisi/src/network_tcp/client.h create mode 100644 daisi/src/network_tcp/definitions.h create mode 100644 daisi/src/network_tcp/framing_manager.cpp create mode 100644 daisi/src/network_tcp/framing_manager.h create mode 100644 daisi/src/network_tcp/server.cpp create mode 100644 daisi/src/network_tcp/server.h create mode 100644 daisi/tests/unittests/network_tcp/CMakeLists.txt create mode 100644 daisi/tests/unittests/network_tcp/framing_manager_test.cpp diff --git a/.github/workflows/build-sola.yml b/.github/workflows/build-sola.yml index e37cef5f..5707f00a 100644 --- a/.github/workflows/build-sola.yml +++ b/.github/workflows/build-sola.yml @@ -143,6 +143,7 @@ jobs: build/tests/unittests/DaisiDatastructureSimpleTemporalNetworkTest build/tests/unittests/DaisiCppsOrderManagementStnOrderManagement build/tests/unittests/DaisiCppsLogicalAuctionParticipantState + build/tests/unittests/network_tcp/daisi_network_tcp_framing_manager_test - name: Run MINHTON integrationtest run: | export LD_LIBRARY_PATH=~/ns-3_install/lib diff --git a/cppcheck.conf b/cppcheck.conf index 45267126..9d3bd559 100644 --- a/cppcheck.conf +++ b/cppcheck.conf @@ -21,6 +21,7 @@ unusedVariable:daisi/src/cpps/negotiation/initiator/iterated_auction_initiator_p unusedVariable:daisi/src/cpps/negotiation/utils/kmeans.h unusedVariable:daisi/src/cpps/negotiation/participant/iterated_auction_participant.cpp unusedVariable:daisi/src/cpps/negotiation/utils/simple_temporal_network.cpp +unusedVariable:daisi/src/network_tcp/server.cpp # From SERIALIZE macro constParameter:daisi/src/path_planning/message/misc/reached_goal.h diff --git a/daisi/src/CMakeLists.txt b/daisi/src/CMakeLists.txt index fc721421..f24de3eb 100644 --- a/daisi/src/CMakeLists.txt +++ b/daisi/src/CMakeLists.txt @@ -4,6 +4,7 @@ add_subdirectory(main) add_subdirectory(cpps) add_subdirectory(minhton-ns3) add_subdirectory(natter-ns3) +add_subdirectory(network_tcp) add_subdirectory(manager) #add_subdirectory(path_planning) add_subdirectory(sola-ns3) diff --git a/daisi/src/network_tcp/CMakeLists.txt b/daisi/src/network_tcp/CMakeLists.txt new file mode 100644 index 00000000..7107fa6c --- /dev/null +++ b/daisi/src/network_tcp/CMakeLists.txt @@ -0,0 +1,53 @@ +add_library(daisi_network_tcp_definitions INTERFACE definitions.h) + +add_library(daisi_network_tcp_framing_manager STATIC) +target_sources(daisi_network_tcp_framing_manager + PRIVATE + framing_manager.cpp + framing_manager.h +) +target_link_libraries(daisi_network_tcp_framing_manager + PRIVATE + daisi_utils +) +target_include_directories(daisi_network_tcp_framing_manager + PUBLIC + ${DAISI_SOURCE_DIR}/src +) + +add_library(daisi_network_tcp_client STATIC) +target_sources(daisi_network_tcp_client + PRIVATE + client.h + client.cpp +) +target_link_libraries(daisi_network_tcp_client + PUBLIC + daisi_network_tcp_framing_manager + ns3::libcore + daisi_network_tcp_definitions + PRIVATE + daisi_utils +) +target_include_directories(daisi_network_tcp_client + PUBLIC + ${DAISI_SOURCE_DIR}/src +) + +add_library(daisi_network_tcp_server STATIC) +target_sources(daisi_network_tcp_server + PRIVATE + server.h + server.cpp +) +target_link_libraries(daisi_network_tcp_server + PUBLIC + ns3::libcore + daisi_network_tcp_framing_manager + PRIVATE + daisi_utils +) +target_include_directories(daisi_network_tcp_server + PUBLIC + ${DAISI_SOURCE_DIR}/src +) diff --git a/daisi/src/network_tcp/client.cpp b/daisi/src/network_tcp/client.cpp new file mode 100644 index 00000000..3384c2bc --- /dev/null +++ b/daisi/src/network_tcp/client.cpp @@ -0,0 +1,131 @@ +// Copyright 2023 The SOLA authors +// +// This file is part of DAISI. +// +// DAISI is free software: you can redistribute it and/or modify it under the terms of the GNU +// General Public License as published by the Free Software Foundation; version 2. +// +// DAISI is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even +// the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General +// Public License for more details. +// +// You should have received a copy of the GNU General Public License along with DAISI. If not, see +// . +// +// SPDX-License-Identifier: GPL-2.0-only + +#include "client.h" + +#include "utils/daisi_check.h" +#include "utils/socket_manager.h" +#include "utils/sola_utils.h" + +namespace daisi::network_tcp { + +Client::Client(ClientCallbacks callbacks, const Endpoint &endpoint) + : Client("", callbacks, endpoint) {} + +Client::Client(const Ipv4 &ip, ClientCallbacks callbacks, const Endpoint &endpoint) + : socket_(daisi::SocketManager::get().createSocket(daisi::SocketType::kTCP)), + callbacks_(std::move(callbacks)) { + socket_->SetConnectCallback(MakeCallback(&Client::connectedSuccessful, this), + MakeCallback(&Client::connectionFailed, this)); + socket_->SetCloseCallbacks(MakeCallback(&Client::closedSocket, this), + MakeCallback(&Client::closedSocket, this)); + socket_->SetRecvCallback(MakeCallback(&Client::readFromSocket, this)); + + // https://groups.google.com/g/ns-3-users/c/tZmjq_KoCfo/m/x1xBvn-H31gJ + ns3::Address addr; + socket_->GetSockName(addr); + auto iaddr = ns3::InetSocketAddress::ConvertFrom(addr); + + ip_ = daisi::getIpv4AddressString(iaddr.GetIpv4()); + port_ = iaddr.GetPort(); + + if (!ip.empty() && ip != ip_) { + throw std::runtime_error("IP requested from application does not match socket IP"); + } + + // Connect to remote + ns3::InetSocketAddress remote_addr(endpoint.ip.c_str(), endpoint.port); + socket_->Connect(remote_addr); +} + +Client::~Client() { + if (socket_) { + socket_->SetRecvCallback(ns3::MakeNullCallback>()); + socket_->SetCloseCallbacks(ns3::MakeNullCallback>(), + ns3::MakeNullCallback>()); + socket_->Close(); + + if (callbacks_.disconnected_cb) { + callbacks_.disconnected_cb(); + } + } +} + +void Client::send(const std::string &msg) { + DAISI_CHECK(connected_, "Not connected"); + DAISI_CHECK(socket_, "Socket not available"); + + std::string framed_msg = FramingManager::frameMsg(msg); + + DAISI_CHECK(connected_, "Not connected"); + const auto res = + socket_->Send(reinterpret_cast(framed_msg.data()), framed_msg.size(), 0); + + if (res != framed_msg.size()) { + throw std::runtime_error("sending failed"); + } +} + +uint16_t Client::getPort() const { return port_; } + +Ipv4 Client::getIP() const { return ip_; } + +void Client::readFromSocket(ns3::Ptr socket) { + DAISI_CHECK(socket == socket_, "Reading from invalid socket"); + ns3::Ptr packet = socket_->Recv(); + processPacket(packet); +} + +void Client::processPacket(ns3::Ptr packet) { + DAISI_CHECK(packet != nullptr, "Invalid packet"); + + std::string data; + data.resize(packet->GetSize()); + packet->CopyData(reinterpret_cast(data.data()), packet->GetSize()); + + manager_.processNewData(data); + + if (!callbacks_.new_msg_cb) return; + + while (manager_.hasPackets()) { + callbacks_.new_msg_cb(manager_.nextPacket()); + } +} + +void Client::closedSocket(ns3::Ptr) { + connected_ = false; + socket_->SetRecvCallback(ns3::MakeNullCallback>()); + socket_->Close(); + socket_ = nullptr; + + if (callbacks_.disconnected_cb) { + callbacks_.disconnected_cb(); + } +} + +void Client::connectedSuccessful(ns3::Ptr) { + connected_ = true; + + if (callbacks_.connected_cb) { + callbacks_.connected_cb(); + } +} + +void Client::connectionFailed(ns3::Ptr) { + throw std::runtime_error("failed to connect"); +} + +} // namespace daisi::network_tcp diff --git a/daisi/src/network_tcp/client.h b/daisi/src/network_tcp/client.h new file mode 100644 index 00000000..34c40888 --- /dev/null +++ b/daisi/src/network_tcp/client.h @@ -0,0 +1,87 @@ +// Copyright 2023 The SOLA authors +// +// This file is part of DAISI. +// +// DAISI is free software: you can redistribute it and/or modify it under the terms of the GNU +// General Public License as published by the Free Software Foundation; version 2. +// +// DAISI is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even +// the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General +// Public License for more details. +// +// You should have received a copy of the GNU General Public License along with DAISI. If not, see +// . +// +// SPDX-License-Identifier: GPL-2.0-only + +#ifndef DAISI_NETWORK_TCP_CLIENT_H_ +#define DAISI_NETWORK_TCP_CLIENT_H_ + +#include +#include +#include +#include + +#include "network_tcp/definitions.h" +#include "network_tcp/framing_manager.h" +#include "ns3/socket.h" + +namespace daisi::network_tcp { + +struct ClientCallbacks { + std::function new_msg_cb; + std::function connected_cb; + std::function disconnected_cb; +}; + +/** + * ns-3 client to connect to a single listening ns-3 TCP sockets and send/recv data + */ +class Client { +public: + Client(ClientCallbacks callbacks, const Endpoint &endpoint); + + Client(const Ipv4 &own_ip, ClientCallbacks callbacks, const Endpoint &endpoint); + + ~Client(); + + // Forbid copy/move operations + Client(const Client &) = delete; + Client &operator=(const Client &) = delete; + Client(const Client &&) = delete; + Client &operator=(Client &&) = delete; + + /** + * Send message + * @param msg message to send + */ + void send(const std::string &msg); + + uint16_t getPort() const; + + Ipv4 getIP() const; + + bool isConnected() const; + +private: + void readFromSocket(ns3::Ptr socket); + void processPacket(ns3::Ptr packet); + + void closedSocket(ns3::Ptr); + void connectedSuccessful(ns3::Ptr); + void connectionFailed(ns3::Ptr); + + ns3::Ptr socket_; + + bool connected_ = false; + + FramingManager manager_; + + Ipv4 ip_; + uint16_t port_; + + const ClientCallbacks callbacks_; +}; +} // namespace daisi::network_tcp + +#endif // DAISI_NETWORK_TCP_CLIENT_H_ diff --git a/daisi/src/network_tcp/definitions.h b/daisi/src/network_tcp/definitions.h new file mode 100644 index 00000000..013013f7 --- /dev/null +++ b/daisi/src/network_tcp/definitions.h @@ -0,0 +1,35 @@ +// Copyright 2023 The SOLA authors +// +// This file is part of DAISI. +// +// DAISI is free software: you can redistribute it and/or modify it under the terms of the GNU +// General Public License as published by the Free Software Foundation; version 2. +// +// DAISI is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even +// the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General +// Public License for more details. +// +// You should have received a copy of the GNU General Public License along with DAISI. If not, see +// . +// +// SPDX-License-Identifier: GPL-2.0-only + +#ifndef DAISI_NETWORK_TCP_DEFINITIONS_H_ +#define DAISI_NETWORK_TCP_DEFINITIONS_H_ + +#include +#include + +namespace daisi::network_tcp { + +using Ipv4 = std::string; +using TcpSocketHandle = uint64_t; + +struct Endpoint { + Ipv4 ip; + uint16_t port; +}; + +} // namespace daisi::network_tcp + +#endif // DAISI_NETWORK_TCP_DEFINITIONS_H_ diff --git a/daisi/src/network_tcp/framing_manager.cpp b/daisi/src/network_tcp/framing_manager.cpp new file mode 100644 index 00000000..36c11667 --- /dev/null +++ b/daisi/src/network_tcp/framing_manager.cpp @@ -0,0 +1,105 @@ +// Copyright 2023 The SOLA authors +// +// This file is part of DAISI. +// +// DAISI is free software: you can redistribute it and/or modify it under the terms of the GNU +// General Public License as published by the Free Software Foundation; version 2. +// +// DAISI is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even +// the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General +// Public License for more details. +// +// You should have received a copy of the GNU General Public License along with DAISI. If not, see +// . +// +// SPDX-License-Identifier: GPL-2.0-only + +#include "framing_manager.h" + +#include + +#include +#include + +#include "utils/daisi_check.h" + +namespace daisi::network_tcp { +std::string FramingManager::frameMsg(const std::string &msg) { + // Prefix msg with 4 byte packet length in network byte order + const uint32_t size = htonl(msg.size()); + + std::string data; + data.resize(msg.size() + 4); + + std::memcpy(data.data(), &size, 4); + std::memcpy(data.data() + 4, msg.data(), msg.size()); + return data; +} + +uint32_t FramingManager::readPacket(const std::string &msg, uint32_t next_offset) { + DAISI_CHECK(inflight_, "No packet currently in processing state"); + + const size_t available = msg.size() - next_offset; + + const size_t read = std::min(available, inflight_->remaining_size); + + DAISI_CHECK(next_offset + read <= msg.size(), "Read out of bounds of msg"); + + const uint32_t current_pos = inflight_->packet.size() - inflight_->remaining_size; + + std::memcpy(inflight_->packet.data() + current_pos, msg.data() + next_offset, read); + inflight_->remaining_size -= read; + + if (inflight_->remaining_size == 0) { + outstanding_packets_.push_back(std::move(inflight_->packet)); + inflight_.reset(); + } + + return next_offset + read; +} + +uint32_t FramingManager::handleNewPacket(const std::string &msg, uint32_t next_offset) { + DAISI_CHECK(!inflight_, "Previous packet not finished yet"); + + const uint32_t current_packet_size = readPacketSize(msg, next_offset); + + InflightPacket packet; + packet.remaining_size = current_packet_size; + packet.packet.resize(current_packet_size); + + inflight_ = std::move(packet); + + return readPacket(msg, next_offset + 4); +} + +void FramingManager::processNewData(const std::string &msg) { + size_t next = 0; + while (next < msg.size()) { + if (inflight_) { + next = readPacket(msg, next); + } else { + next = handleNewPacket(msg, next); + } + } +} + +uint32_t FramingManager::readPacketSize(const std::string &msg, uint32_t next_offset) { + DAISI_CHECK(next_offset + 4 <= msg.size(), + "Read out of bounds: Message splitted in length delimiter. Not supported yet!"); + + uint32_t current_packet_size = 0; + std::memcpy(¤t_packet_size, msg.data() + next_offset, 4); + return ntohl(current_packet_size); +} + +bool FramingManager::hasPackets() const { return !outstanding_packets_.empty(); } + +std::string FramingManager::nextPacket() { + DAISI_CHECK(hasPackets(), "No packets available"); + + std::string msg = outstanding_packets_.front(); + outstanding_packets_.pop_front(); + return msg; +} + +} // namespace daisi::network_tcp diff --git a/daisi/src/network_tcp/framing_manager.h b/daisi/src/network_tcp/framing_manager.h new file mode 100644 index 00000000..53b261e5 --- /dev/null +++ b/daisi/src/network_tcp/framing_manager.h @@ -0,0 +1,59 @@ +// Copyright 2023 The SOLA authors +// +// This file is part of DAISI. +// +// DAISI is free software: you can redistribute it and/or modify it under the terms of the GNU +// General Public License as published by the Free Software Foundation; version 2. +// +// DAISI is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even +// the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General +// Public License for more details. +// +// You should have received a copy of the GNU General Public License along with DAISI. If not, see +// . +// +// SPDX-License-Identifier: GPL-2.0-only + +#ifndef DAISI_NETWORK_TCP_FRAMING_MANAGER_H_ +#define DAISI_NETWORK_TCP_FRAMING_MANAGER_H_ + +#include +#include +#include +#include + +namespace daisi::network_tcp { + +class FramingManager { +public: + void processNewData(const std::string &msg); + + bool hasPackets() const; + + std::string nextPacket(); + + static std::string frameMsg(const std::string &msg); + +private: + /// Extract length prefix from \p msg, starting at \p next_offset + static uint32_t readPacketSize(const std::string &msg, uint32_t next_offset); + + /// Process a new packet contained in \p msg, starting at \p next_offset + uint32_t handleNewPacket(const std::string &msg, uint32_t next_offset); + + /// Process content of \p msg into the current inflight/processing packet. + /// \p next_offset should contain the index of the first PAYLOAD byte (not the length prefix) + uint32_t readPacket(const std::string &msg, uint32_t next_offset); + + std::deque outstanding_packets_; + + struct InflightPacket { + std::string packet; /// payload + size_t remaining_size; + }; + + std::optional inflight_; /// The current inflight packet +}; +} // namespace daisi::network_tcp + +#endif // DAISI_NETWORK_TCP_FRAMING_MANAGER_H_ diff --git a/daisi/src/network_tcp/server.cpp b/daisi/src/network_tcp/server.cpp new file mode 100644 index 00000000..819148c4 --- /dev/null +++ b/daisi/src/network_tcp/server.cpp @@ -0,0 +1,136 @@ +// Copyright 2023 The SOLA authors +// +// This file is part of DAISI. +// +// DAISI is free software: you can redistribute it and/or modify it under the terms of the GNU +// General Public License as published by the Free Software Foundation; version 2. +// +// DAISI is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even +// the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General +// Public License for more details. +// +// You should have received a copy of the GNU General Public License along with DAISI. If not, see +// . +// +// SPDX-License-Identifier: GPL-2.0-only + +#include "server.h" + +#include "utils/daisi_check.h" +#include "utils/socket_manager.h" +#include "utils/sola_utils.h" + +namespace daisi::network_tcp { + +Server::Server(ServerCallbacks callbacks) : Server("", std::move(callbacks)) {} + +Server::Server(const Ipv4 &ip, ServerCallbacks callbacks) + : listening_socket_(daisi::SocketManager::get().createSocket(daisi::SocketType::kTCP)), + callbacks_(std::move(callbacks)) { + listening_socket_->SetAcceptCallback(ns3::MakeCallback(&Server::connectionRequest, this), + ns3::MakeCallback(&Server::newConnectionCreated, this)); + + // https://groups.google.com/g/ns-3-users/c/tZmjq_KoCfo/m/x1xBvn-H31gJ + ns3::Address addr; + listening_socket_->GetSockName(addr); + auto iaddr = ns3::InetSocketAddress::ConvertFrom(addr); + + ip_ = daisi::getIpv4AddressString(iaddr.GetIpv4()); + port_ = iaddr.GetPort(); + + if (!ip.empty() && ip != ip_) { + throw std::runtime_error("IP requested from application does not match socket IP"); + } + + const int res = listening_socket_->Listen(); + DAISI_CHECK(res == 0, "Failed to Listen()"); +} + +Server::~Server() { + listening_socket_->SetAcceptCallback( + ns3::MakeNullCallback, const ns3::Address &>(), + ns3::MakeNullCallback, const ns3::Address &>()); + listening_socket_->Close(); + + for (const auto &[_, connection] : connections_) { + connection.socket->SetRecvCallback(ns3::MakeNullCallback>()); + connection.socket->SetCloseCallbacks(ns3::MakeNullCallback>(), + ns3::MakeNullCallback>()); + + connection.socket->Close(); + } +} + +void Server::send(TcpSocketHandle receiver, const std::string &msg) { + std::string framed_msg = FramingManager::frameMsg(msg); + + DAISI_CHECK(connections_.count(receiver) == 1, "Handle not valid"); + const int res = connections_[receiver].socket->Send( + reinterpret_cast(framed_msg.data()), framed_msg.size(), 0); + + if (res != framed_msg.size()) { + throw std::runtime_error("sending failed"); + } +} + +uint16_t Server::getPort() const { return port_; } + +Ipv4 Server::getIP() const { return ip_; } + +void Server::readFromSocket(TcpSocketHandle handle, ns3::Ptr socket) { + DAISI_CHECK(socket == connections_[handle].socket, "Reading from wrong socket"); + ns3::Ptr packet = socket->Recv(); + processPacket(packet, handle); +} + +void Server::processPacket(ns3::Ptr packet, TcpSocketHandle sender) { + std::string data; + data.resize(packet->GetSize()); + packet->CopyData(reinterpret_cast(data.data()), packet->GetSize()); + + DAISI_CHECK(connections_.count(sender) == 1, "Invalid TCP connection"); + + FramingManager &manager = connections_[sender].manager; + manager.processNewData(data); + + if (!callbacks_.new_msg_cb) return; + + while (manager.hasPackets()) { + callbacks_.new_msg_cb(sender, manager.nextPacket()); + } +} + +void Server::successfulConnect(ns3::Ptr) {} + +void Server::failedConnect(ns3::Ptr) { throw std::runtime_error("failed to connect"); } + +bool Server::connectionRequest(ns3::Ptr, const ns3::Address &) { + // Always accept + return true; +} + +void Server::newConnectionCreated(ns3::Ptr socket, const ns3::Address &addr) { + const auto inet_addr = ns3::InetSocketAddress::ConvertFrom(addr); + const std::string ip = daisi::getIpv4AddressString(inet_addr.GetIpv4()); + const uint16_t port = inet_addr.GetPort(); + if (callbacks_.client_connected_cb) { + callbacks_.client_connected_cb(next_handle_, ip, port); + } + + socket->SetCloseCallbacks(ns3::MakeCallback(&Server::handleClose, this, next_handle_), + ns3::MakeCallback(&Server::handleClose, this, next_handle_)); + socket->SetRecvCallback(ns3::MakeCallback(&Server::readFromSocket, this, next_handle_)); + connections_[next_handle_++] = {socket, {}}; +} + +void Server::handleClose(TcpSocketHandle handle, ns3::Ptr socket) { + if (callbacks_.client_disconnected_cb) { + callbacks_.client_disconnected_cb(handle); + } + + socket->Close(); + socket->SetRecvCallback(ns3::MakeNullCallback>()); + connections_.erase(handle); +} + +} // namespace daisi::network_tcp diff --git a/daisi/src/network_tcp/server.h b/daisi/src/network_tcp/server.h new file mode 100644 index 00000000..8098cba3 --- /dev/null +++ b/daisi/src/network_tcp/server.h @@ -0,0 +1,96 @@ +// Copyright 2023 The SOLA authors +// +// This file is part of DAISI. +// +// DAISI is free software: you can redistribute it and/or modify it under the terms of the GNU +// General Public License as published by the Free Software Foundation; version 2. +// +// DAISI is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even +// the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General +// Public License for more details. +// +// You should have received a copy of the GNU General Public License along with DAISI. If not, see +// . +// +// SPDX-License-Identifier: GPL-2.0-only + +#ifndef DAISI_NETWORK_TCP_SERVER_H_ +#define DAISI_NETWORK_TCP_SERVER_H_ + +#include +#include +#include + +#include "network_tcp/definitions.h" +#include "network_tcp/framing_manager.h" +#include "ns3/socket.h" + +namespace daisi::network_tcp { + +struct ServerCallbacks { + std::function new_msg_cb; + std::function client_connected_cb; + std::function client_disconnected_cb; +}; + +/** + * Class representing a ns-3 server listens for incoming ns-3 TCP connections and send/recv data + */ +class Server { +public: + explicit Server(ServerCallbacks callbacks); + + Server(const Ipv4 &ip, ServerCallbacks callbacks); + + ~Server(); + + // Forbid copy/move operations + Server(const Server &) = delete; + Server &operator=(const Server &) = delete; + Server(const Server &&) = delete; + Server &operator=(Server &&) = delete; + + /** + * Send message + * @param receiver Receiver to send msg to + * @param msg message to send + */ + void send(TcpSocketHandle receiver, const std::string &msg); + + uint16_t getPort() const; + + Ipv4 getIP() const; + +private: + ns3::Ptr listening_socket_; + + std::string ip_; + uint16_t port_; + + const ServerCallbacks callbacks_; + + struct TcpConnection { + ns3::Ptr socket; + FramingManager manager; + }; + + // Handle number for next incoming connection + TcpSocketHandle next_handle_ = 1; + + // Map of all currently open connections + std::unordered_map connections_; + + void readFromSocket(TcpSocketHandle handle, ns3::Ptr socket); + void processPacket(ns3::Ptr packet, TcpSocketHandle sender); + + void handleClose(TcpSocketHandle handle, ns3::Ptr socket); + + bool connectionRequest(ns3::Ptr, const ns3::Address &); + void newConnectionCreated(ns3::Ptr socket, const ns3::Address &addr); + + void successfulConnect(ns3::Ptr); + void failedConnect(ns3::Ptr); +}; +} // namespace daisi::network_tcp + +#endif // DAISI_NETWORK_TCP_SERVER_H_ diff --git a/daisi/tests/unittests/CMakeLists.txt b/daisi/tests/unittests/CMakeLists.txt index 2b36762c..ed60c96f 100644 --- a/daisi/tests/unittests/CMakeLists.txt +++ b/daisi/tests/unittests/CMakeLists.txt @@ -1,3 +1,5 @@ +add_subdirectory(network_tcp) + add_executable(NatterStaticCalculationTest "") target_sources(NatterStaticCalculationTest PRIVATE diff --git a/daisi/tests/unittests/network_tcp/CMakeLists.txt b/daisi/tests/unittests/network_tcp/CMakeLists.txt new file mode 100644 index 00000000..0f0cb06a --- /dev/null +++ b/daisi/tests/unittests/network_tcp/CMakeLists.txt @@ -0,0 +1,10 @@ +add_executable(daisi_network_tcp_framing_manager_test "") +target_sources(daisi_network_tcp_framing_manager_test + PRIVATE + framing_manager_test.cpp +) +target_link_libraries(daisi_network_tcp_framing_manager_test + PRIVATE + Catch2::Catch2WithMain + daisi_network_tcp_framing_manager +) diff --git a/daisi/tests/unittests/network_tcp/framing_manager_test.cpp b/daisi/tests/unittests/network_tcp/framing_manager_test.cpp new file mode 100644 index 00000000..6558c1df --- /dev/null +++ b/daisi/tests/unittests/network_tcp/framing_manager_test.cpp @@ -0,0 +1,128 @@ +// Copyright 2023 The SOLA authors +// +// This file is part of DAISI. +// +// DAISI is free software: you can redistribute it and/or modify it under the terms of the GNU +// General Public License as published by the Free Software Foundation; version 2. +// +// DAISI is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even +// the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General +// Public License for more details. +// +// You should have received a copy of the GNU General Public License along with DAISI. If not, see +// . +// +// SPDX-License-Identifier: GPL-2.0-only + +#include "network_tcp/framing_manager.h" + +#include + +#include +#include +#include + +using namespace daisi::network_tcp; + +TEST_CASE("Frame message", "[frame_message]") { + const std::string a = "ABCDEF"; + + const std::string framed = FramingManager::frameMsg(a); + + REQUIRE(framed.size() == 10); + REQUIRE(framed.substr(4) == a); + + const std::string prefix = framed.substr(0, 4); + uint32_t *x = (uint32_t *)(prefix.data()); + REQUIRE(*x == htonl(6)); +} + +TEST_CASE("Frame empty message", "[frame_empty_message]") { + const std::string a = ""; + + const std::string framed = FramingManager::frameMsg(a); + + REQUIRE(framed.size() == 4); + + const std::string prefix = framed.substr(0); + uint32_t *x = (uint32_t *)(prefix.data()); + REQUIRE(*x == htonl(0)); +} + +TEST_CASE("Process full messages", "[process_full_messages]") { + FramingManager manager; + REQUIRE(!manager.hasPackets()); + + std::string a = "ABCDEF"; + std::string framed1 = FramingManager::frameMsg(a); + + manager.processNewData(framed1); + + REQUIRE(manager.hasPackets()); + REQUIRE(manager.nextPacket() == a); + REQUIRE(!manager.hasPackets()); + + std::string b = "XYZ"; + std::string framed2 = FramingManager::frameMsg(b); + + manager.processNewData(framed2); + REQUIRE(manager.hasPackets()); + REQUIRE(manager.nextPacket() == b); + REQUIRE(!manager.hasPackets()); + + manager.processNewData(framed2); + manager.processNewData(framed1); + REQUIRE(manager.hasPackets()); + REQUIRE(manager.nextPacket() == b); + REQUIRE(manager.hasPackets()); + REQUIRE(manager.nextPacket() == a); + REQUIRE(!manager.hasPackets()); +} + +TEST_CASE("Process splitted messages", "[process_splitted_messages]") { + FramingManager manager; + REQUIRE(!manager.hasPackets()); + + std::string a = "ABCDEF"; + std::string framed1 = FramingManager::frameMsg(a); + + std::string sub1 = framed1.substr(0, 6); + std::string sub2 = framed1.substr(6, 2); + std::string sub3 = framed1.substr(8); + + manager.processNewData(sub1); + REQUIRE(!manager.hasPackets()); + + manager.processNewData(sub2); + REQUIRE(!manager.hasPackets()); + + manager.processNewData(sub3); + REQUIRE(manager.hasPackets()); + REQUIRE(manager.nextPacket() == a); + REQUIRE(!manager.hasPackets()); + + std::string b = "XYZ"; + std::string framed2 = FramingManager::frameMsg(b); + + std::string sub4 = framed2.substr(0, 5); + std::string sub5 = framed2.substr(5); + + manager.processNewData(sub4); + REQUIRE(!manager.hasPackets()); + REQUIRE_THROWS(manager.nextPacket()); + + manager.processNewData(sub5); + REQUIRE(manager.hasPackets()); + REQUIRE(manager.nextPacket() == b); + REQUIRE(!manager.hasPackets()); + + manager.processNewData(framed1 + sub4); + REQUIRE(manager.hasPackets()); + REQUIRE(manager.nextPacket() == a); + REQUIRE(!manager.hasPackets()); + + manager.processNewData(sub5); + REQUIRE(manager.hasPackets()); + REQUIRE(manager.nextPacket() == b); + REQUIRE(!manager.hasPackets()); +}