Skip to content

Tomas/connection raii initial#413

Draft
ProfessorTom wants to merge 182 commits into
dannagle:developmentfrom
ProfessorTom:tomas/connection-raii-initial
Draft

Tomas/connection raii initial#413
ProfessorTom wants to merge 182 commits into
dannagle:developmentfrom
ProfessorTom:tomas/connection-raii-initial

Conversation

@ProfessorTom

@ProfessorTom ProfessorTom commented Mar 6, 2026

Copy link
Copy Markdown
Contributor

Before submitting a pull request:

  • Did you fork from the development branch? yes
  • Are you submitting the pull request to the development branch? (not master) yes

This will likely be a Cthulhu PR that will need to be broken up into a few smaller PRs once the work is done. Unfortunately, the way through is a long running branch in the short term.

Some benefits and thoughts as work continues on this branch:

TcpThread class will be replaced with BaseTcpThread, OutgoingTcpThread and IncomingTcpThread.

The idea here is to combine common code in the base class but separate the different directions so that they have room to breath and you can follow the main flows more easily. All three of those classes will automagically clean up both the socket and the threads via RAII and Qt's object lifecycle and parenting system.

There will be a Connection class (possibly also an IncomingTCPConnection and an OutgoingTcpConnection classes but I'm not sure yet) to handled the TCP connections.

I'm still debating this abstract layer, but at the time of this writing (6.8.2016) there is a Connection class in the code. The idea is that ConnectionManager will manage Connection classes which in turn will hold IncomingTcpThread and OutgoingTcpThread classes.

Unit tests have been added for code I have modified or added and will continue to be added for code I modify or add.

qMake is likely going away.

I'm not sure how Dan builds for each platform.

I was under the impression that most if not all platforms were using CMake now. I know snapcraft and macOS does. Regardless, the unit test only work with CMake, in part, because I am developing on a Mac. There may be some way to get the unit tests to build and run with qMake, but as I understand it, Dan wanted to move away from qMake anyway, so now's the chance.

This means I should be able to delete the .pro files in this PR. At the time of this writing I am not doing so because I don't know what the consequences are for taking that action. e.g. there may be one build that is still using qMake.

This likely means modifying the build process to build and run the unit tests and perhaps have such failures block PR merges. While the latter can be done once here on GitHub, the former will have to be done on each platform that PacketSender is built for. Given we are dealing with some low-level functionality (e.g. IPv4 vs IPv6, os default network stacks, etc.), it's possible that some unit tests may fail on platforms other than macOS. I haven't tested on any other platform. I fully intend for this to be a problem for Dan to solve, not because I'm being lazy or want to give Dan more work, but because at the time of this writing, this PR is not complete and I don't have access to each build system to test them.

Donations accepted here

- New ctor: TCPThread(host, port, initialPacket, parent)
- Creates QSslSocket early
- Connects connected/errorOccurred/stateChanged signals
- Stores host/port for run()
- Sets m_managedByConnection = true
- [[nodiscard]] bool isValid() const
- Checks clientConnection null, error codes, socket state
- Adds detailed qDebug/qWarning logging for failure reasons
- Add null check for clientConnection before calling close()
- Add waitForDisconnected(1000) for clean shutdown when socket exists
- Log warning if called with null socket
- Prevents potential segfault in dtor when socket not initialized
- Add if (clientConnection) guard before close()
- Only call waitForDisconnected() if socket is not UnconnectedState or ClosingState
- Log when wait is skipped or socket is null
- Eliminates Qt warning about waitForDisconnected in UnconnectedState
- Prevents potential null dereference in Connection dtor
…safe cleanup

- Update ctor to pass host/port/initialPacket
- Add public start() method with isValid() check
- Auto-start in ctor
- Add send(), isConnected(), isSecure()
- Forward key TCPThread signals
- Add m_threadStarted flag for safe dtor cleanup
- Add testThreadStartsAndStops() to verify no crash on start/dtor
- Give thread time to start with QTest::qWait(500)
- Check isConnected() (with fallback for Connecting state)
…plication

- Link packet.cpp, tcpthread.cpp, sendpacketbutton.cpp
- Add Qt6::Network for QSslSocket/QTcpSocket
- Switch to per test class QApplication isolation with runGuiTest/runNonGuiTest
- Added `waitForAndProcessIncomingData()` to handle the "wait for incoming data before sending" behavior
- Updated `persistentConnectionLoop()` to branch correctly:
  - `receiveBeforeSend == true` → waitForAndProcessIncomingData()
  - Normal idle path otherwise
- Added comprehensive unit tests for the new flow and call ordering
- Enhanced test double with tracking for `waitForAndProcessIncomingData()`

This completes the core conditional behavior for persistent outgoing connections.
…hex()

- Added `buildReplyPacket()` to OutgoingTcpThread for constructing smart/auto responses
- Fixed hex casing inconsistency: updated ASCIITohex() to always return uppercase (matching byteArrayToHex())
- Added comprehensive parameterized tests for buildReplyPacket() (encrypted/plain, empty responseData, etc.)
- Added testASCIITohex_alwaysReturnsUppercase() to prevent future regressions
- Updated test double with call tracking for the new method

This establishes a clean, testable foundation for smart response functionality.
…est coverage

- Extracted reply decision logic into `shouldSendReply()`
- Added `sendReplyIfNeeded()` as the central method for conditional reply sending
- Command-line reply packet now correctly takes precedence over settings
- Refactored buildReplyPacket() response data handling
- Made sendOutgoingPacket() virtual in BaseTcpThread
- Added proper consoleMode handling
- Improved testability with getSettings() temporary file support for unit tests
- Added comprehensive unit tests for shouldSendReply() (data-driven) and sendReplyIfNeeded()
- Added test helpers: normalizeReplyPacket, setupThreadToSendReply, buildExpectedReplyPacket

This improves separation of concerns and makes reply behavior much more testable and maintainable.
@ProfessorTom ProfessorTom force-pushed the tomas/connection-raii-initial branch from 1c8340b to 99d2b79 Compare May 25, 2026 22:17
- Automatically trigger reply logic after receiving a packet in OutgoingTcpThread
- Added unit test to verify sendReplyIfNeeded() is called from processIncomingData()
- Updated test double to track buildReceivedPacket() in call sequence

This connects the incoming data processing with the newly implemented reply system.
…nections

- Call `processIncomingData()` after sending in the non-persistent path
  of `OutgoingTcpThread::run()`. This wires up `sendReplyIfNeeded()`
  (basic response / command-line reply) for normal one-off sends.
- Updated `SingleSendOutgoingTcpThreadTests` to reflect new call sequence.
…nseData()

- Extracted smart response logic into `getSmartResponseData()`
- Added QSettings overload to `Packet::fetchSmartConfig()` for testability
- Updated `buildReplyPacket()` to use the new smart response function
- Added full unit test coverage (`testGetSmartResponseData`)
- Updated test double for call tracking

Smart responses now have proper priority (after command-line replies)
and work in both single-shot and persistent modes.
- Integrated `getSmartResponseData()` into `buildReplyPacket()` so smart
  responses now have correct priority over basic responses.
- Added `testSingleShot_sendsSmartResponse()` in SingleSendOutgoingTcpThreadTests
- Added `testPersistent_sendsSmartResponse()` in OutgoingTcpThreadPersistentConnectionLoopTests

Smart response support is now fully active for both single-shot and persistent outgoing connections.
- Refactored duplicated call sequence expectations into `getSendReplyIfNeededExpectedCallSequence()`
…ling

- Split run() into handleOutgoingSSL(), handleOutgoingPlainTCP(), etc.
- Added dedicated handshake success/failure handlers and loadSSLCerts()
- Better separation of concerns for future SSL improvements
- Added PacketSenderQSslSocketInterface + RealQSslSocket wrapper
- Updated BaseTcpThread and OutgoingTcpThread to primarily use the new interface
- Removed most qobject_cast / dynamic_cast hacks from test doubles
- MockSslSocket now implements the interface via multiple inheritance
- Simplified socket access methods in BaseTcpThread
- Updated tests and TestUtils to work with the new architecture

All tests passing after migration.
…Interface

- Replaced all remaining getSocket() calls with getSocketInterface()
- Updated BaseTcpThread and OutgoingTcpThread to use the interface consistently
- Expanded PacketSenderQSslSocketInterface with SSL and common socket methods
- Updated RealQSslSocket and MockSslSocket implementations
- Cleaned up test doubles (removed old qobject_cast hacks)
- All tests passing

This completes the core socket abstraction migration.
…ions

- Added `FileUtils` namespace with helpers for loading base64-encoded resources
- Implemented `loadSnakeOilCerts()`, `loadSSLCerts(bool)`, and related helpers
- Added `hasLocalCertificate()` / `hasPrivateKey()` to `PacketSenderQSslSocketInterface`
- Updated `handleOutgoingSSL()` to respect `LOAD_SNAKEOIL_CERTS` setting
- Added comprehensive unit tests in `OutgoingTcpThreadConnectionTests`:
  - SSL handshake success/failure paths
  - Production vs snake oil certificate loading
  - Error handling for missing files
  - `run()` call sequence and integration
- Updated test infrastructure (CMake, mocks, TestUtils)

OutgoingTcpThread now has full SSL parity with the old TcpThread for single-shot connections.
…shot + persistent behavior

- Set `persistent` flag from Packet in constructor (explicit copy)
- Made `persistentConnectionLoop()` virtual for easier testing/subclassing
- Added comprehensive run() tests:
  - Constructor correctly sets persistent flag (true/false/default)
  - Non-persistent path skips persistentConnectionLoop()
  - Persistent path calls persistentConnectionLoop() + closeConnection()
  - SSL + persistent sanity test
- Minor cleanup and test double updates

OutgoingTcpThread is now feature-complete for both single-shot and persistent SSL/TCP connections.
- Add incomingtcpthread.cpp to build
- Extend BaseTcpThread with shouldUseSSL flag and getter
- Add getSocketDescriptor()/setSocketDescriptor() to PacketSenderQSslSocketInterface + RealQSslSocket implementation (needed for taking ownership of accepted sockets)
- Wire up unit tests and update MockSslSocket
- Update test runner

This lays the foundation for handling incoming TCP (and SSL) connections.
- Added buildInitialReceivedPacket() which creates the first Packet
  for an incoming connection (TCP or SSL)
- Populates timestamp, name, tcpOrUdp, IPs, ports, and reads initial
  data from socket if available
- Added comprehensive unit tests (data-driven for secure/insecure cases)
- Updated test double and helper infrastructure

This gives incoming connections a proper initial packet similar to
outgoing ones.
- Added sendSmartReplyIfConfigured() that automatically replies to
  incoming connections when SEND_RESPONSE and RESPONSE_HEX settings
  are configured.
- Supports Packet macro expansion (e.g. {COUNTER}, {UNIXTIME}, etc.)
  before sending the reply.
- Added comprehensive unit tests covering:
  • Disabled by default / SEND_RESPONSE=false
  • Empty RESPONSE_HEX
  • Successful reply path
  • Macro expansion (e.g. {COUNTER} → "1")

- Fixed IncomingTcpThreadTestDouble to use public inheritance
- Added proper call tracking and overrides for sendOutgoingPacket()

This enables smart/auto-reply functionality for incoming TCP/SSL connections.
- Move SSL certificate loading logic (loadSnakeOil* and loadSSLCerts)
  from OutgoingTcpThread up to BaseTcpThread to eliminate duplication.

- Add emitSSLDiagnosticPackets() to IncomingTcpThread that emits four
  diagnostic packets on successful SSL handshake:
  • Cipher encryption method
  • Cipher authentication method
  • Peer certificate issuer
  • Local certificate issuer

- Add corresponding virtual method and interface support (localCertificate()).

- Add comprehensive unit tests covering:
  • null socket
  • non-encrypted socket
  • success path with all four packets

- Update MockSslSocket and IncomingTcpThreadTestDouble accordingly.
- Extract call tracking logic into a reusable `CallTracker` base class
  (with `recordCall()`, `getCallCount()`, `wasMethodCalled()`, etc.)
- Make `IncomingTcpThreadTestDouble` and `OutgoingTcpThreadTestDouble`
  inherit from `CallTracker`
- Replace duplicated per-method counter variables and manual callSequence
  management with centralized tracking
- Update all affected tests to use `getCallCount(CallTracker::XXX())`
- Add `utils/calltracker.cpp` to unit test build

This eliminates boilerplate, improves maintainability, and makes it easier
to add new tracked methods in the future.
- Relocate `testutils.cpp` and `testutils.h` to `src/tests/unit/utils/`
- Update CMakeLists.txt and all including test files to new include path
- Adjust relative include for MockSslSocket in testutils.h

This improves the organization of the unit test directory by grouping
shared test helpers (alongside the newly added CallTracker) into a
`utils/` folder.
- Extracted SSL handshake logic into `performSSLHandshakeIfNeeded()`
- Load snake oil / real certs based on setting
- Call `startServerEncryption()` + `waitForEncrypted(5000)`
- Emit error on timeout
- Call `emitSSLDiagnosticPackets()` only on success

- Updated `PacketSenderQSslSocketInterface` + `RealQSslSocket`
- Enhanced `MockSslSocket` with `mockSSLHandshakeShouldSucceed` control
- Added comprehensive unit tests (null socket, snake oil setting,
  ignore SSL errors, startServerEncryption, waitForEncrypted success/failure,
  diagnostic packets behavior)

- Minor cleanup: commented noisy debug messages in globals.h
- Integrated with CallTracker for better test assertions

This centralizes incoming SSL setup logic and makes it fully testable.
- Moved common `closeConnection()` logic (disconnectFromHost + close + emit status)
  from OutgoingTcpThread up to BaseTcpThread
- Removed duplicate implementations and overrides from IncomingTcpThread
  and OutgoingTcpThread
- Made `closeConnection()` a regular virtual method (no longer pure virtual)
- Added full unit test coverage in BaseTcpThreadTests
- Updated BaseTcpThreadTestDouble and removed duplicated tests from OutgoingTcpThreadTests
- Integrated with CallTracker for call verification

This eliminates code duplication and ensures consistent connection cleanup
behavior for both incoming and outgoing threads.
- Added `handleIncomingConnection()`: emits status, performs SSL handshake (if enabled),
  builds initial packet, emits `packetReceived`, and sends smart reply if configured
- Implemented `run()` to orchestrate the full incoming flow + cleanup via `closeConnection()`
- Added early exit in `performSSLHandshakeIfNeeded()` when `shouldUseSSL` is false
- Updated `IncomingTcpThreadTestDouble` and CallTracker with new tracked methods
- Added comprehensive unit tests covering:
  - SSL disabled path
  - Null socket handling
  - Connection status emission
  - Full success call sequence
  - Packet emission
  - `run()` behavior

This completes the main execution path for incoming TCP/SSL connections.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant