Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
130 commits
Select commit Hold shift + click to select a range
2cb8c73
make `src/CMakeLists.txt` aware of unit tests
ProfessorTom Mar 2, 2026
c44fb61
add first unit test to project
ProfessorTom Mar 2, 2026
9695db2
rename unt test CMake test target
ProfessorTom Mar 2, 2026
e0c119a
add `Connection` class to project
ProfessorTom Mar 5, 2026
f88ac13
add `test_connection.cpp` to project
ProfessorTom Mar 5, 2026
a513dcd
add `test_runner.cpp` to project
ProfessorTom Mar 5, 2026
0ea5ffb
add `Connection` class with one implemented method (`id`) so we can s…
ProfessorTom Mar 5, 2026
5a30ea4
add `connection.cpp` to `src/CMakeLists.txt`
ProfessorTom Mar 5, 2026
ba101c5
add a test runner to support multiple test files in one test executable
ProfessorTom Mar 5, 2026
27b1118
add `TranslationTest` test code (header and implementation file)
ProfessorTom Mar 5, 2026
0dc1c6f
refactor translation tests to work with test runner
ProfessorTom Mar 5, 2026
8cf0036
get unit tests building correctly
ProfessorTom Mar 5, 2026
bad7da8
rename test class so the name makes more sense
ProfessorTom Mar 5, 2026
31795b2
rename test class so the name makes more sense
ProfessorTom Mar 5, 2026
69bc4b1
normalize unit test file names part 1
ProfessorTom Mar 5, 2026
3edad38
normalize unit test file names part 2
ProfessorTom Mar 5, 2026
fbe5a29
Add managed-client constructor to TCPThread for Connection use
ProfessorTom Mar 6, 2026
f9847d7
Skip deleteLater() in TCPThread::run() for Connection-managed mode
ProfessorTom Mar 6, 2026
a0a85df
Add isValid() method to TCPThread for safe state checking
ProfessorTom Mar 6, 2026
aff97ea
Make TCPThread::closeConnection() safe with null check and wait
ProfessorTom Mar 6, 2026
3f672ae
Make TCPThread::closeConnection() safe with null check and state guard
ProfessorTom Mar 6, 2026
c0c96f1
Refactor Connection to use managed TCPThread ctor + explicit start + …
ProfessorTom Mar 6, 2026
5b72452
Add basic thread start/stop test for Connection
ProfessorTom Mar 6, 2026
57a4a22
Update unit test CMake + runner: link production files + per-test QAp…
ProfessorTom Mar 6, 2026
f05233d
refactor `Connection` dstor to call `close()` to simply logic
ProfessorTom Mar 6, 2026
cc2d404
add `ConnectionManager` skeleton with unit tets
ProfessorTom Mar 6, 2026
d115201
add `TcpThread_tests` class files to project
ProfessorTom Mar 6, 2026
8765802
add helper method `wireupSocketSignals()` that DRYs out new construct…
ProfessorTom Mar 6, 2026
9ee1bc7
use `wireupSocketSignals()` in existing constructor
ProfessorTom Mar 6, 2026
1adae72
rearrange initialization to match order in header
ProfessorTom Mar 6, 2026
9595949
add comment to help identify what constructor is doing
ProfessorTom Mar 6, 2026
a7fbfaf
add new constructor to `TcpThread`
ProfessorTom Mar 6, 2026
aeafb38
make accessors available for unit testing
ProfessorTom Mar 6, 2026
d46ed4b
add test double `TestTcpThreadClass` to project
ProfessorTom Mar 6, 2026
0dd1fd1
add unit tests for new TcpThread constructor
ProfessorTom Mar 6, 2026
5dd482f
add `TcpThread` unit tests to test executable
ProfessorTom Mar 6, 2026
c45f3e8
add `TcpThreadTests` class to list of tests to be executed by the tes…
ProfessorTom Mar 6, 2026
e2163b8
add `setupThreadConnections()` to DRY out code
ProfessorTom Mar 6, 2026
78cada9
use `setupThreadConnections()`
ProfessorTom Mar 6, 2026
8666182
add new members with default values
ProfessorTom Mar 6, 2026
0b6529f
add new constructor to handle server/incoming connections
ProfessorTom Mar 6, 2026
b4983e0
TCPThread: default-initialize closeRequest = false in class definition
ProfessorTom Mar 6, 2026
4500094
update comment
ProfessorTom Mar 6, 2026
c4d8576
make assignment of unique ids canonical
ProfessorTom Mar 7, 2026
745a2c4
rename variable `status` to `failures`
ProfessorTom Mar 7, 2026
29d9482
print a message after tests have run
ProfessorTom Mar 7, 2026
75a9bec
move `TestTcpThreadClass()` into its own file for reuse in other unit…
ProfessorTom Mar 7, 2026
ea6ed1f
print timings per `ConnetionTests` tests
ProfessorTom Mar 7, 2026
2f0185a
add virtual method as a step in making unit tests faster
ProfessorTom Mar 7, 2026
e8b9184
add `interruptibleWaitForReadyRead()` to help us get out of thread lo…
ProfessorTom Mar 7, 2026
c26e030
replace `clientConnection->waitForReadyRead()` calls with `interrupti…
ProfessorTom Mar 7, 2026
d528376
update comment to more accurately reflect what the method does
ProfessorTom Mar 7, 2026
6e51308
more get out of loop sooner code
ProfessorTom Mar 7, 2026
6532715
add destructor to TCPThread
ProfessorTom Mar 7, 2026
2db28d4
edge case that needs m_threadStarted set to true
ProfessorTom Mar 7, 2026
5324aef
include `tcpthread.h` and `packet.h` before `connection.h`
ProfessorTom Mar 7, 2026
a73f2fa
use full declaration of `TcpThread` not just a forward declaration
ProfessorTom Mar 7, 2026
27d4ace
remove unused includes
ProfessorTom Mar 7, 2026
4da02c3
replace virtual method that doesn't get overridden for an ivar that c…
ProfessorTom Mar 7, 2026
0b93a29
use delegate and target constructors
ProfessorTom Mar 7, 2026
c108f5d
add methods to make fields visible in unit tests
ProfessorTom Mar 7, 2026
49172e8
expose public getters
ProfessorTom Mar 7, 2026
30f8732
update `TestConnection` to match changes in `Connection`
ProfessorTom Mar 7, 2026
1410f1c
add more `Connection` unit tests
ProfessorTom Mar 7, 2026
85a518a
TCPThread: propagate constructor host/port to sendPacket.toIP/port
ProfessorTom Mar 16, 2026
a88ccf4
TCPThread: add early exit checks and better logging in persistentConn…
ProfessorTom Mar 16, 2026
ce818d3
TCPThread: move socket cleanup to persistentConnectionLoop exit
ProfessorTom Mar 16, 2026
9acb68c
TCPThread: fix incoming persistent mode to use heap-allocated socket
ProfessorTom Mar 16, 2026
bc55493
TCPThread: make closeConnection flag-based only (no direct socket ops)
ProfessorTom Mar 16, 2026
745534d
make isManagedByConnection protected so we can modify it in unit test…
ProfessorTom Mar 16, 2026
9667f83
add setter method for m_managedByConnection to `TestTcpThread` test d…
ProfessorTom Mar 16, 2026
4705c91
Connection: enhance close() with better logging and short wait
ProfessorTom Mar 16, 2026
b11299a
Tests: add tcpthreadqapplicationneededtests to build and runner
ProfessorTom Mar 16, 2026
5fb8301
Connection: add m_isClosing guard member
ProfessorTom Mar 16, 2026
72e1973
TCPThread: add forceShutdown() to unblock blocking socket waits
ProfessorTom Mar 16, 2026
6234786
Connection: extract thread shutdown into private shutdownThreadSafely()
ProfessorTom Mar 16, 2026
0c76c53
Connection: refactor close() to use shutdownThreadSafely() and add re…
ProfessorTom Mar 16, 2026
3841699
Connection: refactor destructor to use shutdownThreadSafely() defensi…
ProfessorTom Mar 16, 2026
46558bb
moar debug!
ProfessorTom Mar 16, 2026
f015fac
Add TcpThread.run() client-side characterization test
ProfessorTom Mar 16, 2026
5263bf9
make sendPacket protected in TcpThread for access in unit tests
ProfessorTom Mar 16, 2026
159be68
extract `getIPConnectionProtocol()` from `run()`
ProfessorTom Mar 16, 2026
9e360e8
add methods to test double
ProfessorTom Mar 16, 2026
a800430
add unit tests for `getIPConnectionProtocol()`
ProfessorTom Mar 16, 2026
95ecfa9
use `getIPConnectionProtocol()` in production
ProfessorTom Mar 16, 2026
8c94b1b
update comment to match method being tested
ProfessorTom Mar 17, 2026
6eb3883
make private members that we need access to in unit tests protected
ProfessorTom Mar 19, 2026
d6ab55d
separate out code that creates and wires up a new socket
ProfessorTom Mar 19, 2026
363a64a
dd `MosckSslSocket` o projext
ProfessorTom Mar 19, 2026
7b56d96
Replace most direct clientConnection accesses with clientSocket()
ProfessorTom Mar 19, 2026
3ae8333
feat(tcpthread): add constructor accepting pre-created QSslSocket
ProfessorTom Mar 19, 2026
1ed03a9
add extracted methods
ProfessorTom Mar 19, 2026
0e26122
use bindClientSocket()
ProfessorTom Mar 19, 2026
543d2ce
update debug statement
ProfessorTom Mar 19, 2026
73ef21a
add unit tests
ProfessorTom Mar 19, 2026
498e95b
add TestUtils class to project
ProfessorTom Apr 4, 2026
1847a3d
add helper method `debugSpy()` that prints out the content of a `QSig…
ProfessorTom Apr 4, 2026
3acc198
add `testtils.cpp` to unit test CMake list.
ProfessorTom Apr 4, 2026
3b1b76a
overload `operator<<` on Packet
ProfessorTom Apr 4, 2026
58a4e4c
add more `run()` characterization tests...
ProfessorTom Apr 4, 2026
a1ddcbe
rename `handleEncryptedConnectionOutcome()` to `handleOutgoingEncrypt…
ProfessorTom Apr 4, 2026
02cb9f6
rename `handleOutgoingEncryptedConnection()` to `handleOutgoingSSLHan…
ProfessorTom Apr 4, 2026
4137147
workaround for `isEncrypted()` not being a virtual method in `QSslSoc…
ProfessorTom Apr 4, 2026
c88e112
provide test double override for `isSocketEncrypted()`
ProfessorTom Apr 4, 2026
7cf21de
Add virtual getSslErrorList helpers to TCPThread for mockable SSL err…
ProfessorTom Apr 5, 2026
154445e
dd getSslErrors / getSslHandshakeErrors overrides in TestTcpThreadClass
ProfessorTom Apr 5, 2026
951112e
Extract incoming SSL handshake logic into handleIncomingSSLHandshake()
ProfessorTom Apr 5, 2026
239a7da
add separator comment
ProfessorTom Apr 5, 2026
59679a6
add wrappers to handle number of method calls on test double.
ProfessorTom Apr 5, 2026
a2040cd
Add unit tests for handleIncomingSSLHandshake and handleOutgoingSSLHa…
ProfessorTom Apr 5, 2026
f568df0
remove extraneous semicolon
ProfessorTom Apr 5, 2026
a1916a3
get rid of unnecessary braces and indention
ProfessorTom Apr 5, 2026
8404b2e
Extract outgoing client logic into runOutgoingClient() + add characte…
ProfessorTom Apr 5, 2026
af488ff
Refactor: Extract buildInitialReceivedPacket() from TCPThread::run()
ProfessorTom Apr 5, 2026
d4325fa
add `setSocketDescriptor()` to `TcpThread`
ProfessorTom Apr 11, 2026
51a7a93
Refactor: Extract runIncomingConnection() for incoming/server path
ProfessorTom Apr 11, 2026
110bfef
Refactor: Extract prepareForPersistentLoop() from incoming path
ProfessorTom Apr 11, 2026
b256e68
add `persistentLoopConnection.cpp` to project
ProfessorTom Apr 11, 2026
0094d1b
add persistentConnectionLoopTests class to projext
ProfessorTom Apr 11, 2026
ecf9d1e
Refactor: Move persistent loop code to its own file
ProfessorTom Apr 11, 2026
18d44fc
remove unnecessary comment
ProfessorTom Apr 11, 2026
6555fc9
Add comprehensive characterization tests for persistentConnectionLoop()
ProfessorTom Apr 12, 2026
bef86c5
Add first unit test for cleanupAfterPersistentConnectionLoop()
ProfessorTom Apr 12, 2026
b55ab44
DRY out debug code for printing out status spy
ProfessorTom Apr 12, 2026
93c039f
Extract cleanupAfterPersistentConnectionLoop() and add unit tests
ProfessorTom Apr 13, 2026
f21db83
extract and use handlePersistentIdleCase()
ProfessorTom Apr 18, 2026
4eb1fab
Extract getPeerAddressAsString() and remove duplicated IPv4/IPv6 logic
ProfessorTom Apr 18, 2026
b3fe2f7
clarify intent
ProfessorTom Apr 18, 2026
624789a
Extract sendCurrentPacket() and add unit tests
ProfessorTom Apr 18, 2026
81dccd1
Add unit test for sendCurrentPacket when no data is present
ProfessorTom Apr 19, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions src/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,8 @@ set(
PACKETSENDER_SRCS
about.cpp
brucethepoodle.cpp
connection.cpp
connectionmanager.cpp
irisandmarigold.cpp
cloudui.cpp
main.cpp
Expand All @@ -87,6 +89,7 @@ set(
association.cpp
dtlsserver.cpp
dtlsthread.cpp
persistentLoopConnection.cpp
)

set(
Expand Down Expand Up @@ -156,6 +159,17 @@ if(APPLE)
)
endif()

# ==================== UNIT TESTS (QtTest) ====================
option(PACKETSENDER_BUILD_TESTS "Build unit tests (QtTest)" OFF)

if(PACKETSENDER_BUILD_TESTS)
find_package(Qt6 REQUIRED COMPONENTS Test)

enable_testing()

add_subdirectory(tests/unit)
endif()

# -------------------
# Packaging
# -------------------
Expand Down
197 changes: 197 additions & 0 deletions src/connection.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,197 @@
//
// Created by Tomas Gallucci on 3/5/26.
//

#include "tcpthread.h"
#include "packet.h"
#include "connection.h"

void Connection::setupThreadConnections()
{
connect(m_thread.get(), &TCPThread::packetReceived, this, &Connection::onThreadPacketReceived);
connect(m_thread.get(), &TCPThread::connectStatus, this, &Connection::onThreadConnectStatus);
connect(m_thread.get(), &TCPThread::error, this, &Connection::onThreadError);
// Future-proof: if you later add more signals to TCPThread, add connects here
}

// Target constructor
Connection::Connection(std::unique_ptr<TCPThread> thread,
bool isIncoming,
bool isSecure,
bool isPersistent,
qintptr socketDescriptor,
QObject *parent)
: QObject(parent),
m_isIncoming(isIncoming),
m_isSecure(isSecure),
m_isPersistent(isPersistent),
m_socketDescriptor(socketDescriptor)
{
if (!thread) {
throw std::invalid_argument("Thread must be provided");
}

m_thread = std::move(thread);
m_thread->setParent(this);

assignUniqueId();
setupThreadConnections();
start();
}

/* Client/outgoing constructor (delegates) */
Connection::Connection(const QString &host,
quint16 port,
const Packet &initialPacket,
QObject *parent,
std::unique_ptr<TCPThread> thread)
: Connection(thread ? std::move(thread)
: std::make_unique<TCPThread>(host, port, initialPacket, nullptr),
false, false, true, -1, parent)
{
}

// Server/incoming constructor (delegates, preserves member assignments)
Connection::Connection(int socketDescriptor, bool isSecure, bool isPersistent, QObject *parent, std::unique_ptr<TCPThread> thread)
: Connection(thread ? std::move(thread)
: std::make_unique<TCPThread>(socketDescriptor, isSecure, isPersistent, nullptr),
true, isSecure, isPersistent, socketDescriptor, parent)
{
}

Connection::~Connection()
{
// NEW: RAII cleanup – close and wait for thread
if (m_thread && m_thread->isRunning()) {
qWarning() << "~Connection(): thread still running for" << m_id
<< "— forcing quick shutdown (user did not call close())";

shutdownThreadSafely(1000); // shorter timeout in destructor
}
// unique_ptr will delete it automatically here
}

void Connection::close()
{
if (!m_thread || !m_threadStarted) {
qDebug() << "close() called but thread not started or already closed";
return;
}

if (m_isClosing) {
qDebug() << "close() already in progress for" << m_id;
return;
}

m_isClosing = true;

qDebug() << "Connection::close() for" << m_id;

shutdownThreadSafely(2000); // normal graceful timeout


m_threadStarted = false;
m_isClosing = false;
emit disconnected();

qDebug() << "close() completed for" << m_id;
}

void Connection::shutdownThreadSafely(int timeoutMs)
{
if (!m_thread || !m_threadStarted) {
qDebug() << "shutdownThreadSafely: no active thread for" << m_id;
return;
}

qDebug() << "shutdownThreadSafely: requesting thread stop for" << m_id
<< "(timeout:" << timeoutMs << "ms)";

m_thread->closeConnection(); // sets closeRequest + interruption
m_thread->requestInterruption();

bool exitedCleanly = m_thread->wait(timeoutMs);

if (!exitedCleanly) {
qWarning() << "shutdownThreadSafely: thread did not exit within" << timeoutMs << "ms";
m_thread->forceShutdown(); // abort socket to unblock waits

// One last short chance
if (!m_thread->wait(1000)) {
qWarning() << "shutdownThreadSafely: force failed — terminating thread";
m_thread->terminate(); // absolute last resort
}
}

qDebug() << "shutdownThreadSafely: completed for" << m_id
<< "(exited cleanly:" << exitedCleanly << ")";
}

QString Connection::id() const
{
return m_id;
}

// public API
void Connection::send(const Packet &packet)
{
if (m_thread) {
m_thread->sendPersistant(packet);
}
}

void Connection::start()
{
if (!m_thread) {
qWarning() << "No thread to start";
return;
}

if (m_thread->isRunning()) {
qDebug() << "Thread already running for" << m_id;
qDebug() << "setting m_threadStarted to true inside if is running";
m_threadStarted = true;
return;
}

if (!m_thread->isValid()) {
qWarning() << "Cannot start - thread invalid for" << m_id;
// Optional: log why (you already have good logging in isValid())
emit errorOccurred("Cannot start connection - initialization failed");
return;
}

qDebug() << "Starting TCPThread for connection" << m_id;
m_thread->start();
m_threadStarted = true; // only set if start() was called successfully
}

// simple state queries
bool Connection::isConnected() const
{
// TODO: you may want to track state internally or ask thread
return m_thread && m_thread->isRunning();
}

bool Connection::isSecure() const
{
return m_thread ? m_thread->isSecure : false;
}

// NEW: internal forwarders
void Connection::onThreadPacketReceived(const Packet &p)
{
emit dataReceived(p);
}

void Connection::onThreadConnectStatus(const QString &msg)
{
emit stateChanged(msg);
}

void Connection::onThreadError(QSslSocket::SocketError error)
{
QString errStr = QString("Socket error: %1").arg(error);
emit errorOccurred(errStr);
emit disconnected();
}
105 changes: 105 additions & 0 deletions src/connection.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
//
// Created by Tomas Gallucci on 3/5/26.
//

#ifndef CONNECTION_H
#define CONNECTION_H


#pragma once

#include <QObject>
#include <QString>
#include <QUuid>
#include <QSslSocket>

#include <memory> // for std::unique_ptr

#include "packet.h"
#include "tcpthread.h"

// forward declarations
class Packet;

/**
* @brief RAII-style wrapper for a persistent connection.
* Initially minimal; will later own a TCPThread or similar.
*/
class Connection : public QObject
{
Q_OBJECT

public:
// target constructor
explicit Connection(std::unique_ptr<TCPThread> thread,
bool isIncoming = false,
bool isSecure = false,
bool isPersistent = true,
qintptr socketDescriptor = -1,
QObject *parent = nullptr);
explicit Connection(const QString &host,
quint16 port,
const Packet &initialPacket = Packet(),
QObject *parent = nullptr,
std::unique_ptr<TCPThread> thread = nullptr);
// Server/incoming constructor
explicit Connection(int socketDescriptor,
bool isSecure = false,
bool isPersistent = true,
QObject *parent = nullptr,
std::unique_ptr<TCPThread> thread = nullptr);
~Connection() override;

[[nodiscard]] QString id() const;
[[nodiscard]] bool isConnected() const;
[[nodiscard]] bool isSecure() const;
[[nodiscard]] bool isIncoming() const { return m_isIncoming; }
[[nodiscard]] bool isPersistent() const { return m_isPersistent; } // rename if you prefer isPersistentConnection()
[[nodiscard]] qintptr socketDescriptor() const { return m_socketDescriptor; }

void send(const Packet &packet);
void start();
void close();

signals:
// NEW: forward important signals from TCPThread
void dataReceived(const Packet &packet);
void stateChanged(const QString &stateMessage); // or use enum later
void errorOccurred(const QString &errorString);
void disconnected();

private slots:
void onThreadPacketReceived(const Packet &p);
void onThreadConnectStatus(const QString &msg);
void onThreadError(QSslSocket::SocketError error);

private:
void setupThreadConnections();
void shutdownThreadSafely(int timeoutMs = 2000);
bool m_isClosing = false;



QString m_id;
// QString m_host; // uncomment later if needed for reconnect
// quint16 m_port = 0;
std::unique_ptr<TCPThread> m_thread; // RAII ownership of the thread
bool m_threadStarted = false;
bool m_isIncoming = false;
bool m_isSecure = false;
bool m_isPersistent = false;
qintptr m_socketDescriptor = -1;

static constexpr int threadShutdownWaitMs = 10000;

protected:
[[nodiscard]] TCPThread* getThread() const { return m_thread.get(); }
[[nodiscard]] bool getThreadStarted() const { return m_threadStarted; }

int m_threadWaitTimeoutMs = 10000;

void assignUniqueId() {m_id = QUuid::createUuid().toString(QUuid::WithoutBraces);}
};


#endif //CONNECTION_H
Loading
Loading