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)]
13pub 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 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 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 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 pub fn values(&self) -> &[Variant] {
144 &self.data
145 }
146
147 pub fn get_field(&self, index: usize) -> Option<&Variant> {
149 self.data.get(index)
150 }
151
152 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 return o.byte_len(ctx);
172 };
173
174 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 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 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 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 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 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 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
355pub struct DynamicTypeLoader {
358 pub(super) type_tree: Arc<DataTypeTree>,
359}
360
361impl DynamicTypeLoader {
362 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 field_ty.is_abstract {
448 Ok(Variant::from(<ExtensionObject as BinaryDecodable>::decode(
449 stream, ctx,
450 )?))
451 } else {
452 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 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 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 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 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 cursor.seek(std::io::SeekFrom::Start(0)).unwrap();
804 BinaryEncodable::encode(&obj2, &mut cursor, &ctx.context()).unwrap();
805
806 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 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 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 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 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 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 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}