2026-05-11 23:06:39 +03:00
|
|
|
/*
|
|
|
|
|
* InfiniPaint
|
|
|
|
|
* Copyright (C) 2025-2026 Yousef Khadadeh
|
|
|
|
|
*
|
|
|
|
|
* This program is free software: you can redistribute it and/or modify
|
|
|
|
|
* it under the terms of the GNU General Public License as published by
|
|
|
|
|
* the Free Software Foundation, either version 3 of the License, or
|
|
|
|
|
* (at your option) any later version.
|
|
|
|
|
*
|
|
|
|
|
* This program is distributed in the hope that it will be useful,
|
|
|
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
|
* GNU General Public License for more details.
|
|
|
|
|
*
|
|
|
|
|
* You should have received a copy of the GNU General Public License
|
|
|
|
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
|
|
|
*/
|
|
|
|
|
|
2025-07-06 20:43:53 +03:00
|
|
|
#include "NetServer.hpp"
|
2025-07-28 17:07:39 +03:00
|
|
|
#include "Helpers/Networking/ByteStream.hpp"
|
2025-07-06 20:43:53 +03:00
|
|
|
#include "NetLibrary.hpp"
|
|
|
|
|
#include <chrono>
|
|
|
|
|
#include <rtc/peerconnection.hpp>
|
|
|
|
|
#include <Helpers/Random.hpp>
|
|
|
|
|
#include <Helpers/Logger.hpp>
|
|
|
|
|
#include "NetClient.hpp"
|
2025-07-28 17:07:39 +03:00
|
|
|
#include "cereal/archives/portable_binary.hpp"
|
2025-07-06 20:43:53 +03:00
|
|
|
|
|
|
|
|
NetServer::NetServer(const std::string& serverLocalID) {
|
|
|
|
|
localID = serverLocalID;
|
|
|
|
|
Logger::get().log("INFO", "[NetServer::NetServer] Server has local id: " + serverLocalID);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void NetServer::update() {
|
|
|
|
|
if(isDisconnected)
|
|
|
|
|
return;
|
2025-07-10 15:52:01 +03:00
|
|
|
for(auto& client : clients) {
|
|
|
|
|
if(client) {
|
|
|
|
|
bool timedOut = std::chrono::steady_clock::now() - client->lastMessageTime > NetLibrary::TIMEOUT_DURATION;
|
|
|
|
|
if(timedOut) {
|
|
|
|
|
client->setToDisconnect = true;
|
2025-07-06 20:43:53 +03:00
|
|
|
Logger::get().log("INFO", "Connection timed out");
|
2025-07-10 15:52:01 +03:00
|
|
|
}
|
|
|
|
|
if(client->setToDisconnect)
|
|
|
|
|
disconnectCallback(client);
|
|
|
|
|
else {
|
2026-01-05 12:43:40 +03:00
|
|
|
#ifdef NDEBUG
|
2025-12-09 17:20:27 +03:00
|
|
|
try {
|
2026-01-05 12:43:40 +03:00
|
|
|
#endif
|
2025-12-09 17:20:27 +03:00
|
|
|
client->parse_received_messages(*this);
|
|
|
|
|
client->send_queued_messages(*this);
|
2026-01-05 12:43:40 +03:00
|
|
|
#ifdef NDEBUG
|
2025-12-09 17:20:27 +03:00
|
|
|
}
|
|
|
|
|
catch(const std::exception& e) {
|
|
|
|
|
Logger::get().log("INFO", "[NetServer::update] Exception thrown while parsing and sending messages for a client: " + std::string(e.what()));
|
|
|
|
|
client->setToDisconnect = true;
|
|
|
|
|
disconnectCallback(client);
|
|
|
|
|
}
|
|
|
|
|
catch(...) {
|
|
|
|
|
Logger::get().log("INFO", "[NetServer::update] Unknown exception thrown while parsing and sending messages for a client.");
|
|
|
|
|
client->setToDisconnect = true;
|
|
|
|
|
disconnectCallback(client);
|
|
|
|
|
}
|
2026-01-05 12:43:40 +03:00
|
|
|
#endif
|
2025-07-10 15:52:01 +03:00
|
|
|
}
|
2025-07-06 20:43:53 +03:00
|
|
|
}
|
2025-07-10 15:52:01 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
std::erase_if(clients, [&](auto& client) {
|
|
|
|
|
return !client || client->setToDisconnect.load();
|
2025-07-06 20:43:53 +03:00
|
|
|
});
|
|
|
|
|
|
|
|
|
|
std::scoped_lock clientAddLock(clientsToAddMutex);
|
|
|
|
|
clients.insert(clients.begin(), clientsToAdd.begin(), clientsToAdd.end());
|
|
|
|
|
clientsToAdd.clear();
|
|
|
|
|
}
|
|
|
|
|
|
2025-10-10 10:34:12 +03:00
|
|
|
bool NetServer::is_disconnected() const {
|
2025-07-06 20:43:53 +03:00
|
|
|
return isDisconnected;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void NetServer::client_connected(std::shared_ptr<rtc::PeerConnection> connection, const std::string& clientLocalID) {
|
|
|
|
|
if(isDisconnected)
|
|
|
|
|
return;
|
|
|
|
|
auto newClient = std::make_shared<ClientData>();
|
|
|
|
|
newClient->lastMessageTime = std::chrono::steady_clock::now();
|
|
|
|
|
|
|
|
|
|
{
|
|
|
|
|
std::scoped_lock clientAddLock(clientsToAddMutex);
|
|
|
|
|
clientsToAdd.emplace_back(newClient);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Logger::get().log("INFO", "Client connected and creating channels for: " + clientLocalID);
|
|
|
|
|
newClient->channels.emplace(RELIABLE_COMMAND_CHANNEL, connection->createDataChannel(clientLocalID + RELIABLE_COMMAND_CHANNEL));
|
|
|
|
|
newClient->channels.emplace(RESOURCE_COMMAND_CHANNEL, connection->createDataChannel(clientLocalID + RESOURCE_COMMAND_CHANNEL));
|
|
|
|
|
rtc::DataChannelInit unreliableCommandInit;
|
|
|
|
|
unreliableCommandInit.reliability.maxPacketLifeTime = std::chrono::milliseconds(2000);
|
|
|
|
|
unreliableCommandInit.reliability.unordered = true;
|
|
|
|
|
newClient->channels.emplace(UNRELIABLE_COMMAND_CHANNEL, connection->createDataChannel(clientLocalID + UNRELIABLE_COMMAND_CHANNEL, unreliableCommandInit));
|
|
|
|
|
|
|
|
|
|
newClient->channels[RELIABLE_COMMAND_CHANNEL]->onMessage([weakClient = make_weak_ptr(newClient)](rtc::message_variant data) {
|
|
|
|
|
std::shared_ptr<ClientData> clientLock = weakClient.lock();
|
|
|
|
|
if(clientLock) {
|
|
|
|
|
std::scoped_lock recvMessLock(clientLock->receivedMessagesMutex);
|
|
|
|
|
clientLock->receivedMessages.emplace(RELIABLE_COMMAND_CHANNEL, std::get<rtc::binary>(data));
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
// If any of the local channels are closed, we can assume the client has disconnected
|
|
|
|
|
newClient->channels[RELIABLE_COMMAND_CHANNEL]->onClosed([weakClient = make_weak_ptr(newClient)]() {
|
|
|
|
|
std::shared_ptr<ClientData> clientLock = weakClient.lock();
|
|
|
|
|
if(clientLock)
|
|
|
|
|
clientLock->setToDisconnect = true;
|
|
|
|
|
});
|
|
|
|
|
newClient->channels[UNRELIABLE_COMMAND_CHANNEL]->onMessage([weakClient = make_weak_ptr(newClient)](rtc::message_variant data) {
|
|
|
|
|
std::shared_ptr<ClientData> clientLock = weakClient.lock();
|
|
|
|
|
if(clientLock) {
|
|
|
|
|
std::scoped_lock recvMessLock(clientLock->receivedMessagesMutex);
|
|
|
|
|
clientLock->receivedMessages.emplace(UNRELIABLE_COMMAND_CHANNEL, std::get<rtc::binary>(data));
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
newClient->channels[RESOURCE_COMMAND_CHANNEL]->onMessage([weakClient = make_weak_ptr(newClient)](rtc::message_variant data) {
|
|
|
|
|
std::shared_ptr<ClientData> clientLock = weakClient.lock();
|
|
|
|
|
if(clientLock) {
|
|
|
|
|
std::scoped_lock recvMessLock(clientLock->receivedMessagesMutex);
|
|
|
|
|
clientLock->receivedMessages.emplace(RESOURCE_COMMAND_CHANNEL, std::get<rtc::binary>(data));
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
2025-12-06 14:16:50 +03:00
|
|
|
void NetServer::send_string_stream_to_client(const std::shared_ptr<ClientData>& client, const std::string& channel, const std::shared_ptr<std::stringstream>& ss) {
|
|
|
|
|
if(!client)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
auto& messageQueue = client->messageQueues[channel];
|
|
|
|
|
if(channel == UNRELIABLE_COMMAND_CHANNEL) {
|
|
|
|
|
if(ss->view().length() <= NetLibrary::MAX_UNRELIABLE_MESSAGE_SIZE) // Drop unreliable messages that are too big
|
|
|
|
|
messageQueue.emplace(NetLibrary::calc_order_for_queued_message(channel, client->nextMessageOrderToSend), ss);
|
|
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
std::vector<std::shared_ptr<std::stringstream>> fragmentedMessage = fragment_message(ss->view(), NetLibrary::FRAGMENT_MESSAGE_STRIDE);
|
|
|
|
|
if(fragmentedMessage.empty())
|
|
|
|
|
messageQueue.emplace(NetLibrary::calc_order_for_queued_message(channel, client->nextMessageOrderToSend), ss);
|
|
|
|
|
else {
|
|
|
|
|
for(auto& ss2 : fragmentedMessage)
|
|
|
|
|
messageQueue.emplace(NetLibrary::calc_order_for_queued_message(channel, client->nextMessageOrderToSend), ss2);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void NetServer::send_string_stream_to_all_clients(const std::string& channel, const std::shared_ptr<std::stringstream>& ss) {
|
|
|
|
|
send_string_stream_to_client_if([&](const std::shared_ptr<ClientData>& c) {
|
|
|
|
|
return true;
|
|
|
|
|
}, channel, ss);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void NetServer::send_string_stream_to_all_clients_except(const std::shared_ptr<ClientData>& client, const std::string& channel, const std::shared_ptr<std::stringstream>& ss) {
|
|
|
|
|
send_string_stream_to_client_if([&](const std::shared_ptr<ClientData>& c) {
|
|
|
|
|
return c != client;
|
|
|
|
|
}, channel, ss);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void NetServer::send_string_stream_to_client_if(std::function<bool(const std::shared_ptr<ClientData>&)> clientChecker, const std::string& channel, const std::shared_ptr<std::stringstream>& ss) {
|
|
|
|
|
if(channel == UNRELIABLE_COMMAND_CHANNEL) {
|
|
|
|
|
if(ss->view().length() <= NetLibrary::MAX_UNRELIABLE_MESSAGE_SIZE) { // Drop unreliable messages that are too big
|
|
|
|
|
for(auto& client : clients) {
|
|
|
|
|
if(client && clientChecker(client)) {
|
|
|
|
|
auto& messageQueue = client->messageQueues[channel];
|
|
|
|
|
messageQueue.emplace(NetLibrary::calc_order_for_queued_message(channel, client->nextMessageOrderToSend), ss);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
std::vector<std::shared_ptr<std::stringstream>> fragmentedMessage = fragment_message(ss->view(), NetLibrary::FRAGMENT_MESSAGE_STRIDE);
|
|
|
|
|
|
|
|
|
|
for(auto& client : clients) {
|
|
|
|
|
if(client && clientChecker(client)) {
|
|
|
|
|
auto& messageQueue = client->messageQueues[channel];
|
|
|
|
|
if(fragmentedMessage.empty())
|
|
|
|
|
messageQueue.emplace(NetLibrary::calc_order_for_queued_message(channel, client->nextMessageOrderToSend), ss);
|
|
|
|
|
else {
|
|
|
|
|
for(auto& ss2 : fragmentedMessage)
|
|
|
|
|
messageQueue.emplace(NetLibrary::calc_order_for_queued_message(channel, client->nextMessageOrderToSend), ss2);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-07-06 20:43:53 +03:00
|
|
|
void NetServer::ClientData::send_queued_messages(NetServer& server) {
|
|
|
|
|
for(auto& [channelName, messageQueue] : messageQueues) {
|
2025-10-02 19:10:58 +03:00
|
|
|
bool addMessageOrder = NetLibrary::is_ordered_channel(channelName);
|
2025-07-06 20:43:53 +03:00
|
|
|
if(this == server.directConnectClientData.get()) {
|
|
|
|
|
NetClient* c = server.directConnectClient;
|
|
|
|
|
std::scoped_lock recvQueueLock(c->receiveQueue->mut);
|
|
|
|
|
while(!messageQueue.empty()) {
|
|
|
|
|
std::string toInsert;
|
|
|
|
|
if(addMessageOrder)
|
2025-10-02 19:10:58 +03:00
|
|
|
toInsert = NetLibrary::attach_message_order(messageQueue.front().order, messageQueue.front().ss->str());
|
2025-07-06 20:43:53 +03:00
|
|
|
else
|
2025-10-02 19:10:58 +03:00
|
|
|
toInsert = messageQueue.front().ss->str();
|
2025-07-06 20:43:53 +03:00
|
|
|
rtc::binary b((std::byte*)toInsert.c_str(), (std::byte*)(toInsert.c_str() + toInsert.length()));
|
|
|
|
|
c->receiveQueue->messages.emplace(channelName, b);
|
|
|
|
|
messageQueue.pop();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
auto& channel = channels[channelName];
|
|
|
|
|
if(channel) {
|
|
|
|
|
if(channel->isOpen()) {
|
|
|
|
|
while(!messageQueue.empty()) {
|
|
|
|
|
#ifdef __EMSCRIPTEN__
|
|
|
|
|
if(channel->bufferedAmount() >= NetLibrary::MAX_BUFFERED_DATA_PER_CHANNEL)
|
|
|
|
|
break;
|
|
|
|
|
#endif
|
|
|
|
|
if(addMessageOrder)
|
2025-10-02 19:10:58 +03:00
|
|
|
send_str_as_bytes(channel, NetLibrary::attach_message_order(messageQueue.front().order, messageQueue.front().ss->str()));
|
2025-07-06 20:43:53 +03:00
|
|
|
else
|
2025-10-02 19:10:58 +03:00
|
|
|
send_str_as_bytes(channel, messageQueue.front().ss->str());
|
2025-07-06 20:43:53 +03:00
|
|
|
messageQueue.pop();
|
|
|
|
|
}
|
|
|
|
|
#ifdef __EMSCRIPTEN__
|
|
|
|
|
if(channel->bufferedAmount() >= NetLibrary::MAX_BUFFERED_DATA_PER_CHANNEL && channelName == UNRELIABLE_COMMAND_CHANNEL)
|
2025-10-02 19:10:58 +03:00
|
|
|
messageQueue = std::queue<OutgoingMessage>();
|
2025-07-06 20:43:53 +03:00
|
|
|
#endif
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void NetServer::ClientData::send_str_as_bytes(std::shared_ptr<rtc::DataChannel> channel, const std::string& str) {
|
|
|
|
|
channel->send((const std::byte*)str.c_str(), str.size());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void NetServer::ClientData::parse_received_messages(NetServer& server) {
|
|
|
|
|
std::scoped_lock recvMessLock(receivedMessagesMutex);
|
|
|
|
|
while(!receivedMessages.empty()) {
|
|
|
|
|
auto& receivedMessage = receivedMessages.front();
|
|
|
|
|
lastMessageTime = std::chrono::steady_clock::now();
|
|
|
|
|
|
|
|
|
|
size_t messageOrderDisplacement = 0;
|
2025-10-02 19:10:58 +03:00
|
|
|
if(NetLibrary::is_ordered_channel(receivedMessage.channel)) {
|
2025-07-06 20:43:53 +03:00
|
|
|
MessageOrder messageOrder = NetLibrary::get_message_order(receivedMessage.data);
|
|
|
|
|
if(nextMessageOrderToExpect > messageOrder && receivedMessage.channel == UNRELIABLE_COMMAND_CHANNEL) {
|
|
|
|
|
receivedMessages.pop();
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
nextMessageOrderToExpect = std::max(messageOrder + 1, nextMessageOrderToExpect);
|
|
|
|
|
messageOrderDisplacement = sizeof(MessageOrder);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ByteMemStream strm((char*)(receivedMessage.data.data() + messageOrderDisplacement), receivedMessage.data.size() - messageOrderDisplacement);
|
|
|
|
|
cereal::PortableBinaryInputArchive inArchive(strm);
|
|
|
|
|
|
|
|
|
|
auto& spfm = pfm[receivedMessage.channel];
|
|
|
|
|
|
|
|
|
|
MessageCommandType commandID;
|
|
|
|
|
inArchive(commandID);
|
|
|
|
|
|
|
|
|
|
if(commandID == 0) {
|
2025-07-28 17:07:39 +03:00
|
|
|
decode_fragmented_message(inArchive, spfm, NetLibrary::FRAGMENT_MESSAGE_STRIDE, [&](cereal::PortableBinaryInputArchive& completeArchive) {
|
2025-07-06 20:43:53 +03:00
|
|
|
MessageCommandType completeCommandID;
|
|
|
|
|
completeArchive(completeCommandID);
|
2025-07-10 15:52:01 +03:00
|
|
|
server.recvCallbacks[completeCommandID](shared_from_this(), completeArchive);
|
2025-07-28 17:07:39 +03:00
|
|
|
});
|
2025-07-06 20:43:53 +03:00
|
|
|
}
|
|
|
|
|
else
|
2025-07-10 15:52:01 +03:00
|
|
|
server.recvCallbacks[commandID](shared_from_this(), inArchive);
|
2025-07-06 20:43:53 +03:00
|
|
|
|
|
|
|
|
receivedMessages.pop();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-10-10 10:34:12 +03:00
|
|
|
NetLibrary::DownloadProgress NetServer::ClientData::get_progress_into_fragmented_message(const std::string& channel) const {
|
|
|
|
|
auto it = pfm.find(channel);
|
|
|
|
|
if(it == pfm.end())
|
|
|
|
|
return {0, 0};
|
|
|
|
|
auto& m = it->second;
|
|
|
|
|
if(m.partialFragmentMessage.empty())
|
|
|
|
|
return {0, 0};
|
|
|
|
|
return {m.partialFragmentMessageLoc, m.partialFragmentMessage.size()};
|
|
|
|
|
}
|
|
|
|
|
|
2025-07-06 20:43:53 +03:00
|
|
|
void NetServer::add_recv_callback(MessageCommandType commandID, const NetServerRecvCallback& callback) {
|
|
|
|
|
recvCallbacks[commandID] = callback;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void NetServer::add_disconnect_callback(const NetServerDisconnectCallback& callback) {
|
|
|
|
|
disconnectCallback = callback;
|
|
|
|
|
}
|
|
|
|
|
|
2025-12-16 12:02:08 +03:00
|
|
|
const std::vector<std::shared_ptr<NetServer::ClientData>>& NetServer::get_client_list() const {
|
2025-10-10 10:34:12 +03:00
|
|
|
return clients;
|
|
|
|
|
}
|
|
|
|
|
|
2025-07-06 20:43:53 +03:00
|
|
|
NetServer::~NetServer() {
|
2025-12-09 13:00:38 +03:00
|
|
|
if(directConnectClient) {
|
2025-07-06 20:43:53 +03:00
|
|
|
directConnectClient->isDisconnected = true;
|
2025-12-09 13:00:38 +03:00
|
|
|
directConnectClient->directConnectServer = nullptr;
|
|
|
|
|
}
|
2025-07-06 20:43:53 +03:00
|
|
|
}
|