From 1c596547af24f600f05f877a153f5f98052f7180 Mon Sep 17 00:00:00 2001 From: Victor Carreras <34163765+vicajilau@users.noreply.github.com> Date: Thu, 2 Jul 2026 08:45:21 +0200 Subject: [PATCH 1/3] fix: prevent SSHTransport busy-loop on partial packets --- lib/src/ssh_transport.dart | 10 +++++++++- test/src/ssh_transport_version_test.dart | 25 ++++++++++++++++++++++++ 2 files changed, 34 insertions(+), 1 deletion(-) diff --git a/lib/src/ssh_transport.dart b/lib/src/ssh_transport.dart index 410e0c3..d0e234f 100644 --- a/lib/src/ssh_transport.dart +++ b/lib/src/ssh_transport.dart @@ -131,6 +131,9 @@ class SSHTransport { /// Guards asynchronous packet processing to preserve message order. var _isProcessingData = false; + /// Tracks whether new socket data was received since packet processing started. + var _hasNewData = false; + /// Identification string sent by us without trailing \r\n. For example, /// "SSH-2.0-DartSSH_2.0". String get _localVersion => 'SSH-2.0-$version'; @@ -475,6 +478,7 @@ class SSHTransport { /// Callback triggered when new raw bytes are received from the socket. void _onSocketData(Uint8List data) { _buffer.add(data); + _hasNewData = true; _scheduleProcessData(); } @@ -496,6 +500,8 @@ class SSHTransport { } _isProcessingData = true; + final lengthBefore = _buffer.length; + _hasNewData = false; _processDataAsync().catchError((error, stackTrace) { if (error is SSHError) { @@ -506,7 +512,9 @@ class SSHTransport { }).whenComplete(() { _isProcessingData = false; if (_buffer.isNotEmpty && !isClosed) { - _scheduleProcessData(); + if (_hasNewData || _buffer.length < lengthBefore) { + _scheduleProcessData(); + } } }); } diff --git a/test/src/ssh_transport_version_test.dart b/test/src/ssh_transport_version_test.dart index ce37450..a4dc13f 100644 --- a/test/src/ssh_transport_version_test.dart +++ b/test/src/ssh_transport_version_test.dart @@ -43,6 +43,27 @@ void main() { client.close(); }); + + test('does not busy loop on partial packet after handshake', () async { + final socket = _FakeSSHSocket(); + final client = SSHClient( + socket, + username: 'demo', + ); + + // Complete the version exchange. + socket.addIncoming('SSH-2.0-OpenSSH_3.6.1p2\r\n'); + await _pumpUntil(() => client.remoteVersion != null); + + // Send the first 4 bytes of a packet indicating a length of 100. + socket.addRawIncoming(Uint8List.fromList([0, 0, 0, 100])); + + // Wait a moment. If there is a microtask busy loop, the delayed future + // will never run and the test will timeout. + await Future.delayed(const Duration(milliseconds: 50)); + + client.close(); + }); }); } @@ -74,6 +95,10 @@ class _FakeSSHSocket implements SSHSocket { _inputController.add(Uint8List.fromList(latin1.encode(data))); } + void addRawIncoming(Uint8List data) { + _inputController.add(data); + } + @override Future close() async { if (!_doneCompleter.isCompleted) { From 9078dfe9e7239af8827f00279511cab6cae24704 Mon Sep 17 00:00:00 2001 From: Victor Carreras <34163765+vicajilau@users.noreply.github.com> Date: Thu, 2 Jul 2026 08:51:54 +0200 Subject: [PATCH 2/3] test: add test case for rescheduling transport processing when extra data remains in buffer --- test/src/ssh_transport_version_test.dart | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/test/src/ssh_transport_version_test.dart b/test/src/ssh_transport_version_test.dart index a4dc13f..6a07fcf 100644 --- a/test/src/ssh_transport_version_test.dart +++ b/test/src/ssh_transport_version_test.dart @@ -64,6 +64,24 @@ void main() { client.close(); }); + + test('reschedules processing when more data remains in the buffer', () async { + final socket = _FakeSSHSocket(); + final client = SSHClient( + socket, + username: 'demo', + ); + + // Send the version banner followed by some extra data in one go. + socket.addIncoming('SSH-2.0-OpenSSH_3.6.1p2\r\nSSH-2.0-SecondLine\r\n'); + + // Pump until the client processes the first version. + await _pumpUntil(() => client.remoteVersion != null); + + expect(client.remoteVersion, 'SSH-2.0-OpenSSH_3.6.1p2'); + + client.close(); + }); }); } From b53960a56f3568aee0ef866cc68c8c7bd29e5558 Mon Sep 17 00:00:00 2001 From: Victor Carreras <34163765+vicajilau@users.noreply.github.com> Date: Thu, 2 Jul 2026 08:53:39 +0200 Subject: [PATCH 3/3] style: wrap test description line in ssh_transport_version_test.dart --- test/src/ssh_transport_version_test.dart | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test/src/ssh_transport_version_test.dart b/test/src/ssh_transport_version_test.dart index 6a07fcf..848c18d 100644 --- a/test/src/ssh_transport_version_test.dart +++ b/test/src/ssh_transport_version_test.dart @@ -65,7 +65,8 @@ void main() { client.close(); }); - test('reschedules processing when more data remains in the buffer', () async { + test('reschedules processing when more data remains in the buffer', + () async { final socket = _FakeSSHSocket(); final client = SSHClient( socket,