Skip to content

Commit 946536c

Browse files
committed
Release S7NetPlus 0.13.0
Release highlights: - Change default TSAP for S7 200 - Add S7 200 Smart support - Add support for custom TSAP's - Align data to even bytes when parsing responses from class and struct reads - Close connection on IOException - Add cancellation for Read/Write - Set default Read-/WriteTimeout to 10 seconds - Cleanup of sync helper methods
2 parents 5318f94 + b475aee commit 946536c

17 files changed

+575
-208
lines changed

.github/workflows/test.yml

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
name: Test
2+
3+
on:
4+
[pull_request, push]
5+
6+
jobs:
7+
8+
build_test:
9+
name: test-${{ matrix.os }}-${{ matrix.test-framework }}
10+
runs-on: ${{ matrix.os }}
11+
env:
12+
configuration: Release
13+
artifacts: ${{ github.workspace }}/artifacts
14+
DOTNET_NOLOGO : 1
15+
strategy:
16+
matrix:
17+
os: [windows-latest, ubuntu-latest, macos-latest]
18+
test-framework: [netcoreapp3.1, net5.0]
19+
include:
20+
- os: ubuntu-latest
21+
test-framework: netcoreapp3.1
22+
installSnap7: true
23+
dotnet-sdk: '3.1.x'
24+
- os: ubuntu-latest
25+
test-framework: net5.0
26+
installSnap7: true
27+
dotnet-sdk: '5.0.x'
28+
- os: macos-latest
29+
test-framework: netcoreapp3.1
30+
installSnap7: true
31+
dotnet-sdk: '3.1.x'
32+
- os: macos-latest
33+
test-framework: net5.0
34+
installSnap7: true
35+
dotnet-sdk: '5.0.x'
36+
- os: windows-latest
37+
test-framework: netcoreapp3.1
38+
dotnet-sdk: '3.1.x'
39+
- os: windows-latest
40+
test-framework: net5.0
41+
dotnet-sdk: '5.0.x'
42+
- os: windows-latest
43+
test-framework: net452
44+
dotnet-sdk: '5.0.x'
45+
fail-fast: false
46+
47+
steps:
48+
- uses: actions/checkout@v2
49+
50+
- name: Install Snap7 Linux
51+
if: ${{ matrix.installSnap7 && matrix.os == 'ubuntu-latest' }}
52+
run: |
53+
sudo add-apt-repository ppa:gijzelaar/snap7
54+
sudo apt-get update
55+
sudo apt-get install libsnap7-1 libsnap7-dev
56+
57+
- name: Install Snap7 MacOs
58+
if: ${{ matrix.installSnap7 && matrix.os == 'macos-latest' }}
59+
run: |
60+
brew install snap7
61+
62+
- name: Setup Dotnet
63+
uses: actions/setup-dotnet@v1
64+
with:
65+
dotnet-version: ${{ matrix.dotnet-sdk }}
66+
67+
- name: Nuget Cache
68+
uses: actions/cache@v2
69+
with:
70+
path: ~/.nuget/packages
71+
# Look to see if there is a cache hit for the corresponding requirements file
72+
key: ${{ runner.os }}-${{ matrix.test-framework }}-nuget-${{ hashFiles('**/packages.lock.json') }}
73+
restore-keys: |
74+
${{ runner.os }}-${{ matrix.test-framework }}-nuget
75+
76+
- name: Restore
77+
run: dotnet restore S7.Net.UnitTest
78+
79+
- name: Test
80+
run: dotnet test --no-restore --nologo --verbosity normal --logger GitHubActions --framework ${{ matrix.test-framework }}
Lines changed: 181 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,181 @@
1+
using Microsoft.VisualStudio.TestTools.UnitTesting;
2+
using System;
3+
using System.IO;
4+
using System.Linq;
5+
using System.Net.Sockets;
6+
using System.Reflection;
7+
using System.Threading;
8+
using System.Threading.Tasks;
9+
10+
namespace S7.Net.UnitTest
11+
{
12+
/// <summary>
13+
/// Test stream which only gives 1 byte per read.
14+
/// </summary>
15+
class TestStreamConnectionClose : Stream
16+
{
17+
private readonly CancellationTokenSource _cancellationTokenSource;
18+
19+
public TestStreamConnectionClose(CancellationTokenSource cancellationTokenSource)
20+
{
21+
_cancellationTokenSource = cancellationTokenSource;
22+
}
23+
public override bool CanRead => false;
24+
25+
public override bool CanSeek => throw new NotImplementedException();
26+
27+
public override bool CanWrite => true;
28+
29+
public override long Length => throw new NotImplementedException();
30+
31+
public override long Position { get => throw new NotImplementedException(); set => throw new NotImplementedException(); }
32+
33+
public override void Flush()
34+
{
35+
throw new NotImplementedException();
36+
}
37+
38+
public override int Read(byte[] buffer, int offset, int count)
39+
{
40+
throw new NotImplementedException();
41+
}
42+
43+
public override long Seek(long offset, SeekOrigin origin)
44+
{
45+
throw new NotImplementedException();
46+
}
47+
48+
public override void SetLength(long value)
49+
{
50+
throw new NotImplementedException();
51+
}
52+
53+
public override void Write(byte[] buffer, int offset, int count)
54+
{
55+
_cancellationTokenSource.Cancel();
56+
}
57+
}
58+
59+
/// <summary>
60+
/// These tests are intended to test <see cref="StreamExtensions"/> functions and other stream-related special cases.
61+
/// </summary>
62+
[TestClass]
63+
public class ConnectionCloseTest
64+
{
65+
const short TestServerPort = 31122;
66+
const string TestServerIp = "127.0.0.1";
67+
68+
[TestMethod]
69+
public async Task Test_CancellationDuringTransmission()
70+
{
71+
var plc = new Plc(CpuType.S7300, TestServerIp, TestServerPort, 0, 2);
72+
73+
// Set up a shared cancellation source so we can let the stream
74+
// initiate cancel after some data has been written to it.
75+
var cancellationSource = new CancellationTokenSource();
76+
var cancellationToken = cancellationSource.Token;
77+
78+
var stream = new TestStreamConnectionClose(cancellationSource);
79+
var requestData = new byte[100]; // empty data, it does not matter what is in there
80+
81+
// Set up access to private method and field
82+
var dynMethod = plc.GetType().GetMethod("NoLockRequestTpduAsync",
83+
BindingFlags.NonPublic | BindingFlags.Instance);
84+
if (dynMethod == null)
85+
{
86+
throw new NullReferenceException("Could not find method 'NoLockRequestTpduAsync' on Plc object.");
87+
}
88+
var tcpClientField = plc.GetType().GetField("tcpClient", BindingFlags.NonPublic | BindingFlags.Instance);
89+
if (tcpClientField == null)
90+
{
91+
throw new NullReferenceException("Could not find field 'tcpClient' on Plc object.");
92+
}
93+
94+
// Set a value to tcpClient field so we can later ensure that it has been closed.
95+
tcpClientField.SetValue(plc, new TcpClient());
96+
var tcpClientValue = tcpClientField.GetValue(plc);
97+
Assert.IsNotNull(tcpClientValue);
98+
99+
try
100+
{
101+
var result = (Task<COTP.TPDU>) dynMethod.Invoke(plc, new object[] { stream, requestData, cancellationToken });
102+
await result;
103+
}
104+
catch (OperationCanceledException)
105+
{
106+
Console.WriteLine("Task was cancelled as expected.");
107+
108+
// Ensure that the plc connection was closed since the task was cancelled
109+
// after data has been sent through the network. We expect that the tcpClient
110+
// object was set to NULL
111+
var tcpClientValueAfter = tcpClientField.GetValue(plc);
112+
Assert.IsNull(tcpClientValueAfter);
113+
return;
114+
}
115+
catch (Exception e)
116+
{
117+
Assert.Fail($"Wrong exception type received. Expected {typeof(OperationCanceledException)}, received {e.GetType()}.");
118+
}
119+
120+
// Ensure test fails if cancellation did not occur.
121+
Assert.Fail("Task was not cancelled as expected.");
122+
}
123+
124+
[TestMethod]
125+
public async Task Test_CancellationBeforeTransmission()
126+
{
127+
var plc = new Plc(CpuType.S7300, TestServerIp, TestServerPort, 0, 2);
128+
129+
// Set up a cancellation source
130+
var cancellationSource = new CancellationTokenSource();
131+
var cancellationToken = cancellationSource.Token;
132+
133+
var stream = new TestStreamConnectionClose(cancellationSource);
134+
var requestData = new byte[100]; // empty data, it does not matter what is in there
135+
136+
// Set up access to private method and field
137+
var dynMethod = plc.GetType().GetMethod("NoLockRequestTpduAsync",
138+
BindingFlags.NonPublic | BindingFlags.Instance);
139+
if (dynMethod == null)
140+
{
141+
throw new NullReferenceException("Could not find method 'NoLockRequestTpduAsync' on Plc object.");
142+
}
143+
var tcpClientField = plc.GetType().GetField("tcpClient", BindingFlags.NonPublic | BindingFlags.Instance);
144+
if (tcpClientField == null)
145+
{
146+
throw new NullReferenceException("Could not find field 'tcpClient' on Plc object.");
147+
}
148+
149+
// Set a value to tcpClient field so we can later ensure that it has been closed.
150+
tcpClientField.SetValue(plc, new TcpClient());
151+
var tcpClientValue = tcpClientField.GetValue(plc);
152+
Assert.IsNotNull(tcpClientValue);
153+
154+
try
155+
{
156+
// cancel the task before we start transmitting data
157+
cancellationSource.Cancel();
158+
var result = (Task<COTP.TPDU>)dynMethod.Invoke(plc, new object[] { stream, requestData, cancellationToken });
159+
await result;
160+
}
161+
catch (OperationCanceledException)
162+
{
163+
Console.WriteLine("Task was cancelled as expected.");
164+
165+
// Ensure that the plc connection was not closed, since we cancelled the task before
166+
// sending data through the network. We expect that the tcpClient
167+
// object was NOT set to NULL
168+
var tcpClientValueAfter = tcpClientField.GetValue(plc);
169+
Assert.IsNotNull(tcpClientValueAfter);
170+
return;
171+
}
172+
catch (Exception e)
173+
{
174+
Assert.Fail($"Wrong exception type received. Expected {typeof(OperationCanceledException)}, received {e.GetType()}.");
175+
}
176+
177+
// Ensure test fails if cancellation did not occur.
178+
Assert.Fail("Task was not cancelled as expected.");
179+
}
180+
}
181+
}

