#include "client_connection.h" #include #include #include namespace scar { ClientConnection::ClientConnection(QObject* parent) : QObject(parent), connected_(false), should_reconnect_(false), reconnect_attempts_(0), backoff_delay_(std::chrono::seconds(INITIAL_BACKOFF_SECONDS)) { io_context_ = std::make_unique(); ssl_context_ = std::make_unique( boost::asio::ssl::context::tlsv12_client ); ssl_context_->set_verify_mode(boost::asio::ssl::verify_none); // TODO: Proper cert verification } ClientConnection::~ClientConnection() { disconnect(); } void ClientConnection::connectToServer(const std::string& host, uint16_t port, const std::string& username, const std::string& password) { last_host_ = host; last_port_ = port; last_username_ = username; last_password_ = password; should_reconnect_ = true; reconnect_attempts_ = 0; doConnect(host, port); // Run io_context in separate thread std::thread([this]() { runIoContext(); }).detach(); } void ClientConnection::disconnect() { should_reconnect_ = false; connected_ = false; if (socket_) { boost::system::error_code ec; socket_->lowest_layer().close(ec); } if (io_context_) { io_context_->stop(); } emit disconnected(); } void ClientConnection::doConnect(const std::string& host, uint16_t port) { io_context_->restart(); work_guard_ = std::make_unique>( io_context_->get_executor() ); socket_ = std::make_unique>( *io_context_, *ssl_context_ ); boost::asio::ip::tcp::resolver resolver(*io_context_); auto endpoints = resolver.resolve(host, std::to_string(port)); boost::asio::async_connect(socket_->lowest_layer(), endpoints, [this](const boost::system::error_code& error, const boost::asio::ip::tcp::endpoint&) { if (!error) { std::cout << "Connected to server" << std::endl; doHandshake(); } else { std::cerr << "Connection error: " << error.message() << std::endl; emit connectionError(QString::fromStdString(error.message())); scheduleReconnect(); } }); } void ClientConnection::doHandshake() { socket_->async_handshake(boost::asio::ssl::stream_base::client, [this](const boost::system::error_code& error) { if (!error) { std::cout << "SSL handshake completed" << std::endl; connected_ = true; reconnect_attempts_ = 0; backoff_delay_ = std::chrono::seconds(INITIAL_BACKOFF_SECONDS); emit connected(); doLogin(last_username_, last_password_); doReadHeader(); } else { std::cerr << "SSL handshake error: " << error.message() << std::endl; emit connectionError(QString::fromStdString(error.message())); scheduleReconnect(); } }); } void ClientConnection::doLogin(const std::string& username, const std::string& password) { std::cout << "doLogin called - Username: '" << username << "', Password length: " << password.length() << std::endl; LoginRequest request(username, password); auto data = request.serialize(); std::cout << "Sending LoginRequest, data size: " << data.size() << std::endl; boost::asio::async_write(*socket_, boost::asio::buffer(data), [this](const boost::system::error_code& error, std::size_t) { if (error) { std::cerr << "Login send error: " << error.message() << std::endl; emit connectionError(QString::fromStdString(error.message())); } else { std::cout << "LoginRequest sent successfully" << std::endl; } }); } void ClientConnection::doReadHeader() { read_buffer_.resize(sizeof(MessageHeader)); boost::asio::async_read(*socket_, boost::asio::buffer(read_buffer_), [this](const boost::system::error_code& error, std::size_t) { if (!error) { MessageHeader header; std::memcpy(&header, read_buffer_.data(), sizeof(MessageHeader)); if (header.length > sizeof(MessageHeader)) { doReadBody(header.length - sizeof(MessageHeader)); } else { doReadHeader(); } } else { std::cerr << "Read error: " << error.message() << std::endl; connected_ = false; emit disconnected(); scheduleReconnect(); } }); } void ClientConnection::doReadBody(uint32_t length) { auto body_buffer = std::make_shared>(length); boost::asio::async_read(*socket_, boost::asio::buffer(*body_buffer), [this, body_buffer](const boost::system::error_code& error, std::size_t) { if (!error) { std::vector full_message; full_message.insert(full_message.end(), read_buffer_.begin(), read_buffer_.end()); full_message.insert(full_message.end(), body_buffer->begin(), body_buffer->end()); try { auto message = Message::deserialize(full_message); handleMessage(std::move(message)); } catch (const std::exception& e) { std::cerr << "Message error: " << e.what() << std::endl; } doReadHeader(); } else { std::cerr << "Read body error: " << error.message() << std::endl; connected_ = false; emit disconnected(); scheduleReconnect(); } }); } void ClientConnection::handleMessage(std::unique_ptr message) { std::cout << "Client received message type: " << static_cast(message->type()) << std::endl; switch (message->type()) { case MessageType::LOGIN_RESPONSE: { auto* response = dynamic_cast(message.get()); std::cout << "LoginResponse - Success: " << response->success() << std::endl; if (response->success()) { std::cout << "Login successful, token received" << std::endl; emit loginSuccess(QString::fromStdString(response->token())); } else { std::cout << "Login failed" << std::endl; emit loginFailed("Authentication failed"); } break; } case MessageType::TEXT_MESSAGE: { auto* text_msg = dynamic_cast(message.get()); emit messageReceived( QString::fromStdString(text_msg->sender()), QString::fromStdString(text_msg->content()) ); break; } default: break; } } void ClientConnection::sendTextMessage(const std::string& content) { if (!connected_) return; TextMessage message(last_username_, content); auto data = message.serialize(); boost::asio::async_write(*socket_, boost::asio::buffer(data), [](const boost::system::error_code& error, std::size_t) { if (error) { std::cerr << "Send error: " << error.message() << std::endl; } }); } void ClientConnection::sendData(const std::vector& data) { if (!connected_) return; boost::asio::async_write(*socket_, boost::asio::buffer(data), [](const boost::system::error_code& error, std::size_t bytes) { if (error) { std::cerr << "Send error: " << error.message() << std::endl; } }); } void ClientConnection::scheduleReconnect() { if (!should_reconnect_ || reconnect_attempts_ >= MAX_RECONNECT_ATTEMPTS) { emit connectionError("Max reconnection attempts reached"); return; } reconnect_attempts_++; std::cout << "Reconnecting in " << backoff_delay_.count() << " seconds (attempt " << reconnect_attempts_ << "/" << MAX_RECONNECT_ATTEMPTS << ")" << std::endl; QTimer::singleShot(backoff_delay_.count() * 1000, [this]() { doConnect(last_host_, last_port_); }); // Exponential backoff backoff_delay_ = std::min( backoff_delay_ * 2, std::chrono::seconds(MAX_BACKOFF_SECONDS) ); } void ClientConnection::runIoContext() { try { io_context_->run(); } catch (const std::exception& e) { std::cerr << "IO context error: " << e.what() << std::endl; } } } // namespace scar