Files
UA-.NETStandard/tests/Opc.Ua.Core.Diagnostics.Tests/Frame/LoopbackFrameBuilderTests.cs
2026-07-16 06:25:54 +02:00

178 lines
7.4 KiB
C#

/* ========================================================================
* Copyright (c) 2005-2026 The OPC Foundation, Inc. All rights reserved.
*
* OPC Foundation MIT License 1.00
*
* Permission is hereby granted, free of charge, to any person
* obtaining a copy of this software and associated documentation
* files (the "Software"), to deal in the Software without
* restriction, including without limitation the rights to use,
* copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following
* conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
* OTHER DEALINGS IN THE SOFTWARE.
*
* The complete license agreement can be found here:
* http://opcfoundation.org/License/MIT/1.00/
* ======================================================================*/
using System;
using System.Buffers.Binary;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using NUnit.Framework;
using Opc.Ua.Pcap.Frame;
namespace Opc.Ua.Pcap.Tests.Frame
{
[TestFixture]
public sealed class LoopbackFrameBuilderTests : TempDirectoryFixture
{
[Test]
public void HeadersAreCorrect()
{
byte[] chunk = [0xDE, 0xAD, 0xBE, 0xEF];
byte[] packet = LoopbackFrameBuilder.Build(fromClient: true, 0x1234, chunk);
Assert.That(packet[..4], Is.EqualTo(new byte[] { 0x02, 0x00, 0x00, 0x00 }).AsCollection);
Assert.That(packet[4] >> 4, Is.EqualTo(4));
Assert.That(packet[4] & 0x0F, Is.EqualTo(5));
Assert.That(BinaryPrimitives.ReadUInt16BigEndian(packet.AsSpan(6)), Is.EqualTo(44));
Assert.That(packet[13], Is.EqualTo(6));
Assert.That(packet[36] >> 4, Is.EqualTo(5));
Assert.That(packet[44..], Is.EqualTo(chunk).AsCollection);
Assert.That(packet, Has.Length.EqualTo(48));
}
[Test]
public void Ipv4ChecksumVerifies()
{
byte[] packet = LoopbackFrameBuilder.Build(fromClient: true, 0x1234, [1, 2, 3]);
ushort sum = PcapTestHelpers.FoldOnesComplement(PcapTestHelpers.SumWords(packet.AsSpan(4, 20)));
Assert.That(sum, Is.EqualTo(0xFFFF));
}
[Test]
public void TcpChecksumVerifies()
{
byte[] packet = LoopbackFrameBuilder.Build(fromClient: true, 0x1234, [1, 2, 3]);
uint sum = PcapTestHelpers.SumWords(packet.AsSpan(16, 4)) + PcapTestHelpers.SumWords(packet.AsSpan(20, 4));
sum += 6;
sum += (uint)(packet.Length - 24);
sum += PcapTestHelpers.SumWords(packet.AsSpan(24));
Assert.That(PcapTestHelpers.FoldOnesComplement(sum), Is.EqualTo(0xFFFF));
}
[Test]
public void DirectionFlipsSourceAndDestination()
{
byte[] fromClient = LoopbackFrameBuilder.Build(fromClient: true, 0x1234, [1]);
byte[] fromServer = LoopbackFrameBuilder.Build(fromClient: false, 0x1234, [1]);
Assert.That(fromClient.AsSpan(16, 4).ToArray(), Is.EqualTo(fromServer.AsSpan(20, 4).ToArray()).AsCollection);
Assert.That(fromClient.AsSpan(20, 4).ToArray(), Is.EqualTo(fromServer.AsSpan(16, 4).ToArray()).AsCollection);
Assert.That(fromClient.AsSpan(24, 2).ToArray(), Is.EqualTo(fromServer.AsSpan(26, 2).ToArray()).AsCollection);
Assert.That(fromClient.AsSpan(26, 2).ToArray(), Is.EqualTo(fromServer.AsSpan(24, 2).ToArray()).AsCollection);
}
[Test]
public void FullChannelIdDistinguishesSyntheticTcpFlows()
{
byte[] first = LoopbackFrameBuilder.Build(fromClient: true, 1, [1]);
byte[] second = LoopbackFrameBuilder.Build(fromClient: true, 0x4001, [2]);
Assert.That(
first.AsSpan(16, 4).ToArray(),
Is.Not.EqualTo(second.AsSpan(16, 4).ToArray()).AsCollection);
Assert.That(
first.AsSpan(24, 2).ToArray(),
Is.EqualTo(second.AsSpan(24, 2).ToArray()).AsCollection);
}
[Test]
public async Task BuiltPacketsRoundTripThroughPcap()
{
byte[] chunk = [0xDE, 0xAD, 0xBE, 0xEF];
byte[] packet = LoopbackFrameBuilder.Build(fromClient: true, 0x1234, chunk);
string path = CreateTempPath("loopback.pcap");
var writer = new PcapFileWriter(path, PcapFileWriter.LinkTypeNull);
try
{
await writer.WriteAsync(DateTimeOffset.UtcNow, packet, CancellationToken.None).ConfigureAwait(false);
}
finally
{
await writer.DisposeAsync().ConfigureAwait(false);
}
List<PcapRecord> records = await PcapTestHelpers.ToListAsync(
PcapFileReader.ReadAllAsync(path, CancellationToken.None),
maxCount: 1).ConfigureAwait(false);
Assert.That(records, Has.Count.EqualTo(1));
Assert.That(records[0].Data.ToArray(), Is.EqualTo(packet).AsCollection);
Assert.That(records[0].Data.ToArray()[44..], Is.EqualTo(chunk).AsCollection);
}
[Test]
public void MaximumUaChunkBuildsSequentialTcpPackets()
{
byte[] chunk = new byte[ushort.MaxValue];
for (int ii = 0; ii < chunk.Length; ii++)
{
chunk[ii] = (byte)ii;
}
const uint sequenceNumber = 1234;
byte[][] packets = LoopbackFrameBuilder.BuildPackets(
fromClient: true,
channelId: 0x1234,
sequenceNumber,
chunk);
Assert.That(packets, Has.Length.EqualTo(2));
Assert.That(
BinaryPrimitives.ReadUInt16BigEndian(packets[0].AsSpan(6)),
Is.EqualTo(ushort.MaxValue - 4));
Assert.That(
BinaryPrimitives.ReadUInt32BigEndian(packets[0].AsSpan(28)),
Is.EqualTo(sequenceNumber));
Assert.That(
BinaryPrimitives.ReadUInt32BigEndian(packets[1].AsSpan(28)),
Is.EqualTo(sequenceNumber + LoopbackFrameBuilder.MaxTcpPayloadSize));
byte[] reassembled = new byte[chunk.Length];
packets[0].AsSpan(44).CopyTo(reassembled);
packets[1].AsSpan(44).CopyTo(
reassembled.AsSpan(LoopbackFrameBuilder.MaxTcpPayloadSize));
Assert.That(reassembled, Is.EqualTo(chunk).AsCollection);
}
[Test]
public void SinglePacketBuildRejectsOversizedPayload()
{
Assert.That(
() => LoopbackFrameBuilder.Build(
fromClient: true,
channelId: 0x1234,
new byte[LoopbackFrameBuilder.MaxTcpPayloadSize + 1]),
Throws.TypeOf<ArgumentOutOfRangeException>());
}
}
}