From 2f770bbd532cb80044a8128682e5940fe639c632 Mon Sep 17 00:00:00 2001 From: just-stuff-tm Date: Tue, 10 Mar 2026 21:38:35 -0400 Subject: [PATCH] fix(tcp): reset state on aborted pre-handshake connect --- lib/connector/meshcore_connector.dart | 42 +++++++++++++------ ...hcore_connector_tcp_error_filter_test.dart | 29 +++++++++++++ 2 files changed, 59 insertions(+), 12 deletions(-) diff --git a/lib/connector/meshcore_connector.dart b/lib/connector/meshcore_connector.dart index 0de1a90a..c89e37f5 100644 --- a/lib/connector/meshcore_connector.dart +++ b/lib/connector/meshcore_connector.dart @@ -992,6 +992,21 @@ class MeshCoreConnector extends ChangeNotifier { _setState(MeshCoreConnectionState.connecting); try { + Future handleTcpConnectAbort({required String message}) async { + _appDebugLogService?.warn(message, tag: 'TCP'); + final shouldResetState = shouldResetStateAfterTcpConnectAbort( + state: _state, + activeTransport: _activeTransport, + ); + if (shouldResetState) { + await disconnect(manual: false); + return; + } + if (_tcpManager.isConnected) { + await _tcpManager.disconnect(); + } + } + await _tcpFrameSubscription?.cancel(); _tcpFrameSubscription = null; await _tcpManager.connect(host: host, port: port); @@ -1000,13 +1015,10 @@ class MeshCoreConnector extends ChangeNotifier { _state != MeshCoreConnectionState.connecting || !_tcpManager.isConnected; if (isTcpConnectCancelled) { - _appDebugLogService?.warn( - 'connectTcp aborted before handshake: state=$_state transport=$_activeTransport connected=${_tcpManager.isConnected}', - tag: 'TCP', + await handleTcpConnectAbort( + message: + 'connectTcp aborted before handshake: state=$_state transport=$_activeTransport connected=${_tcpManager.isConnected}', ); - if (_tcpManager.isConnected) { - await _tcpManager.disconnect(); - } return; } notifyListeners(); @@ -1017,13 +1029,10 @@ class MeshCoreConnector extends ChangeNotifier { _state != MeshCoreConnectionState.connecting || !_tcpManager.isConnected; if (isTcpConnectCancelledAfterDelay) { - _appDebugLogService?.warn( - 'connectTcp aborted after connect delay: state=$_state transport=$_activeTransport connected=${_tcpManager.isConnected}', - tag: 'TCP', + await handleTcpConnectAbort( + message: + 'connectTcp aborted after connect delay: state=$_state transport=$_activeTransport connected=${_tcpManager.isConnected}', ); - if (_tcpManager.isConnected) { - await _tcpManager.disconnect(); - } return; } _tcpFrameSubscription = _tcpManager.frameStream.listen( @@ -1091,6 +1100,15 @@ class MeshCoreConnector extends ChangeNotifier { (activeTransport != MeshCoreTransportType.tcp || !tcpManagerConnected); } + @visibleForTesting + static bool shouldResetStateAfterTcpConnectAbort({ + required MeshCoreConnectionState state, + required MeshCoreTransportType activeTransport, + }) { + return state == MeshCoreConnectionState.connecting && + activeTransport == MeshCoreTransportType.tcp; + } + Future connect(BluetoothDevice device, {String? displayName}) async { if (_state == MeshCoreConnectionState.connecting || _state == MeshCoreConnectionState.connected) { diff --git a/test/connector/meshcore_connector_tcp_error_filter_test.dart b/test/connector/meshcore_connector_tcp_error_filter_test.dart index ee6a382c..c363b975 100644 --- a/test/connector/meshcore_connector_tcp_error_filter_test.dart +++ b/test/connector/meshcore_connector_tcp_error_filter_test.dart @@ -61,4 +61,33 @@ void main() { expect(result, isFalse); }); }); + + group('shouldResetStateAfterTcpConnectAbort', () { + test('returns true when TCP connect is still in connecting state', () { + final result = MeshCoreConnector.shouldResetStateAfterTcpConnectAbort( + state: MeshCoreConnectionState.connecting, + activeTransport: MeshCoreTransportType.tcp, + ); + + expect(result, isTrue); + }); + + test('returns false when state is already disconnected', () { + final result = MeshCoreConnector.shouldResetStateAfterTcpConnectAbort( + state: MeshCoreConnectionState.disconnected, + activeTransport: MeshCoreTransportType.tcp, + ); + + expect(result, isFalse); + }); + + test('returns false when transport switched away from TCP', () { + final result = MeshCoreConnector.shouldResetStateAfterTcpConnectAbort( + state: MeshCoreConnectionState.connecting, + activeTransport: MeshCoreTransportType.bluetooth, + ); + + expect(result, isFalse); + }); + }); }