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