Skip to content

Commit

Permalink
Merge pull request #491 from S7NetPlus/plc-status
Browse files Browse the repository at this point in the history
Plc status
  • Loading branch information
mycroes authored Aug 2, 2023
2 parents 361db8b + addf606 commit f1ae0ea
Show file tree
Hide file tree
Showing 11 changed files with 481 additions and 13 deletions.
28 changes: 28 additions & 0 deletions S7.Net.UnitTest/CommunicationTests/ConnectionOpen.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
using System.Net;
using System.Threading.Tasks;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using S7.Net.Protocol;

namespace S7.Net.UnitTest.CommunicationTests;

[TestClass]
public class ConnectionOpen
{
[TestMethod]
public async Task Does_Not_Throw()
{
var cs = new CommunicationSequence {
ConnectionOpenTemplates.ConnectionRequestConfirm,
ConnectionOpenTemplates.CommunicationSetup
};

async Task Client(int port)
{
var conn = new Plc(IPAddress.Loopback.ToString(), port, new TsapPair(new Tsap(1, 2), new Tsap(3, 4)));
await conn.OpenAsync();
conn.Close();
}

await Task.WhenAll(cs.Serve(out var port), Client(port));
}
}
107 changes: 107 additions & 0 deletions S7.Net.UnitTest/CommunicationTests/ConnectionOpenTemplates.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
namespace S7.Net.UnitTest.CommunicationTests;

internal static class ConnectionOpenTemplates
{
public static RequestResponsePair ConnectionRequestConfirm { get; } = new RequestResponsePair(
"""
// TPKT
03 // Version
00 // Reserved
00 16 // Length

// CR
11 // Number of bytes following
E0 // CR / Credit
00 00 // Destination reference, unused
__ __ // Source reference, unused
00 // Class / Option

// Source TSAP
C1 // Parameter code
02 // Parameter length
TSAP_SRC_CHAN // Channel
TSAP_SRC_POS // Position

// Destination TSAP
C2 // Parameter code
02 // Parameter length
TSAP_DEST_CHAN // Channel
TSAP_DEST_POS // Position

// PDU Size parameter
C0 // Parameter code
01 // Parameter length
0A // 1024 byte PDU (2 ^ 10)
""",
"""
// TPKT
03 // Version
00 // Reserved
00 0B // Length

// CC
06 // Length
D0 // CC / Credit
00 00 // Destination reference
00 00 // Source reference
00 // Class / Option
"""
);

public static RequestResponsePair CommunicationSetup { get; } = new RequestResponsePair(
"""
// TPKT
03 // Version
00 // Reserved
00 19 // Length

// Data header
02 // Length
F0 // Data identifier
80 // PDU number and end of transmission

// S7 header
32 // Protocol ID
01 // Message type job request
00 00 // Reserved
PDU1 PDU2 // PDU reference
00 08 // Parameter length (Communication Setup)
00 00 // Data length

// Communication Setup
F0 // Function code
00 // Reserved
00 03 // Max AMQ caller
00 03 // Max AMQ callee
03 C0 // PDU size (960)
""",
"""
// TPKT
03 // Version
00 // Reserved
00 1B // Length

// Data header
02 // Length
F0 // Data identifier
80 // PDU number and end of transmission

// S7 header
32 // Protocol ID
03 // Message type ack data
00 00 // Reserved
PDU1 PDU2 // PDU reference
00 08 // Parameter length (Communication Setup)
00 00 // Data length
00 // Error class
00 // Error code

// Communication Setup
F0 // Function code
00 // Reserved
00 03 // Max AMQ caller
00 03 // Max AMQ callee
03 C0 // PDU size (960)
"""
);
}
57 changes: 57 additions & 0 deletions S7.Net.UnitTest/CommunicationTests/ReadPlcStatus.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
using System.Net;
using System.Threading.Tasks;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using S7.Net.Protocol;

namespace S7.Net.UnitTest.CommunicationTests;

[TestClass]
public class ReadPlcStatus
{
[TestMethod]
public async Task Read_Status_Run()
{
var cs = new CommunicationSequence {
ConnectionOpenTemplates.ConnectionRequestConfirm,
ConnectionOpenTemplates.CommunicationSetup,
{
"""
// TPKT
03 00 00 21

// COTP
02 f0 80

// S7 SZL read
32 07 00 00 PDU1 PDU2 00 08 00 08 00 01 12 04 11 44
01 00 ff 09 00 04 04 24 00 00
""",
"""
// TPKT
03 00 00 3d

// COTP
02 f0 80

// S7 SZL response
32 07 00 00 PDU1 PDU2 00 0c 00 20 00 01 12 08 12 84
01 02 00 00 00 00 ff 09 00 1c 04 24 00 00 00 14
00 01 51 44 ff 08 00 00 00 00 00 00 00 00 14 08
20 12 05 28 34 94
"""
}
};

async Task Client(int port)
{
var conn = new Plc(IPAddress.Loopback.ToString(), port, new TsapPair(new Tsap(1, 2), new Tsap(3, 4)));
await conn.OpenAsync();
var status = await conn.ReadStatusAsync();

Assert.AreEqual(0x08, status);
conn.Close();
}

await Task.WhenAll(cs.Serve(out var port), Client(port));
}
}
7 changes: 7 additions & 0 deletions S7.Net.UnitTest/Framework/IsExternalInit.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
using System.ComponentModel;

namespace System.Runtime.CompilerServices
{
[EditorBrowsable(EditorBrowsableState.Never)]
internal record IsExternalInit;
}
82 changes: 82 additions & 0 deletions S7.Net.UnitTest/Infrastructure/CommunicationSequence.cs
Original file line number Diff line number Diff line change
@@ -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<RequestResponsePair>
{
private readonly List<RequestResponsePair> _requestResponsePairs = new List<RequestResponsePair>();

public void Add(RequestResponsePair requestResponsePair)
{
_requestResponsePairs.Add(requestResponsePair);
}

public void Add(string requestPattern, string responsePattern)
{
_requestResponsePairs.Add(new RequestResponsePair(requestPattern, responsePattern));
}

public IEnumerator<RequestResponsePair> 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<byte>.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<byte>.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;
}
}
3 changes: 3 additions & 0 deletions S7.Net.UnitTest/Infrastructure/RequestResponsePair.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
namespace S7.Net.UnitTest;

internal record RequestResponsePair(string RequestPattern, string ResponsePattern);
80 changes: 80 additions & 0 deletions S7.Net.UnitTest/Infrastructure/Responder.cs
Original file line number Diff line number Diff line change
@@ -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<string, byte>();
var res = new List<byte>();
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();
}
}
1 change: 1 addition & 0 deletions S7.Net.UnitTest/S7.Net.UnitTest.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
</PropertyGroup>

<PropertyGroup>
<LangVersion>latest</LangVersion>
<SignAssembly>true</SignAssembly>
<AssemblyOriginatorKeyFile>Properties\S7.Net.snk</AssemblyOriginatorKeyFile>
<IsPackable>false</IsPackable>
Expand Down
Loading

0 comments on commit f1ae0ea

Please sign in to comment.