Skip to content

Commit f1ae0ea

Browse files
authored
Merge pull request #491 from S7NetPlus/plc-status
Plc status
2 parents 361db8b + addf606 commit f1ae0ea

File tree

11 files changed

+481
-13
lines changed

11 files changed

+481
-13
lines changed
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
using System.Net;
2+
using System.Threading.Tasks;
3+
using Microsoft.VisualStudio.TestTools.UnitTesting;
4+
using S7.Net.Protocol;
5+
6+
namespace S7.Net.UnitTest.CommunicationTests;
7+
8+
[TestClass]
9+
public class ConnectionOpen
10+
{
11+
[TestMethod]
12+
public async Task Does_Not_Throw()
13+
{
14+
var cs = new CommunicationSequence {
15+
ConnectionOpenTemplates.ConnectionRequestConfirm,
16+
ConnectionOpenTemplates.CommunicationSetup
17+
};
18+
19+
async Task Client(int port)
20+
{
21+
var conn = new Plc(IPAddress.Loopback.ToString(), port, new TsapPair(new Tsap(1, 2), new Tsap(3, 4)));
22+
await conn.OpenAsync();
23+
conn.Close();
24+
}
25+
26+
await Task.WhenAll(cs.Serve(out var port), Client(port));
27+
}
28+
}
Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
namespace S7.Net.UnitTest.CommunicationTests;
2+
3+
internal static class ConnectionOpenTemplates
4+
{
5+
public static RequestResponsePair ConnectionRequestConfirm { get; } = new RequestResponsePair(
6+
"""
7+
// TPKT
8+
03 // Version
9+
00 // Reserved
10+
00 16 // Length
11+
12+
// CR
13+
11 // Number of bytes following
14+
E0 // CR / Credit
15+
00 00 // Destination reference, unused
16+
__ __ // Source reference, unused
17+
00 // Class / Option
18+
19+
// Source TSAP
20+
C1 // Parameter code
21+
02 // Parameter length
22+
TSAP_SRC_CHAN // Channel
23+
TSAP_SRC_POS // Position
24+
25+
// Destination TSAP
26+
C2 // Parameter code
27+
02 // Parameter length
28+
TSAP_DEST_CHAN // Channel
29+
TSAP_DEST_POS // Position
30+
31+
// PDU Size parameter
32+
C0 // Parameter code
33+
01 // Parameter length
34+
0A // 1024 byte PDU (2 ^ 10)
35+
""",
36+
"""
37+
// TPKT
38+
03 // Version
39+
00 // Reserved
40+
00 0B // Length
41+
42+
// CC
43+
06 // Length
44+
D0 // CC / Credit
45+
00 00 // Destination reference
46+
00 00 // Source reference
47+
00 // Class / Option
48+
"""
49+
);
50+
51+
public static RequestResponsePair CommunicationSetup { get; } = new RequestResponsePair(
52+
"""
53+
// TPKT
54+
03 // Version
55+
00 // Reserved
56+
00 19 // Length
57+
58+
// Data header
59+
02 // Length
60+
F0 // Data identifier
61+
80 // PDU number and end of transmission
62+
63+
// S7 header
64+
32 // Protocol ID
65+
01 // Message type job request
66+
00 00 // Reserved
67+
PDU1 PDU2 // PDU reference
68+
00 08 // Parameter length (Communication Setup)
69+
00 00 // Data length
70+
71+
// Communication Setup
72+
F0 // Function code
73+
00 // Reserved
74+
00 03 // Max AMQ caller
75+
00 03 // Max AMQ callee
76+
03 C0 // PDU size (960)
77+
""",
78+
"""
79+
// TPKT
80+
03 // Version
81+
00 // Reserved
82+
00 1B // Length
83+
84+
// Data header
85+
02 // Length
86+
F0 // Data identifier
87+
80 // PDU number and end of transmission
88+
89+
// S7 header
90+
32 // Protocol ID
91+
03 // Message type ack data
92+
00 00 // Reserved
93+
PDU1 PDU2 // PDU reference
94+
00 08 // Parameter length (Communication Setup)
95+
00 00 // Data length
96+
00 // Error class
97+
00 // Error code
98+
99+
// Communication Setup
100+
F0 // Function code
101+
00 // Reserved
102+
00 03 // Max AMQ caller
103+
00 03 // Max AMQ callee
104+
03 C0 // PDU size (960)
105+
"""
106+
);
107+
}
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
using System.Net;
2+
using System.Threading.Tasks;
3+
using Microsoft.VisualStudio.TestTools.UnitTesting;
4+
using S7.Net.Protocol;
5+
6+
namespace S7.Net.UnitTest.CommunicationTests;
7+
8+
[TestClass]
9+
public class ReadPlcStatus
10+
{
11+
[TestMethod]
12+
public async Task Read_Status_Run()
13+
{
14+
var cs = new CommunicationSequence {
15+
ConnectionOpenTemplates.ConnectionRequestConfirm,
16+
ConnectionOpenTemplates.CommunicationSetup,
17+
{
18+
"""
19+
// TPKT
20+
03 00 00 21
21+
22+
// COTP
23+
02 f0 80
24+
25+
// S7 SZL read
26+
32 07 00 00 PDU1 PDU2 00 08 00 08 00 01 12 04 11 44
27+
01 00 ff 09 00 04 04 24 00 00
28+
""",
29+
"""
30+
// TPKT
31+
03 00 00 3d
32+
33+
// COTP
34+
02 f0 80
35+
36+
// S7 SZL response
37+
32 07 00 00 PDU1 PDU2 00 0c 00 20 00 01 12 08 12 84
38+
01 02 00 00 00 00 ff 09 00 1c 04 24 00 00 00 14
39+
00 01 51 44 ff 08 00 00 00 00 00 00 00 00 14 08
40+
20 12 05 28 34 94
41+
"""
42+
}
43+
};
44+
45+
async Task Client(int port)
46+
{
47+
var conn = new Plc(IPAddress.Loopback.ToString(), port, new TsapPair(new Tsap(1, 2), new Tsap(3, 4)));
48+
await conn.OpenAsync();
49+
var status = await conn.ReadStatusAsync();
50+
51+
Assert.AreEqual(0x08, status);
52+
conn.Close();
53+
}
54+
55+
await Task.WhenAll(cs.Serve(out var port), Client(port));
56+
}
57+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
using System.ComponentModel;
2+
3+
namespace System.Runtime.CompilerServices
4+
{
5+
[EditorBrowsable(EditorBrowsableState.Never)]
6+
internal record IsExternalInit;
7+
}
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
using System;
2+
using System.Buffers;
3+
using System.Collections;
4+
using System.Collections.Generic;
5+
using System.Linq;
6+
using System.Net;
7+
using System.Net.Sockets;
8+
using System.Threading.Tasks;
9+
10+
namespace S7.Net.UnitTest;
11+
12+
internal class CommunicationSequence : IEnumerable<RequestResponsePair>
13+
{
14+
private readonly List<RequestResponsePair> _requestResponsePairs = new List<RequestResponsePair>();
15+
16+
public void Add(RequestResponsePair requestResponsePair)
17+
{
18+
_requestResponsePairs.Add(requestResponsePair);
19+
}
20+
21+
public void Add(string requestPattern, string responsePattern)
22+
{
23+
_requestResponsePairs.Add(new RequestResponsePair(requestPattern, responsePattern));
24+
}
25+
26+
public IEnumerator<RequestResponsePair> GetEnumerator()
27+
{
28+
return _requestResponsePairs.GetEnumerator();
29+
}
30+
31+
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
32+
33+
public Task Serve(out int port)
34+
{
35+
var socket = CreateBoundListenSocket(out port);
36+
socket.Listen(0);
37+
38+
async Task Impl()
39+
{
40+
await Task.Yield();
41+
var socketIn = socket.Accept();
42+
43+
var buffer = ArrayPool<byte>.Shared.Rent(1024);
44+
try
45+
{
46+
foreach (var pair in _requestResponsePairs)
47+
{
48+
var bytesReceived = socketIn.Receive(buffer, SocketFlags.None);
49+
50+
var received = buffer.Take(bytesReceived).ToArray();
51+
Console.WriteLine($"=> {BitConverter.ToString(received)}");
52+
53+
var response = Responder.Respond(pair, received);
54+
55+
Console.WriteLine($"<= {BitConverter.ToString(response)}");
56+
socketIn.Send(response);
57+
}
58+
}
59+
finally
60+
{
61+
ArrayPool<byte>.Shared.Return(buffer);
62+
}
63+
64+
socketIn.Close();
65+
}
66+
67+
return Impl();
68+
}
69+
70+
private static Socket CreateBoundListenSocket(out int port)
71+
{
72+
var socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
73+
var endpoint = new IPEndPoint(IPAddress.Loopback, 0);
74+
75+
socket.Bind(endpoint);
76+
77+
var localEndpoint = (IPEndPoint)socket.LocalEndPoint!;
78+
port = localEndpoint.Port;
79+
80+
return socket;
81+
}
82+
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
namespace S7.Net.UnitTest;
2+
3+
internal record RequestResponsePair(string RequestPattern, string ResponsePattern);
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Globalization;
4+
using System.IO;
5+
6+
namespace S7.Net.UnitTest;
7+
8+
internal static class Responder
9+
{
10+
private const string Comment = "//";
11+
private static char[] Space = " ".ToCharArray();
12+
13+
public static byte[] Respond(RequestResponsePair pair, byte[] request)
14+
{
15+
var offset = 0;
16+
var matches = new Dictionary<string, byte>();
17+
var res = new List<byte>();
18+
using var requestReader = new StringReader(pair.RequestPattern);
19+
20+
string line;
21+
while ((line = requestReader.ReadLine()) != null)
22+
{
23+
var tokens = line.Split(Space, StringSplitOptions.RemoveEmptyEntries);
24+
foreach (var token in tokens)
25+
{
26+
if (token.StartsWith(Comment)) break;
27+
28+
if (offset >= request.Length)
29+
{
30+
throw new Exception("Request pattern has more data than request.");
31+
}
32+
33+
var received = request[offset];
34+
35+
if (token.Length == 2 && byte.TryParse(token, NumberStyles.HexNumber, CultureInfo.InvariantCulture, out var value))
36+
{
37+
// Number, exact match
38+
if (value != received)
39+
{
40+
throw new Exception($"Incorrect data at offset {offset}. Expected {value:X2}, received {received:X2}.");
41+
}
42+
}
43+
else
44+
{
45+
matches[token] = received;
46+
}
47+
48+
offset++;
49+
}
50+
}
51+
52+
if (offset != request.Length) throw new Exception("Request contained more data than request pattern.");
53+
54+
using var responseReader = new StringReader(pair.ResponsePattern);
55+
while ((line = responseReader.ReadLine()) != null)
56+
{
57+
var tokens = line.Split(Space, StringSplitOptions.RemoveEmptyEntries);
58+
foreach (var token in tokens)
59+
{
60+
if (token.StartsWith(Comment)) break;
61+
62+
if (token.Length == 2 && byte.TryParse(token, NumberStyles.HexNumber, CultureInfo.InvariantCulture, out var value))
63+
{
64+
res.Add(value);
65+
}
66+
else
67+
{
68+
if (!matches.TryGetValue(token, out var match))
69+
{
70+
throw new Exception($"Unmatched token '{token}' in response.");
71+
}
72+
73+
res.Add(match);
74+
}
75+
}
76+
}
77+
78+
return res.ToArray();
79+
}
80+
}

S7.Net.UnitTest/S7.Net.UnitTest.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
</PropertyGroup>
99

1010
<PropertyGroup>
11+
<LangVersion>latest</LangVersion>
1112
<SignAssembly>true</SignAssembly>
1213
<AssemblyOriginatorKeyFile>Properties\S7.Net.snk</AssemblyOriginatorKeyFile>
1314
<IsPackable>false</IsPackable>

0 commit comments

Comments
 (0)