// tcp_message_handler.cpp - TCP Message Handler for ColumnLynx // Copyright (C) 2026 DcruBro // Distributed under the terms of the GNU General Public License, either version 2 only or version 3. See LICENSES/ for details. #include #include #include #include namespace ColumnLynx::Net::TCP { void MessageHandler::start() { mReadHeader(); } void MessageHandler::sendMessage(AnyMessageType type, const std::string &payload) { // Type is a variant between ServerMessageType and ClientMessageType // Convert to uint8_t dynamically uint8_t typeByte = std::visit([](auto type) -> uint8_t { return static_cast(type); }, type); auto data = std::make_shared>(); data->push_back(typeByte); // Ensure payload fits into protocol's 16-bit length field if (payload.size() > static_cast(std::numeric_limits::max())) { Utils::error("sendMessage(): payload too large (>65535 bytes)"); return; } uint16_t length = static_cast(payload.size()); data->push_back(static_cast(length >> 8)); data->push_back(static_cast(length & 0xFF)); data->insert(data->end(), payload.begin(), payload.end()); auto self = shared_from_this(); asio::async_write(mSocket, asio::buffer(*data), [self, data](asio::error_code ec, std::size_t) { if (ec) { Utils::error("Send failed: " + ec.message()); } } ); } void MessageHandler::onMessage(std::function callback) { mOnMessage = std::move(callback); } void MessageHandler::mReadHeader() { auto self = shared_from_this(); asio::async_read(mSocket, asio::buffer(mHeader), [this, self](asio::error_code ec, std::size_t) { if (!ec) { mCurrentType = decodeMessageType(mHeader[0]); uint16_t len = (mHeader[1] << 8) | mHeader[2]; mReadBody(len); } else { if (!NetHelper::isExpectedDisconnect(ec)) { Utils::error("Header read failed: " + ec.message()); } // Connection closed, trigger disconnect handler if (mOnDisconnect) { mOnDisconnect(ec); } } } ); } void MessageHandler::mReadBody(uint16_t length) { auto self = shared_from_this(); mBody.resize(length); asio::async_read(mSocket, asio::buffer(mBody), [this, self](asio::error_code ec, std::size_t) { if (!ec) { std::string payload(mBody.begin(), mBody.end()); // Dispatch based on message type if (mOnMessage) { mOnMessage(mCurrentType, payload); } mReadHeader(); // Keep listening } else { if (!NetHelper::isExpectedDisconnect(ec)) { Utils::error("Body read failed: " + ec.message()); } // Connection closed, trigger disconnect handler if (mOnDisconnect) { mOnDisconnect(ec); } } } ); } AnyMessageType MessageHandler::decodeMessageType(uint8_t code) { switch (code) { case 0xFE: return ServerMessageType::GRACEFUL_DISCONNECT; case 0xFF: return ServerMessageType::KILL_CONNECTION; default: break; } if (code >= 0xA0) { return static_cast(code); } else { return static_cast(code); } } uint8_t MessageHandler::toUint8(const AnyMessageType& type) { return std::visit([](auto t) -> uint8_t { return static_cast(t); }, type); } }