fix(r2-01): RED framing-fault classification Theory T5 (STAB-15a, task 6)
This commit is contained in:
@@ -40,7 +40,7 @@
|
|||||||
{
|
{
|
||||||
"id": 6,
|
"id": 6,
|
||||||
"subject": "RED: framing-fault classification Theory T5 (TPKT/TPDU/WrongNumberOfBytes/PlcException-WrongNumberReceivedBytes reopen) — must FAIL",
|
"subject": "RED: framing-fault classification Theory T5 (TPKT/TPDU/WrongNumberOfBytes/PlcException-WrongNumberReceivedBytes reopen) — must FAIL",
|
||||||
"status": "pending",
|
"status": "completed",
|
||||||
"blockedBy": [0]
|
"blockedBy": [0]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -247,6 +247,50 @@ public sealed class S7DriverReconnectTests
|
|||||||
async () => await drv.ReadAsync(["W0"], cts.Token));
|
async () => await drv.ReadAsync(["W0"], cts.Token));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ---- STAB-15a: ISO-on-TCP framing/desync faults must reopen ----
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// T5 (STAB-15a). A framing/desync fault on ISO-on-TCP (S7.Net's
|
||||||
|
/// <see cref="TPKTInvalidException"/> / <see cref="TPDUInvalidException"/> /
|
||||||
|
/// <see cref="WrongNumberOfBytesException"/>, or a <see cref="PlcException"/> carrying
|
||||||
|
/// <see cref="ErrorCode.WrongNumberReceivedBytes"/>) leaves the stream position
|
||||||
|
/// untrustworthy — only a reopen recovers. The next read must dispose connection #1 and
|
||||||
|
/// open a fresh #2. Before the fix these were not classified fatal, so the desynced handle
|
||||||
|
/// was reused forever (<c>Created.Count</c> pinned at 1).
|
||||||
|
/// </summary>
|
||||||
|
[Theory]
|
||||||
|
[InlineData("tpkt")]
|
||||||
|
[InlineData("tpdu")]
|
||||||
|
[InlineData("wrongbytes")]
|
||||||
|
[InlineData("plc-wrongnumber")]
|
||||||
|
public async Task Framing_faults_are_classified_connection_fatal_and_reopen(string kind)
|
||||||
|
{
|
||||||
|
Exception framing = kind switch
|
||||||
|
{
|
||||||
|
"tpkt" => new TPKTInvalidException(),
|
||||||
|
"tpdu" => new TPDUInvalidException(),
|
||||||
|
"wrongbytes" => new WrongNumberOfBytesException(),
|
||||||
|
"plc-wrongnumber" => new PlcException(ErrorCode.WrongNumberReceivedBytes, "short read"),
|
||||||
|
_ => throw new ArgumentOutOfRangeException(nameof(kind)),
|
||||||
|
};
|
||||||
|
|
||||||
|
var factory = new FakeS7PlcFactory();
|
||||||
|
using var drv = new S7Driver(Options(), "s7-framing", factory);
|
||||||
|
await drv.InitializeAsync("{}", TestContext.Current.CancellationToken);
|
||||||
|
|
||||||
|
// Read #1 hits the framing fault — Bad status, handle marked dead, but no reopen yet.
|
||||||
|
factory.Created[0].NextReadThrows = framing;
|
||||||
|
var bad = await drv.ReadAsync(["W0"], TestContext.Current.CancellationToken);
|
||||||
|
bad[0].StatusCode.ShouldNotBe(0u);
|
||||||
|
factory.Created.Count.ShouldBe(1);
|
||||||
|
|
||||||
|
// Read #2 reopens a fresh connection and reads Good.
|
||||||
|
var recovered = await drv.ReadAsync(["W0"], TestContext.Current.CancellationToken);
|
||||||
|
recovered[0].StatusCode.ShouldBe(0u);
|
||||||
|
factory.Created.Count.ShouldBe(2);
|
||||||
|
factory.Created[0].Disposed.ShouldBeTrue();
|
||||||
|
}
|
||||||
|
|
||||||
// ---- fakes ----
|
// ---- fakes ----
|
||||||
|
|
||||||
private sealed class FakeS7PlcFactory : IS7PlcFactory
|
private sealed class FakeS7PlcFactory : IS7PlcFactory
|
||||||
|
|||||||
Reference in New Issue
Block a user