#1 - quicr module

This commit is contained in:
Martin Slachta
2026-07-22 17:34:44 +02:00
parent a04f0dc262
commit e6dd954ded
149 changed files with 3997 additions and 2126 deletions
@@ -13,8 +13,9 @@ target_include_directories(${PROJECT_NAME}
target_link_libraries(${PROJECT_NAME}
PRIVATE
tw::chat::service
tw::messaging
tw::protocol
tw::message_protocol
tw::network
tw::quicr
spdlog::spdlog
)
@@ -3,7 +3,7 @@
#include "Chat.pb.h"
#include "MessageRegistry.hpp"
#include <spdlog/spdlog.h>
#include <cstring>
#include <stdexcept>
namespace tw::chat {
@@ -16,108 +16,68 @@ static mmo::chat::ChatErrorCode to_error_code(tl::expected<void, ChatServerError
return mmo::chat::CHAT_ERROR_CODE_CHANNEL_NOT_FOUND;
}
template<typename T>
static std::vector<std::byte> serialize(const T& msg) {
std::vector<std::byte> buf(msg.ByteSizeLong());
(void)msg.SerializeToArray(buf.data(), static_cast<int>(buf.size()));
return buf;
static std::unique_ptr<msg::MessageEndpoint> bind_endpoint(int port) {
auto endpoint_r = msg::MessageEndpoint::bind(port);
if (!endpoint_r) {
throw std::runtime_error("Chat server failed to bind to port " + std::to_string(port) +
": " + endpoint_r.error().message());
}
return std::move(endpoint_r.value());
}
ChatServerController::ChatServerController(int port)
: m_endpoint(net::quicr::QuicrEndpoint::create_and_bind(port).value())
, m_listener(net::quicr::QuicrConnectionListener::listen(m_endpoint.get()).value())
: m_endpoint(bind_endpoint(port))
, m_messages(m_endpoint.get())
, m_service([this](uint64_t id, const ChatMessage& msg) { broadcast(id, msg); })
{
m_endpoint->set_on_peer_connected([](msg::PeerId client_id) {
spdlog::info("Chat client connected: {}", client_id);
});
register_handlers();
spdlog::info("Chat server listening on port {}", port);
}
void ChatServerController::register_handlers() {
m_handlers[Message<mmo::chat::SendChatMessageRequest>::value] =
[this](uint64_t client_id, std::span<const std::byte> data) {
mmo::chat::SendChatMessageRequest msg;
msg.ParseFromArray(data.data(), static_cast<int>(data.size()));
m_messages.set_handler<mmo::chat::SendChatMessageRequest>(
[this](msg::PeerId client_id, const mmo::chat::SendChatMessageRequest& msg) {
mmo::chat::SendChatMessageResponse r;
r.set_channel_id(msg.channel_id());
r.set_error(to_error_code(m_service.send_message(client_id, msg.channel_id(), msg.message())));
send_to(client_id, Message<mmo::chat::SendChatMessageResponse>::value, serialize(r));
};
(void)m_messages.send_to(client_id, r, false);
});
m_handlers[Message<mmo::chat::JoinChannelRequest>::value] =
[this](uint64_t client_id, std::span<const std::byte> data) {
mmo::chat::JoinChannelRequest msg;
msg.ParseFromArray(data.data(), static_cast<int>(data.size()));
m_messages.set_handler<mmo::chat::JoinChannelRequest>(
[this](msg::PeerId client_id, const mmo::chat::JoinChannelRequest& msg) {
m_service.join_channel(client_id, msg.channel_id());
mmo::chat::JoinChannelResponse r;
r.set_channel_id(msg.channel_id());
r.set_error(mmo::chat::CHAT_ERROR_CODE_OK);
send_to(client_id, Message<mmo::chat::JoinChannelResponse>::value, serialize(r));
};
(void)m_messages.send_to(client_id, r, false);
});
m_handlers[Message<mmo::chat::LeaveChannelRequest>::value] =
[this](uint64_t client_id, std::span<const std::byte> data) {
mmo::chat::LeaveChannelRequest msg;
msg.ParseFromArray(data.data(), static_cast<int>(data.size()));
m_messages.set_handler<mmo::chat::LeaveChannelRequest>(
[this](msg::PeerId client_id, const mmo::chat::LeaveChannelRequest& msg) {
m_service.leave_channel(client_id, msg.channel_id());
mmo::chat::LeaveChannelResponse r;
r.set_channel_id(msg.channel_id());
r.set_error(mmo::chat::CHAT_ERROR_CODE_OK);
send_to(client_id, Message<mmo::chat::LeaveChannelResponse>::value, serialize(r));
};
(void)m_messages.send_to(client_id, r, false);
});
}
void ChatServerController::update() {
m_endpoint->poll();
net::quicr::QuicrConnection* conn = nullptr;
while ((conn = m_listener->listen())) {
m_connections.emplace(conn->self_id(), conn);
spdlog::info("Chat client connected: {}", conn->self_id());
}
for (auto& [client_id, conn] : m_connections) {
auto r = conn->read_into(m_recv_buf);
if (!r || *r == 0) continue;
dispatch(client_id, std::span(m_recv_buf.data(), *r));
}
m_endpoint->update();
}
void ChatServerController::dispatch(uint64_t client_id, std::span<const std::byte> data) {
constexpr size_t HEADER = sizeof(uint32_t) * 2;
if (data.size() < HEADER) {
spdlog::warn("ChatServerController: dropped short datagram ({} bytes)", data.size());
return;
}
uint32_t type{};
std::memcpy(&type, data.data(), sizeof(type));
if (type >= m_handlers.size() || !m_handlers[type]) {
spdlog::warn("ChatServerController: no handler for type {}", type);
return;
}
m_handlers[type](client_id, data.subspan(HEADER));
}
void ChatServerController::send_to(uint64_t client_id, uint32_t type,
std::span<const std::byte> payload, bool reliable) {
auto it = m_connections.find(client_id);
if (it == m_connections.end()) return;
constexpr uint32_t SEQ_NONE = 0;
std::vector<std::byte> buf(sizeof(type) + sizeof(SEQ_NONE) + payload.size());
std::memcpy(buf.data(), &type, sizeof(type));
std::memcpy(buf.data() + sizeof(type), &SEQ_NONE, sizeof(SEQ_NONE));
std::memcpy(buf.data() + sizeof(type) + sizeof(SEQ_NONE), payload.data(), payload.size());
(void)it->second->send_message(std::span(buf), reliable);
}
void ChatServerController::broadcast(uint64_t client_id, const ChatMessage& msg) {
void ChatServerController::broadcast(msg::PeerId client_id, const ChatMessage& msg) {
mmo::chat::ChatMessageBroadcastRequest bcast;
bcast.set_channel_id(msg.channel_id);
bcast.set_sender_id(msg.client_id);
bcast.set_message(msg.message);
send_to(client_id, Message<mmo::chat::ChatMessageBroadcastRequest>::value,
serialize(bcast), true);
(void)m_messages.send_to(client_id, bcast, true);
}
} // namespace tw::chat
@@ -1,28 +1,18 @@
#pragma once
#include "ChatService.hpp"
#include "protocol/quicr/QuicrEndpoint.hpp"
#include "protocol/quicr/QuicrConnectionListener.hpp"
#include "ProtobufMessages.hpp"
#include "message_protocol/MessageEndpoint.hpp"
#include <array>
#include <cstdint>
#include <functional>
#include <span>
#include <unordered_map>
#include <vector>
#include <memory>
namespace tw::chat {
class ChatServerController {
static constexpr size_t MAX_TYPES = 32;
std::unique_ptr<net::quicr::QuicrEndpoint> m_endpoint;
std::unique_ptr<net::quicr::QuicrConnectionListener> m_listener;
std::unordered_map<uint64_t, net::quicr::QuicrConnection*> m_connections;
std::vector<std::byte> m_recv_buf{64 * 1024};
ChatService m_service;
std::array<std::function<void(uint64_t, std::span<const std::byte>)>, MAX_TYPES> m_handlers{};
std::unique_ptr<msg::MessageEndpoint> m_endpoint;
ProtobufMessages m_messages;
ChatService m_service;
public:
explicit ChatServerController(int port = CHAT_DEFAULT_PORT);
@@ -31,10 +21,7 @@ public:
private:
void register_handlers();
void dispatch(uint64_t client_id, std::span<const std::byte> data);
void send_to(uint64_t client_id, uint32_t type, std::span<const std::byte> payload,
bool reliable = false);
void broadcast(uint64_t client_id, const ChatMessage& msg);
void broadcast(msg::PeerId client_id, const ChatMessage& msg);
};
} // namespace tw::chat