diff --git a/app/joystick_data_channel/src/main.cpp b/app/joystick_data_channel/src/main.cpp index 2986ab5e1..9804017b8 100644 --- a/app/joystick_data_channel/src/main.cpp +++ b/app/joystick_data_channel/src/main.cpp @@ -71,8 +71,8 @@ int main(int argc, char* argv[]) // -------------------------------------------------------- // 1️⃣ POSIX SIGNALS // -------------------------------------------------------- - // std::signal(SIGINT, signal_handler); - // std::signal(SIGTERM, signal_handler); + std::signal(SIGINT, signal_handler); + std::signal(SIGTERM, signal_handler); // -------------------------------------------------------- // 2️⃣ RTE / EVENT SYSTEM INIT @@ -88,22 +88,22 @@ int main(int argc, char* argv[]) // -------------------------------------------------------- // 4️⃣ TRANSPORT INIT // -------------------------------------------------------- - //std::unique_ptr transport; + std::unique_ptr transport; -// #if 1 -// // ===== REAL WebRTC ===== -// transport = std::make_unique(); -// #else -// // ===== LOOPBACK TEST ===== -// transport = std::make_unique(); -// #endif +#if 1 + // ===== REAL WebRTC ===== + transport = std::make_unique(); +#else + // ===== LOOPBACK TEST ===== + transport = std::make_unique(); +#endif - // // EventHandler consumes RX frames from transport - // transport->set_rx_callback([](const std::vector& data) { - // EventHandler::on_transport_rx(data); - // }); + // EventHandler consumes RX frames from transport + transport->set_rx_callback([](const std::vector& data) { + EventHandler::on_transport_rx(data); + }); - // transport->start(); + transport->start(); // -------------------------------------------------------- // 5️⃣ RUNNABLE THREADS (AUTOSAR style) diff --git a/app/libdc_udp_demo_testing/CMakeLists.txt b/app/libdc_udp_demo_testing/CMakeLists.txt new file mode 100644 index 000000000..446e7142a --- /dev/null +++ b/app/libdc_udp_demo_testing/CMakeLists.txt @@ -0,0 +1,89 @@ +cmake_minimum_required(VERSION 3.10) +project(libdc_udp_demo LANGUAGES CXX) + +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD_REQUIRED ON) + +find_package(Threads REQUIRED) +find_package(PkgConfig QUIET) + +# Try pkg-config first +set(LIBDATACHANNEL_LIBS "") +set(LIBDATACHANNEL_INCLUDE_DIRS "") +set(LIBDATACHANNEL_LIBRARY_DIRS "") + +if (PKG_CONFIG_FOUND) + pkg_check_modules(LIBDATACHANNEL libdatachannel) +endif() + +if (LIBDATACHANNEL_FOUND) + message(STATUS "Found libdatachannel via pkg-config") + list(APPEND LIBDATACHANNEL_INCLUDE_DIRS ${LIBDATACHANNEL_INCLUDE_DIRS}) + list(APPEND LIBDATACHANNEL_LIBRARY_DIRS ${LIBDATACHANNEL_LIBRARY_DIRS}) + set(LIBDATACHANNEL_LIBS ${LIBDATACHANNEL_LIBRARIES}) +else() + message(STATUS "pkg-config couldn't find libdatachannel.") + message(STATUS "You can export PKG_CONFIG_PATH or set environment variables LIBDATACHANNEL_INCLUDE_DIR and LIBDATACHANNEL_LIB before running cmake.") + # Allow manual override via environment variables: + if (DEFINED ENV{LIBDATACHANNEL_INCLUDE_DIR}) + set(LIBDATACHANNEL_INCLUDE_DIRS $ENV{LIBDATACHANNEL_INCLUDE_DIR}) + message(STATUS "Using LIBDATACHANNEL_INCLUDE_DIR = ${LIBDATACHANNEL_INCLUDE_DIRS}") + else() + # common default + set(LIBDATACHANNEL_INCLUDE_DIRS /usr/local/include) + message(STATUS "Defaulting LIBDATACHANNEL_INCLUDE_DIRS = ${LIBDATACHANNEL_INCLUDE_DIRS}") + endif() + + if (DEFINED ENV{LIBDATACHANNEL_LIB}) + # user can provide full path or linker name(s) + set(LIBDATACHANNEL_LIBS $ENV{LIBDATACHANNEL_LIB}) + message(STATUS "Using LIBDATACHANNEL_LIB = ${LIBDATACHANNEL_LIBS}") + else() + # sensible default linker names (adjust if your lib files differ) + set(LIBDATACHANNEL_LIBS datachannel sctp ssl crypto pthread dl) + message(STATUS "Defaulting LIBDATACHANNEL_LIBS = ${LIBDATACHANNEL_LIBS}") + endif() + + # optional library directories (if user set PKG_CONFIG_PATH not used) + if (DEFINED ENV{LIBDATACHANNEL_LIBRARY_DIR}) + set(LIBDATACHANNEL_LIBRARY_DIRS $ENV{LIBDATACHANNEL_LIBRARY_DIR}) + link_directories(${LIBDATACHANNEL_LIBRARY_DIRS}) + else() + # we assume /usr/local/lib is in link path - you can add more if needed + link_directories(/usr/local/lib) + endif() +endif() + +# include dirs for our headers +include_directories(${CMAKE_SOURCE_DIR}/src) +# also include libdatachannel headers +include_directories(${LIBDATACHANNEL_INCLUDE_DIRS}) + +# add signaling static lib +add_library(signaling STATIC src/udp_signaling.cpp src/udp_signaling.hpp) + +# targets +add_executable(peer_server src/peer_server.cpp) +add_executable(peer_client src/peer_client.cpp) + +# make sure signaling target exposes include dir (for consumers) +target_include_directories(signaling PUBLIC ${CMAKE_SOURCE_DIR}/src) + +# Link libraries: correct ordering and visibility +# Note: target_link_libraries( ) +target_link_libraries(peer_server + PRIVATE + signaling + ${LIBDATACHANNEL_LIBS} + Threads::Threads +) + +target_link_libraries(peer_client + PRIVATE + signaling + ${LIBDATACHANNEL_LIBS} + Threads::Threads +) + +# install rules (optional) +install(TARGETS peer_server peer_client DESTINATION bin) diff --git a/app/libdc_udp_demo_testing/src/base64.hpp b/app/libdc_udp_demo_testing/src/base64.hpp new file mode 100644 index 000000000..769191173 --- /dev/null +++ b/app/libdc_udp_demo_testing/src/base64.hpp @@ -0,0 +1,47 @@ +#pragma once +#include +#include + +static const std::string b64_chars = + "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "abcdefghijklmnopqrstuvwxyz" + "0123456789+/"; + +inline std::string b64_encode(const std::string &in) +{ + std::string out; + int val = 0, valb = -6; + for (uint8_t c : in) { + val = (val << 8) + c; + valb += 8; + while (valb >= 0) { + out.push_back(b64_chars[(val >> valb) & 0x3F]); + valb -= 6; + } + } + if (valb > -6) + out.push_back(b64_chars[((val << 8) >> (valb + 8)) & 0x3F]); + while (out.size() % 4) + out.push_back('='); + return out; +} + +inline std::string b64_decode(const std::string &in) +{ + std::vector T(256, -1); + for (int i = 0; i < 64; i++) + T[b64_chars[i]] = i; + + std::string out; + int val = 0, valb = -8; + for (uint8_t c : in) { + if (T[c] == -1) break; + val = (val << 6) + T[c]; + valb += 6; + if (valb >= 0) { + out.push_back(char((val >> valb) & 0xFF)); + valb -= 8; + } + } + return out; +} diff --git a/app/libdc_udp_demo_testing/src/peer_client.cpp b/app/libdc_udp_demo_testing/src/peer_client.cpp new file mode 100644 index 000000000..19d8c8d3a --- /dev/null +++ b/app/libdc_udp_demo_testing/src/peer_client.cpp @@ -0,0 +1,89 @@ +#include +#include +#include + +#include "udp_signaling.hpp" +#include "base64.hpp" + +int main(int argc, char** argv) +{ + if (argc < 2) + { + std::cout << "peer_client \n"; + return 0; + } + + UdpSignaling signaling; + signaling.init(nullptr, 0, argv[1], 6000); + signaling.start(); + + rtc::Configuration cfg; + cfg.iceServers.emplace_back("stun:stun.l.google.com:19302"); + + auto pc = std::make_shared(cfg); + bool answerOnce = false; + + auto dc = pc->createDataChannel("dc"); + + dc->onOpen([]() + { + std::cout << "[Client] DC OPEN\n"; + }); + + pc->onLocalDescription([&](rtc::Description desc) + { + std::string msg = + "SDP|" + b64_encode(std::string(desc)); + + signaling.send( + (uint8_t*)msg.data(), + msg.size()); + + std::cout << "[Client] Sent OFFER\n"; + }); + + pc->onLocalCandidate([&](rtc::Candidate c) + { + std::string msg = + "ICE|" + b64_encode(c.candidate()) + + "|" + b64_encode(c.mid()); + + signaling.send( + (uint8_t*)msg.data(), + msg.size()); + }); + + signaling.setReceiveCallback( + [&](const uint8_t* data, + size_t len, + uint32_t, + uint16_t) + { + std::string msg((char*)data, len); + + if (!answerOnce && + msg.rfind("SDP|", 0) == 0) + { + pc->setRemoteDescription( + rtc::Description( + b64_decode(msg.substr(4)), + rtc::Description::Type::Answer)); + + answerOnce = true; + std::cout << "[Client] Got ANSWER\n"; + } + else if (msg.rfind("ICE|", 0) == 0) + { + auto p = msg.find('|', 4); + pc->addRemoteCandidate( + rtc::Candidate( + b64_decode(msg.substr(4, p - 4)), + b64_decode(msg.substr(p + 1)))); + } + }); + + pc->setLocalDescription(); + + while (true) + std::this_thread::sleep_for(std::chrono::seconds(1)); +} diff --git a/app/libdc_udp_demo_testing/src/peer_server.cpp b/app/libdc_udp_demo_testing/src/peer_server.cpp new file mode 100644 index 000000000..fa1c8fabc --- /dev/null +++ b/app/libdc_udp_demo_testing/src/peer_server.cpp @@ -0,0 +1,137 @@ +#include +#include +#include +#include +#include + +#include "udp_signaling.hpp" +#include "base64.hpp" + +int main() +{ + std::cout << "[Server] Ready on UDP 6000\n"; + + /* ---------- UDP signaling ---------- */ + UdpSignaling signaling; + signaling.init(nullptr, 6000, nullptr, 0); + signaling.start(); + + /* ---------- PeerConnection ---------- */ + rtc::Configuration cfg; + cfg.iceServers.emplace_back("stun:stun.l.google.com:19302"); + + auto pc = std::make_shared(cfg); + + /* ---------- State ---------- */ + bool remoteSet = false; + bool answerSent = false; + std::vector iceBuf; + + /* ---------- DataChannel ---------- */ + pc->onDataChannel([&](std::shared_ptr dc) + { + std::cout << "[Server] DC received\n"; + + dc->onOpen([]() + { + std::cout << "[Server] DC OPEN\n"; + }); + + dc->onMessage([](rtc::message_variant msg) + { + if (const auto* s = std::get_if(&msg)) + std::cout << "[Server] DC msg: " << *s << "\n"; + }); + }); + + /* ---------- Local SDP ---------- */ + pc->onLocalDescription([&](rtc::Description desc) + { + /* CHỈ GỬI ANSWER */ + if (desc.type() != rtc::Description::Type::Answer) + return; + + if (answerSent) + { + std::cout << "[Server] Ignore duplicate ANSWER\n"; + return; + } + + answerSent = true; + + std::string msg = + "SDP|" + b64_encode(std::string(desc)); + + signaling.send( + reinterpret_cast(msg.data()), + msg.size()); + + std::cout << "[Server] Sent ANSWER\n"; + }); + + /* ---------- Local ICE ---------- */ + pc->onLocalCandidate([&](rtc::Candidate c) + { + std::string msg = + "ICE|" + b64_encode(c.candidate()) + + "|" + b64_encode(c.mid()); + + signaling.send( + reinterpret_cast(msg.data()), + msg.size()); + }); + + /* ---------- Receive signaling ---------- */ + signaling.setReceiveCallback( + [&](const uint8_t* data, + size_t len, + uint32_t, + uint16_t) + { + std::string msg((const char*)data, len); + + /* ===== SDP ===== */ + if (msg.rfind("SDP|", 0) == 0) + { + if (remoteSet) + { + std::cout << "[Server] Ignore duplicate OFFER\n"; + return; + } + + pc->setRemoteDescription( + rtc::Description( + b64_decode(msg.substr(4)), + rtc::Description::Type::Offer)); + + remoteSet = true; + std::cout << "[Server] Got OFFER\n"; + + /* Flush buffered ICE */ + for (auto& c : iceBuf) + pc->addRemoteCandidate(c); + iceBuf.clear(); + + /* Create ANSWER */ + pc->setLocalDescription(); + } + /* ===== ICE ===== */ + else if (msg.rfind("ICE|", 0) == 0) + { + auto p = msg.find('|', 4); + + rtc::Candidate c( + b64_decode(msg.substr(4, p - 4)), + b64_decode(msg.substr(p + 1))); + + if (!remoteSet) + iceBuf.push_back(c); + else + pc->addRemoteCandidate(c); + } + }); + + /* ---------- Loop ---------- */ + while (true) + std::this_thread::sleep_for(std::chrono::seconds(1)); +} diff --git a/app/libdc_udp_demo_testing/src/udp_signaling.cpp b/app/libdc_udp_demo_testing/src/udp_signaling.cpp new file mode 100644 index 000000000..425b4dc13 --- /dev/null +++ b/app/libdc_udp_demo_testing/src/udp_signaling.cpp @@ -0,0 +1,126 @@ +#include "udp_signaling.hpp" + +#include +#include +#include +#include + +UdpSignaling::UdpSignaling() noexcept + : sock_(-1), + running_(false), + hasRemote_(false) +{ + std::memset(&localAddr_, 0, sizeof(localAddr_)); + std::memset(&remoteAddr_, 0, sizeof(remoteAddr_)); +} + +UdpSignaling::~UdpSignaling() noexcept +{ + stop(); +} + +UdpSignaling::Result +UdpSignaling::init(const char* localIp, + std::uint16_t localPort, + const char* remoteIp, + std::uint16_t remotePort) noexcept +{ + sock_ = socket(AF_INET, SOCK_DGRAM, 0); + if (sock_ < 0) + return Result::Error; + + localAddr_.sin_family = AF_INET; + localAddr_.sin_port = htons(localPort); + localAddr_.sin_addr.s_addr = + localIp ? inet_addr(localIp) : INADDR_ANY; + + if (bind(sock_, + (sockaddr*)&localAddr_, + sizeof(localAddr_)) < 0) + return Result::Error; + + if (remoteIp) + { + remoteAddr_.sin_family = AF_INET; + remoteAddr_.sin_port = htons(remotePort); + remoteAddr_.sin_addr.s_addr = inet_addr(remoteIp); + hasRemote_ = true; + } + + return Result::Ok; +} + +UdpSignaling::Result +UdpSignaling::start() noexcept +{ + running_ = true; + std::thread(&UdpSignaling::recvLoop, this).detach(); + return Result::Ok; +} + +UdpSignaling::Result +UdpSignaling::stop() noexcept +{ + running_ = false; + + if (sock_ >= 0) + { + close(sock_); + sock_ = -1; + } + return Result::Ok; +} + +void +UdpSignaling::setReceiveCallback(RxCallback cb) noexcept +{ + rxCb_ = cb; +} + +UdpSignaling::Result +UdpSignaling::send(const std::uint8_t* data, + std::size_t len) noexcept +{ + if (!hasRemote_) + return Result::Error; + + sendto(sock_, + data, + len, + 0, + (sockaddr*)&remoteAddr_, + sizeof(remoteAddr_)); + return Result::Ok; +} + +void +UdpSignaling::recvLoop() noexcept +{ + std::uint8_t buf[2048]; + + while (running_) + { + sockaddr_in from{}; + socklen_t fromLen = sizeof(from); + + ssize_t n = recvfrom(sock_, + buf, + sizeof(buf), + 0, + (sockaddr*)&from, + &fromLen); + if (n <= 0) + continue; + + remoteAddr_ = from; + hasRemote_ = true; + + if (rxCb_) + { + rxCb_(buf, + static_cast(n), + ntohl(from.sin_addr.s_addr), + ntohs(from.sin_port)); + } + } +} diff --git a/app/libdc_udp_demo_testing/src/udp_signaling.hpp b/app/libdc_udp_demo_testing/src/udp_signaling.hpp new file mode 100644 index 000000000..486a7898c --- /dev/null +++ b/app/libdc_udp_demo_testing/src/udp_signaling.hpp @@ -0,0 +1,50 @@ +#pragma once + +#include +#include +#include +#include + +class UdpSignaling +{ +public: + enum class Result + { + Ok, + Error + }; + + using RxCallback = + std::function; + + UdpSignaling() noexcept; + ~UdpSignaling() noexcept; + + Result init(const char* localIp, + std::uint16_t localPort, + const char* remoteIp, + std::uint16_t remotePort) noexcept; + + Result start() noexcept; + Result stop() noexcept; + + Result send(const std::uint8_t* data, + std::size_t len) noexcept; + + void setReceiveCallback(RxCallback cb) noexcept; + +private: + int sock_; + bool running_; + + sockaddr_in localAddr_; + sockaddr_in remoteAddr_; + bool hasRemote_; + + RxCallback rxCb_; + + void recvLoop() noexcept; +};