Skip to main content

opcua_types/custom/
custom_struct.rs

1use std::{io::Write, sync::Arc};
2
3use crate::{
4    write_i32, write_u32, Array, BinaryDecodable, BinaryEncodable, ByteString, Context, DataValue,
5    DateTime, DiagnosticInfo, EncodingResult, Error, ExpandedMessageInfo, ExpandedNodeId,
6    ExtensionObject, Guid, LocalizedText, NodeId, QualifiedName, StatusCode, StructureType,
7    TypeLoader, UAString, UaNullable, Variant, XmlElement,
8};
9
10use super::type_tree::{DataTypeTree, ParsedStructureField, StructTypeInfo};
11
12#[derive(Debug, Clone)]
13/// A type representing an OPC-UA structure decoded dynamically.
14/// This can use runtime information to properly encode and decode
15/// binary information. In order to make the type encodable, it
16/// contains references to a [StructTypeInfo] describing the type it contains,
17/// as well as the [DataTypeTree] containing any types it references.
18///
19/// This type can be used to reason about data on a server without explicitly
20/// defining the server types in code, which is useful if you want to create
21/// a generic client that can work with data from any server.
22///
23/// Internally it is simply an array of [Variant].
24///
25/// Note that this type is intended to support all encoding/decoding types
26/// in the OPC-UA standard, which is more than what the encoding macros
27/// currently do. This includes structurs with optional fields, unions,
28/// and multi-dimensional arrays, none of which are used in the core types.
29pub struct DynamicStructure {
30    pub(super) type_def: Arc<StructTypeInfo>,
31    pub(super) discriminant: u32,
32    pub(super) type_tree: Arc<DataTypeTree>,
33    pub(super) data: Vec<Variant>,
34}
35
36impl PartialEq for DynamicStructure {
37    fn eq(&self, other: &Self) -> bool {
38        self.type_def.node_id == other.type_def.node_id
39            && self.discriminant == other.discriminant
40            && self.data == other.data
41    }
42}
43
44impl ExpandedMessageInfo for DynamicStructure {
45    fn full_json_type_id(&self) -> ExpandedNodeId {
46        ExpandedNodeId::new(self.type_def.encoding_ids.json_id.clone())
47    }
48
49    fn full_type_id(&self) -> ExpandedNodeId {
50        ExpandedNodeId::new(self.type_def.encoding_ids.binary_id.clone())
51    }
52
53    fn full_xml_type_id(&self) -> ExpandedNodeId {
54        ExpandedNodeId::new(self.type_def.encoding_ids.xml_id.clone())
55    }
56
57    fn full_data_type_id(&self) -> ExpandedNodeId {
58        ExpandedNodeId::new(self.type_def.node_id.clone())
59    }
60}
61
62impl DynamicStructure {
63    /// Create a new struct, validating that it matches the provided type definition.
64    pub fn new_struct(
65        type_def: Arc<StructTypeInfo>,
66        type_tree: Arc<DataTypeTree>,
67        data: Vec<Variant>,
68    ) -> Result<Self, Error> {
69        if data.len() != type_def.fields.len() {
70            return Err(Error::new(
71                StatusCode::BadInvalidArgument,
72                format!(
73                    "Invalid number of fields, got {}, expected {}",
74                    data.len(),
75                    type_def.fields.len()
76                ),
77            ));
78        }
79        if matches!(type_def.structure_type, StructureType::Union) {
80            return Err(Error::new(
81                StatusCode::BadInvalidArgument,
82                "Cannot construct a union using new_struct, call new_union instead",
83            ));
84        }
85
86        for (value, field) in data.iter().zip(type_def.fields.iter()) {
87            field.validate(value)?;
88        }
89        Ok(Self {
90            type_def,
91            discriminant: 0,
92            type_tree,
93            data,
94        })
95    }
96
97    /// Create a new union, validating that it matches the provided type definition.
98    pub fn new_union(
99        type_def: Arc<StructTypeInfo>,
100        type_tree: Arc<DataTypeTree>,
101        data: Variant,
102        discriminant: u32,
103    ) -> Result<Self, Error> {
104        if discriminant == 0 {
105            return Err(Error::new(
106                StatusCode::BadInvalidArgument,
107                "Discriminant must be non-zero.",
108            ));
109        }
110
111        if !matches!(type_def.structure_type, StructureType::Union) {
112            return Err(Error::new(
113                StatusCode::BadInvalidArgument,
114                "Cannot construct a struct using new_union, call new_struct instead",
115            ));
116        }
117        let Some(field) = type_def.fields.get(discriminant as usize - 1) else {
118            return Err(Error::new(
119                StatusCode::BadInvalidArgument,
120                format!("Invalid discriminant {discriminant}"),
121            ));
122        };
123        field.validate(&data)?;
124        Ok(Self {
125            type_def,
126            discriminant,
127            type_tree,
128            data: vec![data],
129        })
130    }
131
132    /// Create a new union, with value null.
133    pub fn new_null_union(type_def: Arc<StructTypeInfo>, type_tree: Arc<DataTypeTree>) -> Self {
134        Self {
135            type_def,
136            type_tree,
137            discriminant: 0,
138            data: Vec::new(),
139        }
140    }
141
142    /// Get a reference to the fields in order.
143    pub fn values(&self) -> &[Variant] {
144        &self.data
145    }
146
147    /// Get a reference to the field at `index`
148    pub fn get_field(&self, index: usize) -> Option<&Variant> {
149        self.data.get(index)
150    }
151
152    /// Get a reference to the field with the given name.
153    pub fn get_field_by_name(&self, name: &str) -> Option<&Variant> {
154        self.type_def
155            .index_by_name
156            .get(name)
157            .and_then(|v| self.data.get(*v))
158    }
159
160    fn field_variant_len(
161        &self,
162        f: &Variant,
163        field: &ParsedStructureField,
164        ctx: &Context<'_>,
165    ) -> usize {
166        match f {
167            Variant::ExtensionObject(o) => {
168                let Some(field_ty) = self.type_tree.get_struct_type(&field.type_id) else {
169                    // The field is missing, we'll fail later, probably, but for now just assume that we're encoding
170                    // an extension object.
171                    return o.byte_len(ctx);
172                };
173
174                // If the field is abstract, encode it as an extension object
175                if field_ty.is_abstract {
176                    return o.byte_len(ctx);
177                }
178                match &o.body {
179                    Some(b) => b.byte_len_dyn(ctx),
180                    None => 0,
181                }
182            }
183            r => r.value_byte_len(ctx),
184        }
185    }
186
187    fn encode_field<S: Write + ?Sized>(
188        &self,
189        mut stream: &mut S,
190        f: &Variant,
191        field: &ParsedStructureField,
192        ctx: &Context<'_>,
193    ) -> EncodingResult<()> {
194        match f {
195            Variant::ExtensionObject(o) => {
196                let Some(field_ty) = self.type_tree.get_struct_type(&field.type_id) else {
197                    return Err(Error::encoding(format!(
198                        "Dynamic type field missing from type tree: {}",
199                        field.type_id
200                    )));
201                };
202
203                if field_ty.is_abstract {
204                    o.encode(stream, ctx)
205                } else {
206                    let Some(body) = &o.body else {
207                        return Err(Error::encoding(
208                            "Dynamic type field is missing extension object body",
209                        ));
210                    };
211                    body.encode_binary(&mut stream, ctx)
212                }
213            }
214            Variant::Array(a) => {
215                if field.value_rank > 1 {
216                    let Some(dims) = &a.dimensions else {
217                        return Err(Error::encoding(
218                            "ArrayDimensions are required for fields with value rank > 1",
219                        ));
220                    };
221                    // Note array dimensions are encoded as Int32 even though they are presented
222                    // as UInt32 through attribute.
223
224                    // Encode dimensions
225                    write_i32(stream, dims.len() as i32)?;
226                    for dimension in dims {
227                        write_i32(stream, *dimension as i32)?;
228                    }
229                } else {
230                    write_i32(stream, a.values.len() as i32)?;
231                }
232
233                for value in a.values.iter() {
234                    self.encode_field(stream, value, field, ctx)?;
235                }
236                Ok(())
237            }
238            Variant::Empty => Err(Error::encoding("Empty variant value in structure")),
239            r => r.encode_value(stream, ctx),
240        }
241    }
242}
243
244impl UaNullable for DynamicStructure {
245    fn is_ua_null(&self) -> bool {
246        if self.type_def.structure_type == StructureType::Union {
247            self.discriminant == 0
248        } else {
249            false
250        }
251    }
252}
253
254impl BinaryEncodable for DynamicStructure {
255    fn byte_len(&self, ctx: &crate::Context<'_>) -> usize {
256        // Byte length is the sum of the individual structure fields
257        let mut size = 0;
258        let s = &self.type_def;
259
260        match s.structure_type {
261            StructureType::Structure => {
262                for (value, field) in self.data.iter().zip(s.fields.iter()) {
263                    size += self.field_variant_len(value, field, ctx);
264                }
265            }
266            StructureType::StructureWithOptionalFields => {
267                // encoding mask.
268                size += 4;
269                for (value, field) in self.data.iter().zip(s.fields.iter()) {
270                    if !field.is_optional || !matches!(value, Variant::Empty) {
271                        size += self.field_variant_len(value, field, ctx);
272                    }
273                }
274            }
275            StructureType::Union => {
276                // discriminant
277                size += 4;
278                if self.discriminant != 0 {
279                    let (Some(value), Some(field)) = (
280                        self.data.first(),
281                        s.fields.get(self.discriminant as usize - 1),
282                    ) else {
283                        return 0;
284                    };
285                    size += self.field_variant_len(value, field, ctx);
286                }
287            }
288            // Unsupported; encode returns an error for these before any
289            // bytes are written, so the length is never used.
290            StructureType::StructureWithSubtypedValues | StructureType::UnionWithSubtypedValues => {
291                return 0
292            }
293        }
294
295        size
296    }
297
298    fn encode<S: std::io::Write + ?Sized>(
299        &self,
300        stream: &mut S,
301        ctx: &crate::Context<'_>,
302    ) -> crate::EncodingResult<()> {
303        let s = &self.type_def;
304        match s.structure_type {
305            StructureType::Structure => {
306                // Invariant used here: The data list must contain the correct fields with the correct values.
307                for (value, field) in self.data.iter().zip(s.fields.iter()) {
308                    self.encode_field(stream, value, field, ctx)?;
309                }
310            }
311            StructureType::StructureWithOptionalFields => {
312                let mut encoding_mask = 0u32;
313                let mut optional_idx = 0;
314                for (value, field) in self.data.iter().zip(s.fields.iter()) {
315                    if field.is_optional {
316                        if !matches!(value, Variant::Empty) {
317                            encoding_mask |= 1 << optional_idx;
318                        }
319                        optional_idx += 1;
320                    }
321                }
322                write_u32(stream, encoding_mask)?;
323                for (value, field) in self.data.iter().zip(s.fields.iter()) {
324                    if !field.is_optional || !matches!(value, Variant::Empty) {
325                        self.encode_field(stream, value, field, ctx)?;
326                    }
327                }
328            }
329            StructureType::Union => {
330                write_u32(stream, self.discriminant)?;
331                if self.discriminant != 0 {
332                    let (Some(value), Some(field)) = (
333                        self.data.first(),
334                        s.fields.get(self.discriminant as usize - 1),
335                    ) else {
336                        return Err(Error::encoding(
337                            "Discriminant was out of range of known fields",
338                        ));
339                    };
340
341                    self.encode_field(stream, value, field, ctx)?;
342                }
343            }
344            StructureType::StructureWithSubtypedValues | StructureType::UnionWithSubtypedValues => {
345                return Err(Error::encoding(
346                    "Structures and unions with subtyped values are unsupported",
347                ));
348            }
349        }
350
351        Ok(())
352    }
353}
354
355/// Type loader that can load types dynamically using data type definitions loaded at
356/// runtime.
357pub struct DynamicTypeLoader {
358    pub(super) type_tree: Arc<DataTypeTree>,
359}
360
361impl DynamicTypeLoader {
362    /// Create a new type loader that loads types from the given type tree.
363    pub fn new(type_tree: Arc<DataTypeTree>) -> Self {
364        Self { type_tree }
365    }
366
367    fn decode_field_value(
368        &self,
369        field: &ParsedStructureField,
370        stream: &mut dyn std::io::Read,
371        ctx: &Context<'_>,
372    ) -> EncodingResult<Variant> {
373        match field.scalar_type {
374            crate::VariantScalarTypeId::Boolean => Ok(Variant::from(
375                <bool as BinaryDecodable>::decode(stream, ctx)?,
376            )),
377            crate::VariantScalarTypeId::SByte => {
378                Ok(Variant::from(<i8 as BinaryDecodable>::decode(stream, ctx)?))
379            }
380            crate::VariantScalarTypeId::Byte => {
381                Ok(Variant::from(<u8 as BinaryDecodable>::decode(stream, ctx)?))
382            }
383            crate::VariantScalarTypeId::Int16 => Ok(Variant::from(
384                <i16 as BinaryDecodable>::decode(stream, ctx)?,
385            )),
386            crate::VariantScalarTypeId::UInt16 => Ok(Variant::from(
387                <u16 as BinaryDecodable>::decode(stream, ctx)?,
388            )),
389            crate::VariantScalarTypeId::Int32 => Ok(Variant::from(
390                <i32 as BinaryDecodable>::decode(stream, ctx)?,
391            )),
392            crate::VariantScalarTypeId::UInt32 => Ok(Variant::from(
393                <u32 as BinaryDecodable>::decode(stream, ctx)?,
394            )),
395            crate::VariantScalarTypeId::Int64 => Ok(Variant::from(
396                <i64 as BinaryDecodable>::decode(stream, ctx)?,
397            )),
398            crate::VariantScalarTypeId::UInt64 => Ok(Variant::from(
399                <u64 as BinaryDecodable>::decode(stream, ctx)?,
400            )),
401            crate::VariantScalarTypeId::Float => Ok(Variant::from(
402                <f32 as BinaryDecodable>::decode(stream, ctx)?,
403            )),
404            crate::VariantScalarTypeId::Double => Ok(Variant::from(
405                <f64 as BinaryDecodable>::decode(stream, ctx)?,
406            )),
407            crate::VariantScalarTypeId::String => Ok(Variant::from(
408                <UAString as BinaryDecodable>::decode(stream, ctx)?,
409            )),
410            crate::VariantScalarTypeId::DateTime => Ok(Variant::from(
411                <DateTime as BinaryDecodable>::decode(stream, ctx)?,
412            )),
413            crate::VariantScalarTypeId::Guid => Ok(Variant::from(
414                <Guid as BinaryDecodable>::decode(stream, ctx)?,
415            )),
416            crate::VariantScalarTypeId::ByteString => Ok(Variant::from(
417                <ByteString as BinaryDecodable>::decode(stream, ctx)?,
418            )),
419            crate::VariantScalarTypeId::XmlElement => Ok(Variant::from(
420                <XmlElement as BinaryDecodable>::decode(stream, ctx)?,
421            )),
422            crate::VariantScalarTypeId::NodeId => Ok(Variant::from(
423                <NodeId as BinaryDecodable>::decode(stream, ctx)?,
424            )),
425            crate::VariantScalarTypeId::ExpandedNodeId => Ok(Variant::from(
426                <ExpandedNodeId as BinaryDecodable>::decode(stream, ctx)?,
427            )),
428            crate::VariantScalarTypeId::StatusCode => Ok(Variant::from(
429                <StatusCode as BinaryDecodable>::decode(stream, ctx)?,
430            )),
431            crate::VariantScalarTypeId::QualifiedName => Ok(Variant::from(
432                <QualifiedName as BinaryDecodable>::decode(stream, ctx)?,
433            )),
434            crate::VariantScalarTypeId::LocalizedText => Ok(Variant::from(
435                <LocalizedText as BinaryDecodable>::decode(stream, ctx)?,
436            )),
437            crate::VariantScalarTypeId::ExtensionObject => {
438                let Some(field_ty) = self.type_tree.get_struct_type(&field.type_id) else {
439                    return Err(Error::decoding(format!(
440                        "Dynamic type field missing from type tree: {}",
441                        field.type_id
442                    )));
443                };
444
445                // If the field is abstract, it's encoded as an extension object,
446                // or so we assume.
447                if field_ty.is_abstract {
448                    Ok(Variant::from(<ExtensionObject as BinaryDecodable>::decode(
449                        stream, ctx,
450                    )?))
451                } else {
452                    // Else, load the extension object body directly.
453                    Ok(Variant::from(ctx.load_from_binary(
454                        &field_ty.node_id,
455                        stream,
456                        None,
457                    )?))
458                }
459            }
460            crate::VariantScalarTypeId::DataValue => Ok(Variant::from(
461                <DataValue as BinaryDecodable>::decode(stream, ctx)?,
462            )),
463            crate::VariantScalarTypeId::Variant => Ok(Variant::Variant(Box::new(
464                <Variant as BinaryDecodable>::decode(stream, ctx)?,
465            ))),
466            crate::VariantScalarTypeId::DiagnosticInfo => Ok(Variant::from(
467                <DiagnosticInfo as BinaryDecodable>::decode(stream, ctx)?,
468            )),
469        }
470    }
471
472    fn decode_field(
473        &self,
474        field: &ParsedStructureField,
475        stream: &mut dyn std::io::Read,
476        ctx: &Context<'_>,
477    ) -> EncodingResult<Variant> {
478        if field.value_rank > 0 {
479            let (len, array_dims) = if field.value_rank > 1 {
480                let Some(array_dims) = <Option<Vec<i32>> as BinaryDecodable>::decode(stream, ctx)?
481                else {
482                    return Err(Error::decoding("Array has invalid ArrayDimensions"));
483                };
484                if array_dims.len() != field.value_rank as usize {
485                    return Err(Error::decoding(
486                        "Array has incorrect ArrayDimensions, must match value rank",
487                    ));
488                }
489                let mut len = 1;
490                let mut final_dims = Vec::with_capacity(array_dims.len());
491                for dim in &array_dims {
492                    if *dim <= 0 {
493                        return Err(Error::decoding("Array has incorrect ArrayDimensions, all dimensions must be greater than zero"));
494                    }
495                    len *= *dim as u32;
496                    final_dims.push(*dim as u32);
497                }
498                (len as usize, Some(final_dims))
499            } else {
500                let len = <u32 as BinaryDecodable>::decode(stream, ctx)?;
501                (len as usize, None)
502            };
503
504            if len > ctx.options().max_array_length {
505                return Err(Error::decoding(format!(
506                    "Array length {} exceeds decoding limit {}",
507                    len,
508                    ctx.options().max_array_length
509                )));
510            }
511
512            let mut res = Vec::with_capacity(len);
513            for _ in 0..len {
514                res.push(self.decode_field_value(field, stream, ctx)?);
515            }
516            if let Some(dims) = array_dims {
517                Ok(Variant::Array(Box::new(
518                    Array::new_multi(field.scalar_type, res, dims).map_err(Error::decoding)?,
519                )))
520            } else {
521                Ok(Variant::Array(Box::new(
522                    Array::new(field.scalar_type, res).map_err(Error::decoding)?,
523                )))
524            }
525        } else {
526            self.decode_field_value(field, stream, ctx)
527        }
528    }
529
530    fn decode_type_inner(
531        &self,
532        stream: &mut dyn std::io::Read,
533        ctx: &Context<'_>,
534        t: &Arc<StructTypeInfo>,
535    ) -> crate::EncodingResult<Box<dyn crate::DynEncodable>> {
536        match t.structure_type {
537            StructureType::Structure => {
538                let mut values = Vec::with_capacity(t.fields.len());
539                for field in &t.fields {
540                    values.push(self.decode_field(field, stream, ctx)?);
541                }
542                Ok(Box::new(DynamicStructure {
543                    type_def: t.clone(),
544                    discriminant: 0,
545                    type_tree: self.type_tree.clone(),
546                    data: values,
547                }))
548            }
549            StructureType::StructureWithOptionalFields => {
550                let mask = <u32 as BinaryDecodable>::decode(stream, ctx)?;
551                let mut values = Vec::with_capacity(t.fields.len());
552                let mut optional_idx = 0;
553                for field in t.fields.iter() {
554                    if field.is_optional {
555                        if (1 << optional_idx) & mask != 0 {
556                            values.push(self.decode_field(field, stream, ctx)?);
557                        } else {
558                            values.push(Variant::Empty);
559                        }
560                        optional_idx += 1;
561                    } else {
562                        values.push(self.decode_field(field, stream, ctx)?);
563                    }
564                }
565                Ok(Box::new(DynamicStructure {
566                    type_def: t.clone(),
567                    discriminant: 0,
568                    type_tree: self.type_tree.clone(),
569                    data: values,
570                }))
571            }
572            StructureType::Union => {
573                let discriminant = <u32 as BinaryDecodable>::decode(stream, ctx)?;
574                if discriminant == 0 {
575                    return Ok(Box::new(DynamicStructure::new_null_union(
576                        t.clone(),
577                        self.type_tree.clone(),
578                    )));
579                }
580                let Some(field) = t.fields.get(discriminant as usize - 1) else {
581                    return Err(Error::decoding(format!(
582                        "Invalid discriminant: {discriminant}"
583                    )));
584                };
585                let values = vec![self.decode_field(field, stream, ctx)?];
586                Ok(Box::new(DynamicStructure {
587                    type_def: t.clone(),
588                    discriminant,
589                    type_tree: self.type_tree.clone(),
590                    data: values,
591                }))
592            }
593            StructureType::StructureWithSubtypedValues | StructureType::UnionWithSubtypedValues => {
594                Err(Error::decoding(
595                    "Structures and unions with subtyped values are unsupported",
596                ))
597            }
598        }
599    }
600}
601
602impl TypeLoader for DynamicTypeLoader {
603    fn load_from_binary(
604        &self,
605        node_id: &NodeId,
606        stream: &mut dyn std::io::Read,
607        ctx: &Context<'_>,
608        _length: Option<usize>,
609    ) -> Option<crate::EncodingResult<Box<dyn crate::DynEncodable>>> {
610        let ty_node_id = if let Some(mapped) = self.type_tree.encoding_to_data_type().get(node_id) {
611            mapped
612        } else {
613            node_id
614        };
615        let t = self.type_tree.get_struct_type(ty_node_id)?;
616
617        if !t.is_supported() {
618            return None;
619        }
620
621        Some(self.decode_type_inner(stream, ctx, t))
622    }
623
624    fn priority(&self) -> crate::TypeLoaderPriority {
625        crate::TypeLoaderPriority::Dynamic(50)
626    }
627
628    #[cfg(feature = "xml")]
629    fn load_from_xml(
630        &self,
631        node_id: &crate::NodeId,
632        stream: &mut crate::xml::XmlStreamReader<&mut dyn std::io::Read>,
633        ctx: &Context<'_>,
634        _name: &str,
635    ) -> Option<crate::EncodingResult<Box<dyn crate::DynEncodable>>> {
636        let ty_node_id = if let Some(mapped) = self.type_tree.encoding_to_data_type().get(node_id) {
637            mapped
638        } else {
639            node_id
640        };
641        let t = self.type_tree.get_struct_type(ty_node_id)?;
642
643        if !t.is_supported() {
644            return None;
645        }
646
647        Some(self.xml_decode_type_inner(stream, ctx, t))
648    }
649
650    #[cfg(feature = "json")]
651    fn load_from_json(
652        &self,
653        node_id: &crate::NodeId,
654        stream: &mut crate::json::JsonStreamReader<&mut dyn std::io::Read>,
655        ctx: &Context<'_>,
656    ) -> Option<crate::EncodingResult<Box<dyn crate::DynEncodable>>> {
657        let ty_node_id = if let Some(mapped) = self.type_tree.encoding_to_data_type().get(node_id) {
658            mapped
659        } else {
660            node_id
661        };
662        let t = self.type_tree.get_struct_type(ty_node_id)?;
663
664        if !t.is_supported() {
665            return None;
666        }
667
668        Some(self.json_decode_type_inner(stream, ctx, t))
669    }
670}
671
672#[cfg(test)]
673pub(crate) mod tests {
674    use std::{
675        io::{Cursor, Seek},
676        sync::{Arc, LazyLock},
677    };
678
679    use opcua_macros::ua_encodable;
680
681    use crate::{
682        binary_decode_to_enc, json_decode_to_enc, xml_decode_to_enc, Array, BinaryDecodable,
683        BinaryEncodable, ContextOwned, DataTypeDefinition, DataTypeId, DecodingOptions,
684        EUInformation, ExpandedMessageInfo, ExtensionObject, LocalizedText, NamespaceMap, NodeId,
685        ObjectId, StaticTypeLoader, StructureDefinition, StructureField, TypeLoaderCollection,
686        TypeLoaderInstance, UAString, Variant, VariantScalarTypeId,
687    };
688
689    use crate::custom::type_tree::{
690        DataTypeTree, EncodingIds, GenericTypeInfo, ParentIds, TypeInfo,
691    };
692
693    use super::{DynamicStructure, DynamicTypeLoader};
694
695    pub(crate) fn make_type_tree() -> DataTypeTree {
696        // Add a few builtins we need.
697        let mut type_tree = DataTypeTree::new(ParentIds::new());
698        type_tree.add_type(DataTypeId::Int32.into(), GenericTypeInfo::new(false));
699        type_tree.add_type(DataTypeId::Boolean.into(), GenericTypeInfo::new(false));
700        type_tree.add_type(
701            DataTypeId::LocalizedText.into(),
702            GenericTypeInfo::new(false),
703        );
704        type_tree.add_type(DataTypeId::String.into(), GenericTypeInfo::new(false));
705        type_tree
706    }
707
708    pub(crate) fn add_eu_information(type_tree: &mut DataTypeTree) {
709        type_tree.parent_ids_mut().add_type(
710            DataTypeId::EUInformation.into(),
711            DataTypeId::Structure.into(),
712        );
713        type_tree.add_type(
714            DataTypeId::EUInformation.into(),
715            TypeInfo::from_type_definition(
716                DataTypeDefinition::Structure(StructureDefinition {
717                    default_encoding_id: NodeId::null(),
718                    base_data_type: DataTypeId::Structure.into(),
719                    structure_type: crate::StructureType::Structure,
720                    fields: Some(vec![
721                        StructureField {
722                            name: "NamespaceUri".into(),
723                            data_type: DataTypeId::String.into(),
724                            value_rank: -1,
725                            ..Default::default()
726                        },
727                        StructureField {
728                            name: "UnitId".into(),
729                            data_type: DataTypeId::Int32.into(),
730                            value_rank: -1,
731                            ..Default::default()
732                        },
733                        StructureField {
734                            name: "DisplayName".into(),
735                            data_type: DataTypeId::LocalizedText.into(),
736                            value_rank: -1,
737                            ..Default::default()
738                        },
739                        StructureField {
740                            name: "Description".into(),
741                            data_type: DataTypeId::LocalizedText.into(),
742                            value_rank: -1,
743                            ..Default::default()
744                        },
745                    ]),
746                }),
747                "EUInformation".to_owned(),
748                Some(EncodingIds {
749                    binary_id: ObjectId::EUInformation_Encoding_DefaultBinary.into(),
750                    json_id: ObjectId::EUInformation_Encoding_DefaultJson.into(),
751                    xml_id: ObjectId::EUInformation_Encoding_DefaultXml.into(),
752                }),
753                false,
754                &DataTypeId::EUInformation.into(),
755                type_tree.parent_ids(),
756            )
757            .unwrap(),
758        );
759    }
760
761    #[test]
762    fn dynamic_struct_round_trip() {
763        let mut type_tree = make_type_tree();
764        add_eu_information(&mut type_tree);
765        // Add a structure for EUInformation
766
767        let loader = DynamicTypeLoader::new(Arc::new(type_tree));
768        let mut loaders = TypeLoaderCollection::new_empty();
769        loaders.add_type_loader(loader);
770        let ctx = ContextOwned::new(NamespaceMap::new(), loaders, DecodingOptions::test());
771
772        let mut write_buf = Vec::<u8>::new();
773        let mut cursor = Cursor::new(&mut write_buf);
774
775        let obj = ExtensionObject::from_message(EUInformation {
776            namespace_uri: "my.namespace.uri".into(),
777            unit_id: 5,
778            display_name: "Degrees Celsius".into(),
779            description: "Description".into(),
780        });
781
782        // Encode the object, will use the regular encode implementation for EUInformation.
783        BinaryEncodable::encode(&obj, &mut cursor, &ctx.context()).unwrap();
784        cursor.seek(std::io::SeekFrom::Start(0)).unwrap();
785
786        let obj2: ExtensionObject = BinaryDecodable::decode(&mut cursor, &ctx.context()).unwrap();
787
788        // Decode it back, resulting in a dynamic structure.
789        let value = obj2.inner_as::<DynamicStructure>().unwrap();
790        assert_eq!(value.data.len(), 4);
791        assert_eq!(value.data[0], Variant::from("my.namespace.uri"));
792        assert_eq!(value.data[1], Variant::from(5i32));
793        assert_eq!(
794            value.data[2],
795            Variant::from(LocalizedText::from("Degrees Celsius"))
796        );
797        assert_eq!(
798            value.data[3],
799            Variant::from(LocalizedText::from("Description"))
800        );
801
802        // Re-encode it
803        cursor.seek(std::io::SeekFrom::Start(0)).unwrap();
804        BinaryEncodable::encode(&obj2, &mut cursor, &ctx.context()).unwrap();
805
806        // Make a new context, this time with the regular decoder for EUInformation
807        let ctx = ContextOwned::new_default(NamespaceMap::new(), DecodingOptions::test());
808        cursor.seek(std::io::SeekFrom::Start(0)).unwrap();
809        let obj3: ExtensionObject = BinaryDecodable::decode(&mut cursor, &ctx.context()).unwrap();
810
811        assert_eq!(obj, obj3);
812    }
813
814    #[test]
815    fn dynamic_nested_struct_round_trip() {
816        let mut type_tree = make_type_tree();
817        add_eu_information(&mut type_tree);
818        let type_node_id = NodeId::new(1, 5);
819        type_tree
820            .parent_ids_mut()
821            .add_type(type_node_id.clone(), DataTypeId::Structure.into());
822        type_tree.add_type(
823            type_node_id.clone(),
824            TypeInfo::from_type_definition(
825                DataTypeDefinition::Structure(StructureDefinition {
826                    default_encoding_id: NodeId::null(),
827                    base_data_type: DataTypeId::Structure.into(),
828                    structure_type: crate::StructureType::Structure,
829                    fields: Some(vec![
830                        StructureField {
831                            name: "Info".into(),
832                            data_type: DataTypeId::EUInformation.into(),
833                            value_rank: -1,
834                            ..Default::default()
835                        },
836                        StructureField {
837                            name: "InfoArray".into(),
838                            data_type: DataTypeId::EUInformation.into(),
839                            value_rank: 1,
840                            ..Default::default()
841                        },
842                        StructureField {
843                            name: "AbstractField".into(),
844                            data_type: DataTypeId::BaseDataType.into(),
845                            value_rank: -1,
846                            ..Default::default()
847                        },
848                        StructureField {
849                            name: "PrimitiveArray".into(),
850                            data_type: DataTypeId::Int32.into(),
851                            value_rank: 2,
852                            ..Default::default()
853                        },
854                    ]),
855                }),
856                "MyType".to_owned(),
857                Some(EncodingIds {
858                    binary_id: NodeId::new(1, 6),
859                    json_id: NodeId::new(1, 7),
860                    xml_id: NodeId::new(1, 8),
861                }),
862                false,
863                &type_node_id,
864                type_tree.parent_ids(),
865            )
866            .unwrap(),
867        );
868        let type_tree = Arc::new(type_tree);
869        let loader = DynamicTypeLoader::new(type_tree.clone());
870        let mut loaders = TypeLoaderCollection::new();
871        loaders.add_type_loader(loader);
872        let ctx = ContextOwned::new(NamespaceMap::new(), loaders, DecodingOptions::test());
873
874        let obj = DynamicStructure::new_struct(
875            type_tree.get_struct_type(&type_node_id).unwrap().clone(),
876            type_tree,
877            vec![
878                Variant::from(ExtensionObject::from_message(EUInformation {
879                    namespace_uri: "my.namespace.uri".into(),
880                    unit_id: 5,
881                    display_name: "Degrees Celsius".into(),
882                    description: "Description".into(),
883                })),
884                Variant::from(vec![
885                    ExtensionObject::from_message(EUInformation {
886                        namespace_uri: "my.namespace.uri".into(),
887                        unit_id: 5,
888                        display_name: "Degrees Celsius".into(),
889                        description: "Description".into(),
890                    }),
891                    ExtensionObject::from_message(EUInformation {
892                        namespace_uri: "my.namespace.uri.2".into(),
893                        unit_id: 6,
894                        display_name: "Degrees Celsius 2".into(),
895                        description: "Description 2".into(),
896                    }),
897                ]),
898                Variant::Variant(Box::new(Variant::from(123))),
899                Variant::from(
900                    Array::new_multi(
901                        VariantScalarTypeId::Int32,
902                        [1i32, 2, 3, 4, 5, 6]
903                            .into_iter()
904                            .map(Variant::from)
905                            .collect::<Vec<_>>(),
906                        vec![2, 3],
907                    )
908                    .unwrap(),
909                ),
910            ],
911        )
912        .unwrap();
913        let obj = ExtensionObject::from_message(obj);
914
915        let mut write_buf = Vec::<u8>::new();
916        let mut cursor = Cursor::new(&mut write_buf);
917
918        BinaryEncodable::encode(&obj, &mut cursor, &ctx.context()).unwrap();
919        cursor.seek(std::io::SeekFrom::Start(0)).unwrap();
920        let obj2: ExtensionObject = BinaryDecodable::decode(&mut cursor, &ctx.context()).unwrap();
921
922        assert_eq!(obj, obj2);
923    }
924
925    pub(crate) fn get_namespaces() -> NamespaceMap {
926        let mut namespaces = NamespaceMap::new();
927        namespaces.add_namespace(TYPE_NAMESPACE);
928        namespaces
929    }
930
931    pub(crate) fn get_custom_union() -> ContextOwned {
932        let mut type_tree = make_type_tree();
933        type_tree
934            .parent_ids_mut()
935            .add_type(NodeId::new(1, 1), DataTypeId::Structure.into());
936        type_tree.add_type(
937            NodeId::new(1, 1),
938            TypeInfo::from_type_definition(
939                DataTypeDefinition::Structure(StructureDefinition {
940                    default_encoding_id: NodeId::null(),
941                    base_data_type: DataTypeId::Structure.into(),
942                    structure_type: crate::StructureType::Union,
943                    fields: Some(vec![
944                        StructureField {
945                            name: "Integer".into(),
946                            data_type: DataTypeId::Int32.into(),
947                            value_rank: -1,
948                            ..Default::default()
949                        },
950                        StructureField {
951                            name: "StringVariant".into(),
952                            data_type: DataTypeId::String.into(),
953                            value_rank: -1,
954                            ..Default::default()
955                        },
956                    ]),
957                }),
958                "MyUnion".to_owned(),
959                Some(EncodingIds {
960                    binary_id: NodeId::new(1, 2),
961                    json_id: NodeId::new(1, 3),
962                    xml_id: NodeId::new(1, 4),
963                }),
964                false,
965                &NodeId::new(1, 1),
966                type_tree.parent_ids(),
967            )
968            .unwrap(),
969        );
970
971        let loader = DynamicTypeLoader::new(Arc::new(type_tree));
972        let mut loaders = TypeLoaderCollection::new_empty();
973        loaders.add_type_loader(loader);
974
975        ContextOwned::new(get_namespaces(), loaders, DecodingOptions::test())
976    }
977
978    mod opcua {
979        pub(super) use crate as types;
980    }
981
982    const TYPE_NAMESPACE: &str = "my.custom.namespace.uri";
983
984    #[derive(Debug, Clone, PartialEq)]
985    #[ua_encodable]
986    pub(crate) enum MyUnion {
987        Null,
988        Integer(i32),
989        StringVariant(UAString),
990    }
991
992    impl ExpandedMessageInfo for MyUnion {
993        fn full_type_id(&self) -> crate::ExpandedNodeId {
994            crate::ExpandedNodeId::new_with_namespace(TYPE_NAMESPACE, 2)
995        }
996
997        fn full_json_type_id(&self) -> crate::ExpandedNodeId {
998            crate::ExpandedNodeId::new_with_namespace(TYPE_NAMESPACE, 3)
999        }
1000
1001        fn full_xml_type_id(&self) -> crate::ExpandedNodeId {
1002            crate::ExpandedNodeId::new_with_namespace(TYPE_NAMESPACE, 4)
1003        }
1004
1005        fn full_data_type_id(&self) -> crate::ExpandedNodeId {
1006            crate::ExpandedNodeId::new_with_namespace(TYPE_NAMESPACE, 1)
1007        }
1008    }
1009
1010    static TYPES: LazyLock<TypeLoaderInstance> = LazyLock::new(|| {
1011        let mut inst = opcua::types::TypeLoaderInstance::new();
1012        inst.add_binary_type(1, 2, binary_decode_to_enc::<MyUnion>);
1013        inst.add_json_type(1, 3, json_decode_to_enc::<MyUnion>);
1014        inst.add_xml_type(1, 4, xml_decode_to_enc::<MyUnion>);
1015        inst
1016    });
1017
1018    pub(crate) struct MyUnionTypeLoader;
1019
1020    impl StaticTypeLoader for MyUnionTypeLoader {
1021        fn instance() -> &'static TypeLoaderInstance {
1022            &TYPES
1023        }
1024
1025        fn namespace() -> &'static str {
1026            TYPE_NAMESPACE
1027        }
1028    }
1029
1030    #[test]
1031    fn union_round_trip() {
1032        let ctx = get_custom_union();
1033
1034        let mut write_buf = Vec::<u8>::new();
1035        let mut cursor = Cursor::new(&mut write_buf);
1036
1037        let obj = ExtensionObject::from_message(MyUnion::Integer(123));
1038
1039        // Encode the object, using the regular BinaryEncodable implementation
1040        BinaryEncodable::encode(&obj, &mut cursor, &ctx.context()).unwrap();
1041        cursor.seek(std::io::SeekFrom::Start(0)).unwrap();
1042
1043        let obj2: ExtensionObject = BinaryDecodable::decode(&mut cursor, &ctx.context()).unwrap();
1044
1045        // Decode it back, resulting in a dynamic structure.
1046        let value = obj2.inner_as::<DynamicStructure>().unwrap();
1047        assert_eq!(value.data.len(), 1);
1048
1049        assert_eq!(value.data[0], Variant::from(123i32));
1050        assert_eq!(value.discriminant, 1);
1051
1052        cursor.seek(std::io::SeekFrom::Start(0)).unwrap();
1053        BinaryEncodable::encode(&obj2, &mut cursor, &ctx.context()).unwrap();
1054
1055        // Make a new context, this time with the regular decoder for MyUnion
1056        let mut ctx = ContextOwned::new_default(get_namespaces(), DecodingOptions::test());
1057        ctx.loaders_mut().add_type_loader(MyUnionTypeLoader);
1058        cursor.seek(std::io::SeekFrom::Start(0)).unwrap();
1059        let obj3: ExtensionObject = BinaryDecodable::decode(&mut cursor, &ctx.context()).unwrap();
1060
1061        assert_eq!(obj, obj3);
1062    }
1063
1064    #[test]
1065    fn union_null() {
1066        let ctx = get_custom_union();
1067
1068        let mut write_buf = Vec::<u8>::new();
1069        let mut cursor = Cursor::new(&mut write_buf);
1070
1071        let obj = ExtensionObject::from_message(MyUnion::Null);
1072
1073        // Encode the object, using the regular BinaryEncodable implementation
1074        BinaryEncodable::encode(&obj, &mut cursor, &ctx.context()).unwrap();
1075        cursor.seek(std::io::SeekFrom::Start(0)).unwrap();
1076
1077        let obj2: ExtensionObject = BinaryDecodable::decode(&mut cursor, &ctx.context()).unwrap();
1078
1079        // Decode it back, resulting in a dynamic structure.
1080        let value = obj2.inner_as::<DynamicStructure>().unwrap();
1081        assert_eq!(value.data.len(), 0);
1082        assert_eq!(value.discriminant, 0);
1083
1084        cursor.seek(std::io::SeekFrom::Start(0)).unwrap();
1085        BinaryEncodable::encode(&obj2, &mut cursor, &ctx.context()).unwrap();
1086
1087        // Make a new context, this time with the regular decoder for MyUnion
1088        let mut ctx = ContextOwned::new_default(get_namespaces(), DecodingOptions::test());
1089        ctx.loaders_mut().add_type_loader(MyUnionTypeLoader);
1090        cursor.seek(std::io::SeekFrom::Start(0)).unwrap();
1091        let obj3: ExtensionObject = BinaryDecodable::decode(&mut cursor, &ctx.context()).unwrap();
1092
1093        assert_eq!(obj, obj3);
1094    }
1095}