305 lines
11 KiB
C++
305 lines
11 KiB
C++
#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;
|
|
}
|
|
|
|
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) {
|
|
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
|