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:
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