1use std::cell::Cell;
16use std::ops::ControlFlow;
17use std::time::Duration;
18
19use bacnet_client::client::{BACnetClient, DeviceEventKind};
20use bacnet_client::discovery::DiscoveredDevice;
21use bacnet_client::log_reader::{LogCursor, LogPage};
22use bacnet_services::alarm_event::GetEventInformationAck;
23use bacnet_services::alarm_summary::GetAlarmSummaryAck;
24use bacnet_services::enrollment_summary::{GetEnrollmentSummaryAck, GetEnrollmentSummaryRequest};
25use bacnet_services::file::{AtomicReadFileAck, FileAccessMethod, FileWriteAccessMethod};
26use bacnet_services::read_property::ReadPropertyACK;
27use bacnet_services::read_range::{ReadRangeReply, ReadRangeRequest, ReadRangeValidation};
28use bacnet_services::rpm::ReadPropertyMultipleACK;
29use bacnet_services::who_is::DeviceInstanceRange;
30use bacnet_services::wpm::WriteAccessSpecification;
31use bacnet_transport::port::TransportPort;
32use bacnet_types::constructed::{PropertyReference, ReadAccessSpecification};
33use bacnet_types::enums::PropertyIdentifier;
34use bacnet_types::error::Error;
35use bacnet_types::primitives::ObjectIdentifier;
36use tokio::sync::broadcast::error::RecvError;
37
38use super::target::{Target, bip6_vmac, parse_target_on};
39use crate::engine::{RpmFailure, rpm_failure, rpm_rows, unreachable};
40use crate::format::{LogColumns, RawRow, RowError, StateTexts};
41
42#[derive(Debug, Clone, PartialEq, Eq)]
44pub enum Destination {
45 Mac(Vec<u8>),
47 Device(u32),
49}
50
51fn text(error: Error) -> String {
53 error.to_string()
54}
55
56fn not_found(instance: u32) -> String {
57 format!("Device {instance} not found. Run 'discover' first or use an address.")
58}
59
60pub async fn resolve<T: TransportPort + 'static>(
64 client: &BACnetClient<T>,
65 text: &str,
66 wait: Duration,
67) -> Result<Destination, String> {
68 match parse_target_on(client.transport(), text)? {
69 Target::Mac(mac) => Ok(Destination::Mac(mac)),
70 Target::Bip6(peer) => Ok(Destination::Mac(bip6_vmac(client.transport(), peer).await?)),
71 Target::Instance(instance) => match client.get_device(instance).await {
72 Some(_) => Ok(Destination::Device(instance)),
73 None => Err(not_found(instance)),
74 },
75 Target::Routed { dnet, instance } => {
76 find_routed(client, dnet, &Station::Instance(instance), wait).await
77 }
78 Target::RoutedMac { dnet, mac } => {
79 find_routed(client, dnet, &Station::Mac(mac), wait).await
80 }
81 }
82}
83
84pub async fn resolve_mac<T: TransportPort + 'static>(
88 client: &BACnetClient<T>,
89 text: &str,
90) -> Result<Vec<u8>, String> {
91 match parse_target_on(client.transport(), text)? {
92 Target::Mac(mac) => Ok(mac),
93 Target::Bip6(peer) => bip6_vmac(client.transport(), peer).await,
94 Target::Instance(instance) => match client.get_device(instance).await {
95 Some(device) if device.source_network.is_some() => Err(format!(
96 "Device {instance} is behind a router; only read, readm, read-range, alarms, \
97 alarm-summary, enrollment-summary, file-read, file-write, write, writem, subscribe, control, reinit, time-sync, create-object, delete-object and ack-alarm reach routed devices"
98 )),
99 Some(device) => Ok(device.mac_address.to_vec()),
100 None => Err(not_found(instance)),
101 },
102 Target::Routed { .. } | Target::RoutedMac { .. } => Err(format!(
103 "'{text}' is a routed target; only read, readm, read-range, alarms, \
104 alarm-summary, enrollment-summary, file-read, file-write, write, writem, subscribe, control, reinit, time-sync, create-object, delete-object and ack-alarm reach routed devices"
105 )),
106 }
107}
108
109enum Station {
111 Instance(u32),
112 Mac(Vec<u8>),
113}
114
115impl Station {
116 fn matches(&self, dnet: u16, device: &DiscoveredDevice) -> bool {
117 device.source_network == Some(dnet)
118 && match self {
119 Self::Instance(instance) => device.object_identifier.instance_number() == *instance,
120 Self::Mac(mac) => device
121 .source_address
122 .as_ref()
123 .is_some_and(|address| address.as_slice() == mac.as_slice()),
124 }
125 }
126}
127
128async fn find_routed<T: TransportPort + 'static>(
132 client: &BACnetClient<T>,
133 dnet: u16,
134 station: &Station,
135 wait: Duration,
136) -> Result<Destination, String> {
137 let known = |devices: Vec<DiscoveredDevice>| {
138 devices
139 .iter()
140 .find(|device| station.matches(dnet, device))
141 .map(|device| Destination::Device(device.object_identifier.instance_number()))
142 };
143 if let Some(found) = known(client.discovered_devices().await) {
144 return Ok(found);
145 }
146 let mut events = client.device_events();
148 let range = match station {
149 Station::Instance(instance) => {
150 Some(DeviceInstanceRange::new(*instance, *instance).map_err(text)?)
151 }
152 Station::Mac(_) => None,
153 };
154 client.who_is_network(dnet, range).await.map_err(text)?;
155 let deadline = tokio::time::Instant::now() + wait;
156 loop {
157 match tokio::time::timeout_at(deadline, events.recv()).await {
158 Err(_) => {
159 let name = match station {
160 Station::Instance(instance) => format!("{dnet}:{instance}"),
161 Station::Mac(mac) => super::target::routed_station(dnet, mac)
164 .unwrap_or_else(|| format!("{dnet}:*")),
165 };
166 return Err(format!(
167 "device {name} did not answer a Who-Is on network {dnet} within {} ms",
168 wait.as_millis()
169 ));
170 }
171 Ok(Ok(event)) => {
172 if event.kind != DeviceEventKind::Lost && station.matches(dnet, &event.device) {
173 return Ok(Destination::Device(
174 event.device.object_identifier.instance_number(),
175 ));
176 }
177 }
178 Ok(Err(RecvError::Lagged(_))) => {
180 if let Some(found) = known(client.discovered_devices().await) {
181 return Ok(found);
182 }
183 }
184 Ok(Err(RecvError::Closed)) => return Err("the client stopped".into()),
185 }
186 }
187}
188
189impl Destination {
190 pub async fn atomic_read_file<T: TransportPort + 'static>(
192 &self,
193 client: &BACnetClient<T>,
194 file: ObjectIdentifier,
195 access: FileAccessMethod,
196 ) -> Result<AtomicReadFileAck, Error> {
197 match self {
198 Self::Mac(mac) => client.atomic_read_file_decoded(mac, file, access).await,
199 Self::Device(instance) => {
200 client
201 .atomic_read_file_decoded_from_device(*instance, file, access)
202 .await
203 }
204 }
205 }
206
207 pub async fn atomic_write_file<T: TransportPort + 'static>(
209 &self,
210 client: &BACnetClient<T>,
211 file: ObjectIdentifier,
212 access: FileWriteAccessMethod,
213 ) -> Result<bytes::Bytes, Error> {
214 match self {
215 Self::Mac(mac) => client.atomic_write_file(mac, file, access).await,
216 Self::Device(instance) => {
217 client
218 .atomic_write_file_from_device(*instance, file, access)
219 .await
220 }
221 }
222 }
223 pub async fn create_object<T: TransportPort + 'static>(
225 &self,
226 client: &BACnetClient<T>,
227 specifier: bacnet_services::object_mgmt::ObjectSpecifier,
228 initial_values: Vec<bacnet_services::common::BACnetPropertyValue>,
229 ) -> Result<bacnet_types::primitives::ObjectIdentifier, Error> {
230 match self {
231 Self::Mac(mac) => client.create_object(mac, specifier, initial_values).await,
232 Self::Device(instance) => {
233 client
234 .create_object_from_device(*instance, specifier, initial_values)
235 .await
236 }
237 }
238 }
239
240 pub async fn delete_object<T: TransportPort + 'static>(
242 &self,
243 client: &BACnetClient<T>,
244 object: ObjectIdentifier,
245 ) -> Result<(), Error> {
246 match self {
247 Self::Mac(mac) => client.delete_object(mac, object).await,
248 Self::Device(instance) => client.delete_object_from_device(*instance, object).await,
249 }
250 }
251
252 pub async fn acknowledge_alarm_request<T: TransportPort + 'static>(
254 &self,
255 client: &BACnetClient<T>,
256 request: &bacnet_services::alarm_event::AcknowledgeAlarmRequest,
257 ) -> Result<(), Error> {
258 match self {
259 Self::Mac(mac) => client.acknowledge_alarm_request(mac, request).await,
260 Self::Device(instance) => {
261 client
262 .acknowledge_alarm_request_from_device(*instance, request)
263 .await
264 }
265 }
266 }
267
268 pub async fn device_communication_control<T: TransportPort + 'static>(
270 &self,
271 client: &BACnetClient<T>,
272 state: bacnet_types::enums::EnableDisable,
273 duration: Option<u16>,
274 password: Option<String>,
275 ) -> Result<(), Error> {
276 match self {
277 Self::Mac(mac) => {
278 client
279 .device_communication_control(mac, state, duration, password)
280 .await
281 }
282 Self::Device(instance) => {
283 client
284 .device_communication_control_from_device(*instance, state, duration, password)
285 .await
286 }
287 }
288 }
289
290 pub async fn reinitialize_device<T: TransportPort + 'static>(
292 &self,
293 client: &BACnetClient<T>,
294 state: bacnet_types::enums::ReinitializedState,
295 password: Option<String>,
296 ) -> Result<(), Error> {
297 match self {
298 Self::Mac(mac) => client.reinitialize_device(mac, state, password).await,
299 Self::Device(instance) => {
300 client
301 .reinitialize_device_from_device(*instance, state, password)
302 .await
303 }
304 }
305 }
306
307 pub async fn time_synchronization<T: TransportPort + 'static>(
309 &self,
310 client: &BACnetClient<T>,
311 date: bacnet_types::primitives::Date,
312 time: bacnet_types::primitives::Time,
313 utc: bool,
314 ) -> Result<(), Error> {
315 match (self, utc) {
316 (Self::Mac(mac), false) => client.time_synchronization(mac, date, time).await,
317 (Self::Mac(mac), true) => client.utc_time_synchronization(mac, date, time).await,
318 (Self::Device(instance), false) => {
319 client
320 .time_synchronization_from_device(*instance, date, time)
321 .await
322 }
323 (Self::Device(instance), true) => {
324 client
325 .utc_time_synchronization_from_device(*instance, date, time)
326 .await
327 }
328 }
329 }
330
331 pub async fn get_event_information<T: TransportPort + 'static>(
333 &self,
334 client: &BACnetClient<T>,
335 last: Option<ObjectIdentifier>,
336 ) -> Result<GetEventInformationAck, Error> {
337 match self {
338 Self::Mac(mac) => client.get_event_information(mac, last).await,
339 Self::Device(instance) => {
340 client
341 .get_event_information_from_device(*instance, last)
342 .await
343 }
344 }
345 }
346
347 pub async fn get_alarm_summary<T: TransportPort + 'static>(
349 &self,
350 client: &BACnetClient<T>,
351 ) -> Result<GetAlarmSummaryAck, Error> {
352 match self {
353 Self::Mac(mac) => client.get_alarm_summary(mac).await,
354 Self::Device(instance) => client.get_alarm_summary_from_device(*instance).await,
355 }
356 }
357
358 pub async fn get_enrollment_summary<T: TransportPort + 'static>(
360 &self,
361 client: &BACnetClient<T>,
362 request: &GetEnrollmentSummaryRequest,
363 ) -> Result<GetEnrollmentSummaryAck, Error> {
364 match self {
365 Self::Mac(mac) => client.get_enrollment_summary(mac, request).await,
366 Self::Device(instance) => {
367 client
368 .get_enrollment_summary_from_device(*instance, request)
369 .await
370 }
371 }
372 }
373
374 pub async fn read_range<T: TransportPort + 'static>(
376 &self,
377 client: &BACnetClient<T>,
378 request: &ReadRangeRequest,
379 validation: ReadRangeValidation,
380 ) -> Result<ReadRangeReply, Error> {
381 match self {
382 Self::Mac(mac) => client.read_range_with(mac, request, validation).await,
383 Self::Device(instance) => {
384 client
385 .read_range_from_device_with(*instance, request, validation)
386 .await
387 }
388 }
389 }
390
391 pub async fn read_log_page<T: TransportPort + 'static>(
393 &self,
394 client: &BACnetClient<T>,
395 log: ObjectIdentifier,
396 cursor: LogCursor,
397 page_size: u16,
398 ) -> Result<LogPage, Error> {
399 match self {
400 Self::Mac(mac) => client.read_log_page(mac, log, cursor, page_size).await,
401 Self::Device(instance) => {
402 client
403 .read_log_page_from_device(*instance, log, cursor, page_size)
404 .await
405 }
406 }
407 }
408
409 pub async fn log_columns<T: TransportPort + 'static>(
414 &self,
415 client: &BACnetClient<T>,
416 log: ObjectIdentifier,
417 ) -> LogColumns {
418 if !LogColumns::applies(log.object_type()) {
419 return LogColumns::default();
420 }
421 self.read_property(
422 client,
423 log,
424 PropertyIdentifier::LOG_DEVICE_OBJECT_PROPERTY,
425 None,
426 )
427 .await
428 .map(|ack| LogColumns::decode(&ack.property_value))
429 .unwrap_or_default()
430 }
431
432 pub async fn log_state_texts<T: TransportPort + 'static>(
443 &self,
444 client: &BACnetClient<T>,
445 columns: &mut LogColumns,
446 ) {
447 let log_device = match self {
448 Self::Device(instance) => Some(*instance),
449 Self::Mac(mac) => {
450 let devices = client.discovered_devices().await;
451 let mut at_mac = devices.iter().filter(|device| {
452 device.source_network.is_none()
453 && device.mac_address.as_slice() == mac.as_slice()
454 });
455 match (at_mac.next(), at_mac.next()) {
457 (Some(device), None) => Some(device.object_identifier.instance_number()),
458 _ => None,
459 }
460 }
461 };
462 let no_rpm = Cell::new(false);
463 let no_rpm = &no_rpm;
464 columns
465 .read_state_texts(log_device, |object| async move {
466 match self.state_text_rows(client, object, no_rpm).await {
467 Ok(rows) => ControlFlow::Continue(StateTexts::from_rows(&rows)),
468 Err(error) if unreachable(&error) => ControlFlow::Break(()),
469 Err(_) => ControlFlow::Continue(StateTexts::default()),
470 }
471 })
472 .await;
473 }
474
475 pub async fn state_texts<T: TransportPort + 'static>(
489 &self,
490 client: &BACnetClient<T>,
491 object: ObjectIdentifier,
492 no_rpm: &Cell<bool>,
493 ) -> Result<StateTexts, Error> {
494 if StateTexts::properties(object.object_type()).is_empty() {
495 return Ok(StateTexts::default());
496 }
497 match self.state_text_rows(client, object, no_rpm).await {
498 Ok(rows) => Ok(StateTexts::from_rows(&rows)),
499 Err(error) if unreachable(&error) => Err(error),
500 Err(_) => Ok(StateTexts::default()),
501 }
502 }
503
504 async fn state_text_rows<T: TransportPort + 'static>(
509 &self,
510 client: &BACnetClient<T>,
511 object: ObjectIdentifier,
512 no_rpm: &Cell<bool>,
513 ) -> Result<Vec<RawRow>, Error> {
514 let properties = StateTexts::properties(object.object_type());
515 let read_each = async || {
516 let mut rows = Vec::with_capacity(properties.len());
517 for property in properties {
518 match self.read_property(client, object, *property, None).await {
519 Ok(ack) => rows.push((*property, Ok(ack.property_value))),
520 Err(error) if unreachable(&error) => return Err(error),
521 Err(error) => rows.push((*property, Err(RowError::from(error)))),
522 }
523 }
524 Ok(rows)
525 };
526 if no_rpm.get() {
527 return read_each().await;
528 }
529 if let [property] = properties {
530 let ack = self.read_property(client, object, *property, None).await?;
531 return Ok(vec![(*property, Ok(ack.property_value))]);
532 }
533 let spec = ReadAccessSpecification {
534 object_identifier: object,
535 list_of_property_references: properties
536 .iter()
537 .map(|property| PropertyReference {
538 property_identifier: *property,
539 property_array_index: None,
540 })
541 .collect(),
542 };
543 match self.read_property_multiple(client, vec![spec]).await {
544 Ok(ack) => Ok(rpm_rows(ack)),
545 Err(error) => match rpm_failure(&error) {
546 RpmFailure::Other => Err(error),
547 RpmFailure::Refused => {
548 no_rpm.set(true);
549 read_each().await
550 }
551 RpmFailure::TooLarge => read_each().await,
552 },
553 }
554 }
555
556 pub async fn read_property<T: TransportPort + 'static>(
558 &self,
559 client: &BACnetClient<T>,
560 object: ObjectIdentifier,
561 property: PropertyIdentifier,
562 index: Option<u32>,
563 ) -> Result<ReadPropertyACK, Error> {
564 match self {
565 Self::Mac(mac) => client.read_property(mac, object, property, index).await,
566 Self::Device(instance) => {
567 client
568 .read_property_from_device(*instance, object, property, index)
569 .await
570 }
571 }
572 }
573
574 pub async fn read_property_multiple<T: TransportPort + 'static>(
576 &self,
577 client: &BACnetClient<T>,
578 specs: Vec<ReadAccessSpecification>,
579 ) -> Result<ReadPropertyMultipleACK, Error> {
580 match self {
581 Self::Mac(mac) => client.read_property_multiple(mac, specs).await,
582 Self::Device(instance) => {
583 client
584 .read_property_multiple_from_device(*instance, specs)
585 .await
586 }
587 }
588 }
589
590 pub async fn write_property<T: TransportPort + 'static>(
592 &self,
593 client: &BACnetClient<T>,
594 object: ObjectIdentifier,
595 property: PropertyIdentifier,
596 index: Option<u32>,
597 value: Vec<u8>,
598 priority: Option<u8>,
599 ) -> Result<(), Error> {
600 match self {
601 Self::Mac(mac) => {
602 client
603 .write_property(mac, object, property, index, value, priority)
604 .await
605 }
606 Self::Device(instance) => {
607 client
608 .write_property_to_device(*instance, object, property, index, value, priority)
609 .await
610 }
611 }
612 }
613
614 pub async fn write_property_multiple<T: TransportPort + 'static>(
616 &self,
617 client: &BACnetClient<T>,
618 specs: Vec<WriteAccessSpecification>,
619 ) -> Result<(), Error> {
620 match self {
621 Self::Mac(mac) => client.write_property_multiple(mac, specs).await,
622 Self::Device(instance) => {
623 client
624 .write_property_multiple_to_device(*instance, specs)
625 .await
626 }
627 }
628 }
629
630 pub async fn subscribe_cov<T: TransportPort + 'static>(
632 &self,
633 client: &BACnetClient<T>,
634 process: u32,
635 object: ObjectIdentifier,
636 confirmed: bool,
637 lifetime: Option<u32>,
638 ) -> Result<(), Error> {
639 match self {
640 Self::Mac(mac) => {
641 client
642 .subscribe_cov(mac, process, object, confirmed, lifetime)
643 .await
644 }
645 Self::Device(instance) => {
646 client
647 .subscribe_cov_to_device(*instance, process, object, confirmed, lifetime)
648 .await
649 }
650 }
651 }
652
653 pub async fn unsubscribe_cov<T: TransportPort + 'static>(
655 &self,
656 client: &BACnetClient<T>,
657 process: u32,
658 object: ObjectIdentifier,
659 ) -> Result<(), Error> {
660 match self {
661 Self::Mac(mac) => client.unsubscribe_cov(mac, process, object).await,
662 Self::Device(instance) => {
663 client
664 .unsubscribe_cov_to_device(*instance, process, object)
665 .await
666 }
667 }
668 }
669}