mirror of
https://github.com/BotChain-Robots/rpc.git
synced 2026-03-09 23:12:27 +01:00
Prepare files for public release
This commit is contained in:
5
.gitignore
vendored
Normal file
5
.gitignore
vendored
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
/build
|
||||||
|
.DS_Store
|
||||||
|
/cmake-build-debug
|
||||||
|
/.cache
|
||||||
|
**/.DS_Store
|
||||||
25
CMakeLists.txt
Normal file
25
CMakeLists.txt
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
cmake_minimum_required(VERSION 3.15)
|
||||||
|
project(librpc)
|
||||||
|
|
||||||
|
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
|
||||||
|
set(CMAKE_COMPILE_WARNING_AS_ERROR ON)
|
||||||
|
|
||||||
|
find_package(Threads REQUIRED)
|
||||||
|
find_package(flatbuffers REQUIRED)
|
||||||
|
find_package(spdlog REQUIRED)
|
||||||
|
|
||||||
|
add_library(rpc src/librpc.cpp src/TCPClient.cpp src/UDPClient.cpp src/mDNSDiscoveryService.cpp src/MPIMessageBuilder.cpp
|
||||||
|
include/util/log.h)
|
||||||
|
target_include_directories(rpc
|
||||||
|
PUBLIC
|
||||||
|
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
|
||||||
|
$<INSTALL_INTERFACE:include>
|
||||||
|
)
|
||||||
|
|
||||||
|
target_link_libraries(rpc PUBLIC flatbuffers::flatbuffers spdlog::spdlog)
|
||||||
|
|
||||||
|
set_property(TARGET rpc PROPERTY CXX_STANDARD 23)
|
||||||
|
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
|
||||||
|
|
||||||
|
install(TARGETS rpc DESTINATION lib)
|
||||||
|
install(DIRECTORY include/ DESTINATION include)
|
||||||
9
CMakeUserPresets.json
Normal file
9
CMakeUserPresets.json
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
{
|
||||||
|
"version": 4,
|
||||||
|
"vendor": {
|
||||||
|
"conan": {}
|
||||||
|
},
|
||||||
|
"include": [
|
||||||
|
"build/Release/generators/CMakePresets.json"
|
||||||
|
]
|
||||||
|
}
|
||||||
93
README.md
Normal file
93
README.md
Normal file
@@ -0,0 +1,93 @@
|
|||||||
|
# RPC Library
|
||||||
|
The RPC library provides an interface to interact directly with the BotChain devices. The library is managed by the [conan](https://conan.io) package manager, and is consumed by BotChain's higher level libraries. This library provides the following features:
|
||||||
|
- mDNS discovery of modules
|
||||||
|
- TCP connection to modules
|
||||||
|
- UDP connection to modules
|
||||||
|
- An MPI like messaging interface
|
||||||
|
|
||||||
|
The latest releases of the RPC library can be found in our [artifactory](http://jslightham.com:8082), or on [Jenkins](https://jenkins.jslightham.com/job/Botchain/job/librpc/).
|
||||||
|
|
||||||
|
## Platform Support
|
||||||
|
- MacOS (Apple silicon)
|
||||||
|
- MacoS (x86)
|
||||||
|
- Ubuntu (x86)
|
||||||
|
- Windows (x86)
|
||||||
|
|
||||||
|
## Setup
|
||||||
|
### MacOS
|
||||||
|
Install xcode command line tools (if you do not already have them)
|
||||||
|
```
|
||||||
|
xcode-select --install
|
||||||
|
```
|
||||||
|
|
||||||
|
Install conan and dependencies
|
||||||
|
```
|
||||||
|
brew install conan cmake ninja
|
||||||
|
```
|
||||||
|
|
||||||
|
Generate a conan profile
|
||||||
|
```
|
||||||
|
conan profile detect --force
|
||||||
|
```
|
||||||
|
|
||||||
|
### Ubuntu
|
||||||
|
On newer versions of Ubuntu, the package manager is responsible for managing python packages. We use `pipx` to create a virtual environment.
|
||||||
|
|
||||||
|
Install `pipx` and dependencies
|
||||||
|
```
|
||||||
|
sudo apt install pipx cmake ninja-build
|
||||||
|
```
|
||||||
|
|
||||||
|
Install conan with pipx
|
||||||
|
```
|
||||||
|
pipx install conan
|
||||||
|
```
|
||||||
|
|
||||||
|
Generate a conan profile
|
||||||
|
```
|
||||||
|
conan profile detect --force
|
||||||
|
```
|
||||||
|
|
||||||
|
### Artifactory Setup (optional)
|
||||||
|
These instructions should only be followed after you have completed all setup steps for your platform.
|
||||||
|
|
||||||
|
This is an optional section that is only required if you plan on uploading releases to the artifactory manually.
|
||||||
|
Releases tagged with new versions in `conanfile.py` that are merged into the main branch are automatically uploaded to the artifactory by [Jenkins](https://jenkins.jslightham.com/job/Botchain/job/librpc/).
|
||||||
|
|
||||||
|
Add the artifactory
|
||||||
|
```
|
||||||
|
conan remote add artifactory http://jslightham.com:8081/artifactory/api/conan/botchain
|
||||||
|
```
|
||||||
|
|
||||||
|
Add credentials to connect to the remote artifactory
|
||||||
|
```
|
||||||
|
conan remote login artifactory <username> -p <password>
|
||||||
|
```
|
||||||
|
|
||||||
|
Contact Johnathon to get login credentials for the artifactory.
|
||||||
|
|
||||||
|
## Development
|
||||||
|
```
|
||||||
|
# On macos or Linux, you can run
|
||||||
|
./build_rpc_library
|
||||||
|
|
||||||
|
# Building manually
|
||||||
|
build_type=Release # change to the build type you want (ex. Debug, RelWithDebInfo).
|
||||||
|
conan install . --build=missing --output-folder=. -s build_type="${build_type}"
|
||||||
|
cmake -S . -B "build/${build_type}" -DCMAKE_TOOLCHAIN_FILE="$build/${build_type}/generators/conan_toolchain.cmake" -DCMAKE_BUILD_TYPE="${build_type}"
|
||||||
|
cmake --build "build/${build_type}" --config "${build_type}"
|
||||||
|
conan create .
|
||||||
|
```
|
||||||
|
|
||||||
|
## Building For Release
|
||||||
|
Bump the version in `conanfile.py`.
|
||||||
|
|
||||||
|
Create the package
|
||||||
|
```
|
||||||
|
conan create .
|
||||||
|
```
|
||||||
|
|
||||||
|
Upload to the artifactory
|
||||||
|
```
|
||||||
|
conan upload librpc -r=artifactory
|
||||||
|
```
|
||||||
62
build_rpc_library.sh
Executable file
62
build_rpc_library.sh
Executable file
@@ -0,0 +1,62 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
set -e
|
||||||
|
|
||||||
|
function usage() {
|
||||||
|
echo "Usage:"
|
||||||
|
echo "${SCRIPT_NAME} [-b <build type>] [-h]"
|
||||||
|
echo " -b | --build-type - The build type (ie. Release, Debug, RelWithDebInfo)"
|
||||||
|
echo " -h | --help - Print usage"
|
||||||
|
echo "Example:"
|
||||||
|
echo "${SCRIPT_NAME} -b Release"
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
function parse_args() {
|
||||||
|
while [ -n "${1}" ]; do
|
||||||
|
case "${1}" in
|
||||||
|
-h | --help)
|
||||||
|
usage
|
||||||
|
;;
|
||||||
|
-b | --build-type)
|
||||||
|
[ -n "${2}" ] || usage || echo "ERROR: Not enough parameters"
|
||||||
|
build_type="${2}"
|
||||||
|
shift 2
|
||||||
|
;;
|
||||||
|
-d | --disable-format)
|
||||||
|
disable_format=true
|
||||||
|
shift 1
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
echo "ERROR: Invalid parameter. Exiting..."
|
||||||
|
usage
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
function check_pre_req() {
|
||||||
|
if [ "${build_type}" != "Debug" ] && [ "${build_type}" != "Release" ] && [ "${build_type}" != "RelWithDebInfo" ]; then
|
||||||
|
usage
|
||||||
|
echo "ERROR: Build type must be one of: Release, Debug, RelWithDebInfo"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
SCRIPT_NAME="$(basename "${0}")"
|
||||||
|
ROOT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )
|
||||||
|
|
||||||
|
build_type=""
|
||||||
|
disable_format=false
|
||||||
|
parse_args "${@}"
|
||||||
|
check_pre_req
|
||||||
|
|
||||||
|
if [ "$disable_format" != "true" ]; then
|
||||||
|
echo "Formatting with clang-format..."
|
||||||
|
find "${ROOT_DIR}" -iname '*.h' -o -iname '*.cpp' | xargs clang-format -i -style=file
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Building..."
|
||||||
|
conan install "${ROOT_DIR}" --build=missing --output-folder="${ROOT_DIR}" -s build_type="${build_type}"
|
||||||
|
cmake -S "${ROOT_DIR}" -B "${ROOT_DIR}/build/${build_type}" -DCMAKE_TOOLCHAIN_FILE="${ROOT_DIR}/build/${build_type}/generators/conan_toolchain.cmake" -DCMAKE_BUILD_TYPE="${build_type}"
|
||||||
|
cmake --build "${ROOT_DIR}/build/${build_type}" --config "${build_type}"
|
||||||
|
conan create .
|
||||||
1
compile_commands.json
Symbolic link
1
compile_commands.json
Symbolic link
@@ -0,0 +1 @@
|
|||||||
|
build/Release/compile_commands.json
|
||||||
44
conanfile.py
Normal file
44
conanfile.py
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
from conan import ConanFile
|
||||||
|
from conan.tools.cmake import CMake, cmake_layout, CMakeToolchain, CMakeDeps
|
||||||
|
from conan.tools.files import copy
|
||||||
|
import os
|
||||||
|
|
||||||
|
class MyLibraryConan(ConanFile):
|
||||||
|
name = "librpc"
|
||||||
|
version = "1.1.6"
|
||||||
|
|
||||||
|
settings = "os", "compiler", "build_type", "arch"
|
||||||
|
options = {"shared": [True, False], "fPIC": [True, False]}
|
||||||
|
default_options = {"shared": False, "fPIC": True}
|
||||||
|
|
||||||
|
exports_sources = "CMakeLists.txt", "src/*", "include/*"
|
||||||
|
|
||||||
|
def layout(self):
|
||||||
|
cmake_layout(self)
|
||||||
|
|
||||||
|
def generate(self):
|
||||||
|
deps = CMakeDeps(self)
|
||||||
|
deps.generate()
|
||||||
|
tc = CMakeToolchain(self)
|
||||||
|
tc.generate()
|
||||||
|
|
||||||
|
def build(self):
|
||||||
|
cmake = CMake(self)
|
||||||
|
cmake.configure()
|
||||||
|
cmake.build()
|
||||||
|
|
||||||
|
def package(self):
|
||||||
|
cmake = CMake(self)
|
||||||
|
cmake.install()
|
||||||
|
|
||||||
|
def package_info(self):
|
||||||
|
self.cpp_info.libs = ["rpc"]
|
||||||
|
self.cpp_info.includedirs = ["include"]
|
||||||
|
|
||||||
|
def requirements(self):
|
||||||
|
self.requires("flatbuffers/24.12.23")
|
||||||
|
self.requires("spdlog/1.16.0")
|
||||||
|
|
||||||
|
def configure(self):
|
||||||
|
if self.settings.os == "Linux":
|
||||||
|
self.options.fPIC = True
|
||||||
53
include/BlockingQueue.h
Normal file
53
include/BlockingQueue.h
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
//
|
||||||
|
// Created by Johnathon Slightham on 2025-07-10.
|
||||||
|
//
|
||||||
|
|
||||||
|
#ifndef BLOCKINGQUEUE_H
|
||||||
|
#define BLOCKINGQUEUE_H
|
||||||
|
|
||||||
|
#include <chrono>
|
||||||
|
#include <condition_variable>
|
||||||
|
#include <mutex>
|
||||||
|
#include <optional>
|
||||||
|
#include <queue>
|
||||||
|
|
||||||
|
template <typename T> class BlockingQueue {
|
||||||
|
public:
|
||||||
|
explicit BlockingQueue(const size_t capacity) : m_capacity(capacity) {
|
||||||
|
}
|
||||||
|
|
||||||
|
// Enqueue with timeout. Returns true on success, false on timeout.
|
||||||
|
bool enqueue(T &&item, std::chrono::milliseconds max_wait) {
|
||||||
|
std::unique_lock lock(m_mutex);
|
||||||
|
if (!m_cond_not_full.wait_for(lock, max_wait,
|
||||||
|
[this]() { return m_queue.size() < m_capacity; })) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_queue.push(std::move(item));
|
||||||
|
m_cond_not_empty.notify_one();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Dequeue with timeout. Returns optional<T> (empty on timeout).
|
||||||
|
std::optional<T> dequeue(std::chrono::milliseconds max_wait) {
|
||||||
|
std::unique_lock lock(m_mutex);
|
||||||
|
if (!m_cond_not_empty.wait_for(lock, max_wait, [this]() { return !m_queue.empty(); })) {
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
|
T item = std::move(m_queue.front());
|
||||||
|
m_queue.pop();
|
||||||
|
m_cond_not_full.notify_one();
|
||||||
|
return item;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::queue<T> m_queue;
|
||||||
|
size_t m_capacity;
|
||||||
|
std::mutex m_mutex;
|
||||||
|
std::condition_variable m_cond_not_empty;
|
||||||
|
std::condition_variable m_cond_not_full;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // BLOCKINGQUEUE_H
|
||||||
15
include/ICommunicationClient.h
Normal file
15
include/ICommunicationClient.h
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
//
|
||||||
|
// Created by Johnathon Slightham on 2025-06-10.
|
||||||
|
//
|
||||||
|
|
||||||
|
#ifndef INETWORKCLIENT_H
|
||||||
|
#define INETWORKCLIENT_H
|
||||||
|
|
||||||
|
class ICommunicationClient {
|
||||||
|
public:
|
||||||
|
virtual ~ICommunicationClient() = default;
|
||||||
|
virtual int init() = 0;
|
||||||
|
virtual int send_msg(void *sendbuff, uint32_t len) = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif //INETWORKCLIENT_H
|
||||||
24
include/IDiscoveryService.h
Normal file
24
include/IDiscoveryService.h
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
//
|
||||||
|
// Created by Johnathon Slightham on 2025-06-10.
|
||||||
|
//
|
||||||
|
|
||||||
|
#ifndef IDISCOVERYSERVICE_H
|
||||||
|
#define IDISCOVERYSERVICE_H
|
||||||
|
#include <unordered_set>
|
||||||
|
|
||||||
|
#include "ICommunicationClient.h"
|
||||||
|
#include "mDNSRobotModule.h"
|
||||||
|
|
||||||
|
class IDiscoveryService {
|
||||||
|
public:
|
||||||
|
virtual ~IDiscoveryService() = default;
|
||||||
|
virtual std::unordered_set<uint8_t> find_modules(std::chrono::duration<double> wait_time) = 0;
|
||||||
|
virtual std::unordered_map<uint8_t, std::shared_ptr<ICommunicationClient>> get_lossy_clients(
|
||||||
|
const std::shared_ptr<BlockingQueue<std::unique_ptr<std::vector<uint8_t>>>> &rx_queue,
|
||||||
|
std::vector<uint8_t> &skip_modules) = 0;
|
||||||
|
virtual std::unordered_map<uint8_t, std::shared_ptr<ICommunicationClient>> get_lossless_clients(
|
||||||
|
const std::shared_ptr<BlockingQueue<std::unique_ptr<std::vector<uint8_t>>>> &rx_queue,
|
||||||
|
std::vector<uint8_t> &skip_modules) = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // IDISCOVERYSERVICE_H
|
||||||
54
include/TCPClient.h
Normal file
54
include/TCPClient.h
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
//
|
||||||
|
// Created by Johnathon Slightham on 2025-06-10.
|
||||||
|
//
|
||||||
|
|
||||||
|
#ifndef TCPCLIENT_H
|
||||||
|
#define TCPCLIENT_H
|
||||||
|
#include <thread>
|
||||||
|
#include <utility>
|
||||||
|
|
||||||
|
#include "ICommunicationClient.h"
|
||||||
|
|
||||||
|
#ifdef _WIN32
|
||||||
|
#include <winsock2.h>
|
||||||
|
#include <ws2tcpip.h>
|
||||||
|
#define CLOSE_SOCKET closesocket
|
||||||
|
#pragma comment(lib, "ws2_32.lib")
|
||||||
|
typedef SOCKET socket_t;
|
||||||
|
#else
|
||||||
|
#include <arpa/inet.h>
|
||||||
|
#include <netinet/in.h>
|
||||||
|
#include <sys/socket.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
#define CLOSE_SOCKET close
|
||||||
|
typedef int socket_t;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include "BlockingQueue.h"
|
||||||
|
|
||||||
|
class TCPClient final : public ICommunicationClient {
|
||||||
|
|
||||||
|
public:
|
||||||
|
TCPClient(std::string ip,
|
||||||
|
const std::shared_ptr<BlockingQueue<std::unique_ptr<std::vector<uint8_t>>>> &rx_queue)
|
||||||
|
: port{3000}, m_ip{std::move(ip)}, m_stop_flag(false),
|
||||||
|
m_thread(std::thread(&TCPClient::rx_thread, this)), m_rx_queue(rx_queue) {
|
||||||
|
}
|
||||||
|
~TCPClient() override;
|
||||||
|
int init() override;
|
||||||
|
int send_msg(void *sendbuff, uint32_t len) override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
void deinit();
|
||||||
|
void rx_thread() const;
|
||||||
|
|
||||||
|
socket_t m_socket = -1;
|
||||||
|
int port;
|
||||||
|
bool m_initialized = false;
|
||||||
|
std::string m_ip;
|
||||||
|
std::atomic<bool> m_stop_flag;
|
||||||
|
std::thread m_thread;
|
||||||
|
std::shared_ptr<BlockingQueue<std::unique_ptr<std::vector<uint8_t>>>> m_rx_queue;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // TCPCLIENT_H
|
||||||
53
include/UDPClient.h
Normal file
53
include/UDPClient.h
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
//
|
||||||
|
// Created by Johnathon Slightham on 2025-12-27.
|
||||||
|
//
|
||||||
|
|
||||||
|
#ifndef UDPCLIENT_H
|
||||||
|
#define UDPCLIENT_H
|
||||||
|
#include <thread>
|
||||||
|
#include <utility>
|
||||||
|
|
||||||
|
#include "ICommunicationClient.h"
|
||||||
|
|
||||||
|
#ifdef _WIN32
|
||||||
|
#include <winsock2.h>
|
||||||
|
#include <ws2tcpip.h>
|
||||||
|
#define CLOSE_SOCKET closesocket
|
||||||
|
#pragma comment(lib, "ws2_32.lib")
|
||||||
|
typedef SOCKET socket_t;
|
||||||
|
#else
|
||||||
|
#include <arpa/inet.h>
|
||||||
|
#include <netinet/in.h>
|
||||||
|
#include <sys/socket.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
#define CLOSE_SOCKET close
|
||||||
|
typedef int socket_t;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include "BlockingQueue.h"
|
||||||
|
|
||||||
|
class UDPClient final : public ICommunicationClient {
|
||||||
|
|
||||||
|
public:
|
||||||
|
UDPClient(std::string /* ip */,
|
||||||
|
const std::shared_ptr<BlockingQueue<std::unique_ptr<std::vector<uint8_t>>>> &rx_queue)
|
||||||
|
: m_stop_flag(false), m_thread(std::thread(&UDPClient::rx_thread, this)),
|
||||||
|
m_rx_queue(rx_queue) {
|
||||||
|
}
|
||||||
|
~UDPClient() override;
|
||||||
|
int init() override;
|
||||||
|
int send_msg(void *sendbuff, uint32_t len) override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
void deinit();
|
||||||
|
void rx_thread() const;
|
||||||
|
|
||||||
|
socket_t m_tx_socket = -1;
|
||||||
|
socket_t m_rx_socket = -1;
|
||||||
|
bool m_initialized = false;
|
||||||
|
std::atomic<bool> m_stop_flag;
|
||||||
|
std::thread m_thread;
|
||||||
|
std::shared_ptr<BlockingQueue<std::unique_ptr<std::vector<uint8_t>>>> m_rx_queue;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // UDPCLIENT_H
|
||||||
7
include/constants.h
Normal file
7
include/constants.h
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
#ifndef CONSTANTS_H
|
||||||
|
#define CONSTANTS_H
|
||||||
|
|
||||||
|
constexpr auto PC_MODULE_ID = 1;
|
||||||
|
constexpr auto MAX_BUFFER_SIZE = 1024;
|
||||||
|
|
||||||
|
#endif // CONSTANTS_H
|
||||||
33
include/flatbuffers/MPIMessageBuilder.h
Normal file
33
include/flatbuffers/MPIMessageBuilder.h
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
//
|
||||||
|
// Created by Johnathon Slightham on 2025-06-30.
|
||||||
|
//
|
||||||
|
|
||||||
|
#ifndef MPIMESSAGEBUILDER_H
|
||||||
|
#define MPIMESSAGEBUILDER_H
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include "../flatbuffers_generated/MPIMessage_generated.h"
|
||||||
|
#include "SerializedMessage.h"
|
||||||
|
#include "flatbuffers/flatbuffers.h"
|
||||||
|
|
||||||
|
namespace Flatbuffers {
|
||||||
|
class MPIMessageBuilder {
|
||||||
|
public:
|
||||||
|
MPIMessageBuilder() : builder_(1024) {
|
||||||
|
}
|
||||||
|
|
||||||
|
SerializedMessage build_mpi_message(Messaging::MessageType type, uint8_t sender,
|
||||||
|
uint8_t destination, uint16_t sequence_number,
|
||||||
|
bool is_durable, uint8_t tag,
|
||||||
|
const std::vector<uint8_t> &payload);
|
||||||
|
|
||||||
|
static const Messaging::MPIMessage *parse_mpi_message(const uint8_t *buffer);
|
||||||
|
|
||||||
|
private:
|
||||||
|
flatbuffers::FlatBufferBuilder builder_;
|
||||||
|
};
|
||||||
|
} // namespace Flatbuffers
|
||||||
|
|
||||||
|
#endif //MPIMESSAGEBUILDER_H
|
||||||
15
include/flatbuffers/SerializedMessage.h
Normal file
15
include/flatbuffers/SerializedMessage.h
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
//
|
||||||
|
// Created by Johnathon Slightham on 2025-07-05.
|
||||||
|
//
|
||||||
|
|
||||||
|
#ifndef SERIALIZEDMESSAGE_H
|
||||||
|
#define SERIALIZEDMESSAGE_H
|
||||||
|
|
||||||
|
namespace Flatbuffers {
|
||||||
|
struct SerializedMessage {
|
||||||
|
void *data;
|
||||||
|
size_t size;
|
||||||
|
};
|
||||||
|
} // namespace Flatbuffers
|
||||||
|
|
||||||
|
#endif //SERIALIZEDMESSAGE_H
|
||||||
188
include/flatbuffers_generated/MPIMessage_generated.h
Normal file
188
include/flatbuffers_generated/MPIMessage_generated.h
Normal file
@@ -0,0 +1,188 @@
|
|||||||
|
// automatically generated by the FlatBuffers compiler, do not modify
|
||||||
|
|
||||||
|
|
||||||
|
#ifndef FLATBUFFERS_GENERATED_MPIMESSAGE_MESSAGING_H_
|
||||||
|
#define FLATBUFFERS_GENERATED_MPIMESSAGE_MESSAGING_H_
|
||||||
|
|
||||||
|
#include "flatbuffers/flatbuffers.h"
|
||||||
|
|
||||||
|
// Ensure the included flatbuffers.h is the same version as when this file was
|
||||||
|
// generated, otherwise it may not be compatible.
|
||||||
|
// static_assert(FLATBUFFERS_VERSION_MAJOR == 25 &&
|
||||||
|
// FLATBUFFERS_VERSION_MINOR == 2 &&
|
||||||
|
// FLATBUFFERS_VERSION_REVISION == 10,
|
||||||
|
// "Non-compatible flatbuffers version included");
|
||||||
|
|
||||||
|
namespace Messaging {
|
||||||
|
|
||||||
|
struct MPIMessage;
|
||||||
|
struct MPIMessageBuilder;
|
||||||
|
|
||||||
|
enum MessageType : int8_t {
|
||||||
|
MessageType_BROADCAST = 0,
|
||||||
|
MessageType_PTP = 1,
|
||||||
|
MessageType_MIN = MessageType_BROADCAST,
|
||||||
|
MessageType_MAX = MessageType_PTP
|
||||||
|
};
|
||||||
|
|
||||||
|
inline const MessageType (&EnumValuesMessageType())[2] {
|
||||||
|
static const MessageType values[] = {MessageType_BROADCAST, MessageType_PTP};
|
||||||
|
return values;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline const char *const *EnumNamesMessageType() {
|
||||||
|
static const char *const names[3] = {"BROADCAST", "PTP", nullptr};
|
||||||
|
return names;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline const char *EnumNameMessageType(MessageType e) {
|
||||||
|
if (::flatbuffers::IsOutRange(e, MessageType_BROADCAST, MessageType_PTP))
|
||||||
|
return "";
|
||||||
|
const size_t index = static_cast<size_t>(e);
|
||||||
|
return EnumNamesMessageType()[index];
|
||||||
|
}
|
||||||
|
|
||||||
|
struct MPIMessage FLATBUFFERS_FINAL_CLASS : private ::flatbuffers::Table {
|
||||||
|
typedef MPIMessageBuilder Builder;
|
||||||
|
enum FlatBuffersVTableOffset FLATBUFFERS_VTABLE_UNDERLYING_TYPE {
|
||||||
|
VT_TYPE = 4,
|
||||||
|
VT_SENDER = 6,
|
||||||
|
VT_DESTINATION = 8,
|
||||||
|
VT_SEQUENCE_NUMBER = 10,
|
||||||
|
VT_IS_DURABLE = 12,
|
||||||
|
VT_LENGTH = 14,
|
||||||
|
VT_TAG = 16,
|
||||||
|
VT_PAYLOAD = 18
|
||||||
|
};
|
||||||
|
Messaging::MessageType type() const {
|
||||||
|
return static_cast<Messaging::MessageType>(GetField<int8_t>(VT_TYPE, 0));
|
||||||
|
}
|
||||||
|
uint8_t sender() const {
|
||||||
|
return GetField<uint8_t>(VT_SENDER, 0);
|
||||||
|
}
|
||||||
|
uint8_t destination() const {
|
||||||
|
return GetField<uint8_t>(VT_DESTINATION, 0);
|
||||||
|
}
|
||||||
|
uint16_t sequence_number() const {
|
||||||
|
return GetField<uint16_t>(VT_SEQUENCE_NUMBER, 0);
|
||||||
|
}
|
||||||
|
bool is_durable() const {
|
||||||
|
return GetField<uint8_t>(VT_IS_DURABLE, 0) != 0;
|
||||||
|
}
|
||||||
|
uint16_t length() const {
|
||||||
|
return GetField<uint16_t>(VT_LENGTH, 0);
|
||||||
|
}
|
||||||
|
uint8_t tag() const {
|
||||||
|
return GetField<uint8_t>(VT_TAG, 0);
|
||||||
|
}
|
||||||
|
const ::flatbuffers::Vector<uint8_t> *payload() const {
|
||||||
|
return GetPointer<const ::flatbuffers::Vector<uint8_t> *>(VT_PAYLOAD);
|
||||||
|
}
|
||||||
|
bool Verify(::flatbuffers::Verifier &verifier) const {
|
||||||
|
return VerifyTableStart(verifier) && VerifyField<int8_t>(verifier, VT_TYPE, 1) &&
|
||||||
|
VerifyField<uint8_t>(verifier, VT_SENDER, 1) &&
|
||||||
|
VerifyField<uint8_t>(verifier, VT_DESTINATION, 1) &&
|
||||||
|
VerifyField<uint16_t>(verifier, VT_SEQUENCE_NUMBER, 2) &&
|
||||||
|
VerifyField<uint8_t>(verifier, VT_IS_DURABLE, 1) &&
|
||||||
|
VerifyField<uint16_t>(verifier, VT_LENGTH, 2) &&
|
||||||
|
VerifyField<uint8_t>(verifier, VT_TAG, 1) && VerifyOffset(verifier, VT_PAYLOAD) &&
|
||||||
|
verifier.VerifyVector(payload()) && verifier.EndTable();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct MPIMessageBuilder {
|
||||||
|
typedef MPIMessage Table;
|
||||||
|
::flatbuffers::FlatBufferBuilder &fbb_;
|
||||||
|
::flatbuffers::uoffset_t start_;
|
||||||
|
void add_type(Messaging::MessageType type) {
|
||||||
|
fbb_.AddElement<int8_t>(MPIMessage::VT_TYPE, static_cast<int8_t>(type), 0);
|
||||||
|
}
|
||||||
|
void add_sender(uint8_t sender) {
|
||||||
|
fbb_.AddElement<uint8_t>(MPIMessage::VT_SENDER, sender, 0);
|
||||||
|
}
|
||||||
|
void add_destination(uint8_t destination) {
|
||||||
|
fbb_.AddElement<uint8_t>(MPIMessage::VT_DESTINATION, destination, 0);
|
||||||
|
}
|
||||||
|
void add_sequence_number(uint16_t sequence_number) {
|
||||||
|
fbb_.AddElement<uint16_t>(MPIMessage::VT_SEQUENCE_NUMBER, sequence_number, 0);
|
||||||
|
}
|
||||||
|
void add_is_durable(bool is_durable) {
|
||||||
|
fbb_.AddElement<uint8_t>(MPIMessage::VT_IS_DURABLE, static_cast<uint8_t>(is_durable), 0);
|
||||||
|
}
|
||||||
|
void add_length(uint16_t length) {
|
||||||
|
fbb_.AddElement<uint16_t>(MPIMessage::VT_LENGTH, length, 0);
|
||||||
|
}
|
||||||
|
void add_tag(uint8_t tag) {
|
||||||
|
fbb_.AddElement<uint8_t>(MPIMessage::VT_TAG, tag, 0);
|
||||||
|
}
|
||||||
|
void add_payload(::flatbuffers::Offset<::flatbuffers::Vector<uint8_t>> payload) {
|
||||||
|
fbb_.AddOffset(MPIMessage::VT_PAYLOAD, payload);
|
||||||
|
}
|
||||||
|
explicit MPIMessageBuilder(::flatbuffers::FlatBufferBuilder &_fbb) : fbb_(_fbb) {
|
||||||
|
start_ = fbb_.StartTable();
|
||||||
|
}
|
||||||
|
::flatbuffers::Offset<MPIMessage> Finish() {
|
||||||
|
const auto end = fbb_.EndTable(start_);
|
||||||
|
auto o = ::flatbuffers::Offset<MPIMessage>(end);
|
||||||
|
return o;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
inline ::flatbuffers::Offset<MPIMessage>
|
||||||
|
CreateMPIMessage(::flatbuffers::FlatBufferBuilder &_fbb,
|
||||||
|
Messaging::MessageType type = Messaging::MessageType_BROADCAST, uint8_t sender = 0,
|
||||||
|
uint8_t destination = 0, uint16_t sequence_number = 0, bool is_durable = false,
|
||||||
|
uint16_t length = 0, uint8_t tag = 0,
|
||||||
|
::flatbuffers::Offset<::flatbuffers::Vector<uint8_t>> payload = 0) {
|
||||||
|
MPIMessageBuilder builder_(_fbb);
|
||||||
|
builder_.add_payload(payload);
|
||||||
|
builder_.add_length(length);
|
||||||
|
builder_.add_sequence_number(sequence_number);
|
||||||
|
builder_.add_tag(tag);
|
||||||
|
builder_.add_is_durable(is_durable);
|
||||||
|
builder_.add_destination(destination);
|
||||||
|
builder_.add_sender(sender);
|
||||||
|
builder_.add_type(type);
|
||||||
|
return builder_.Finish();
|
||||||
|
}
|
||||||
|
|
||||||
|
inline ::flatbuffers::Offset<MPIMessage>
|
||||||
|
CreateMPIMessageDirect(::flatbuffers::FlatBufferBuilder &_fbb,
|
||||||
|
Messaging::MessageType type = Messaging::MessageType_BROADCAST,
|
||||||
|
uint8_t sender = 0, uint8_t destination = 0, uint16_t sequence_number = 0,
|
||||||
|
bool is_durable = false, uint16_t length = 0, uint8_t tag = 0,
|
||||||
|
const std::vector<uint8_t> *payload = nullptr) {
|
||||||
|
auto payload__ = payload ? _fbb.CreateVector<uint8_t>(*payload) : 0;
|
||||||
|
return Messaging::CreateMPIMessage(_fbb, type, sender, destination, sequence_number, is_durable,
|
||||||
|
length, tag, payload__);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline const Messaging::MPIMessage *GetMPIMessage(const void *buf) {
|
||||||
|
return ::flatbuffers::GetRoot<Messaging::MPIMessage>(buf);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline const Messaging::MPIMessage *GetSizePrefixedMPIMessage(const void *buf) {
|
||||||
|
return ::flatbuffers::GetSizePrefixedRoot<Messaging::MPIMessage>(buf);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline bool VerifyMPIMessageBuffer(::flatbuffers::Verifier &verifier) {
|
||||||
|
return verifier.VerifyBuffer<Messaging::MPIMessage>(nullptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline bool VerifySizePrefixedMPIMessageBuffer(::flatbuffers::Verifier &verifier) {
|
||||||
|
return verifier.VerifySizePrefixedBuffer<Messaging::MPIMessage>(nullptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void FinishMPIMessageBuffer(::flatbuffers::FlatBufferBuilder &fbb,
|
||||||
|
::flatbuffers::Offset<Messaging::MPIMessage> root) {
|
||||||
|
fbb.Finish(root);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void FinishSizePrefixedMPIMessageBuffer(::flatbuffers::FlatBufferBuilder &fbb,
|
||||||
|
::flatbuffers::Offset<Messaging::MPIMessage> root) {
|
||||||
|
fbb.FinishSizePrefixed(root);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Messaging
|
||||||
|
|
||||||
|
#endif // FLATBUFFERS_GENERATED_MPIMESSAGE_MESSAGING_H_
|
||||||
286
include/flatbuffers_generated/RobotModule_generated.h
Normal file
286
include/flatbuffers_generated/RobotModule_generated.h
Normal file
@@ -0,0 +1,286 @@
|
|||||||
|
// automatically generated by the FlatBuffers compiler, do not modify
|
||||||
|
|
||||||
|
|
||||||
|
#ifndef FLATBUFFERS_GENERATED_ROBOTMODULE_H_
|
||||||
|
#define FLATBUFFERS_GENERATED_ROBOTMODULE_H_
|
||||||
|
|
||||||
|
#include "flatbuffers/flatbuffers.h"
|
||||||
|
|
||||||
|
// Ensure the included flatbuffers.h is the same version as when this file was
|
||||||
|
// generated, otherwise it may not be compatible.
|
||||||
|
//static_assert(FLATBUFFERS_VERSION_MAJOR == 25 &&
|
||||||
|
// FLATBUFFERS_VERSION_MINOR == 2 &&
|
||||||
|
// FLATBUFFERS_VERSION_REVISION == 10,
|
||||||
|
// "Non-compatible flatbuffers version included");
|
||||||
|
|
||||||
|
struct MotorState;
|
||||||
|
struct MotorStateBuilder;
|
||||||
|
|
||||||
|
struct RobotModule;
|
||||||
|
struct RobotModuleBuilder;
|
||||||
|
|
||||||
|
enum ModuleType : int8_t {
|
||||||
|
ModuleType_SPLITTER = 0,
|
||||||
|
ModuleType_SERVO_1 = 1,
|
||||||
|
ModuleType_DC_MOTOR = 2,
|
||||||
|
ModuleType_BATTERY = 3,
|
||||||
|
ModuleType_MIN = ModuleType_SPLITTER,
|
||||||
|
ModuleType_MAX = ModuleType_BATTERY
|
||||||
|
};
|
||||||
|
|
||||||
|
inline const ModuleType (&EnumValuesModuleType())[4] {
|
||||||
|
static const ModuleType values[] = {ModuleType_SPLITTER, ModuleType_SERVO_1,
|
||||||
|
ModuleType_DC_MOTOR, ModuleType_BATTERY};
|
||||||
|
return values;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline const char *const *EnumNamesModuleType() {
|
||||||
|
static const char *const names[5] = {"SPLITTER", "SERVO_1", "DC_MOTOR", "BATTERY", nullptr};
|
||||||
|
return names;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline const char *EnumNameModuleType(ModuleType e) {
|
||||||
|
if (::flatbuffers::IsOutRange(e, ModuleType_SPLITTER, ModuleType_BATTERY))
|
||||||
|
return "";
|
||||||
|
const size_t index = static_cast<size_t>(e);
|
||||||
|
return EnumNamesModuleType()[index];
|
||||||
|
}
|
||||||
|
|
||||||
|
enum Orientation : int8_t {
|
||||||
|
Orientation_Deg0 = 0,
|
||||||
|
Orientation_Deg90 = 1,
|
||||||
|
Orientation_Deg180 = 2,
|
||||||
|
Orientation_Deg270 = 3,
|
||||||
|
Orientation_MIN = Orientation_Deg0,
|
||||||
|
Orientation_MAX = Orientation_Deg270
|
||||||
|
};
|
||||||
|
|
||||||
|
inline const Orientation (&EnumValuesOrientation())[4] {
|
||||||
|
static const Orientation values[] = {Orientation_Deg0, Orientation_Deg90, Orientation_Deg180,
|
||||||
|
Orientation_Deg270};
|
||||||
|
return values;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline const char *const *EnumNamesOrientation() {
|
||||||
|
static const char *const names[5] = {"Deg0", "Deg90", "Deg180", "Deg270", nullptr};
|
||||||
|
return names;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline const char *EnumNameOrientation(Orientation e) {
|
||||||
|
if (::flatbuffers::IsOutRange(e, Orientation_Deg0, Orientation_Deg270))
|
||||||
|
return "";
|
||||||
|
const size_t index = static_cast<size_t>(e);
|
||||||
|
return EnumNamesOrientation()[index];
|
||||||
|
}
|
||||||
|
|
||||||
|
enum ModuleState : uint8_t {
|
||||||
|
ModuleState_NONE = 0,
|
||||||
|
ModuleState_MotorState = 1,
|
||||||
|
ModuleState_MIN = ModuleState_NONE,
|
||||||
|
ModuleState_MAX = ModuleState_MotorState
|
||||||
|
};
|
||||||
|
|
||||||
|
inline const ModuleState (&EnumValuesModuleState())[2] {
|
||||||
|
static const ModuleState values[] = {ModuleState_NONE, ModuleState_MotorState};
|
||||||
|
return values;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline const char *const *EnumNamesModuleState() {
|
||||||
|
static const char *const names[3] = {"NONE", "MotorState", nullptr};
|
||||||
|
return names;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline const char *EnumNameModuleState(ModuleState e) {
|
||||||
|
if (::flatbuffers::IsOutRange(e, ModuleState_NONE, ModuleState_MotorState))
|
||||||
|
return "";
|
||||||
|
const size_t index = static_cast<size_t>(e);
|
||||||
|
return EnumNamesModuleState()[index];
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T> struct ModuleStateTraits {
|
||||||
|
static const ModuleState enum_value = ModuleState_NONE;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <> struct ModuleStateTraits<MotorState> {
|
||||||
|
static const ModuleState enum_value = ModuleState_MotorState;
|
||||||
|
};
|
||||||
|
|
||||||
|
bool VerifyModuleState(::flatbuffers::Verifier &verifier, const void *obj, ModuleState type);
|
||||||
|
bool VerifyModuleStateVector(::flatbuffers::Verifier &verifier,
|
||||||
|
const ::flatbuffers::Vector<::flatbuffers::Offset<void>> *values,
|
||||||
|
const ::flatbuffers::Vector<uint8_t> *types);
|
||||||
|
|
||||||
|
struct MotorState FLATBUFFERS_FINAL_CLASS : private ::flatbuffers::Table {
|
||||||
|
typedef MotorStateBuilder Builder;
|
||||||
|
enum FlatBuffersVTableOffset FLATBUFFERS_VTABLE_UNDERLYING_TYPE { VT_ANGLE = 4 };
|
||||||
|
int32_t angle() const {
|
||||||
|
return GetField<int32_t>(VT_ANGLE, 0);
|
||||||
|
}
|
||||||
|
bool Verify(::flatbuffers::Verifier &verifier) const {
|
||||||
|
return VerifyTableStart(verifier) && VerifyField<int32_t>(verifier, VT_ANGLE, 4) &&
|
||||||
|
verifier.EndTable();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct MotorStateBuilder {
|
||||||
|
typedef MotorState Table;
|
||||||
|
::flatbuffers::FlatBufferBuilder &fbb_;
|
||||||
|
::flatbuffers::uoffset_t start_;
|
||||||
|
void add_angle(int32_t angle) {
|
||||||
|
fbb_.AddElement<int32_t>(MotorState::VT_ANGLE, angle, 0);
|
||||||
|
}
|
||||||
|
explicit MotorStateBuilder(::flatbuffers::FlatBufferBuilder &_fbb) : fbb_(_fbb) {
|
||||||
|
start_ = fbb_.StartTable();
|
||||||
|
}
|
||||||
|
::flatbuffers::Offset<MotorState> Finish() {
|
||||||
|
const auto end = fbb_.EndTable(start_);
|
||||||
|
auto o = ::flatbuffers::Offset<MotorState>(end);
|
||||||
|
return o;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
inline ::flatbuffers::Offset<MotorState> CreateMotorState(::flatbuffers::FlatBufferBuilder &_fbb,
|
||||||
|
int32_t angle = 0) {
|
||||||
|
MotorStateBuilder builder_(_fbb);
|
||||||
|
builder_.add_angle(angle);
|
||||||
|
return builder_.Finish();
|
||||||
|
}
|
||||||
|
|
||||||
|
struct RobotModule FLATBUFFERS_FINAL_CLASS : private ::flatbuffers::Table {
|
||||||
|
typedef RobotModuleBuilder Builder;
|
||||||
|
enum FlatBuffersVTableOffset FLATBUFFERS_VTABLE_UNDERLYING_TYPE {
|
||||||
|
VT_ID = 4,
|
||||||
|
VT_MODULE_TYPE = 6,
|
||||||
|
VT_CONFIGURATION_TYPE = 8,
|
||||||
|
VT_CONFIGURATION = 10
|
||||||
|
};
|
||||||
|
uint8_t id() const {
|
||||||
|
return GetField<uint8_t>(VT_ID, 0);
|
||||||
|
}
|
||||||
|
ModuleType module_type() const {
|
||||||
|
return static_cast<ModuleType>(GetField<int8_t>(VT_MODULE_TYPE, 0));
|
||||||
|
}
|
||||||
|
ModuleState configuration_type() const {
|
||||||
|
return static_cast<ModuleState>(GetField<uint8_t>(VT_CONFIGURATION_TYPE, 0));
|
||||||
|
}
|
||||||
|
const void *configuration() const {
|
||||||
|
return GetPointer<const void *>(VT_CONFIGURATION);
|
||||||
|
}
|
||||||
|
template <typename T> const T *configuration_as() const;
|
||||||
|
const MotorState *configuration_as_MotorState() const {
|
||||||
|
return configuration_type() == ModuleState_MotorState
|
||||||
|
? static_cast<const MotorState *>(configuration())
|
||||||
|
: nullptr;
|
||||||
|
}
|
||||||
|
bool Verify(::flatbuffers::Verifier &verifier) const {
|
||||||
|
return VerifyTableStart(verifier) && VerifyField<uint8_t>(verifier, VT_ID, 1) &&
|
||||||
|
VerifyField<int8_t>(verifier, VT_MODULE_TYPE, 1) &&
|
||||||
|
VerifyField<uint8_t>(verifier, VT_CONFIGURATION_TYPE, 1) &&
|
||||||
|
VerifyOffset(verifier, VT_CONFIGURATION) &&
|
||||||
|
VerifyModuleState(verifier, configuration(), configuration_type()) &&
|
||||||
|
verifier.EndTable();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
template <> inline const MotorState *RobotModule::configuration_as<MotorState>() const {
|
||||||
|
return configuration_as_MotorState();
|
||||||
|
}
|
||||||
|
|
||||||
|
struct RobotModuleBuilder {
|
||||||
|
typedef RobotModule Table;
|
||||||
|
::flatbuffers::FlatBufferBuilder &fbb_;
|
||||||
|
::flatbuffers::uoffset_t start_;
|
||||||
|
void add_id(uint8_t id) {
|
||||||
|
fbb_.AddElement<uint8_t>(RobotModule::VT_ID, id, 0);
|
||||||
|
}
|
||||||
|
void add_module_type(ModuleType module_type) {
|
||||||
|
fbb_.AddElement<int8_t>(RobotModule::VT_MODULE_TYPE, static_cast<int8_t>(module_type), 0);
|
||||||
|
}
|
||||||
|
void add_configuration_type(ModuleState configuration_type) {
|
||||||
|
fbb_.AddElement<uint8_t>(RobotModule::VT_CONFIGURATION_TYPE,
|
||||||
|
static_cast<uint8_t>(configuration_type), 0);
|
||||||
|
}
|
||||||
|
void add_configuration(::flatbuffers::Offset<void> configuration) {
|
||||||
|
fbb_.AddOffset(RobotModule::VT_CONFIGURATION, configuration);
|
||||||
|
}
|
||||||
|
explicit RobotModuleBuilder(::flatbuffers::FlatBufferBuilder &_fbb) : fbb_(_fbb) {
|
||||||
|
start_ = fbb_.StartTable();
|
||||||
|
}
|
||||||
|
::flatbuffers::Offset<RobotModule> Finish() {
|
||||||
|
const auto end = fbb_.EndTable(start_);
|
||||||
|
auto o = ::flatbuffers::Offset<RobotModule>(end);
|
||||||
|
return o;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
inline ::flatbuffers::Offset<RobotModule>
|
||||||
|
CreateRobotModule(::flatbuffers::FlatBufferBuilder &_fbb, uint8_t id = 0,
|
||||||
|
ModuleType module_type = ModuleType_SPLITTER,
|
||||||
|
ModuleState configuration_type = ModuleState_NONE,
|
||||||
|
::flatbuffers::Offset<void> configuration = 0) {
|
||||||
|
RobotModuleBuilder builder_(_fbb);
|
||||||
|
builder_.add_configuration(configuration);
|
||||||
|
builder_.add_configuration_type(configuration_type);
|
||||||
|
builder_.add_module_type(module_type);
|
||||||
|
builder_.add_id(id);
|
||||||
|
return builder_.Finish();
|
||||||
|
}
|
||||||
|
|
||||||
|
inline bool VerifyModuleState(::flatbuffers::Verifier &verifier, const void *obj,
|
||||||
|
ModuleState type) {
|
||||||
|
switch (type) {
|
||||||
|
case ModuleState_NONE: {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
case ModuleState_MotorState: {
|
||||||
|
auto ptr = reinterpret_cast<const MotorState *>(obj);
|
||||||
|
return verifier.VerifyTable(ptr);
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
inline bool
|
||||||
|
VerifyModuleStateVector(::flatbuffers::Verifier &verifier,
|
||||||
|
const ::flatbuffers::Vector<::flatbuffers::Offset<void>> *values,
|
||||||
|
const ::flatbuffers::Vector<uint8_t> *types) {
|
||||||
|
if (!values || !types)
|
||||||
|
return !values && !types;
|
||||||
|
if (values->size() != types->size())
|
||||||
|
return false;
|
||||||
|
for (::flatbuffers::uoffset_t i = 0; i < values->size(); ++i) {
|
||||||
|
if (!VerifyModuleState(verifier, values->Get(i), types->GetEnum<ModuleState>(i))) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline const RobotModule *GetRobotModule(const void *buf) {
|
||||||
|
return ::flatbuffers::GetRoot<RobotModule>(buf);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline const RobotModule *GetSizePrefixedRobotModule(const void *buf) {
|
||||||
|
return ::flatbuffers::GetSizePrefixedRoot<RobotModule>(buf);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline bool VerifyRobotModuleBuffer(::flatbuffers::Verifier &verifier) {
|
||||||
|
return verifier.VerifyBuffer<RobotModule>(nullptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline bool VerifySizePrefixedRobotModuleBuffer(::flatbuffers::Verifier &verifier) {
|
||||||
|
return verifier.VerifySizePrefixedBuffer<RobotModule>(nullptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void FinishRobotModuleBuffer(::flatbuffers::FlatBufferBuilder &fbb,
|
||||||
|
::flatbuffers::Offset<RobotModule> root) {
|
||||||
|
fbb.Finish(root);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void FinishSizePrefixedRobotModuleBuffer(::flatbuffers::FlatBufferBuilder &fbb,
|
||||||
|
::flatbuffers::Offset<RobotModule> root) {
|
||||||
|
fbb.FinishSizePrefixed(root);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif // FLATBUFFERS_GENERATED_ROBOTMODULE_H_
|
||||||
59
include/librpc.h
Normal file
59
include/librpc.h
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
#ifndef RPC_LIBRARY_H
|
||||||
|
#define RPC_LIBRARY_H
|
||||||
|
|
||||||
|
#include <chrono>
|
||||||
|
#include <memory>
|
||||||
|
#include <shared_mutex>
|
||||||
|
#include <thread>
|
||||||
|
|
||||||
|
#include "BlockingQueue.h"
|
||||||
|
#include "constants.h"
|
||||||
|
#include "mDNSDiscoveryService.h"
|
||||||
|
|
||||||
|
|
||||||
|
constexpr auto RX_QUEUE_SIZE = 100;
|
||||||
|
|
||||||
|
struct SizeAndSource {
|
||||||
|
size_t bytes_written;
|
||||||
|
uint8_t sender;
|
||||||
|
};
|
||||||
|
|
||||||
|
class MessagingInterface {
|
||||||
|
public:
|
||||||
|
MessagingInterface()
|
||||||
|
: m_stop_flag(false), m_rx_thread(std::thread(&MessagingInterface::handle_recv, this)),
|
||||||
|
m_rx_queue(std::make_shared<BlockingQueue<std::unique_ptr<std::vector<uint8_t>>>>(
|
||||||
|
RX_QUEUE_SIZE)) {
|
||||||
|
#ifdef _WIN32
|
||||||
|
WSADATA wsaData;
|
||||||
|
WSAStartup(MAKEWORD(2, 2), &wsaData);
|
||||||
|
#endif
|
||||||
|
// Initialization must be after call to WSAStartup
|
||||||
|
m_discovery_service = std::make_unique<mDNSDiscoveryService>();
|
||||||
|
}
|
||||||
|
|
||||||
|
~MessagingInterface();
|
||||||
|
int send(uint8_t *buffer, size_t size, uint8_t destination, uint8_t tag, bool durable);
|
||||||
|
int broadcast(uint8_t *buffer, size_t size, bool durable); // todo
|
||||||
|
std::optional<SizeAndSource> recv(uint8_t *buffer, size_t size, uint8_t tag);
|
||||||
|
int sendrecv(uint8_t *send_buffer, size_t send_size, uint8_t dest, uint8_t send_tag,
|
||||||
|
uint8_t *recv_buffer, size_t recv_size, uint8_t recv_tag); // todo
|
||||||
|
std::unordered_set<uint8_t> find_connected_modules(std::chrono::duration<double> scan_duration);
|
||||||
|
|
||||||
|
private:
|
||||||
|
void handle_recv();
|
||||||
|
|
||||||
|
uint16_t m_sequence_number = 0;
|
||||||
|
std::unordered_map<uint8_t, std::shared_ptr<ICommunicationClient>> m_id_to_lossless_client;
|
||||||
|
std::unordered_map<uint8_t, std::shared_ptr<ICommunicationClient>> m_id_to_lossy_client;
|
||||||
|
std::unordered_map<int, std::unique_ptr<BlockingQueue<std::unique_ptr<std::vector<uint8_t>>>>>
|
||||||
|
m_tag_to_queue_map;
|
||||||
|
std::unique_ptr<IDiscoveryService> m_discovery_service;
|
||||||
|
std::atomic<bool> m_stop_flag;
|
||||||
|
std::thread m_rx_thread;
|
||||||
|
std::shared_ptr<BlockingQueue<std::unique_ptr<std::vector<uint8_t>>>> m_rx_queue;
|
||||||
|
std::shared_mutex m_client_mutex;
|
||||||
|
std::shared_mutex m_scan_mutex;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // RPC_LIBRARY_H
|
||||||
56
include/mDNSDiscoveryService.h
Normal file
56
include/mDNSDiscoveryService.h
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
//
|
||||||
|
// Created by Johnathon Slightham on 2025-06-10.
|
||||||
|
//
|
||||||
|
|
||||||
|
#ifndef MDNSDISCOVERYSERVICE_H
|
||||||
|
#define MDNSDISCOVERYSERVICE_H
|
||||||
|
|
||||||
|
#include <chrono>
|
||||||
|
#include <unordered_map>
|
||||||
|
|
||||||
|
#include "BlockingQueue.h"
|
||||||
|
#include "ICommunicationClient.h"
|
||||||
|
#include "IDiscoveryService.h"
|
||||||
|
#include "mDNSRobotModule.h"
|
||||||
|
|
||||||
|
#ifdef _WIN32
|
||||||
|
#include <winsock2.h>
|
||||||
|
#include <ws2tcpip.h>
|
||||||
|
#define CLOSE_SOCKET closesocket
|
||||||
|
#pragma comment(lib, "ws2_32.lib")
|
||||||
|
typedef SOCKET socket_t;
|
||||||
|
#else
|
||||||
|
#include <arpa/inet.h>
|
||||||
|
#include <netinet/in.h>
|
||||||
|
#include <sys/socket.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
#define CLOSE_SOCKET close
|
||||||
|
typedef int socket_t;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
class mDNSDiscoveryService final : public IDiscoveryService {
|
||||||
|
|
||||||
|
public:
|
||||||
|
mDNSDiscoveryService();
|
||||||
|
~mDNSDiscoveryService() override;
|
||||||
|
std::unordered_set<uint8_t> find_modules(std::chrono::duration<double> wait_time) override;
|
||||||
|
std::unordered_map<uint8_t, std::shared_ptr<ICommunicationClient>> get_lossy_clients(
|
||||||
|
const std::shared_ptr<BlockingQueue<std::unique_ptr<std::vector<uint8_t>>>> &rx_queue,
|
||||||
|
std::vector<uint8_t> &skip_modules) override;
|
||||||
|
std::unordered_map<uint8_t, std::shared_ptr<ICommunicationClient>> get_lossless_clients(
|
||||||
|
const std::shared_ptr<BlockingQueue<std::unique_ptr<std::vector<uint8_t>>>> &rx_queue,
|
||||||
|
std::vector<uint8_t> &skip_modules) override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
template <typename T>
|
||||||
|
std::unordered_map<uint8_t, std::shared_ptr<ICommunicationClient>> create_clients(
|
||||||
|
const std::shared_ptr<BlockingQueue<std::unique_ptr<std::vector<uint8_t>>>> &rx_queue,
|
||||||
|
std::vector<uint8_t> &skip_modules);
|
||||||
|
static void send_mdns_query(socket_t sock, const sockaddr_in &addr);
|
||||||
|
static std::optional<mDNSRobotModule> parse_response(uint8_t *buffer, int size);
|
||||||
|
static std::tuple<std::string, int> read_mdns_name(const uint8_t *buffer, int size, int ptr);
|
||||||
|
|
||||||
|
std::unordered_map<uint8_t, mDNSRobotModule> module_to_mdns{};
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // MDNSDISCOVERYSERVICE_H
|
||||||
19
include/mDNSRobotModule.h
Normal file
19
include/mDNSRobotModule.h
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
//
|
||||||
|
// Created by Johnathon Slightham on 2025-07-05.
|
||||||
|
//
|
||||||
|
|
||||||
|
#ifndef ROBOTMODULEINSTANCE_H
|
||||||
|
#define ROBOTMODULEINSTANCE_H
|
||||||
|
|
||||||
|
#include "flatbuffers_generated/RobotModule_generated.h"
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
struct mDNSRobotModule {
|
||||||
|
int id;
|
||||||
|
std::string ip;
|
||||||
|
std::string hostname;
|
||||||
|
ModuleType module_type;
|
||||||
|
std::vector<int> connected_module_ids;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif //ROBOTMODULEINSTANCE_H
|
||||||
24
include/util/ip.h
Normal file
24
include/util/ip.h
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
#ifndef IP_UTIL_H
|
||||||
|
#define IP_UTIL_H
|
||||||
|
|
||||||
|
#ifdef _WIN32
|
||||||
|
#include <winsock2.h>
|
||||||
|
#include <ws2tcpip.h>
|
||||||
|
#define CLOSE_SOCKET closesocket
|
||||||
|
#pragma comment(lib, "ws2_32.lib")
|
||||||
|
typedef SOCKET socket_t;
|
||||||
|
#else
|
||||||
|
#include <arpa/inet.h>
|
||||||
|
#include <netinet/in.h>
|
||||||
|
#include <sys/socket.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
#define CLOSE_SOCKET close
|
||||||
|
typedef int socket_t;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
bool is_valid_ipv4(const std::string &ip) {
|
||||||
|
struct in_addr addr;
|
||||||
|
return inet_pton(AF_INET, ip.c_str(), &addr) == 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif // IP_UTIL_H
|
||||||
32
include/util/log.h
Normal file
32
include/util/log.h
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
//
|
||||||
|
// Created by sligh on 2026-01-09.
|
||||||
|
//
|
||||||
|
|
||||||
|
#ifndef LOG_H
|
||||||
|
#define LOG_H
|
||||||
|
|
||||||
|
#define ERRBUF_SIZE 300
|
||||||
|
|
||||||
|
#include "spdlog/spdlog.h"
|
||||||
|
|
||||||
|
#ifdef _WIN32
|
||||||
|
|
||||||
|
void print_errno() {
|
||||||
|
char errbuf[ERRBUF_SIZE];
|
||||||
|
FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM, NULL, WSAGetLastError(), 0, errbuf, sizeof(errbuf),
|
||||||
|
NULL);
|
||||||
|
spdlog::error("{}", errbuf);
|
||||||
|
}
|
||||||
|
|
||||||
|
#else
|
||||||
|
|
||||||
|
#include <errno.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
void print_errno() {
|
||||||
|
spdlog::error("{}", strerror(errno));
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif //LOG_H
|
||||||
23
include/util/string.h
Normal file
23
include/util/string.h
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
//
|
||||||
|
// Created by Johnathon Slightham on 2025-07-05.
|
||||||
|
//
|
||||||
|
|
||||||
|
#ifndef STRING_H
|
||||||
|
#define STRING_H
|
||||||
|
#include <sstream>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
inline std::vector<std::string> split(const std::string &str, const char delimiter) {
|
||||||
|
std::vector<std::string> result;
|
||||||
|
std::stringstream ss(str);
|
||||||
|
std::string token;
|
||||||
|
|
||||||
|
while (std::getline(ss, token, delimiter)) {
|
||||||
|
result.push_back(token);
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif //STRING_H
|
||||||
31
src/MPIMessageBuilder.cpp
Normal file
31
src/MPIMessageBuilder.cpp
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
//
|
||||||
|
// Created by Johnathon Slightham on 2025-06-30.
|
||||||
|
//
|
||||||
|
|
||||||
|
#include "flatbuffers/MPIMessageBuilder.h"
|
||||||
|
#include "flatbuffers/SerializedMessage.h"
|
||||||
|
|
||||||
|
namespace Flatbuffers {
|
||||||
|
SerializedMessage MPIMessageBuilder::build_mpi_message(const Messaging::MessageType type,
|
||||||
|
const uint8_t sender,
|
||||||
|
const uint8_t destination,
|
||||||
|
const uint16_t sequence_number,
|
||||||
|
const bool is_durable, const uint8_t tag,
|
||||||
|
const std::vector<uint8_t> &payload) {
|
||||||
|
builder_.Clear();
|
||||||
|
|
||||||
|
const auto payload_vector = builder_.CreateVector(payload);
|
||||||
|
|
||||||
|
const auto message = Messaging::CreateMPIMessage(
|
||||||
|
builder_, type, sender, destination, sequence_number, is_durable,
|
||||||
|
static_cast<int>(payload.size()), tag, payload_vector);
|
||||||
|
|
||||||
|
builder_.Finish(message);
|
||||||
|
|
||||||
|
return {builder_.GetBufferPointer(), builder_.GetSize()};
|
||||||
|
}
|
||||||
|
|
||||||
|
const Messaging::MPIMessage *MPIMessageBuilder::parse_mpi_message(const uint8_t *buffer) {
|
||||||
|
return flatbuffers::GetRoot<Messaging::MPIMessage>(buffer);
|
||||||
|
}
|
||||||
|
} // namespace Flatbuffers
|
||||||
114
src/TCPClient.cpp
Normal file
114
src/TCPClient.cpp
Normal file
@@ -0,0 +1,114 @@
|
|||||||
|
//
|
||||||
|
// Created by Johnathon Slightham on 2025-06-10.
|
||||||
|
//
|
||||||
|
|
||||||
|
#include <chrono>
|
||||||
|
#include <iostream>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include "TCPClient.h"
|
||||||
|
#include "constants.h"
|
||||||
|
#include "spdlog/spdlog.h"
|
||||||
|
|
||||||
|
constexpr auto SLEEP_WHILE_INITIALIZING = std::chrono::milliseconds(250);
|
||||||
|
constexpr int PORT = 3001;
|
||||||
|
constexpr auto QUEUE_ADD_TIMEOUT = std::chrono::milliseconds(100);
|
||||||
|
constexpr auto RX_SLEEP_ON_ERROR = std::chrono::milliseconds(100);
|
||||||
|
constexpr auto SOCKET_TIMEOUT_MS = 2500;
|
||||||
|
|
||||||
|
// todo: - add authentication
|
||||||
|
// - encryption
|
||||||
|
|
||||||
|
TCPClient::~TCPClient() {
|
||||||
|
this->m_stop_flag = true;
|
||||||
|
this->m_thread.join();
|
||||||
|
this->deinit();
|
||||||
|
}
|
||||||
|
|
||||||
|
int TCPClient::init() {
|
||||||
|
sockaddr_in serv_addr{};
|
||||||
|
|
||||||
|
if ((this->m_socket = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
|
||||||
|
spdlog::error("[TCP] Failed to create socket");
|
||||||
|
return -2;
|
||||||
|
}
|
||||||
|
|
||||||
|
serv_addr.sin_family = AF_INET;
|
||||||
|
serv_addr.sin_port = htons(PORT);
|
||||||
|
|
||||||
|
if (inet_pton(AF_INET, this->m_ip.c_str(), &serv_addr.sin_addr) <= 0) {
|
||||||
|
spdlog::error("[TCP] Invalid address");
|
||||||
|
deinit();
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
timeval timeout{};
|
||||||
|
timeout.tv_sec = SOCKET_TIMEOUT_MS / 1000;
|
||||||
|
timeout.tv_usec = (SOCKET_TIMEOUT_MS % 1000) * 1000;
|
||||||
|
|
||||||
|
#ifdef _WIN32
|
||||||
|
setsockopt(this->m_socket, SOL_SOCKET, SO_RCVTIMEO, (char *)&timeout, sizeof(timeout));
|
||||||
|
setsockopt(this->m_socket, SOL_SOCKET, SO_SNDTIMEO, (char *)&timeout, sizeof(timeout));
|
||||||
|
#else
|
||||||
|
setsockopt(this->m_socket, SOL_SOCKET, SO_RCVTIMEO, &timeout, sizeof(timeout));
|
||||||
|
setsockopt(this->m_socket, SOL_SOCKET, SO_SNDTIMEO, &timeout, sizeof(timeout));
|
||||||
|
#endif
|
||||||
|
|
||||||
|
if (connect(this->m_socket, reinterpret_cast<sockaddr *>(&serv_addr), sizeof(serv_addr)) < 0) {
|
||||||
|
spdlog::error("[TCP] Connection failed");
|
||||||
|
deinit();
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
this->m_initialized = true;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void TCPClient::deinit() {
|
||||||
|
this->m_initialized = false;
|
||||||
|
if (this->m_socket > 0) {
|
||||||
|
CLOSE_SOCKET(this->m_socket);
|
||||||
|
this->m_socket = -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int TCPClient::send_msg(void *sendbuff, const uint32_t len) {
|
||||||
|
if (!m_initialized) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (send(this->m_socket, (char *)&len, 4, 0) < 4) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return send(this->m_socket, (char *)sendbuff, len, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
void TCPClient::rx_thread() const {
|
||||||
|
while (!m_stop_flag) {
|
||||||
|
if (!m_initialized) {
|
||||||
|
std::this_thread::sleep_for(SLEEP_WHILE_INITIALIZING);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t data_len = 0;
|
||||||
|
if (recv(this->m_socket, (char *)&data_len, 4, MSG_WAITALL) < 0) {
|
||||||
|
std::this_thread::sleep_for(RX_SLEEP_ON_ERROR);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data_len > MAX_BUFFER_SIZE || data_len < 1) {
|
||||||
|
std::this_thread::sleep_for(RX_SLEEP_ON_ERROR);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto buffer = std::make_unique<std::vector<uint8_t>>();
|
||||||
|
buffer->resize(MAX_BUFFER_SIZE);
|
||||||
|
if (const auto read = recv(this->m_socket, (char *)buffer->data(), data_len, MSG_WAITALL);
|
||||||
|
read > 0) {
|
||||||
|
m_rx_queue->enqueue(std::move(buffer), QUEUE_ADD_TIMEOUT);
|
||||||
|
} else {
|
||||||
|
std::this_thread::sleep_for(RX_SLEEP_ON_ERROR);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
187
src/UDPClient.cpp
Normal file
187
src/UDPClient.cpp
Normal file
@@ -0,0 +1,187 @@
|
|||||||
|
//
|
||||||
|
// Created by Johnathon Slightham on 2025-06-10.
|
||||||
|
//
|
||||||
|
|
||||||
|
#include <chrono>
|
||||||
|
#include <cstring>
|
||||||
|
#include <iostream>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include "UDPClient.h"
|
||||||
|
#include "spdlog/spdlog.h"
|
||||||
|
#include "util/log.h"
|
||||||
|
|
||||||
|
constexpr auto SLEEP_WHILE_INITIALIZING = std::chrono::milliseconds(250);
|
||||||
|
constexpr int TX_PORT = 3101;
|
||||||
|
constexpr int RX_PORT = 3100;
|
||||||
|
constexpr std::string RECV_MCAST = "239.1.1.2";
|
||||||
|
constexpr std::string SEND_MCAST = "239.1.1.1";
|
||||||
|
constexpr auto SOCKET_TIMEOUT_MS = 2500;
|
||||||
|
constexpr auto QUEUE_ADD_TIMEOUT = std::chrono::milliseconds(100);
|
||||||
|
constexpr auto RX_SLEEP_ON_ERROR = std::chrono::milliseconds(100);
|
||||||
|
constexpr auto RX_BUFFER_SIZE = 1024;
|
||||||
|
|
||||||
|
// todo: - add authentication
|
||||||
|
// - encryption
|
||||||
|
|
||||||
|
UDPClient::~UDPClient() {
|
||||||
|
this->m_stop_flag = true;
|
||||||
|
this->m_thread.join();
|
||||||
|
this->deinit();
|
||||||
|
}
|
||||||
|
|
||||||
|
int UDPClient::init() {
|
||||||
|
if ((this->m_rx_socket = socket(AF_INET, SOCK_DGRAM, 0)) < 0) {
|
||||||
|
spdlog::error("[UDP] Failed to create socket");
|
||||||
|
print_errno();
|
||||||
|
return -2;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((this->m_tx_socket = socket(AF_INET, SOCK_DGRAM, 0)) < 0) {
|
||||||
|
spdlog::error("[UDP] Failed to create socket");
|
||||||
|
print_errno();
|
||||||
|
deinit();
|
||||||
|
return -2;
|
||||||
|
}
|
||||||
|
|
||||||
|
timeval timeout{};
|
||||||
|
timeout.tv_sec = SOCKET_TIMEOUT_MS / 1000;
|
||||||
|
timeout.tv_usec = (SOCKET_TIMEOUT_MS % 1000) * 1000;
|
||||||
|
|
||||||
|
#ifdef _WIN32
|
||||||
|
setsockopt(this->m_rx_socket, SOL_SOCKET, SO_RCVTIMEO, (char *)&timeout, sizeof(timeout));
|
||||||
|
setsockopt(this->m_tx_socket, SOL_SOCKET, SO_SNDTIMEO, (char *)&timeout, sizeof(timeout));
|
||||||
|
#else
|
||||||
|
setsockopt(this->m_rx_socket, SOL_SOCKET, SO_RCVTIMEO, &timeout, sizeof(timeout));
|
||||||
|
setsockopt(this->m_tx_socket, SOL_SOCKET, SO_SNDTIMEO, &timeout, sizeof(timeout));
|
||||||
|
#endif
|
||||||
|
|
||||||
|
sockaddr_in server_addr = {
|
||||||
|
.sin_family = AF_INET,
|
||||||
|
.sin_port = htons(RX_PORT),
|
||||||
|
};
|
||||||
|
server_addr.sin_addr.s_addr = INADDR_ANY;
|
||||||
|
|
||||||
|
if (int err = bind(m_rx_socket, reinterpret_cast<struct sockaddr *>(&server_addr),
|
||||||
|
sizeof(server_addr));
|
||||||
|
0 != err) {
|
||||||
|
spdlog::error("[UDP] Socket unable to bind");
|
||||||
|
print_errno();
|
||||||
|
deinit();
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr int opt = 1;
|
||||||
|
#ifdef _WIN32
|
||||||
|
setsockopt(m_rx_socket, SOL_SOCKET, SO_REUSEADDR, (char *)&opt, sizeof(opt));
|
||||||
|
setsockopt(m_tx_socket, SOL_SOCKET, SO_REUSEADDR, (char *)&opt, sizeof(opt));
|
||||||
|
#else
|
||||||
|
setsockopt(m_rx_socket, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
|
||||||
|
setsockopt(m_tx_socket, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
|
||||||
|
#endif
|
||||||
|
|
||||||
|
ip_mreq mreq;
|
||||||
|
mreq.imr_multiaddr.s_addr = inet_addr(RECV_MCAST.c_str());
|
||||||
|
mreq.imr_interface.s_addr = INADDR_ANY;
|
||||||
|
|
||||||
|
#ifdef _WIN32
|
||||||
|
// Get hostname, resolve to primary IPv4 (won't work for all cases)
|
||||||
|
char hostname[256];
|
||||||
|
gethostname(hostname, sizeof(hostname));
|
||||||
|
hostent *host = gethostbyname(hostname);
|
||||||
|
if (host && host->h_addr_list[0]) {
|
||||||
|
mreq.imr_interface.s_addr = *(uint32_t *)host->h_addr_list[0];
|
||||||
|
} else {
|
||||||
|
mreq.imr_interface.s_addr = INADDR_ANY; // Fallback
|
||||||
|
}
|
||||||
|
|
||||||
|
spdlog::info("[UDP] Listening on {}", mreq.imr_interface.s_addr);
|
||||||
|
|
||||||
|
if (setsockopt(m_rx_socket, IPPROTO_IP, IP_ADD_MEMBERSHIP, (char *)&mreq, sizeof(mreq)) < 0) {
|
||||||
|
spdlog::error("[UDP] Failed to join multicast group");
|
||||||
|
print_errno();
|
||||||
|
deinit();
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
setsockopt(m_rx_socket, IPPROTO_IP, IP_ADD_MEMBERSHIP, &mreq, sizeof(mreq));
|
||||||
|
#endif
|
||||||
|
|
||||||
|
this->m_initialized = true;
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void UDPClient::deinit() {
|
||||||
|
this->m_initialized = false;
|
||||||
|
|
||||||
|
if (this->m_tx_socket > 0) {
|
||||||
|
CLOSE_SOCKET(this->m_tx_socket);
|
||||||
|
this->m_tx_socket = -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this->m_rx_socket > 0) {
|
||||||
|
CLOSE_SOCKET(this->m_rx_socket);
|
||||||
|
this->m_rx_socket = -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int UDPClient::send_msg(void *sendbuff, const uint32_t len) {
|
||||||
|
if (!m_initialized) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<uint8_t> buffer;
|
||||||
|
buffer.resize(len + 4);
|
||||||
|
|
||||||
|
*reinterpret_cast<uint32_t *>(buffer.data()) = static_cast<uint32_t>(len);
|
||||||
|
std::memcpy(buffer.data() + 4, sendbuff, len);
|
||||||
|
|
||||||
|
sockaddr_in mcast_dest = {
|
||||||
|
.sin_family = AF_INET,
|
||||||
|
.sin_port = htons(TX_PORT),
|
||||||
|
};
|
||||||
|
inet_pton(AF_INET, SEND_MCAST.c_str(), &mcast_dest.sin_addr);
|
||||||
|
|
||||||
|
#ifdef _WIN32
|
||||||
|
return sendto(m_tx_socket, reinterpret_cast<const char *>(buffer.data()), buffer.size(), 0,
|
||||||
|
reinterpret_cast<sockaddr *>(&mcast_dest), sizeof(mcast_dest));
|
||||||
|
#else
|
||||||
|
return sendto(m_tx_socket, buffer.data(), buffer.size(), 0,
|
||||||
|
reinterpret_cast<sockaddr *>(&mcast_dest), sizeof(mcast_dest));
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
void UDPClient::rx_thread() const {
|
||||||
|
|
||||||
|
while (!m_stop_flag) {
|
||||||
|
if (!m_initialized) {
|
||||||
|
std::this_thread::sleep_for(RX_SLEEP_ON_ERROR);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto buffer = std::make_unique<std::vector<uint8_t>>();
|
||||||
|
buffer->resize(RX_BUFFER_SIZE);
|
||||||
|
|
||||||
|
#ifdef _WIN32
|
||||||
|
const auto len = recv(m_rx_socket, (char *)buffer->data(), RX_BUFFER_SIZE, 0);
|
||||||
|
#else
|
||||||
|
const auto len = recv(m_rx_socket, buffer->data(), RX_BUFFER_SIZE, 0);
|
||||||
|
#endif
|
||||||
|
if (len < 0) {
|
||||||
|
std::this_thread::sleep_for(RX_SLEEP_ON_ERROR);
|
||||||
|
} else if (len < 4 || len > RX_BUFFER_SIZE) {
|
||||||
|
spdlog::error("[UDP] Message size of {} incorrect", len);
|
||||||
|
} else {
|
||||||
|
uint32_t msg_size = *reinterpret_cast<uint32_t *>(buffer->data());
|
||||||
|
if (msg_size > len - 4) {
|
||||||
|
spdlog::error("[UDP] Message size incorrect {}", msg_size);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
buffer->erase(buffer->begin(), buffer->begin() + 4);
|
||||||
|
buffer->resize(msg_size);
|
||||||
|
m_rx_queue->enqueue(std::move(buffer), QUEUE_ADD_TIMEOUT);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
134
src/librpc.cpp
Normal file
134
src/librpc.cpp
Normal file
@@ -0,0 +1,134 @@
|
|||||||
|
#include "librpc.h"
|
||||||
|
|
||||||
|
#include <mutex>
|
||||||
|
#include <optional>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#undef min
|
||||||
|
#include <algorithm>
|
||||||
|
|
||||||
|
#include "flatbuffers/MPIMessageBuilder.h"
|
||||||
|
#include "spdlog/spdlog.h"
|
||||||
|
|
||||||
|
constexpr auto MAX_RECV_WAIT_TIME = std::chrono::seconds(3);
|
||||||
|
constexpr auto PER_TAG_MAX_QUEUE_SIZE = 50;
|
||||||
|
constexpr auto MAX_WAIT_TIME_TAG_ENQUEUE = std::chrono::milliseconds(250);
|
||||||
|
constexpr auto MAX_WAIT_TIME_RX_THREAD_DEQUEUE = std::chrono::milliseconds(250);
|
||||||
|
|
||||||
|
MessagingInterface::~MessagingInterface() {
|
||||||
|
m_stop_flag = true;
|
||||||
|
m_rx_thread.join();
|
||||||
|
|
||||||
|
#ifdef _WIN32
|
||||||
|
WSACleanup();
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
int MessagingInterface::send(uint8_t *buffer, const size_t size, const uint8_t destination,
|
||||||
|
const uint8_t tag, const bool durable) {
|
||||||
|
if (!this->m_id_to_lossless_client.contains(destination)) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
Flatbuffers::MPIMessageBuilder builder;
|
||||||
|
const auto [mpi_buffer, mpi_size] = builder.build_mpi_message(
|
||||||
|
Messaging::MessageType_PTP, PC_MODULE_ID, destination, m_sequence_number++, durable, tag,
|
||||||
|
std::vector<uint8_t>(buffer, buffer + size));
|
||||||
|
|
||||||
|
std::shared_lock lock(m_client_mutex);
|
||||||
|
if (durable) {
|
||||||
|
this->m_id_to_lossless_client[destination]->send_msg(mpi_buffer, mpi_size);
|
||||||
|
} else {
|
||||||
|
this->m_id_to_lossy_client[destination]->send_msg(mpi_buffer, mpi_size);
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int MessagingInterface::broadcast(uint8_t *buffer, size_t size, bool durable) {
|
||||||
|
return -1; // todo
|
||||||
|
}
|
||||||
|
|
||||||
|
std::optional<SizeAndSource> MessagingInterface::recv(uint8_t *buffer, const size_t size,
|
||||||
|
uint8_t tag) {
|
||||||
|
if (!m_tag_to_queue_map.contains(tag)) {
|
||||||
|
m_tag_to_queue_map.insert(
|
||||||
|
{tag, std::make_unique<BlockingQueue<std::unique_ptr<std::vector<uint8_t>>>>(
|
||||||
|
PER_TAG_MAX_QUEUE_SIZE)});
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto data = m_tag_to_queue_map[tag]->dequeue(MAX_RECV_WAIT_TIME);
|
||||||
|
|
||||||
|
if (!data.has_value()) {
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Anything in the queue should already be validated
|
||||||
|
const auto mpi_message =
|
||||||
|
Flatbuffers::MPIMessageBuilder::parse_mpi_message(data.value()->data());
|
||||||
|
const auto data_size = std::min(size, static_cast<size_t>(mpi_message->length()));
|
||||||
|
|
||||||
|
std::memcpy(buffer, mpi_message->payload()->data(), data_size);
|
||||||
|
|
||||||
|
return std::make_optional<SizeAndSource>({data_size, mpi_message->sender()});
|
||||||
|
}
|
||||||
|
|
||||||
|
int MessagingInterface::sendrecv(uint8_t *send_buffer, size_t send_size, uint8_t dest,
|
||||||
|
uint8_t send_tag, uint8_t *recv_buffer, size_t recv_size,
|
||||||
|
uint8_t recv_tag) {
|
||||||
|
// no-op
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::unordered_set<uint8_t>
|
||||||
|
MessagingInterface::find_connected_modules(const std::chrono::duration<double> scan_duration) {
|
||||||
|
// Cannot just skip the call if already running, since the caller needs the list of modules.
|
||||||
|
std::unique_lock scan_lock(m_scan_mutex);
|
||||||
|
const auto foundModules = this->m_discovery_service->find_modules(scan_duration);
|
||||||
|
scan_lock.unlock();
|
||||||
|
|
||||||
|
std::unique_lock lock(m_client_mutex);
|
||||||
|
|
||||||
|
std::vector<uint8_t> existing_clients;
|
||||||
|
existing_clients.reserve(m_id_to_lossless_client.size());
|
||||||
|
for (auto &kv : m_id_to_lossless_client) {
|
||||||
|
existing_clients.push_back(kv.first);
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto new_lossless =
|
||||||
|
this->m_discovery_service->get_lossless_clients(m_rx_queue, existing_clients);
|
||||||
|
const auto new_lossy =
|
||||||
|
this->m_discovery_service->get_lossy_clients(m_rx_queue, existing_clients);
|
||||||
|
|
||||||
|
m_id_to_lossless_client.insert(new_lossless.begin(), new_lossless.end());
|
||||||
|
m_id_to_lossy_client.insert(new_lossy.begin(), new_lossy.end());
|
||||||
|
|
||||||
|
return foundModules;
|
||||||
|
}
|
||||||
|
|
||||||
|
void MessagingInterface::handle_recv() {
|
||||||
|
while (!m_stop_flag) {
|
||||||
|
if (auto data = this->m_rx_queue->dequeue(MAX_WAIT_TIME_RX_THREAD_DEQUEUE);
|
||||||
|
data.has_value()) {
|
||||||
|
flatbuffers::Verifier verifier(data.value()->data(), data.value()->size());
|
||||||
|
bool ok = Messaging::VerifyMPIMessageBuffer(verifier);
|
||||||
|
if (!ok) {
|
||||||
|
spdlog::error("[LibRPC] Got invalid flatbuffer data");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto &mpi_message =
|
||||||
|
Flatbuffers::MPIMessageBuilder::parse_mpi_message(data.value()->data());
|
||||||
|
|
||||||
|
if (!m_tag_to_queue_map.contains(mpi_message->tag())) {
|
||||||
|
m_tag_to_queue_map.insert(
|
||||||
|
{mpi_message->tag(),
|
||||||
|
std::make_unique<BlockingQueue<std::unique_ptr<std::vector<uint8_t>>>>(
|
||||||
|
PER_TAG_MAX_QUEUE_SIZE)});
|
||||||
|
}
|
||||||
|
|
||||||
|
m_tag_to_queue_map[mpi_message->tag()]->enqueue(std::move(data.value()),
|
||||||
|
MAX_WAIT_TIME_TAG_ENQUEUE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
367
src/mDNSDiscoveryService.cpp
Normal file
367
src/mDNSDiscoveryService.cpp
Normal file
@@ -0,0 +1,367 @@
|
|||||||
|
//
|
||||||
|
// Created by Johnathon Slightham on 2025-06-10.
|
||||||
|
//
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <cstring>
|
||||||
|
#include <iostream>
|
||||||
|
#include <optional>
|
||||||
|
#include <thread>
|
||||||
|
|
||||||
|
#include "TCPClient.h"
|
||||||
|
#include "mDNSDiscoveryService.h"
|
||||||
|
|
||||||
|
#include "UDPClient.h"
|
||||||
|
#include "spdlog/spdlog.h"
|
||||||
|
#include "util/ip.h"
|
||||||
|
#include "util/string.h"
|
||||||
|
|
||||||
|
#define MDNS_PORT 5353
|
||||||
|
#define MDNS_GROUP "224.0.0.251"
|
||||||
|
#define RECV_BLOCK_SIZE 1024
|
||||||
|
#define MODULE_TYPE_STR "module_type"
|
||||||
|
#define MODULE_ID_STR "module_id"
|
||||||
|
#define CONNECTED_MODULES_STR "connected_modules"
|
||||||
|
|
||||||
|
#pragma pack(push, 1) // prevent padding between struct members
|
||||||
|
struct query_header {
|
||||||
|
uint16_t id;
|
||||||
|
uint16_t flags;
|
||||||
|
uint16_t num_questions;
|
||||||
|
uint16_t num_answers;
|
||||||
|
uint16_t num_authority;
|
||||||
|
uint16_t num_additional;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct query_footer { // footer for the question not for the packet
|
||||||
|
uint16_t type = htons(0x00FF);
|
||||||
|
uint16_t class_id = htons(0x8001);
|
||||||
|
};
|
||||||
|
|
||||||
|
struct answer {
|
||||||
|
uint16_t type;
|
||||||
|
uint16_t answer_class;
|
||||||
|
uint32_t ttl;
|
||||||
|
uint16_t data_length;
|
||||||
|
};
|
||||||
|
|
||||||
|
#pragma pack(pop)
|
||||||
|
|
||||||
|
mDNSDiscoveryService::mDNSDiscoveryService() = default;
|
||||||
|
|
||||||
|
mDNSDiscoveryService::~mDNSDiscoveryService() = default;
|
||||||
|
|
||||||
|
std::unordered_set<uint8_t>
|
||||||
|
mDNSDiscoveryService::find_modules(const std::chrono::duration<double> wait_time) {
|
||||||
|
std::unordered_set<uint8_t> modules{};
|
||||||
|
|
||||||
|
const socket_t sock = socket(AF_INET, SOCK_DGRAM, 0);
|
||||||
|
if (sock < 0) {
|
||||||
|
printf("socket() failed: %s\n", strerror(errno));
|
||||||
|
return modules;
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr int reuse = 1;
|
||||||
|
timeval tv{};
|
||||||
|
tv.tv_sec = 1;
|
||||||
|
tv.tv_usec = 0;
|
||||||
|
#ifdef _WIN32
|
||||||
|
setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (char *)&reuse, sizeof(reuse));
|
||||||
|
setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, (char *)&tv, sizeof tv);
|
||||||
|
// Windows does not support SO_REUSEPORT
|
||||||
|
#else
|
||||||
|
setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse));
|
||||||
|
setsockopt(sock, SOL_SOCKET, SO_REUSEPORT, &reuse, sizeof(reuse));
|
||||||
|
setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof tv);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
sockaddr_in localAddr{};
|
||||||
|
localAddr.sin_family = AF_INET;
|
||||||
|
localAddr.sin_port = htons(MDNS_PORT);
|
||||||
|
localAddr.sin_addr.s_addr = INADDR_ANY;
|
||||||
|
if (bind(sock, reinterpret_cast<sockaddr *>(&localAddr), sizeof(localAddr)) < 0) {
|
||||||
|
printf("bind() failed: %s\n", strerror(errno));
|
||||||
|
CLOSE_SOCKET(sock);
|
||||||
|
return modules;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Join mDNS multicast group
|
||||||
|
ip_mreq mreq{};
|
||||||
|
mreq.imr_multiaddr.s_addr = inet_addr(MDNS_GROUP);
|
||||||
|
mreq.imr_interface.s_addr = INADDR_ANY;
|
||||||
|
|
||||||
|
#ifdef _WIN32
|
||||||
|
if (setsockopt(sock, IPPROTO_IP, IP_ADD_MEMBERSHIP, (char *)&mreq, sizeof(mreq)) < 0) {
|
||||||
|
#else
|
||||||
|
if (setsockopt(sock, IPPROTO_IP, IP_ADD_MEMBERSHIP, &mreq, sizeof(mreq)) < 0) {
|
||||||
|
#endif
|
||||||
|
printf("setsockopt() failed: %s\n", strerror(errno));
|
||||||
|
CLOSE_SOCKET(sock);
|
||||||
|
return modules;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send mDNS query and get responses
|
||||||
|
sockaddr_in mcastAddr{};
|
||||||
|
mcastAddr.sin_family = AF_INET;
|
||||||
|
mcastAddr.sin_port = htons(MDNS_PORT);
|
||||||
|
inet_pton(AF_INET, MDNS_GROUP, &mcastAddr.sin_addr);
|
||||||
|
const auto start = std::chrono::system_clock::now();
|
||||||
|
std::vector<std::unique_ptr<std::vector<uint8_t>>> responses;
|
||||||
|
|
||||||
|
while (std::chrono::system_clock::now() - start < wait_time) {
|
||||||
|
send_mdns_query(sock, mcastAddr);
|
||||||
|
|
||||||
|
std::this_thread::sleep_for(wait_time / 5);
|
||||||
|
|
||||||
|
responses.emplace_back(std::make_unique<std::vector<uint8_t>>());
|
||||||
|
responses.back()->resize(RECV_BLOCK_SIZE);
|
||||||
|
#ifdef _WIN32
|
||||||
|
const auto len = recv(sock, (char *)responses.back()->data(), RECV_BLOCK_SIZE, 0);
|
||||||
|
#else
|
||||||
|
const auto len = recv(sock, responses.back()->data(), RECV_BLOCK_SIZE, 0);
|
||||||
|
#endif
|
||||||
|
if (len > 0) {
|
||||||
|
responses.back()->resize(len);
|
||||||
|
} else {
|
||||||
|
responses.pop_back();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
CLOSE_SOCKET(sock);
|
||||||
|
|
||||||
|
this->module_to_mdns.clear();
|
||||||
|
|
||||||
|
for (const auto &response : responses) {
|
||||||
|
if (const auto parsed_response = parse_response(response->data(), response->size());
|
||||||
|
parsed_response.has_value()) {
|
||||||
|
modules.insert(parsed_response.value().id);
|
||||||
|
this->module_to_mdns.insert({parsed_response.value().id, parsed_response.value()});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return modules;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::unordered_map<uint8_t, std::shared_ptr<ICommunicationClient>>
|
||||||
|
mDNSDiscoveryService::get_lossy_clients(
|
||||||
|
const std::shared_ptr<BlockingQueue<std::unique_ptr<std::vector<uint8_t>>>> &rx_queue,
|
||||||
|
std::vector<uint8_t> &skip_modules) {
|
||||||
|
return this->create_clients<UDPClient>(rx_queue, skip_modules);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::unordered_map<uint8_t, std::shared_ptr<ICommunicationClient>>
|
||||||
|
mDNSDiscoveryService::get_lossless_clients(
|
||||||
|
const std::shared_ptr<BlockingQueue<std::unique_ptr<std::vector<uint8_t>>>> &rx_queue,
|
||||||
|
std::vector<uint8_t> &skip_modules) {
|
||||||
|
return this->create_clients<TCPClient>(rx_queue, skip_modules);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
std::unordered_map<uint8_t, std::shared_ptr<ICommunicationClient>>
|
||||||
|
mDNSDiscoveryService::create_clients(
|
||||||
|
const std::shared_ptr<BlockingQueue<std::unique_ptr<std::vector<uint8_t>>>> &rx_queue,
|
||||||
|
std::vector<uint8_t> &skip_modules) {
|
||||||
|
std::unordered_map<uint8_t, std::shared_ptr<ICommunicationClient>> clients;
|
||||||
|
|
||||||
|
for (const auto &[id, module] : this->module_to_mdns) {
|
||||||
|
if (std::find(skip_modules.begin(), skip_modules.end(), id) != skip_modules.end()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto client = std::make_shared<T>(module.ip, rx_queue);
|
||||||
|
client->init();
|
||||||
|
|
||||||
|
for (const auto &connected_module : module.connected_module_ids) {
|
||||||
|
// todo: add only if not connected directly
|
||||||
|
clients[connected_module] = client;
|
||||||
|
}
|
||||||
|
|
||||||
|
clients[id] = client;
|
||||||
|
}
|
||||||
|
|
||||||
|
return clients;
|
||||||
|
}
|
||||||
|
|
||||||
|
void mDNSDiscoveryService::send_mdns_query(const socket_t sock, const sockaddr_in &addr) {
|
||||||
|
query_header header{};
|
||||||
|
header.id = htons(0);
|
||||||
|
header.flags = htons(0x0000);
|
||||||
|
header.num_questions = htons(1);
|
||||||
|
header.num_answers = htons(0);
|
||||||
|
header.num_authority = htons(0);
|
||||||
|
header.num_additional = htons(0);
|
||||||
|
|
||||||
|
constexpr uint8_t domain_name[] = {
|
||||||
|
13, '_', 'r', 'o', 'b', 'o', 't', 'c', 'o', 'n', 't', 'r', 'o',
|
||||||
|
'l', 4, '_', 't', 'c', 'p', 5, 'l', 'o', 'c', 'a', 'l', 0,
|
||||||
|
};
|
||||||
|
|
||||||
|
query_footer footer;
|
||||||
|
footer.type = htons(0x00FF);
|
||||||
|
footer.class_id = htons(0x0001);
|
||||||
|
|
||||||
|
uint8_t buffer[1024] = {};
|
||||||
|
memcpy(buffer, &header, sizeof(header));
|
||||||
|
memcpy(buffer + sizeof(header), &domain_name, sizeof(domain_name));
|
||||||
|
memcpy(buffer + sizeof(header) + sizeof(domain_name), &footer, sizeof(footer));
|
||||||
|
#ifdef _WIN32
|
||||||
|
sendto(sock, (char *)&buffer, sizeof(header) + sizeof(domain_name) + sizeof(footer), 0,
|
||||||
|
(sockaddr *)&addr, sizeof(addr));
|
||||||
|
#else
|
||||||
|
sendto(sock, &buffer, sizeof(header) + sizeof(domain_name) + sizeof(footer), 0,
|
||||||
|
(sockaddr *)&addr, sizeof(addr));
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
std::optional<mDNSRobotModule> mDNSDiscoveryService::parse_response(uint8_t *buffer,
|
||||||
|
const int size) {
|
||||||
|
int ptr = 0;
|
||||||
|
mDNSRobotModule response{};
|
||||||
|
|
||||||
|
// Header
|
||||||
|
if (size < sizeof(query_header)) {
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
const auto h = reinterpret_cast<query_header *>(buffer + ptr);
|
||||||
|
ptr += sizeof(query_header);
|
||||||
|
h->num_questions = ntohs(h->num_questions);
|
||||||
|
h->num_answers = ntohs(h->num_answers);
|
||||||
|
h->num_authority = ntohs(h->num_authority);
|
||||||
|
h->num_additional = ntohs(h->num_additional);
|
||||||
|
|
||||||
|
// Questions
|
||||||
|
for (int i = 0; i < h->num_questions; i++) {
|
||||||
|
if (ptr > size) {
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
|
// We ignore questions for now
|
||||||
|
auto [name, new_ptr] = read_mdns_name(buffer, size, ptr);
|
||||||
|
if (new_ptr < 1) {
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
ptr = new_ptr;
|
||||||
|
ptr += sizeof(query_footer);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Answers and authority (we do not care about authority).
|
||||||
|
bool robot_module = false;
|
||||||
|
for (int i = 0; i < h->num_answers + h->num_authority + h->num_additional; i++) {
|
||||||
|
if (ptr > size) {
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
// We assume that the boards mdns does not send any questions asking for
|
||||||
|
// other boards (and thus does not compress the domain name we are looking
|
||||||
|
// for).
|
||||||
|
|
||||||
|
const auto [name, new_ptr] = read_mdns_name(buffer, size, ptr);
|
||||||
|
if (new_ptr < 1) {
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
ptr = new_ptr;
|
||||||
|
|
||||||
|
robot_module |= name.find("_robotcontrol") != std::string::npos;
|
||||||
|
response.hostname = name;
|
||||||
|
|
||||||
|
const auto a = reinterpret_cast<answer *>(buffer + ptr);
|
||||||
|
a->type = ntohs(a->type);
|
||||||
|
a->answer_class = ntohs(a->answer_class);
|
||||||
|
a->ttl = ntohs(a->ttl);
|
||||||
|
a->data_length = ntohs(a->data_length);
|
||||||
|
ptr += sizeof(answer);
|
||||||
|
|
||||||
|
// A-Record
|
||||||
|
if (a->type == 1 && robot_module) {
|
||||||
|
std::vector<uint8_t> data;
|
||||||
|
data.resize(a->data_length);
|
||||||
|
std::memcpy(data.data(), buffer + ptr, a->data_length);
|
||||||
|
|
||||||
|
std::stringstream ip;
|
||||||
|
for (int j = 0; j < a->data_length; j++) {
|
||||||
|
ip << static_cast<int>(data[j]);
|
||||||
|
if (j < a->data_length - 1) {
|
||||||
|
ip << '.';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
response.ip = ip.str();
|
||||||
|
}
|
||||||
|
|
||||||
|
// TXT-Recrod
|
||||||
|
if (a->type == 16 && robot_module) {
|
||||||
|
int inner_ptr = ptr;
|
||||||
|
while (inner_ptr < a->data_length + ptr) {
|
||||||
|
const int len = buffer[inner_ptr++];
|
||||||
|
std::string s(reinterpret_cast<char *>(buffer + inner_ptr), len);
|
||||||
|
inner_ptr += len;
|
||||||
|
|
||||||
|
const auto split_string = split(s, '=');
|
||||||
|
if (split_string.size() != 2) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (split_string[0] == MODULE_ID_STR) {
|
||||||
|
response.id = stoi(split_string[1]);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (split_string[0] == MODULE_TYPE_STR) {
|
||||||
|
response.module_type = static_cast<ModuleType>(stoi(split_string[1]));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (split_string[0] == CONNECTED_MODULES_STR) {
|
||||||
|
for (const auto connected_modules = split(split_string[1], ',');
|
||||||
|
const auto &module_id : connected_modules) {
|
||||||
|
response.connected_module_ids.emplace_back(stoi(module_id));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ptr += a->data_length;
|
||||||
|
}
|
||||||
|
|
||||||
|
return robot_module && is_valid_ipv4(response.ip) ? std::optional{response} : std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::tuple<std::string, int> mDNSDiscoveryService::read_mdns_name(const uint8_t *buffer,
|
||||||
|
const int size, int ptr) {
|
||||||
|
int len = 0;
|
||||||
|
std::stringstream ss;
|
||||||
|
|
||||||
|
int i = 0;
|
||||||
|
while (ptr < size) {
|
||||||
|
if (0 >= len) {
|
||||||
|
if (0 == buffer[ptr]) { // end
|
||||||
|
ptr++;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (0 != i) {
|
||||||
|
ss << ".";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (buffer[ptr] >= 0xC0) { // compressed
|
||||||
|
ptr++;
|
||||||
|
if (buffer[ptr] < 0 || buffer[ptr] > ptr) {
|
||||||
|
return {"", -1};
|
||||||
|
}
|
||||||
|
const auto [name, l] = read_mdns_name(buffer, size, buffer[ptr]);
|
||||||
|
if (l < 1) {
|
||||||
|
return {"", -1};
|
||||||
|
}
|
||||||
|
ptr++;
|
||||||
|
ss << name;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
len = buffer[ptr]; // update length
|
||||||
|
} else {
|
||||||
|
len--;
|
||||||
|
ss << buffer[ptr];
|
||||||
|
}
|
||||||
|
ptr++;
|
||||||
|
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {ss.str(), ptr};
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user