-
Notifications
You must be signed in to change notification settings - Fork 595
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #491 from S7NetPlus/plc-status
Plc status
- Loading branch information
Showing
11 changed files
with
481 additions
and
13 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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
107
S7.Net.UnitTest/CommunicationTests/ConnectionOpenTemplates.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | ||
""" | ||
); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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)); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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(); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.