From f4daa418a0bc6d1fea9d443f04d3d90615fd9f1b Mon Sep 17 00:00:00 2001 From: Danylo Kovalenko Date: Tue, 7 Apr 2026 17:29:48 +0300 Subject: [PATCH 01/19] setup SonarCloud --- .github/workflows/sonarcloud.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/sonarcloud.yml b/.github/workflows/sonarcloud.yml index e784069..22803bc 100644 --- a/.github/workflows/sonarcloud.yml +++ b/.github/workflows/sonarcloud.yml @@ -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:"Dandddyy_ReengineeringCourse" ` + /o:"dandddyy" ` /d:sonar.token="${{ secrets.SONAR_TOKEN }}" ` /d:sonar.cs.opencover.reportsPaths="**/coverage.xml" ` /d:sonar.cpd.cs.minimumTokens=40 ` From 255b5b3493119bfbbb7ee26084556838b0f8333a Mon Sep 17 00:00:00 2001 From: Danylo Kovalenko <113691027+Dandddyy@users.noreply.github.com> Date: Wed, 8 Apr 2026 12:40:46 +0300 Subject: [PATCH 02/19] Update README.md --- README.md | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index b3a9029..29d9d09 100644 --- a/README.md +++ b/README.md @@ -1,12 +1,9 @@ # Лабораторні з реінжинірингу (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=Dandddyy_ReengineeringCourse&metric=alert_status)](https://sonarcloud.io/summary/new_code?id=Dandddyy_ReengineeringCourse) +[![Bugs](https://sonarcloud.io/api/project_badges/measure?project=Dandddyy_ReengineeringCourse&metric=bugs)](https://sonarcloud.io/summary/new_code?id=Dandddyy_ReengineeringCourse) +[![Code Smells](https://sonarcloud.io/api/project_badges/measure?project=Dandddyy_ReengineeringCourse&metric=code_smells)](https://sonarcloud.io/summary/new_code?id=Dandddyy_ReengineeringCourse) +[![Coverage](https://sonarcloud.io/api/project_badges/measure?project=Dandddyy_ReengineeringCourse&metric=coverage)](https://sonarcloud.io/summary/new_code?id=Dandddyy_ReengineeringCourse) +[![Duplicated Lines (%)](https://sonarcloud.io/api/project_badges/measure?project=Dandddyy_ReengineeringCourse&metric=duplicated_lines_density)](https://sonarcloud.io/summary/new_code?id=Dandddyy_ReengineeringCourse) Цей репозиторій використовується для курсу **реінжиніринг ПЗ**. From 5a7a5402de4a8a5fadaf2b1e68730bc0184a0034 Mon Sep 17 00:00:00 2001 From: Danylo Kovalenko <113691027+Dandddyy@users.noreply.github.com> Date: Wed, 8 Apr 2026 12:42:12 +0300 Subject: [PATCH 03/19] Update README.md --- README.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/README.md b/README.md index 29d9d09..5da61fc 100644 --- a/README.md +++ b/README.md @@ -4,6 +4,12 @@ [![Code Smells](https://sonarcloud.io/api/project_badges/measure?project=Dandddyy_ReengineeringCourse&metric=code_smells)](https://sonarcloud.io/summary/new_code?id=Dandddyy_ReengineeringCourse) [![Coverage](https://sonarcloud.io/api/project_badges/measure?project=Dandddyy_ReengineeringCourse&metric=coverage)](https://sonarcloud.io/summary/new_code?id=Dandddyy_ReengineeringCourse) [![Duplicated Lines (%)](https://sonarcloud.io/api/project_badges/measure?project=Dandddyy_ReengineeringCourse&metric=duplicated_lines_density)](https://sonarcloud.io/summary/new_code?id=Dandddyy_ReengineeringCourse) +[![Lines of Code](https://sonarcloud.io/api/project_badges/measure?project=Dandddyy_ReengineeringCourse&metric=ncloc)](https://sonarcloud.io/summary/new_code?id=Dandddyy_ReengineeringCourse) +[![Reliability Rating](https://sonarcloud.io/api/project_badges/measure?project=Dandddyy_ReengineeringCourse&metric=reliability_rating)](https://sonarcloud.io/summary/new_code?id=Dandddyy_ReengineeringCourse) +[![Security Rating](https://sonarcloud.io/api/project_badges/measure?project=Dandddyy_ReengineeringCourse&metric=security_rating)](https://sonarcloud.io/summary/new_code?id=Dandddyy_ReengineeringCourse) +[![Technical Debt](https://sonarcloud.io/api/project_badges/measure?project=Dandddyy_ReengineeringCourse&metric=sqale_index)](https://sonarcloud.io/summary/new_code?id=Dandddyy_ReengineeringCourse) +[![Maintainability Rating](https://sonarcloud.io/api/project_badges/measure?project=Dandddyy_ReengineeringCourse&metric=sqale_rating)](https://sonarcloud.io/summary/new_code?id=Dandddyy_ReengineeringCourse) +[![Vulnerabilities](https://sonarcloud.io/api/project_badges/measure?project=Dandddyy_ReengineeringCourse&metric=vulnerabilities)](https://sonarcloud.io/summary/new_code?id=Dandddyy_ReengineeringCourse) Цей репозиторій використовується для курсу **реінжиніринг ПЗ**. From ae33938e1d068931ccdc81b98e812596e79bdcb5 Mon Sep 17 00:00:00 2001 From: Danylo Kovalenko Date: Wed, 8 Apr 2026 13:16:14 +0300 Subject: [PATCH 04/19] discarded unused parameters --- .DS_Store | Bin 0 -> 8196 bytes NetSdrClientApp/NetSdrClient.cs | 4 ++-- 2 files changed, 2 insertions(+), 2 deletions(-) create mode 100644 .DS_Store diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..ae591b12feab3262a9c2adb21c5fd30d6386885b GIT binary patch literal 8196 zcmeHM&u<$=6n>K~*ltL&X&p$5kXG>psZmKo+JI20<0OIz(L}KolK#N#+GA&x&5pIZ zPH3Y@KEny_aOcVii3>+AT)83m58%p)elxR9?G34fkg7GnA7zPXjhJo|I z0M2Zg@@Jg;y4RG30mHxx$$;D+9AqiWsqAT~whk2P3IJ)LStaP|)gRK44Upwj_OwI` z64Ovf4Q1&SgQYhIrs3!>r}jN9HJq3l8Gh1{S$acZ>BWON%A8nPOH&#K3uWaOk_0r3^Tp>4_dsy8Sht&C1z|IV%BS?B28`MFtV@$JRq z<3fI_^wxWu^}S#}6kjNYh^5;Tjp_PH`6aXr2Y%xqT^0MKzpCGDJ6%`4aB=jNvGECe z^3vtW;^frS)T^&eU%C4F)w+Gza~pf@NX~az1R|;gU4FkE`ORv_ZTMk(JK>)vRsS)s z+m|w?o4ony)27;^4s41~8F*SJbD zap|&j=PylWsr*r2c~dJkm9&C)*2H=}Hk)DB*aExF?ywKp8oS3DY?t{g(hf%YkNOQ=@0TowmU+|% z)hG-^*hY^;T|5Z}AAuK=Kq&D{NPG>u!QdOCRcg>KY-^Amsv_tjI*{csl4R&z9|&8U z?!qboq6x$@1qfS?Q4`D}MlBr1t6&_&Pn?Ng!aO<|w4C>UKsVz?;2~5cEyZUKgoR_D z!L&1b&4_#z7#y-dn;>h6R`76FvB-UD4o`AKKEgr76>G-2X)Re5>wRn9x=D_;Xw5-e zKzSR#b#mb6F1*zsJ0P+(?2+VNIp`5plMDmrnt=;?cGL3yzk2rf|8u3_JlW5q}Z@ literal 0 HcmV?d00001 diff --git a/NetSdrClientApp/NetSdrClient.cs b/NetSdrClientApp/NetSdrClient.cs index b0a7c05..db7ab58 100644 --- a/NetSdrClientApp/NetSdrClient.cs +++ b/NetSdrClientApp/NetSdrClient.cs @@ -1,4 +1,4 @@ -using NetSdrClientApp.Messages; +using NetSdrClientApp.Messages; using NetSdrClientApp.Networking; using System; using System.Collections.Generic; @@ -116,7 +116,7 @@ public async Task ChangeFrequencyAsync(long hz, int channel) private void _udpClient_MessageReceived(object? sender, byte[] e) { - NetSdrMessageHelper.TranslateMessage(e, out MsgTypes type, out ControlItemCodes code, out ushort sequenceNum, out byte[] body); + NetSdrMessageHelper.TranslateMessage(e, out _, out _, out _, out byte[] body); var samples = NetSdrMessageHelper.GetSamples(16, body); Console.WriteLine($"Samples recieved: " + body.Select(b => Convert.ToString(b, toBase: 16)).Aggregate((l, r) => $"{l} {r}")); From b8b75bb81c633d73aa3750d619dcf96a27f99dab Mon Sep 17 00:00:00 2001 From: Danylo Kovalenko Date: Wed, 8 Apr 2026 13:21:18 +0300 Subject: [PATCH 05/19] removed empty expression, replaced the returned null with an empty array --- NetSdrClientApp/NetSdrClient.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/NetSdrClientApp/NetSdrClient.cs b/NetSdrClientApp/NetSdrClient.cs index db7ab58..efb8ef0 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; @@ -138,7 +138,7 @@ private async Task SendTcpRequest(byte[] msg) if (!_tcpClient.Connected) { Console.WriteLine("No active connection."); - return null; + return Array.Empty(); } responseTaskSource = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); From f4776226e6100c44583755fae6476a74c3d919a8 Mon Sep 17 00:00:00 2001 From: Danylo Kovalenko Date: Wed, 8 Apr 2026 13:25:19 +0300 Subject: [PATCH 06/19] added readonly keyword, made nullable variable --- NetSdrClientApp/NetSdrClient.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/NetSdrClientApp/NetSdrClient.cs b/NetSdrClientApp/NetSdrClient.cs index efb8ef0..8d55e99 100644 --- a/NetSdrClientApp/NetSdrClient.cs +++ b/NetSdrClientApp/NetSdrClient.cs @@ -14,8 +14,8 @@ namespace NetSdrClientApp { public class NetSdrClient { - private ITcpClient _tcpClient; - private IUdpClient _udpClient; + private readonly ITcpClient _tcpClient; + private readonly IUdpClient _udpClient; public bool IQStarted { get; set; } @@ -131,7 +131,7 @@ private void _udpClient_MessageReceived(object? sender, byte[] e) } } - private TaskCompletionSource responseTaskSource; + private TaskCompletionSource? responseTaskSource; private async Task SendTcpRequest(byte[] msg) { From 35ea90d38fde5633bd63e94d916d0f0b7e3662c7 Mon Sep 17 00:00:00 2001 From: Danylo Kovalenko Date: Wed, 8 Apr 2026 14:17:43 +0300 Subject: [PATCH 07/19] added unit tests --- .github/workflows/sonarcloud.yml | 16 ++--- .../NetSdrClientAppTests.csproj | 7 ++- NetSdrClientAppTests/NetSdrClientTests.cs | 59 ++++++++++++++++++- 3 files changed, 72 insertions(+), 10 deletions(-) diff --git a/.github/workflows/sonarcloud.yml b/.github/workflows/sonarcloud.yml index 22803bc..3bf7460 100644 --- a/.github/workflows/sonarcloud.yml +++ b/.github/workflows/sonarcloud.yml @@ -70,14 +70,14 @@ 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 - # 3) END: SonarScanner + - 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 }}" shell: pwsh diff --git a/NetSdrClientAppTests/NetSdrClientAppTests.csproj b/NetSdrClientAppTests/NetSdrClientAppTests.csproj index 3cbc46a..2690b0f 100644 --- a/NetSdrClientAppTests/NetSdrClientAppTests.csproj +++ b/NetSdrClientAppTests/NetSdrClientAppTests.csproj @@ -10,6 +10,11 @@ + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + @@ -26,4 +31,4 @@ - + \ No newline at end of file diff --git a/NetSdrClientAppTests/NetSdrClientTests.cs b/NetSdrClientAppTests/NetSdrClientTests.cs index ad00c4f..fd367a4 100644 --- a/NetSdrClientAppTests/NetSdrClientTests.cs +++ b/NetSdrClientAppTests/NetSdrClientTests.cs @@ -115,5 +115,62 @@ public async Task StopIQTest() Assert.That(_client.IQStarted, Is.False); } - //TODO: cover the rest of the NetSdrClient code here + [Test] + public async Task ConnectAsync_WhenAlreadyConnected_ShouldNotSendMessages() + { + // Arrange + _tcpMock.Setup(tcp => tcp.Connected).Returns(true); + + // 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 ChangeFrequencyAsync_WhenNoConnection_ShouldNotSendMessage() + { + // Arrange + _tcpMock.Setup(tcp => tcp.Connected).Returns(false); + + // Act + await _client.ChangeFrequencyAsync(14000000, 0); + + // Assert + _tcpMock.Verify(tcp => tcp.SendMessageAsync(It.IsAny()), Times.Never); + } + + [Test] + public async Task ChangeFrequencyAsync_WhenConnected_ShouldSendMessage() + { + // Arrange + await _client.ConnectAsync(); + + // Act + await _client.ChangeFrequencyAsync(14000000, 0); + + // Assert + _tcpMock.Verify(tcp => tcp.SendMessageAsync(It.IsAny()), Times.Exactly(4)); + } + + [Test] + public void TcpClient_MessageReceived_Unsolicited_DoesNotThrow() + { + // Arrange + byte[] dummyMessage = { 0x00, 0x01, 0x02 }; + + // Act & Assert + Assert.DoesNotThrow(() => + { + _tcpMock.Raise(tcp => tcp.MessageReceived += null, this, dummyMessage); + }); + } + + [Test] + public void NetSdrClient_InitialState_IQStartedIsFalse() + { + Assert.That(_client.IQStarted, Is.False); + } } From 92de4756cc7e13c5f90f6e83942d589249ae2c28 Mon Sep 17 00:00:00 2001 From: Danylo Kovalenko Date: Wed, 8 Apr 2026 14:24:51 +0300 Subject: [PATCH 08/19] minor fix yml --- .github/workflows/sonarcloud.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/sonarcloud.yml b/.github/workflows/sonarcloud.yml index 3bf7460..d8bd96d 100644 --- a/.github/workflows/sonarcloud.yml +++ b/.github/workflows/sonarcloud.yml @@ -77,7 +77,7 @@ jobs: /p:CoverletOutput=TestResults/coverage.xml ` /p:CoverletOutputFormat=opencover shell: pwsh - 3) END: SonarScanner + # 3) END: SonarScanner - name: SonarScanner End run: dotnet sonarscanner end /d:sonar.token="${{ secrets.SONAR_TOKEN }}" shell: pwsh From b522410ed461a3ec9edf4d34e8323a7cc8f11e0c Mon Sep 17 00:00:00 2001 From: Danylo Kovalenko Date: Wed, 8 Apr 2026 14:43:17 +0300 Subject: [PATCH 09/19] added some unit tests --- NetSdrClientAppTests/NetSdrClientTests.cs | 53 ++++++++++++++++++++--- 1 file changed, 47 insertions(+), 6 deletions(-) diff --git a/NetSdrClientAppTests/NetSdrClientTests.cs b/NetSdrClientAppTests/NetSdrClientTests.cs index fd367a4..1add540 100644 --- a/NetSdrClientAppTests/NetSdrClientTests.cs +++ b/NetSdrClientAppTests/NetSdrClientTests.cs @@ -1,6 +1,8 @@ -using Moq; +using Moq; using NetSdrClientApp; using NetSdrClientApp.Networking; +using System.IO; +using System.Threading.Tasks; namespace NetSdrClientAppTests; @@ -35,6 +37,15 @@ public void Setup() _client = new NetSdrClient(_tcpMock.Object, _updMock.Object); } + + [TearDown] + public void TearDown() + { + if (File.Exists("samples.bin")) + { + File.Delete("samples.bin"); + } + } [Test] public async Task ConnectAsyncTest() @@ -61,7 +72,7 @@ public async Task DisconnectWithNoConnectionTest() [Test] public async Task DisconnectTest() { - //Arrange + //Arrange await ConnectAsyncTest(); //act @@ -75,7 +86,6 @@ public async Task DisconnectTest() [Test] public async Task StartIQNoConnectionTest() { - //act await _client.StartIQAsync(); @@ -88,7 +98,7 @@ public async Task StartIQNoConnectionTest() [Test] public async Task StartIQTest() { - //Arrange + //Arrange await ConnectAsyncTest(); //act @@ -103,7 +113,7 @@ public async Task StartIQTest() [Test] public async Task StopIQTest() { - //Arrange + //Arrange await ConnectAsyncTest(); //act @@ -145,7 +155,7 @@ public async Task ChangeFrequencyAsync_WhenNoConnection_ShouldNotSendMessage() [Test] public async Task ChangeFrequencyAsync_WhenConnected_ShouldSendMessage() { - // Arrange + // Arrange await _client.ConnectAsync(); // Act @@ -173,4 +183,35 @@ public void NetSdrClient_InitialState_IQStartedIsFalse() { Assert.That(_client.IQStarted, Is.False); } + + [Test] + public async Task StopIQNoConnectionTest() + { + // Act + await _client.StopIQAsync(); + + // Assert + _tcpMock.Verify(tcp => tcp.SendMessageAsync(It.IsAny()), Times.Never); + _tcpMock.VerifyGet(tcp => tcp.Connected, Times.AtLeastOnce); + } + + [Test] + public void UdpClient_MessageReceived_ShouldWriteToFile() + { + // Arrange + byte[] dummyPacket = new byte[64]; + for (int i = 0; i < dummyPacket.Length; i++) + { + dummyPacket[i] = (byte)i; + } + + // Act + Assert.DoesNotThrow(() => + { + _updMock.Raise(udp => udp.MessageReceived += null, this, dummyPacket); + }); + + // Assert + Assert.That(File.Exists("samples.bin"), Is.True); + } } From 29d5c99a13ae39fc18ca42ed8a3d43a4401d3c65 Mon Sep 17 00:00:00 2001 From: Danylo Kovalenko Date: Wed, 8 Apr 2026 14:54:08 +0300 Subject: [PATCH 10/19] fix one issue --- NetSdrClientApp/Messages/NetSdrMessageHelper.cs | 2 +- NetSdrClientAppTests/NetSdrClientTests.cs | 9 ++++----- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/NetSdrClientApp/Messages/NetSdrMessageHelper.cs b/NetSdrClientApp/Messages/NetSdrMessageHelper.cs index 0d69b4d..1fb0248 100644 --- a/NetSdrClientApp/Messages/NetSdrMessageHelper.cs +++ b/NetSdrClientApp/Messages/NetSdrMessageHelper.cs @@ -83,7 +83,7 @@ public static bool TranslateMessage(byte[] msg, out MsgTypes type, out ControlIt msgEnumarable = msgEnumarable.Skip(_msgControlItemLength); msgLength -= _msgControlItemLength; - if (Enum.IsDefined(typeof(ControlItemCodes), value)) + if (Enum.IsDefined(typeof(ControlItemCodes), (int)value)) { itemCode = (ControlItemCodes)value; } diff --git a/NetSdrClientAppTests/NetSdrClientTests.cs b/NetSdrClientAppTests/NetSdrClientTests.cs index 1add540..562ae56 100644 --- a/NetSdrClientAppTests/NetSdrClientTests.cs +++ b/NetSdrClientAppTests/NetSdrClientTests.cs @@ -200,10 +200,9 @@ public void UdpClient_MessageReceived_ShouldWriteToFile() { // Arrange byte[] dummyPacket = new byte[64]; - for (int i = 0; i < dummyPacket.Length; i++) - { - dummyPacket[i] = (byte)i; - } + + dummyPacket[0] = 64; + dummyPacket[1] = 0; // Act Assert.DoesNotThrow(() => @@ -212,6 +211,6 @@ public void UdpClient_MessageReceived_ShouldWriteToFile() }); // Assert - Assert.That(File.Exists("samples.bin"), Is.True); + Assert.That(File.Exists("samples.bin"), Is.True, "Файл samples.bin має бути створений"); } } From 0858c8190f0c74965b997512b1e9d900b5d2ceb4 Mon Sep 17 00:00:00 2001 From: Danylo Kovalenko Date: Wed, 8 Apr 2026 18:52:51 +0300 Subject: [PATCH 11/19] fixed duplicated code --- NetSdrClientApp/Networking/TcpClientWrapper.cs | 12 ++---------- NetSdrClientApp/Networking/UdpClientWrapper.cs | 15 +++------------ 2 files changed, 5 insertions(+), 22 deletions(-) diff --git a/NetSdrClientApp/Networking/TcpClientWrapper.cs b/NetSdrClientApp/Networking/TcpClientWrapper.cs index 1f37e2e..f18a547 100644 --- a/NetSdrClientApp/Networking/TcpClientWrapper.cs +++ b/NetSdrClientApp/Networking/TcpClientWrapper.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.IO; using System.Linq; @@ -87,15 +87,7 @@ 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."); - } + await SendMessageAsync(data); } private async Task StartListeningAsync() diff --git a/NetSdrClientApp/Networking/UdpClientWrapper.cs b/NetSdrClientApp/Networking/UdpClientWrapper.cs index 31e0b79..9ccc733 100644 --- a/NetSdrClientApp/Networking/UdpClientWrapper.cs +++ b/NetSdrClientApp/Networking/UdpClientWrapper.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Net; using System.Net.Sockets; using System.Security.Cryptography; @@ -61,16 +61,7 @@ public void StopListening() public void Exit() { - try - { - _cts?.Cancel(); - _udpClient?.Close(); - Console.WriteLine("Stopped listening for UDP messages."); - } - catch (Exception ex) - { - Console.WriteLine($"Error while stopping: {ex.Message}"); - } + StopListening(); } public override int GetHashCode() @@ -82,4 +73,4 @@ public override int GetHashCode() return BitConverter.ToInt32(hash, 0); } -} \ No newline at end of file +} From e19ccc5d9d101d9b8b1bbfaabd60f2a0090f463b Mon Sep 17 00:00:00 2001 From: Danylo Kovalenko Date: Wed, 8 Apr 2026 19:06:59 +0300 Subject: [PATCH 12/19] Update sonarcloud.yml --- .github/workflows/sonarcloud.yml | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/.github/workflows/sonarcloud.yml b/.github/workflows/sonarcloud.yml index d8bd96d..a678254 100644 --- a/.github/workflows/sonarcloud.yml +++ b/.github/workflows/sonarcloud.yml @@ -70,13 +70,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 }}" From edca38478f42df90c80714d6110b250f6106725d Mon Sep 17 00:00:00 2001 From: Danylo Kovalenko Date: Wed, 8 Apr 2026 19:28:17 +0300 Subject: [PATCH 13/19] added tests --- .github/workflows/sonarcloud.yml | 14 ++-- NetSdrClientAppTests/TcpClientWrapperTests.cs | 77 +++++++++++++++++++ NetSdrClientAppTests/UdpClientWrapperTests.cs | 52 +++++++++++++ 3 files changed, 136 insertions(+), 7 deletions(-) create mode 100644 NetSdrClientAppTests/TcpClientWrapperTests.cs create mode 100644 NetSdrClientAppTests/UdpClientWrapperTests.cs diff --git a/.github/workflows/sonarcloud.yml b/.github/workflows/sonarcloud.yml index a678254..d8bd96d 100644 --- a/.github/workflows/sonarcloud.yml +++ b/.github/workflows/sonarcloud.yml @@ -70,13 +70,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/TcpClientWrapperTests.cs b/NetSdrClientAppTests/TcpClientWrapperTests.cs new file mode 100644 index 0000000..a78a854 --- /dev/null +++ b/NetSdrClientAppTests/TcpClientWrapperTests.cs @@ -0,0 +1,77 @@ +using NUnit.Framework; +using NetSdrClientApp.Networking; +using System; +using System.Net; +using System.Net.Sockets; +using System.Threading.Tasks; + +namespace NetSdrClientAppTests +{ + [TestFixture] + public class TcpClientWrapperTests + { + [Test] + public void Connected_InitialState_ShouldBeFalse() + { + var wrapper = new TcpClientWrapper("127.0.0.1", 12345); + Assert.That(wrapper.Connected, Is.False); + } + + [Test] + public void Disconnect_WhenNotConnected_ShouldNotThrow() + { + var wrapper = new TcpClientWrapper("127.0.0.1", 12345); + Assert.DoesNotThrow(() => wrapper.Disconnect()); + } + + [Test] + public void SendMessageAsync_Bytes_WhenNotConnected_ShouldThrowInvalidOperationException() + { + var wrapper = new TcpClientWrapper("127.0.0.1", 12345); + Assert.ThrowsAsync(async () => await wrapper.SendMessageAsync(new byte[] { 1, 2, 3 })); + } + + [Test] + public void SendMessageAsync_String_WhenNotConnected_ShouldThrowInvalidOperationException() + { + var wrapper = new TcpClientWrapper("127.0.0.1", 12345); + Assert.ThrowsAsync(async () => await wrapper.SendMessageAsync("Hello")); + } + + [Test] + public void Connect_WhenServerIsDown_ShouldCatchExceptionAndRemainDisconnected() + { + var wrapper = new TcpClientWrapper("127.0.0.1", 55555); + wrapper.Connect(); + + Assert.That(wrapper.Connected, Is.False); + } + + [Test] + public async Task ConnectAndDisconnect_WithRealLocalServer_ShouldChangeState() + { + // Arrange + var listener = new TcpListener(IPAddress.Loopback, 0); + listener.Start(); + int port = ((IPEndPoint)listener.LocalEndpoint).Port; + + var wrapper = new TcpClientWrapper("127.0.0.1", port); + + // Act + wrapper.Connect(); + + using var client = await listener.AcceptTcpClientAsync(); + + // Assert + Assert.That(wrapper.Connected, Is.True); + + // Act + wrapper.Disconnect(); + + // Assert + Assert.That(wrapper.Connected, Is.False); + + listener.Stop(); + } + } +} diff --git a/NetSdrClientAppTests/UdpClientWrapperTests.cs b/NetSdrClientAppTests/UdpClientWrapperTests.cs new file mode 100644 index 0000000..17e8f9e --- /dev/null +++ b/NetSdrClientAppTests/UdpClientWrapperTests.cs @@ -0,0 +1,52 @@ +using NUnit.Framework; +using NetSdrClientApp.Networking; +using System.Threading.Tasks; + +namespace NetSdrClientAppTests +{ + [TestFixture] + public class UdpClientWrapperTests + { + [Test] + public void StopListening_WhenNotStarted_ShouldNotThrow() + { + var wrapper = new UdpClientWrapper(0); + Assert.DoesNotThrow(() => wrapper.StopListening()); + } + + [Test] + public void Exit_WhenNotStarted_ShouldNotThrow() + { + var wrapper = new UdpClientWrapper(0); + Assert.DoesNotThrow(() => wrapper.Exit()); + } + + [Test] + public void GetHashCode_ShouldReturnConsistentValueForSameEndpoint() + { + // Arrange + var wrapper1 = new UdpClientWrapper(12348); + var wrapper2 = new UdpClientWrapper(12348); + + // Act & Assert + Assert.That(wrapper1.GetHashCode(), Is.EqualTo(wrapper2.GetHashCode())); + } + + [Test] + public async Task StartAndStopListening_ShouldHandleCancellationGracefully() + { + // Arrange + var wrapper = new UdpClientWrapper(0); + + // Act + var listenTask = wrapper.StartListeningAsync(); + + await Task.Delay(50); + + wrapper.StopListening(); + + // Assert + Assert.DoesNotThrowAsync(async () => await listenTask); + } + } +} From e3e9cf9a68b3b800a426e6875147427e098539ba Mon Sep 17 00:00:00 2001 From: Danylo Kovalenko Date: Wed, 8 Apr 2026 19:48:34 +0300 Subject: [PATCH 14/19] added ShouldNotDependOn test --- .../Messages/NetSdrMessageHelper.cs | 2 + NetSdrClientAppTests/ArchitectureTests.cs | 52 +++++++++++++++++++ .../NetSdrClientAppTests.csproj | 1 + 3 files changed, 55 insertions(+) create mode 100644 NetSdrClientAppTests/ArchitectureTests.cs diff --git a/NetSdrClientApp/Messages/NetSdrMessageHelper.cs b/NetSdrClientApp/Messages/NetSdrMessageHelper.cs index 1fb0248..acbe146 100644 --- a/NetSdrClientApp/Messages/NetSdrMessageHelper.cs +++ b/NetSdrClientApp/Messages/NetSdrMessageHelper.cs @@ -15,6 +15,8 @@ public static class NetSdrMessageHelper private const short _msgHeaderLength = 2; //2 byte, 16 bit private const short _msgControlItemLength = 2; //2 byte, 16 bit private const short _msgSequenceNumberLength = 2; //2 byte, 16 bit + + private static NetSdrClientApp.Networking.ITcpClient _badDependency; public enum MsgTypes { diff --git a/NetSdrClientAppTests/ArchitectureTests.cs b/NetSdrClientAppTests/ArchitectureTests.cs new file mode 100644 index 0000000..17e8f9e --- /dev/null +++ b/NetSdrClientAppTests/ArchitectureTests.cs @@ -0,0 +1,52 @@ +using NUnit.Framework; +using NetSdrClientApp.Networking; +using System.Threading.Tasks; + +namespace NetSdrClientAppTests +{ + [TestFixture] + public class UdpClientWrapperTests + { + [Test] + public void StopListening_WhenNotStarted_ShouldNotThrow() + { + var wrapper = new UdpClientWrapper(0); + Assert.DoesNotThrow(() => wrapper.StopListening()); + } + + [Test] + public void Exit_WhenNotStarted_ShouldNotThrow() + { + var wrapper = new UdpClientWrapper(0); + Assert.DoesNotThrow(() => wrapper.Exit()); + } + + [Test] + public void GetHashCode_ShouldReturnConsistentValueForSameEndpoint() + { + // Arrange + var wrapper1 = new UdpClientWrapper(12348); + var wrapper2 = new UdpClientWrapper(12348); + + // Act & Assert + Assert.That(wrapper1.GetHashCode(), Is.EqualTo(wrapper2.GetHashCode())); + } + + [Test] + public async Task StartAndStopListening_ShouldHandleCancellationGracefully() + { + // Arrange + var wrapper = new UdpClientWrapper(0); + + // Act + var listenTask = wrapper.StartListeningAsync(); + + await Task.Delay(50); + + wrapper.StopListening(); + + // Assert + Assert.DoesNotThrowAsync(async () => await listenTask); + } + } +} diff --git a/NetSdrClientAppTests/NetSdrClientAppTests.csproj b/NetSdrClientAppTests/NetSdrClientAppTests.csproj index 2690b0f..470d4fa 100644 --- a/NetSdrClientAppTests/NetSdrClientAppTests.csproj +++ b/NetSdrClientAppTests/NetSdrClientAppTests.csproj @@ -18,6 +18,7 @@ + From c0ab83f9c40f142fe3f278cb711db1317987a3a2 Mon Sep 17 00:00:00 2001 From: Danylo Kovalenko Date: Wed, 8 Apr 2026 20:00:27 +0300 Subject: [PATCH 15/19] some fixes --- NetSdrClientAppTests/ArchitectureTests.cs | 50 +++++++---------------- 1 file changed, 15 insertions(+), 35 deletions(-) diff --git a/NetSdrClientAppTests/ArchitectureTests.cs b/NetSdrClientAppTests/ArchitectureTests.cs index 17e8f9e..fa0da2d 100644 --- a/NetSdrClientAppTests/ArchitectureTests.cs +++ b/NetSdrClientAppTests/ArchitectureTests.cs @@ -1,52 +1,32 @@ +using NetArchTest.Rules; +using NetSdrClientApp; using NUnit.Framework; -using NetSdrClientApp.Networking; -using System.Threading.Tasks; namespace NetSdrClientAppTests { [TestFixture] - public class UdpClientWrapperTests + public class ArchitectureTests { [Test] - public void StopListening_WhenNotStarted_ShouldNotThrow() + public void Messages_ShouldNotDependOn_Networking() { - var wrapper = new UdpClientWrapper(0); - Assert.DoesNotThrow(() => wrapper.StopListening()); - } + var result = Types.InAssembly(typeof(NetSdrClient).Assembly) + .That().ResideInNamespace("NetSdrClientApp.Messages") + .ShouldNot().HaveDependencyOn("NetSdrClientApp.Networking") + .GetResult(); - [Test] - public void Exit_WhenNotStarted_ShouldNotThrow() - { - var wrapper = new UdpClientWrapper(0); - Assert.DoesNotThrow(() => wrapper.Exit()); + Assert.That(result.IsSuccessful, Is.True, "Architectural error: The Messages module should not know about the Networking module!"); } [Test] - public void GetHashCode_ShouldReturnConsistentValueForSameEndpoint() + public void Interfaces_ShouldStartWith_I() { - // Arrange - var wrapper1 = new UdpClientWrapper(12348); - var wrapper2 = new UdpClientWrapper(12348); - - // Act & Assert - Assert.That(wrapper1.GetHashCode(), Is.EqualTo(wrapper2.GetHashCode())); - } - - [Test] - public async Task StartAndStopListening_ShouldHandleCancellationGracefully() - { - // Arrange - var wrapper = new UdpClientWrapper(0); - - // Act - var listenTask = wrapper.StartListeningAsync(); - - await Task.Delay(50); - - wrapper.StopListening(); + var result = Types.InAssembly(typeof(NetSdrClient).Assembly) + .That().AreInterfaces() + .Should().HaveNameStartingWith("I") + .GetResult(); - // Assert - Assert.DoesNotThrowAsync(async () => await listenTask); + Assert.That(result.IsSuccessful, Is.True, "All interfaces must begin with the letter 'I'."); } } } From b5463e5edf41c942e6b8f795b089fd3d0449bceb Mon Sep 17 00:00:00 2001 From: Danylo Kovalenko Date: Wed, 8 Apr 2026 20:10:57 +0300 Subject: [PATCH 16/19] remove test variable --- NetSdrClientApp/Messages/NetSdrMessageHelper.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/NetSdrClientApp/Messages/NetSdrMessageHelper.cs b/NetSdrClientApp/Messages/NetSdrMessageHelper.cs index acbe146..1fb0248 100644 --- a/NetSdrClientApp/Messages/NetSdrMessageHelper.cs +++ b/NetSdrClientApp/Messages/NetSdrMessageHelper.cs @@ -15,8 +15,6 @@ public static class NetSdrMessageHelper private const short _msgHeaderLength = 2; //2 byte, 16 bit private const short _msgControlItemLength = 2; //2 byte, 16 bit private const short _msgSequenceNumberLength = 2; //2 byte, 16 bit - - private static NetSdrClientApp.Networking.ITcpClient _badDependency; public enum MsgTypes { From c29ad9e13d79e9d817d3f2d59c9dd5583b7836ab Mon Sep 17 00:00:00 2001 From: Danylo Kovalenko Date: Fri, 24 Apr 2026 17:18:58 +0300 Subject: [PATCH 17/19] lab6 --- .github/workflows/sonarcloud.yml | 2 +- EchoTcpServer/EchoServer.cs | 80 +++++++++ EchoTcpServer/EchoServer.csproj | 2 +- EchoTcpServer/IUdpClient.cs | 18 ++ EchoTcpServer/Program.cs | 164 +------------------ EchoTcpServer/UdpTimedSender.cs | 64 ++++++++ EchoTcpServerTests/EchoServerTests.cs | 43 +++++ EchoTcpServerTests/EchoTcpServerTests.csproj | 31 ++++ EchoTcpServerTests/UdpTimedSenderTests.cs | 44 +++++ NetSdrClient.sln | 6 + 10 files changed, 297 insertions(+), 157 deletions(-) create mode 100644 EchoTcpServer/EchoServer.cs create mode 100644 EchoTcpServer/IUdpClient.cs create mode 100644 EchoTcpServer/UdpTimedSender.cs create mode 100644 EchoTcpServerTests/EchoServerTests.cs create mode 100644 EchoTcpServerTests/EchoTcpServerTests.csproj create mode 100644 EchoTcpServerTests/UdpTimedSenderTests.cs diff --git a/.github/workflows/sonarcloud.yml b/.github/workflows/sonarcloud.yml index d8bd96d..0b30ac2 100644 --- a/.github/workflows/sonarcloud.yml +++ b/.github/workflows/sonarcloud.yml @@ -72,7 +72,7 @@ jobs: run: dotnet build NetSdrClient.sln -c Release --no-restore - name: Tests with coverage (OpenCover) run: | - dotnet test NetSdrClientAppTests/NetSdrClientAppTests.csproj -c Release--no-build ` + dotnet test NetSdrClient.sln -c Release --no-build ` /p:CollectCoverage=true ` /p:CoverletOutput=TestResults/coverage.xml ` /p:CoverletOutputFormat=opencover diff --git a/EchoTcpServer/EchoServer.cs b/EchoTcpServer/EchoServer.cs new file mode 100644 index 0000000..f305dcd --- /dev/null +++ b/EchoTcpServer/EchoServer.cs @@ -0,0 +1,80 @@ +using System; +using System.Net; +using System.Net.Sockets; +using System.Threading; +using System.Threading.Tasks; + +namespace EchoTcpServer +{ + public class EchoServer : IDisposable + { + private readonly IPAddress _ipAddress; + private readonly int _port; + private TcpListener? _listener; + private CancellationTokenSource _cancellationTokenSource; + + public EchoServer(IPAddress ipAddress, int port) + { + _ipAddress = ipAddress; + _port = port; + _cancellationTokenSource = new CancellationTokenSource(); + } + + public async Task StartAsync() + { + _listener = new TcpListener(_ipAddress, _port); + _listener.Start(); + Console.WriteLine($"Server started on {_ipAddress}:{_port}."); + + try + { + while (!_cancellationTokenSource.Token.IsCancellationRequested) + { + TcpClient client = await _listener.AcceptTcpClientAsync(_cancellationTokenSource.Token); + Console.WriteLine("Client connected."); + _ = Task.Run(() => HandleClientAsync(client, _cancellationTokenSource.Token)); + } + } + catch (OperationCanceledException) { /* Очікувано при вимкненні */ } + catch (Exception ex) { Console.WriteLine($"Server error: {ex.Message}"); } + finally { Console.WriteLine("Server shutdown."); } + } + + private async Task HandleClientAsync(TcpClient client, CancellationToken token) + { + using (client) + 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) + { + await stream.WriteAsync(buffer, 0, bytesRead, token); + Console.WriteLine($"Echoed {bytesRead} bytes to the client."); + } + } + catch (Exception ex) when (ex is not OperationCanceledException) + { + Console.WriteLine($"Error handling client: {ex.Message}"); + } + } + } + + public void Stop() + { + if (!_cancellationTokenSource.IsCancellationRequested) + { + _cancellationTokenSource.Cancel(); + } + _listener?.Stop(); + } + + public void Dispose() + { + Stop(); + _cancellationTokenSource.Dispose(); + } + } +} \ No newline at end of file diff --git a/EchoTcpServer/EchoServer.csproj b/EchoTcpServer/EchoServer.csproj index 2150e37..91b464a 100644 --- a/EchoTcpServer/EchoServer.csproj +++ b/EchoTcpServer/EchoServer.csproj @@ -1,4 +1,4 @@ - + Exe diff --git a/EchoTcpServer/IUdpClient.cs b/EchoTcpServer/IUdpClient.cs new file mode 100644 index 0000000..c298fd9 --- /dev/null +++ b/EchoTcpServer/IUdpClient.cs @@ -0,0 +1,18 @@ +using System; +using System.Net; +using System.Net.Sockets; + +namespace EchoTcpServer +{ + public interface IUdpClient : IDisposable + { + void Send(byte[] dgram, int bytes, IPEndPoint endPoint); + } + + public class StandardUdpClient : IUdpClient + { + private readonly UdpClient _client = new UdpClient(); + public void Send(byte[] dgram, int bytes, IPEndPoint endPoint) => _client.Send(dgram, bytes, endPoint); + public void Dispose() => _client.Dispose(); + } +} \ No newline at end of file diff --git a/EchoTcpServer/Program.cs b/EchoTcpServer/Program.cs index 5966c57..0982aec 100644 --- a/EchoTcpServer/Program.cs +++ b/EchoTcpServer/Program.cs @@ -1,173 +1,27 @@ using System; using System.Net; -using System.Net.Sockets; -using System.Text; -using System.Threading; using System.Threading.Tasks; -/// -/// This program was designed for test purposes only -/// Not for a review -/// -public class EchoServer +namespace EchoTcpServer { - private readonly int _port; - private TcpListener _listener; - private CancellationTokenSource _cancellationTokenSource; - - - public EchoServer(int port) + public class Program { - _port = port; - _cancellationTokenSource = new 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 static async Task Main(string[] args) { - try - { - TcpClient client = await _listener.AcceptTcpClientAsync(); - Console.WriteLine("Client connected."); + using var server = new EchoServer(IPAddress.Any, 5000); + _ = Task.Run(() => server.StartAsync()); - _ = Task.Run(() => HandleClientAsync(client, _cancellationTokenSource.Token)); - } - catch (ObjectDisposedException) - { - // Listener has been closed - break; - } - } + using var standardUdpClient = new StandardUdpClient(); + using var sender = new UdpTimedSender("127.0.0.1", 60000, standardUdpClient); - 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() - { - _cancellationTokenSource.Cancel(); - _listener.Stop(); - _cancellationTokenSource.Dispose(); - Console.WriteLine("Server stopped."); - } - - 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); + sender.StartSending(5000); Console.WriteLine("Press 'q' to quit..."); - while (Console.ReadKey(intercept: true).Key != ConsoleKey.Q) - { - // Just wait until 'q' is pressed - } + while (Console.ReadKey(intercept: true).Key != ConsoleKey.Q) { } 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 - { - //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} "); - } - 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/EchoTcpServer/UdpTimedSender.cs b/EchoTcpServer/UdpTimedSender.cs new file mode 100644 index 0000000..86e4cdd --- /dev/null +++ b/EchoTcpServer/UdpTimedSender.cs @@ -0,0 +1,64 @@ +using System; +using System.Linq; +using System.Net; +using System.Threading; + +namespace EchoTcpServer +{ + public class UdpTimedSender : IDisposable + { + private readonly string _host; + private readonly int _port; + private readonly IUdpClient _udpClient; + private Timer? _timer; + private ushort _messageCount = 0; + + public UdpTimedSender(string host, int port, IUdpClient udpClient) + { + _host = host; + _port = port; + _udpClient = udpClient; + } + + public void StartSending(int intervalMilliseconds) + { + if (_timer != null) + throw new InvalidOperationException("Sender is already running."); + + _timer = new Timer(SendMessageCallback, null, 0, intervalMilliseconds); + } + + private void SendMessageCallback(object? state) + { + try + { + Random rnd = new Random(); + byte[] samples = new byte[1024]; + rnd.NextBytes(samples); + _messageCount++; + + byte[] msg = new byte[] { 0x04, 0x84 }.Concat(BitConverter.GetBytes(_messageCount)).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/EchoTcpServerTests/EchoServerTests.cs b/EchoTcpServerTests/EchoServerTests.cs new file mode 100644 index 0000000..7cc0dae --- /dev/null +++ b/EchoTcpServerTests/EchoServerTests.cs @@ -0,0 +1,43 @@ +using NUnit.Framework; +using EchoTcpServer; +using System.Net; +using System.Net.Sockets; +using System.Text; +using System.Threading.Tasks; + +namespace EchoTcpServerTests +{ + [TestFixture] + public class EchoServerTests + { + [Test] + public async Task EchoServer_ShouldEchoDataBack() + { + // Arrange + int testPort = 5055; + using var server = new EchoServer(IPAddress.Loopback, testPort); + var serverTask = Task.Run(() => server.StartAsync()); + + await Task.Delay(100); + + using var client = new TcpClient(); + await client.ConnectAsync(IPAddress.Loopback, testPort); + using var stream = client.GetStream(); + + byte[] dataToSend = Encoding.UTF8.GetBytes("Hello Lab 6"); + byte[] receiveBuffer = new byte[1024]; + + // Act + await stream.WriteAsync(dataToSend, 0, dataToSend.Length); + int bytesRead = await stream.ReadAsync(receiveBuffer, 0, receiveBuffer.Length); + string response = Encoding.UTF8.GetString(receiveBuffer, 0, bytesRead); + + // Assert + Assert.That(response, Is.EqualTo("Hello Lab 6")); + + // Cleanup + server.Stop(); + await serverTask; + } + } +} \ No newline at end of file diff --git a/EchoTcpServerTests/EchoTcpServerTests.csproj b/EchoTcpServerTests/EchoTcpServerTests.csproj new file mode 100644 index 0000000..cc755d9 --- /dev/null +++ b/EchoTcpServerTests/EchoTcpServerTests.csproj @@ -0,0 +1,31 @@ + + + + net8.0 + enable + enable + + false + true + + + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + + + + + diff --git a/EchoTcpServerTests/UdpTimedSenderTests.cs b/EchoTcpServerTests/UdpTimedSenderTests.cs new file mode 100644 index 0000000..441b41f --- /dev/null +++ b/EchoTcpServerTests/UdpTimedSenderTests.cs @@ -0,0 +1,44 @@ +using Moq; +using NUnit.Framework; +using EchoTcpServer; +using System; +using System.Net; +using System.Threading.Tasks; + +namespace EchoTcpServerTests +{ + [TestFixture] + public class UdpTimedSenderTests + { + [Test] + public void StartSending_WhenAlreadyRunning_ShouldThrowException() + { + var mockUdp = new Mock(); + using var sender = new UdpTimedSender("127.0.0.1", 60000, mockUdp.Object); + + sender.StartSending(1000); + + Assert.Throws(() => sender.StartSending(1000)); + } + + [Test] + public async Task StartSending_ShouldSendMessagesPeriodically() + { + // Arrange + var mockUdp = new Mock(); + int callCount = 0; + mockUdp.Setup(u => u.Send(It.IsAny(), It.IsAny(), It.IsAny())) + .Callback(() => callCount++); + + using var sender = new UdpTimedSender("127.0.0.1", 60000, mockUdp.Object); + + // Act + sender.StartSending(50); + await Task.Delay(150); + sender.StopSending(); + + // Assert + Assert.That(callCount, Is.GreaterThanOrEqualTo(2), "The timer should have triggered the sending at least 2 times."); + } + } +} \ No newline at end of file diff --git a/NetSdrClient.sln b/NetSdrClient.sln index 42431fb..3b88600 100644 --- a/NetSdrClient.sln +++ b/NetSdrClient.sln @@ -9,6 +9,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NetSdrClientAppTests", "Net EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EchoServer", "EchoTcpServer\EchoServer.csproj", "{9179F2F7-EBEE-4A5D-9FD9-F6E3C18DD263}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EchoTcpServerTests", "EchoTcpServerTests\EchoTcpServerTests.csproj", "{7298C746-69F8-45A3-8A35-4513081C1936}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -27,6 +29,10 @@ Global {9179F2F7-EBEE-4A5D-9FD9-F6E3C18DD263}.Debug|Any CPU.Build.0 = Debug|Any CPU {9179F2F7-EBEE-4A5D-9FD9-F6E3C18DD263}.Release|Any CPU.ActiveCfg = Release|Any CPU {9179F2F7-EBEE-4A5D-9FD9-F6E3C18DD263}.Release|Any CPU.Build.0 = Release|Any CPU + {7298C746-69F8-45A3-8A35-4513081C1936}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {7298C746-69F8-45A3-8A35-4513081C1936}.Debug|Any CPU.Build.0 = Debug|Any CPU + {7298C746-69F8-45A3-8A35-4513081C1936}.Release|Any CPU.ActiveCfg = Release|Any CPU + {7298C746-69F8-45A3-8A35-4513081C1936}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE From 28d2e50cd8b9d8b068f8d2c42739b6b94d091a99 Mon Sep 17 00:00:00 2001 From: Danylo Kovalenko Date: Fri, 24 Apr 2026 17:40:32 +0300 Subject: [PATCH 18/19] lab6 some changes --- EchoTcpServer/IUdpClient.cs | 2 ++ EchoTcpServer/Program.cs | 2 ++ EchoTcpServer/UdpTimedSender.cs | 4 ++-- EchoTcpServerTests/EchoServerTests.cs | 15 +++++++++++++++ 4 files changed, 21 insertions(+), 2 deletions(-) diff --git a/EchoTcpServer/IUdpClient.cs b/EchoTcpServer/IUdpClient.cs index c298fd9..d21bc4e 100644 --- a/EchoTcpServer/IUdpClient.cs +++ b/EchoTcpServer/IUdpClient.cs @@ -1,6 +1,7 @@ using System; using System.Net; using System.Net.Sockets; +using System.Diagnostics.CodeAnalysis; namespace EchoTcpServer { @@ -9,6 +10,7 @@ public interface IUdpClient : IDisposable void Send(byte[] dgram, int bytes, IPEndPoint endPoint); } + [ExcludeFromCodeCoverage] public class StandardUdpClient : IUdpClient { private readonly UdpClient _client = new UdpClient(); diff --git a/EchoTcpServer/Program.cs b/EchoTcpServer/Program.cs index 0982aec..011b208 100644 --- a/EchoTcpServer/Program.cs +++ b/EchoTcpServer/Program.cs @@ -1,9 +1,11 @@ using System; using System.Net; using System.Threading.Tasks; +using System.Diagnostics.CodeAnalysis; namespace EchoTcpServer { + [ExcludeFromCodeCoverage] public class Program { public static async Task Main(string[] args) diff --git a/EchoTcpServer/UdpTimedSender.cs b/EchoTcpServer/UdpTimedSender.cs index 86e4cdd..f5885ba 100644 --- a/EchoTcpServer/UdpTimedSender.cs +++ b/EchoTcpServer/UdpTimedSender.cs @@ -2,6 +2,7 @@ using System.Linq; using System.Net; using System.Threading; +using System.Security.Cryptography; namespace EchoTcpServer { @@ -32,9 +33,8 @@ private void SendMessageCallback(object? state) { try { - Random rnd = new Random(); byte[] samples = new byte[1024]; - rnd.NextBytes(samples); + RandomNumberGenerator.Fill(samples); _messageCount++; byte[] msg = new byte[] { 0x04, 0x84 }.Concat(BitConverter.GetBytes(_messageCount)).Concat(samples).ToArray(); diff --git a/EchoTcpServerTests/EchoServerTests.cs b/EchoTcpServerTests/EchoServerTests.cs index 7cc0dae..21a6f22 100644 --- a/EchoTcpServerTests/EchoServerTests.cs +++ b/EchoTcpServerTests/EchoServerTests.cs @@ -39,5 +39,20 @@ public async Task EchoServer_ShouldEchoDataBack() server.Stop(); await serverTask; } + + [Test] + public async Task Stop_ShouldGracefullyCancelListenerLoop() + { + // Arrange + using var server = new EchoServer(IPAddress.Loopback, 5056); + var serverTask = Task.Run(() => server.StartAsync()); + + await Task.Delay(50); + + // Act + server.Stop(); + + Assert.DoesNotThrowAsync(async () => await serverTask); + } } } \ No newline at end of file From 64e3729e8e85f02d5928f76342ff38d425c855cb Mon Sep 17 00:00:00 2001 From: Danylo Kovalenko Date: Wed, 29 Apr 2026 13:36:19 +0300 Subject: [PATCH 19/19] add dependabot configuration --- .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..446b951 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,6 @@ +version: 2 +updates: + - package-ecosystem: "nuget" + directory: "/" + schedule: + interval: "weekly"