opcua_types/node_id/
mod.rs

1//! Contains implementation of the OPC-UA `NodeId` type,
2//! which is used to identify nodes in the address space of an OPC-UA server.
3
4use std::{
5    fmt,
6    io::{Read, Write},
7    str::FromStr,
8    sync::{
9        atomic::{AtomicU32, Ordering},
10        LazyLock,
11    },
12};
13
14mod id_ref;
15mod identifier;
16#[cfg(feature = "json")]
17mod json;
18#[cfg(feature = "xml")]
19mod xml;
20
21pub use id_ref::{IdentifierRef, IntoNodeIdRef, NodeIdRef};
22pub use identifier::Identifier;
23pub use identifier::{
24    IDENTIFIER_HASH_BYTE_STRING, IDENTIFIER_HASH_GUID, IDENTIFIER_HASH_NUMERIC,
25    IDENTIFIER_HASH_STRING,
26};
27
28use crate::{
29    read_u16, read_u32, read_u8, write_u16, write_u32, write_u8, BinaryDecodable, BinaryEncodable,
30    ByteString, DataTypeId, EncodingResult, Error, Guid, MethodId, ObjectId, ReferenceTypeId,
31    StatusCode, UAString, UaNullable, VariableId,
32};
33
34#[derive(Debug)]
35/// Error returned from working with node IDs.
36pub struct NodeIdError;
37
38impl fmt::Display for NodeIdError {
39    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
40        write!(f, "NodeIdError")
41    }
42}
43
44impl std::error::Error for NodeIdError {}
45
46/// An identifier for a node in the address space of an OPC UA Server.
47#[derive(PartialEq, Eq, Clone, Debug, Hash)]
48pub struct NodeId {
49    /// The index for a namespace
50    pub namespace: u16,
51    /// The identifier for the node in the address space
52    pub identifier: Identifier,
53}
54
55impl fmt::Display for NodeId {
56    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
57        if self.namespace != 0 {
58            write!(f, "ns={};{}", self.namespace, self.identifier)
59        } else {
60            write!(f, "{}", self.identifier)
61        }
62    }
63}
64
65impl UaNullable for NodeId {
66    fn is_ua_null(&self) -> bool {
67        self.is_null()
68    }
69}
70
71impl BinaryEncodable for NodeId {
72    fn byte_len(&self, ctx: &crate::Context<'_>) -> usize {
73        // Type determines the byte code
74        let size: usize = match self.identifier {
75            Identifier::Numeric(value) => {
76                if self.namespace == 0 && value <= 255 {
77                    2
78                } else if self.namespace <= 255 && value <= 65535 {
79                    4
80                } else {
81                    7
82                }
83            }
84            Identifier::String(ref value) => 3 + value.byte_len(ctx),
85            Identifier::Guid(ref value) => 3 + value.byte_len(ctx),
86            Identifier::ByteString(ref value) => 3 + value.byte_len(ctx),
87        };
88        size
89    }
90
91    fn encode<S: Write + ?Sized>(
92        &self,
93        stream: &mut S,
94        ctx: &crate::Context<'_>,
95    ) -> EncodingResult<()> {
96        // Type determines the byte code
97        match &self.identifier {
98            Identifier::Numeric(value) => {
99                if self.namespace == 0 && *value <= 255 {
100                    // node id fits into 2 bytes when the namespace is 0 and the value <= 255
101                    write_u8(stream, 0x0)?;
102                    write_u8(stream, *value as u8)
103                } else if self.namespace <= 255 && *value <= 65535 {
104                    // node id fits into 4 bytes when namespace <= 255 and value <= 65535
105                    write_u8(stream, 0x1)?;
106                    write_u8(stream, self.namespace as u8)?;
107                    write_u16(stream, *value as u16)
108                } else {
109                    // full node id
110                    write_u8(stream, 0x2)?;
111                    write_u16(stream, self.namespace)?;
112                    write_u32(stream, *value)
113                }
114            }
115            Identifier::String(value) => {
116                write_u8(stream, 0x3)?;
117                write_u16(stream, self.namespace)?;
118                value.encode(stream, ctx)
119            }
120            Identifier::Guid(value) => {
121                write_u8(stream, 0x4)?;
122                write_u16(stream, self.namespace)?;
123                value.encode(stream, ctx)
124            }
125            Identifier::ByteString(value) => {
126                write_u8(stream, 0x5)?;
127                write_u16(stream, self.namespace)?;
128                value.encode(stream, ctx)
129            }
130        }
131    }
132}
133
134impl BinaryDecodable for NodeId {
135    fn decode<S: Read + ?Sized>(stream: &mut S, ctx: &crate::Context<'_>) -> EncodingResult<Self> {
136        let identifier = read_u8(stream)?;
137        let node_id = match identifier {
138            0x0 => {
139                let namespace = 0;
140                let value = read_u8(stream)?;
141                NodeId::new(namespace, u32::from(value))
142            }
143            0x1 => {
144                let namespace = read_u8(stream)?;
145                let value = read_u16(stream)?;
146                NodeId::new(u16::from(namespace), u32::from(value))
147            }
148            0x2 => {
149                let namespace = read_u16(stream)?;
150                let value = read_u32(stream)?;
151                NodeId::new(namespace, value)
152            }
153            0x3 => {
154                let namespace = read_u16(stream)?;
155                let value = UAString::decode(stream, ctx)?;
156                NodeId::new(namespace, value)
157            }
158            0x4 => {
159                let namespace = read_u16(stream)?;
160                let value = Guid::decode(stream, ctx)?;
161                NodeId::new(namespace, value)
162            }
163            0x5 => {
164                let namespace = read_u16(stream)?;
165                let value = ByteString::decode(stream, ctx)?;
166                NodeId::new(namespace, value)
167            }
168            _ => {
169                return Err(Error::decoding(format!(
170                    "Unrecognized node id type {identifier}"
171                )));
172            }
173        };
174        Ok(node_id)
175    }
176}
177
178impl FromStr for NodeId {
179    type Err = StatusCode;
180    fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
181        use regex::Regex;
182
183        // Parses a node from a string using the format specified in 5.3.1.10 part 6
184        //
185        // ns=<namespaceindex>;<type>=<value>
186        //
187        // Where type:
188        //   i = NUMERIC
189        //   s = STRING
190        //   g = GUID
191        //   b = OPAQUE (ByteString)
192        //
193        // If namespace == 0, the ns=0; will be omitted
194
195        static RE: LazyLock<Regex> =
196            LazyLock::new(|| Regex::new(r"^(ns=(?P<ns>[0-9]+);)?(?P<t>[isgb]=.+)$").unwrap());
197
198        let captures = RE.captures(s).ok_or(StatusCode::BadNodeIdInvalid)?;
199
200        // Check namespace (optional)
201        let namespace = if let Some(ns) = captures.name("ns") {
202            ns.as_str()
203                .parse::<u16>()
204                .map_err(|_| StatusCode::BadNodeIdInvalid)?
205        } else {
206            0
207        };
208
209        // Type identifier
210        let t = captures.name("t").unwrap();
211        Identifier::from_str(t.as_str())
212            .map(|t| NodeId::new(namespace, t))
213            .map_err(|_| StatusCode::BadNodeIdInvalid)
214    }
215}
216
217impl<'a> From<&'a str> for NodeId {
218    fn from(value: &'a str) -> Self {
219        (0u16, value).into()
220    }
221}
222
223impl From<UAString> for NodeId {
224    fn from(value: UAString) -> Self {
225        (0u16, value).into()
226    }
227}
228
229impl From<u32> for NodeId {
230    fn from(value: u32) -> Self {
231        (0, value).into()
232    }
233}
234
235impl From<Guid> for NodeId {
236    fn from(value: Guid) -> Self {
237        (0, value).into()
238    }
239}
240
241impl From<ByteString> for NodeId {
242    fn from(value: ByteString) -> Self {
243        (0, value).into()
244    }
245}
246
247impl From<&NodeId> for NodeId {
248    fn from(v: &NodeId) -> Self {
249        v.clone()
250    }
251}
252
253impl From<NodeId> for String {
254    fn from(value: NodeId) -> Self {
255        value.to_string()
256    }
257}
258
259impl<'a> From<(u16, &'a str)> for NodeId {
260    fn from(v: (u16, &'a str)) -> Self {
261        Self::new(v.0, UAString::from(v.1))
262    }
263}
264
265impl From<(u16, UAString)> for NodeId {
266    fn from(v: (u16, UAString)) -> Self {
267        Self::new(v.0, v.1)
268    }
269}
270
271impl From<(u16, u32)> for NodeId {
272    fn from(v: (u16, u32)) -> Self {
273        Self::new(v.0, v.1)
274    }
275}
276
277impl From<(u16, Guid)> for NodeId {
278    fn from(v: (u16, Guid)) -> Self {
279        Self::new(v.0, v.1)
280    }
281}
282
283impl From<(u16, ByteString)> for NodeId {
284    fn from(v: (u16, ByteString)) -> Self {
285        Self::new(v.0, v.1)
286    }
287}
288
289static NEXT_NODE_ID_NUMERIC: AtomicU32 = AtomicU32::new(1);
290
291impl Default for NodeId {
292    fn default() -> Self {
293        NodeId::null()
294    }
295}
296
297impl NodeId {
298    /// Constructs a new NodeId from anything that can be turned into Identifier
299    /// u32, Guid, ByteString or String
300    pub fn new<T>(namespace: u16, value: T) -> NodeId
301    where
302        T: Into<Identifier>,
303    {
304        NodeId {
305            namespace,
306            identifier: value.into(),
307        }
308    }
309
310    /// Returns the node id for the root folder.
311    pub fn root_folder_id() -> NodeId {
312        ObjectId::RootFolder.into()
313    }
314
315    /// Returns the node id for the objects folder.
316    pub fn objects_folder_id() -> NodeId {
317        ObjectId::ObjectsFolder.into()
318    }
319
320    /// Returns the node id for the types folder.
321    pub fn types_folder_id() -> NodeId {
322        ObjectId::TypesFolder.into()
323    }
324
325    /// Returns the node id for the views folder.
326    pub fn views_folder_id() -> NodeId {
327        ObjectId::ViewsFolder.into()
328    }
329
330    /// Test if the node id is null, i.e. 0 namespace and 0 identifier
331    pub fn is_null(&self) -> bool {
332        self.namespace == 0 && self.identifier == Identifier::Numeric(0)
333    }
334
335    /// Returns a null node id
336    pub fn null() -> NodeId {
337        NodeId::new(0, 0u32)
338    }
339
340    /// Creates a numeric node id with an id incrementing up from 1000
341    pub fn next_numeric(namespace: u16) -> NodeId {
342        NodeId::new(
343            namespace,
344            NEXT_NODE_ID_NUMERIC.fetch_add(1, Ordering::SeqCst),
345        )
346    }
347
348    /// Extracts an ObjectId from a node id, providing the node id holds an object id
349    pub fn as_object_id(&self) -> std::result::Result<ObjectId, NodeIdError> {
350        match self.identifier {
351            Identifier::Numeric(id) if self.namespace == 0 => {
352                ObjectId::try_from(id).map_err(|_| NodeIdError)
353            }
354            _ => Err(NodeIdError),
355        }
356    }
357
358    /// Try to convert this to a builtin variable ID.
359    pub fn as_variable_id(&self) -> std::result::Result<VariableId, NodeIdError> {
360        match self.identifier {
361            Identifier::Numeric(id) if self.namespace == 0 => {
362                VariableId::try_from(id).map_err(|_| NodeIdError)
363            }
364            _ => Err(NodeIdError),
365        }
366    }
367
368    /// Try to convert this to a builtin reference type ID.
369    pub fn as_reference_type_id(&self) -> std::result::Result<ReferenceTypeId, NodeIdError> {
370        if self.is_null() {
371            Err(NodeIdError)
372        } else {
373            match self.identifier {
374                Identifier::Numeric(id) if self.namespace == 0 => {
375                    ReferenceTypeId::try_from(id).map_err(|_| NodeIdError)
376                }
377                _ => Err(NodeIdError),
378            }
379        }
380    }
381
382    /// Try to convert this to a builtin data type ID.
383    pub fn as_data_type_id(&self) -> std::result::Result<DataTypeId, NodeIdError> {
384        match self.identifier {
385            Identifier::Numeric(id) if self.namespace == 0 => {
386                DataTypeId::try_from(id).map_err(|_| NodeIdError)
387            }
388            _ => Err(NodeIdError),
389        }
390    }
391
392    /// Try to convert this to a builtin method ID.
393    pub fn as_method_id(&self) -> std::result::Result<MethodId, NodeIdError> {
394        match self.identifier {
395            Identifier::Numeric(id) if self.namespace == 0 => {
396                MethodId::try_from(id).map_err(|_| NodeIdError)
397            }
398            _ => Err(NodeIdError),
399        }
400    }
401
402    /// Test if the node id is numeric
403    pub fn is_numeric(&self) -> bool {
404        matches!(self.identifier, Identifier::Numeric(_))
405    }
406
407    /// Test if the node id is a string
408    pub fn is_string(&self) -> bool {
409        matches!(self.identifier, Identifier::String(_))
410    }
411
412    /// Test if the node id is a guid
413    pub fn is_guid(&self) -> bool {
414        matches!(self.identifier, Identifier::Guid(_))
415    }
416
417    /// Test if the node id us a byte string
418    pub fn is_byte_string(&self) -> bool {
419        matches!(self.identifier, Identifier::ByteString(_))
420    }
421
422    /// Get the numeric value of this node ID if it is numeric.
423    pub fn as_u32(&self) -> Option<u32> {
424        match &self.identifier {
425            Identifier::Numeric(i) => Some(*i),
426            _ => None,
427        }
428    }
429}