Skip to main content

mabi_core/
capabilities.rs

1//! Protocol capabilities and feature detection.
2//!
3//! This module provides a way to define and query protocol-specific
4//! capabilities, enabling extensible feature detection and runtime
5//! capability negotiation.
6//!
7//! # Example
8//!
9//! ```rust,ignore
10//! use mabi_core::capabilities::{ProtocolCapabilities, Capability};
11//!
12//! // Check if a device supports subscriptions
13//! if device.capabilities().supports(Capability::Subscription) {
14//!     device.subscribe(point_id).await?;
15//! }
16//!
17//! // Get all supported capabilities
18//! for cap in device.capabilities().list() {
19//!     println!("Supports: {:?}", cap);
20//! }
21//! ```
22
23use std::collections::HashSet;
24
25use serde::{Deserialize, Serialize};
26
27use crate::protocol::Protocol;
28
29/// Standard capabilities that protocols may support.
30#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
31#[serde(rename_all = "snake_case")]
32pub enum Capability {
33    // Data Operations
34    /// Single point read operations.
35    Read,
36    /// Single point write operations.
37    Write,
38    /// Batch read operations (multiple points at once).
39    BatchRead,
40    /// Batch write operations (multiple points at once).
41    BatchWrite,
42
43    // Subscription/Notification
44    /// Push-based subscriptions for value changes.
45    Subscription,
46    /// Change of Value (COV) notifications.
47    ChangeOfValue,
48    /// Deadband filtering for subscriptions.
49    Deadband,
50
51    // Historical Data
52    /// Historical data read support.
53    HistoryRead,
54    /// Historical data write support.
55    HistoryWrite,
56    /// Trend logging.
57    TrendLog,
58
59    // Discovery
60    /// Device discovery (broadcast/multicast).
61    Discovery,
62    /// Browse/enumerate data points.
63    Browse,
64
65    // Security
66    /// Authentication support.
67    Authentication,
68    /// Encryption support.
69    Encryption,
70    /// Certificate-based security.
71    Certificates,
72
73    // Advanced Features
74    /// Alarms and events.
75    Alarms,
76    /// Scheduling support.
77    Scheduling,
78    /// Time synchronization.
79    TimeSync,
80    /// Diagnostics and self-test.
81    Diagnostics,
82
83    // Data Types
84    /// Array/sequence data types.
85    ArrayTypes,
86    /// Structure/complex data types.
87    StructTypes,
88    /// Bit string operations.
89    BitStrings,
90
91    // Protocol-Specific
92    /// Modbus function codes 1-4 (basic read).
93    ModbusBasicRead,
94    /// Modbus function codes 5-6, 15-16 (basic write).
95    ModbusBasicWrite,
96    /// OPC UA method calls.
97    OpcUaMethods,
98    /// BACnet services.
99    BacnetServices,
100    /// KNX group communication.
101    KnxGroupComm,
102}
103
104impl Capability {
105    /// Get a human-readable description of this capability.
106    pub fn description(&self) -> &'static str {
107        match self {
108            Self::Read => "Read single data points",
109            Self::Write => "Write single data points",
110            Self::BatchRead => "Read multiple data points at once",
111            Self::BatchWrite => "Write multiple data points at once",
112            Self::Subscription => "Subscribe to value changes",
113            Self::ChangeOfValue => "Change of Value notifications",
114            Self::Deadband => "Deadband filtering for subscriptions",
115            Self::HistoryRead => "Read historical data",
116            Self::HistoryWrite => "Write historical data",
117            Self::TrendLog => "Trend logging support",
118            Self::Discovery => "Device discovery",
119            Self::Browse => "Browse/enumerate data points",
120            Self::Authentication => "Authentication support",
121            Self::Encryption => "Encryption support",
122            Self::Certificates => "Certificate-based security",
123            Self::Alarms => "Alarms and events",
124            Self::Scheduling => "Scheduling support",
125            Self::TimeSync => "Time synchronization",
126            Self::Diagnostics => "Diagnostics and self-test",
127            Self::ArrayTypes => "Array/sequence data types",
128            Self::StructTypes => "Structure/complex data types",
129            Self::BitStrings => "Bit string operations",
130            Self::ModbusBasicRead => "Modbus basic read functions",
131            Self::ModbusBasicWrite => "Modbus basic write functions",
132            Self::OpcUaMethods => "OPC UA method calls",
133            Self::BacnetServices => "BACnet services",
134            Self::KnxGroupComm => "KNX group communication",
135        }
136    }
137
138    /// Check if this is a core capability (expected in most protocols).
139    pub fn is_core(&self) -> bool {
140        matches!(self, Self::Read | Self::Write | Self::Browse)
141    }
142}
143
144/// A set of capabilities supported by a device or protocol.
145#[derive(Debug, Clone, Default, Serialize, Deserialize)]
146pub struct CapabilitySet {
147    capabilities: HashSet<Capability>,
148}
149
150impl CapabilitySet {
151    /// Create an empty capability set.
152    pub fn new() -> Self {
153        Self::default()
154    }
155
156    /// Create a capability set with the given capabilities.
157    pub fn with_capabilities(caps: impl IntoIterator<Item = Capability>) -> Self {
158        Self {
159            capabilities: caps.into_iter().collect(),
160        }
161    }
162
163    /// Add a capability.
164    pub fn add(&mut self, cap: Capability) {
165        self.capabilities.insert(cap);
166    }
167
168    /// Remove a capability.
169    pub fn remove(&mut self, cap: Capability) {
170        self.capabilities.remove(&cap);
171    }
172
173    /// Check if a capability is supported.
174    pub fn supports(&self, cap: Capability) -> bool {
175        self.capabilities.contains(&cap)
176    }
177
178    /// Check if all given capabilities are supported.
179    pub fn supports_all(&self, caps: &[Capability]) -> bool {
180        caps.iter().all(|c| self.supports(*c))
181    }
182
183    /// Check if any of the given capabilities are supported.
184    pub fn supports_any(&self, caps: &[Capability]) -> bool {
185        caps.iter().any(|c| self.supports(*c))
186    }
187
188    /// List all supported capabilities.
189    pub fn list(&self) -> impl Iterator<Item = Capability> + '_ {
190        self.capabilities.iter().copied()
191    }
192
193    /// Get the number of supported capabilities.
194    pub fn len(&self) -> usize {
195        self.capabilities.len()
196    }
197
198    /// Check if empty.
199    pub fn is_empty(&self) -> bool {
200        self.capabilities.is_empty()
201    }
202
203    /// Merge with another capability set.
204    pub fn merge(&mut self, other: &CapabilitySet) {
205        self.capabilities.extend(&other.capabilities);
206    }
207
208    /// Create the intersection of two capability sets.
209    pub fn intersection(&self, other: &CapabilitySet) -> CapabilitySet {
210        CapabilitySet {
211            capabilities: self
212                .capabilities
213                .intersection(&other.capabilities)
214                .copied()
215                .collect(),
216        }
217    }
218}
219
220impl From<Vec<Capability>> for CapabilitySet {
221    fn from(caps: Vec<Capability>) -> Self {
222        Self::with_capabilities(caps)
223    }
224}
225
226impl FromIterator<Capability> for CapabilitySet {
227    fn from_iter<T: IntoIterator<Item = Capability>>(iter: T) -> Self {
228        Self::with_capabilities(iter)
229    }
230}
231
232/// Trait for types that have protocol capabilities.
233pub trait ProtocolCapabilities {
234    /// Get the capability set for this protocol/device.
235    fn capabilities(&self) -> &CapabilitySet;
236
237    /// Check if a specific capability is supported.
238    fn supports(&self, cap: Capability) -> bool {
239        self.capabilities().supports(cap)
240    }
241
242    /// Check if all the given capabilities are supported.
243    fn supports_all(&self, caps: &[Capability]) -> bool {
244        self.capabilities().supports_all(caps)
245    }
246}
247
248/// Get the default capabilities for a protocol.
249pub fn default_capabilities(protocol: Protocol) -> CapabilitySet {
250    match protocol {
251        Protocol::ModbusTcp | Protocol::ModbusRtu => CapabilitySet::with_capabilities([
252            Capability::Read,
253            Capability::Write,
254            Capability::BatchRead,
255            Capability::BatchWrite,
256            Capability::Browse,
257            Capability::ModbusBasicRead,
258            Capability::ModbusBasicWrite,
259            Capability::BitStrings,
260        ]),
261
262        Protocol::OpcUa => CapabilitySet::with_capabilities([
263            Capability::Read,
264            Capability::Write,
265            Capability::BatchRead,
266            Capability::BatchWrite,
267            Capability::Browse,
268            Capability::Subscription,
269            Capability::Deadband,
270            Capability::HistoryRead,
271            Capability::HistoryWrite,
272            Capability::Discovery,
273            Capability::Authentication,
274            Capability::Encryption,
275            Capability::Certificates,
276            Capability::Alarms,
277            Capability::OpcUaMethods,
278            Capability::ArrayTypes,
279            Capability::StructTypes,
280        ]),
281
282        Protocol::BacnetIp => CapabilitySet::with_capabilities([
283            Capability::Read,
284            Capability::Write,
285            Capability::BatchRead,
286            Capability::BatchWrite,
287            Capability::Browse,
288            Capability::ChangeOfValue,
289            Capability::Discovery,
290            Capability::TrendLog,
291            Capability::Alarms,
292            Capability::Scheduling,
293            Capability::TimeSync,
294            Capability::BacnetServices,
295        ]),
296
297        Protocol::KnxIp => CapabilitySet::with_capabilities([
298            Capability::Read,
299            Capability::Write,
300            Capability::Browse,
301            Capability::Discovery,
302            Capability::KnxGroupComm,
303        ]),
304    }
305}
306
307/// Builder for creating custom capability sets.
308#[derive(Debug, Default)]
309pub struct CapabilitySetBuilder {
310    set: CapabilitySet,
311}
312
313impl CapabilitySetBuilder {
314    /// Create a new builder.
315    pub fn new() -> Self {
316        Self::default()
317    }
318
319    /// Start with default capabilities for a protocol.
320    pub fn from_protocol(protocol: Protocol) -> Self {
321        Self {
322            set: default_capabilities(protocol),
323        }
324    }
325
326    /// Add a capability.
327    pub fn add(mut self, cap: Capability) -> Self {
328        self.set.add(cap);
329        self
330    }
331
332    /// Add multiple capabilities.
333    pub fn add_all(mut self, caps: impl IntoIterator<Item = Capability>) -> Self {
334        for cap in caps {
335            self.set.add(cap);
336        }
337        self
338    }
339
340    /// Remove a capability.
341    pub fn remove(mut self, cap: Capability) -> Self {
342        self.set.remove(cap);
343        self
344    }
345
346    /// Add core capabilities (Read, Write, Browse).
347    pub fn with_core(self) -> Self {
348        self.add(Capability::Read)
349            .add(Capability::Write)
350            .add(Capability::Browse)
351    }
352
353    /// Add subscription capabilities.
354    pub fn with_subscriptions(self) -> Self {
355        self.add(Capability::Subscription)
356            .add(Capability::ChangeOfValue)
357            .add(Capability::Deadband)
358    }
359
360    /// Add history capabilities.
361    pub fn with_history(self) -> Self {
362        self.add(Capability::HistoryRead)
363            .add(Capability::HistoryWrite)
364            .add(Capability::TrendLog)
365    }
366
367    /// Add security capabilities.
368    pub fn with_security(self) -> Self {
369        self.add(Capability::Authentication)
370            .add(Capability::Encryption)
371            .add(Capability::Certificates)
372    }
373
374    /// Build the capability set.
375    pub fn build(self) -> CapabilitySet {
376        self.set
377    }
378}
379
380#[cfg(test)]
381mod tests {
382    use super::*;
383
384    #[test]
385    fn test_capability_set() {
386        let mut set = CapabilitySet::new();
387        set.add(Capability::Read);
388        set.add(Capability::Write);
389
390        assert!(set.supports(Capability::Read));
391        assert!(set.supports(Capability::Write));
392        assert!(!set.supports(Capability::Subscription));
393        assert_eq!(set.len(), 2);
394    }
395
396    #[test]
397    fn test_supports_all_any() {
398        let set = CapabilitySet::with_capabilities([
399            Capability::Read,
400            Capability::Write,
401            Capability::Browse,
402        ]);
403
404        assert!(set.supports_all(&[Capability::Read, Capability::Write]));
405        assert!(!set.supports_all(&[Capability::Read, Capability::Subscription]));
406
407        assert!(set.supports_any(&[Capability::Read, Capability::Subscription]));
408        assert!(!set.supports_any(&[Capability::Subscription, Capability::HistoryRead]));
409    }
410
411    #[test]
412    fn test_default_capabilities() {
413        let modbus_caps = default_capabilities(Protocol::ModbusTcp);
414        assert!(modbus_caps.supports(Capability::Read));
415        assert!(modbus_caps.supports(Capability::ModbusBasicRead));
416        assert!(!modbus_caps.supports(Capability::Subscription));
417
418        let opcua_caps = default_capabilities(Protocol::OpcUa);
419        assert!(opcua_caps.supports(Capability::Subscription));
420        assert!(opcua_caps.supports(Capability::HistoryRead));
421        assert!(opcua_caps.supports(Capability::OpcUaMethods));
422    }
423
424    #[test]
425    fn test_capability_builder() {
426        let set = CapabilitySetBuilder::new()
427            .with_core()
428            .with_subscriptions()
429            .add(Capability::Discovery)
430            .build();
431
432        assert!(set.supports(Capability::Read));
433        assert!(set.supports(Capability::Write));
434        assert!(set.supports(Capability::Browse));
435        assert!(set.supports(Capability::Subscription));
436        assert!(set.supports(Capability::Discovery));
437    }
438
439    #[test]
440    fn test_capability_builder_from_protocol() {
441        let set = CapabilitySetBuilder::from_protocol(Protocol::ModbusTcp)
442            .remove(Capability::Write) // Remove write capability
443            .add(Capability::Subscription) // Add custom capability
444            .build();
445
446        assert!(set.supports(Capability::Read));
447        assert!(!set.supports(Capability::Write));
448        assert!(set.supports(Capability::Subscription));
449    }
450
451    #[test]
452    fn test_intersection() {
453        let set1 = CapabilitySet::with_capabilities([
454            Capability::Read,
455            Capability::Write,
456            Capability::Browse,
457        ]);
458
459        let set2 = CapabilitySet::with_capabilities([
460            Capability::Read,
461            Capability::Browse,
462            Capability::Subscription,
463        ]);
464
465        let intersection = set1.intersection(&set2);
466        assert!(intersection.supports(Capability::Read));
467        assert!(intersection.supports(Capability::Browse));
468        assert!(!intersection.supports(Capability::Write));
469        assert!(!intersection.supports(Capability::Subscription));
470    }
471
472    #[test]
473    fn test_capability_description() {
474        assert_eq!(Capability::Read.description(), "Read single data points");
475        assert!(Capability::Read.is_core());
476        assert!(!Capability::Subscription.is_core());
477    }
478}