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 constructor supplies the flat application-level IPV4 profile.
4//! Configuration is read-only over BACnet: this object has no pending
5//! activation owner, socket, or NIC discovery. Reconstruct it to change local
6//! configuration. An explicit B/IP owner may publish its actual bind and mode,
7//! and learn Network_Number/Quality, without changing configured provenance.
8//! In FOREIGN or BBMD mode the object serves that mode's properties, a
9//! BBMD's tables read live from the transport (see `bip_mode`).
10//! Non-B/IP snapshots expose only the common application rows;
11//! they do not claim a complete transport-specific SC, Ethernet or MS/TP profile.
12
13use bacnet_types::enums::{NetworkType, ObjectType, PropertyIdentifier, Reliability};
14use bacnet_types::error::Error;
15use bacnet_types::primitives::{ObjectIdentifier, PropertyValue, StatusFlags};
16use bacnet_types::MacAddr;
17use std::borrow::Cow;
18
19use crate::common::{self, read_common_properties};
20use crate::traits::BACnetObject;
21
22mod bip_config;
23mod bip_mode;
24mod metadata;
25mod registration;
26use bacnet_types::network_number::NetworkNumber;
27pub use bip_config::BipPortConfig;
28
29/// A declared application-port snapshot, optionally associated with an owned link.
30pub struct NetworkPortObject {
31    oid: ObjectIdentifier,
32    name: String,
33    description: String,
34    status_flags: StatusFlags,
35    out_of_service: bool,
36    reliability: Reliability,
37    network_type: NetworkType,
38    network_number: NetworkNumber,
39    mac_address: MacAddr,
40    apdu_length: u32,
41    bip: Option<BipPortConfig>,
42    /// The mode the last published owner reported; NORMAL until one
43    /// publishes. Like the bind, it stays after that owner stops, until a
44    /// new registration reserves the port.
45    mode: bip_mode::LiveMode,
46    binding: std::sync::Weak<()>,
47}
48
49impl NetworkPortObject {
50    /// Complete IPV4 snapshot, in NORMAL mode until a registered owner
51    /// publishes another. Instance is the declared local Port ID (local
52    /// policy: 1..=255); UDP port zero remains valid unbound configuration.
53    pub fn new_bip(
54        instance: u32,
55        name: impl Into<String>,
56        config: BipPortConfig,
57    ) -> Result<Self, Error> {
58        config.validate(instance)?;
59        let mut mac = MacAddr::from_slice(&config.ip_address);
60        mac.extend_from_slice(&config.udp_port.to_be_bytes());
61        Ok(Self {
62            oid: ObjectIdentifier::new(ObjectType::NETWORK_PORT, instance)?,
63            name: name.into(),
64            description: String::new(),
65            status_flags: StatusFlags::empty(),
66            out_of_service: false,
67            reliability: Reliability::NO_FAULT_DETECTED,
68            network_type: NetworkType::IPV4,
69            network_number: NetworkNumber::configured(config.network_number)
70                .expect("validated Network Number"),
71            mac_address: mac,
72            apdu_length: config.apdu_length,
73            bip: Some(config),
74            mode: Default::default(),
75            binding: std::sync::Weak::new(),
76        })
77    }
78
79    /// Common application rows for Ethernet or VIRTUAL configuration. This is not a
80    /// complete link-specific profile. In particular it exposes no IPv4 fields.
81    /// IPV4 must use `new_bip`; the flat B/IP instance policy does not apply here.
82    pub fn new_non_bip(
83        instance: u32,
84        name: impl Into<String>,
85        network_type: NetworkType,
86        network_number: u16,
87        mac_address: MacAddr,
88        apdu_length: u32,
89    ) -> Result<Self, Error> {
90        if !matches!(network_type, NetworkType::ETHERNET | NetworkType::VIRTUAL)
91            || network_number == u16::MAX
92            || apdu_length < 50
93        {
94            return Err(common::value_out_of_range_error());
95        }
96        Ok(Self {
97            oid: ObjectIdentifier::new(ObjectType::NETWORK_PORT, instance)?,
98            name: name.into(),
99            description: String::new(),
100            status_flags: StatusFlags::empty(),
101            out_of_service: false,
102            reliability: Reliability::NO_FAULT_DETECTED,
103            network_type,
104            network_number: NetworkNumber::configured(network_number)
105                .expect("validated Network Number"),
106            mac_address,
107            apdu_length,
108            bip: None,
109            mode: Default::default(),
110            binding: std::sync::Weak::new(),
111        })
112    }
113
114    /// Set the optional descriptive label without altering port configuration.
115    pub fn set_description(&mut self, desc: impl Into<String>) {
116        self.description = desc.into();
117    }
118}
119
120impl BACnetObject for NetworkPortObject {
121    fn object_identifier(&self) -> ObjectIdentifier {
122        self.oid
123    }
124    fn object_name(&self) -> &str {
125        &self.name
126    }
127
128    fn read_property(
129        &self,
130        property: PropertyIdentifier,
131        array_index: Option<u32>,
132    ) -> Result<PropertyValue, Error> {
133        use PropertyIdentifier as P;
134        if let Some(result) = read_common_properties!(self, property, array_index) {
135            return result;
136        }
137        match property {
138            P::OBJECT_TYPE => Ok(PropertyValue::Enumerated(ObjectType::NETWORK_PORT.to_raw())),
139            P::NETWORK_TYPE => Ok(PropertyValue::Enumerated(self.network_type.to_raw())),
140            P::PROTOCOL_LEVEL => Ok(PropertyValue::Enumerated(2)),
141            P::NETWORK_NUMBER => Ok(PropertyValue::Unsigned(
142                self.network_number.snapshot().0.into(),
143            )),
144            P::NETWORK_NUMBER_QUALITY => Ok(PropertyValue::Enumerated(
145                self.network_number.snapshot().1.into(),
146            )),
147            P::MAC_ADDRESS => Ok(PropertyValue::OctetString(self.mac_address.to_vec())),
148            P::APDU_LENGTH => Ok(PropertyValue::Unsigned(self.apdu_length.into())),
149            // Optional unknown rate, not a measured NIC speed (errata item 23).
150            P::LINK_SPEED => Ok(PropertyValue::Real(0.0)),
151            P::CHANGES_PENDING => Ok(PropertyValue::Boolean(false)),
152            _ => {
153                let config = self
154                    .bip
155                    .as_ref()
156                    .ok_or_else(common::unknown_property_error)?;
157                match property {
158                    P::BACNET_IP_MODE => {
159                        Ok(PropertyValue::Enumerated(self.mode.ip_mode().to_raw()))
160                    }
161                    P::IP_ADDRESS => Ok(PropertyValue::OctetString(config.ip_address.to_vec())),
162                    P::IP_SUBNET_MASK => {
163                        Ok(PropertyValue::OctetString(config.subnet_mask.to_vec()))
164                    }
165                    P::IP_DEFAULT_GATEWAY => {
166                        Ok(PropertyValue::OctetString(config.default_gateway.to_vec()))
167                    }
168                    P::BACNET_IP_UDP_PORT => Ok(PropertyValue::Unsigned(config.udp_port.into())),
169                    P::IP_DNS_SERVER => match array_index {
170                        None => Ok(PropertyValue::List(
171                            config
172                                .dns_servers
173                                .iter()
174                                .map(|ip| PropertyValue::OctetString(ip.to_vec()))
175                                .collect(),
176                        )),
177                        Some(0) => Ok(PropertyValue::Unsigned(config.dns_servers.len() as u64)),
178                        Some(index) => config
179                            .dns_servers
180                            .get(index as usize - 1)
181                            .map(|ip| PropertyValue::OctetString(ip.to_vec()))
182                            .ok_or_else(common::invalid_array_index_error),
183                    },
184                    _ => self
185                        .mode
186                        .read(property)
187                        .unwrap_or_else(|| Err(common::unknown_property_error())),
188                }
189            }
190        }
191    }
192
193    fn write_property(
194        &mut self,
195        property: PropertyIdentifier,
196        _array_index: Option<u32>,
197        value: PropertyValue,
198        _priority: Option<u8>,
199    ) -> Result<(), Error> {
200        if property == PropertyIdentifier::OUT_OF_SERVICE && self.is_bound() {
201            return Err(common::write_access_denied_error());
202        }
203        if let Some(result) =
204            common::write_out_of_service(&mut self.out_of_service, property, &value)
205        {
206            return result;
207        }
208        if let Some(result) = common::write_description(&mut self.description, property, &value) {
209            return result;
210        }
211        // Configuration writes require actual activation (12.56); this snapshot
212        // deliberately provides no apply/Command path or inert pending state.
213        if self
214            .property_metadata()
215            .iter()
216            .any(|row| row.property_identifier == property)
217        {
218            Err(common::write_access_denied_error())
219        } else {
220            Err(common::unknown_property_error())
221        }
222    }
223
224    fn property_metadata(&self) -> Cow<'_, [crate::property_metadata::PropertyMetadata]> {
225        metadata::for_object(self)
226    }
227    fn property_list(&self) -> Cow<'static, [PropertyIdentifier]> {
228        crate::property_metadata::property_list_from_metadata(self.property_metadata().as_ref())
229    }
230    fn is_createable(&self) -> bool {
231        false
232    }
233    fn is_deleteable(&self) -> bool {
234        false
235    }
236}
237
238#[cfg(test)]
239mod mode_tests;
240#[cfg(test)]
241mod tests;