diff --git a/archreview/plans/R2-01-s7-fault-hardening-plan.md.tasks.json b/archreview/plans/R2-01-s7-fault-hardening-plan.md.tasks.json index bb19fed9..06a23643 100644 --- a/archreview/plans/R2-01-s7-fault-hardening-plan.md.tasks.json +++ b/archreview/plans/R2-01-s7-fault-hardening-plan.md.tasks.json @@ -46,7 +46,7 @@ { "id": 7, "subject": "GREEN: broaden IsS7ConnectionFatal to the ISO-on-TCP framing surface (NOT InvalidDataException) + xmldoc (STAB-15a)", - "status": "pending", + "status": "completed", "blockedBy": [6] }, { diff --git a/src/Drivers/ZB.MOM.WW.OtOpcUa.Driver.S7/S7Driver.cs b/src/Drivers/ZB.MOM.WW.OtOpcUa.Driver.S7/S7Driver.cs index 1e93068b..0fc53c50 100644 --- a/src/Drivers/ZB.MOM.WW.OtOpcUa.Driver.S7/S7Driver.cs +++ b/src/Drivers/ZB.MOM.WW.OtOpcUa.Driver.S7/S7Driver.cs @@ -1256,10 +1256,24 @@ public sealed class S7Driver /// /// True when (or any inner exception) is a socket-level / connection - /// loss that a reopen can repair — a / - /// / , or an S7.Net - /// carrying . A - /// data-address / type error (S7.Net's / + /// loss OR an ISO-on-TCP framing/desync fault that a reopen can repair — a + /// / / + /// ; an S7.Net framing violation + /// ( / / + /// ); or a carrying + /// or . + /// 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). + /// + /// Deliberately NOT : the driver's own decode + /// backstop ( / ) 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. + /// + /// A data-address / type error (S7.Net's / /// for a bad address, PUT/GET-denied, etc.) is deliberately /// NOT treated as fatal — reopening wouldn't help and would churn a healthy connection. /// @@ -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;