Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
77cdca9
lab1: configure SonarCloud CI
MinTins Mar 22, 2026
16992a5
lab1: fix sonarcloud keys, remove build.yml
MinTins Mar 22, 2026
7b7ab27
lab1: fix sonarcloud keys
MinTins Mar 22, 2026
d186042
lab1: fix
MinTins Mar 22, 2026
54ab14c
lab1: disable qualitygate wait, suppress node deprecation warning
MinTins Mar 22, 2026
f92d12b
lab2: make _tcpClient readonly
MinTins Mar 22, 2026
4c83db4
lab2: make _udpClient readonly
MinTins Mar 22, 2026
f1144bc
lab2: remove empty statement in NetSdrClient
MinTins Mar 22, 2026
1c999d9
lab2: discard unused out variables type, code, sequenceNum
MinTins Mar 22, 2026
6642cb9
lab2: make _host readonly in TcpClientWrapper
MinTins Mar 22, 2026
00a7706
lab2: make _port readonly in TcpClientWrapper
MinTins Mar 22, 2026
ccfac4b
lab2: remove unused exception variable in OperationCanceledException …
MinTins Mar 22, 2026
86430a8
lab2: make _cancellationTokenSource readonly in EchoServer
MinTins Mar 22, 2026
635625a
lab3: add unit tests, coverage 87%, fix Enum.IsDefined bug, exclude i…
MinTins Mar 22, 2026
1fec8b4
lab3: fix sonarcloud.yml
MinTins Mar 22, 2026
e27cde8
lab4: remove code duplications — extract helper, delegate duplicate m…
MinTins May 28, 2026
6b578ca
ci: trigger SonarCloud analysis on master (lab4 baseline)
MinTins May 28, 2026
bb69177
Merge branch 'lab4/fix-duplications' into master
MinTins May 28, 2026
6b72e76
lab5: add NetArchTest architecture rules + intentional breaking rule
MinTins May 28, 2026
031a602
lab5: remove intentional breaking rule — all arch tests green
MinTins May 28, 2026
032d3d3
lab5: fix ArchitectureTests — account for UdpClientWrapper in global …
MinTins May 28, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 12 additions & 10 deletions .github/workflows/sonarcloud.yml
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,8 @@ jobs:
runs-on: windows-latest # безпечно для будь-яких .NET проектів
steps:
- uses: actions/checkout@v4
env:
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
with: { fetch-depth: 0 }

- uses: actions/setup-dotnet@v4
Expand All @@ -56,27 +58,27 @@ jobs:
dotnet tool install --global dotnet-sonarscanner
echo "$env:USERPROFILE\.dotnet\tools" >> $env:GITHUB_PATH
dotnet sonarscanner begin `
/k:"ppanchen_NetSdrClient" `
/o:"ppanchen" `
/k:"MinTins_ReengineeringCourse" `
/o:"roman-flakei" `
/d:sonar.token="${{ secrets.SONAR_TOKEN }}" `
/d:sonar.cs.opencover.reportsPaths="**/coverage.xml" `
/d:sonar.cpd.cs.minimumTokens=40 `
/d:sonar.cpd.cs.minimumLines=5 `
/d:sonar.exclusions=**/bin/**,**/obj/**,**/sonarcloud.yml `
/d:sonar.qualitygate.wait=true
/d:sonar.qualitygate.wait=false
shell: pwsh
# 2) BUILD & TEST
- name: Restore
run: dotnet restore NetSdrClient.sln
- name: Build
run: dotnet build NetSdrClient.sln -c Release --no-restore
#- name: Tests with coverage (OpenCover)
# run: |
# dotnet test NetSdrClientAppTests/NetSdrClientAppTests.csproj -c Release --no-build `
# /p:CollectCoverage=true `
# /p:CoverletOutput=TestResults/coverage.xml `
# /p:CoverletOutputFormat=opencover
# shell: pwsh
- 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 }}"
Expand Down
2 changes: 1 addition & 1 deletion EchoTcpServer/EchoServer.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
Expand Down
2 changes: 1 addition & 1 deletion EchoTcpServer/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
109 changes: 109 additions & 0 deletions NetSdrArchTests/ArchitectureTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
using NetArchTest.Rules;
using NUnit.Framework;

