From 714ac62ab1f92b9a200c1c0a3c30eff98239830f Mon Sep 17 00:00:00 2001 From: Michael Croes Date: Mon, 31 Jul 2023 22:57:03 +0200 Subject: [PATCH] test: Add CommunicationSequence --- S7.Net.UnitTest/Framework/IsExternalInit.cs | 7 ++ .../Infrastructure/CommunicationSequence.cs | 82 +++++++++++++++++++ .../Infrastructure/RequestResponsePair.cs | 3 + S7.Net.UnitTest/Infrastructure/Responder.cs | 80 ++++++++++++++++++ S7.Net.UnitTest/S7.Net.UnitTest.csproj | 1 + 5 files changed, 173 insertions(+) create mode 100644 S7.Net.UnitTest/Framework/IsExternalInit.cs create mode 100644 S7.Net.UnitTest/Infrastructure/CommunicationSequence.cs create mode 100644 S7.Net.UnitTest/Infrastructure/RequestResponsePair.cs create mode 100644 S7.Net.UnitTest/Infrastructure/Responder.cs diff --git a/S7.Net.UnitTest/Framework/IsExternalInit.cs b/S7.Net.UnitTest/Framework/IsExternalInit.cs new file mode 100644 index 0000000..f70856c --- /dev/null +++ b/S7.Net.UnitTest/Framework/IsExternalInit.cs @@ -0,0 +1,7 @@ +using System.ComponentModel; + +namespace System.Runtime.CompilerServices +{ + [EditorBrowsable(EditorBrowsableState.Never)] + internal record IsExternalInit; +} \ No newline at end of file diff --git a/S7.Net.UnitTest/Infrastructure/CommunicationSequence.cs b/S7.Net.UnitTest/Infrastructure/CommunicationSequence.cs new file mode 100644 index 0000000..429b346 --- /dev/null +++ b/S7.Net.UnitTest/Infrastructure/CommunicationSequence.cs @@ -0,0 +1,82 @@ +using System; +using System.Buffers; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using System.Net; +using System.Net.Sockets; +using System.Threading.Tasks; + +namespace S7.Net.UnitTest; + +internal class CommunicationSequence : IEnumerable +{ + private readonly List _requestResponsePairs = new List(); + + public CommunicationSequence() + { + + } + + public void Add(string requestPattern, string responsePattern) + { + _requestResponsePairs.Add(new RequestResponsePair(requestPattern, responsePattern)); + } + + public IEnumerator GetEnumerator() + { + return _requestResponsePairs.GetEnumerator(); + } + + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + + public Task Serve(out int port) + { + var socket = CreateBoundListenSocket(out port); + socket.Listen(0); + + async Task Impl() + { + await Task.Yield(); + var socketIn = socket.Accept(); + + var buffer = ArrayPool.Shared.Rent(1024); + try + { + foreach (var pair in _requestResponsePairs) + { + var bytesReceived = socketIn.Receive(buffer, SocketFlags.None); + + var received = buffer.Take(bytesReceived).ToArray(); + Console.WriteLine($"=> {BitConverter.ToString(received)}"); + + var response = Responder.Respond(pair, received); + + Console.WriteLine($"<= {BitConverter.ToString(response)}"); + socketIn.Send(response); + } + } + finally + { + ArrayPool.Shared.Return(buffer); + } + + socketIn.Close(); + } + + return Impl(); + } + + private static Socket CreateBoundListenSocket(out int port) + { + var socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); + var endpoint = new IPEndPoint(IPAddress.Loopback, 0); + + socket.Bind(endpoint); + + var localEndpoint = (IPEndPoint)socket.LocalEndPoint!; + port = localEndpoint.Port; + + return socket; + } +} diff --git a/S7.Net.UnitTest/Infrastructure/RequestResponsePair.cs b/S7.Net.UnitTest/Infrastructure/RequestResponsePair.cs new file mode 100644 index 0000000..390ee62 --- /dev/null +++ b/S7.Net.UnitTest/Infrastructure/RequestResponsePair.cs @@ -0,0 +1,3 @@ +namespace S7.Net.UnitTest; + +internal record RequestResponsePair(string RequestPattern, string ResponsePattern); diff --git a/S7.Net.UnitTest/Infrastructure/Responder.cs b/S7.Net.UnitTest/Infrastructure/Responder.cs new file mode 100644 index 0000000..0fa15ea --- /dev/null +++ b/S7.Net.UnitTest/Infrastructure/Responder.cs @@ -0,0 +1,80 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.IO; + +namespace S7.Net.UnitTest; + +internal static class Responder +{ + private const string Comment = "//"; + private static char[] Space = " ".ToCharArray(); + + public static byte[] Respond(RequestResponsePair pair, byte[] request) + { + var offset = 0; + var matches = new Dictionary(); + var res = new List(); + using var requestReader = new StringReader(pair.RequestPattern); + + string line; + while ((line = requestReader.ReadLine()) != null) + { + var tokens = line.Split(Space, StringSplitOptions.RemoveEmptyEntries); + foreach (var token in tokens) + { + if (token.StartsWith(Comment)) break; + + if (offset >= request.Length) + { + throw new Exception("Request pattern has more data than request."); + } + + var received = request[offset]; + + if (token.Length == 2 && byte.TryParse(token, NumberStyles.HexNumber, CultureInfo.InvariantCulture, out var value)) + { + // Number, exact match + if (value != received) + { + throw new Exception($"Incorrect data at offset {offset}. Expected {value:X2}, received {received:X2}."); + } + } + else + { + matches[token] = received; + } + + offset++; + } + } + + if (offset != request.Length) throw new Exception("Request contained more data than request pattern."); + + using var responseReader = new StringReader(pair.ResponsePattern); + while ((line = responseReader.ReadLine()) != null) + { + var tokens = line.Split(Space, StringSplitOptions.RemoveEmptyEntries); + foreach (var token in tokens) + { + if (token.StartsWith(Comment)) break; + + if (token.Length == 2 && byte.TryParse(token, NumberStyles.HexNumber, CultureInfo.InvariantCulture, out var value)) + { + res.Add(value); + } + else + { + if (!matches.TryGetValue(token, out var match)) + { + throw new Exception($"Unmatched token '{token}' in response."); + } + + res.Add(match); + } + } + } + + return res.ToArray(); + } +} \ No newline at end of file diff --git a/S7.Net.UnitTest/S7.Net.UnitTest.csproj b/S7.Net.UnitTest/S7.Net.UnitTest.csproj index 2b29986..a22bf0d 100644 --- a/S7.Net.UnitTest/S7.Net.UnitTest.csproj +++ b/S7.Net.UnitTest/S7.Net.UnitTest.csproj @@ -8,6 +8,7 @@ + latest true Properties\S7.Net.snk false