Prepare files for public release

This commit is contained in:
2026-01-24 10:26:37 -05:00
parent 2f358c30e6
commit d89c636e2f
28 changed files with 2013 additions and 0 deletions

31
src/MPIMessageBuilder.cpp Normal file
View 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
View 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
View 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
View 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);
}
}
}

View 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};
}