opcua/types/
extension_object.rs

1// OPCUA for Rust
2// SPDX-License-Identifier: MPL-2.0
3// Copyright (C) 2017-2024 Adam Lock
4
5//! Contains the implementation of `ExtensionObject`.
6
7use std::{
8    error::Error,
9    fmt,
10    io::{Cursor, Read, Write},
11};
12
13use super::{
14    byte_string::ByteString, encoding::*, node_id::NodeId, node_ids::ObjectId,
15    status_codes::StatusCode, string::XmlElement,
16};
17
18#[derive(Debug)]
19pub struct ExtensionObjectError;
20
21impl fmt::Display for ExtensionObjectError {
22    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
23        write!(f, "ExtensionObjectError")
24    }
25}
26
27impl Error for ExtensionObjectError {}
28
29/// Enumeration that holds the kinds of encoding that an ExtensionObject data may be encoded with.
30#[derive(PartialEq, Debug, Clone, Serialize, Deserialize)]
31pub enum ExtensionObjectEncoding {
32    /// For an extension object with nothing encoded with it
33    None,
34    /// For an extension object with data encoded in a ByteString
35    ByteString(ByteString),
36    /// For an extension object with data encoded in an XML string
37    XmlElement(XmlElement),
38}
39
40/// An extension object holds a serialized object identified by its node id.
41#[derive(PartialEq, Debug, Clone, Serialize, Deserialize)]
42#[serde(rename_all = "PascalCase")]
43pub struct ExtensionObject {
44    pub node_id: NodeId,
45    pub body: ExtensionObjectEncoding,
46}
47
48impl Default for ExtensionObject {
49    fn default() -> Self {
50        Self::null()
51    }
52}
53
54impl BinaryEncoder<ExtensionObject> for ExtensionObject {
55    fn byte_len(&self) -> usize {
56        let mut size = self.node_id.byte_len();
57        size += match self.body {
58            ExtensionObjectEncoding::None => 1,
59            ExtensionObjectEncoding::ByteString(ref value) => {
60                // Encoding mask + data
61                1 + value.byte_len()
62            }
63            ExtensionObjectEncoding::XmlElement(ref value) => {
64                // Encoding mask + data
65                1 + value.byte_len()
66            }
67        };
68        size
69    }
70
71    fn encode<S: Write>(&self, stream: &mut S) -> EncodingResult<usize> {
72        let mut size = 0;
73        size += self.node_id.encode(stream)?;
74        match self.body {
75            ExtensionObjectEncoding::None => {
76                size += write_u8(stream, 0x0)?;
77            }
78            ExtensionObjectEncoding::ByteString(ref value) => {
79                // Encoding mask + data
80                size += write_u8(stream, 0x1)?;
81                size += value.encode(stream)?;
82            }
83            ExtensionObjectEncoding::XmlElement(ref value) => {
84                // Encoding mask + data
85                size += write_u8(stream, 0x2)?;
86                size += value.encode(stream)?;
87            }
88        }
89        assert_eq!(size, self.byte_len());
90        Ok(size)
91    }
92
93    fn decode<S: Read>(stream: &mut S, decoding_options: &DecodingOptions) -> EncodingResult<Self> {
94        // Extension object is depth checked to prevent deep recursion
95        let _depth_lock = decoding_options.depth_lock()?;
96        let node_id = NodeId::decode(stream, decoding_options)?;
97        let encoding_type = u8::decode(stream, decoding_options)?;
98        let body = match encoding_type {
99            0x0 => ExtensionObjectEncoding::None,
100            0x1 => {
101                ExtensionObjectEncoding::ByteString(ByteString::decode(stream, decoding_options)?)
102            }
103            0x2 => {
104                ExtensionObjectEncoding::XmlElement(XmlElement::decode(stream, decoding_options)?)
105            }
106            _ => {
107                error!("Invalid encoding type {} in stream", encoding_type);
108                return Err(StatusCode::BadDecodingError);
109            }
110        };
111        Ok(ExtensionObject { node_id, body })
112    }
113}
114
115impl ExtensionObject {
116    /// Creates a null extension object, i.e. one with no value or payload
117    pub fn null() -> ExtensionObject {
118        ExtensionObject {
119            node_id: NodeId::null(),
120            body: ExtensionObjectEncoding::None,
121        }
122    }
123
124    /// Tests for null node id.
125    pub fn is_null(&self) -> bool {
126        self.node_id.is_null()
127    }
128
129    /// Tests for empty body.
130    pub fn is_empty(&self) -> bool {
131        self.is_null() || matches!(self.body, ExtensionObjectEncoding::None)
132    }
133
134    /// Returns the object id of the thing this extension object contains, assuming the
135    /// object id can be recognised from the node id.
136    pub fn object_id(&self) -> Result<ObjectId, ExtensionObjectError> {
137        self.node_id
138            .as_object_id()
139            .map_err(|_| ExtensionObjectError)
140    }
141
142    /// Creates an extension object with the specified node id and the encodable object as its payload.
143    /// The body is set to a byte string containing the encoded struct.
144    pub fn from_encodable<N, T>(node_id: N, encodable: &T) -> ExtensionObject
145    where
146        N: Into<NodeId>,
147        T: BinaryEncoder<T>,
148    {
149        // Serialize to extension object
150        let mut stream = Cursor::new(vec![0u8; encodable.byte_len()]);
151        let _ = encodable.encode(&mut stream);
152        ExtensionObject {
153            node_id: node_id.into(),
154            body: ExtensionObjectEncoding::ByteString(ByteString::from(stream.into_inner())),
155        }
156    }
157
158    /// Decodes the inner content of the extension object and returns it. The node id is ignored
159    /// for decoding. The caller supplies the binary encoder impl that should be used to extract
160    /// the data. Errors result in a decoding error.
161    pub fn decode_inner<T>(&self, decoding_options: &DecodingOptions) -> EncodingResult<T>
162    where
163        T: BinaryEncoder<T>,
164    {
165        match self.body {
166            ExtensionObjectEncoding::ByteString(ref byte_string) => {
167                if let Some(ref value) = byte_string.value {
168                    // let value = value.clone();
169                    let mut stream = Cursor::new(value);
170                    T::decode(&mut stream, decoding_options)
171                } else {
172                    Err(StatusCode::BadDecodingError)
173                }
174            }
175            _ => {
176                error!("decode_inner called on an unsupported ExtensionObject type");
177                Err(StatusCode::BadDecodingError)
178            }
179        }
180    }
181}