namespace ZB.MOM.WW.OtOpcUa.Driver.Ab.Transport.Cip; /// How CIP requests reach the target's message router (review finding B3; the captures decide /// each family's mode). public enum CipRouting { /// Forward Open a class-3 connection, then SendUnitData. Logix and Micro800 (empty route). Connected, /// SendRRData wrapping each request in Unconnected Send (0x52) carrying the route. UnconnectedSend, /// SendRRData carrying the request bare; any configured CIP path is ignored (PCCC families). Direct, } /// Per-device connection settings. Validated on assignment so an unusable value fails at the /// configuration seam rather than on the wire. public sealed record ConnectionOptions { /// Press safety floor: no setting may let Forward Opens to one device start closer together. public static readonly TimeSpan MinimumForwardOpenInterval = TimeSpan.FromSeconds(1); /// Originator serial shared by every connection this process opens. private static readonly uint ProcessOriginatorSerial = (uint)Random.Shared.Next(1, int.MaxValue); private readonly TimeSpan _timeout = TimeSpan.FromSeconds(5); private readonly int _connectionSize = 4002; private readonly TimeSpan _minForwardOpenInterval = MinimumForwardOpenInterval; private readonly uint _rpiMicros = 2_000_000; private readonly byte _timeoutMultiplier = 1; private readonly TimeSpan? _keepAliveInterval; /// How requests reach the target's message router. public CipRouting Routing { get; init; } = CipRouting.Connected; /// Pack queued requests into Multiple Service Packets (the scheduler's switch). public bool AllowPacking { get; init; } /// Bound on each single exchange (TCP connect, RegisterSession, Forward Open, request). /// Must be finite and positive: an infinite timeout would let an abandoned exchange /// hold the session's single-flight gate forever. public TimeSpan Timeout { get => _timeout; init => _timeout = value > TimeSpan.Zero && value != System.Threading.Timeout.InfiniteTimeSpan ? value : throw new ArgumentOutOfRangeException(nameof(Timeout), value, "Timeout must be finite and positive."); } /// Requested connection size in bytes. Above 511 the open is a Large Forward Open (0x5B). public int ConnectionSize { get => _connectionSize; init => _connectionSize = value is >= 1 and <= ushort.MaxValue ? value : throw new ArgumentOutOfRangeException(nameof(ConnectionSize), value, "Connection size must be 1..65535."); } /// Minimum time between Forward Open attempt starts for one device (≥ 1 s, press safety). public TimeSpan MinForwardOpenInterval { get => _minForwardOpenInterval; init => _minForwardOpenInterval = value >= MinimumForwardOpenInterval && value != System.Threading.Timeout.InfiniteTimeSpan ? value : throw new ArgumentOutOfRangeException(nameof(MinForwardOpenInterval), value, "Must be finite and at least 1 s (press safety)."); } /// Requested packet interval for both directions, in microseconds. public uint RpiMicros { get => _rpiMicros; init => _rpiMicros = value > 0 ? value : throw new ArgumentOutOfRangeException(nameof(RpiMicros), value, "RPI must be positive."); } /// Encoded connection timeout multiplier: the connection expires after RPI × (4 << value). /// The default 1 means ×8, so 2 s × 8 = 16 s. public byte TimeoutMultiplier { get => _timeoutMultiplier; init => _timeoutMultiplier = value <= 7 ? value : throw new ArgumentOutOfRangeException(nameof(TimeoutMultiplier), value, "Multiplier must be 0..7."); } /// How long an idle connected session waits before the target would drop it. public TimeSpan ConnectionExpiry => TimeSpan.FromTicks(RpiMicros * 10L * (4L << TimeoutMultiplier)); /// Idle time after which a keep-alive is sent (Connected only). Defaults to half the /// connection expiry: RPI × 8 / 2 = 8 s. public TimeSpan KeepAliveInterval { get => _keepAliveInterval ?? ConnectionExpiry / 2; init => _keepAliveInterval = value > TimeSpan.Zero && value != System.Threading.Timeout.InfiniteTimeSpan ? value : throw new ArgumentOutOfRangeException(nameof(KeepAliveInterval), value, "Keep-alive interval must be finite and positive."); } /// The vendor id sent in the Forward Open originator identity. public ushort VendorId { get; init; } = 0x1337; /// Random per process; together with the vendor id and connection serial it names a connection. public uint OriginatorSerial { get; init; } = ProcessOriginatorSerial; }