Skip to main content

bacnet_objects/
network_port.rs

1//! NetworkPort object (type 56) per ASHRAE 135-2020 Clause 12.56.
2//!
3//! Represents a physical or virtual network port on a BACnet device,
4//! exposing network configuration (IP address, subnet, gateway, etc.)
5//! and link status information.
6
7use bacnet_types::enums::{ObjectType, PropertyIdentifier};
8use bacnet_types::error::Error;
9use bacnet_types::primitives::{ObjectIdentifier, PropertyValue, StatusFlags};
10use bacnet_types::MacAddr;
11use std::borrow::Cow;
12
13use crate::common::{self, read_common_properties};
14use crate::traits::{BACnetObject, WritePropertyRollback};
15
16/// BACnet Network Port object.
17///
18/// Models a network interface on the device. Key properties include
19/// the network type (IPv4, IPv6, MS/TP, etc.), link speed, MAC address,
20/// and IP configuration parameters.
21pub struct NetworkPortObject {
22    oid: ObjectIdentifier,
23    name: String,
24    description: String,
25    status_flags: StatusFlags,
26    out_of_service: bool,
27    reliability: u32,
28    /// Network type: 0=IPv4, 1=IPv6, 2=MSTP, etc.
29    network_type: u32,
30    /// The BACnet network number this port is connected to.
31    network_number: u32,
32    /// MAC address of this port.
33    mac_address: MacAddr,
34    /// Maximum APDU length accepted on this port.
35    max_apdu_length_accepted: u32,
36    /// Link speed in bits per second.
37    link_speed: f32,
38    /// Whether uncommitted configuration changes are pending.
39    changes_pending: bool,
40    /// NetworkPortCommand: 0=idle, 1=discardChanges, 2=renewFdRegistration, etc.
41    command: u32,
42    /// IP address (4 bytes for IPv4).
43    ip_address: Vec<u8>,
44    /// Default gateway IP address.
45    ip_default_gateway: Vec<u8>,
46    /// Subnet mask.
47    ip_subnet_mask: Vec<u8>,
48    /// BACnet/IP UDP port number.
49    ip_udp_port: u16,
50}
51
52struct NetworkPortWriteRollback {
53    changes_pending: bool,
54}
55
56impl NetworkPortObject {
57    /// Create a new Network Port object.
58    ///
59    /// `network_type` specifies the port type: 0=IPv4, 1=IPv6, 2=MSTP, etc.
60    pub fn new(instance: u32, name: impl Into<String>, network_type: u32) -> Result<Self, Error> {
61        let oid = ObjectIdentifier::new(ObjectType::NETWORK_PORT, instance)?;
62        Ok(Self {
63            oid,
64            name: name.into(),
65            description: String::new(),
66            status_flags: StatusFlags::empty(),
67            out_of_service: false,
68            reliability: 0,
69            network_type,
70            network_number: 0,
71            mac_address: MacAddr::new(),
72            max_apdu_length_accepted: 1476,
73            link_speed: 0.0,
74            changes_pending: false,
75            command: 0,
76            ip_address: vec![0, 0, 0, 0],
77            ip_default_gateway: vec![0, 0, 0, 0],
78            ip_subnet_mask: vec![255, 255, 255, 0],
79            ip_udp_port: 0xBAC0,
80        })
81    }
82
83    /// Set the description string.
84    pub fn set_description(&mut self, desc: impl Into<String>) {
85        self.description = desc.into();
86    }
87
88    /// Set the IP address (4 bytes for IPv4).
89    pub fn set_ip_address(&mut self, addr: Vec<u8>) {
90        self.ip_address = addr;
91    }
92
93    /// Set the default gateway IP address.
94    pub fn set_ip_default_gateway(&mut self, gw: Vec<u8>) {
95        self.ip_default_gateway = gw;
96    }
97
98    /// Set the subnet mask.
99    pub fn set_ip_subnet_mask(&mut self, mask: Vec<u8>) {
100        self.ip_subnet_mask = mask;
101    }
102
103    /// Set the MAC address.
104    pub fn set_mac_address(&mut self, mac: MacAddr) {
105        self.mac_address = mac;
106    }
107
108    /// Set the network number.
109    pub fn set_network_number(&mut self, num: u32) {
110        self.network_number = num;
111    }
112
113    /// Set the link speed in bits per second.
114    pub fn set_link_speed(&mut self, speed: f32) {
115        self.link_speed = speed;
116    }
117
118    /// Set the BACnet/IP UDP port.
119    pub fn set_udp_port(&mut self, port: u16) {
120        self.ip_udp_port = port;
121    }
122}
123
124impl BACnetObject for NetworkPortObject {
125    fn object_identifier(&self) -> ObjectIdentifier {
126        self.oid
127    }
128
129    fn object_name(&self) -> &str {
130        &self.name
131    }
132
133    fn read_property(
134        &self,
135        property: PropertyIdentifier,
136        array_index: Option<u32>,
137    ) -> Result<PropertyValue, Error> {
138        if let Some(result) = read_common_properties!(self, property, array_index) {
139            return result;
140        }
141        match property {
142            p if p == PropertyIdentifier::OBJECT_TYPE => {
143                Ok(PropertyValue::Enumerated(ObjectType::NETWORK_PORT.to_raw()))
144            }
145            p if p == PropertyIdentifier::NETWORK_TYPE => {
146                Ok(PropertyValue::Enumerated(self.network_type))
147            }
148            p if p == PropertyIdentifier::NETWORK_NUMBER => {
149                Ok(PropertyValue::Unsigned(self.network_number as u64))
150            }
151            p if p == PropertyIdentifier::MAC_ADDRESS => {
152                Ok(PropertyValue::OctetString(self.mac_address.to_vec()))
153            }
154            p if p == PropertyIdentifier::MAX_APDU_LENGTH_ACCEPTED => Ok(PropertyValue::Unsigned(
155                self.max_apdu_length_accepted as u64,
156            )),
157            p if p == PropertyIdentifier::LINK_SPEED => Ok(PropertyValue::Real(self.link_speed)),
158            p if p == PropertyIdentifier::CHANGES_PENDING => {
159                Ok(PropertyValue::Boolean(self.changes_pending))
160            }
161            p if p == PropertyIdentifier::COMMAND_NP => Ok(PropertyValue::Enumerated(self.command)),
162            p if p == PropertyIdentifier::IP_ADDRESS => {
163                Ok(PropertyValue::OctetString(self.ip_address.clone()))
164            }
165            p if p == PropertyIdentifier::IP_DEFAULT_GATEWAY => {
166                Ok(PropertyValue::OctetString(self.ip_default_gateway.clone()))
167            }
168            p if p == PropertyIdentifier::IP_SUBNET_MASK => {
169                Ok(PropertyValue::OctetString(self.ip_subnet_mask.clone()))
170            }
171            p if p == PropertyIdentifier::BACNET_IP_UDP_PORT => {
172                Ok(PropertyValue::Unsigned(self.ip_udp_port as u64))
173            }
174            _ => Err(common::unknown_property_error()),
175        }
176    }
177
178    fn write_property(
179        &mut self,
180        property: PropertyIdentifier,
181        _array_index: Option<u32>,
182        value: PropertyValue,
183        _priority: Option<u8>,
184    ) -> Result<(), Error> {
185        if let Some(result) =
186            common::write_out_of_service(&mut self.out_of_service, property, &value)
187        {
188            return result;
189        }
190        if let Some(result) = common::write_description(&mut self.description, property, &value) {
191            return result;
192        }
193        if property == PropertyIdentifier::COMMAND_NP {
194            if let PropertyValue::Enumerated(v) = value {
195                self.command = v;
196                return Ok(());
197            }
198            return Err(common::invalid_data_type_error());
199        }
200        if property == PropertyIdentifier::IP_ADDRESS {
201            if let PropertyValue::OctetString(v) = value {
202                self.ip_address = v;
203                self.changes_pending = true;
204                return Ok(());
205            }
206            return Err(common::invalid_data_type_error());
207        }
208        if property == PropertyIdentifier::IP_DEFAULT_GATEWAY {
209            if let PropertyValue::OctetString(v) = value {
210                self.ip_default_gateway = v;
211                self.changes_pending = true;
212                return Ok(());
213            }
214            return Err(common::invalid_data_type_error());
215        }
216        if property == PropertyIdentifier::IP_SUBNET_MASK {
217            if let PropertyValue::OctetString(v) = value {
218                self.ip_subnet_mask = v;
219                self.changes_pending = true;
220                return Ok(());
221            }
222            return Err(common::invalid_data_type_error());
223        }
224        if property == PropertyIdentifier::BACNET_IP_UDP_PORT {
225            if let PropertyValue::Unsigned(v) = value {
226                if v > u16::MAX as u64 {
227                    return Err(common::value_out_of_range_error());
228                }
229                self.ip_udp_port = v as u16;
230                self.changes_pending = true;
231                return Ok(());
232            }
233            return Err(common::invalid_data_type_error());
234        }
235        if property == PropertyIdentifier::NETWORK_NUMBER {
236            if let PropertyValue::Unsigned(v) = value {
237                self.network_number = common::u64_to_u32(v)?;
238                return Ok(());
239            }
240            return Err(common::invalid_data_type_error());
241        }
242        if property == PropertyIdentifier::MAC_ADDRESS {
243            if let PropertyValue::OctetString(v) = value {
244                self.mac_address = v.into();
245                return Ok(());
246            }
247            return Err(common::invalid_data_type_error());
248        }
249        Err(common::write_access_denied_error())
250    }
251
252    fn property_list(&self) -> Cow<'static, [PropertyIdentifier]> {
253        static PROPS: &[PropertyIdentifier] = &[
254            PropertyIdentifier::OBJECT_IDENTIFIER,
255            PropertyIdentifier::OBJECT_NAME,
256            PropertyIdentifier::DESCRIPTION,
257            PropertyIdentifier::OBJECT_TYPE,
258            PropertyIdentifier::STATUS_FLAGS,
259            PropertyIdentifier::OUT_OF_SERVICE,
260            PropertyIdentifier::RELIABILITY,
261            PropertyIdentifier::NETWORK_TYPE,
262            PropertyIdentifier::NETWORK_NUMBER,
263            PropertyIdentifier::MAC_ADDRESS,
264            PropertyIdentifier::MAX_APDU_LENGTH_ACCEPTED,
265            PropertyIdentifier::LINK_SPEED,
266            PropertyIdentifier::CHANGES_PENDING,
267            PropertyIdentifier::COMMAND_NP,
268            PropertyIdentifier::IP_ADDRESS,
269            PropertyIdentifier::IP_DEFAULT_GATEWAY,
270            PropertyIdentifier::IP_SUBNET_MASK,
271            PropertyIdentifier::BACNET_IP_UDP_PORT,
272        ];
273        Cow::Borrowed(PROPS)
274    }
275
276    fn capture_write_property_rollback(
277        &mut self,
278        property: PropertyIdentifier,
279        _value: &PropertyValue,
280    ) -> Option<WritePropertyRollback> {
281        matches!(
282            property,
283            PropertyIdentifier::IP_ADDRESS
284                | PropertyIdentifier::IP_DEFAULT_GATEWAY
285                | PropertyIdentifier::IP_SUBNET_MASK
286                | PropertyIdentifier::BACNET_IP_UDP_PORT
287        )
288        .then(|| {
289            WritePropertyRollback::new(NetworkPortWriteRollback {
290                changes_pending: self.changes_pending,
291            })
292        })
293    }
294
295    fn restore_write_property_rollback(
296        &mut self,
297        rollback: WritePropertyRollback,
298    ) -> Result<(), Error> {
299        self.changes_pending = rollback
300            .downcast::<NetworkPortWriteRollback>()?
301            .changes_pending;
302        Ok(())
303    }
304
305    /// NetworkPort is not createable or deleteable at runtime.
306    fn is_createable(&self) -> bool {
307        false
308    }
309    fn is_deleteable(&self) -> bool {
310        false
311    }
312}
313
314#[cfg(test)]
315mod tests;