#1 - quicr module
This commit is contained in:
@@ -26,8 +26,8 @@ target_link_libraries(${PROJECT_NAME}
|
||||
PUBLIC
|
||||
tw::network
|
||||
tl::expected
|
||||
tw::messaging
|
||||
tw::protocol
|
||||
tw::message_protocol
|
||||
protobuf::libprotobuf
|
||||
tw::chat::lib
|
||||
spdlog::spdlog
|
||||
|
||||
@@ -1,16 +1,20 @@
|
||||
#pragma once
|
||||
|
||||
#include "MessageSession.hpp"
|
||||
#include "ProtobufMessages.hpp"
|
||||
#include "SendChatMessage.hpp"
|
||||
#include "ChatClientError.hpp"
|
||||
#include "models/ChatMessage.hpp"
|
||||
#include "message_protocol/MessageEndpoint.hpp"
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
namespace tw::chat {
|
||||
|
||||
class ChatClient {
|
||||
tw::MessageSession m_session;
|
||||
std::unique_ptr<msg::MessageEndpoint> m_endpoint;
|
||||
msg::MessageConnection* m_server;
|
||||
ProtobufMessages m_messages;
|
||||
|
||||
std::function<void(ChatMessage)> m_on_message;
|
||||
std::function<void(tl::expected<void, ChatClientError>)> m_on_send_response;
|
||||
|
||||
@@ -22,45 +22,68 @@ tl::expected<void, tw::chat::ChatClientError> from_error_code(mmo::chat::ChatErr
|
||||
|
||||
namespace tw::chat {
|
||||
|
||||
namespace {
|
||||
|
||||
std::unique_ptr<tw::msg::MessageEndpoint> create_endpoint() {
|
||||
auto endpoint_r = tw::msg::MessageEndpoint::create();
|
||||
if (!endpoint_r) {
|
||||
throw std::runtime_error("Chat client failed to create an endpoint: " +
|
||||
endpoint_r.error().message());
|
||||
}
|
||||
|
||||
return std::move(endpoint_r.value());
|
||||
}
|
||||
|
||||
tw::msg::MessageConnection* connect_to_server(tw::msg::MessageEndpoint* endpoint,
|
||||
const std::string& server_address,
|
||||
int16_t port) {
|
||||
auto server_r = endpoint->connect(server_address, port);
|
||||
if (!server_r) {
|
||||
throw std::runtime_error("Chat client failed to connect: " + server_r.error().message());
|
||||
}
|
||||
|
||||
return server_r.value();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
ChatClient::ChatClient(const std::string& server_address, int16_t port)
|
||||
: m_session(net::Address(server_address, port))
|
||||
: m_endpoint(create_endpoint())
|
||||
, m_server(connect_to_server(m_endpoint.get(), server_address, port))
|
||||
, m_messages(m_endpoint.get())
|
||||
{
|
||||
m_session.set_handler(CHAT_MESSAGE_BROADCAST_REQUEST, [this](std::span<const std::byte> data) {
|
||||
if (!m_on_message) return;
|
||||
mmo::chat::ChatMessageBroadcastRequest proto;
|
||||
if (!proto.ParseFromArray(data.data(), static_cast<int>(data.size()))) return;
|
||||
ChatMessage msg;
|
||||
msg.channel_id = proto.channel_id();
|
||||
msg.client_id = proto.sender_id();
|
||||
msg.message = proto.message();
|
||||
msg.timestamp = ChatMessage::Clock::now();
|
||||
m_on_message(std::move(msg));
|
||||
});
|
||||
m_messages.set_handler<mmo::chat::ChatMessageBroadcastRequest>(
|
||||
[this](msg::PeerId, const mmo::chat::ChatMessageBroadcastRequest& proto) {
|
||||
if (!m_on_message) return;
|
||||
ChatMessage msg;
|
||||
msg.channel_id = proto.channel_id();
|
||||
msg.client_id = proto.sender_id();
|
||||
msg.message = proto.message();
|
||||
msg.timestamp = ChatMessage::Clock::now();
|
||||
m_on_message(std::move(msg));
|
||||
});
|
||||
|
||||
m_session.set_handler(CHAT_SEND_MESSAGE_RESPONSE, [this](std::span<const std::byte> data) {
|
||||
if (!m_on_send_response) return;
|
||||
mmo::chat::SendChatMessageResponse proto;
|
||||
if (!proto.ParseFromArray(data.data(), static_cast<int>(data.size()))) return;
|
||||
m_on_send_response(from_error_code(proto.error()));
|
||||
});
|
||||
m_messages.set_handler<mmo::chat::SendChatMessageResponse>(
|
||||
[this](msg::PeerId, const mmo::chat::SendChatMessageResponse& proto) {
|
||||
if (!m_on_send_response) return;
|
||||
m_on_send_response(from_error_code(proto.error()));
|
||||
});
|
||||
|
||||
m_session.set_handler(CHAT_JOIN_CHANNEL_RESPONSE, [this](std::span<const std::byte> data) {
|
||||
if (!m_on_join_response) return;
|
||||
mmo::chat::JoinChannelResponse proto;
|
||||
if (!proto.ParseFromArray(data.data(), static_cast<int>(data.size()))) return;
|
||||
m_on_join_response(static_cast<uint32_t>(proto.channel_id()), from_error_code(proto.error()));
|
||||
});
|
||||
m_messages.set_handler<mmo::chat::JoinChannelResponse>(
|
||||
[this](msg::PeerId, const mmo::chat::JoinChannelResponse& proto) {
|
||||
if (!m_on_join_response) return;
|
||||
m_on_join_response(static_cast<uint32_t>(proto.channel_id()), from_error_code(proto.error()));
|
||||
});
|
||||
|
||||
m_session.set_handler(CHAT_LEAVE_CHANNEL_RESPONSE, [this](std::span<const std::byte> data) {
|
||||
if (!m_on_leave_response) return;
|
||||
mmo::chat::LeaveChannelResponse proto;
|
||||
if (!proto.ParseFromArray(data.data(), static_cast<int>(data.size()))) return;
|
||||
m_on_leave_response(static_cast<uint32_t>(proto.channel_id()), from_error_code(proto.error()));
|
||||
});
|
||||
m_messages.set_handler<mmo::chat::LeaveChannelResponse>(
|
||||
[this](msg::PeerId, const mmo::chat::LeaveChannelResponse& proto) {
|
||||
if (!m_on_leave_response) return;
|
||||
m_on_leave_response(static_cast<uint32_t>(proto.channel_id()), from_error_code(proto.error()));
|
||||
});
|
||||
}
|
||||
|
||||
void ChatClient::update() {
|
||||
m_session.update();
|
||||
m_endpoint->update();
|
||||
}
|
||||
|
||||
tl::expected<void, ChatClientError> ChatClient::send_mesg(SendChatMessage message) {
|
||||
@@ -68,11 +91,7 @@ tl::expected<void, ChatClientError> ChatClient::send_mesg(SendChatMessage messag
|
||||
mesg.set_channel_id(message.channel_id);
|
||||
mesg.set_message(message.message);
|
||||
|
||||
std::vector<std::byte> buf(mesg.ByteSizeLong());
|
||||
(void)mesg.SerializeToArray(buf.data(), static_cast<int>(buf.size()));
|
||||
|
||||
auto send_r = m_session.send(Message<mmo::chat::SendChatMessageRequest>::value,
|
||||
std::span(buf), true);
|
||||
auto send_r = m_messages.send(m_server, mesg, true);
|
||||
if (!send_r)
|
||||
return tl::make_unexpected(ChatClientError::PermissionDenied);
|
||||
return {};
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -5,7 +5,8 @@ add_executable(${PROJECT_NAME} ChatMockClient.cpp)
|
||||
target_link_libraries(${PROJECT_NAME}
|
||||
PRIVATE
|
||||
tw::protocol
|
||||
tw::messaging
|
||||
tw::message_protocol
|
||||
tw::network
|
||||
tw::quicr
|
||||
spdlog::spdlog
|
||||
)
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
#include "Address.hpp"
|
||||
#include "MessageSession.hpp"
|
||||
#include "ProtobufMessages.hpp"
|
||||
#include "MessageRegistry.hpp"
|
||||
#include "Chat.pb.h"
|
||||
#include "message_protocol/MessageEndpoint.hpp"
|
||||
|
||||
#include <spdlog/spdlog.h>
|
||||
#include <atomic>
|
||||
@@ -56,30 +57,42 @@ int main(int argc, char* argv[]) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
tw::MessageSession session(tw::net::Address{std::string{host}, port});
|
||||
auto endpoint_r = tw::msg::MessageEndpoint::create();
|
||||
if (!endpoint_r) {
|
||||
spdlog::error("Failed to create an endpoint: {}", endpoint_r.error().message());
|
||||
return 1;
|
||||
}
|
||||
auto& endpoint = endpoint_r.value();
|
||||
|
||||
auto server_r = endpoint->connect(host, port);
|
||||
if (!server_r) {
|
||||
spdlog::error("Failed to connect: {}", server_r.error().message());
|
||||
return 1;
|
||||
}
|
||||
auto* server = server_r.value();
|
||||
|
||||
tw::ProtobufMessages messages(endpoint.get());
|
||||
|
||||
spdlog::info("Connecting to {}:{}...", host, port);
|
||||
const auto deadline = std::chrono::steady_clock::now() + std::chrono::seconds(5);
|
||||
while (!session.is_established()) {
|
||||
while (!server->is_established()) {
|
||||
if (std::chrono::steady_clock::now() > deadline) {
|
||||
spdlog::error("Connection timed out");
|
||||
return 1;
|
||||
}
|
||||
session.update();
|
||||
endpoint->update();
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(10));
|
||||
}
|
||||
spdlog::info("Connected. Joining channel {}...", channel_id);
|
||||
|
||||
session.set_handler(tw::Message<mmo::chat::ChatMessageBroadcastRequest>::value,
|
||||
[](std::span<const std::byte> data) {
|
||||
mmo::chat::ChatMessageBroadcastRequest bcast;
|
||||
bcast.ParseFromArray(data.data(), static_cast<int>(data.size()));
|
||||
messages.set_handler<mmo::chat::ChatMessageBroadcastRequest>(
|
||||
[](tw::msg::PeerId, const mmo::chat::ChatMessageBroadcastRequest& bcast) {
|
||||
std::println("[ch:{}] <{}> {}", bcast.channel_id(), bcast.sender_id(), bcast.message());
|
||||
});
|
||||
|
||||
mmo::chat::JoinChannelRequest join;
|
||||
join.set_channel_id(channel_id);
|
||||
(void)session.request(
|
||||
(void)server->request(
|
||||
tw::Message<mmo::chat::JoinChannelRequest>::value,
|
||||
serialize(join),
|
||||
[channel_id](std::span<const std::byte> data) {
|
||||
@@ -102,7 +115,7 @@ int main(int argc, char* argv[]) {
|
||||
mmo::chat::SendChatMessageRequest msg;
|
||||
msg.set_channel_id(channel_id);
|
||||
msg.set_message(line);
|
||||
(void)session.request(
|
||||
(void)server->request(
|
||||
tw::Message<mmo::chat::SendChatMessageRequest>::value,
|
||||
serialize(msg),
|
||||
[channel_id](std::span<const std::byte> data) {
|
||||
@@ -113,7 +126,7 @@ int main(int argc, char* argv[]) {
|
||||
}
|
||||
}
|
||||
|
||||
session.update();
|
||||
endpoint->update();
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
Reference in New Issue
Block a user