namespace NetSdrArchTests
{
/// <summary>
/// Lab 5 — Architectural rules using NetArchTest.
/// These tests enforce dependency constraints between layers.
///
/// Note on project structure:
/// - TcpClientWrapper, ITcpClient → namespace NetSdrClientApp.Networking
/// - UdpClientWrapper, IUdpClient → global namespace (no namespace declaration)
/// Rules are written to reflect this actual structure.
/// </summary>
public class ArchitectureTests
{
private static readonly System.Reflection.Assembly AppAssembly =
typeof(NetSdrClientApp.Networking.ITcpClient).Assembly;

// ---------------------------------------------------------------
// Rule 1: Networking namespace must NOT depend on Messages namespace
// Rationale: networking is infrastructure — must not know about
// domain-level message construction
// ---------------------------------------------------------------
[Test]
public void Networking_ShouldNotDependOn_Messages()
{
var result = Types
.InAssembly(AppAssembly)
.That()
.ResideInNamespace("NetSdrClientApp.Networking")
.ShouldNot()
.HaveDependencyOn("NetSdrClientApp.Messages")
.GetResult();

Assert.That(result.IsSuccessful, Is.True,
"Networking layer must not depend on Messages layer. " +
"Failing types: " + string.Join(", ", result.FailingTypeNames ?? []));
}

// ---------------------------------------------------------------
// Rule 2: Messages namespace must NOT depend on Networking namespace
// Rationale: message building is pure domain logic,
// independent of transport implementation
// ---------------------------------------------------------------
[Test]
public void Messages_ShouldNotDependOn_Networking()
{
var result = Types
.InAssembly(AppAssembly)
.That()
.ResideInNamespace("NetSdrClientApp.Messages")
.ShouldNot()
.HaveDependencyOn("NetSdrClientApp.Networking")
.GetResult();

Assert.That(result.IsSuccessful, Is.True,
"Messages layer must not depend on Networking layer. " +
"Failing types: " + string.Join(", ", result.FailingTypeNames ?? []));
}

// ---------------------------------------------------------------
// Rule 3: TcpClientWrapper must implement ITcpClient
// Rationale: TCP networking component must be abstracted
// behind interface for testability and substitutability
// Note: scoped to ITcpClient only because UdpClientWrapper/IUdpClient
// reside in global namespace (outside NetSdrClientApp.Networking)
// ---------------------------------------------------------------
[Test]
public void TcpClientWrapper_ShouldImplement_ITcpClient()
{
var result = Types
.InAssembly(AppAssembly)
.That()
.ResideInNamespace("NetSdrClientApp.Networking")
.And()
.AreClasses()
.Should()
.ImplementInterface(typeof(NetSdrClientApp.Networking.ITcpClient))
.GetResult();

Assert.That(result.IsSuccessful, Is.True,
"All concrete classes in NetSdrClientApp.Networking must implement ITcpClient. " +
"Failing types: " + string.Join(", ", result.FailingTypeNames ?? []));
}

// ---------------------------------------------------------------
// Rule 4: NetSdrClient orchestrator must reside in root namespace
// Rationale: the top-level client must not leak into sub-layers
// ---------------------------------------------------------------
[Test]
public void NetSdrClient_ShouldResideIn_RootNamespace()
{
var result = Types
.InAssembly(AppAssembly)
.That()
.HaveNameStartingWith("NetSdrClient")
.And()
.AreClasses()
.Should()
.ResideInNamespace("NetSdrClientApp")
.GetResult();

Assert.That(result.IsSuccessful, Is.True,
"NetSdrClient class must reside in root NetSdrClientApp namespace. " +
"Failing types: " + string.Join(", ", result.FailingTypeNames ?? []));
}
}
}
28 changes: 28 additions & 0 deletions NetSdrArchTests/NetSdrArchTests.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<LangVersion>latest</LangVersion>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<IsPackable>false</IsPackable>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="coverlet.collector" Version="6.0.4" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="18.6.0" />
<PackageReference Include="NetArchTest.Rules" Version="1.3.2" />
<PackageReference Include="NUnit" Version="4.3.2" />
<PackageReference Include="NUnit.Analyzers" Version="4.7.0" />
<PackageReference Include="NUnit3TestAdapter" Version="5.0.0" />
</ItemGroup>

<ItemGroup>
<Using Include="NUnit.Framework" />
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\NetSdrClientApp\NetSdrClientApp.csproj" />
</ItemGroup>

