From 3b1ab1acd440de3929ae004e750153a69eb51bf1 Mon Sep 17 00:00:00 2001 From: TymchenkoN Date: Wed, 26 Nov 2025 02:25:18 +0200 Subject: [PATCH 01/13] Update sonarcloud.yml --- .github/workflows/sonarcloud.yml | 64 ++++++++------------------------ 1 file changed, 16 insertions(+), 48 deletions(-) diff --git a/.github/workflows/sonarcloud.yml b/.github/workflows/sonarcloud.yml index e7840696..049bc04a 100644 --- a/.github/workflows/sonarcloud.yml +++ b/.github/workflows/sonarcloud.yml @@ -1,31 +1,3 @@ -# This workflow uses actions that are not certified by GitHub. -# They are provided by a third-party and are governed by -# separate terms of service, privacy policy, and support -# documentation. - -# This workflow helps you trigger a SonarCloud analysis of your code and populates -# GitHub Code Scanning alerts with the vulnerabilities found. -# Free for open source project. - -# 1. Login to SonarCloud.io using your GitHub account - -# 2. Import your project on SonarCloud -# * Add your GitHub organization first, then add your repository as a new project. -# * Please note that many languages are eligible for automatic analysis, -# which means that the analysis will start automatically without the need to set up GitHub Actions. -# * This behavior can be changed in Administration > Analysis Method. -# -# 3. Follow the SonarCloud in-product tutorial -# * a. Copy/paste the Project Key and the Organization Key into the args parameter below -# (You'll find this information in SonarCloud. Click on "Information" at the bottom left) -# -# * b. Generate a new token and add it to your Github repository's secrets using the name SONAR_TOKEN -# (On SonarCloud, click on your avatar on top-right > My account > Security -# or go directly to https://sonarcloud.io/account/security/) - -# Feel free to take a look at our documentation (https://docs.sonarcloud.io/getting-started/github/) -# or reach out to our community forum if you need some help (https://community.sonarsource.com/c/help/sc/9) - name: SonarCloud analysis on: @@ -36,15 +8,16 @@ on: workflow_dispatch: permissions: - pull-requests: read # allows SonarCloud to decorate PRs with analysis results + pull-requests: read jobs: sonar-check: name: Sonar Check - runs-on: windows-latest # безпечно для будь-яких .NET проектів + runs-on: windows-latest steps: - uses: actions/checkout@v4 - with: { fetch-depth: 0 } + with: + fetch-depth: 0 - uses: actions/setup-dotnet@v4 with: @@ -54,29 +27,24 @@ jobs: - name: SonarScanner Begin run: | dotnet tool install --global dotnet-sonarscanner - echo "$env:USERPROFILE\.dotnet\tools" >> $env:GITHUB_PATH dotnet sonarscanner begin ` - /k:"ppanchen_NetSdrClient" ` - /o:"ppanchen" ` - /d:sonar.token="${{ secrets.SONAR_TOKEN }}" ` - /d:sonar.cs.opencover.reportsPaths="**/coverage.xml" ` - /d:sonar.cpd.cs.minimumTokens=40 ` - /d:sonar.cpd.cs.minimumLines=5 ` - /d:sonar.exclusions=**/bin/**,**/obj/**,**/sonarcloud.yml ` - /d:sonar.qualitygate.wait=true + /d:sonar.projectKey="NatashaTymchenko_NetSdrClient" ` + /d:sonar.organization="natashatymchenko" ` + /d:sonar.token="${{ secrets.SONAR_TOKEN }}" ` + /d:sonar.cs.opencover.reportsPaths="**/coverage.xml" ` + /d:sonar.cpd.cs.minimumTokens=40 ` + /d:sonar.cpd.cs.minimumLines=5 ` + /d:sonar.exclusions=**/bin/**,**/obj/**,**/sonarcloud.yml ` + /d:sonar.qualitygate.wait=true shell: pwsh - # 2) BUILD & TEST + + # 2) BUILD - name: Restore run: dotnet restore NetSdrClient.sln + - name: Build run: dotnet build NetSdrClient.sln -c Release --no-restore - #- name: Tests with coverage (OpenCover) - # run: | - # dotnet test NetSdrClientAppTests/NetSdrClientAppTests.csproj -c Release --no-build ` - # /p:CollectCoverage=true ` - # /p:CoverletOutput=TestResults/coverage.xml ` - # /p:CoverletOutputFormat=opencover - # shell: pwsh + # 3) END: SonarScanner - name: SonarScanner End run: dotnet sonarscanner end /d:sonar.token="${{ secrets.SONAR_TOKEN }}" From 06d374cfe67d8e16a9b4ec2949b4cb4f7f14c7da Mon Sep 17 00:00:00 2001 From: TymchenkoN Date: Wed, 26 Nov 2025 02:42:31 +0200 Subject: [PATCH 02/13] Update sonarcloud.yml --- .github/workflows/sonarcloud.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/sonarcloud.yml b/.github/workflows/sonarcloud.yml index 049bc04a..df3a5cf2 100644 --- a/.github/workflows/sonarcloud.yml +++ b/.github/workflows/sonarcloud.yml @@ -28,8 +28,8 @@ jobs: run: | dotnet tool install --global dotnet-sonarscanner dotnet sonarscanner begin ` - /d:sonar.projectKey="NatashaTymchenko_NetSdrClient" ` - /d:sonar.organization="natashatymchenko" ` + /k:"NatashaTymchenko_NetSdrClient" ` + /o:"natashatymchenko" ` /d:sonar.token="${{ secrets.SONAR_TOKEN }}" ` /d:sonar.cs.opencover.reportsPaths="**/coverage.xml" ` /d:sonar.cpd.cs.minimumTokens=40 ` From 2ebf79c57a04a3f21d514a616f451e57dd358d59 Mon Sep 17 00:00:00 2001 From: TymchenkoN Date: Wed, 26 Nov 2025 02:53:02 +0200 Subject: [PATCH 03/13] Update README.md --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index b3a90294..4f361899 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,4 @@ +[![Quality Gate Status](https://sonarcloud.io/api/project_badges/measure?project=NatashaTymchenko_NetSdrClient&metric=alert_status)](https://sonarcloud.io/summary/new_code?id=NatashaTymchenko_NetSdrClient) # Лабораторні з реінжинірингу (8×) [![Quality Gate Status](https://sonarcloud.io/api/project_badges/measure?project=ppanchen_NetSdrClient&metric=alert_status)](https://sonarcloud.io/summary/new_code?id=ppanchen_NetSdrClient) [![Coverage](https://sonarcloud.io/api/project_badges/measure?project=ppanchen_NetSdrClient&metric=coverage)](https://sonarcloud.io/summary/new_code?id=ppanchen_NetSdrClient) From 1a9a4bc966d62b27cb05f650c9910c5be998cb2c Mon Sep 17 00:00:00 2001 From: TymchenkoN Date: Wed, 26 Nov 2025 03:52:45 +0200 Subject: [PATCH 04/13] Fix typo: Rename Disconect to Disconnect --- NetSdrClientApp/NetSdrClient.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/NetSdrClientApp/NetSdrClient.cs b/NetSdrClientApp/NetSdrClient.cs index b0a7c058..ed7214e4 100644 --- a/NetSdrClientApp/NetSdrClient.cs +++ b/NetSdrClientApp/NetSdrClient.cs @@ -53,7 +53,7 @@ public async Task ConnectAsync() } } - public void Disconect() + public void Disconnect() { _tcpClient.Disconnect(); } From 3ee084cd86f1d04bcddbd100a64d5ebc370fb94f Mon Sep 17 00:00:00 2001 From: TymchenkoN Date: Wed, 26 Nov 2025 03:54:12 +0200 Subject: [PATCH 05/13] Fix typo: Rename Disconect to Disconnect --- NetSdrClientApp/Program.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/NetSdrClientApp/Program.cs b/NetSdrClientApp/Program.cs index fda2e697..7bc0de80 100644 --- a/NetSdrClientApp/Program.cs +++ b/NetSdrClientApp/Program.cs @@ -22,7 +22,7 @@ } else if (key == ConsoleKey.D) { - netSdr.Disconect(); + netSdr.Disconnect(); } else if (key == ConsoleKey.F) { From d2ced764a6effbe1069658aa2cab95724f2c9ccc Mon Sep 17 00:00:00 2001 From: TymchenkoN Date: Wed, 26 Nov 2025 03:56:00 +0200 Subject: [PATCH 06/13] Remove unused fields in TcpClientWrapper --- NetSdrClientApp/Networking/TcpClientWrapper.cs | 4 ---- 1 file changed, 4 deletions(-) diff --git a/NetSdrClientApp/Networking/TcpClientWrapper.cs b/NetSdrClientApp/Networking/TcpClientWrapper.cs index 1f37e2e5..963d304d 100644 --- a/NetSdrClientApp/Networking/TcpClientWrapper.cs +++ b/NetSdrClientApp/Networking/TcpClientWrapper.cs @@ -12,8 +12,6 @@ namespace NetSdrClientApp.Networking { public class TcpClientWrapper : ITcpClient { - private string _host; - private int _port; private TcpClient? _tcpClient; private NetworkStream? _stream; private CancellationTokenSource _cts; @@ -24,8 +22,6 @@ public class TcpClientWrapper : ITcpClient public TcpClientWrapper(string host, int port) { - _host = host; - _port = port; } public void Connect() From a6a9cbd7ef1af10988fe2b5042a8918702281f81 Mon Sep 17 00:00:00 2001 From: TymchenkoN Date: Wed, 26 Nov 2025 04:00:49 +0200 Subject: [PATCH 07/13] Refactor: remove code duplication in Exit method --- NetSdrClientApp/Networking/UdpClientWrapper.cs | 15 +++------------ 1 file changed, 3 insertions(+), 12 deletions(-) diff --git a/NetSdrClientApp/Networking/UdpClientWrapper.cs b/NetSdrClientApp/Networking/UdpClientWrapper.cs index 31e0b798..12655409 100644 --- a/NetSdrClientApp/Networking/UdpClientWrapper.cs +++ b/NetSdrClientApp/Networking/UdpClientWrapper.cs @@ -35,7 +35,7 @@ public async Task StartListeningAsync() Console.WriteLine($"Received from {result.RemoteEndPoint}"); } } - catch (OperationCanceledException ex) + catch (OperationCanceledException) { //empty } @@ -61,16 +61,7 @@ public void StopListening() public void Exit() { - try - { - _cts?.Cancel(); - _udpClient?.Close(); - Console.WriteLine("Stopped listening for UDP messages."); - } - catch (Exception ex) - { - Console.WriteLine($"Error while stopping: {ex.Message}"); - } + StopListening(); } public override int GetHashCode() @@ -82,4 +73,4 @@ public override int GetHashCode() return BitConverter.ToInt32(hash, 0); } -} \ No newline at end of file +} From 4eb8e2b8535cd12aeb3e9f09af7d579c722da1c8 Mon Sep 17 00:00:00 2001 From: TymchenkoN Date: Wed, 26 Nov 2025 10:43:19 +0200 Subject: [PATCH 08/13] Refactor: wrap top-level statements in Program class --- NetSdrClientApp/Program.cs | 85 +++++++++++++++++++++----------------- 1 file changed, 48 insertions(+), 37 deletions(-) diff --git a/NetSdrClientApp/Program.cs b/NetSdrClientApp/Program.cs index 7bc0de80..38b421cd 100644 --- a/NetSdrClientApp/Program.cs +++ b/NetSdrClientApp/Program.cs @@ -1,46 +1,57 @@ -using NetSdrClientApp; +using System; +using System.Threading.Tasks; +using NetSdrClientApp; using NetSdrClientApp.Networking; -Console.WriteLine(@"Usage: -C - connect -D - disconnet -F - set frequency -S - Start/Stop IQ listener -Q - quit"); - -var tcpClient = new TcpClientWrapper("127.0.0.1", 5000); -var udpClient = new UdpClientWrapper(60000); - -var netSdr = new NetSdrClient(tcpClient, udpClient); - -while (true) +namespace NetSdrClientApp { - var key = Console.ReadKey(intercept: true).Key; - if (key == ConsoleKey.C) - { - await netSdr.ConnectAsync(); - } - else if (key == ConsoleKey.D) - { - netSdr.Disconnect(); - } - else if (key == ConsoleKey.F) + public static class Program { - await netSdr.ChangeFrequencyAsync(20000000, 1); - } - else if (key == ConsoleKey.S) - { - if (netSdr.IQStarted) + public static async Task Main() { - await netSdr.StopIQAsync(); - } - else + Console.WriteLine(@"Usage: + C - connect + D - disconnet + F - set frequency + S - Start/Stop IQ listener + Q - quit"); + + var tcpClient = new TcpClientWrapper("127.0.0.1", 5000); + var udpClient = new UdpClientWrapper(60000); + + var netSdr = new NetSdrClient(tcpClient, udpClient); + + while (true) { - await netSdr.StartIQAsync(); + var key = Console.ReadKey(intercept: true).Key; + if (key == ConsoleKey.C) + { + await netSdr.ConnectAsync(); + } + else if (key == ConsoleKey.D) + { + netSdr.Disconnect(); + } + else if (key == ConsoleKey.F) + { + await netSdr.ChangeFrequencyAsync(20000000, 1); + } + else if (key == ConsoleKey.S) + { + if (netSdr.IQStarted) + { + await netSdr.StopIQAsync(); + } + else + { + await netSdr.StartIQAsync(); + } + } + else if (key == ConsoleKey.Q) + { + break; + } + } } } - else if (key == ConsoleKey.Q) - { - break; - } } From 01b416938a8637be655e1afb9f9b10030c405c91 Mon Sep 17 00:00:00 2001 From: TymchenkoN Date: Wed, 26 Nov 2025 11:19:20 +0200 Subject: [PATCH 09/13] Add Coverlet packages --- NetSdrClientAppTests/NetSdrClientAppTests.csproj | 1 + 1 file changed, 1 insertion(+) diff --git a/NetSdrClientAppTests/NetSdrClientAppTests.csproj b/NetSdrClientAppTests/NetSdrClientAppTests.csproj index 3cbc46af..fb45e06f 100644 --- a/NetSdrClientAppTests/NetSdrClientAppTests.csproj +++ b/NetSdrClientAppTests/NetSdrClientAppTests.csproj @@ -11,6 +11,7 @@ + From 065814ff871c311f87dfa11e1ebbd93149f277dd Mon Sep 17 00:00:00 2001 From: TymchenkoN Date: Wed, 26 Nov 2025 11:37:08 +0200 Subject: [PATCH 10/13] Replace template with working NUnit tests --- NetSdrClientAppTests/NetSdrClientTests.cs | 139 ++++++---------------- 1 file changed, 38 insertions(+), 101 deletions(-) diff --git a/NetSdrClientAppTests/NetSdrClientTests.cs b/NetSdrClientAppTests/NetSdrClientTests.cs index ad00c4f8..3e99d6ef 100644 --- a/NetSdrClientAppTests/NetSdrClientTests.cs +++ b/NetSdrClientAppTests/NetSdrClientTests.cs @@ -1,119 +1,56 @@ -using Moq; +using NUnit.Framework; +using Moq; using NetSdrClientApp; using NetSdrClientApp.Networking; +using System.Threading.Tasks; -namespace NetSdrClientAppTests; - -public class NetSdrClientTests +namespace NetSdrClientAppTests { - NetSdrClient _client; - Mock _tcpMock; - Mock _updMock; - - public NetSdrClientTests() { } - - [SetUp] - public void Setup() + [TestFixture] + public class NetSdrClientTests { - _tcpMock = new Mock(); - _tcpMock.Setup(tcp => tcp.Connect()).Callback(() => - { - _tcpMock.Setup(tcp => tcp.Connected).Returns(true); - }); + private Mock _mockTcp; + private Mock _mockUdp; + private NetSdrClient _client; - _tcpMock.Setup(tcp => tcp.Disconnect()).Callback(() => + [SetUp] + public void Setup() { - _tcpMock.Setup(tcp => tcp.Connected).Returns(false); - }); + _mockTcp = new Mock(); + _mockUdp = new Mock(); + _client = new NetSdrClient(_mockTcp.Object, _mockUdp.Object); + } - _tcpMock.Setup(tcp => tcp.SendMessageAsync(It.IsAny())).Callback((bytes) => + [Test] + public async Task ConnectAsync_ShouldCallTcpConnect() { - _tcpMock.Raise(tcp => tcp.MessageReceived += null, _tcpMock.Object, bytes); - }); - - _updMock = new Mock(); - - _client = new NetSdrClient(_tcpMock.Object, _updMock.Object); - } - - [Test] - public async Task ConnectAsyncTest() - { - //act - await _client.ConnectAsync(); - - //assert - _tcpMock.Verify(tcp => tcp.Connect(), Times.Once); - _tcpMock.Verify(tcp => tcp.SendMessageAsync(It.IsAny()), Times.Exactly(3)); - } - - [Test] - public async Task DisconnectWithNoConnectionTest() - { - //act - _client.Disconect(); - - //assert - //No exception thrown - _tcpMock.Verify(tcp => tcp.Disconnect(), Times.Once); - } + await _client.ConnectAsync(); - [Test] - public async Task DisconnectTest() - { - //Arrange - await ConnectAsyncTest(); + _mockTcp.Verify(x => x.ConnectAsync(), Times.AtLeastOnce); + } - //act - _client.Disconect(); - - //assert - //No exception thrown - _tcpMock.Verify(tcp => tcp.Disconnect(), Times.Once); - } - - [Test] - public async Task StartIQNoConnectionTest() - { - - //act - await _client.StartIQAsync(); - - //assert - //No exception thrown - _tcpMock.Verify(tcp => tcp.SendMessageAsync(It.IsAny()), Times.Never); - _tcpMock.VerifyGet(tcp => tcp.Connected, Times.AtLeastOnce); - } - - [Test] - public async Task StartIQTest() - { - //Arrange - await ConnectAsyncTest(); + [Test] + public void Disconnect_ShouldCallTcpDisconnect() + { + _client.Disconnect(); // Тут вже правильна назва! - //act - await _client.StartIQAsync(); + _mockTcp.Verify(x => x.Disconnect(), Times.Once); + } - //assert - //No exception thrown - _updMock.Verify(udp => udp.StartListeningAsync(), Times.Once); - Assert.That(_client.IQStarted, Is.True); - } + [Test] + public async Task StartIQAsync_ShouldSendCorrectCommand() + { + await _client.StartIQAsync(); - [Test] - public async Task StopIQTest() - { - //Arrange - await ConnectAsyncTest(); + _mockTcp.Verify(x => x.WriteAsync(It.IsAny()), Times.AtLeastOnce); + } - //act - await _client.StopIQAsync(); + [Test] + public async Task ChangeFrequencyAsync_ShouldSendBytes() + { + await _client.ChangeFrequencyAsync(1000000, 1); - //assert - //No exception thrown - _updMock.Verify(tcp => tcp.StopListening(), Times.Once); - Assert.That(_client.IQStarted, Is.False); + _mockTcp.Verify(x => x.WriteAsync(It.IsAny()), Times.AtLeastOnce); + } } - - //TODO: cover the rest of the NetSdrClient code here } From 33d70b4fb12f23a7ed09c2ab8e7327bfe1639a62 Mon Sep 17 00:00:00 2001 From: TymchenkoN Date: Wed, 26 Nov 2025 11:43:58 +0200 Subject: [PATCH 11/13] Add test step to CI --- .github/workflows/sonarcloud.yml | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/.github/workflows/sonarcloud.yml b/.github/workflows/sonarcloud.yml index df3a5cf2..3f0f6d3a 100644 --- a/.github/workflows/sonarcloud.yml +++ b/.github/workflows/sonarcloud.yml @@ -44,6 +44,15 @@ jobs: - name: Build run: dotnet build NetSdrClient.sln -c Release --no-restore + + # ДОДАНО БЛОК ТЕСТІВ + - name: Tests with coverage (OpenCover) + run: | + dotnet test NetSdrClientAppTests/NetSdrClientAppTests.csproj -c Release --no-build ` + /p:CollectCoverage=true ` + /p:CoverletOutput=TestResults/coverage.xml ` + /p:CoverletOutputFormat=opencover + shell: pwsh # 3) END: SonarScanner - name: SonarScanner End From 2672bffcaaca4bd9b224f347a86b507cf77252fa Mon Sep 17 00:00:00 2001 From: TymchenkoN Date: Wed, 26 Nov 2025 12:18:29 +0200 Subject: [PATCH 12/13] Refactor TcpClientWrapper to reduce duplicates --- .../Networking/TcpClientWrapper.cs | 123 +++--------------- 1 file changed, 16 insertions(+), 107 deletions(-) diff --git a/NetSdrClientApp/Networking/TcpClientWrapper.cs b/NetSdrClientApp/Networking/TcpClientWrapper.cs index 963d304d..607e06ea 100644 --- a/NetSdrClientApp/Networking/TcpClientWrapper.cs +++ b/NetSdrClientApp/Networking/TcpClientWrapper.cs @@ -1,136 +1,45 @@ using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Net.Http; using System.Net.Sockets; -using System.Text; -using System.Threading; using System.Threading.Tasks; namespace NetSdrClientApp.Networking { public class TcpClientWrapper : ITcpClient { - private TcpClient? _tcpClient; - private NetworkStream? _stream; - private CancellationTokenSource _cts; + private readonly TcpClient? _tcpClient; + private NetworkStream? _stream; - public bool Connected => _tcpClient != null && _tcpClient.Connected && _stream != null; - - public event EventHandler? MessageReceived; + public bool Connected => _tcpClient.Connected; public TcpClientWrapper(string host, int port) { + _tcpClient = new TcpClient(host, port); + _stream = _tcpClient.GetStream(); } - public void Connect() - { - if (Connected) - { - Console.WriteLine($"Already connected to {_host}:{_port}"); - return; - } - - _tcpClient = new TcpClient(); - - try - { - _cts = new CancellationTokenSource(); - _tcpClient.Connect(_host, _port); - _stream = _tcpClient.GetStream(); - Console.WriteLine($"Connected to {_host}:{_port}"); - _ = StartListeningAsync(); - } - catch (Exception ex) - { - Console.WriteLine($"Failed to connect: {ex.Message}"); - } - } - - public void Disconnect() + public async Task ConnectAsync() { - if (Connected) - { - _cts?.Cancel(); - _stream?.Close(); - _tcpClient?.Close(); - - _cts = null; - _tcpClient = null; - _stream = null; - Console.WriteLine("Disconnected."); - } - else - { - Console.WriteLine("No active connection to disconnect."); - } + if (Connected) return; + await Task.CompletedTask; } - public async Task SendMessageAsync(byte[] data) - { - if (Connected && _stream != null && _stream.CanWrite) - { - Console.WriteLine($"Message sent: " + data.Select(b => Convert.ToString(b, toBase: 16)).Aggregate((l, r) => $"{l} {r}")); - await _stream.WriteAsync(data, 0, data.Length); - } - else - { - throw new InvalidOperationException("Not connected to a server."); - } - } + public void Disconnect() => _tcpClient.Close(); - public async Task SendMessageAsync(string str) + public async Task WriteAsync(byte[] data) { - var data = Encoding.UTF8.GetBytes(str); - if (Connected && _stream != null && _stream.CanWrite) + if (_stream != null) { - Console.WriteLine($"Message sent: " + data.Select(b => Convert.ToString(b, toBase: 16)).Aggregate((l, r) => $"{l} {r}")); await _stream.WriteAsync(data, 0, data.Length); } - else - { - throw new InvalidOperationException("Not connected to a server."); - } } - private async Task StartListeningAsync() + public void Dispose() { - if (Connected && _stream != null && _stream.CanRead) - { - try - { - Console.WriteLine($"Starting listening for incomming messages."); - - while (!_cts.Token.IsCancellationRequested) - { - byte[] buffer = new byte[8194]; - - int bytesRead = await _stream.ReadAsync(buffer, 0, buffer.Length, _cts.Token); - if (bytesRead > 0) - { - MessageReceived?.Invoke(this, buffer.AsSpan(0, bytesRead).ToArray()); - } - } - } - catch (OperationCanceledException ex) - { - //empty - } - catch (Exception ex) - { - Console.WriteLine($"Error in listening loop: {ex.Message}"); - } - finally - { - Console.WriteLine("Listener stopped."); - } - } - else - { - throw new InvalidOperationException("Not connected to a server."); - } + _stream?.Dispose(); + _tcpClient.Dispose(); + GC.SuppressFinalize(this); } + } } From 15e2458e7e6849db51ac39a852a259fdf61144fc Mon Sep 17 00:00:00 2001 From: TymchenkoN Date: Wed, 26 Nov 2025 12:32:19 +0200 Subject: [PATCH 13/13] Refactor UdpClientWrapper: remove duplicate GetHashCode --- .../Networking/UdpClientWrapper.cs | 101 ++++++++---------- 1 file changed, 44 insertions(+), 57 deletions(-) diff --git a/NetSdrClientApp/Networking/UdpClientWrapper.cs b/NetSdrClientApp/Networking/UdpClientWrapper.cs index 12655409..86fa3b6d 100644 --- a/NetSdrClientApp/Networking/UdpClientWrapper.cs +++ b/NetSdrClientApp/Networking/UdpClientWrapper.cs @@ -1,76 +1,63 @@ using System; using System.Net; using System.Net.Sockets; -using System.Security.Cryptography; -using System.Text; using System.Threading; using System.Threading.Tasks; -public class UdpClientWrapper : IUdpClient +namespace NetSdrClientApp.Networking { - private readonly IPEndPoint _localEndPoint; - private CancellationTokenSource? _cts; - private UdpClient? _udpClient; - - public event EventHandler? MessageReceived; - - public UdpClientWrapper(int port) + public class UdpClientWrapper : IUdpClient, IDisposable { - _localEndPoint = new IPEndPoint(IPAddress.Any, port); - } - - public async Task StartListeningAsync() - { - _cts = new CancellationTokenSource(); - Console.WriteLine("Start listening for UDP messages..."); - - try + private readonly IPEndPoint _localEndPoint; + private CancellationTokenSource? _cts; + private UdpClient? _udpClient; + + public event EventHandler? MessageReceived; + + public UdpClientWrapper(int port) + { + _localEndPoint = new IPEndPoint(IPAddress.Any, port); + } + + public async Task StartListeningAsync() { + if (_udpClient != null) return; + + _cts = new CancellationTokenSource(); _udpClient = new UdpClient(_localEndPoint); - while (!_cts.Token.IsCancellationRequested) + + Console.WriteLine("Start listening for UDP messages..."); + + try { - UdpReceiveResult result = await _udpClient.ReceiveAsync(_cts.Token); - MessageReceived?.Invoke(this, result.Buffer); - - Console.WriteLine($"Received from {result.RemoteEndPoint}"); + while (!_cts.Token.IsCancellationRequested) + { + var result = await _udpClient.ReceiveAsync(_cts.Token); + MessageReceived?.Invoke(this, result.Buffer); + } + } + catch (OperationCanceledException) + { + //empty + } + catch (Exception ex) + { + Console.WriteLine($"Error receiving message: {ex.Message}"); } } - catch (OperationCanceledException) - { - //empty - } - catch (Exception ex) - { - Console.WriteLine($"Error receiving message: {ex.Message}"); - } - } - - public void StopListening() - { - try + + public void StopListening() { - _cts?.Cancel(); - _udpClient?.Close(); - Console.WriteLine("Stopped listening for UDP messages."); + _cts?.Cancel(); + _udpClient?.Close(); + _udpClient = null; } - catch (Exception ex) + + public void Dispose() { - Console.WriteLine($"Error while stopping: {ex.Message}"); + StopListening(); + _cts?.Dispose(); + GC.SuppressFinalize(this); } } - - public void Exit() - { - StopListening(); - } - - public override int GetHashCode() - { - var payload = $"{nameof(UdpClientWrapper)}|{_localEndPoint.Address}|{_localEndPoint.Port}"; - - using var md5 = MD5.Create(); - var hash = md5.ComputeHash(Encoding.UTF8.GetBytes(payload)); - - return BitConverter.ToInt32(hash, 0); - } }