1use std::collections::HashSet;
24
25use serde::{Deserialize, Serialize};
26
27use crate::protocol::Protocol;
28
29#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
31#[serde(rename_all = "snake_case")]
32pub enum Capability {
33 Read,
36 Write,
38 BatchRead,
40 BatchWrite,
42
43 Subscription,
46 ChangeOfValue,
48 Deadband,
50
51 HistoryRead,
54 HistoryWrite,
56 TrendLog,
58
59 Discovery,
62 Browse,
64
65 Authentication,
68 Encryption,
70 Certificates,
72
73 Alarms,
76 Scheduling,
78 TimeSync,
80 Diagnostics,
82
83 ArrayTypes,
86 StructTypes,
88 BitStrings,
90
91 ModbusBasicRead,
94 ModbusBasicWrite,
96 OpcUaMethods,
98 BacnetServices,
100 KnxGroupComm,
102}
103
104impl Capability {
105 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 pub fn is_core(&self) -> bool {
140 matches!(self, Self::Read | Self::Write | Self::Browse)
141 }
142}
143
144#[derive(Debug, Clone, Default, Serialize, Deserialize)]
146pub struct CapabilitySet {
147 capabilities: HashSet<Capability>,
148}
149
150impl CapabilitySet {
151 pub fn new() -> Self {
153 Self::default()
154 }
155
156 pub fn with_capabilities(caps: impl IntoIterator<Item = Capability>) -> Self {
158 Self {
159 capabilities: caps.into_iter().collect(),
160 }
161 }
162
163 pub fn add(&mut self, cap: Capability) {
165 self.capabilities.insert(cap);
166 }
167
168 pub fn remove(&mut self, cap: Capability) {
170 self.capabilities.remove(&cap);
171 }
172
173 pub fn supports(&self, cap: Capability) -> bool {
175 self.capabilities.contains(&cap)
176 }
177
178 pub fn supports_all(&self, caps: &[Capability]) -> bool {
180 caps.iter().all(|c| self.supports(*c))
181 }
182
183 pub fn supports_any(&self, caps: &[Capability]) -> bool {
185 caps.iter().any(|c| self.supports(*c))
186 }
187
188 pub fn list(&self) -> impl Iterator<Item = Capability> + '_ {
190 self.capabilities.iter().copied()
191 }
192
193 pub fn len(&self) -> usize {
195 self.capabilities.len()
196 }
197
198 pub fn is_empty(&self) -> bool {
200 self.capabilities.is_empty()
201 }
202
203 pub fn merge(&mut self, other: &CapabilitySet) {
205 self.capabilities.extend(&other.capabilities);
206 }
207
208 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
232pub trait ProtocolCapabilities {
234 fn capabilities(&self) -> &CapabilitySet;
236
237 fn supports(&self, cap: Capability) -> bool {
239 self.capabilities().supports(cap)
240 }
241
242 fn supports_all(&self, caps: &[Capability]) -> bool {
244 self.capabilities().supports_all(caps)
245 }
246}
247
248pub 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#[derive(Debug, Default)]
309pub struct CapabilitySetBuilder {
310 set: CapabilitySet,
311}
312
313impl CapabilitySetBuilder {
314 pub fn new() -> Self {
316 Self::default()
317 }
318
319 pub fn from_protocol(protocol: Protocol) -> Self {
321 Self {
322 set: default_capabilities(protocol),
323 }
324 }
325
326 #[allow(clippy::should_implement_trait)]
328 pub fn add(mut self, cap: Capability) -> Self {
329 self.set.add(cap);
330 self
331 }
332
333 pub fn add_all(mut self, caps: impl IntoIterator<Item = Capability>) -> Self {
335 for cap in caps {
336 self.set.add(cap);
337 }
338 self
339 }
340
341 pub fn remove(mut self, cap: Capability) -> Self {
343 self.set.remove(cap);
344 self
345 }
346
347 pub fn with_core(self) -> Self {
349 self.add(Capability::Read)
350 .add(Capability::Write)
351 .add(Capability::Browse)
352 }
353
354 pub fn with_subscriptions(self) -> Self {
356 self.add(Capability::Subscription)
357 .add(Capability::ChangeOfValue)
358 .add(Capability::Deadband)
359 }
360
361 pub fn with_history(self) -> Self {
363 self.add(Capability::HistoryRead)
364 .add(Capability::HistoryWrite)
365 .add(Capability::TrendLog)
366 }
367
368 pub fn with_security(self) -> Self {
370 self.add(Capability::Authentication)
371 .add(Capability::Encryption)
372 .add(Capability::Certificates)
373 }
374
375 pub fn build(self) -> CapabilitySet {
377 self.set
378 }
379}
380
381#[cfg(test)]
382mod tests {
383 use super::*;
384
385 #[test]
386 fn test_capability_set() {
387 let mut set = CapabilitySet::new();
388 set.add(Capability::Read);
389 set.add(Capability::Write);
390
391 assert!(set.supports(Capability::Read));
392 assert!(set.supports(Capability::Write));
393 assert!(!set.supports(Capability::Subscription));
394 assert_eq!(set.len(), 2);
395 }
396
397 #[test]
398 fn test_supports_all_any() {
399 let set = CapabilitySet::with_capabilities([
400 Capability::Read,
401 Capability::Write,
402 Capability::Browse,
403 ]);
404
405 assert!(set.supports_all(&[Capability::Read, Capability::Write]));
406 assert!(!set.supports_all(&[Capability::Read, Capability::Subscription]));
407
408 assert!(set.supports_any(&[Capability::Read, Capability::Subscription]));
409 assert!(!set.supports_any(&[Capability::Subscription, Capability::HistoryRead]));
410 }
411
412 #[test]
413 fn test_default_capabilities() {
414 let modbus_caps = default_capabilities(Protocol::ModbusTcp);
415 assert!(modbus_caps.supports(Capability::Read));
416 assert!(modbus_caps.supports(Capability::ModbusBasicRead));
417 assert!(!modbus_caps.supports(Capability::Subscription));
418
419 let opcua_caps = default_capabilities(Protocol::OpcUa);
420 assert!(opcua_caps.supports(Capability::Subscription));
421 assert!(opcua_caps.supports(Capability::HistoryRead));
422 assert!(opcua_caps.supports(Capability::OpcUaMethods));
423 }
424
425 #[test]
426 fn test_capability_builder() {
427 let set = CapabilitySetBuilder::new()
428 .with_core()
429 .with_subscriptions()
430 .add(Capability::Discovery)
431 .build();
432
433 assert!(set.supports(Capability::Read));
434 assert!(set.supports(Capability::Write));
435 assert!(set.supports(Capability::Browse));
436 assert!(set.supports(Capability::Subscription));
437 assert!(set.supports(Capability::Discovery));
438 }
439
440 #[test]
441 fn test_capability_builder_from_protocol() {
442 let set = CapabilitySetBuilder::from_protocol(Protocol::ModbusTcp)
443 .remove(Capability::Write) .add(Capability::Subscription) .build();
446
447 assert!(set.supports(Capability::Read));
448 assert!(!set.supports(Capability::Write));
449 assert!(set.supports(Capability::Subscription));
450 }
451
452 #[test]
453 fn test_intersection() {
454 let set1 = CapabilitySet::with_capabilities([
455 Capability::Read,
456 Capability::Write,
457 Capability::Browse,
458 ]);
459
460 let set2 = CapabilitySet::with_capabilities([
461 Capability::Read,
462 Capability::Browse,
463 Capability::Subscription,
464 ]);
465
466 let intersection = set1.intersection(&set2);
467 assert!(intersection.supports(Capability::Read));
468 assert!(intersection.supports(Capability::Browse));
469 assert!(!intersection.supports(Capability::Write));
470 assert!(!intersection.supports(Capability::Subscription));
471 }
472
473 #[test]
474 fn test_capability_description() {
475 assert_eq!(Capability::Read.description(), "Read single data points");
476 assert!(Capability::Read.is_core());
477 assert!(!Capability::Subscription.is_core());
478 }
479}