</Project>
42 changes: 42 additions & 0 deletions NetSdrClient.sln
Original file line number Diff line number Diff line change
Expand Up @@ -9,24 +9,66 @@ 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}") = "NetSdrArchTests", "NetSdrArchTests\NetSdrArchTests.csproj", "{23A9A89D-B1B6-495C-B709-3E3C88B0B957}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Debug|x64 = Debug|x64
Debug|x86 = Debug|x86
Release|Any CPU = Release|Any CPU
Release|x64 = Release|x64
Release|x86 = Release|x86
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{3CA739B1-888F-4B84-8E76-387640E3B3E9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{3CA739B1-888F-4B84-8E76-387640E3B3E9}.Debug|Any CPU.Build.0 = Debug|Any CPU
{3CA739B1-888F-4B84-8E76-387640E3B3E9}.Debug|x64.ActiveCfg = Debug|Any CPU
{3CA739B1-888F-4B84-8E76-387640E3B3E9}.Debug|x64.Build.0 = Debug|Any CPU
{3CA739B1-888F-4B84-8E76-387640E3B3E9}.Debug|x86.ActiveCfg = Debug|Any CPU
{3CA739B1-888F-4B84-8E76-387640E3B3E9}.Debug|x86.Build.0 = Debug|Any CPU
{3CA739B1-888F-4B84-8E76-387640E3B3E9}.Release|Any CPU.ActiveCfg = Release|Any CPU
{3CA739B1-888F-4B84-8E76-387640E3B3E9}.Release|Any CPU.Build.0 = Release|Any CPU
{3CA739B1-888F-4B84-8E76-387640E3B3E9}.Release|x64.ActiveCfg = Release|Any CPU
{3CA739B1-888F-4B84-8E76-387640E3B3E9}.Release|x64.Build.0 = Release|Any CPU
{3CA739B1-888F-4B84-8E76-387640E3B3E9}.Release|x86.ActiveCfg = Release|Any CPU
{3CA739B1-888F-4B84-8E76-387640E3B3E9}.Release|x86.Build.0 = Release|Any CPU
{D0155366-89B4-4BA4-90E2-2ECC8C1EB915}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{D0155366-89B4-4BA4-90E2-2ECC8C1EB915}.Debug|Any CPU.Build.0 = Debug|Any CPU
{D0155366-89B4-4BA4-90E2-2ECC8C1EB915}.Debug|x64.ActiveCfg = Debug|Any CPU
{D0155366-89B4-4BA4-90E2-2ECC8C1EB915}.Debug|x64.Build.0 = Debug|Any CPU
{D0155366-89B4-4BA4-90E2-2ECC8C1EB915}.Debug|x86.ActiveCfg = Debug|Any CPU
{D0155366-89B4-4BA4-90E2-2ECC8C1EB915}.Debug|x86.Build.0 = Debug|Any CPU
{D0155366-89B4-4BA4-90E2-2ECC8C1EB915}.Release|Any CPU.ActiveCfg = Release|Any CPU
{D0155366-89B4-4BA4-90E2-2ECC8C1EB915}.Release|Any CPU.Build.0 = Release|Any CPU
{D0155366-89B4-4BA4-90E2-2ECC8C1EB915}.Release|x64.ActiveCfg = Release|Any CPU
{D0155366-89B4-4BA4-90E2-2ECC8C1EB915}.Release|x64.Build.0 = Release|Any CPU
{D0155366-89B4-4BA4-90E2-2ECC8C1EB915}.Release|x86.ActiveCfg = Release|Any CPU
{D0155366-89B4-4BA4-90E2-2ECC8C1EB915}.Release|x86.Build.0 = Release|Any CPU
{9179F2F7-EBEE-4A5D-9FD9-F6E3C18DD263}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{9179F2F7-EBEE-4A5D-9FD9-F6E3C18DD263}.Debug|Any CPU.Build.0 = Debug|Any CPU
{9179F2F7-EBEE-4A5D-9FD9-F6E3C18DD263}.Debug|x64.ActiveCfg = Debug|Any CPU
{9179F2F7-EBEE-4A5D-9FD9-F6E3C18DD263}.Debug|x64.Build.0 = Debug|Any CPU
{9179F2F7-EBEE-4A5D-9FD9-F6E3C18DD263}.Debug|x86.ActiveCfg = Debug|Any CPU
{9179F2F7-EBEE-4A5D-9FD9-F6E3C18DD263}.Debug|x86.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
{9179F2F7-EBEE-4A5D-9FD9-F6E3C18DD263}.Release|x64.ActiveCfg = Release|Any CPU
{9179F2F7-EBEE-4A5D-9FD9-F6E3C18DD263}.Release|x64.Build.0 = Release|Any CPU
{9179F2F7-EBEE-4A5D-9FD9-F6E3C18DD263}.Release|x86.ActiveCfg = Release|Any CPU
{9179F2F7-EBEE-4A5D-9FD9-F6E3C18DD263}.Release|x86.Build.0 = Release|Any CPU
{23A9A89D-B1B6-495C-B709-3E3C88B0B957}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{23A9A89D-B1B6-495C-B709-3E3C88B0B957}.Debug|Any CPU.Build.0 = Debug|Any CPU
{23A9A89D-B1B6-495C-B709-3E3C88B0B957}.Debug|x64.ActiveCfg = Debug|Any CPU
{23A9A89D-B1B6-495C-B709-3E3C88B0B957}.Debug|x64.Build.0 = Debug|Any CPU
{23A9A89D-B1B6-495C-B709-3E3C88B0B957}.Debug|x86.ActiveCfg = Debug|Any CPU
{23A9A89D-B1B6-495C-B709-3E3C88B0B957}.Debug|x86.Build.0 = Debug|Any CPU
{23A9A89D-B1B6-495C-B709-3E3C88B0B957}.Release|Any CPU.ActiveCfg = Release|Any CPU
{23A9A89D-B1B6-495C-B709-3E3C88B0B957}.Release|Any CPU.Build.0 = Release|Any CPU
{23A9A89D-B1B6-495C-B709-3E3C88B0B957}.Release|x64.ActiveCfg = Release|Any CPU
{23A9A89D-B1B6-495C-B709-3E3C88B0B957}.Release|x64.Build.0 = Release|Any CPU
{23A9A89D-B1B6-495C-B709-3E3C88B0B957}.Release|x86.ActiveCfg = Release|Any CPU
{23A9A89D-B1B6-495C-B709-3E3C88B0B957}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand Down
2 changes: 1 addition & 1 deletion NetSdrClientApp/Messages/NetSdrMessageHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand Down
36 changes: 16 additions & 20 deletions NetSdrClientApp/NetSdrClient.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
using NetSdrClientApp.Messages;
using NetSdrClientApp.Messages;
using NetSdrClientApp.Networking;
using System;
using System.Collections.Generic;
Expand All @@ -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; }

