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 pub fn add(mut self, cap: Capability) -> Self {
328 self.set.add(cap);
329 self
330 }
331
332 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 pub fn remove(mut self, cap: Capability) -> Self {
342 self.set.remove(cap);
343 self
344 }
345
346 pub fn with_core(self) -> Self {
348 self.add(Capability::Read)
349 .add(Capability::Write)
350 .add(Capability::Browse)
351 }
352
353 pub fn with_subscriptions(self) -> Self {
355 self.add(Capability::Subscription)
356 .add(Capability::ChangeOfValue)
357 .add(Capability::Deadband)
358 }
359
360 pub fn with_history(self) -> Self {
362 self.add(Capability::HistoryRead)
363 .add(Capability::HistoryWrite)
364 .add(Capability::TrendLog)
365 }
366
367 pub fn with_security(self) -> Self {
369 self.add(Capability::Authentication)
370 .add(Capability::Encryption)
371 .add(Capability::Certificates)
372 }
373
374 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) .add(Capability::Subscription) .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}