fix(r2-01): RED framing-fault classification Theory T5 (STAB-15a, task 6)
This commit is contained in:
@@ -40,7 +40,7 @@
|
||||
{
|
||||
"id": 6,
|
||||
"subject": "RED: framing-fault classification Theory T5 (TPKT/TPDU/WrongNumberOfBytes/PlcException-WrongNumberReceivedBytes reopen) — must FAIL",
|
||||
"status": "pending",
|
||||
"status": "completed",
|
||||
"blockedBy": [0]
|
||||
},
|
||||
{
|
||||
|
||||
@@ -247,6 +247,50 @@ public sealed class S7DriverReconnectTests
|
||||
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 ----
|
||||
|
||||
private sealed class FakeS7PlcFactory : IS7PlcFactory
|
||||
|
||||
Reference in New Issue
Block a user