S7.Net.UnitTest/ConnectionRequestTest.cs

Lines changed: 18 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -9,52 +9,52 @@ public class ConnectionRequestTest
99
[TestMethod]
1010
public void Test_ConnectionRequest_S7_200()
1111
{
12-
CollectionAssert.AreEqual(MakeConnectionRequest(16, 0, 16, 0),
13-
ConnectionRequest.GetCOTPConnectionRequest(CpuType.S7200, 0, 0));
12+
CollectionAssert.AreEqual(MakeConnectionRequest(16, 0, 16, 1),
13+
ConnectionRequest.GetCOTPConnectionRequest(TsapPair.GetDefaultTsapPair(CpuType.S7200, 0, 0)));
1414
}
1515

1616
[TestMethod]
1717
public void Test_ConnectionRequest_S7_300()
1818
{
1919
CollectionAssert.AreEqual(MakeConnectionRequest(1, 0, 3, 0),
20-
ConnectionRequest.GetCOTPConnectionRequest(CpuType.S7300, 0, 0));
20+
ConnectionRequest.GetCOTPConnectionRequest(TsapPair.GetDefaultTsapPair(CpuType.S7300, 0, 0)));
2121
CollectionAssert.AreEqual(MakeConnectionRequest(1, 0, 3, 1),
22-
ConnectionRequest.GetCOTPConnectionRequest(CpuType.S7300, 0, 1));
22+
ConnectionRequest.GetCOTPConnectionRequest(TsapPair.GetDefaultTsapPair(CpuType.S7300, 0, 1)));
2323
CollectionAssert.AreEqual(MakeConnectionRequest(1, 0, 3, 33),
24-
ConnectionRequest.GetCOTPConnectionRequest(CpuType.S7300, 1, 1));
24+
ConnectionRequest.GetCOTPConnectionRequest(TsapPair.GetDefaultTsapPair(CpuType.S7300, 1, 1)));
2525
}
2626