Expand Down Expand Up @@ -60,13 +60,9 @@ public void Disconect()

public async Task StartIQAsync()
{
if (!_tcpClient.Connected)
{
Console.WriteLine("No active connection.");
return;
}
if (!EnsureConnected()) return;

; var iqDataMode = (byte)0x80;
var iqDataMode = (byte)0x80;
var start = (byte)0x02;
var fifo16bitCaptureMode = (byte)0x01;
var n = (byte)1;
Expand All @@ -84,11 +80,7 @@ public async Task StartIQAsync()

public async Task StopIQAsync()
{
if (!_tcpClient.Connected)
{
Console.WriteLine("No active connection.");
return;
}
if (!EnsureConnected()) return;

var stop = (byte)0x01;

Expand All @@ -114,9 +106,17 @@ public async Task ChangeFrequencyAsync(long hz, int channel)
await SendTcpRequest(msg);
}


private bool EnsureConnected()
{
if (_tcpClient.Connected) return true;
Console.WriteLine("No active connection.");
return false;
}

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}"));
Expand All @@ -135,11 +135,7 @@ private void _udpClient_MessageReceived(object? sender, byte[] e)

private async Task<byte[]> SendTcpRequest(byte[] msg)
{
if (!_tcpClient.Connected)
{
Console.WriteLine("No active connection.");
return null;
}
if (!EnsureConnected()) return null;

responseTaskSource = new TaskCompletionSource<byte[]>(TaskCreationOptions.RunContinuationsAsynchronously);
var responseTask = responseTaskSource.Task;
Expand Down
2 changes: 1 addition & 1 deletion NetSdrClientApp/NetSdrClientApp.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
Expand Down
Loading