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 }}"
diff --git a/.github/workflows/sonarcloud.yml b/.github/workflows/sonarcloud.yml
index e784069..0edf275 100644
--- a/.github/workflows/sonarcloud.yml
+++ b/.github/workflows/sonarcloud.yml
@@ -1,38 +1,10 @@
-# This workflow uses actions that are not certified by GitHub.
-# They are provided by a third-party and are governed by
-# separate terms of service, privacy policy, and support
-# documentation.
-
-# This workflow helps you trigger a SonarCloud analysis of your code and populates
-# GitHub Code Scanning alerts with the vulnerabilities found.
-# Free for open source project.
-
-# 1. Login to SonarCloud.io using your GitHub account
-
-# 2. Import your project on SonarCloud
-# * Add your GitHub organization first, then add your repository as a new project.
-# * Please note that many languages are eligible for automatic analysis,
-# which means that the analysis will start automatically without the need to set up GitHub Actions.
-# * This behavior can be changed in Administration > Analysis Method.
-#
-# 3. Follow the SonarCloud in-product tutorial
-# * a. Copy/paste the Project Key and the Organization Key into the args parameter below
-# (You'll find this information in SonarCloud. Click on "Information" at the bottom left)
-#
-# * b. Generate a new token and add it to your Github repository's secrets using the name SONAR_TOKEN
-# (On SonarCloud, click on your avatar on top-right > My account > Security
-# or go directly to https://sonarcloud.io/account/security/)
-
-# Feel free to take a look at our documentation (https://docs.sonarcloud.io/getting-started/github/)
-# or reach out to our community forum if you need some help (https://community.sonarsource.com/c/help/sc/9)
-
name: SonarCloud analysis
on:
push:
- branches: [ "master" ]
+ branches: ["master"]
pull_request:
- branches: [ "master" ]
+ branches: ["master"]
workflow_dispatch:
permissions:
@@ -41,14 +13,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 +28,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 `
@@ -70,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/EchoTcpServer/Program.cs b/EchoTcpServer/Program.cs
index 5966c57..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 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
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;
diff --git a/NetSdrClientApp/NetSdrClient.cs b/NetSdrClientApp/NetSdrClient.cs
index b0a7c05..d153baf 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; }
@@ -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;
@@ -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);
@@ -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);
@@ -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}"));
}
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..6a12682 100644
--- a/NetSdrClientAppTests/NetSdrClientTests.cs
+++ b/NetSdrClientAppTests/NetSdrClientTests.cs
@@ -115,5 +115,126 @@ 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);
+ }
+
+ [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
diff --git a/README.md b/README.md
index b3a9029..ace6cce 100644
--- a/README.md
+++ b/README.md
@@ -1,12 +1,5 @@
# Лабораторні з реінжинірингу (8×)
-[](https://sonarcloud.io/summary/new_code?id=ppanchen_NetSdrClient)
-[](https://sonarcloud.io/summary/new_code?id=ppanchen_NetSdrClient)
-[](https://sonarcloud.io/summary/new_code?id=ppanchen_NetSdrClient)
-[](https://sonarcloud.io/summary/new_code?id=ppanchen_NetSdrClient)
-[](https://sonarcloud.io/summary/new_code?id=ppanchen_NetSdrClient)
-[](https://sonarcloud.io/summary/new_code?id=ppanchen_NetSdrClient)
-[](https://sonarcloud.io/summary/new_code?id=ppanchen_NetSdrClient)
-[](https://sonarcloud.io/summary/new_code?id=ppanchen_NetSdrClient)
+[](https://sonarcloud.io/summary/new_code?id=KonstantinDanger_reengineering-course-fork2)
Цей репозиторій використовується для курсу **реінжиніринг ПЗ**.