2727
[TestMethod]
2828
public void Test_ConnectionRequest_S7_400()
2929
{
3030
CollectionAssert.AreEqual(MakeConnectionRequest(1, 0, 3, 0),
31-
ConnectionRequest.GetCOTPConnectionRequest(CpuType.S7400, 0, 0));
31+
ConnectionRequest.GetCOTPConnectionRequest(TsapPair.GetDefaultTsapPair(CpuType.S7400, 0, 0)));
3232
CollectionAssert.AreEqual(MakeConnectionRequest(1, 0, 3, 1),
33-
ConnectionRequest.GetCOTPConnectionRequest(CpuType.S7400, 0, 1));
33+
ConnectionRequest.GetCOTPConnectionRequest(TsapPair.GetDefaultTsapPair(CpuType.S7400, 0, 1)));
3434
CollectionAssert.AreEqual(MakeConnectionRequest(1, 0, 3, 33),
35-
ConnectionRequest.GetCOTPConnectionRequest(CpuType.S7400, 1, 1));
35+
ConnectionRequest.GetCOTPConnectionRequest(TsapPair.GetDefaultTsapPair(CpuType.S7400, 1, 1)));
3636
}
3737

3838
[TestMethod]
3939
public void Test_ConnectionRequest_S7_1200()
4040
{
4141
CollectionAssert.AreEqual(MakeConnectionRequest(1, 0, 3, 0),
42-
ConnectionRequest.GetCOTPConnectionRequest(CpuType.S71200, 0, 0));
42+
ConnectionRequest.GetCOTPConnectionRequest(TsapPair.GetDefaultTsapPair(CpuType.S71200, 0, 0)));
4343
CollectionAssert.AreEqual(MakeConnectionRequest(1, 0, 3, 1),
44-
ConnectionRequest.GetCOTPConnectionRequest(CpuType.S71200, 0, 1));
44+
ConnectionRequest.GetCOTPConnectionRequest(TsapPair.GetDefaultTsapPair(CpuType.S71200, 0, 1)));
4545
CollectionAssert.AreEqual(MakeConnectionRequest(1, 0, 3, 33),
46-
ConnectionRequest.GetCOTPConnectionRequest(CpuType.S71200, 1, 1));
46+
ConnectionRequest.GetCOTPConnectionRequest(TsapPair.GetDefaultTsapPair(CpuType.S71200, 1, 1)));
4747
}
4848

