using ZB.MOM.WW.ScadaBridge.Commons.Types.Alarms; using ZB.MOM.WW.ScadaBridge.Commons.Types.Enums; namespace ZB.MOM.WW.ScadaBridge.DataConnectionLayer; /// /// Parsed native-alarm condition filter (M2.4 / #8). /// /// /// A source's conditionFilter is a comma-separated, case-insensitive list /// of alarm/condition type names, matched against /// . A null, blank, or /// all-empty list means "mirror every condition" (the historical behaviour), /// represented here by . /// /// /// /// This is the authoritative client-side gate consulted in the /// DataConnectionActor routing path, so it applies uniformly across OPC UA /// (whose server-side WhereClause is only a bandwidth optimisation) and the /// MxGateway (whose single gateway-wide feed has no server-side filter at all). /// Parse once at subscribe time; is the hot-path check. /// /// public sealed class AlarmConditionFilter { /// The shared allow-all instance (empty filter set). public static readonly AlarmConditionFilter AllowAll = new(new HashSet(StringComparer.OrdinalIgnoreCase)); private readonly HashSet _names; private AlarmConditionFilter(HashSet names) => _names = names; /// true when no type names are configured — every condition is allowed. public bool IsEmpty => _names.Count == 0; /// The normalized (trimmed) type names, for the OPC UA server-side WhereClause optimisation. public IReadOnlyCollection Names => _names; /// /// Parses a raw conditionFilter string into a normalized, case-insensitive /// type-name set. null/blank/all-empty input yields an empty (allow-all) filter. /// /// The raw comma-separated filter string, or null. /// A parsed ; never null. public static AlarmConditionFilter Parse(string? conditionFilter) { if (string.IsNullOrWhiteSpace(conditionFilter)) return AllowAll; var names = new HashSet(StringComparer.OrdinalIgnoreCase); foreach (var raw in conditionFilter.Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries)) names.Add(raw); return names.Count == 0 ? AllowAll : new AlarmConditionFilter(names); } /// /// Returns true when should be delivered: /// the filter is empty (allow all), the transition is a framing sentinel /// (, which carries no condition /// type and must never be swallowed or the snapshot swap never completes), or its /// is in the configured set. /// /// The protocol-neutral transition to test. /// true to deliver the transition; false to drop it. public bool IsAllowed(NativeAlarmTransition transition) { if (_names.Count == 0) return true; // SnapshotComplete is pure framing (no condition payload) — never filter it. if (transition.Kind == AlarmTransitionKind.SnapshotComplete) return true; return _names.Contains(transition.AlarmTypeName); } }