Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Plc status #491

Merged
merged 24 commits into from
Aug 2, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
9c8b453
refactor: Extract WriteTpktHeader
mycroes Jul 19, 2023
42194aa
refactor: Extract WriteDataHeader
mycroes Jul 19, 2023
ebf3da6
refactor: Extract WriteS7Header
mycroes Jul 19, 2023
296ead6
refactor: Use Word.ToByteArray in WriteTpktHeader
mycroes Jul 19, 2023
8becc56
refactor: Cleanup inline math in BuildHeaderPackage
mycroes Jul 19, 2023
cf94f8a
fix(PLCHelpers): Fix errors from refactors
mycroes Jul 20, 2023
38b26e0
fix: Update test project target frameworks
mycroes Jul 20, 2023
7d21213
refactor: Rename BuildHeaderPackage to WriteReadHeader
mycroes Jul 21, 2023
1f26833
fix: Add missing xmldoc nodes in PLCHelpers
mycroes Jul 21, 2023
18c3883
feat: Add WriteUserDataHeader
mycroes Jul 21, 2023
1fc6899
feat: Add WriteSzlReadRequest
mycroes Jul 21, 2023
0d9ccea
feat: Add Plc.ReadStatusAsync
mycroes Jul 22, 2023
019aeb2
Merge branch 'main' into plc-status
mycroes Jul 23, 2023
5891a30
Merge branch 'main' into plc-status
mycroes Jul 25, 2023
088cd0a
Merge branch 'main' into plc-status
mycroes Jul 29, 2023
714ac62
test: Add CommunicationSequence
mycroes Jul 31, 2023
8b8ad13
test: Add ConnectionOpen communication test
mycroes Jul 31, 2023
54dadec
test: Extract connection open templates
mycroes Aug 1, 2023
9b1faa0
test: Add test for reading PLC status
mycroes Aug 1, 2023
97e27cc
chore(ReadStatusAsync): Make cancellationToken optional
mycroes Aug 1, 2023
e5823f2
doc(ReadStatusAsync): Add missing cancellationToken documentation
mycroes Aug 1, 2023
c3934c3
fix(ReadStatusAsync): Fix index of status in response message
mycroes Aug 1, 2023
970e9d4
feat: Add sync version of ReadStatus
mycroes Aug 1, 2023
addf606
style(ReadStatusAsync): Move opening brace to new line
mycroes Aug 1, 2023
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
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