4949
[TestMethod]
5050
public void Test_ConnectionRequest_S7_1500()
5151
{
52-
CollectionAssert.AreEqual(MakeConnectionRequest(0x10, 0x2, 3, 0),
53-
ConnectionRequest.GetCOTPConnectionRequest(CpuType.S71500, 0, 0));
54-
CollectionAssert.AreEqual(MakeConnectionRequest(0x10, 0x2, 3, 1),
55-
ConnectionRequest.GetCOTPConnectionRequest(CpuType.S71500, 0, 1));
56-
CollectionAssert.AreEqual(MakeConnectionRequest(0x10, 0x2, 3, 33),
57-
ConnectionRequest.GetCOTPConnectionRequest(CpuType.S71500, 1, 1));
52+
CollectionAssert.AreEqual(MakeConnectionRequest(1, 0, 3, 0),
53+
ConnectionRequest.GetCOTPConnectionRequest(TsapPair.GetDefaultTsapPair(CpuType.S71500, 0, 0)));
54+
CollectionAssert.AreEqual(MakeConnectionRequest(1, 0, 3, 1),
55+
ConnectionRequest.GetCOTPConnectionRequest(TsapPair.GetDefaultTsapPair(CpuType.S71500, 0, 1)));
56+
CollectionAssert.AreEqual(MakeConnectionRequest(1, 0, 3, 33),
57+
ConnectionRequest.GetCOTPConnectionRequest(TsapPair.GetDefaultTsapPair(CpuType.S71500, 1, 1)));
5858
}
5959

6060
private static byte[] MakeConnectionRequest(byte sourceTsap1, byte sourceTsap2, byte destTsap1, byte destTsap2)
@@ -63,7 +63,7 @@ private static byte[] MakeConnectionRequest(byte sourceTsap1, byte sourceTsap2,
6363
{
6464
3, 0, 0, 22, //TPKT
6565
17, //COTP Header Length
66-
224, //Connect Request
66+
224, //Connect Request
6767
0, 0, //Destination Reference
6868
0, 46, //Source Reference
6969
0, //Flags

0 commit comments

Comments
 (0)