harden(modbus-rtu): factory throws on unknown transport + document RTU FC-shape assumption

Claude-Session: https://claude.ai/code/session_01GASWkNEi68FSCtvr6rLoEW
This commit is contained in:
Joseph Doherty
2026-07-24 15:33:49 -04:00
parent 4a8c9badce
commit fcc7ed698e
3 changed files with 16 additions and 4 deletions
@@ -105,8 +105,9 @@ public static class ModbusRtuFraming
} }
else else
{ {
// Write-echo response (FC 05/06/15/16) or any other length-less fixed shape: // Fixed 4-byte echo: correct for the write FCs this driver emits (05/06/0F/10). A future
// fixed 4 payload bytes + CRC(2). // variable-length response FC outside 01-04 (e.g. FC23) would be mis-sized here and
// surface as a desync — revisit the FC-shape table if the driver starts emitting one.
trailing = 4 + 2; trailing = 4 + 2;
} }
@@ -16,7 +16,11 @@ public static class ModbusTransportFactory
/// </summary> /// </summary>
/// <param name="options">Driver configuration options.</param> /// <param name="options">Driver configuration options.</param>
/// <returns>A <see cref="ModbusRtuOverTcpTransport"/> when <see cref="ModbusTransportMode.RtuOverTcp"/> /// <returns>A <see cref="ModbusRtuOverTcpTransport"/> when <see cref="ModbusTransportMode.RtuOverTcp"/>
/// is selected; otherwise a <see cref="ModbusTcpTransport"/> (also the default/unrecognized fallback).</returns> /// is selected; a <see cref="ModbusTcpTransport"/> when <see cref="ModbusTransportMode.Tcp"/> is selected.</returns>
/// <exception cref="ArgumentOutOfRangeException">
/// <paramref name="options"/>.<see cref="ModbusDriverOptions.Transport"/> is not a recognized
/// <see cref="ModbusTransportMode"/> member — fail loudly rather than silently falling back to TCP/MBAP.
/// </exception>
public static IModbusTransport Create(ModbusDriverOptions options) public static IModbusTransport Create(ModbusDriverOptions options)
{ {
ArgumentNullException.ThrowIfNull(options); ArgumentNullException.ThrowIfNull(options);
@@ -27,11 +31,13 @@ public static class ModbusTransportFactory
keepAlive: options.KeepAlive, keepAlive: options.KeepAlive,
idleDisconnect: options.IdleDisconnectTimeout, idleDisconnect: options.IdleDisconnectTimeout,
reconnect: options.Reconnect), reconnect: options.Reconnect),
_ => new ModbusTcpTransport( ModbusTransportMode.Tcp => new ModbusTcpTransport(
options.Host, options.Port, options.Timeout, options.AutoReconnect, options.Host, options.Port, options.Timeout, options.AutoReconnect,
keepAlive: options.KeepAlive, keepAlive: options.KeepAlive,
idleDisconnect: options.IdleDisconnectTimeout, idleDisconnect: options.IdleDisconnectTimeout,
reconnect: options.Reconnect), reconnect: options.Reconnect),
_ => throw new ArgumentOutOfRangeException(
nameof(options), options.Transport, "Unknown Modbus transport mode."),
}; };
} }
} }
@@ -19,4 +19,9 @@ public sealed class ModbusTransportFactoryTests
public void Default_options_build_tcp_transport() public void Default_options_build_tcp_transport()
=> ModbusTransportFactory.Create(new ModbusDriverOptions()) => ModbusTransportFactory.Create(new ModbusDriverOptions())
.ShouldBeOfType<ModbusTcpTransport>(); .ShouldBeOfType<ModbusTcpTransport>();
[Fact]
public void Unknown_transport_mode_throws()
=> Should.Throw<ArgumentOutOfRangeException>(
() => ModbusTransportFactory.Create(new ModbusDriverOptions { Transport = (ModbusTransportMode)999 }));
} }