fix(r2-01): classify ISO-on-TCP framing/desync faults as connection-fatal (STAB-15a, task 7)

This commit is contained in:
Joseph Doherty
2026-07-13 09:58:11 -04:00
parent 8aed9ac365
commit dab0029add
2 changed files with 26 additions and 6 deletions
@@ -1256,10 +1256,24 @@ public sealed class S7Driver
/// <summary>
/// True when <paramref name="ex"/> (or any inner exception) is a socket-level / connection
/// loss that a reopen can repair — a <see cref="System.Net.Sockets.SocketException"/> /
/// <see cref="System.IO.IOException"/> / <see cref="ObjectDisposedException"/>, or an S7.Net
/// <see cref="PlcException"/> carrying <see cref="ErrorCode.ConnectionError"/>. A
/// data-address / type error (S7.Net's <see cref="ErrorCode.ReadData"/> /
/// loss OR an ISO-on-TCP framing/desync fault that a reopen can repair — a
/// <see cref="System.Net.Sockets.SocketException"/> / <see cref="System.IO.IOException"/> /
/// <see cref="ObjectDisposedException"/>; an S7.Net framing violation
/// (<see cref="TPKTInvalidException"/> / <see cref="TPDUInvalidException"/> /
/// <see cref="WrongNumberOfBytesException"/>); or a <see cref="PlcException"/> carrying
/// <see cref="ErrorCode.ConnectionError"/> or <see cref="ErrorCode.WrongNumberReceivedBytes"/>.
/// A framing violation means the single serialized stream's position is untrustworthy — a
/// half-read PDU left by a timeout/cancellation makes every later response mis-frame, so only
/// a reopen recovers (STAB-15a: the Modbus STAB-3 desync mode rebuilt in S7).
/// <para>
/// Deliberately NOT <see cref="System.IO.InvalidDataException"/>: the driver's own decode
/// backstop (<see cref="ReinterpretRawValue"/> / <see cref="DecodeScalarBlock"/>) throws
/// it for a declared-type/address-size CONFIG mismatch on a HEALTHY socket — classifying
/// it would churn a full reopen on every read of a mis-authored tag. A genuinely desynced
/// stream's very next PDU still lands in TPKT/TPDU/WrongNumber territory, which now tears
/// down, so a real desync self-heals within one extra failed call.
/// </para>
/// A data-address / type error (S7.Net's <see cref="ErrorCode.ReadData"/> /
/// <see cref="ErrorCode.WriteData"/> for a bad address, PUT/GET-denied, etc.) is deliberately
/// NOT treated as fatal — reopening wouldn't help and would churn a healthy connection.
/// </summary>
@@ -1271,7 +1285,13 @@ public sealed class S7Driver
{
if (e is System.Net.Sockets.SocketException or System.IO.IOException or ObjectDisposedException)
return true;
if (e is PlcException { ErrorCode: ErrorCode.ConnectionError })
// ISO-on-TCP framing/desync surface (STAB-15a): the stream position is untrustworthy —
// a half-read PDU left by a timeout/cancellation makes every later response mis-frame.
// NOTE: deliberately NOT System.IO.InvalidDataException — ReinterpretRawValue throws it
// for a declared-type/address-size CONFIG mismatch on a healthy socket.
if (e is TPKTInvalidException or TPDUInvalidException or WrongNumberOfBytesException)
return true;
if (e is PlcException { ErrorCode: ErrorCode.ConnectionError or ErrorCode.WrongNumberReceivedBytes })
return true;
}
return false;