Skip to main content

bacnet_client/client/
read_range.rs

1//! ReadRange: one page, checked strictly or leniently against its request.
2use super::*;
3
4use bacnet_services::read_range::{
5    RangeSpec, ReadRangeAck, ReadRangeReply, ReadRangeRequest, ReadRangeValidation,
6};
7use bacnet_types::enums::PropertyIdentifier;
8use bacnet_types::primitives::ObjectIdentifier;
9
10use crate::log_reader::{LogCursor, LogPage, LogRequester};
11
12impl<T: TransportPort + 'static> BACnetClient<T> {
13    /// Read a range of items from a list or log-buffer property.
14    ///
15    /// The acknowledgement must keep every rule
16    /// [`ReadRangeAck::violations`] checks against the request; one that
17    /// breaks a rule fails with [`Error::ReadRangeViolation`]. Use
18    /// [`read_range_with`](Self::read_range_with) and
19    /// [`ReadRangeValidation::Lenient`] to keep such an answer.
20    pub async fn read_range(
21        &self,
22        destination_mac: &[u8],
23        object_identifier: ObjectIdentifier,
24        property_identifier: PropertyIdentifier,
25        property_array_index: Option<u32>,
26        range: Option<RangeSpec>,
27    ) -> Result<ReadRangeAck, Error> {
28        let request = ReadRangeRequest {
29            object_identifier,
30            property_identifier,
31            property_array_index,
32            range,
33        };
34        self.read_range_with(destination_mac, &request, ReadRangeValidation::Strict)
35            .await
36            .map(|reply| reply.ack)
37    }
38
39    /// Read a range of items, choosing how to treat an acknowledgement that
40    /// breaks a rule: [`Strict`](ReadRangeValidation::Strict) refuses it,
41    /// [`Lenient`](ReadRangeValidation::Lenient) returns it with the list of
42    /// rules it broke, so a device that numbers the record after its
43    /// sequence wrap 0 doesn't cost the page.
44    ///
45    /// An acknowledgement that doesn't decode fails under either mode.
46    pub async fn read_range_with(
47        &self,
48        destination_mac: &[u8],
49        request: &ReadRangeRequest,
50        validation: ReadRangeValidation,
51    ) -> Result<ReadRangeReply, Error> {
52        let mut buf = BytesMut::new();
53        request.encode(&mut buf)?;
54        let response = self
55            .confirmed_request(destination_mac, ConfirmedServiceChoice::READ_RANGE, &buf)
56            .await?;
57        ReadRangeReply::check(request, ReadRangeAck::decode(&response)?, validation)
58    }
59
60    /// Read a range from a discovered device, routing when needed.
61    ///
62    /// The acknowledgement is checked strictly, as in [`Self::read_range`].
63    /// A missing device fails before sending.
64    pub async fn read_range_from_device(
65        &self,
66        device_instance: u32,
67        object_identifier: ObjectIdentifier,
68        property_identifier: PropertyIdentifier,
69        property_array_index: Option<u32>,
70        range: Option<RangeSpec>,
71    ) -> Result<ReadRangeAck, Error> {
72        let request = ReadRangeRequest {
73            object_identifier,
74            property_identifier,
75            property_array_index,
76            range,
77        };
78        self.read_range_from_device_with(device_instance, &request, ReadRangeValidation::Strict)
79            .await
80            .map(|reply| reply.ack)
81    }
82
83    /// Read a range from a discovered device with strict or lenient validation.
84    ///
85    /// This resolves the device once and preserves the acknowledgement checks
86    /// of [`Self::read_range_with`], including decode failures in either mode.
87    pub async fn read_range_from_device_with(
88        &self,
89        device_instance: u32,
90        request: &ReadRangeRequest,
91        validation: ReadRangeValidation,
92    ) -> Result<ReadRangeReply, Error> {
93        let (mac, routing) = self.resolve_device(device_instance).await?;
94        if let Some((network, address)) = routing {
95            self.read_range_routed(&mac, network, &address, request, validation)
96                .await
97        } else {
98            self.read_range_with(&mac, request, validation).await
99        }
100    }
101
102    /// Read one log page from a discovered device, routing every subrequest.
103    ///
104    /// The router, remote network and station are resolved once for this page;
105    /// metadata property reads and range requests all use that address snapshot.
106    /// A later page resolves the device again. Cursor, page-size and reply rules
107    /// are those of [`Self::read_log_page`]; a missing device fails before sending.
108    pub async fn read_log_page_from_device(
109        &self,
110        device_instance: u32,
111        log: ObjectIdentifier,
112        cursor: LogCursor,
113        page_size: u16,
114    ) -> Result<LogPage, Error> {
115        let (mac, routing) = self.resolve_device(device_instance).await?;
116        if let Some((network, address)) = routing {
117            let requester = RoutedLogRequester {
118                client: self,
119                network,
120                address: &address,
121            };
122            crate::log_reader::read_log_page(&requester, &mac, log, cursor, page_size).await
123        } else {
124            self.read_log_page(&mac, log, cursor, page_size).await
125        }
126    }
127
128    /// One routed range with the same decoding and validation as the local path.
129    async fn read_range_routed(
130        &self,
131        router_mac: &[u8],
132        network: u16,
133        address: &[u8],
134        request: &ReadRangeRequest,
135        validation: ReadRangeValidation,
136    ) -> Result<ReadRangeReply, Error> {
137        let mut buf = BytesMut::new();
138        request.encode(&mut buf)?;
139        let response = self
140            .confirmed_request_routed(
141                router_mac,
142                network,
143                address,
144                ConfirmedServiceChoice::READ_RANGE,
145                &buf,
146            )
147            .await?;
148        ReadRangeReply::check(request, ReadRangeAck::decode(&response)?, validation)
149    }
150}
151
152/// A page's remote station, fixed across metadata reads and range retries.
153struct RoutedLogRequester<'a, T: TransportPort> {
154    client: &'a BACnetClient<T>,
155    network: u16,
156    address: &'a [u8],
157}
158
159impl<T: TransportPort + 'static> LogRequester for RoutedLogRequester<'_, T> {
160    async fn read_unsigned(
161        &self,
162        router_mac: &[u8],
163        log: ObjectIdentifier,
164        property: PropertyIdentifier,
165    ) -> Result<u64, Error> {
166        let ack = self
167            .client
168            .read_property_routed(router_mac, self.network, self.address, log, property, None)
169            .await?;
170        crate::log_reader::unsigned_value(&ack.property_value)
171    }
172
173    async fn read_range_lenient(
174        &self,
175        router_mac: &[u8],
176        request: &ReadRangeRequest,
177    ) -> Result<ReadRangeReply, Error> {
178        self.client
179            .read_range_routed(
180                router_mac,
181                self.network,
182                self.address,
183                request,
184                ReadRangeValidation::Lenient,
185            )
186            .await
187    }
188}
189
190#[cfg(test)]
191#[path = "routed_read_services_tests.rs"]
192mod tests;