From bf78b7488e57fb562f22b83701d4622941f45865 Mon Sep 17 00:00:00 2001 From: DangerKrip Date: Thu, 30 Apr 2026 21:52:27 +0300 Subject: [PATCH 01/40] Lab-1. Changed project and organization keys in sonarcloud.yml. --- .github/workflows/sonarcloud.yml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/sonarcloud.yml b/.github/workflows/sonarcloud.yml index e784069..e543a8a 100644 --- a/.github/workflows/sonarcloud.yml +++ b/.github/workflows/sonarcloud.yml @@ -30,9 +30,9 @@ name: SonarCloud analysis on: push: - branches: [ "master" ] + branches: ["master"] pull_request: - branches: [ "master" ] + branches: ["master"] workflow_dispatch: permissions: @@ -41,14 +41,14 @@ permissions: jobs: sonar-check: name: Sonar Check - runs-on: windows-latest # безпечно для будь-яких .NET проектів + runs-on: windows-latest # безпечно для будь-яких .NET проектів steps: - uses: actions/checkout@v4 with: { fetch-depth: 0 } - uses: actions/setup-dotnet@v4 with: - dotnet-version: '8.0.x' + dotnet-version: "8.0.x" # 1) BEGIN: SonarScanner for .NET - name: SonarScanner Begin @@ -56,8 +56,8 @@ jobs: dotnet tool install --global dotnet-sonarscanner echo "$env:USERPROFILE\.dotnet\tools" >> $env:GITHUB_PATH dotnet sonarscanner begin ` - /k:"ppanchen_NetSdrClient" ` - /o:"ppanchen" ` + /k:"KonstantinDanger_reengineering-course-fork2" ` + /o:"konstantindanger" ` /d:sonar.token="${{ secrets.SONAR_TOKEN }}" ` /d:sonar.cs.opencover.reportsPaths="**/coverage.xml" ` /d:sonar.cpd.cs.minimumTokens=40 ` From 5a148d9fd36ed0e73d0e8e62f9385cff464e9162 Mon Sep 17 00:00:00 2001 From: DangerKrip Date: Thu, 30 Apr 2026 23:40:16 +0300 Subject: [PATCH 02/40] Lab-1. Modified .yaml file. --- .github/workflows/sonarcloud.yml | 9 --------- 1 file changed, 9 deletions(-) diff --git a/.github/workflows/sonarcloud.yml b/.github/workflows/sonarcloud.yml index e543a8a..b7104e9 100644 --- a/.github/workflows/sonarcloud.yml +++ b/.github/workflows/sonarcloud.yml @@ -1,12 +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 From fc2bcdf6b488197ba59b280353950b8538306b68 Mon Sep 17 00:00:00 2001 From: DangerKrip Date: Fri, 1 May 2026 16:01:51 +0300 Subject: [PATCH 03/40] Added build config --- .github/workflows/build.yml | 47 +++++++++++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) create mode 100644 .github/workflows/build.yml diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000..1165846 --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,47 @@ +name: SonarQube +on: + push: + branches: + - master + pull_request: + types: [opened, synchronize, reopened] +jobs: + build: + name: Build and analyze + runs-on: windows-latest + steps: + - name: Set up JDK 17 + uses: actions/setup-java@c1e323688fd81a25caa38c78aa6df2d33d3e20d9 # v4.8.0 + with: + java-version: 17 + distribution: "zulu" # Alternative distribution options are available. + - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 + with: + fetch-depth: 0 # Shallow clones should be disabled for a better relevancy of analysis + - name: Cache SonarQube Cloud packages + uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0 + with: + path: ~\sonar\cache + key: ${{ runner.os }}-sonar + restore-keys: ${{ runner.os }}-sonar + - name: Cache SonarQube Cloud scanner + id: cache-sonar-scanner + uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0 + with: + path: ${{ runner.temp }}\scanner + key: ${{ runner.os }}-sonar-scanner + restore-keys: ${{ runner.os }}-sonar-scanner + - name: Install SonarQube Cloud scanner + if: steps.cache-sonar-scanner.outputs.cache-hit != 'true' + shell: powershell + run: | + New-Item -Path ${{ runner.temp }}\scanner -ItemType Directory + dotnet tool update dotnet-sonarscanner --tool-path ${{ runner.temp }}\scanner + - name: Build and analyze + env: + SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} + shell: powershell + run: | + ${{ runner.temp }}\scanner\dotnet-sonarscanner begin /k:"KonstantinDanger_reengineering-course-fork2" /o:"konstantindanger" /d:sonar.token="${{ secrets.SONAR_TOKEN }}" + dotnet build + ${{ runner.temp }}\scanner\dotnet-sonarscanner end /d:sonar.token="${{ secrets.SONAR_TOKEN }}" From 6e4ae91496bf8ad40ece4233242ac0256d09c5ef Mon Sep 17 00:00:00 2001 From: DangerKrip Date: Fri, 1 May 2026 16:06:01 +0300 Subject: [PATCH 04/40] Modified config --- .github/workflows/sonarcloud.yml | 19 ------------------- 1 file changed, 19 deletions(-) diff --git a/.github/workflows/sonarcloud.yml b/.github/workflows/sonarcloud.yml index b7104e9..cae3cb2 100644 --- a/.github/workflows/sonarcloud.yml +++ b/.github/workflows/sonarcloud.yml @@ -1,22 +1,3 @@ -# 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: From ea779f668dc39f3665b7968646b5d81383efaf0b Mon Sep 17 00:00:00 2001 From: KonstantinDanger <106324015+KonstantinDanger@users.noreply.github.com> Date: Fri, 1 May 2026 16:24:42 +0300 Subject: [PATCH 05/40] Update README.md badges --- README.md | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/README.md b/README.md index b3a9029..ace6cce 100644 --- a/README.md +++ b/README.md @@ -1,12 +1,5 @@ # Лабораторні з реінжинірингу (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) -[![Bugs](https://sonarcloud.io/api/project_badges/measure?project=ppanchen_NetSdrClient&metric=bugs)](https://sonarcloud.io/summary/new_code?id=ppanchen_NetSdrClient) -[![Code Smells](https://sonarcloud.io/api/project_badges/measure?project=ppanchen_NetSdrClient&metric=code_smells)](https://sonarcloud.io/summary/new_code?id=ppanchen_NetSdrClient) -[![Vulnerabilities](https://sonarcloud.io/api/project_badges/measure?project=ppanchen_NetSdrClient&metric=vulnerabilities)](https://sonarcloud.io/summary/new_code?id=ppanchen_NetSdrClient) -[![Duplicated Lines (%)](https://sonarcloud.io/api/project_badges/measure?project=ppanchen_NetSdrClient&metric=duplicated_lines_density)](https://sonarcloud.io/summary/new_code?id=ppanchen_NetSdrClient) -[![Security Rating](https://sonarcloud.io/api/project_badges/measure?project=ppanchen_NetSdrClient&metric=security_rating)](https://sonarcloud.io/summary/new_code?id=ppanchen_NetSdrClient) -[![Maintainability Rating](https://sonarcloud.io/api/project_badges/measure?project=ppanchen_NetSdrClient&metric=sqale_rating)](https://sonarcloud.io/summary/new_code?id=ppanchen_NetSdrClient) +[![Quality Gate Status](https://sonarcloud.io/api/project_badges/measure?project=KonstantinDanger_reengineering-course-fork2&metric=alert_status)](https://sonarcloud.io/summary/new_code?id=KonstantinDanger_reengineering-course-fork2) Цей репозиторій використовується для курсу **реінжиніринг ПЗ**. From 7cd02eba1ecb06acf1d87e19b06859223c1a753f Mon Sep 17 00:00:00 2001 From: DangerKrip Date: Fri, 1 May 2026 22:41:54 +0300 Subject: [PATCH 06/40] Made _cancellationTokenSource readonly --- EchoTcpServer/Program.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/EchoTcpServer/Program.cs b/EchoTcpServer/Program.cs index 5966c57..fab6e1f 100644 --- a/EchoTcpServer/Program.cs +++ b/EchoTcpServer/Program.cs @@ -13,7 +13,7 @@ public class EchoServer { private readonly int _port; private TcpListener _listener; - private CancellationTokenSource _cancellationTokenSource; + private readonly CancellationTokenSource _cancellationTokenSource; public EchoServer(int port) From 43acbc8b7772d45202761570bb7fc0859c410059 Mon Sep 17 00:00:00 2001 From: DangerKrip Date: Fri, 1 May 2026 22:45:44 +0300 Subject: [PATCH 07/40] Declared EchoServer type in corresponding namespace --- EchoTcpServer/Program.cs | 252 ++++++++++++++++++++------------------- 1 file changed, 128 insertions(+), 124 deletions(-) diff --git a/EchoTcpServer/Program.cs b/EchoTcpServer/Program.cs index fab6e1f..d411eb0 100644 --- a/EchoTcpServer/Program.cs +++ b/EchoTcpServer/Program.cs @@ -5,169 +5,173 @@ using System.Threading; using System.Threading.Tasks; -/// -/// This program was designed for test purposes only -/// Not for a review -/// -public class EchoServer +namespace EchoServer { - private readonly int _port; - private TcpListener _listener; - private readonly CancellationTokenSource _cancellationTokenSource; - - public EchoServer(int port) + /// + /// This program was designed for test purposes only + /// Not for a review + /// + public class EchoServer { - _port = port; - _cancellationTokenSource = new CancellationTokenSource(); - } + private readonly int _port; + private TcpListener _listener; + private readonly CancellationTokenSource _cancellationTokenSource; - public async Task StartAsync() - { - _listener = new TcpListener(IPAddress.Any, _port); - _listener.Start(); - Console.WriteLine($"Server started on port {_port}."); - while (!_cancellationTokenSource.Token.IsCancellationRequested) + public EchoServer(int port) { - try - { - TcpClient client = await _listener.AcceptTcpClientAsync(); - Console.WriteLine("Client connected."); - - _ = Task.Run(() => HandleClientAsync(client, _cancellationTokenSource.Token)); - } - catch (ObjectDisposedException) - { - // Listener has been closed - break; - } + _port = port; + _cancellationTokenSource = new CancellationTokenSource(); } - Console.WriteLine("Server shutdown."); - } - - private async Task HandleClientAsync(TcpClient client, CancellationToken token) - { - using (NetworkStream stream = client.GetStream()) + public async Task StartAsync() { - try + _listener = new TcpListener(IPAddress.Any, _port); + _listener.Start(); + Console.WriteLine($"Server started on port {_port}."); + + while (!_cancellationTokenSource.Token.IsCancellationRequested) { - byte[] buffer = new byte[8192]; - int bytesRead; + try + { + TcpClient client = await _listener.AcceptTcpClientAsync(); + Console.WriteLine("Client connected."); - while (!token.IsCancellationRequested && (bytesRead = await stream.ReadAsync(buffer, 0, buffer.Length, token)) > 0) + _ = Task.Run(() => HandleClientAsync(client, _cancellationTokenSource.Token)); + } + catch (ObjectDisposedException) { - // Echo back the received message - await stream.WriteAsync(buffer, 0, bytesRead, token); - Console.WriteLine($"Echoed {bytesRead} bytes to the client."); + // Listener has been closed + break; } } - catch (Exception ex) when (!(ex is OperationCanceledException)) - { - Console.WriteLine($"Error: {ex.Message}"); - } - finally + + Console.WriteLine("Server shutdown."); + } + + private async Task HandleClientAsync(TcpClient client, CancellationToken token) + { + using (NetworkStream stream = client.GetStream()) { - client.Close(); - Console.WriteLine("Client disconnected."); + try + { + byte[] buffer = new byte[8192]; + int bytesRead; + + while (!token.IsCancellationRequested && (bytesRead = await stream.ReadAsync(buffer, 0, buffer.Length, token)) > 0) + { + // Echo back the received message + await stream.WriteAsync(buffer, 0, bytesRead, token); + Console.WriteLine($"Echoed {bytesRead} bytes to the client."); + } + } + catch (Exception ex) when (!(ex is OperationCanceledException)) + { + Console.WriteLine($"Error: {ex.Message}"); + } + finally + { + client.Close(); + Console.WriteLine("Client disconnected."); + } } } - } - public void Stop() - { - _cancellationTokenSource.Cancel(); - _listener.Stop(); - _cancellationTokenSource.Dispose(); - Console.WriteLine("Server stopped."); - } - - public static async Task Main(string[] args) - { - EchoServer server = new EchoServer(5000); + public void Stop() + { + _cancellationTokenSource.Cancel(); + _listener.Stop(); + _cancellationTokenSource.Dispose(); + Console.WriteLine("Server stopped."); + } - // Start the server in a separate task - _ = Task.Run(() => server.StartAsync()); + public static async Task Main(string[] args) + { + EchoServer server = new EchoServer(5000); - string host = "127.0.0.1"; // Target IP - int port = 60000; // Target Port - int intervalMilliseconds = 5000; // Send every 3 seconds + // Start the server in a separate task + _ = Task.Run(() => server.StartAsync()); - using (var sender = new UdpTimedSender(host, port)) - { - Console.WriteLine("Press any key to stop sending..."); - sender.StartSending(intervalMilliseconds); + string host = "127.0.0.1"; // Target IP + int port = 60000; // Target Port + int intervalMilliseconds = 5000; // Send every 3 seconds - Console.WriteLine("Press 'q' to quit..."); - while (Console.ReadKey(intercept: true).Key != ConsoleKey.Q) + using (var sender = new UdpTimedSender(host, port)) { - // Just wait until 'q' is pressed - } + Console.WriteLine("Press any key to stop sending..."); + sender.StartSending(intervalMilliseconds); - sender.StopSending(); - server.Stop(); - Console.WriteLine("Sender stopped."); + Console.WriteLine("Press 'q' to quit..."); + while (Console.ReadKey(intercept: true).Key != ConsoleKey.Q) + { + // Just wait until 'q' is pressed + } + + sender.StopSending(); + server.Stop(); + Console.WriteLine("Sender stopped."); + } } } -} -public class UdpTimedSender : IDisposable -{ - private readonly string _host; - private readonly int _port; - private readonly UdpClient _udpClient; - private Timer _timer; - - public UdpTimedSender(string host, int port) + public class UdpTimedSender : IDisposable { - _host = host; - _port = port; - _udpClient = new UdpClient(); - } + private readonly string _host; + private readonly int _port; + private readonly UdpClient _udpClient; + private Timer _timer; - public void StartSending(int intervalMilliseconds) - { - if (_timer != null) - throw new InvalidOperationException("Sender is already running."); + public UdpTimedSender(string host, int port) + { + _host = host; + _port = port; + _udpClient = new UdpClient(); + } - _timer = new Timer(SendMessageCallback, null, 0, intervalMilliseconds); - } + public void StartSending(int intervalMilliseconds) + { + if (_timer != null) + throw new InvalidOperationException("Sender is already running."); - ushort i = 0; + _timer = new Timer(SendMessageCallback, null, 0, intervalMilliseconds); + } - private void SendMessageCallback(object state) - { - try + ushort i = 0; + + private void SendMessageCallback(object state) { - //dummy data - Random rnd = new Random(); - byte[] samples = new byte[1024]; - rnd.NextBytes(samples); - i++; + try + { + //dummy data + Random rnd = new Random(); + byte[] samples = new byte[1024]; + rnd.NextBytes(samples); + i++; - byte[] msg = (new byte[] { 0x04, 0x84 }).Concat(BitConverter.GetBytes(i)).Concat(samples).ToArray(); - var endpoint = new IPEndPoint(IPAddress.Parse(_host), _port); + byte[] msg = (new byte[] { 0x04, 0x84 }).Concat(BitConverter.GetBytes(i)).Concat(samples).ToArray(); + var endpoint = new IPEndPoint(IPAddress.Parse(_host), _port); - _udpClient.Send(msg, msg.Length, endpoint); - Console.WriteLine($"Message sent to {_host}:{_port} "); + _udpClient.Send(msg, msg.Length, endpoint); + Console.WriteLine($"Message sent to {_host}:{_port} "); + } + catch (Exception ex) + { + Console.WriteLine($"Error sending message: {ex.Message}"); + } } - catch (Exception ex) + + public void StopSending() { - Console.WriteLine($"Error sending message: {ex.Message}"); + _timer?.Dispose(); + _timer = null; } - } - - public void StopSending() - { - _timer?.Dispose(); - _timer = null; - } - public void Dispose() - { - StopSending(); - _udpClient.Dispose(); + public void Dispose() + { + StopSending(); + _udpClient.Dispose(); + } } } \ No newline at end of file From a6a123b8a15eedf3253869aa746ecff4cf853499 Mon Sep 17 00:00:00 2001 From: DangerKrip Date: Fri, 1 May 2026 22:57:18 +0300 Subject: [PATCH 08/40] Used proper constructor overload of ArgumentOutOfRangeException --- NetSdrClientApp/Messages/NetSdrMessageHelper.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/NetSdrClientApp/Messages/NetSdrMessageHelper.cs b/NetSdrClientApp/Messages/NetSdrMessageHelper.cs index 0d69b4d..0ea0982 100644 --- a/NetSdrClientApp/Messages/NetSdrMessageHelper.cs +++ b/NetSdrClientApp/Messages/NetSdrMessageHelper.cs @@ -111,7 +111,7 @@ public static IEnumerable GetSamples(ushort sampleSize, byte[] body) sampleSize /= 8; //to bytes if (sampleSize > 4) { - throw new ArgumentOutOfRangeException(); + throw new ArgumentOutOfRangeException(sampleSize.ToString(), "sample size was bigger then 4"); } var bodyEnumerable = body as IEnumerable; From 11d004126dc56aebbfedf8465bb80918ca331be7 Mon Sep 17 00:00:00 2001 From: DangerKrip Date: Fri, 1 May 2026 22:59:32 +0300 Subject: [PATCH 09/40] Made _tcpClient readonly --- NetSdrClientApp/NetSdrClient.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/NetSdrClientApp/NetSdrClient.cs b/NetSdrClientApp/NetSdrClient.cs index b0a7c05..cbe55b0 100644 --- a/NetSdrClientApp/NetSdrClient.cs +++ b/NetSdrClientApp/NetSdrClient.cs @@ -14,7 +14,7 @@ namespace NetSdrClientApp { public class NetSdrClient { - private ITcpClient _tcpClient; + private readonly ITcpClient _tcpClient; private IUdpClient _udpClient; public bool IQStarted { get; set; } From 08afc52249e774ffe8875988957758ca74dd4252 Mon Sep 17 00:00:00 2001 From: DangerKrip Date: Fri, 1 May 2026 23:00:07 +0300 Subject: [PATCH 10/40] Made _udpClient readonly --- NetSdrClientApp/NetSdrClient.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/NetSdrClientApp/NetSdrClient.cs b/NetSdrClientApp/NetSdrClient.cs index cbe55b0..c4908a6 100644 --- a/NetSdrClientApp/NetSdrClient.cs +++ b/NetSdrClientApp/NetSdrClient.cs @@ -15,7 +15,7 @@ namespace NetSdrClientApp public class NetSdrClient { private readonly ITcpClient _tcpClient; - private IUdpClient _udpClient; + private readonly IUdpClient _udpClient; public bool IQStarted { get; set; } From 6ef295c233f270ffcee827d192df6c8204d93dd1 Mon Sep 17 00:00:00 2001 From: DangerKrip Date: Fri, 1 May 2026 23:02:28 +0300 Subject: [PATCH 11/40] Removed empty statement --- NetSdrClientApp/NetSdrClient.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/NetSdrClientApp/NetSdrClient.cs b/NetSdrClientApp/NetSdrClient.cs index c4908a6..dbf6cb8 100644 --- a/NetSdrClientApp/NetSdrClient.cs +++ b/NetSdrClientApp/NetSdrClient.cs @@ -66,7 +66,7 @@ public async Task StartIQAsync() return; } -; var iqDataMode = (byte)0x80; + var iqDataMode = (byte)0x80; var start = (byte)0x02; var fifo16bitCaptureMode = (byte)0x01; var n = (byte)1; From 001043771a02098dbd53f804e757943068ec7044 Mon Sep 17 00:00:00 2001 From: DangerKrip Date: Fri, 1 May 2026 23:05:50 +0300 Subject: [PATCH 12/40] Marked _udpClient_MessageReceived method as static --- NetSdrClientApp/NetSdrClient.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/NetSdrClientApp/NetSdrClient.cs b/NetSdrClientApp/NetSdrClient.cs index dbf6cb8..0c2cf22 100644 --- a/NetSdrClientApp/NetSdrClient.cs +++ b/NetSdrClientApp/NetSdrClient.cs @@ -114,7 +114,7 @@ public async Task ChangeFrequencyAsync(long hz, int channel) await SendTcpRequest(msg); } - private void _udpClient_MessageReceived(object? sender, byte[] e) + private static void _udpClient_MessageReceived(object? sender, byte[] e) { NetSdrMessageHelper.TranslateMessage(e, out MsgTypes type, out ControlItemCodes code, out ushort sequenceNum, out byte[] body); var samples = NetSdrMessageHelper.GetSamples(16, body); From 27c0d71e4fd39a001dd9b5d3bf090d73edded909 Mon Sep 17 00:00:00 2001 From: DangerKrip Date: Fri, 1 May 2026 23:11:08 +0300 Subject: [PATCH 13/40] Fixed possible null reference return --- NetSdrClientApp/NetSdrClient.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/NetSdrClientApp/NetSdrClient.cs b/NetSdrClientApp/NetSdrClient.cs index 0c2cf22..daacab3 100644 --- a/NetSdrClientApp/NetSdrClient.cs +++ b/NetSdrClientApp/NetSdrClient.cs @@ -138,7 +138,7 @@ private async Task SendTcpRequest(byte[] msg) if (!_tcpClient.Connected) { Console.WriteLine("No active connection."); - return null; + return []; } responseTaskSource = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); From 699382ba42c52315f342b5cd1d93d394d213bf56 Mon Sep 17 00:00:00 2001 From: DangerKrip Date: Fri, 1 May 2026 23:13:49 +0300 Subject: [PATCH 14/40] Fixed null literal to non-nullable reference type conversion --- NetSdrClientApp/NetSdrClient.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/NetSdrClientApp/NetSdrClient.cs b/NetSdrClientApp/NetSdrClient.cs index daacab3..d153baf 100644 --- a/NetSdrClientApp/NetSdrClient.cs +++ b/NetSdrClientApp/NetSdrClient.cs @@ -157,7 +157,7 @@ private void _tcpClient_MessageReceived(object? sender, byte[] e) if (responseTaskSource != null) { responseTaskSource.SetResult(e); - responseTaskSource = null; + responseTaskSource = new TaskCompletionSource(); } Console.WriteLine("Response recieved: " + e.Select(b => Convert.ToString(b, toBase: 16)).Aggregate((l, r) => $"{l} {r}")); } From 288103fed85789f25d4e608792a4fa6066976667 Mon Sep 17 00:00:00 2001 From: DangerKrip Date: Sat, 2 May 2026 19:54:13 +0300 Subject: [PATCH 15/40] Added 5 additional unit tests --- .github/workflows/sonarcloud.yml | 14 ++-- .../NetSdrClientAppTests.csproj | 6 +- NetSdrClientAppTests/NetSdrClientTests.cs | 68 ++++++++++++++++++- 3 files changed, 79 insertions(+), 9 deletions(-) diff --git a/.github/workflows/sonarcloud.yml b/.github/workflows/sonarcloud.yml index cae3cb2..0edf275 100644 --- a/.github/workflows/sonarcloud.yml +++ b/.github/workflows/sonarcloud.yml @@ -42,13 +42,13 @@ jobs: 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 + - 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 }}" diff --git a/NetSdrClientAppTests/NetSdrClientAppTests.csproj b/NetSdrClientAppTests/NetSdrClientAppTests.csproj index 3cbc46a..74c896e 100644 --- a/NetSdrClientAppTests/NetSdrClientAppTests.csproj +++ b/NetSdrClientAppTests/NetSdrClientAppTests.csproj @@ -11,7 +11,11 @@ - + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + diff --git a/NetSdrClientAppTests/NetSdrClientTests.cs b/NetSdrClientAppTests/NetSdrClientTests.cs index ad00c4f..082cb2b 100644 --- a/NetSdrClientAppTests/NetSdrClientTests.cs +++ b/NetSdrClientAppTests/NetSdrClientTests.cs @@ -115,5 +115,71 @@ public async Task StopIQTest() Assert.That(_client.IQStarted, Is.False); } - //TODO: cover the rest of the NetSdrClient code here + [Test] + public async Task ChangeFrequencyTest() + { + //arrange + await _client.ConnectAsync(); + _tcpMock.Invocations.Clear(); + + //act + await _client.ChangeFrequencyAsync(100_000_000L, channel: 1); + + //assert + _tcpMock.Verify(tcp => tcp.SendMessageAsync(It.IsAny()), Times.Once); + } + + [Test] + public async Task StopIQNoConnectionTest() + { + //act + await _client.StopIQAsync(); + + //assert + _tcpMock.Verify(tcp => tcp.SendMessageAsync(It.IsAny()), Times.Never); + _updMock.Verify(udp => udp.StopListening(), Times.Never); + Assert.That(_client.IQStarted, Is.False); + } + + [Test] + public async Task StartIQSendsTcpMessageTest() + { + //arrange + await _client.ConnectAsync(); + _tcpMock.Invocations.Clear(); + + //act + await _client.StartIQAsync(); + + //assert + _tcpMock.Verify(tcp => tcp.SendMessageAsync(It.IsAny()), Times.Once); + } + + [Test] + public async Task StopIQSendsTcpMessageTest() + { + //arrange + await _client.ConnectAsync(); + _tcpMock.Invocations.Clear(); + + //act + await _client.StopIQAsync(); + + //assert + _tcpMock.Verify(tcp => tcp.SendMessageAsync(It.IsAny()), Times.Once); + } + + [Test] + public async Task StopIQWithoutStartTest() + { + //arrange + await _client.ConnectAsync(); + + //act + await _client.StopIQAsync(); + + //assert + Assert.That(_client.IQStarted, Is.False); + _updMock.Verify(udp => udp.StopListening(), Times.Once); + } } From 5c5a0cc81a5cabfcc3eb9fe367ebe8462f4ec8ab Mon Sep 17 00:00:00 2001 From: DangerKrip Date: Sun, 3 May 2026 00:12:52 +0300 Subject: [PATCH 16/40] Added more tests to gain bigger coverage percentage --- NetSdrClientAppTests/NetSdrClientTests.cs | 55 +++++++++ .../NetSdrMessageHelperTests.cs | 112 +++++++++++++++++- 2 files changed, 166 insertions(+), 1 deletion(-) diff --git a/NetSdrClientAppTests/NetSdrClientTests.cs b/NetSdrClientAppTests/NetSdrClientTests.cs index 082cb2b..6a12682 100644 --- a/NetSdrClientAppTests/NetSdrClientTests.cs +++ b/NetSdrClientAppTests/NetSdrClientTests.cs @@ -182,4 +182,59 @@ public async Task StopIQWithoutStartTest() Assert.That(_client.IQStarted, Is.False); _updMock.Verify(udp => udp.StopListening(), Times.Once); } + + [Test] + public async Task ChangeFrequencyNoConnectionTest() + { + //act + await _client.ChangeFrequencyAsync(100_000_000L, channel: 1); + + //assert + _tcpMock.Verify(tcp => tcp.SendMessageAsync(It.IsAny()), Times.Never); + } + + [Test] + public async Task ConnectAsyncAlreadyConnectedTest() + { + //arrange + await _client.ConnectAsync(); + _tcpMock.Invocations.Clear(); + + //act + await _client.ConnectAsync(); + + //assert + _tcpMock.Verify(tcp => tcp.Connect(), Times.Never); + _tcpMock.Verify(tcp => tcp.SendMessageAsync(It.IsAny()), Times.Never); + } + + [Test] + public async Task StartThenStopIQTogglesIQStartedTest() + { + //arrange + await _client.ConnectAsync(); + + //act + await _client.StartIQAsync(); + Assert.That(_client.IQStarted, Is.True); + + await _client.StopIQAsync(); + Assert.That(_client.IQStarted, Is.False); + } + + [Test] + public async Task StartIQTwiceDoesNotStartListeningTwiceTest() + { + //arrange + await _client.ConnectAsync(); + await _client.StartIQAsync(); + _updMock.Invocations.Clear(); + + //act + await _client.StartIQAsync(); + + //assert + Assert.That(_client.IQStarted, Is.True); + _updMock.Verify(udp => udp.StartListeningAsync(), Times.Once); + } } diff --git a/NetSdrClientAppTests/NetSdrMessageHelperTests.cs b/NetSdrClientAppTests/NetSdrMessageHelperTests.cs index b40fff7..9fdb96f 100644 --- a/NetSdrClientAppTests/NetSdrMessageHelperTests.cs +++ b/NetSdrClientAppTests/NetSdrMessageHelperTests.cs @@ -64,6 +64,116 @@ public void GetDataItemMessageTest() Assert.That(parametersBytes.Count(), Is.EqualTo(parametersLength)); } - //TODO: add more NetSdrMessageHelper tests + [Test] + public void GetControlItemMessage_EmptyParameters_ProducesMinimalMessage() + { + // Arrange + var type = NetSdrMessageHelper.MsgTypes.SetControlItem; + var code = NetSdrMessageHelper.ControlItemCodes.ReceiverState; + + // Act + byte[] msg = NetSdrMessageHelper.GetControlItemMessage(type, code, Array.Empty()); + + // Assert: 2 header bytes + 2 code bytes = 4 + Assert.That(msg.Length, Is.EqualTo(4)); + } + + [Test] + public void GetControlItemMessage_TooLargeParameters_ThrowsArgumentException() + { + // Arrange + var type = NetSdrMessageHelper.MsgTypes.SetControlItem; + var code = NetSdrMessageHelper.ControlItemCodes.ReceiverState; + // header(2) + code(2) + params must exceed 8191 + var tooLarge = new byte[8191]; + + // Act / Assert + Assert.Throws( + () => NetSdrMessageHelper.GetControlItemMessage(type, code, tooLarge)); + } + + [Test] + public void GetDataItemMessage_MaxDataItemLength_EncodesZeroInHeader() + { + // Arrange — DataItem edge case: total length == 8194 → header length field must be 0 + var type = NetSdrMessageHelper.MsgTypes.DataItem0; + // 8194 - 2 header bytes = 8192 parameter bytes + var parameters = new byte[8192]; + + // Act + byte[] msg = NetSdrMessageHelper.GetDataItemMessage(type, parameters); + + var raw = BitConverter.ToUInt16(msg.Take(2).ToArray()); + var encodedLength = raw - ((int)type << 13); + + // Assert: the special-case length is encoded as 0 + Assert.That(encodedLength, Is.EqualTo(0)); + Assert.That(msg.Length, Is.EqualTo(8194)); + } + + [Test] + public void GetDataItemMessage_DoesNotContainControlItemCode() + { + // Arrange + var type = NetSdrMessageHelper.MsgTypes.DataItem1; + var parameters = new byte[] { 0xAA, 0xBB, 0xCC }; + + // Act + byte[] msg = NetSdrMessageHelper.GetDataItemMessage(type, parameters); + + // Assert: total = 2 header + 3 params (no 2-byte code in between) + Assert.That(msg.Length, Is.EqualTo(5)); + Assert.That(msg.Skip(2).ToArray(), Is.EqualTo(parameters)); + } + + [Test] + public void TranslateMessage_DataItem_RoundTrip() + { + // Arrange + var type = NetSdrMessageHelper.MsgTypes.DataItem2; + ushort sequenceNumber = 42; + var payload = new byte[] { 0xDE, 0xAD, 0xBE, 0xEF }; + + // Build manually: header + sequence number + payload + byte[] msg = NetSdrMessageHelper.GetDataItemMessage( + type, + BitConverter.GetBytes(sequenceNumber).Concat(payload).ToArray()); + + // Act + bool success = NetSdrMessageHelper.TranslateMessage( + msg, out var actualType, out _, out var actualSeq, out var body); + + // Assert + Assert.That(success, Is.True); + Assert.That(actualType, Is.EqualTo(type)); + Assert.That(actualSeq, Is.EqualTo(sequenceNumber)); + Assert.That(body, Is.EqualTo(payload)); + } + + [Test] + public void GetSamples_16Bit_ExtractsCorrectValues() + { + // Arrange: two 16-bit little-endian samples: 256 (0x00, 0x01) and 512 (0x00, 0x02) + var body = new byte[] { 0x00, 0x01, 0x00, 0x02 }; + + // Act + var samples = NetSdrMessageHelper.GetSamples(16, body).ToList(); + + // Assert + Assert.That(samples.Count, Is.EqualTo(2)); + Assert.That(samples[0], Is.EqualTo(256)); + Assert.That(samples[1], Is.EqualTo(512)); + } + + [Test] + public void GetSamples_OversizedSampleSize_ThrowsArgumentOutOfRangeException() + { + // Arrange + var body = new byte[] { 0x01, 0x02, 0x03, 0x04, 0x05 }; + + // Act / Assert: 40-bit sample size (5 bytes) exceeds the 4-byte cap + Assert.Throws( + () => NetSdrMessageHelper.GetSamples(40, body).ToList()); + } } } \ No newline at end of file From b2cdd6c7fab43c7c84a388bceb2a6a88f512f5d0 Mon Sep 17 00:00:00 2001 From: DangerKrip Date: Mon, 4 May 2026 21:04:57 +0300 Subject: [PATCH 17/40] Removed code duplication --- NetSdrClientApp/Networking/TcpClientWrapper.cs | 13 +++---------- NetSdrClientApp/Networking/UdpClientWrapper.cs | 14 +------------- 2 files changed, 4 insertions(+), 23 deletions(-) diff --git a/NetSdrClientApp/Networking/TcpClientWrapper.cs b/NetSdrClientApp/Networking/TcpClientWrapper.cs index 1f37e2e..8fc25ec 100644 --- a/NetSdrClientApp/Networking/TcpClientWrapper.cs +++ b/NetSdrClientApp/Networking/TcpClientWrapper.cs @@ -86,16 +86,9 @@ public async Task SendMessageAsync(byte[] data) public async Task SendMessageAsync(string str) { - var data = Encoding.UTF8.GetBytes(str); - 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."); - } + byte[] data = Encoding.UTF8.GetBytes(str); + + await SendMessageAsync(data); } private async Task StartListeningAsync() diff --git a/NetSdrClientApp/Networking/UdpClientWrapper.cs b/NetSdrClientApp/Networking/UdpClientWrapper.cs index 31e0b79..f6998a2 100644 --- a/NetSdrClientApp/Networking/UdpClientWrapper.cs +++ b/NetSdrClientApp/Networking/UdpClientWrapper.cs @@ -59,19 +59,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}"); - } - } + public void Exit() => StopListening(); public override int GetHashCode() { From 697bb472b91d754851362d350d0337ffff0349d0 Mon Sep 17 00:00:00 2001 From: DangerKrip Date: Mon, 4 May 2026 22:09:25 +0300 Subject: [PATCH 18/40] Excluded new methods from coverage check --- NetSdrClientApp/Networking/TcpClientWrapper.cs | 3 ++- NetSdrClientApp/Networking/UdpClientWrapper.cs | 2 ++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/NetSdrClientApp/Networking/TcpClientWrapper.cs b/NetSdrClientApp/Networking/TcpClientWrapper.cs index 8fc25ec..6aab5f9 100644 --- a/NetSdrClientApp/Networking/TcpClientWrapper.cs +++ b/NetSdrClientApp/Networking/TcpClientWrapper.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.IO; using System.Linq; using System.Net.Http; @@ -84,6 +85,7 @@ public async Task SendMessageAsync(byte[] data) } } + [ExcludeFromCodeCoverage] public async Task SendMessageAsync(string str) { byte[] data = Encoding.UTF8.GetBytes(str); @@ -129,5 +131,4 @@ private async Task StartListeningAsync() } } } - } diff --git a/NetSdrClientApp/Networking/UdpClientWrapper.cs b/NetSdrClientApp/Networking/UdpClientWrapper.cs index f6998a2..c4a06f9 100644 --- a/NetSdrClientApp/Networking/UdpClientWrapper.cs +++ b/NetSdrClientApp/Networking/UdpClientWrapper.cs @@ -1,4 +1,5 @@ using System; +using System.Diagnostics.CodeAnalysis; using System.Net; using System.Net.Sockets; using System.Security.Cryptography; @@ -59,6 +60,7 @@ public void StopListening() } } + [ExcludeFromCodeCoverage] public void Exit() => StopListening(); public override int GetHashCode() From 59325535f34a6e7cd656e34ebc5192cbd8e15d10 Mon Sep 17 00:00:00 2001 From: DangerKrip Date: Sat, 16 May 2026 23:45:19 +0300 Subject: [PATCH 19/40] Added dependencies and corresponding tests. Made tests to fail on purpose. --- .../Messages/NetSdrMessageHelper.cs | 20 +++------- .../Networking/TcpClientWrapper.cs | 10 ++--- NetSdrClientAppTests/ArchitectureTests.cs | 37 +++++++++++++++++++ .../NetSdrClientAppTests.csproj | 1 + 4 files changed, 46 insertions(+), 22 deletions(-) create mode 100644 NetSdrClientAppTests/ArchitectureTests.cs diff --git a/NetSdrClientApp/Messages/NetSdrMessageHelper.cs b/NetSdrClientApp/Messages/NetSdrMessageHelper.cs index 0ea0982..11beeb0 100644 --- a/NetSdrClientApp/Messages/NetSdrMessageHelper.cs +++ b/NetSdrClientApp/Messages/NetSdrMessageHelper.cs @@ -1,15 +1,11 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Reflection.PortableExecutable; -using System.Text; -using System.Threading.Tasks; +using NetSdrClientApp.Networking; namespace NetSdrClientApp.Messages { //TODO: analyze possible use of [StructLayout] for better performance and readability public static class NetSdrMessageHelper { + public static readonly ITcpClient Client = new TcpClientWrapper("http://localhost/", 3000); private const short _maxMessageLength = 8191; private const short _maxDataItemMessageLength = 8194; private const short _msgHeaderLength = 2; //2 byte, 16 bit @@ -38,15 +34,9 @@ public enum ControlItemCodes ReceiverFrequency = 0x0020 } - public static byte[] GetControlItemMessage(MsgTypes type, ControlItemCodes itemCode, byte[] parameters) - { - return GetMessage(type, itemCode, parameters); - } + public static byte[] GetControlItemMessage(MsgTypes type, ControlItemCodes itemCode, byte[] parameters) => GetMessage(type, itemCode, parameters); - public static byte[] GetDataItemMessage(MsgTypes type, byte[] parameters) - { - return GetMessage(type, ControlItemCodes.None, parameters); - } + public static byte[] GetDataItemMessage(MsgTypes type, byte[] parameters) => GetMessage(type, ControlItemCodes.None, parameters); private static byte[] GetMessage(MsgTypes type, ControlItemCodes itemCode, byte[] parameters) { @@ -109,7 +99,7 @@ public static bool TranslateMessage(byte[] msg, out MsgTypes type, out ControlIt public static IEnumerable GetSamples(ushort sampleSize, byte[] body) { sampleSize /= 8; //to bytes - if (sampleSize > 4) + if (sampleSize > 4) { throw new ArgumentOutOfRangeException(sampleSize.ToString(), "sample size was bigger then 4"); } diff --git a/NetSdrClientApp/Networking/TcpClientWrapper.cs b/NetSdrClientApp/Networking/TcpClientWrapper.cs index 6aab5f9..c46cbc3 100644 --- a/NetSdrClientApp/Networking/TcpClientWrapper.cs +++ b/NetSdrClientApp/Networking/TcpClientWrapper.cs @@ -1,18 +1,14 @@ -using System; -using System.Collections.Generic; +using NetSdrClientApp.Messages; using System.Diagnostics.CodeAnalysis; -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 ITcpClient _client = NetSdrMessageHelper.Client; + private string _host; private int _port; private TcpClient? _tcpClient; diff --git a/NetSdrClientAppTests/ArchitectureTests.cs b/NetSdrClientAppTests/ArchitectureTests.cs new file mode 100644 index 0000000..594cf47 --- /dev/null +++ b/NetSdrClientAppTests/ArchitectureTests.cs @@ -0,0 +1,37 @@ +using NetArchTest.Rules; + +namespace NetSdrClientAppTests; + +public class ArchitectureTests +{ + private readonly string _networkNamespace = "NetSdrClientApp.Networking"; + private readonly string _messagesNamespace = "NetSdrClientApp.Messages"; + + [Test] + public void Messages_ShouldNotDependOn_Networking() + { + var result = Types + .InCurrentDomain() + .That() + .ResideInNamespace(_messagesNamespace) + .ShouldNot() + .HaveDependencyOn(_networkNamespace) + .GetResult(); + + Assert.That(result.IsSuccessful, Is.True); + } + + [Test] + public void Networking_ShouldNotDependOn_Messages() + { + var result = Types + .InCurrentDomain() + .That() + .ResideInNamespace(_networkNamespace) + .ShouldNot() + .HaveDependencyOn(_messagesNamespace) + .GetResult(); + + Assert.That(result.IsSuccessful, Is.True); + } +} diff --git a/NetSdrClientAppTests/NetSdrClientAppTests.csproj b/NetSdrClientAppTests/NetSdrClientAppTests.csproj index 74c896e..d4f7f9b 100644 --- a/NetSdrClientAppTests/NetSdrClientAppTests.csproj +++ b/NetSdrClientAppTests/NetSdrClientAppTests.csproj @@ -17,6 +17,7 @@ + From e0da58b7b943b5ad9e1930cc71b5a52881e86941 Mon Sep 17 00:00:00 2001 From: DangerKrip Date: Sat, 16 May 2026 23:52:29 +0300 Subject: [PATCH 20/40] Removed dependencies to make tests succeed. --- NetSdrClientApp/Messages/NetSdrMessageHelper.cs | 6 ++---- NetSdrClientApp/Networking/TcpClientWrapper.cs | 5 ++--- 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/NetSdrClientApp/Messages/NetSdrMessageHelper.cs b/NetSdrClientApp/Messages/NetSdrMessageHelper.cs index 11beeb0..1394872 100644 --- a/NetSdrClientApp/Messages/NetSdrMessageHelper.cs +++ b/NetSdrClientApp/Messages/NetSdrMessageHelper.cs @@ -1,11 +1,9 @@ -using NetSdrClientApp.Networking; - -namespace NetSdrClientApp.Messages +namespace NetSdrClientApp.Messages { //TODO: analyze possible use of [StructLayout] for better performance and readability public static class NetSdrMessageHelper { - public static readonly ITcpClient Client = new TcpClientWrapper("http://localhost/", 3000); + //public static readonly ITcpClient Client = new TcpClientWrapper("http://localhost/", 3000); private const short _maxMessageLength = 8191; private const short _maxDataItemMessageLength = 8194; private const short _msgHeaderLength = 2; //2 byte, 16 bit diff --git a/NetSdrClientApp/Networking/TcpClientWrapper.cs b/NetSdrClientApp/Networking/TcpClientWrapper.cs index c46cbc3..390067d 100644 --- a/NetSdrClientApp/Networking/TcpClientWrapper.cs +++ b/NetSdrClientApp/Networking/TcpClientWrapper.cs @@ -1,5 +1,4 @@ -using NetSdrClientApp.Messages; -using System.Diagnostics.CodeAnalysis; +using System.Diagnostics.CodeAnalysis; using System.Net.Sockets; using System.Text; @@ -7,7 +6,7 @@ namespace NetSdrClientApp.Networking { public class TcpClientWrapper : ITcpClient { - private ITcpClient _client = NetSdrMessageHelper.Client; + //private ITcpClient _client = NetSdrMessageHelper.Client; private string _host; private int _port; From 593e728d6d667181be7a75ee5b9b43a3fb0bbde8 Mon Sep 17 00:00:00 2001 From: DangerKrip Date: Sun, 17 May 2026 00:02:03 +0300 Subject: [PATCH 21/40] Removed commented code. --- NetSdrClientApp/Messages/NetSdrMessageHelper.cs | 2 -- NetSdrClientApp/Networking/TcpClientWrapper.cs | 2 -- 2 files changed, 4 deletions(-) diff --git a/NetSdrClientApp/Messages/NetSdrMessageHelper.cs b/NetSdrClientApp/Messages/NetSdrMessageHelper.cs index 1394872..2212f49 100644 --- a/NetSdrClientApp/Messages/NetSdrMessageHelper.cs +++ b/NetSdrClientApp/Messages/NetSdrMessageHelper.cs @@ -1,9 +1,7 @@ namespace NetSdrClientApp.Messages { - //TODO: analyze possible use of [StructLayout] for better performance and readability public static class NetSdrMessageHelper { - //public static readonly ITcpClient Client = new TcpClientWrapper("http://localhost/", 3000); private const short _maxMessageLength = 8191; private const short _maxDataItemMessageLength = 8194; private const short _msgHeaderLength = 2; //2 byte, 16 bit diff --git a/NetSdrClientApp/Networking/TcpClientWrapper.cs b/NetSdrClientApp/Networking/TcpClientWrapper.cs index 390067d..71887fb 100644 --- a/NetSdrClientApp/Networking/TcpClientWrapper.cs +++ b/NetSdrClientApp/Networking/TcpClientWrapper.cs @@ -6,8 +6,6 @@ namespace NetSdrClientApp.Networking { public class TcpClientWrapper : ITcpClient { - //private ITcpClient _client = NetSdrMessageHelper.Client; - private string _host; private int _port; private TcpClient? _tcpClient; From d1e98a7c9add7bd686dbf3595202b90f42eee89f Mon Sep 17 00:00:00 2001 From: DangerKrip Date: Mon, 18 May 2026 16:56:43 +0300 Subject: [PATCH 22/40] EchoServer refactoring. --- EchoTcpServer/Program.cs | 169 ++++++++---------------- EchoTcpServer/UdpTimedSender.cs | 65 +++++++++ NetSdrClientAppTests/EchoServerTests.cs | 9 ++ 3 files changed, 127 insertions(+), 116 deletions(-) create mode 100644 EchoTcpServer/UdpTimedSender.cs create mode 100644 NetSdrClientAppTests/EchoServerTests.cs diff --git a/EchoTcpServer/Program.cs b/EchoTcpServer/Program.cs index d411eb0..6970f87 100644 --- a/EchoTcpServer/Program.cs +++ b/EchoTcpServer/Program.cs @@ -1,30 +1,55 @@ -using System; -using System.Net; +using System.Net; using System.Net.Sockets; -using System.Text; -using System.Threading; -using System.Threading.Tasks; namespace EchoServer { - /// /// This program was designed for test purposes only /// Not for a review /// public class EchoServer { + private TcpListener? _listener; + private readonly int _port; - private TcpListener _listener; private readonly CancellationTokenSource _cancellationTokenSource; - public EchoServer(int port) { _port = port; _cancellationTokenSource = new CancellationTokenSource(); } + public static async Task Main(string[] args) + { + EchoServer server = new EchoServer(5000); + + // Start the server in a separate task + _ = Task.Run(() => server.StartAsync()); + + string host = "127.0.0.1"; // Target IP + int port = 60000; // Target Port + int intervalMilliseconds = 5000; // Send every 3 seconds + + using var sender = new UdpTimedSender(host, port); + + Console.WriteLine("Press any key to stop sending..."); + sender.StartSending(intervalMilliseconds); + + Console.WriteLine("Press 'q' to quit..."); + + while (Console.ReadKey(intercept: true).Key != ConsoleKey.Q) + { + // Just wait until 'q' is pressed + } + + sender.StopSending(); + server.Stop(); + Console.WriteLine("Sender stopped."); + + } + + public async Task StartAsync() { _listener = new TcpListener(IPAddress.Any, _port); @@ -50,128 +75,40 @@ public async Task StartAsync() Console.WriteLine("Server shutdown."); } - private async Task HandleClientAsync(TcpClient client, CancellationToken token) - { - using (NetworkStream stream = client.GetStream()) - { - try - { - byte[] buffer = new byte[8192]; - int bytesRead; - - while (!token.IsCancellationRequested && (bytesRead = await stream.ReadAsync(buffer, 0, buffer.Length, token)) > 0) - { - // Echo back the received message - await stream.WriteAsync(buffer, 0, bytesRead, token); - Console.WriteLine($"Echoed {bytesRead} bytes to the client."); - } - } - catch (Exception ex) when (!(ex is OperationCanceledException)) - { - Console.WriteLine($"Error: {ex.Message}"); - } - finally - { - client.Close(); - Console.WriteLine("Client disconnected."); - } - } - } - - public void Stop() + private static async Task HandleClientAsync(TcpClient client, CancellationToken token) { - _cancellationTokenSource.Cancel(); - _listener.Stop(); - _cancellationTokenSource.Dispose(); - Console.WriteLine("Server stopped."); - } + using NetworkStream stream = client.GetStream(); - public static async Task Main(string[] args) - { - EchoServer server = new EchoServer(5000); - - // Start the server in a separate task - _ = Task.Run(() => server.StartAsync()); - - string host = "127.0.0.1"; // Target IP - int port = 60000; // Target Port - int intervalMilliseconds = 5000; // Send every 3 seconds - - using (var sender = new UdpTimedSender(host, port)) + try { - Console.WriteLine("Press any key to stop sending..."); - sender.StartSending(intervalMilliseconds); + const int bufferSize = 8192; + byte[] buffer = new byte[bufferSize]; + int bytesRead; - Console.WriteLine("Press 'q' to quit..."); - while (Console.ReadKey(intercept: true).Key != ConsoleKey.Q) + while (!token.IsCancellationRequested && (bytesRead = await stream.ReadAsync(buffer, 0, buffer.Length, token)) > 0) { - // Just wait until 'q' is pressed + // Echo back the received message + await stream.WriteAsync(buffer, 0, bytesRead, token); + Console.WriteLine($"Echoed {bytesRead} bytes to the client."); } - - sender.StopSending(); - server.Stop(); - Console.WriteLine("Sender stopped."); } - } - } - - - public class UdpTimedSender : IDisposable - { - private readonly string _host; - private readonly int _port; - private readonly UdpClient _udpClient; - private Timer _timer; - - public UdpTimedSender(string host, int port) - { - _host = host; - _port = port; - _udpClient = new UdpClient(); - } - - public void StartSending(int intervalMilliseconds) - { - if (_timer != null) - throw new InvalidOperationException("Sender is already running."); - - _timer = new Timer(SendMessageCallback, null, 0, intervalMilliseconds); - } - - ushort i = 0; - - private void SendMessageCallback(object state) - { - try + catch (Exception ex) when (ex is not OperationCanceledException) { - //dummy data - Random rnd = new Random(); - byte[] samples = new byte[1024]; - rnd.NextBytes(samples); - i++; - - byte[] msg = (new byte[] { 0x04, 0x84 }).Concat(BitConverter.GetBytes(i)).Concat(samples).ToArray(); - var endpoint = new IPEndPoint(IPAddress.Parse(_host), _port); - - _udpClient.Send(msg, msg.Length, endpoint); - Console.WriteLine($"Message sent to {_host}:{_port} "); + Console.WriteLine($"Error: {ex.Message}"); } - catch (Exception ex) + finally { - Console.WriteLine($"Error sending message: {ex.Message}"); + client.Close(); + Console.WriteLine("Client disconnected."); } } - public void StopSending() - { - _timer?.Dispose(); - _timer = null; - } - - public void Dispose() + public void Stop() { - StopSending(); - _udpClient.Dispose(); + _cancellationTokenSource.Cancel(); + _listener?.Stop(); + _cancellationTokenSource.Dispose(); + Console.WriteLine("Server stopped."); } } } \ No newline at end of file diff --git a/EchoTcpServer/UdpTimedSender.cs b/EchoTcpServer/UdpTimedSender.cs new file mode 100644 index 0000000..feb3ebd --- /dev/null +++ b/EchoTcpServer/UdpTimedSender.cs @@ -0,0 +1,65 @@ +using System.Net; +using System.Net.Sockets; + +namespace EchoServer +{ + public class UdpTimedSender : IDisposable + { + private readonly string _host; + private readonly int _port; + private readonly UdpClient _udpClient; + private Timer? _timer; + + public UdpTimedSender(string host, int port) + { + _host = host; + _port = port; + _udpClient = new UdpClient(); + } + + public void StartSending(int intervalMilliseconds) + { + if (_timer != null) + throw new InvalidOperationException("Sender is already running."); + + _timer = new Timer(SendMessageCallback, null, 0, intervalMilliseconds); + } + + ushort i = 0; + + private void SendMessageCallback(object state) + { + try + { + //dummy data + Random rnd = new Random(); + const int samplesSize = 1024; + byte[] samples = new byte[samplesSize]; + rnd.NextBytes(samples); + i++; + + byte[] msg = (new byte[] { 0x04, 0x84 }).Concat(BitConverter.GetBytes(i)).Concat(samples).ToArray(); + var endpoint = new IPEndPoint(IPAddress.Parse(_host), _port); + + _udpClient.Send(msg, msg.Length, endpoint); + Console.WriteLine($"Message sent to {_host}:{_port} "); + } + catch (Exception ex) + { + Console.WriteLine($"Error sending message: {ex.Message}"); + } + } + + public void StopSending() + { + _timer?.Dispose(); + _timer = null; + } + + public void Dispose() + { + StopSending(); + _udpClient.Dispose(); + } + } +} \ No newline at end of file diff --git a/NetSdrClientAppTests/EchoServerTests.cs b/NetSdrClientAppTests/EchoServerTests.cs new file mode 100644 index 0000000..715f5e1 --- /dev/null +++ b/NetSdrClientAppTests/EchoServerTests.cs @@ -0,0 +1,9 @@ +namespace NetSdrClientAppTests; + +public class EchoServerTests +{ + [Test] + public void TestMethod1() + { + } +} From 4d9aa19b089135c6185e4624565ec26ff384c8ae Mon Sep 17 00:00:00 2001 From: DangerKrip Date: Mon, 18 May 2026 16:57:42 +0300 Subject: [PATCH 23/40] EchoServer refactoring. --- EchoTcpServer/Program.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/EchoTcpServer/Program.cs b/EchoTcpServer/Program.cs index 6970f87..77c635b 100644 --- a/EchoTcpServer/Program.cs +++ b/EchoTcpServer/Program.cs @@ -25,7 +25,7 @@ public static async Task Main(string[] args) EchoServer server = new EchoServer(5000); // Start the server in a separate task - _ = Task.Run(() => server.StartAsync()); + await Task.Run(server.StartAsync); string host = "127.0.0.1"; // Target IP int port = 60000; // Target Port From 9255c6d431d6dddf5da046f755f7489625f138e7 Mon Sep 17 00:00:00 2001 From: DangerKrip Date: Mon, 18 May 2026 18:41:57 +0300 Subject: [PATCH 24/40] Added unit tests. --- EchoTcpServer/Program.cs | 7 +- .../EchoServer.EchoServer.Tests.cs | 131 ++++++++++++++++++ .../EchoServer.UdpTimedSender.Tests.cs | 75 ++++++++++ NetSdrClientAppTests/EchoServerTests.cs | 9 -- .../NetSdrClientAppTests.csproj | 1 + 5 files changed, 210 insertions(+), 13 deletions(-) create mode 100644 NetSdrClientAppTests/EchoServer.EchoServer.Tests.cs create mode 100644 NetSdrClientAppTests/EchoServer.UdpTimedSender.Tests.cs delete mode 100644 NetSdrClientAppTests/EchoServerTests.cs diff --git a/EchoTcpServer/Program.cs b/EchoTcpServer/Program.cs index 77c635b..81f537d 100644 --- a/EchoTcpServer/Program.cs +++ b/EchoTcpServer/Program.cs @@ -7,14 +7,14 @@ namespace EchoServer /// This program was designed for test purposes only /// Not for a review /// - public class EchoServer + public class Program { private TcpListener? _listener; private readonly int _port; private readonly CancellationTokenSource _cancellationTokenSource; - public EchoServer(int port) + public Program(int port) { _port = port; _cancellationTokenSource = new CancellationTokenSource(); @@ -22,7 +22,7 @@ public EchoServer(int port) public static async Task Main(string[] args) { - EchoServer server = new EchoServer(5000); + Program server = new Program(5000); // Start the server in a separate task await Task.Run(server.StartAsync); @@ -49,7 +49,6 @@ public static async Task Main(string[] args) } - public async Task StartAsync() { _listener = new TcpListener(IPAddress.Any, _port); diff --git a/NetSdrClientAppTests/EchoServer.EchoServer.Tests.cs b/NetSdrClientAppTests/EchoServer.EchoServer.Tests.cs new file mode 100644 index 0000000..430948a --- /dev/null +++ b/NetSdrClientAppTests/EchoServer.EchoServer.Tests.cs @@ -0,0 +1,131 @@ +using System.Net.Sockets; +using System.Text; +using Program = EchoServer.Program; + +namespace NetSdrClientAppTests; + +[TestFixture] +public class EchoServerTests +{ + private const int TestPort = 5055; + private const int TaskDelay = 200; + + [Test] + public async Task StartAsync_ClientCanConnect() + { + // Arrange + var server = new Program(TestPort); + + // Act + _ = Task.Run(server.StartAsync); + + await Task.Delay(TaskDelay); + + using var client = new TcpClient(); + + // Assert + Assert.DoesNotThrowAsync(async () => + { + await client.ConnectAsync("127.0.0.1", TestPort); + }); + + server.Stop(); + } + + [Test] + public async Task Server_EchoesBack_Message() + { + // Arrange + var server = new Program(TestPort); + + _ = Task.Run(server.StartAsync); + + await Task.Delay(TaskDelay); + + using var client = new TcpClient(); + await client.ConnectAsync("127.0.0.1", TestPort); + + using NetworkStream stream = client.GetStream(); + + byte[] message = Encoding.UTF8.GetBytes("message"); + byte[] buffer = new byte[1024]; + + // Act + await stream.WriteAsync(message, 0, message.Length); + + int bytesRead = await stream.ReadAsync(buffer, 0, buffer.Length); + + string response = Encoding.UTF8.GetString(buffer, 0, bytesRead); + + // Assert + Assert.That(response, Is.EqualTo("message")); + + server.Stop(); + } + + [Test] + public async Task Stop_ServerStopsAcceptingConnections() + { + // Arrange + var server = new Program(TestPort); + + _ = Task.Run(server.StartAsync); + + await Task.Delay(TaskDelay); + + server.Stop(); + + await Task.Delay(TaskDelay); + + using var client = new TcpClient(); + + // Act and Assert + Assert.ThrowsAsync(async () => + { + await client.ConnectAsync("127.0.0.1", TestPort); + }); + } + + [Test] + public async Task Server_CanHandle_MultipleClients() + { + // Arrange + var server = new Program(TestPort); + + _ = Task.Run(server.StartAsync); + + await Task.Delay(TaskDelay); + + // Act + var clients = Enumerable.Range(0, 5) + .Select(async i => + { + using var client = new TcpClient(); + + await client.ConnectAsync("127.0.0.1", TestPort); + + using var stream = client.GetStream(); + + string text = $"client-{i}"; + byte[] message = Encoding.UTF8.GetBytes(text); + + await stream.WriteAsync(message, 0, message.Length); + + byte[] buffer = new byte[1024]; + + int bytesRead = await stream.ReadAsync(buffer, 0, buffer.Length); + + return Encoding.UTF8.GetString(buffer, 0, bytesRead); + }); + + string[] responses = await Task.WhenAll(clients); + + // Assert + for (int i = 0; i < responses.Length; i++) + { + Assert.That(responses[i], Is.EqualTo($"client-{i}")); + } + + server.Stop(); + } +} diff --git a/NetSdrClientAppTests/EchoServer.UdpTimedSender.Tests.cs b/NetSdrClientAppTests/EchoServer.UdpTimedSender.Tests.cs new file mode 100644 index 0000000..e2344c5 --- /dev/null +++ b/NetSdrClientAppTests/EchoServer.UdpTimedSender.Tests.cs @@ -0,0 +1,75 @@ +using EchoServer; +using System.Net.Sockets; + +namespace NetSdrClientAppTests; + +public class UdpTimedSenderTests +{ + private const string TestHost = "127.0.0.1"; + private const int TestPort = 60001; + private UdpClient _receiver; + + [SetUp] + public void SetUp() => _receiver = new UdpClient(TestPort); + + [TearDown] + public void TearDown() => _receiver.Dispose(); + + [Test] + public async Task Sender_ShouldSendPacketWithCorrectHeaderAndLength() + { + // Arrange + using var sender = new UdpTimedSender(TestHost, TestPort); + + // Act + sender.StartSending(1000); + + var receiveTask = _receiver.ReceiveAsync(); + if (await Task.WhenAny(receiveTask, Task.Delay(2000)) == receiveTask) + { + var result = receiveTask.Result; + + // Assert + Assert.That(result.Buffer.Length, Is.EqualTo(1028)); + Assert.That(result.Buffer[0], Is.EqualTo(0x04)); + Assert.That(result.Buffer[1], Is.EqualTo(0x84)); + } + else + { + Assert.Fail("Timed out waiting for UDP packet."); + } + } + + [Test] + public void StartSending_CalledTwice_ShouldThrowInvalidOperationException() + { + // Arrange + using var sender = new UdpTimedSender(TestHost, TestPort); + sender.StartSending(1000); + + // Act + var ex = Assert.Throws(() => sender.StartSending(1000)); + + // Assert + Assert.That(ex.Message, Is.EqualTo("Sender is already running.")); + } + + [Test] + public async Task StopSending_ShouldPreventFurtherMessagesFromBeingSent() + { + // Arrange + using var sender = new UdpTimedSender(TestHost, TestPort); + + // Act + sender.StartSending(100); + + await _receiver.ReceiveAsync(); + + sender.StopSending(); + + await Task.Delay(300); + + // Assert + Assert.That(_receiver.Available, Is.EqualTo(0)); + } +} diff --git a/NetSdrClientAppTests/EchoServerTests.cs b/NetSdrClientAppTests/EchoServerTests.cs deleted file mode 100644 index 715f5e1..0000000 --- a/NetSdrClientAppTests/EchoServerTests.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace NetSdrClientAppTests; - -public class EchoServerTests -{ - [Test] - public void TestMethod1() - { - } -} diff --git a/NetSdrClientAppTests/NetSdrClientAppTests.csproj b/NetSdrClientAppTests/NetSdrClientAppTests.csproj index d4f7f9b..da1c575 100644 --- a/NetSdrClientAppTests/NetSdrClientAppTests.csproj +++ b/NetSdrClientAppTests/NetSdrClientAppTests.csproj @@ -24,6 +24,7 @@ + From 4e6eba812bfe583d77d58e8e8c6189a7721b19f0 Mon Sep 17 00:00:00 2001 From: DangerKrip Date: Mon, 18 May 2026 19:08:02 +0300 Subject: [PATCH 25/40] Added new test --- EchoTcpServer/Program.cs | 2 +- .../EchoServer.EchoServer.Tests.cs | 50 +++++++++++++++++++ 2 files changed, 51 insertions(+), 1 deletion(-) diff --git a/EchoTcpServer/Program.cs b/EchoTcpServer/Program.cs index 81f537d..1df5456 100644 --- a/EchoTcpServer/Program.cs +++ b/EchoTcpServer/Program.cs @@ -74,7 +74,7 @@ public async Task StartAsync() Console.WriteLine("Server shutdown."); } - private static async Task HandleClientAsync(TcpClient client, CancellationToken token) + public static async Task HandleClientAsync(TcpClient client, CancellationToken token) { using NetworkStream stream = client.GetStream(); diff --git a/NetSdrClientAppTests/EchoServer.EchoServer.Tests.cs b/NetSdrClientAppTests/EchoServer.EchoServer.Tests.cs index 430948a..a40565c 100644 --- a/NetSdrClientAppTests/EchoServer.EchoServer.Tests.cs +++ b/NetSdrClientAppTests/EchoServer.EchoServer.Tests.cs @@ -1,3 +1,4 @@ +using System.Net; using System.Net.Sockets; using System.Text; using Program = EchoServer.Program; @@ -128,4 +129,53 @@ public async Task Server_CanHandle_MultipleClients() server.Stop(); } + + [Test] + public async Task HandleClientAsync_EchoesMessage_ClosesClient() + { + // Arrange + var listener = new TcpListener(IPAddress.Loopback, 0); + listener.Start(); + + int port = ((IPEndPoint)listener.LocalEndpoint).Port; + + using var client = new TcpClient(); + + Task acceptTask = listener.AcceptTcpClientAsync(); + + await client.ConnectAsync(IPAddress.Loopback, port); + + TcpClient serverClient = await acceptTask; + + using var cts = new CancellationTokenSource(); + + // Start HandleClientAsync in background + Task handlerTask = Program.HandleClientAsync(serverClient, cts.Token); + + using NetworkStream clientStream = client.GetStream(); + + byte[] message = Encoding.UTF8.GetBytes("ping"); + byte[] buffer = new byte[1024]; + + // Act + await clientStream.WriteAsync(message, 0, message.Length); + + int bytesRead = await clientStream.ReadAsync(buffer, 0, buffer.Length); + + string response = Encoding.UTF8.GetString(buffer, 0, bytesRead); + + // Assert + Assert.That(response, Is.EqualTo("ping")); + + // Close client stream to end server read loop + client.Close(); + + // Wait for handler to finish + await handlerTask; + + Assert.That(serverClient.Connected, Is.False); + + // Cleanup + listener.Stop(); + } } From 5556fc3b8bfac98e62a821a0923b6cb42152a4a3 Mon Sep 17 00:00:00 2001 From: DangerKrip Date: Mon, 18 May 2026 19:27:09 +0300 Subject: [PATCH 26/40] Replaced weak random --- EchoTcpServer/UdpTimedSender.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/EchoTcpServer/UdpTimedSender.cs b/EchoTcpServer/UdpTimedSender.cs index feb3ebd..1275e5b 100644 --- a/EchoTcpServer/UdpTimedSender.cs +++ b/EchoTcpServer/UdpTimedSender.cs @@ -1,5 +1,6 @@ using System.Net; using System.Net.Sockets; +using System.Security.Cryptography; namespace EchoServer { @@ -32,10 +33,10 @@ private void SendMessageCallback(object state) try { //dummy data - Random rnd = new Random(); + var randomGenerator = RandomNumberGenerator.Create(); const int samplesSize = 1024; byte[] samples = new byte[samplesSize]; - rnd.NextBytes(samples); + randomGenerator.GetBytes(samples); i++; byte[] msg = (new byte[] { 0x04, 0x84 }).Concat(BitConverter.GetBytes(i)).Concat(samples).ToArray(); From f39e33cc33f5504e26f47fc2a05326e45f868711 Mon Sep 17 00:00:00 2001 From: DangerKrip Date: Tue, 19 May 2026 16:37:19 +0300 Subject: [PATCH 27/40] Refactored code and made unit tests succeed --- EchoTcpServer/Program.cs | 31 +++++--- .../EchoServer.EchoServer.Tests.cs | 73 ++++++++++++++++++- 2 files changed, 92 insertions(+), 12 deletions(-) diff --git a/EchoTcpServer/Program.cs b/EchoTcpServer/Program.cs index 1df5456..ee388b2 100644 --- a/EchoTcpServer/Program.cs +++ b/EchoTcpServer/Program.cs @@ -55,19 +55,28 @@ public async Task StartAsync() _listener.Start(); Console.WriteLine($"Server started on port {_port}."); - while (!_cancellationTokenSource.Token.IsCancellationRequested) + CancellationToken token = _cancellationTokenSource.Token; + + while (!token.IsCancellationRequested) { try { - TcpClient client = await _listener.AcceptTcpClientAsync(); + TcpClient client = await _listener.AcceptTcpClientAsync(token); Console.WriteLine("Client connected."); - _ = Task.Run(() => HandleClientAsync(client, _cancellationTokenSource.Token)); + _ = Task.Run(() => HandleClientAsync(client, token)); } - catch (ObjectDisposedException) + catch (Exception ex) when ( + ex is ObjectDisposedException || + ex is SocketException || + ex is OperationCanceledException) { - // Listener has been closed - break; + if (token.IsCancellationRequested) + { + break; + } + + throw; } } @@ -84,14 +93,18 @@ public static async Task HandleClientAsync(TcpClient client, CancellationToken t byte[] buffer = new byte[bufferSize]; int bytesRead; - while (!token.IsCancellationRequested && (bytesRead = await stream.ReadAsync(buffer, 0, buffer.Length, token)) > 0) + while (!token.IsCancellationRequested && (bytesRead = await stream.ReadAsync(buffer, token)) > 0) { // Echo back the received message - await stream.WriteAsync(buffer, 0, bytesRead, token); + await stream.WriteAsync(buffer.AsMemory(0, bytesRead), token); Console.WriteLine($"Echoed {bytesRead} bytes to the client."); } } - catch (Exception ex) when (ex is not OperationCanceledException) + catch (OperationCanceledException ex) + { + Console.WriteLine($"Error: {ex.Message}"); + } + catch (Exception ex) { Console.WriteLine($"Error: {ex.Message}"); } diff --git a/NetSdrClientAppTests/EchoServer.EchoServer.Tests.cs b/NetSdrClientAppTests/EchoServer.EchoServer.Tests.cs index a40565c..c06d9f3 100644 --- a/NetSdrClientAppTests/EchoServer.EchoServer.Tests.cs +++ b/NetSdrClientAppTests/EchoServer.EchoServer.Tests.cs @@ -1,5 +1,6 @@ using System.Net; using System.Net.Sockets; +using System.Reflection; using System.Text; using Program = EchoServer.Program; @@ -54,7 +55,7 @@ public async Task Server_EchoesBack_Message() // Act await stream.WriteAsync(message, 0, message.Length); - int bytesRead = await stream.ReadAsync(buffer, 0, buffer.Length); + int bytesRead = await stream.ReadAsync(buffer); string response = Encoding.UTF8.GetString(buffer, 0, bytesRead); @@ -114,7 +115,7 @@ public async Task Server_CanHandle_MultipleClients() byte[] buffer = new byte[1024]; - int bytesRead = await stream.ReadAsync(buffer, 0, buffer.Length); + int bytesRead = await stream.ReadAsync(buffer); return Encoding.UTF8.GetString(buffer, 0, bytesRead); }); @@ -160,7 +161,7 @@ public async Task HandleClientAsync_EchoesMessage_ClosesClient() // Act await clientStream.WriteAsync(message, 0, message.Length); - int bytesRead = await clientStream.ReadAsync(buffer, 0, buffer.Length); + int bytesRead = await clientStream.ReadAsync(buffer); string response = Encoding.UTF8.GetString(buffer, 0, bytesRead); @@ -178,4 +179,70 @@ public async Task HandleClientAsync_EchoesMessage_ClosesClient() // Cleanup listener.Stop(); } + + [Test] + public void Main_Method_Exists_And_IsAsync() + { + // Arrange + MethodInfo? method = typeof(Program).GetMethod( + "Main", + BindingFlags.Public | BindingFlags.Static); + + // Assert + Assert.That(method, Is.Not.Null); + + Assert.That( + method!.ReturnType, + Is.EqualTo(typeof(Task))); + } + + [Test] + public async Task StartAsync_StopsWhenListenerDisposed() + { + // Arrange + var server = new Program(5056); + + Task serverTask = Task.Run(server.StartAsync); + + await Task.Delay(TaskDelay); + + // Act + server.Stop(); + + // Assert + await serverTask; + + Assert.That(serverTask.IsCompletedSuccessfully, Is.True); + } + + [Test] + public async Task HandleClientAsync_HandlesCancellation() + { + // Arrange + var listener = new TcpListener(IPAddress.Loopback, 0); + listener.Start(); + + int port = ((IPEndPoint)listener.LocalEndpoint).Port; + + using var client = new TcpClient(); + + Task acceptTask = listener.AcceptTcpClientAsync(); + + await client.ConnectAsync(IPAddress.Loopback, port); + + TcpClient serverClient = await acceptTask; + + using var cts = new CancellationTokenSource(); + + Task handlerTask = Program.HandleClientAsync(serverClient, cts.Token); + + // Act + cts.Cancel(); + + // Assert + Assert.DoesNotThrowAsync(async () => await handlerTask); + + listener.Stop(); + } + } From be4f016691c92ab5b3a5227e77df11449a8857ce Mon Sep 17 00:00:00 2001 From: DangerKrip Date: Tue, 19 May 2026 17:11:56 +0300 Subject: [PATCH 28/40] Added more tests to increase coverage --- EchoTcpServer/Program.cs | 35 ++++++++++++---- .../EchoServer.EchoServer.Tests.cs | 41 +++++++++++++++++++ 2 files changed, 67 insertions(+), 9 deletions(-) diff --git a/EchoTcpServer/Program.cs b/EchoTcpServer/Program.cs index ee388b2..bce8fd6 100644 --- a/EchoTcpServer/Program.cs +++ b/EchoTcpServer/Program.cs @@ -22,14 +22,27 @@ public Program(int port) public static async Task Main(string[] args) { - Program server = new Program(5000); + using var cts = new CancellationTokenSource(); - // Start the server in a separate task - await Task.Run(server.StartAsync); + // Cancel on Ctrl+C or 'q' + Console.CancelKeyPress += (_, e) => { e.Cancel = true; cts.Cancel(); }; + Task keyTask = Task.Run(() => + { + while (Console.ReadKey(intercept: true).Key != ConsoleKey.Q) { } + cts.Cancel(); + }); + + await RunAsync(cts.Token); + } + + public static async Task RunAsync(CancellationToken cancellationToken) + { + var server = new Program(5000); + _ = Task.Run(server.StartAsync, cancellationToken); - string host = "127.0.0.1"; // Target IP - int port = 60000; // Target Port - int intervalMilliseconds = 5000; // Send every 3 seconds + string host = "127.0.0.1"; + int port = 60000; + int intervalMilliseconds = 5000; using var sender = new UdpTimedSender(host, port); @@ -38,15 +51,19 @@ public static async Task Main(string[] args) Console.WriteLine("Press 'q' to quit..."); - while (Console.ReadKey(intercept: true).Key != ConsoleKey.Q) + try + { + await Task.Delay(Timeout.Infinite, cancellationToken); + } + catch (OperationCanceledException) { - // Just wait until 'q' is pressed + // Expected — triggered by Stop() } sender.StopSending(); server.Stop(); - Console.WriteLine("Sender stopped."); + Console.WriteLine("Sender stopped."); } public async Task StartAsync() diff --git a/NetSdrClientAppTests/EchoServer.EchoServer.Tests.cs b/NetSdrClientAppTests/EchoServer.EchoServer.Tests.cs index c06d9f3..c5e800d 100644 --- a/NetSdrClientAppTests/EchoServer.EchoServer.Tests.cs +++ b/NetSdrClientAppTests/EchoServer.EchoServer.Tests.cs @@ -245,4 +245,45 @@ public async Task HandleClientAsync_HandlesCancellation() listener.Stop(); } + [Test] + public async Task RunAsync_StartsServerAndAcceptsConnection_BeforeCancellation() + { + // Arrange + using var cts = new CancellationTokenSource(); + + Task runTask = Program.RunAsync(cts.Token); + + await Task.Delay(TaskDelay); + + // Act + using var client = new TcpClient(); + + Assert.DoesNotThrowAsync(async () => + await client.ConnectAsync("127.0.0.1", 5000)); + + cts.Cancel(); + await runTask; + + // Assert + Assert.That(runTask.IsCompletedSuccessfully, Is.True); + } + + [Test] + public async Task RunAsync_CompletesCleanly_WhenCancelledAfterDelay() + { + // Arrange + using var cts = new CancellationTokenSource(); + + Task runTask = Program.RunAsync(cts.Token); + + await Task.Delay(TaskDelay); + + // Act + cts.Cancel(); + await runTask; + + // Assert + Assert.That(runTask.IsCompletedSuccessfully, Is.True); + Assert.That(runTask.IsFaulted, Is.False); + } } From 39989d6da93a644c3e79544377c3599b16f58e37 Mon Sep 17 00:00:00 2001 From: DangerKrip Date: Tue, 19 May 2026 18:58:38 +0300 Subject: [PATCH 29/40] Added new test --- .../EchoServer.EchoServer.Tests.cs | 15 ++++++++++++++ .../EchoServer.UdpTimedSender.Tests.cs | 20 +++++++++++++------ 2 files changed, 29 insertions(+), 6 deletions(-) diff --git a/NetSdrClientAppTests/EchoServer.EchoServer.Tests.cs b/NetSdrClientAppTests/EchoServer.EchoServer.Tests.cs index c5e800d..91b14ad 100644 --- a/NetSdrClientAppTests/EchoServer.EchoServer.Tests.cs +++ b/NetSdrClientAppTests/EchoServer.EchoServer.Tests.cs @@ -286,4 +286,19 @@ public async Task RunAsync_CompletesCleanly_WhenCancelledAfterDelay() Assert.That(runTask.IsCompletedSuccessfully, Is.True); Assert.That(runTask.IsFaulted, Is.False); } + + [Test] + public void RunAsync_WhenCancellationRequested_DoesNotThrowOperationCanceledException() + { + // Arrange + using var cts = new CancellationTokenSource(); + + cts.CancelAfter(100); + + // Act + Assert + Assert.DoesNotThrowAsync(async () => + { + await Program.RunAsync(cts.Token); + }); + } } diff --git a/NetSdrClientAppTests/EchoServer.UdpTimedSender.Tests.cs b/NetSdrClientAppTests/EchoServer.UdpTimedSender.Tests.cs index e2344c5..6accd7d 100644 --- a/NetSdrClientAppTests/EchoServer.UdpTimedSender.Tests.cs +++ b/NetSdrClientAppTests/EchoServer.UdpTimedSender.Tests.cs @@ -6,20 +6,28 @@ namespace NetSdrClientAppTests; public class UdpTimedSenderTests { private const string TestHost = "127.0.0.1"; - private const int TestPort = 60001; + private UdpClient _receiver; + private int _port; + [SetUp] - public void SetUp() => _receiver = new UdpClient(TestPort); + public void SetUp() + { + _receiver = new UdpClient(0); + + System.Net.IPEndPoint endpoint = (System.Net.IPEndPoint)_receiver.Client.LocalEndPoint!; + _port = endpoint.Port; + } [TearDown] - public void TearDown() => _receiver.Dispose(); + public void TearDown() => _receiver?.Dispose(); [Test] public async Task Sender_ShouldSendPacketWithCorrectHeaderAndLength() { // Arrange - using var sender = new UdpTimedSender(TestHost, TestPort); + using var sender = new UdpTimedSender(TestHost, _port); // Act sender.StartSending(1000); @@ -44,7 +52,7 @@ public async Task Sender_ShouldSendPacketWithCorrectHeaderAndLength() public void StartSending_CalledTwice_ShouldThrowInvalidOperationException() { // Arrange - using var sender = new UdpTimedSender(TestHost, TestPort); + using var sender = new UdpTimedSender(TestHost, _port); sender.StartSending(1000); // Act @@ -58,7 +66,7 @@ public void StartSending_CalledTwice_ShouldThrowInvalidOperationException() public async Task StopSending_ShouldPreventFurtherMessagesFromBeingSent() { // Arrange - using var sender = new UdpTimedSender(TestHost, TestPort); + using var sender = new UdpTimedSender(TestHost, _port); // Act sender.StartSending(100); From 384eb18415d5ca93c2d9f5a33b4d328e9676c5cc Mon Sep 17 00:00:00 2001 From: DangerKrip Date: Tue, 19 May 2026 19:28:54 +0300 Subject: [PATCH 30/40] Changed test to increase coverage --- .../EchoServer.EchoServer.Tests.cs | 28 +++++++++++++------ 1 file changed, 20 insertions(+), 8 deletions(-) diff --git a/NetSdrClientAppTests/EchoServer.EchoServer.Tests.cs b/NetSdrClientAppTests/EchoServer.EchoServer.Tests.cs index 91b14ad..2c1df59 100644 --- a/NetSdrClientAppTests/EchoServer.EchoServer.Tests.cs +++ b/NetSdrClientAppTests/EchoServer.EchoServer.Tests.cs @@ -287,18 +287,30 @@ public async Task RunAsync_CompletesCleanly_WhenCancelledAfterDelay() Assert.That(runTask.IsFaulted, Is.False); } + [Test] - public void RunAsync_WhenCancellationRequested_DoesNotThrowOperationCanceledException() + public async Task StartAsync_RethrowsException_WhenListenerFailsWithoutCancellation() { // Arrange - using var cts = new CancellationTokenSource(); + var server = new Program(TestPort); + Task serverTask = server.StartAsync(); - cts.CancelAfter(100); + await Task.Delay(TaskDelay); - // Act + Assert - Assert.DoesNotThrowAsync(async () => - { - await Program.RunAsync(cts.Token); - }); + var listenerField = typeof(Program) + .GetField("_listener", BindingFlags.NonPublic | BindingFlags.Instance); + + var listener = (TcpListener)listenerField!.GetValue(server)!; + + // Act + listener.Stop(); + + // Assert + var exception = Assert.CatchAsync(async () => await serverTask); + + Assert.That(exception, + Is.InstanceOf() + .Or.InstanceOf() + .Or.InstanceOf()); } } From 1d679cd461aa2adff16d22ceb0a7e26f58efabd1 Mon Sep 17 00:00:00 2001 From: KonstantinDanger <106324015+KonstantinDanger@users.noreply.github.com> Date: Wed, 20 May 2026 14:20:14 +0300 Subject: [PATCH 31/40] Create dependabot.yml --- .github/dependabot.yml | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 .github/dependabot.yml diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..951c66c --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,6 @@ +version: 2 +updates: + - package-ecosystem: "" # See documentation for possible values + directory: "/" # Location of package manifests + schedule: + interval: "weekly" From 8595904411eb8dd61a6df0f8ce846e828c9e86bb Mon Sep 17 00:00:00 2001 From: DangerKrip Date: Wed, 20 May 2026 14:59:28 +0300 Subject: [PATCH 32/40] Intentionally downgraded Netonsoft.Json to a vulnerable version --- NetSdrClientApp/NetSdrClientApp.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/NetSdrClientApp/NetSdrClientApp.csproj b/NetSdrClientApp/NetSdrClientApp.csproj index 2ac9100..b3cae09 100644 --- a/NetSdrClientApp/NetSdrClientApp.csproj +++ b/NetSdrClientApp/NetSdrClientApp.csproj @@ -7,7 +7,7 @@ enable - + From fcf9f934b580a898d7cf3c2f0cf2b2ba97ee6ca1 Mon Sep 17 00:00:00 2001 From: DangerKrip Date: Wed, 20 May 2026 15:13:30 +0300 Subject: [PATCH 33/40] Update github/dependabot.yml --- .github/dependabot.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 951c66c..446b951 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -1,6 +1,6 @@ version: 2 updates: - - package-ecosystem: "" # See documentation for possible values - directory: "/" # Location of package manifests + - package-ecosystem: "nuget" + directory: "/" schedule: interval: "weekly" From 17befc1d78e84a8bbbe33842a7117145f36b3108 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 20 May 2026 12:18:45 +0000 Subject: [PATCH 34/40] Bump the nuget group with 2 updates Bumps Newtonsoft.Json from 12.0.3 to 13.0.1 Bumps SharpZipLib from 1.3.2 to 1.3.3 --- updated-dependencies: - dependency-name: Newtonsoft.Json dependency-version: 13.0.1 dependency-type: direct:production dependency-group: nuget - dependency-name: SharpZipLib dependency-version: 1.3.3 dependency-type: direct:production dependency-group: nuget ... Signed-off-by: dependabot[bot] --- NetSdrClientApp/NetSdrClientApp.csproj | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/NetSdrClientApp/NetSdrClientApp.csproj b/NetSdrClientApp/NetSdrClientApp.csproj index b3cae09..17d121b 100644 --- a/NetSdrClientApp/NetSdrClientApp.csproj +++ b/NetSdrClientApp/NetSdrClientApp.csproj @@ -7,8 +7,8 @@ enable - - + + From de1dbe04444a880f2ca9cd93ad3e9c54cb8ced7c Mon Sep 17 00:00:00 2001 From: DangerKrip Date: Wed, 20 May 2026 16:35:59 +0300 Subject: [PATCH 35/40] Refactored code --- .../Messages/NetSdrMessageHelper.cs | 23 ++-- NetSdrClientApp/NetSdrClient.cs | 65 +++++------ NetSdrClientApp/Networking/ITcpClient.cs | 9 +- NetSdrClientApp/Networking/IUdpClient.cs | 14 +-- .../Networking/TcpClientWrapper.cs | 17 +-- .../Networking/UdpClientWrapper.cs | 101 +++++++++--------- NetSdrClientApp/Program.cs | 2 +- .../EchoServer.EchoServer.Tests.cs | 13 ++- .../EchoServer.UdpTimedSender.Tests.cs | 9 +- NetSdrClientAppTests/NetSdrClientTests.cs | 6 +- .../NetSdrMessageHelperTests.cs | 53 +++++---- 11 files changed, 151 insertions(+), 161 deletions(-) diff --git a/NetSdrClientApp/Messages/NetSdrMessageHelper.cs b/NetSdrClientApp/Messages/NetSdrMessageHelper.cs index 2212f49..41d8ad1 100644 --- a/NetSdrClientApp/Messages/NetSdrMessageHelper.cs +++ b/NetSdrClientApp/Messages/NetSdrMessageHelper.cs @@ -44,12 +44,9 @@ private static byte[] GetMessage(MsgTypes type, ControlItemCodes itemCode, byte[ var headerBytes = GetHeader(type, itemCodeBytes.Length + parameters.Length); - List msg = new List(); - msg.AddRange(headerBytes); - msg.AddRange(itemCodeBytes); - msg.AddRange(parameters); + List msg = [.. headerBytes, .. itemCodeBytes, .. parameters]; - return msg.ToArray(); + return [.. msg]; } public static bool TranslateMessage(byte[] msg, out MsgTypes type, out ControlItemCodes itemCode, out ushort sequenceNumber, out byte[] body) @@ -57,16 +54,16 @@ public static bool TranslateMessage(byte[] msg, out MsgTypes type, out ControlIt itemCode = ControlItemCodes.None; sequenceNumber = 0; bool success = true; - var msgEnumarable = msg as IEnumerable; + var msgEnumerable = msg as IEnumerable; - TranslateHeader(msgEnumarable.Take(_msgHeaderLength).ToArray(), out type, out int msgLength); - msgEnumarable = msgEnumarable.Skip(_msgHeaderLength); + TranslateHeader([.. msgEnumerable.Take(_msgHeaderLength)], out type, out int msgLength); + msgEnumerable = msgEnumerable.Skip(_msgHeaderLength); msgLength -= _msgHeaderLength; if (type < MsgTypes.DataItem0) // get item code { - var value = BitConverter.ToUInt16(msgEnumarable.Take(_msgControlItemLength).ToArray()); - msgEnumarable = msgEnumarable.Skip(_msgControlItemLength); + var value = BitConverter.ToUInt16(msgEnumerable.Take(_msgControlItemLength).ToArray()); + msgEnumerable = msgEnumerable.Skip(_msgControlItemLength); msgLength -= _msgControlItemLength; if (Enum.IsDefined(typeof(ControlItemCodes), value)) @@ -80,12 +77,12 @@ public static bool TranslateMessage(byte[] msg, out MsgTypes type, out ControlIt } else // get sequenceNumber { - sequenceNumber = BitConverter.ToUInt16(msgEnumarable.Take(_msgSequenceNumberLength).ToArray()); - msgEnumarable = msgEnumarable.Skip(_msgSequenceNumberLength); + sequenceNumber = BitConverter.ToUInt16(msgEnumerable.Take(_msgSequenceNumberLength).ToArray()); + msgEnumerable = msgEnumerable.Skip(_msgSequenceNumberLength); msgLength -= _msgSequenceNumberLength; } - body = msgEnumarable.ToArray(); + body = [.. msgEnumerable]; success &= body.Length == msgLength; diff --git a/NetSdrClientApp/NetSdrClient.cs b/NetSdrClientApp/NetSdrClient.cs index d153baf..a1c19e2 100644 --- a/NetSdrClientApp/NetSdrClient.cs +++ b/NetSdrClientApp/NetSdrClient.cs @@ -1,14 +1,5 @@ -using NetSdrClientApp.Messages; -using NetSdrClientApp.Networking; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading; -using System.Threading.Channels; -using System.Threading.Tasks; +using NetSdrClientApp.Networking; using static NetSdrClientApp.Messages.NetSdrMessageHelper; -using static System.Runtime.InteropServices.JavaScript.JSType; namespace NetSdrClientApp { @@ -17,6 +8,8 @@ public class NetSdrClient private readonly ITcpClient _tcpClient; private readonly IUdpClient _udpClient; + private TaskCompletionSource? responseTaskSource; + public bool IQStarted { get; set; } public NetSdrClient(ITcpClient tcpClient, IUdpClient udpClient) @@ -24,8 +17,8 @@ public NetSdrClient(ITcpClient tcpClient, IUdpClient udpClient) _tcpClient = tcpClient; _udpClient = udpClient; - _tcpClient.MessageReceived += _tcpClient_MessageReceived; - _udpClient.MessageReceived += _udpClient_MessageReceived; + _tcpClient.MessageReceived += TcpClient_MessageReceived; + _udpClient.MessageReceived += UdpClient_MessageReceived; } public async Task ConnectAsync() @@ -39,24 +32,21 @@ public async Task ConnectAsync() var adMode = new byte[] { 0x00, 0x03 }; //Host pre setup - var msgs = new List + var messages = new List { - NetSdrMessageHelper.GetControlItemMessage(MsgTypes.SetControlItem, ControlItemCodes.IQOutputDataSampleRate, sampleRate), - NetSdrMessageHelper.GetControlItemMessage(MsgTypes.SetControlItem, ControlItemCodes.RFFilter, automaticFilterMode), - NetSdrMessageHelper.GetControlItemMessage(MsgTypes.SetControlItem, ControlItemCodes.ADModes, adMode), + GetControlItemMessage(MsgTypes.SetControlItem, ControlItemCodes.IQOutputDataSampleRate, sampleRate), + GetControlItemMessage(MsgTypes.SetControlItem, ControlItemCodes.RFFilter, automaticFilterMode), + GetControlItemMessage(MsgTypes.SetControlItem, ControlItemCodes.ADModes, adMode), }; - foreach (var msg in msgs) + foreach (var msg in messages) { await SendTcpRequest(msg); } } } - public void Disconect() - { - _tcpClient.Disconnect(); - } + public void Disconnect() => _tcpClient.Disconnect(); public async Task StartIQAsync() { @@ -73,8 +63,8 @@ public async Task StartIQAsync() var args = new[] { iqDataMode, start, fifo16bitCaptureMode, n }; - var msg = NetSdrMessageHelper.GetControlItemMessage(MsgTypes.SetControlItem, ControlItemCodes.ReceiverState, args); - + var msg = GetControlItemMessage(MsgTypes.SetControlItem, ControlItemCodes.ReceiverState, args); + await SendTcpRequest(msg); IQStarted = true; @@ -94,7 +84,7 @@ public async Task StopIQAsync() var args = new byte[] { 0, stop, 0, 0 }; - var msg = NetSdrMessageHelper.GetControlItemMessage(MsgTypes.SetControlItem, ControlItemCodes.ReceiverState, args); + var msg = GetControlItemMessage(MsgTypes.SetControlItem, ControlItemCodes.ReceiverState, args); await SendTcpRequest(msg); @@ -109,30 +99,27 @@ public async Task ChangeFrequencyAsync(long hz, int channel) var frequencyArg = BitConverter.GetBytes(hz).Take(5); var args = new[] { channelArg }.Concat(frequencyArg).ToArray(); - var msg = NetSdrMessageHelper.GetControlItemMessage(MsgTypes.SetControlItem, ControlItemCodes.ReceiverFrequency, args); + var msg = GetControlItemMessage(MsgTypes.SetControlItem, ControlItemCodes.ReceiverFrequency, args); await SendTcpRequest(msg); } - private static void _udpClient_MessageReceived(object? sender, byte[] e) + private static void UdpClient_MessageReceived(object? sender, byte[] e) { - NetSdrMessageHelper.TranslateMessage(e, out MsgTypes type, out ControlItemCodes code, out ushort sequenceNum, out byte[] body); - var samples = NetSdrMessageHelper.GetSamples(16, body); + TranslateMessage(e, out MsgTypes type, out ControlItemCodes code, out ushort sequenceNum, out byte[] body); + var samples = GetSamples(16, body); - Console.WriteLine($"Samples recieved: " + body.Select(b => Convert.ToString(b, toBase: 16)).Aggregate((l, r) => $"{l} {r}")); + Console.WriteLine($"Samples received: " + body.Select(b => Convert.ToString(b, toBase: 16)).Aggregate((l, r) => $"{l} {r}")); - using (FileStream fs = new FileStream("samples.bin", FileMode.Append, FileAccess.Write, FileShare.Read)) - using (BinaryWriter sw = new BinaryWriter(fs)) + using FileStream fs = new FileStream("samples.bin", FileMode.Append, FileAccess.Write, FileShare.Read); + using BinaryWriter sw = new BinaryWriter(fs); + + foreach (var sample in samples) { - foreach (var sample in samples) - { - sw.Write((short)sample); //write 16 bit per sample as configured - } + sw.Write((short)sample); //write 16 bit per sample as configured } } - private TaskCompletionSource responseTaskSource; - private async Task SendTcpRequest(byte[] msg) { if (!_tcpClient.Connected) @@ -151,7 +138,7 @@ private async Task SendTcpRequest(byte[] msg) return resp; } - private void _tcpClient_MessageReceived(object? sender, byte[] e) + private void TcpClient_MessageReceived(object? sender, byte[] e) { //TODO: add Unsolicited messages handling here if (responseTaskSource != null) @@ -159,7 +146,7 @@ private void _tcpClient_MessageReceived(object? sender, byte[] e) responseTaskSource.SetResult(e); responseTaskSource = new TaskCompletionSource(); } - Console.WriteLine("Response recieved: " + e.Select(b => Convert.ToString(b, toBase: 16)).Aggregate((l, r) => $"{l} {r}")); + Console.WriteLine("Response received: " + e.Select(b => Convert.ToString(b, toBase: 16)).Aggregate((l, r) => $"{l} {r}")); } } } diff --git a/NetSdrClientApp/Networking/ITcpClient.cs b/NetSdrClientApp/Networking/ITcpClient.cs index 3470b5d..e779e95 100644 --- a/NetSdrClientApp/Networking/ITcpClient.cs +++ b/NetSdrClientApp/Networking/ITcpClient.cs @@ -1,11 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using static System.Runtime.InteropServices.JavaScript.JSType; - -namespace NetSdrClientApp.Networking +namespace NetSdrClientApp.Networking { public interface ITcpClient { diff --git a/NetSdrClientApp/Networking/IUdpClient.cs b/NetSdrClientApp/Networking/IUdpClient.cs index 1b9f931..b85244c 100644 --- a/NetSdrClientApp/Networking/IUdpClient.cs +++ b/NetSdrClientApp/Networking/IUdpClient.cs @@ -1,10 +1,12 @@ - -public interface IUdpClient +namespace NetSdrClientApp.Networking { - event EventHandler? MessageReceived; + public interface IUdpClient + { + event EventHandler? MessageReceived; - Task StartListeningAsync(); + Task StartListeningAsync(); - void StopListening(); - void Exit(); + void StopListening(); + void Exit(); + } } \ No newline at end of file diff --git a/NetSdrClientApp/Networking/TcpClientWrapper.cs b/NetSdrClientApp/Networking/TcpClientWrapper.cs index 71887fb..5564237 100644 --- a/NetSdrClientApp/Networking/TcpClientWrapper.cs +++ b/NetSdrClientApp/Networking/TcpClientWrapper.cs @@ -6,11 +6,11 @@ namespace NetSdrClientApp.Networking { public class TcpClientWrapper : ITcpClient { - private string _host; - private int _port; + private readonly string _host; + private readonly int _port; private TcpClient? _tcpClient; private NetworkStream? _stream; - private CancellationTokenSource _cts; + private CancellationTokenSource? _cts; public bool Connected => _tcpClient != null && _tcpClient.Connected && _stream != null; @@ -70,7 +70,7 @@ 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); + await _stream.WriteAsync(data); } else { @@ -92,20 +92,23 @@ private async Task StartListeningAsync() { try { - Console.WriteLine($"Starting listening for incomming messages."); + Console.WriteLine($"Starting listening for incoming messages."); + + if (_cts == null) + throw new NullReferenceException("The token was null"); while (!_cts.Token.IsCancellationRequested) { byte[] buffer = new byte[8194]; - int bytesRead = await _stream.ReadAsync(buffer, 0, buffer.Length, _cts.Token); + int bytesRead = await _stream.ReadAsync(buffer, _cts.Token); if (bytesRead > 0) { MessageReceived?.Invoke(this, buffer.AsSpan(0, bytesRead).ToArray()); } } } - catch (OperationCanceledException ex) + catch (OperationCanceledException) { //empty } diff --git a/NetSdrClientApp/Networking/UdpClientWrapper.cs b/NetSdrClientApp/Networking/UdpClientWrapper.cs index c4a06f9..6c1b9f4 100644 --- a/NetSdrClientApp/Networking/UdpClientWrapper.cs +++ b/NetSdrClientApp/Networking/UdpClientWrapper.cs @@ -1,75 +1,70 @@ -using System; -using System.Diagnostics.CodeAnalysis; +using System.Diagnostics.CodeAnalysis; 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 { - _localEndPoint = new IPEndPoint(IPAddress.Any, port); - } + private readonly IPEndPoint _localEndPoint; + private CancellationTokenSource? _cts; + private UdpClient? _udpClient; - public async Task StartListeningAsync() - { - _cts = new CancellationTokenSource(); - Console.WriteLine("Start listening for UDP messages..."); + public event EventHandler? MessageReceived; - try + public UdpClientWrapper(int port) => _localEndPoint = new IPEndPoint(IPAddress.Any, port); + + public async Task StartListeningAsync() { - _udpClient = new UdpClient(_localEndPoint); - while (!_cts.Token.IsCancellationRequested) + _cts = new CancellationTokenSource(); + Console.WriteLine("Start listening for UDP messages..."); + + try { - UdpReceiveResult result = await _udpClient.ReceiveAsync(_cts.Token); - MessageReceived?.Invoke(this, result.Buffer); + _udpClient = new UdpClient(_localEndPoint); + while (!_cts.Token.IsCancellationRequested) + { + UdpReceiveResult result = await _udpClient.ReceiveAsync(_cts.Token); + MessageReceived?.Invoke(this, result.Buffer); - Console.WriteLine($"Received from {result.RemoteEndPoint}"); + Console.WriteLine($"Received from {result.RemoteEndPoint}"); + } + } + catch (OperationCanceledException) + { + //empty + } + catch (Exception ex) + { + Console.WriteLine($"Error receiving message: {ex.Message}"); } } - catch (OperationCanceledException ex) - { - //empty - } - catch (Exception ex) - { - Console.WriteLine($"Error receiving message: {ex.Message}"); - } - } - public void StopListening() - { - try - { - _cts?.Cancel(); - _udpClient?.Close(); - Console.WriteLine("Stopped listening for UDP messages."); - } - catch (Exception ex) + public void StopListening() { - Console.WriteLine($"Error while stopping: {ex.Message}"); + try + { + _cts?.Cancel(); + _udpClient?.Close(); + Console.WriteLine("Stopped listening for UDP messages."); + } + catch (Exception ex) + { + Console.WriteLine($"Error while stopping: {ex.Message}"); + } } - } - - [ExcludeFromCodeCoverage] - public void Exit() => StopListening(); - public override int GetHashCode() - { - var payload = $"{nameof(UdpClientWrapper)}|{_localEndPoint.Address}|{_localEndPoint.Port}"; + [ExcludeFromCodeCoverage] + public void Exit() => StopListening(); - using var md5 = MD5.Create(); - var hash = md5.ComputeHash(Encoding.UTF8.GetBytes(payload)); + public override int GetHashCode() + { + var payload = $"{nameof(UdpClientWrapper)}|{_localEndPoint.Address}|{_localEndPoint.Port}"; + var hash = MD5.HashData(Encoding.UTF8.GetBytes(payload)); - return BitConverter.ToInt32(hash, 0); + return BitConverter.ToInt32(hash, 0); + } } } \ No newline at end of file diff --git a/NetSdrClientApp/Program.cs b/NetSdrClientApp/Program.cs index fda2e69..7bc0de8 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) { diff --git a/NetSdrClientAppTests/EchoServer.EchoServer.Tests.cs b/NetSdrClientAppTests/EchoServer.EchoServer.Tests.cs index 2c1df59..c97efa3 100644 --- a/NetSdrClientAppTests/EchoServer.EchoServer.Tests.cs +++ b/NetSdrClientAppTests/EchoServer.EchoServer.Tests.cs @@ -53,7 +53,7 @@ public async Task Server_EchoesBack_Message() byte[] buffer = new byte[1024]; // Act - await stream.WriteAsync(message, 0, message.Length); + await stream.WriteAsync(message); int bytesRead = await stream.ReadAsync(buffer); @@ -111,7 +111,7 @@ public async Task Server_CanHandle_MultipleClients() string text = $"client-{i}"; byte[] message = Encoding.UTF8.GetBytes(text); - await stream.WriteAsync(message, 0, message.Length); + await stream.WriteAsync(message); byte[] buffer = new byte[1024]; @@ -282,9 +282,12 @@ public async Task RunAsync_CompletesCleanly_WhenCancelledAfterDelay() cts.Cancel(); await runTask; - // Assert - Assert.That(runTask.IsCompletedSuccessfully, Is.True); - Assert.That(runTask.IsFaulted, Is.False); + Assert.Multiple(() => + { + // Assert + Assert.That(runTask.IsCompletedSuccessfully, Is.True); + Assert.That(runTask.IsFaulted, Is.False); + }); } diff --git a/NetSdrClientAppTests/EchoServer.UdpTimedSender.Tests.cs b/NetSdrClientAppTests/EchoServer.UdpTimedSender.Tests.cs index 6accd7d..1b7519a 100644 --- a/NetSdrClientAppTests/EchoServer.UdpTimedSender.Tests.cs +++ b/NetSdrClientAppTests/EchoServer.UdpTimedSender.Tests.cs @@ -38,9 +38,12 @@ public async Task Sender_ShouldSendPacketWithCorrectHeaderAndLength() var result = receiveTask.Result; // Assert - Assert.That(result.Buffer.Length, Is.EqualTo(1028)); - Assert.That(result.Buffer[0], Is.EqualTo(0x04)); - Assert.That(result.Buffer[1], Is.EqualTo(0x84)); + Assert.That(result.Buffer, Has.Length.EqualTo(1028)); + Assert.Multiple(() => + { + Assert.That(result.Buffer[0], Is.EqualTo(0x04)); + Assert.That(result.Buffer[1], Is.EqualTo(0x84)); + }); } else { diff --git a/NetSdrClientAppTests/NetSdrClientTests.cs b/NetSdrClientAppTests/NetSdrClientTests.cs index 6a12682..5bf6b4b 100644 --- a/NetSdrClientAppTests/NetSdrClientTests.cs +++ b/NetSdrClientAppTests/NetSdrClientTests.cs @@ -48,10 +48,10 @@ public async Task ConnectAsyncTest() } [Test] - public async Task DisconnectWithNoConnectionTest() + public void DisconnectWithNoConnectionTest() { //act - _client.Disconect(); + _client.Disconnect(); //assert //No exception thrown @@ -65,7 +65,7 @@ public async Task DisconnectTest() await ConnectAsyncTest(); //act - _client.Disconect(); + _client.Disconnect(); //assert //No exception thrown diff --git a/NetSdrClientAppTests/NetSdrMessageHelperTests.cs b/NetSdrClientAppTests/NetSdrMessageHelperTests.cs index 9fdb96f..82eaaf8 100644 --- a/NetSdrClientAppTests/NetSdrMessageHelperTests.cs +++ b/NetSdrClientAppTests/NetSdrMessageHelperTests.cs @@ -4,11 +4,6 @@ namespace NetSdrClientAppTests { public class NetSdrMessageHelperTests { - [SetUp] - public void Setup() - { - } - [Test] public void GetControlItemMessageTest() { @@ -56,12 +51,15 @@ public void GetDataItemMessageTest() var actualType = (NetSdrMessageHelper.MsgTypes)(num >> 13); var actualLength = num - ((int)actualType << 13); - //Assert - Assert.That(headerBytes.Count(), Is.EqualTo(2)); - Assert.That(msg.Length, Is.EqualTo(actualLength)); - Assert.That(type, Is.EqualTo(actualType)); + Assert.Multiple(() => + { + //Assert + Assert.That(headerBytes.Count(), Is.EqualTo(2)); + Assert.That(msg.Length, Is.EqualTo(actualLength)); + Assert.That(type, Is.EqualTo(actualType)); - Assert.That(parametersBytes.Count(), Is.EqualTo(parametersLength)); + Assert.That(parametersBytes.Count(), Is.EqualTo(parametersLength)); + }); } [Test] @@ -75,7 +73,7 @@ public void GetControlItemMessage_EmptyParameters_ProducesMinimalMessage() byte[] msg = NetSdrMessageHelper.GetControlItemMessage(type, code, Array.Empty()); // Assert: 2 header bytes + 2 code bytes = 4 - Assert.That(msg.Length, Is.EqualTo(4)); + Assert.That(msg, Has.Length.EqualTo(4)); } [Test] @@ -106,9 +104,12 @@ public void GetDataItemMessage_MaxDataItemLength_EncodesZeroInHeader() var raw = BitConverter.ToUInt16(msg.Take(2).ToArray()); var encodedLength = raw - ((int)type << 13); - // Assert: the special-case length is encoded as 0 - Assert.That(encodedLength, Is.EqualTo(0)); - Assert.That(msg.Length, Is.EqualTo(8194)); + Assert.Multiple(() => + { + // Assert: the special-case length is encoded as 0 + Assert.That(encodedLength, Is.EqualTo(0)); + Assert.That(msg, Has.Length.EqualTo(8194)); + }); } [Test] @@ -122,7 +123,7 @@ public void GetDataItemMessage_DoesNotContainControlItemCode() byte[] msg = NetSdrMessageHelper.GetDataItemMessage(type, parameters); // Assert: total = 2 header + 3 params (no 2-byte code in between) - Assert.That(msg.Length, Is.EqualTo(5)); + Assert.That(msg, Has.Length.EqualTo(5)); Assert.That(msg.Skip(2).ToArray(), Is.EqualTo(parameters)); } @@ -143,11 +144,14 @@ public void TranslateMessage_DataItem_RoundTrip() bool success = NetSdrMessageHelper.TranslateMessage( msg, out var actualType, out _, out var actualSeq, out var body); - // Assert - Assert.That(success, Is.True); - Assert.That(actualType, Is.EqualTo(type)); - Assert.That(actualSeq, Is.EqualTo(sequenceNumber)); - Assert.That(body, Is.EqualTo(payload)); + Assert.Multiple(() => + { + // Assert + Assert.That(success, Is.True); + Assert.That(actualType, Is.EqualTo(type)); + Assert.That(actualSeq, Is.EqualTo(sequenceNumber)); + Assert.That(body, Is.EqualTo(payload)); + }); } [Test] @@ -160,9 +164,12 @@ public void GetSamples_16Bit_ExtractsCorrectValues() var samples = NetSdrMessageHelper.GetSamples(16, body).ToList(); // Assert - Assert.That(samples.Count, Is.EqualTo(2)); - Assert.That(samples[0], Is.EqualTo(256)); - Assert.That(samples[1], Is.EqualTo(512)); + Assert.That(samples, Has.Count.EqualTo(2)); + Assert.Multiple(() => + { + Assert.That(samples[0], Is.EqualTo(256)); + Assert.That(samples[1], Is.EqualTo(512)); + }); } [Test] From 7d950c9a3cfb40006cf80352d84481b87356505b Mon Sep 17 00:00:00 2001 From: DangerKrip Date: Wed, 20 May 2026 21:52:21 +0300 Subject: [PATCH 36/40] Refactoring and new tests --- EchoTcpServer/Program.cs | 8 - NetSdrClientApp/NetSdrClient.cs | 2 +- .../Networking/TcpClientWrapper.cs | 2 +- .../Networking/UdpClientWrapper.cs | 9 +- .../NetSdrClientApp.TcpClientWrapper.Tests.cs | 197 ++++++++++++++++++ .../NetSdrClientApp.UdpClientWrapper.Tests.cs | 141 +++++++++++++ .../NetSdrMessageHelperTests.cs | 40 ++-- 7 files changed, 364 insertions(+), 35 deletions(-) create mode 100644 NetSdrClientAppTests/NetSdrClientApp.TcpClientWrapper.Tests.cs create mode 100644 NetSdrClientAppTests/NetSdrClientApp.UdpClientWrapper.Tests.cs diff --git a/EchoTcpServer/Program.cs b/EchoTcpServer/Program.cs index bce8fd6..a2179be 100644 --- a/EchoTcpServer/Program.cs +++ b/EchoTcpServer/Program.cs @@ -24,14 +24,6 @@ public static async Task Main(string[] args) { using var cts = new CancellationTokenSource(); - // Cancel on Ctrl+C or 'q' - Console.CancelKeyPress += (_, e) => { e.Cancel = true; cts.Cancel(); }; - Task keyTask = Task.Run(() => - { - while (Console.ReadKey(intercept: true).Key != ConsoleKey.Q) { } - cts.Cancel(); - }); - await RunAsync(cts.Token); } diff --git a/NetSdrClientApp/NetSdrClient.cs b/NetSdrClientApp/NetSdrClient.cs index a1c19e2..8ac06ce 100644 --- a/NetSdrClientApp/NetSdrClient.cs +++ b/NetSdrClientApp/NetSdrClient.cs @@ -106,7 +106,7 @@ public async Task ChangeFrequencyAsync(long hz, int channel) private static void UdpClient_MessageReceived(object? sender, byte[] e) { - TranslateMessage(e, out MsgTypes type, out ControlItemCodes code, out ushort sequenceNum, out byte[] body); + TranslateMessage(e, out MsgTypes _, out ControlItemCodes _, out ushort _, out byte[] body); var samples = GetSamples(16, body); Console.WriteLine($"Samples received: " + body.Select(b => Convert.ToString(b, toBase: 16)).Aggregate((l, r) => $"{l} {r}")); diff --git a/NetSdrClientApp/Networking/TcpClientWrapper.cs b/NetSdrClientApp/Networking/TcpClientWrapper.cs index 5564237..4c7007c 100644 --- a/NetSdrClientApp/Networking/TcpClientWrapper.cs +++ b/NetSdrClientApp/Networking/TcpClientWrapper.cs @@ -95,7 +95,7 @@ private async Task StartListeningAsync() Console.WriteLine($"Starting listening for incoming messages."); if (_cts == null) - throw new NullReferenceException("The token was null"); + throw new ArgumentNullException("The token was null"); while (!_cts.Token.IsCancellationRequested) { diff --git a/NetSdrClientApp/Networking/UdpClientWrapper.cs b/NetSdrClientApp/Networking/UdpClientWrapper.cs index 6c1b9f4..48b1b90 100644 --- a/NetSdrClientApp/Networking/UdpClientWrapper.cs +++ b/NetSdrClientApp/Networking/UdpClientWrapper.cs @@ -1,5 +1,4 @@ -using System.Diagnostics.CodeAnalysis; -using System.Net; +using System.Net; using System.Net.Sockets; using System.Security.Cryptography; using System.Text; @@ -56,13 +55,13 @@ public void StopListening() } } - [ExcludeFromCodeCoverage] public void Exit() => StopListening(); public override int GetHashCode() { - var payload = $"{nameof(UdpClientWrapper)}|{_localEndPoint.Address}|{_localEndPoint.Port}"; - var hash = MD5.HashData(Encoding.UTF8.GetBytes(payload)); + string payload = $"{nameof(UdpClientWrapper)}|{_localEndPoint.Address}|{_localEndPoint.Port}"; + byte[] inputBytes = Encoding.UTF8.GetBytes(payload); + byte[] hash = SHA512.HashData(inputBytes); return BitConverter.ToInt32(hash, 0); } diff --git a/NetSdrClientAppTests/NetSdrClientApp.TcpClientWrapper.Tests.cs b/NetSdrClientAppTests/NetSdrClientApp.TcpClientWrapper.Tests.cs new file mode 100644 index 0000000..3e23788 --- /dev/null +++ b/NetSdrClientAppTests/NetSdrClientApp.TcpClientWrapper.Tests.cs @@ -0,0 +1,197 @@ +using NetSdrClientApp.Networking; +using System.Net; +using System.Net.Sockets; + +namespace NetSdrClientAppTests; + +public class TcpClientWrapperTests +{ + private TcpListener _server = null!; + private int _port; + + [SetUp] + public void SetUp() + { + _server = new TcpListener(IPAddress.Loopback, 0); + _server.Start(); + _port = ((IPEndPoint)_server.LocalEndpoint).Port; + } + + [TearDown] + public void TearDown() + { + _server.Stop(); + _server.Dispose(); + } + + [Test] + public void Connected_BeforeConnect_ReturnsFalse() + { + var client = new TcpClientWrapper("127.0.0.1", _port); + + Assert.That(client.Connected, Is.False); + } + + [Test] + public void Connected_AfterSuccessfulConnect_ReturnsTrue() + { + var client = new TcpClientWrapper("127.0.0.1", _port); + _ = _server.AcceptTcpClientAsync(); + + client.Connect(); + + Assert.That(client.Connected, Is.True); + + client.Disconnect(); + } + + [Test] + public void Connected_AfterDisconnect_ReturnsFalse() + { + var client = new TcpClientWrapper("127.0.0.1", _port); + _ = _server.AcceptTcpClientAsync(); + + client.Connect(); + client.Disconnect(); + + Assert.That(client.Connected, Is.False); + } + + [Test] + public void Connect_WhenAlreadyConnected_DoesNotThrowAndRemainsConnected() + { + var client = new TcpClientWrapper("127.0.0.1", _port); + _ = _server.AcceptTcpClientAsync(); + + client.Connect(); + + Assert.DoesNotThrow(client.Connect); + Assert.That(client.Connected, Is.True); + + client.Disconnect(); + } + + [Test] + public void Connect_WhenServerUnreachable_DoesNotThrowAndRemainsDisconnected() + { + var client = new TcpClientWrapper("127.0.0.1", 1); + + Assert.DoesNotThrow(client.Connect); + Assert.That(client.Connected, Is.False); + } + + [Test] + public void Disconnect_WhenNotConnected_DoesNotThrow() + { + var client = new TcpClientWrapper("127.0.0.1", _port); + + Assert.DoesNotThrow(client.Disconnect); + } + + [Test] + public void Disconnect_WhenConnected_ClosesConnection() + { + var client = new TcpClientWrapper("127.0.0.1", _port); + _ = _server.AcceptTcpClientAsync(); + + client.Connect(); + client.Disconnect(); + + Assert.That(client.Connected, Is.False); + } + + [Test] + public void SendMessageAsync_WhenNotConnected_ThrowsInvalidOperationException() + { + var client = new TcpClientWrapper("127.0.0.1", _port); + + Assert.ThrowsAsync( + () => client.SendMessageAsync([0x01, 0x02])); + } + + [Test] + public async Task SendMessageAsync_WhenConnected_DataArrivesOnServer() + { + var client = new TcpClientWrapper("127.0.0.1", _port); + var serverAcceptTask = _server.AcceptTcpClientAsync(); + + client.Connect(); + + using var serverClient = await serverAcceptTask; + byte[] payload = { 0xDE, 0xAD, 0xBE, 0xEF }; + + await client.SendMessageAsync(payload); + + var stream = serverClient.GetStream(); + var buffer = new byte[16]; + int bytesRead = await stream + .ReadAsync(buffer) + .AsTask() + .WaitAsync(TimeSpan.FromSeconds(3)); + + Assert.Multiple(() => + { + Assert.That(bytesRead, Is.EqualTo(payload.Length)); + Assert.That(buffer[..bytesRead], Is.EqualTo(payload)); + }); + + client.Disconnect(); + } + + [Test] + public async Task MessageReceived_WhenServerWritesData_EventIsRaisedWithCorrectPayload() + { + var client = new TcpClientWrapper("127.0.0.1", _port); + var serverAcceptTask = _server.AcceptTcpClientAsync(); + + var tcs = new TaskCompletionSource(); + client.MessageReceived += (_, data) => tcs.TrySetResult(data); + + client.Connect(); + + using var serverClient = await serverAcceptTask; + byte[] toSend = [0xAA, 0xBB, 0xCC]; + await serverClient.GetStream().WriteAsync(toSend); + + var received = await tcs.Task.WaitAsync(TimeSpan.FromSeconds(3)); + + Assert.That(received, Is.EqualTo(toSend)); + + client.Disconnect(); + } + + [Test] + public async Task MessageReceived_MultipleChunks_AllChunksDelivered() + { + var client = new TcpClientWrapper("127.0.0.1", _port); + var serverAcceptTask = _server.AcceptTcpClientAsync(); + + var receivedChunks = new List(); + var tcs = new TaskCompletionSource(); + + client.MessageReceived += (_, data) => + { + lock (receivedChunks) + { + receivedChunks.Add(data); + if (receivedChunks.Count >= 2) + tcs.TrySetResult(true); + } + }; + + client.Connect(); + + using var serverClient = await serverAcceptTask; + var stream = serverClient.GetStream(); + + await stream.WriteAsync(new byte[] { 0x01, 0x02 }); + await Task.Delay(50); + await stream.WriteAsync(new byte[] { 0x03, 0x04 }); + + await tcs.Task.WaitAsync(TimeSpan.FromSeconds(3)); + + Assert.That(receivedChunks, Is.Not.Empty); + + client.Disconnect(); + } +} diff --git a/NetSdrClientAppTests/NetSdrClientApp.UdpClientWrapper.Tests.cs b/NetSdrClientAppTests/NetSdrClientApp.UdpClientWrapper.Tests.cs new file mode 100644 index 0000000..fb95032 --- /dev/null +++ b/NetSdrClientAppTests/NetSdrClientApp.UdpClientWrapper.Tests.cs @@ -0,0 +1,141 @@ +using NetSdrClientApp.Networking; +using System.Net; +using System.Net.Sockets; + +namespace NetSdrClientAppTests; + +public class UdpClientWrapperTests +{ + private static int GetFreeUdpPort() + { + using var probe = new UdpClient(new IPEndPoint(IPAddress.Any, 0)); + return ((IPEndPoint)probe.Client.LocalEndPoint!).Port; + } + + [Test] + public void StopListening_BeforeStart_DoesNotThrow() + { + var client = new UdpClientWrapper(GetFreeUdpPort()); + + Assert.DoesNotThrow(client.StopListening); + } + + [Test] + public async Task StopListening_AfterStartListeningAsync_CompletesListenLoop() + { + int port = GetFreeUdpPort(); + var client = new UdpClientWrapper(port); + + var listenTask = client.StartListeningAsync(); + await Task.Delay(80); + + client.StopListening(); + + await listenTask.WaitAsync(TimeSpan.FromSeconds(3)); + + Assert.Pass("Listening loop stopped cleanly."); + } + + [Test] + public async Task StartListeningAsync_OnIncomingDatagram_RaisesMessageReceivedEvent() + { + int port = GetFreeUdpPort(); + var client = new UdpClientWrapper(port); + + var tcs = new TaskCompletionSource(); + client.MessageReceived += (_, data) => tcs.TrySetResult(data); + + var listenTask = client.StartListeningAsync(); + await Task.Delay(80); + + byte[] payload = [0x11, 0x22, 0x33]; + using var sender = new UdpClient(); + await sender.SendAsync(payload, new IPEndPoint(IPAddress.Loopback, port)); + + var received = await tcs.Task.WaitAsync(TimeSpan.FromSeconds(3)); + + Assert.That(received, Is.EqualTo(payload)); + + client.StopListening(); + await listenTask.WaitAsync(TimeSpan.FromSeconds(3)); + } + + [Test] + public async Task StartListeningAsync_MultipleDatagrams_EventRaisedForEachDatagram() + { + int port = GetFreeUdpPort(); + var client = new UdpClientWrapper(port); + + var received = new List(); + var tcs = new TaskCompletionSource(); + + client.MessageReceived += (_, data) => + { + lock (received) + { + received.Add(data); + if (received.Count >= 3) + tcs.TrySetResult(true); + } + }; + + var listenTask = client.StartListeningAsync(); + await Task.Delay(80); + + using var sender = new UdpClient(); + var endpoint = new IPEndPoint(IPAddress.Loopback, port); + + for (byte i = 1; i <= 3; i++) + await sender.SendAsync(new byte[] { i }, endpoint); + + await tcs.Task.WaitAsync(TimeSpan.FromSeconds(3)); + + Assert.That(received, Has.Count.EqualTo(3)); + Assert.That(received.Select(r => r[0]).Order(), Is.EqualTo(new byte[] { 1, 2, 3 })); + + client.StopListening(); + await listenTask.WaitAsync(TimeSpan.FromSeconds(3)); + } + + [Test] + public void Exit_WhenNotStarted_DoesNotThrow() + { + var client = new UdpClientWrapper(GetFreeUdpPort()); + + Assert.DoesNotThrow(client.Exit); + } + + [Test] + public async Task Exit_AfterStart_StopsListening() + { + int port = GetFreeUdpPort(); + var client = new UdpClientWrapper(port); + + var listenTask = client.StartListeningAsync(); + await Task.Delay(80); + + client.Exit(); + + await listenTask.WaitAsync(TimeSpan.FromSeconds(3)); + Assert.Pass("Exit() stopped the listening loop."); + } + + [Test] + public void GetHashCode_SamePort_ReturnsSameValue() + { + const int port = 55_000; + var a = new UdpClientWrapper(port); + var b = new UdpClientWrapper(port); + + Assert.That(a.GetHashCode(), Is.EqualTo(b.GetHashCode())); + } + + [Test] + public void GetHashCode_DifferentPorts_ReturnDifferentValues() + { + var a = new UdpClientWrapper(55_001); + var b = new UdpClientWrapper(55_002); + + Assert.That(a.GetHashCode(), Is.Not.EqualTo(b.GetHashCode())); + } +} \ No newline at end of file diff --git a/NetSdrClientAppTests/NetSdrMessageHelperTests.cs b/NetSdrClientAppTests/NetSdrMessageHelperTests.cs index 82eaaf8..510cac6 100644 --- a/NetSdrClientAppTests/NetSdrMessageHelperTests.cs +++ b/NetSdrClientAppTests/NetSdrMessageHelperTests.cs @@ -24,14 +24,17 @@ public void GetControlItemMessageTest() var actualLength = num - ((int)actualType << 13); var actualCode = BitConverter.ToInt16(codeBytes.ToArray()); - //Assert - Assert.That(headerBytes.Count(), Is.EqualTo(2)); - Assert.That(msg.Length, Is.EqualTo(actualLength)); - Assert.That(type, Is.EqualTo(actualType)); + Assert.Multiple(() => + { + //Assert + Assert.That(headerBytes.Count(), Is.EqualTo(2)); + Assert.That(msg, Has.Length.EqualTo(actualLength)); + Assert.That(type, Is.EqualTo(actualType)); - Assert.That(actualCode, Is.EqualTo((short)code)); + Assert.That(actualCode, Is.EqualTo((short)code)); - Assert.That(parametersBytes.Count(), Is.EqualTo(parametersLength)); + Assert.That(parametersBytes.Count(), Is.EqualTo(parametersLength)); + }); } [Test] @@ -55,7 +58,7 @@ public void GetDataItemMessageTest() { //Assert Assert.That(headerBytes.Count(), Is.EqualTo(2)); - Assert.That(msg.Length, Is.EqualTo(actualLength)); + Assert.That(msg, Has.Length.EqualTo(actualLength)); Assert.That(type, Is.EqualTo(actualType)); Assert.That(parametersBytes.Count(), Is.EqualTo(parametersLength)); @@ -70,9 +73,9 @@ public void GetControlItemMessage_EmptyParameters_ProducesMinimalMessage() var code = NetSdrMessageHelper.ControlItemCodes.ReceiverState; // Act - byte[] msg = NetSdrMessageHelper.GetControlItemMessage(type, code, Array.Empty()); + byte[] msg = NetSdrMessageHelper.GetControlItemMessage(type, code, []); - // Assert: 2 header bytes + 2 code bytes = 4 + // Assert Assert.That(msg, Has.Length.EqualTo(4)); } @@ -82,10 +85,10 @@ public void GetControlItemMessage_TooLargeParameters_ThrowsArgumentException() // Arrange var type = NetSdrMessageHelper.MsgTypes.SetControlItem; var code = NetSdrMessageHelper.ControlItemCodes.ReceiverState; - // header(2) + code(2) + params must exceed 8191 + var tooLarge = new byte[8191]; - // Act / Assert + // Act + Assert Assert.Throws( () => NetSdrMessageHelper.GetControlItemMessage(type, code, tooLarge)); } @@ -93,9 +96,7 @@ public void GetControlItemMessage_TooLargeParameters_ThrowsArgumentException() [Test] public void GetDataItemMessage_MaxDataItemLength_EncodesZeroInHeader() { - // Arrange — DataItem edge case: total length == 8194 → header length field must be 0 var type = NetSdrMessageHelper.MsgTypes.DataItem0; - // 8194 - 2 header bytes = 8192 parameter bytes var parameters = new byte[8192]; // Act @@ -104,9 +105,9 @@ public void GetDataItemMessage_MaxDataItemLength_EncodesZeroInHeader() var raw = BitConverter.ToUInt16(msg.Take(2).ToArray()); var encodedLength = raw - ((int)type << 13); + // Assert Assert.Multiple(() => { - // Assert: the special-case length is encoded as 0 Assert.That(encodedLength, Is.EqualTo(0)); Assert.That(msg, Has.Length.EqualTo(8194)); }); @@ -122,7 +123,7 @@ public void GetDataItemMessage_DoesNotContainControlItemCode() // Act byte[] msg = NetSdrMessageHelper.GetDataItemMessage(type, parameters); - // Assert: total = 2 header + 3 params (no 2-byte code in between) + // Assert Assert.That(msg, Has.Length.EqualTo(5)); Assert.That(msg.Skip(2).ToArray(), Is.EqualTo(parameters)); } @@ -135,18 +136,17 @@ public void TranslateMessage_DataItem_RoundTrip() ushort sequenceNumber = 42; var payload = new byte[] { 0xDE, 0xAD, 0xBE, 0xEF }; - // Build manually: header + sequence number + payload byte[] msg = NetSdrMessageHelper.GetDataItemMessage( type, - BitConverter.GetBytes(sequenceNumber).Concat(payload).ToArray()); + [.. BitConverter.GetBytes(sequenceNumber), .. payload]); // Act bool success = NetSdrMessageHelper.TranslateMessage( msg, out var actualType, out _, out var actualSeq, out var body); + // Assert Assert.Multiple(() => { - // Assert Assert.That(success, Is.True); Assert.That(actualType, Is.EqualTo(type)); Assert.That(actualSeq, Is.EqualTo(sequenceNumber)); @@ -157,7 +157,7 @@ public void TranslateMessage_DataItem_RoundTrip() [Test] public void GetSamples_16Bit_ExtractsCorrectValues() { - // Arrange: two 16-bit little-endian samples: 256 (0x00, 0x01) and 512 (0x00, 0x02) + // Arrange var body = new byte[] { 0x00, 0x01, 0x00, 0x02 }; // Act @@ -178,7 +178,7 @@ public void GetSamples_OversizedSampleSize_ThrowsArgumentOutOfRangeException() // Arrange var body = new byte[] { 0x01, 0x02, 0x03, 0x04, 0x05 }; - // Act / Assert: 40-bit sample size (5 bytes) exceeds the 4-byte cap + // Act + Assert Assert.Throws( () => NetSdrMessageHelper.GetSamples(40, body).ToList()); } From 81d55719e8f9139a686d34a1d5f83333e6fe8dd2 Mon Sep 17 00:00:00 2001 From: DangerKrip Date: Wed, 20 May 2026 23:45:02 +0300 Subject: [PATCH 37/40] Restructured entry point. Added new tests --- NetSdrClientApp/App.cs | 74 +++++++++++++++++++ NetSdrClientApp/Program.cs | 50 +++---------- .../NetSdrClientApp.App.Tests.cs | 40 ++++++++++ 3 files changed, 125 insertions(+), 39 deletions(-) create mode 100644 NetSdrClientApp/App.cs create mode 100644 NetSdrClientAppTests/NetSdrClientApp.App.Tests.cs diff --git a/NetSdrClientApp/App.cs b/NetSdrClientApp/App.cs new file mode 100644 index 0000000..07b9616 --- /dev/null +++ b/NetSdrClientApp/App.cs @@ -0,0 +1,74 @@ +using NetSdrClientApp.Networking; + +namespace NetSdrClientApp +{ + public class App + { + private readonly TcpClientWrapper _tcpClient; + private readonly UdpClientWrapper _udpClient; + private readonly Dictionary> _commands = new() + { + [ConsoleKey.C] = async client => await client.ConnectAsync(), + [ConsoleKey.D] = async client => await Task.Run(client.Disconnect), + [ConsoleKey.F] = async client => await client.ChangeFrequencyAsync(20000000, 1), + [ConsoleKey.S] = async client => + { + if (client.IQStarted) + await client.StopIQAsync(); + else + await client.StartIQAsync(); + }, + }; + + public NetSdrClient NetSdr { get; private set; } + + public App(TcpClientWrapper tcpClient, UdpClientWrapper udpClient) + { + _tcpClient = tcpClient; + _udpClient = udpClient; + + NetSdr = new(_tcpClient, _udpClient); + } + + public async void Start() + { + Console.WriteLine("Program has started"); + + ShowCommands(); + + while (true) + { + var key = ReadKey(); + bool update = await PerformCommand(NetSdr, key); + if (!update) + break; + } + } + + public async Task PerformCommand(NetSdrClient netSdr, ConsoleKey key) + { + if (!_commands.TryGetValue(key, out Func? command)) + return false; + + if (command == null || key == ConsoleKey.Q) + return false; + + await command(netSdr); + + return true; + } + + public static void ShowCommands() + { + Console.WriteLine(@"Usage: + C - connect + D - disconnect + F - set frequency + S - Start/Stop IQ listener + Q - quit"); + } + + public static ConsoleKey ReadKey() => Console.ReadKey(intercept: true).Key; + } +} + diff --git a/NetSdrClientApp/Program.cs b/NetSdrClientApp/Program.cs index 7bc0de8..1eb1733 100644 --- a/NetSdrClientApp/Program.cs +++ b/NetSdrClientApp/Program.cs @@ -1,46 +1,18 @@ -using NetSdrClientApp; -using NetSdrClientApp.Networking; +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) - { - await netSdr.ChangeFrequencyAsync(20000000, 1); - } - else if (key == ConsoleKey.S) + public class Program { - if (netSdr.IQStarted) - { - await netSdr.StopIQAsync(); - } - else + public static void Main(string[] args) { - await netSdr.StartIQAsync(); + var tcpClient = new TcpClientWrapper("127.0.0.1", 5000); + var udpClient = new UdpClientWrapper(60000); + + App app = new(tcpClient, udpClient); + + app.Start(); } } - else if (key == ConsoleKey.Q) - { - break; - } } + diff --git a/NetSdrClientAppTests/NetSdrClientApp.App.Tests.cs b/NetSdrClientAppTests/NetSdrClientApp.App.Tests.cs new file mode 100644 index 0000000..4e1536c --- /dev/null +++ b/NetSdrClientAppTests/NetSdrClientApp.App.Tests.cs @@ -0,0 +1,40 @@ +using NetSdrClientApp; +using NetSdrClientApp.Networking; + +namespace NetSdrClientAppTests; + +public class AppTests +{ + private static App CreateApp() + { + var tcp = new TcpClientWrapper("127.0.0.1", 5000); + var udp = new UdpClientWrapper(60000); + return new App(tcp, udp); + } + + [Test] + public void ShowCommands_ThrowsNoExceptions() + => Assert.DoesNotThrow(App.ShowCommands); + + [Test] + public async Task PerformCommand_ReceiveCorrectKey_ReturnTrue() + { + App app = CreateApp(); + ConsoleKey key = ConsoleKey.C; + + bool result = await app.PerformCommand(app.NetSdr, key); + + Assert.That(result, Is.True); + } + + [Test] + public async Task PerformCommand_ReceiveIncorrectKey_ReturnFalse() + { + App app = CreateApp(); + ConsoleKey key = ConsoleKey.DownArrow; + + bool result = await app.PerformCommand(app.NetSdr, key); + + Assert.That(result, Is.False); + } +} \ No newline at end of file From c88ebffde0550ebd7387f7e494de653267558dd7 Mon Sep 17 00:00:00 2001 From: DangerKrip Date: Wed, 20 May 2026 23:56:10 +0300 Subject: [PATCH 38/40] Issue fixes --- NetSdrClientApp/App.cs | 19 +++++-------------- .../Networking/TcpClientWrapper.cs | 2 +- NetSdrClientApp/Program.cs | 7 ++++--- .../NetSdrClientApp.App.Tests.cs | 14 +++++++++++++- 4 files changed, 23 insertions(+), 19 deletions(-) diff --git a/NetSdrClientApp/App.cs b/NetSdrClientApp/App.cs index 07b9616..afdfdaa 100644 --- a/NetSdrClientApp/App.cs +++ b/NetSdrClientApp/App.cs @@ -1,11 +1,7 @@ -using NetSdrClientApp.Networking; - -namespace NetSdrClientApp +namespace NetSdrClientApp { public class App { - private readonly TcpClientWrapper _tcpClient; - private readonly UdpClientWrapper _udpClient; private readonly Dictionary> _commands = new() { [ConsoleKey.C] = async client => await client.ConnectAsync(), @@ -22,15 +18,10 @@ public class App public NetSdrClient NetSdr { get; private set; } - public App(TcpClientWrapper tcpClient, UdpClientWrapper udpClient) - { - _tcpClient = tcpClient; - _udpClient = udpClient; - - NetSdr = new(_tcpClient, _udpClient); - } + public App(NetSdrClient netSdrClient) + => NetSdr = netSdrClient; - public async void Start() + public async Task Start() { Console.WriteLine("Program has started"); @@ -50,7 +41,7 @@ public async Task PerformCommand(NetSdrClient netSdr, ConsoleKey key) if (!_commands.TryGetValue(key, out Func? command)) return false; - if (command == null || key == ConsoleKey.Q) + if (key == ConsoleKey.Q) return false; await command(netSdr); diff --git a/NetSdrClientApp/Networking/TcpClientWrapper.cs b/NetSdrClientApp/Networking/TcpClientWrapper.cs index 4c7007c..c152a22 100644 --- a/NetSdrClientApp/Networking/TcpClientWrapper.cs +++ b/NetSdrClientApp/Networking/TcpClientWrapper.cs @@ -95,7 +95,7 @@ private async Task StartListeningAsync() Console.WriteLine($"Starting listening for incoming messages."); if (_cts == null) - throw new ArgumentNullException("The token was null"); + throw new ArgumentNullException(nameof(_cts), "The token was null"); while (!_cts.Token.IsCancellationRequested) { diff --git a/NetSdrClientApp/Program.cs b/NetSdrClientApp/Program.cs index 1eb1733..8366b9f 100644 --- a/NetSdrClientApp/Program.cs +++ b/NetSdrClientApp/Program.cs @@ -4,14 +4,15 @@ namespace NetSdrClientApp { public class Program { - public static void Main(string[] args) + public static async Task Main(string[] args) { var tcpClient = new TcpClientWrapper("127.0.0.1", 5000); var udpClient = new UdpClientWrapper(60000); + var client = new NetSdrClient(tcpClient, udpClient); - App app = new(tcpClient, udpClient); + App app = new(client); - app.Start(); + await app.Start(); } } } diff --git a/NetSdrClientAppTests/NetSdrClientApp.App.Tests.cs b/NetSdrClientAppTests/NetSdrClientApp.App.Tests.cs index 4e1536c..4399256 100644 --- a/NetSdrClientAppTests/NetSdrClientApp.App.Tests.cs +++ b/NetSdrClientAppTests/NetSdrClientApp.App.Tests.cs @@ -9,7 +9,8 @@ private static App CreateApp() { var tcp = new TcpClientWrapper("127.0.0.1", 5000); var udp = new UdpClientWrapper(60000); - return new App(tcp, udp); + var netSdrClient = new NetSdrClient(tcp, udp); + return new App(netSdrClient); } [Test] @@ -37,4 +38,15 @@ public async Task PerformCommand_ReceiveIncorrectKey_ReturnFalse() Assert.That(result, Is.False); } + + [Test] + public async Task PerformCommand_ReceiveQuitKey_ReturnFalse() + { + App app = CreateApp(); + ConsoleKey key = ConsoleKey.Q; + + bool result = await app.PerformCommand(app.NetSdr, key); + + Assert.That(result, Is.False); + } } \ No newline at end of file From 372c66eedbea67b8251973339221a4cd3e5f44b4 Mon Sep 17 00:00:00 2001 From: DangerKrip Date: Thu, 21 May 2026 00:07:29 +0300 Subject: [PATCH 39/40] Added few tests. Excluded some untestable methods from coverage --- NetSdrClientApp/App.cs | 10 +++-- NetSdrClientApp/Program.cs | 2 + .../NetSdrClientApp.App.Tests.cs | 37 +++++++++++++++++++ 3 files changed, 46 insertions(+), 3 deletions(-) diff --git a/NetSdrClientApp/App.cs b/NetSdrClientApp/App.cs index afdfdaa..908095d 100644 --- a/NetSdrClientApp/App.cs +++ b/NetSdrClientApp/App.cs @@ -1,4 +1,6 @@ -namespace NetSdrClientApp +using System.Diagnostics.CodeAnalysis; + +namespace NetSdrClientApp { public class App { @@ -21,10 +23,10 @@ public class App public App(NetSdrClient netSdrClient) => NetSdr = netSdrClient; + [ExcludeFromCodeCoverage] public async Task Start() { - Console.WriteLine("Program has started"); - + Greet(); ShowCommands(); while (true) @@ -49,6 +51,8 @@ public async Task PerformCommand(NetSdrClient netSdr, ConsoleKey key) return true; } + public static void Greet() => Console.WriteLine("Program has started"); + public static void ShowCommands() { Console.WriteLine(@"Usage: diff --git a/NetSdrClientApp/Program.cs b/NetSdrClientApp/Program.cs index 8366b9f..dd90b77 100644 --- a/NetSdrClientApp/Program.cs +++ b/NetSdrClientApp/Program.cs @@ -1,9 +1,11 @@ using NetSdrClientApp.Networking; +using System.Diagnostics.CodeAnalysis; namespace NetSdrClientApp { public class Program { + [ExcludeFromCodeCoverage] public static async Task Main(string[] args) { var tcpClient = new TcpClientWrapper("127.0.0.1", 5000); diff --git a/NetSdrClientAppTests/NetSdrClientApp.App.Tests.cs b/NetSdrClientAppTests/NetSdrClientApp.App.Tests.cs index 4399256..754af5a 100644 --- a/NetSdrClientAppTests/NetSdrClientApp.App.Tests.cs +++ b/NetSdrClientAppTests/NetSdrClientApp.App.Tests.cs @@ -17,6 +17,10 @@ private static App CreateApp() public void ShowCommands_ThrowsNoExceptions() => Assert.DoesNotThrow(App.ShowCommands); + [Test] + public void Greet_ThrowsNoExceptions() + => Assert.DoesNotThrow(App.Greet); + [Test] public async Task PerformCommand_ReceiveCorrectKey_ReturnTrue() { @@ -49,4 +53,37 @@ public async Task PerformCommand_ReceiveQuitKey_ReturnFalse() Assert.That(result, Is.False); } + + [Test] + public async Task PerformCommand_ReceiveDKey_ReturnTrue() + { + App app = CreateApp(); + ConsoleKey key = ConsoleKey.D; + + bool result = await app.PerformCommand(app.NetSdr, key); + + Assert.That(result, Is.True); + } + + [Test] + public async Task PerformCommand_ReceiveFKey_ReturnTrue() + { + App app = CreateApp(); + ConsoleKey key = ConsoleKey.F; + + bool result = await app.PerformCommand(app.NetSdr, key); + + Assert.That(result, Is.True); + } + + [Test] + public async Task PerformCommand_ReceiveSKey_ReturnTrue() + { + App app = CreateApp(); + ConsoleKey key = ConsoleKey.S; + + bool result = await app.PerformCommand(app.NetSdr, key); + + Assert.That(result, Is.True); + } } \ No newline at end of file From b3a203b9c45b50c96c49275b52fceb952bb65a03 Mon Sep 17 00:00:00 2001 From: DangerKrip Date: Thu, 21 May 2026 00:16:24 +0300 Subject: [PATCH 40/40] Little issues fix --- NetSdrClientApp/Networking/TcpClientWrapper.cs | 2 +- NetSdrClientApp/Program.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/NetSdrClientApp/Networking/TcpClientWrapper.cs b/NetSdrClientApp/Networking/TcpClientWrapper.cs index c152a22..f158462 100644 --- a/NetSdrClientApp/Networking/TcpClientWrapper.cs +++ b/NetSdrClientApp/Networking/TcpClientWrapper.cs @@ -95,7 +95,7 @@ private async Task StartListeningAsync() Console.WriteLine($"Starting listening for incoming messages."); if (_cts == null) - throw new ArgumentNullException(nameof(_cts), "The token was null"); + throw new InvalidOperationException("The operation cannot be performed because the CancellationTokenSource has not been initialized."); while (!_cts.Token.IsCancellationRequested) { diff --git a/NetSdrClientApp/Program.cs b/NetSdrClientApp/Program.cs index dd90b77..dff3ceb 100644 --- a/NetSdrClientApp/Program.cs +++ b/NetSdrClientApp/Program.cs @@ -3,7 +3,7 @@ namespace NetSdrClientApp { - public class Program + public static class Program { [ExcludeFromCodeCoverage] public static async Task Main(string[] args)