scar-chat7/client/connection/client_connection.cpp

305 lines
11 KiB
C++
Raw Normal View History

2025-12-07 12:00:44 -07:00
#include "client_connection.h"
#include <QTimer>
#include <thread>
#include <iostream>
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<boost::asio::io_context>();
ssl_context_ = std::make_unique<boost::asio::ssl::context>(
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<boost::asio::executor_work_guard<boost::asio::io_context::executor_type>>(
io_context_->get_executor()
);
socket_ = std::make_unique<boost::asio::ssl::stream<boost::asio::ip::tcp::socket>>(
*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<std::vector<uint8_t>>(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<uint8_t> 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> message) {
std::cout << "Client received message type: " << static_cast<int>(message->type()) << std::endl;
switch (message->type()) {
case MessageType::LOGIN_RESPONSE: {
auto* response = dynamic_cast<LoginResponse*>(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<TextMessage*>(message.get());
emit messageReceived(
QString::fromStdString(text_msg->sender()),
QString::fromStdString(text_msg->content())
);
break;
}
case MessageType::SCREEN_SHARE_START: {
auto* start_msg = dynamic_cast<ScreenShareStart*>(message.get());
std::cout << "ScreenShareStart received - Width: " << start_msg->width()
<< " Height: " << start_msg->height() << std::endl;
if (!screen_share_decoder_) {
screen_share_decoder_ = std::make_unique<VideoDecoder>();
if (!screen_share_decoder_->initialize()) {
std::cerr << "Failed to initialize video decoder" << std::endl;
screen_share_decoder_.reset();
}
}
break;
}
case MessageType::SCREEN_SHARE_DATA: {
auto* data_msg = dynamic_cast<ScreenShareData*>(message.get());
if (screen_share_decoder_) {
int width = 0, height = 0;
std::cout << "About to call decode..." << std::endl;
std::vector<uint8_t> rgbData = screen_share_decoder_->decode(data_msg->frameData(), width, height);
std::cout << "Decode returned, rgbData.size()=" << rgbData.size() << std::endl;
if (!rgbData.empty()) {
std::cout << "About to emit signal..." << std::endl;
emit screenShareFrameReceived(rgbData, width, height);
std::cout << "Signal emitted successfully" << std::endl;
}
} else {
std::cerr << "Received screen share data but decoder not initialized" << std::endl;
}
break;
}
case MessageType::SCREEN_SHARE_STOP: {
std::cout << "ScreenShareStop received" << std::endl;
if (screen_share_decoder_) {
screen_share_decoder_->cleanup();
screen_share_decoder_.reset();
}
break;
}
2025-12-07 12:00:44 -07:00
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<uint8_t>& 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) {
2025-12-07 12:00:44 -07:00
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