Skip to main content

bacnet_objects/
network_port.rs

1//! Configured Network Port snapshots and selected live state (135-2020 Clause 12.56).
2//!
3//! The B/IP and B/IPv6 constructors supply the flat application-level IPV4
4//! and IPV6 profiles (`ip_config`). Configuration is read-only over BACnet:
5//! this object has no socket or NIC discovery. Reconstruct it to change
6//! local configuration. An explicit B/IP or B/IPv6 owner may publish its
7//! actual bind and mode, and learn Network_Number/Quality, without changing
8//! configured provenance. In FOREIGN or BBMD mode the object serves that
9//! mode's properties, read live from the transport (see `bip_mode`), for
10//! either IP version. A BBMD's BDT and registration setting, and a foreign
11//! device's BBMD and time-to-live, are writable when the transport can
12//! change them, and wait for activation (`pending`), as is a B/IP port's
13//! multicast group (`multicast`).
14//! Other snapshots expose only the common application rows;
15//! they do not claim a complete transport-specific SC, Ethernet or MS/TP profile.
16
17use crate::prelude::*;
18use alloc::borrow::Cow;
19use bacnet_types::MacAddr;
20use bacnet_types::enums::{NetworkType, ObjectType, PropertyIdentifier, Reliability};
21use bacnet_types::error::Error;
22use bacnet_types::primitives::{ObjectIdentifier, PropertyValue, StatusFlags};
23
24use crate::common::{self, read_common_properties};
25use crate::traits::BACnetObject;
26
27mod bip6_config;
28mod bip_config;
29mod bip_mode;
30mod ip_config;
31mod metadata;
32mod multicast;
33mod pending;
34mod registration;
35use bacnet_types::network_number::NetworkNumber;
36pub use bip_config::BipPortConfig;
37pub(crate) use bip6_config::Bip6Bind;
38pub use bip6_config::Bip6PortConfig;
39use ip_config::IpConfig;
40pub(crate) use ip_config::{PortAddress, PortBind};
41
42/// A declared application-port snapshot, optionally associated with an owned link.
43pub struct NetworkPortObject {
44    oid: ObjectIdentifier,
45    name: String,
46    description: String,
47    status_flags: StatusFlags,
48    out_of_service: bool,
49    reliability: Reliability,
50    network_type: NetworkType,
51    network_number: NetworkNumber,
52    mac_address: MacAddr,
53    apdu_length: u32,
54    /// The B/IP or B/IPv6 profile; `None` for the other network types.
55    ip: Option<IpConfig>,
56    /// The mode the last published owner reported; NORMAL until one
57    /// publishes. Like the bind, it stays after that owner stops, until a
58    /// new registration reserves the port.
59    mode: bip_mode::LiveMode,
60    /// Values written to the mode's properties, waiting for activation.
61    pending: pending::Pending,
62    /// Whether the owner activates written values on ReinitializeDevice;
63    /// without it the pending-activation rows refuse writes.
64    activation: bool,
65    /// The B/IP multicast group the owner lends, and a written one waiting.
66    multicast: multicast::Multicast,
67    binding: alloc::sync::Weak<()>,
68}
69
70impl NetworkPortObject {
71    /// Complete IPV4 snapshot, in NORMAL mode until a registered owner
72    /// publishes another. Instance is the declared local Port ID (local
73    /// policy: 1..=255); UDP port zero remains valid unbound configuration.
74    pub fn new_bip(
75        instance: u32,
76        name: impl Into<String>,
77        config: BipPortConfig,
78    ) -> Result<Self, Error> {
79        Self::new_ip(instance, name.into(), IpConfig::V4(config))
80    }
81
82    /// Complete IPV6 (B/IPv6, Annex U) snapshot, in NORMAL mode until a
83    /// registered owner publishes another. The instance policy is B/IP's:
84    /// the declared local Port ID, 1..=255. MAC_Address is the configured
85    /// VMAC until an owner publishes the one it uses.
86    pub fn new_bip6(
87        instance: u32,
88        name: impl Into<String>,
89        config: Bip6PortConfig,
90    ) -> Result<Self, Error> {
91        Self::new_ip(instance, name.into(), IpConfig::V6(config))
92    }
93
94    fn new_ip(instance: u32, name: String, config: IpConfig) -> Result<Self, Error> {
95        config.validate(instance)?;
96        Ok(Self {
97            oid: ObjectIdentifier::new(ObjectType::NETWORK_PORT, instance)?,
98            name,
99            description: String::new(),
100            status_flags: StatusFlags::empty(),
101            out_of_service: false,
102            reliability: Reliability::NO_FAULT_DETECTED,
103            network_type: config.network_type(),
104            network_number: NetworkNumber::configured(config.network_number())
105                .expect("validated Network Number"),
106            mac_address: config.mac(),
107            apdu_length: config.apdu_length(),
108            ip: Some(config),
109            mode: Default::default(),
110            pending: Default::default(),
111            activation: false,
112            multicast: Default::default(),
113            binding: alloc::sync::Weak::new(),
114        })
115    }
116
117    /// Common application rows for Ethernet or VIRTUAL configuration. This is not a
118    /// complete link-specific profile. In particular it exposes no IP fields.
119    /// IPV4 must use `new_bip` and IPV6 `new_bip6`; the flat B/IP instance
120    /// policy does not apply here.
121    pub fn new_non_bip(
122        instance: u32,
123        name: impl Into<String>,
124        network_type: NetworkType,
125        network_number: u16,
126        mac_address: MacAddr,
127        apdu_length: u32,
128    ) -> Result<Self, Error> {
129        if !matches!(network_type, NetworkType::ETHERNET | NetworkType::VIRTUAL)
130            || network_number == u16::MAX
131            || apdu_length < 50
132        {
133            return Err(common::value_out_of_range_error());
134        }
135        Ok(Self {
136            oid: ObjectIdentifier::new(ObjectType::NETWORK_PORT, instance)?,
137            name: name.into(),
138            description: String::new(),
139            status_flags: StatusFlags::empty(),
140            out_of_service: false,
141            reliability: Reliability::NO_FAULT_DETECTED,
142            network_type,
143            network_number: NetworkNumber::configured(network_number)
144                .expect("validated Network Number"),
145            mac_address,
146            apdu_length,
147            ip: None,
148            mode: Default::default(),
149            pending: Default::default(),
150            activation: false,
151            multicast: Default::default(),
152            binding: alloc::sync::Weak::new(),
153        })
154    }
155
156    /// Set the optional descriptive label without altering port configuration.
157    pub fn set_description(&mut self, desc: impl Into<String>) {
158        self.description = desc.into();
159    }
160}
161
162impl BACnetObject for NetworkPortObject {
163    fn object_identifier(&self) -> ObjectIdentifier {
164        self.oid
165    }
166    fn object_name(&self) -> &str {
167        &self.name
168    }
169
170    fn read_property(
171        &self,
172        property: PropertyIdentifier,
173        array_index: Option<u32>,
174    ) -> Result<PropertyValue, Error> {
175        use PropertyIdentifier as P;
176        if let Some(result) = read_common_properties!(self, property, array_index) {
177            return result;
178        }
179        match property {
180            P::OBJECT_TYPE => Ok(PropertyValue::Enumerated(ObjectType::NETWORK_PORT.to_raw())),
181            P::NETWORK_TYPE => Ok(PropertyValue::Enumerated(self.network_type.to_raw())),
182            P::PROTOCOL_LEVEL => Ok(PropertyValue::Enumerated(2)),
183            P::NETWORK_NUMBER => Ok(PropertyValue::Unsigned(
184                self.network_number.snapshot().0.into(),
185            )),
186            P::NETWORK_NUMBER_QUALITY => Ok(PropertyValue::Enumerated(
187                self.network_number.snapshot().1.into(),
188            )),
189            P::MAC_ADDRESS => Ok(PropertyValue::OctetString(self.mac_address.to_vec())),
190            P::APDU_LENGTH => Ok(PropertyValue::Unsigned(self.apdu_length.into())),
191            // Optional unknown rate, not a measured NIC speed (errata item 23).
192            P::LINK_SPEED => Ok(PropertyValue::Real(0.0)),
193            P::CHANGES_PENDING => Ok(PropertyValue::Boolean(self.changes_pending())),
194            _ => {
195                let config = self
196                    .ip
197                    .as_ref()
198                    .ok_or_else(common::unknown_property_error)?;
199                if property == config.mode_property() {
200                    return Ok(PropertyValue::Enumerated(self.mode.ip_mode().to_raw()));
201                }
202                if pending::nat_row(property) && !self.serves_nat() {
203                    return Err(common::unknown_property_error());
204                }
205                if let Some(result) = self.read_multicast(property) {
206                    return result;
207                }
208                config
209                    .read(property, array_index)
210                    .or_else(|| self.pending_value(property))
211                    .or_else(|| self.mode.read(property))
212                    .unwrap_or_else(|| Err(common::unknown_property_error()))
213            }
214        }
215    }
216
217    fn write_property(
218        &mut self,
219        property: PropertyIdentifier,
220        array_index: Option<u32>,
221        value: PropertyValue,
222        _priority: Option<u8>,
223    ) -> Result<(), Error> {
224        if property == PropertyIdentifier::OUT_OF_SERVICE && self.is_bound() {
225            return Err(common::write_access_denied_error());
226        }
227        if let Some(result) =
228            common::write_out_of_service(&mut self.out_of_service, property, &value)
229        {
230            return result;
231        }
232        if let Some(result) = common::write_description(&mut self.description, property, &value) {
233            return result;
234        }
235        if let Some(result) = self.write_pending(property, array_index, &value) {
236            return result;
237        }
238        if let Some(result) = self.write_multicast(property, array_index, &value) {
239            return result;
240        }
241        // Other configuration writes would need an activation owner (12.56)
242        // this snapshot doesn't have.
243        if self
244            .property_metadata()
245            .iter()
246            .any(|row| row.property_identifier == property)
247        {
248            Err(common::write_access_denied_error())
249        } else {
250            Err(common::unknown_property_error())
251        }
252    }
253
254    fn property_metadata(&self) -> Cow<'_, [crate::property_metadata::PropertyMetadata]> {
255        metadata::for_object(self)
256    }
257    fn property_list(&self) -> Cow<'static, [PropertyIdentifier]> {
258        crate::property_metadata::property_list_from_metadata(self.property_metadata().as_ref())
259    }
260    fn is_createable(&self) -> bool {
261        false
262    }
263    fn is_deleteable(&self) -> bool {
264        false
265    }
266}
267
268#[cfg(test)]
269mod bip6_tests;
270#[cfg(test)]
271mod foreign_pending_tests;
272#[cfg(test)]
273mod mode_tests;
274#[cfg(test)]
275mod multicast_tests;
276#[cfg(test)]
277mod nat_pending_tests;
278#[cfg(test)]
279mod pending_tests;
280#[cfg(test)]
281mod tests;