Skip to main content

opcua_types/variant/
type_id.rs

1// OPCUA for Rust
2// SPDX-License-Identifier: MPL-2.0
3// Copyright (C) 2017-2024 Adam Lock
4
5//! The [`VariantTypeId`] type, which is used to inspect variant types without looking at the value.
6
7use std::fmt::Display;
8
9use crate::{DataTypeId, NodeId, NodeIdError, StatusCode};
10
11/// The variant type id is the type of the variant but without its payload.
12#[derive(Debug, Clone, Copy, PartialEq)]
13pub enum VariantTypeId<'a> {
14    /// The variant is empty.
15    Empty,
16    /// The variant is a scalar with this inner type.
17    Scalar(VariantScalarTypeId),
18    /// The variant is an array with this inner type and optionally these ArrayDimensions.
19    Array(VariantScalarTypeId, Option<&'a [u32]>),
20}
21
22impl From<VariantScalarTypeId> for VariantTypeId<'_> {
23    fn from(value: VariantScalarTypeId) -> Self {
24        Self::Scalar(value)
25    }
26}
27
28impl<'a> From<(VariantScalarTypeId, &'a [u32])> for VariantTypeId<'a> {
29    fn from(value: (VariantScalarTypeId, &'a [u32])) -> Self {
30        Self::Array(value.0, Some(value.1))
31    }
32}
33
34#[derive(Debug, Clone, Copy, PartialEq)]
35#[repr(u32)]
36/// The scalar type of a variant.
37pub enum VariantScalarTypeId {
38    /// Boolean
39    Boolean = 1,
40    /// Signed byte
41    SByte = 2,
42    /// Unsigned byte
43    Byte = 3,
44    /// Signed 16 bit integer
45    Int16 = 4,
46    /// Unsigned 16 bit integer
47    UInt16 = 5,
48    /// Signed 32 bit integer
49    Int32 = 6,
50    /// Unsigned 32 bit integer
51    UInt32 = 7,
52    /// Signed 64 bit integer
53    Int64 = 8,
54    /// Unsigned 64 bit integer
55    UInt64 = 9,
56    /// 32 bit floating point number
57    Float = 10,
58    /// 64 bit floating point number
59    Double = 11,
60    /// String
61    String = 12,
62    /// Datetime
63    DateTime = 13,
64    /// Globally unique ID
65    Guid = 14,
66    /// Byte string
67    ByteString = 15,
68    /// XmlElement
69    XmlElement = 16,
70    /// Node ID
71    NodeId = 17,
72    /// Expanded node ID
73    ExpandedNodeId = 18,
74    /// Status code
75    StatusCode = 19,
76    /// Qualified name
77    QualifiedName = 20,
78    /// Localized text
79    LocalizedText = 21,
80    /// Extension object, containing some dynamic structure.
81    ExtensionObject = 22,
82    /// Data value
83    DataValue = 23,
84    /// A nested variant.
85    Variant = 24,
86    /// Diagnostic info
87    DiagnosticInfo = 25,
88}
89
90impl Display for VariantScalarTypeId {
91    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
92        match self {
93            VariantScalarTypeId::Boolean => write!(f, "Boolean"),
94            VariantScalarTypeId::SByte => write!(f, "SByte"),
95            VariantScalarTypeId::Byte => write!(f, "Byte"),
96            VariantScalarTypeId::Int16 => write!(f, "Int16"),
97            VariantScalarTypeId::UInt16 => write!(f, "UInt16"),
98            VariantScalarTypeId::Int32 => write!(f, "Int32"),
99            VariantScalarTypeId::UInt32 => write!(f, "UInt32"),
100            VariantScalarTypeId::Int64 => write!(f, "Int64"),
101            VariantScalarTypeId::UInt64 => write!(f, "UInt64"),
102            VariantScalarTypeId::Float => write!(f, "Float"),
103            VariantScalarTypeId::Double => write!(f, "Double"),
104            VariantScalarTypeId::String => write!(f, "String"),
105            VariantScalarTypeId::DateTime => write!(f, "DateTime"),
106            VariantScalarTypeId::Guid => write!(f, "Guid"),
107            VariantScalarTypeId::ByteString => write!(f, "ByteString"),
108            VariantScalarTypeId::XmlElement => write!(f, "XmlElement"),
109            VariantScalarTypeId::NodeId => write!(f, "NodeId"),
110            VariantScalarTypeId::ExpandedNodeId => write!(f, "ExpandedNodeId"),
111            VariantScalarTypeId::StatusCode => write!(f, "StatusCode"),
112            VariantScalarTypeId::QualifiedName => write!(f, "QualifiedName"),
113            VariantScalarTypeId::LocalizedText => write!(f, "LocalizedText"),
114            VariantScalarTypeId::ExtensionObject => write!(f, "ExtensionObject"),
115            VariantScalarTypeId::DataValue => write!(f, "DataValue"),
116            VariantScalarTypeId::Variant => write!(f, "Variant"),
117            VariantScalarTypeId::DiagnosticInfo => write!(f, "DiagnosticInfo"),
118        }
119    }
120}
121
122impl TryFrom<u32> for VariantScalarTypeId {
123    type Error = StatusCode;
124    fn try_from(value: u32) -> Result<Self, Self::Error> {
125        Ok(match value {
126            1 => Self::Boolean,
127            2 => Self::SByte,
128            3 => Self::Byte,
129            4 => Self::Int16,
130            5 => Self::UInt16,
131            6 => Self::Int32,
132            7 => Self::UInt32,
133            8 => Self::Int64,
134            9 => Self::UInt64,
135            10 => Self::Float,
136            11 => Self::Double,
137            12 => Self::String,
138            13 => Self::DateTime,
139            14 => Self::Guid,
140            15 => Self::ByteString,
141            16 => Self::XmlElement,
142            17 => Self::NodeId,
143            18 => Self::ExpandedNodeId,
144            19 => Self::StatusCode,
145            20 => Self::QualifiedName,
146            21 => Self::LocalizedText,
147            22 => Self::ExtensionObject,
148            23 => Self::DataValue,
149            24 => Self::Variant,
150            25 => Self::DiagnosticInfo,
151            r => {
152                tracing::error!("Got unexpected vlaue for enum VariantScalarTypeId: {r}");
153                return Err(StatusCode::BadDecodingError);
154            }
155        })
156    }
157}
158
159impl TryFrom<&NodeId> for VariantScalarTypeId {
160    type Error = NodeIdError;
161    fn try_from(value: &NodeId) -> Result<Self, NodeIdError> {
162        let type_id = value.as_data_type_id()?;
163
164        Ok(match type_id {
165            DataTypeId::Boolean => Self::Boolean,
166            DataTypeId::Byte => Self::Byte,
167            DataTypeId::Int16 => Self::Int16,
168            DataTypeId::UInt16 => Self::UInt16,
169            DataTypeId::Int32 => Self::Int32,
170            DataTypeId::UInt32 => Self::UInt32,
171            DataTypeId::Int64 => Self::Int64,
172            DataTypeId::UInt64 => Self::UInt64,
173            DataTypeId::Float => Self::Float,
174            DataTypeId::Double => Self::Double,
175            DataTypeId::String => Self::String,
176            DataTypeId::DateTime => Self::DateTime,
177            DataTypeId::Guid => Self::Guid,
178            DataTypeId::ByteString => Self::ByteString,
179            DataTypeId::XmlElement => Self::XmlElement,
180            DataTypeId::NodeId => Self::NodeId,
181            DataTypeId::ExpandedNodeId => Self::ExpandedNodeId,
182            DataTypeId::StatusCode => Self::StatusCode,
183            DataTypeId::QualifiedName => Self::QualifiedName,
184            DataTypeId::LocalizedText => Self::LocalizedText,
185            DataTypeId::DataValue => Self::DataValue,
186            DataTypeId::BaseDataType => Self::Variant,
187            DataTypeId::DiagnosticInfo => Self::DiagnosticInfo,
188            _ => return Err(NodeIdError),
189        })
190    }
191}
192
193impl TryFrom<&NodeId> for VariantTypeId<'_> {
194    type Error = NodeIdError;
195    fn try_from(value: &NodeId) -> Result<Self, NodeIdError> {
196        Ok(Self::Scalar(VariantScalarTypeId::try_from(value)?))
197    }
198}
199
200impl VariantScalarTypeId {
201    /// Get the encoding mask corresponding to this type ID.
202    pub fn encoding_mask(&self) -> u8 {
203        match self {
204            Self::Boolean => EncodingMask::BOOLEAN,
205            Self::SByte => EncodingMask::SBYTE,
206            Self::Byte => EncodingMask::BYTE,
207            Self::Int16 => EncodingMask::INT16,
208            Self::UInt16 => EncodingMask::UINT16,
209            Self::Int32 => EncodingMask::INT32,
210            Self::UInt32 => EncodingMask::UINT32,
211            Self::Int64 => EncodingMask::INT64,
212            Self::UInt64 => EncodingMask::UINT64,
213            Self::Float => EncodingMask::FLOAT,
214            Self::Double => EncodingMask::DOUBLE,
215            Self::String => EncodingMask::STRING,
216            Self::DateTime => EncodingMask::DATE_TIME,
217            Self::Guid => EncodingMask::GUID,
218            Self::StatusCode => EncodingMask::STATUS_CODE,
219            Self::ByteString => EncodingMask::BYTE_STRING,
220            Self::XmlElement => EncodingMask::XML_ELEMENT,
221            Self::QualifiedName => EncodingMask::QUALIFIED_NAME,
222            Self::LocalizedText => EncodingMask::LOCALIZED_TEXT,
223            Self::NodeId => EncodingMask::NODE_ID,
224            Self::ExpandedNodeId => EncodingMask::EXPANDED_NODE_ID,
225            Self::ExtensionObject => EncodingMask::EXTENSION_OBJECT,
226            Self::Variant => EncodingMask::VARIANT,
227            Self::DataValue => EncodingMask::DATA_VALUE,
228            Self::DiagnosticInfo => EncodingMask::DIAGNOSTIC_INFO,
229        }
230    }
231
232    /// Try to get a scalar type from the encoding mask.
233    pub fn from_encoding_mask(encoding_mask: u8) -> Option<Self> {
234        Some(match encoding_mask & !EncodingMask::ARRAY_MASK {
235            EncodingMask::BOOLEAN => Self::Boolean,
236            EncodingMask::SBYTE => Self::SByte,
237            EncodingMask::BYTE => Self::Byte,
238            EncodingMask::INT16 => Self::Int16,
239            EncodingMask::UINT16 => Self::UInt16,
240            EncodingMask::INT32 => Self::Int32,
241            EncodingMask::UINT32 => Self::UInt32,
242            EncodingMask::INT64 => Self::Int64,
243            EncodingMask::UINT64 => Self::UInt64,
244            EncodingMask::FLOAT => Self::Float,
245            EncodingMask::DOUBLE => Self::Double,
246            EncodingMask::STRING => Self::String,
247            EncodingMask::DATE_TIME => Self::DateTime,
248            EncodingMask::GUID => Self::Guid,
249            EncodingMask::STATUS_CODE => Self::StatusCode,
250            EncodingMask::BYTE_STRING => Self::ByteString,
251            EncodingMask::XML_ELEMENT => Self::XmlElement,
252            EncodingMask::QUALIFIED_NAME => Self::QualifiedName,
253            EncodingMask::LOCALIZED_TEXT => Self::LocalizedText,
254            EncodingMask::NODE_ID => Self::NodeId,
255            EncodingMask::EXPANDED_NODE_ID => Self::ExpandedNodeId,
256            EncodingMask::EXTENSION_OBJECT => Self::ExtensionObject,
257            EncodingMask::VARIANT => Self::Variant,
258            EncodingMask::DATA_VALUE => Self::DataValue,
259            EncodingMask::DIAGNOSTIC_INFO => Self::DiagnosticInfo,
260            _ => {
261                return None;
262            }
263        })
264    }
265
266    /// Tests and returns true if the variant holds a numeric type
267    pub fn is_numeric(&self) -> bool {
268        matches!(
269            self,
270            Self::SByte
271                | Self::Byte
272                | Self::Int16
273                | Self::UInt16
274                | Self::Int32
275                | Self::UInt32
276                | Self::Int64
277                | Self::UInt64
278                | Self::Float
279                | Self::Double
280        )
281    }
282
283    /// Returns a data precedence rank for scalar types, OPC UA part 4 table 119. This is used
284    /// when operators are comparing values of differing types. The type with
285    /// the highest precedence dictates how values are converted in order to be compared.
286    pub fn precedence(&self) -> u8 {
287        match self {
288            Self::Double => 1,
289            Self::Float => 2,
290            Self::Int64 => 3,
291            Self::UInt64 => 4,
292            Self::Int32 => 5,
293            Self::UInt32 => 6,
294            Self::StatusCode => 7,
295            Self::Int16 => 8,
296            Self::UInt16 => 9,
297            Self::SByte => 10,
298            Self::Byte => 11,
299            Self::Boolean => 12,
300            Self::Guid => 13,
301            Self::String => 14,
302            Self::ExpandedNodeId => 15,
303            Self::NodeId => 16,
304            Self::LocalizedText => 17,
305            Self::QualifiedName => 18,
306            _ => 100,
307        }
308    }
309}
310
311impl VariantTypeId<'_> {
312    /// Get the encoding mask.
313    pub fn encoding_mask(&self) -> u8 {
314        match self {
315            // Null / Empty
316            VariantTypeId::Empty => 0u8,
317            // Scalar types
318            VariantTypeId::Scalar(s) => s.encoding_mask(),
319            VariantTypeId::Array(s, dims) => {
320                let mask = s.encoding_mask() | EncodingMask::ARRAY_VALUES_BIT;
321                if dims.is_some() {
322                    mask | EncodingMask::ARRAY_DIMENSIONS_BIT
323                } else {
324                    mask
325                }
326            }
327        }
328    }
329
330    /// Get the precedence when converting between different variant types.
331    pub fn precedence(&self) -> u8 {
332        match self {
333            Self::Scalar(s) => s.precedence(),
334            Self::Array(s, _) => s.precedence(),
335            Self::Empty => 100,
336        }
337    }
338}
339
340pub(crate) struct EncodingMask;
341
342impl EncodingMask {
343    // These are values, not bits
344    pub(crate) const BOOLEAN: u8 = DataTypeId::Boolean as u8;
345    pub(crate) const SBYTE: u8 = DataTypeId::SByte as u8;
346    pub(crate) const BYTE: u8 = DataTypeId::Byte as u8;
347    pub(crate) const INT16: u8 = DataTypeId::Int16 as u8;
348    pub(crate) const UINT16: u8 = DataTypeId::UInt16 as u8;
349    pub(crate) const INT32: u8 = DataTypeId::Int32 as u8;
350    pub(crate) const UINT32: u8 = DataTypeId::UInt32 as u8;
351    pub(crate) const INT64: u8 = DataTypeId::Int64 as u8;
352    pub(crate) const UINT64: u8 = DataTypeId::UInt64 as u8;
353    pub(crate) const FLOAT: u8 = DataTypeId::Float as u8;
354    pub(crate) const DOUBLE: u8 = DataTypeId::Double as u8;
355    pub(crate) const STRING: u8 = DataTypeId::String as u8;
356    pub(crate) const DATE_TIME: u8 = DataTypeId::DateTime as u8;
357    pub(crate) const GUID: u8 = DataTypeId::Guid as u8;
358    pub(crate) const BYTE_STRING: u8 = DataTypeId::ByteString as u8;
359    pub(crate) const XML_ELEMENT: u8 = DataTypeId::XmlElement as u8;
360    pub(crate) const NODE_ID: u8 = DataTypeId::NodeId as u8;
361    pub(crate) const EXPANDED_NODE_ID: u8 = DataTypeId::ExpandedNodeId as u8;
362    pub(crate) const STATUS_CODE: u8 = DataTypeId::StatusCode as u8;
363    pub(crate) const QUALIFIED_NAME: u8 = DataTypeId::QualifiedName as u8;
364    pub(crate) const LOCALIZED_TEXT: u8 = DataTypeId::LocalizedText as u8;
365    pub(crate) const EXTENSION_OBJECT: u8 = 22; // DataTypeId::ExtensionObject as u8;
366    pub(crate) const DATA_VALUE: u8 = DataTypeId::DataValue as u8;
367    pub(crate) const VARIANT: u8 = 24;
368    pub(crate) const DIAGNOSTIC_INFO: u8 = DataTypeId::DiagnosticInfo as u8;
369    /// Bit indicates an array with dimensions
370    pub(crate) const ARRAY_DIMENSIONS_BIT: u8 = 1 << 6;
371    /// Bit indicates an array with values
372    pub(crate) const ARRAY_VALUES_BIT: u8 = 1 << 7;
373
374    pub(crate) const ARRAY_MASK: u8 =
375        EncodingMask::ARRAY_DIMENSIONS_BIT | EncodingMask::ARRAY_VALUES_BIT;
376}