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 => {
289 todo!("StructureWithSubtypedValues is unsupported")
290 }
291 StructureType::UnionWithSubtypedValues => {
292 todo!("UnionWithSubtypedValues is unsupported")
293 }
294 }
295
296 size
297 }
298
299 fn encode<S: std::io::Write + ?Sized>(
300 &self,
301 stream: &mut S,
302 ctx: &crate::Context<'_>,
303 ) -> crate::EncodingResult<()> {
304 let s = &self.type_def;
305 match s.structure_type {
306 StructureType::Structure => {
307 for (value, field) in self.data.iter().zip(s.fields.iter()) {
309 self.encode_field(stream, value, field, ctx)?;
310 }
311 }
312 StructureType::StructureWithOptionalFields => {
313 let mut encoding_mask = 0u32;
314 let mut optional_idx = 0;
315 for (value, field) in self.data.iter().zip(s.fields.iter()) {
316 if field.is_optional {
317 if !matches!(value, Variant::Empty) {
318 encoding_mask |= 1 << optional_idx;
319 }
320 optional_idx += 1;
321 }
322 }
323 write_u32(stream, encoding_mask)?;
324 for (value, field) in self.data.iter().zip(s.fields.iter()) {
325 if !field.is_optional || !matches!(value, Variant::Empty) {
326 self.encode_field(stream, value, field, ctx)?;
327 }
328 }
329 }
330 StructureType::Union => {
331 write_u32(stream, self.discriminant)?;
332 if self.discriminant != 0 {
333 let (Some(value), Some(field)) = (
334 self.data.first(),
335 s.fields.get(self.discriminant as usize - 1),
336 ) else {
337 return Err(Error::encoding(
338 "Discriminant was out of range of known fields",
339 ));
340 };
341
342 self.encode_field(stream, value, field, ctx)?;
343 }
344 }
345 StructureType::StructureWithSubtypedValues => {
346 todo!("StructureWithSubtypedValues is unsupported")
347 }
348 StructureType::UnionWithSubtypedValues => {
349 todo!("UnionWithSubtypedValues is unsupported")
350 }
351 }
352
353 Ok(())
354 }
355}
356
357pub struct DynamicTypeLoader {
360 pub(super) type_tree: Arc<DataTypeTree>,
361}
362
363impl DynamicTypeLoader {
364 pub fn new(type_tree: Arc<DataTypeTree>) -> Self {
366 Self { type_tree }
367 }
368
369 fn decode_field_value(
370 &self,
371 field: &ParsedStructureField,
372 stream: &mut dyn std::io::Read,
373 ctx: &Context<'_>,
374 ) -> EncodingResult<Variant> {
375 match field.scalar_type {
376 crate::VariantScalarTypeId::Boolean => Ok(Variant::from(
377 <bool as BinaryDecodable>::decode(stream, ctx)?,
378 )),
379 crate::VariantScalarTypeId::SByte => {
380 Ok(Variant::from(<i8 as BinaryDecodable>::decode(stream, ctx)?))
381 }
382 crate::VariantScalarTypeId::Byte => {
383 Ok(Variant::from(<u8 as BinaryDecodable>::decode(stream, ctx)?))
384 }
385 crate::VariantScalarTypeId::Int16 => Ok(Variant::from(
386 <i16 as BinaryDecodable>::decode(stream, ctx)?,
387 )),
388 crate::VariantScalarTypeId::UInt16 => Ok(Variant::from(
389 <u16 as BinaryDecodable>::decode(stream, ctx)?,
390 )),
391 crate::VariantScalarTypeId::Int32 => Ok(Variant::from(
392 <i32 as BinaryDecodable>::decode(stream, ctx)?,
393 )),
394 crate::VariantScalarTypeId::UInt32 => Ok(Variant::from(
395 <u32 as BinaryDecodable>::decode(stream, ctx)?,
396 )),
397 crate::VariantScalarTypeId::Int64 => Ok(Variant::from(
398 <i64 as BinaryDecodable>::decode(stream, ctx)?,
399 )),
400 crate::VariantScalarTypeId::UInt64 => Ok(Variant::from(
401 <u64 as BinaryDecodable>::decode(stream, ctx)?,
402 )),
403 crate::VariantScalarTypeId::Float => Ok(Variant::from(
404 <f32 as BinaryDecodable>::decode(stream, ctx)?,
405 )),
406 crate::VariantScalarTypeId::Double => Ok(Variant::from(
407 <f64 as BinaryDecodable>::decode(stream, ctx)?,
408 )),
409 crate::VariantScalarTypeId::String => Ok(Variant::from(
410 <UAString as BinaryDecodable>::decode(stream, ctx)?,
411 )),
412 crate::VariantScalarTypeId::DateTime => Ok(Variant::from(
413 <DateTime as BinaryDecodable>::decode(stream, ctx)?,
414 )),
415 crate::VariantScalarTypeId::Guid => Ok(Variant::from(
416 <Guid as BinaryDecodable>::decode(stream, ctx)?,
417 )),
418 crate::VariantScalarTypeId::ByteString => Ok(Variant::from(
419 <ByteString as BinaryDecodable>::decode(stream, ctx)?,
420 )),
421 crate::VariantScalarTypeId::XmlElement => Ok(Variant::from(
422 <XmlElement as BinaryDecodable>::decode(stream, ctx)?,
423 )),
424 crate::VariantScalarTypeId::NodeId => Ok(Variant::from(
425 <NodeId as BinaryDecodable>::decode(stream, ctx)?,
426 )),
427 crate::VariantScalarTypeId::ExpandedNodeId => Ok(Variant::from(
428 <ExpandedNodeId as BinaryDecodable>::decode(stream, ctx)?,
429 )),
430 crate::VariantScalarTypeId::StatusCode => Ok(Variant::from(
431 <StatusCode as BinaryDecodable>::decode(stream, ctx)?,
432 )),
433 crate::VariantScalarTypeId::QualifiedName => Ok(Variant::from(
434 <QualifiedName as BinaryDecodable>::decode(stream, ctx)?,
435 )),
436 crate::VariantScalarTypeId::LocalizedText => Ok(Variant::from(
437 <LocalizedText as BinaryDecodable>::decode(stream, ctx)?,
438 )),
439 crate::VariantScalarTypeId::ExtensionObject => {
440 let Some(field_ty) = self.type_tree.get_struct_type(&field.type_id) else {
441 return Err(Error::decoding(format!(
442 "Dynamic type field missing from type tree: {}",
443 field.type_id
444 )));
445 };
446
447 if field_ty.is_abstract {
450 Ok(Variant::from(<ExtensionObject as BinaryDecodable>::decode(
451 stream, ctx,
452 )?))
453 } else {
454 Ok(Variant::from(ctx.load_from_binary(
456 &field_ty.node_id,
457 stream,
458 None,
459 )?))
460 }
461 }
462 crate::VariantScalarTypeId::DataValue => Ok(Variant::from(
463 <DataValue as BinaryDecodable>::decode(stream, ctx)?,
464 )),
465 crate::VariantScalarTypeId::Variant => Ok(Variant::Variant(Box::new(
466 <Variant as BinaryDecodable>::decode(stream, ctx)?,
467 ))),
468 crate::VariantScalarTypeId::DiagnosticInfo => Ok(Variant::from(
469 <DiagnosticInfo as BinaryDecodable>::decode(stream, ctx)?,
470 )),
471 }
472 }
473
474 fn decode_field(
475 &self,
476 field: &ParsedStructureField,
477 stream: &mut dyn std::io::Read,
478 ctx: &Context<'_>,
479 ) -> EncodingResult<Variant> {
480 if field.value_rank > 0 {
481 let (len, array_dims) = if field.value_rank > 1 {
482 let Some(array_dims) = <Option<Vec<i32>> as BinaryDecodable>::decode(stream, ctx)?
483 else {
484 return Err(Error::decoding("Array has invalid ArrayDimensions"));
485 };
486 if array_dims.len() != field.value_rank as usize {
487 return Err(Error::decoding(
488 "Array has incorrect ArrayDimensions, must match value rank",
489 ));
490 }
491 let mut len = 1;
492 let mut final_dims = Vec::with_capacity(array_dims.len());
493 for dim in &array_dims {
494 if *dim <= 0 {
495 return Err(Error::decoding("Array has incorrect ArrayDimensions, all dimensions must be greater than zero"));
496 }
497 len *= *dim as u32;
498 final_dims.push(*dim as u32);
499 }
500 (len as usize, Some(final_dims))
501 } else {
502 let len = <u32 as BinaryDecodable>::decode(stream, ctx)?;
503 (len as usize, None)
504 };
505
506 if len > ctx.options().max_array_length {
507 return Err(Error::decoding(format!(
508 "Array length {} exceeds decoding limit {}",
509 len,
510 ctx.options().max_array_length
511 )));
512 }
513
514 let mut res = Vec::with_capacity(len);
515 for _ in 0..len {
516 res.push(self.decode_field_value(field, stream, ctx)?);
517 }
518 if let Some(dims) = array_dims {
519 Ok(Variant::Array(Box::new(
520 Array::new_multi(field.scalar_type, res, dims).map_err(Error::decoding)?,
521 )))
522 } else {
523 Ok(Variant::Array(Box::new(
524 Array::new(field.scalar_type, res).map_err(Error::decoding)?,
525 )))
526 }
527 } else {
528 self.decode_field_value(field, stream, ctx)
529 }
530 }
531
532 fn decode_type_inner(
533 &self,
534 stream: &mut dyn std::io::Read,
535 ctx: &Context<'_>,
536 t: &Arc<StructTypeInfo>,
537 ) -> crate::EncodingResult<Box<dyn crate::DynEncodable>> {
538 match t.structure_type {
539 StructureType::Structure => {
540 let mut values = Vec::with_capacity(t.fields.len());
541 for field in &t.fields {
542 values.push(self.decode_field(field, stream, ctx)?);
543 }
544 Ok(Box::new(DynamicStructure {
545 type_def: t.clone(),
546 discriminant: 0,
547 type_tree: self.type_tree.clone(),
548 data: values,
549 }))
550 }
551 StructureType::StructureWithOptionalFields => {
552 let mask = <u32 as BinaryDecodable>::decode(stream, ctx)?;
553 let mut values = Vec::with_capacity(t.fields.len());
554 let mut optional_idx = 0;
555 for field in t.fields.iter() {
556 if field.is_optional {
557 if (1 << optional_idx) & mask != 0 {
558 values.push(self.decode_field(field, stream, ctx)?);
559 } else {
560 values.push(Variant::Empty);
561 }
562 optional_idx += 1;
563 } else {
564 values.push(self.decode_field(field, stream, ctx)?);
565 }
566 }
567 Ok(Box::new(DynamicStructure {
568 type_def: t.clone(),
569 discriminant: 0,
570 type_tree: self.type_tree.clone(),
571 data: values,
572 }))
573 }
574 StructureType::Union => {
575 let discriminant = <u32 as BinaryDecodable>::decode(stream, ctx)?;
576 if discriminant == 0 {
577 return Ok(Box::new(DynamicStructure::new_null_union(
578 t.clone(),
579 self.type_tree.clone(),
580 )));
581 }
582 let Some(field) = t.fields.get(discriminant as usize - 1) else {
583 return Err(Error::decoding(format!(
584 "Invalid discriminant: {discriminant}"
585 )));
586 };
587 let values = vec![self.decode_field(field, stream, ctx)?];
588 Ok(Box::new(DynamicStructure {
589 type_def: t.clone(),
590 discriminant,
591 type_tree: self.type_tree.clone(),
592 data: values,
593 }))
594 }
595 StructureType::StructureWithSubtypedValues => {
596 todo!("StructureWithSubtypedValues is unsupported")
597 }
598 StructureType::UnionWithSubtypedValues => {
599 todo!("UnionWithSubtypedValues is unsupported")
600 }
601 }
602 }
603}
604
605impl TypeLoader for DynamicTypeLoader {
606 fn load_from_binary(
607 &self,
608 node_id: &NodeId,
609 stream: &mut dyn std::io::Read,
610 ctx: &Context<'_>,
611 _length: Option<usize>,
612 ) -> Option<crate::EncodingResult<Box<dyn crate::DynEncodable>>> {
613 let ty_node_id = if let Some(mapped) = self.type_tree.encoding_to_data_type().get(node_id) {
614 mapped
615 } else {
616 node_id
617 };
618 let t = self.type_tree.get_struct_type(ty_node_id)?;
619
620 if !t.is_supported() {
621 return None;
622 }
623
624 Some(self.decode_type_inner(stream, ctx, t))
625 }
626
627 fn priority(&self) -> crate::TypeLoaderPriority {
628 crate::TypeLoaderPriority::Dynamic(50)
629 }
630
631 #[cfg(feature = "xml")]
632 fn load_from_xml(
633 &self,
634 node_id: &crate::NodeId,
635 stream: &mut crate::xml::XmlStreamReader<&mut dyn std::io::Read>,
636 ctx: &Context<'_>,
637 _name: &str,
638 ) -> Option<crate::EncodingResult<Box<dyn crate::DynEncodable>>> {
639 let ty_node_id = if let Some(mapped) = self.type_tree.encoding_to_data_type().get(node_id) {
640 mapped
641 } else {
642 node_id
643 };
644 let t = self.type_tree.get_struct_type(ty_node_id)?;
645
646 if !t.is_supported() {
647 return None;
648 }
649
650 Some(self.xml_decode_type_inner(stream, ctx, t))
651 }
652
653 #[cfg(feature = "json")]
654 fn load_from_json(
655 &self,
656 node_id: &crate::NodeId,
657 stream: &mut crate::json::JsonStreamReader<&mut dyn std::io::Read>,
658 ctx: &Context<'_>,
659 ) -> Option<crate::EncodingResult<Box<dyn crate::DynEncodable>>> {
660 let ty_node_id = if let Some(mapped) = self.type_tree.encoding_to_data_type().get(node_id) {
661 mapped
662 } else {
663 node_id
664 };
665 let t = self.type_tree.get_struct_type(ty_node_id)?;
666
667 if !t.is_supported() {
668 return None;
669 }
670
671 Some(self.json_decode_type_inner(stream, ctx, t))
672 }
673}
674
675#[cfg(test)]
676pub(crate) mod tests {
677 use std::{
678 io::{Cursor, Seek},
679 sync::{Arc, LazyLock},
680 };
681
682 use opcua_macros::ua_encodable;
683
684 use crate::{
685 binary_decode_to_enc, json_decode_to_enc, xml_decode_to_enc, Array, BinaryDecodable,
686 BinaryEncodable, ContextOwned, DataTypeDefinition, DataTypeId, DecodingOptions,
687 EUInformation, ExpandedMessageInfo, ExtensionObject, LocalizedText, NamespaceMap, NodeId,
688 ObjectId, StaticTypeLoader, StructureDefinition, StructureField, TypeLoaderCollection,
689 TypeLoaderInstance, UAString, Variant, VariantScalarTypeId,
690 };
691
692 use crate::custom::type_tree::{
693 DataTypeTree, EncodingIds, GenericTypeInfo, ParentIds, TypeInfo,
694 };
695
696 use super::{DynamicStructure, DynamicTypeLoader};
697
698 pub(crate) fn make_type_tree() -> DataTypeTree {
699 let mut type_tree = DataTypeTree::new(ParentIds::new());
701 type_tree.add_type(DataTypeId::Int32.into(), GenericTypeInfo::new(false));
702 type_tree.add_type(DataTypeId::Boolean.into(), GenericTypeInfo::new(false));
703 type_tree.add_type(
704 DataTypeId::LocalizedText.into(),
705 GenericTypeInfo::new(false),
706 );
707 type_tree.add_type(DataTypeId::String.into(), GenericTypeInfo::new(false));
708 type_tree
709 }
710
711 pub(crate) fn add_eu_information(type_tree: &mut DataTypeTree) {
712 type_tree.parent_ids_mut().add_type(
713 DataTypeId::EUInformation.into(),
714 DataTypeId::Structure.into(),
715 );
716 type_tree.add_type(
717 DataTypeId::EUInformation.into(),
718 TypeInfo::from_type_definition(
719 DataTypeDefinition::Structure(StructureDefinition {
720 default_encoding_id: NodeId::null(),
721 base_data_type: DataTypeId::Structure.into(),
722 structure_type: crate::StructureType::Structure,
723 fields: Some(vec![
724 StructureField {
725 name: "NamespaceUri".into(),
726 data_type: DataTypeId::String.into(),
727 value_rank: -1,
728 ..Default::default()
729 },
730 StructureField {
731 name: "UnitId".into(),
732 data_type: DataTypeId::Int32.into(),
733 value_rank: -1,
734 ..Default::default()
735 },
736 StructureField {
737 name: "DisplayName".into(),
738 data_type: DataTypeId::LocalizedText.into(),
739 value_rank: -1,
740 ..Default::default()
741 },
742 StructureField {
743 name: "Description".into(),
744 data_type: DataTypeId::LocalizedText.into(),
745 value_rank: -1,
746 ..Default::default()
747 },
748 ]),
749 }),
750 "EUInformation".to_owned(),
751 Some(EncodingIds {
752 binary_id: ObjectId::EUInformation_Encoding_DefaultBinary.into(),
753 json_id: ObjectId::EUInformation_Encoding_DefaultJson.into(),
754 xml_id: ObjectId::EUInformation_Encoding_DefaultXml.into(),
755 }),
756 false,
757 &DataTypeId::EUInformation.into(),
758 type_tree.parent_ids(),
759 )
760 .unwrap(),
761 );
762 }
763
764 #[test]
765 fn dynamic_struct_round_trip() {
766 let mut type_tree = make_type_tree();
767 add_eu_information(&mut type_tree);
768 let loader = DynamicTypeLoader::new(Arc::new(type_tree));
771 let mut loaders = TypeLoaderCollection::new_empty();
772 loaders.add_type_loader(loader);
773 let ctx = ContextOwned::new(NamespaceMap::new(), loaders, DecodingOptions::test());
774
775 let mut write_buf = Vec::<u8>::new();
776 let mut cursor = Cursor::new(&mut write_buf);
777
778 let obj = ExtensionObject::from_message(EUInformation {
779 namespace_uri: "my.namespace.uri".into(),
780 unit_id: 5,
781 display_name: "Degrees Celsius".into(),
782 description: "Description".into(),
783 });
784
785 BinaryEncodable::encode(&obj, &mut cursor, &ctx.context()).unwrap();
787 cursor.seek(std::io::SeekFrom::Start(0)).unwrap();
788
789 let obj2: ExtensionObject = BinaryDecodable::decode(&mut cursor, &ctx.context()).unwrap();
790
791 let value = obj2.inner_as::<DynamicStructure>().unwrap();
793 assert_eq!(value.data.len(), 4);
794 assert_eq!(value.data[0], Variant::from("my.namespace.uri"));
795 assert_eq!(value.data[1], Variant::from(5i32));
796 assert_eq!(
797 value.data[2],
798 Variant::from(LocalizedText::from("Degrees Celsius"))
799 );
800 assert_eq!(
801 value.data[3],
802 Variant::from(LocalizedText::from("Description"))
803 );
804
805 cursor.seek(std::io::SeekFrom::Start(0)).unwrap();
807 BinaryEncodable::encode(&obj2, &mut cursor, &ctx.context()).unwrap();
808
809 let ctx = ContextOwned::new_default(NamespaceMap::new(), DecodingOptions::test());
811 cursor.seek(std::io::SeekFrom::Start(0)).unwrap();
812 let obj3: ExtensionObject = BinaryDecodable::decode(&mut cursor, &ctx.context()).unwrap();
813
814 assert_eq!(obj, obj3);
815 }
816
817 #[test]
818 fn dynamic_nested_struct_round_trip() {
819 let mut type_tree = make_type_tree();
820 add_eu_information(&mut type_tree);
821 let type_node_id = NodeId::new(1, 5);
822 type_tree
823 .parent_ids_mut()
824 .add_type(type_node_id.clone(), DataTypeId::Structure.into());
825 type_tree.add_type(
826 type_node_id.clone(),
827 TypeInfo::from_type_definition(
828 DataTypeDefinition::Structure(StructureDefinition {
829 default_encoding_id: NodeId::null(),
830 base_data_type: DataTypeId::Structure.into(),
831 structure_type: crate::StructureType::Structure,
832 fields: Some(vec![
833 StructureField {
834 name: "Info".into(),
835 data_type: DataTypeId::EUInformation.into(),
836 value_rank: -1,
837 ..Default::default()
838 },
839 StructureField {
840 name: "InfoArray".into(),
841 data_type: DataTypeId::EUInformation.into(),
842 value_rank: 1,
843 ..Default::default()
844 },
845 StructureField {
846 name: "AbstractField".into(),
847 data_type: DataTypeId::BaseDataType.into(),
848 value_rank: -1,
849 ..Default::default()
850 },
851 StructureField {
852 name: "PrimitiveArray".into(),
853 data_type: DataTypeId::Int32.into(),
854 value_rank: 2,
855 ..Default::default()
856 },
857 ]),
858 }),
859 "MyType".to_owned(),
860 Some(EncodingIds {
861 binary_id: NodeId::new(1, 6),
862 json_id: NodeId::new(1, 7),
863 xml_id: NodeId::new(1, 8),
864 }),
865 false,
866 &type_node_id,
867 type_tree.parent_ids(),
868 )
869 .unwrap(),
870 );
871 let type_tree = Arc::new(type_tree);
872 let loader = DynamicTypeLoader::new(type_tree.clone());
873 let mut loaders = TypeLoaderCollection::new();
874 loaders.add_type_loader(loader);
875 let ctx = ContextOwned::new(NamespaceMap::new(), loaders, DecodingOptions::test());
876
877 let obj = DynamicStructure::new_struct(
878 type_tree.get_struct_type(&type_node_id).unwrap().clone(),
879 type_tree,
880 vec![
881 Variant::from(ExtensionObject::from_message(EUInformation {
882 namespace_uri: "my.namespace.uri".into(),
883 unit_id: 5,
884 display_name: "Degrees Celsius".into(),
885 description: "Description".into(),
886 })),
887 Variant::from(vec![
888 ExtensionObject::from_message(EUInformation {
889 namespace_uri: "my.namespace.uri".into(),
890 unit_id: 5,
891 display_name: "Degrees Celsius".into(),
892 description: "Description".into(),
893 }),
894 ExtensionObject::from_message(EUInformation {
895 namespace_uri: "my.namespace.uri.2".into(),
896 unit_id: 6,
897 display_name: "Degrees Celsius 2".into(),
898 description: "Description 2".into(),
899 }),
900 ]),
901 Variant::Variant(Box::new(Variant::from(123))),
902 Variant::from(
903 Array::new_multi(
904 VariantScalarTypeId::Int32,
905 [1i32, 2, 3, 4, 5, 6]
906 .into_iter()
907 .map(Variant::from)
908 .collect::<Vec<_>>(),
909 vec![2, 3],
910 )
911 .unwrap(),
912 ),
913 ],
914 )
915 .unwrap();
916 let obj = ExtensionObject::from_message(obj);
917
918 let mut write_buf = Vec::<u8>::new();
919 let mut cursor = Cursor::new(&mut write_buf);
920
921 BinaryEncodable::encode(&obj, &mut cursor, &ctx.context()).unwrap();
922 cursor.seek(std::io::SeekFrom::Start(0)).unwrap();
923 let obj2: ExtensionObject = BinaryDecodable::decode(&mut cursor, &ctx.context()).unwrap();
924
925 assert_eq!(obj, obj2);
926 }
927
928 pub(crate) fn get_namespaces() -> NamespaceMap {
929 let mut namespaces = NamespaceMap::new();
930 namespaces.add_namespace(TYPE_NAMESPACE);
931 namespaces
932 }
933
934 pub(crate) fn get_custom_union() -> ContextOwned {
935 let mut type_tree = make_type_tree();
936 type_tree
937 .parent_ids_mut()
938 .add_type(NodeId::new(1, 1), DataTypeId::Structure.into());
939 type_tree.add_type(
940 NodeId::new(1, 1),
941 TypeInfo::from_type_definition(
942 DataTypeDefinition::Structure(StructureDefinition {
943 default_encoding_id: NodeId::null(),
944 base_data_type: DataTypeId::Structure.into(),
945 structure_type: crate::StructureType::Union,
946 fields: Some(vec![
947 StructureField {
948 name: "Integer".into(),
949 data_type: DataTypeId::Int32.into(),
950 value_rank: -1,
951 ..Default::default()
952 },
953 StructureField {
954 name: "StringVariant".into(),
955 data_type: DataTypeId::String.into(),
956 value_rank: -1,
957 ..Default::default()
958 },
959 ]),
960 }),
961 "MyUnion".to_owned(),
962 Some(EncodingIds {
963 binary_id: NodeId::new(1, 2),
964 json_id: NodeId::new(1, 3),
965 xml_id: NodeId::new(1, 4),
966 }),
967 false,
968 &NodeId::new(1, 1),
969 type_tree.parent_ids(),
970 )
971 .unwrap(),
972 );
973
974 let loader = DynamicTypeLoader::new(Arc::new(type_tree));
975 let mut loaders = TypeLoaderCollection::new_empty();
976 loaders.add_type_loader(loader);
977
978 ContextOwned::new(get_namespaces(), loaders, DecodingOptions::test())
979 }
980
981 mod opcua {
982 pub(super) use crate as types;
983 }
984
985 const TYPE_NAMESPACE: &str = "my.custom.namespace.uri";
986
987 #[derive(Debug, Clone, PartialEq)]
988 #[ua_encodable]
989 pub(crate) enum MyUnion {
990 Null,
991 Integer(i32),
992 StringVariant(UAString),
993 }
994
995 impl ExpandedMessageInfo for MyUnion {
996 fn full_type_id(&self) -> crate::ExpandedNodeId {
997 crate::ExpandedNodeId::new_with_namespace(TYPE_NAMESPACE, 2)
998 }
999
1000 fn full_json_type_id(&self) -> crate::ExpandedNodeId {
1001 crate::ExpandedNodeId::new_with_namespace(TYPE_NAMESPACE, 3)
1002 }
1003
1004 fn full_xml_type_id(&self) -> crate::ExpandedNodeId {
1005 crate::ExpandedNodeId::new_with_namespace(TYPE_NAMESPACE, 4)
1006 }
1007
1008 fn full_data_type_id(&self) -> crate::ExpandedNodeId {
1009 crate::ExpandedNodeId::new_with_namespace(TYPE_NAMESPACE, 1)
1010 }
1011 }
1012
1013 static TYPES: LazyLock<TypeLoaderInstance> = LazyLock::new(|| {
1014 let mut inst = opcua::types::TypeLoaderInstance::new();
1015 inst.add_binary_type(1, 2, binary_decode_to_enc::<MyUnion>);
1016 inst.add_json_type(1, 3, json_decode_to_enc::<MyUnion>);
1017 inst.add_xml_type(1, 4, xml_decode_to_enc::<MyUnion>);
1018 inst
1019 });
1020
1021 pub(crate) struct MyUnionTypeLoader;
1022
1023 impl StaticTypeLoader for MyUnionTypeLoader {
1024 fn instance() -> &'static TypeLoaderInstance {
1025 &TYPES
1026 }
1027
1028 fn namespace() -> &'static str {
1029 TYPE_NAMESPACE
1030 }
1031 }
1032
1033 #[test]
1034 fn union_round_trip() {
1035 let ctx = get_custom_union();
1036
1037 let mut write_buf = Vec::<u8>::new();
1038 let mut cursor = Cursor::new(&mut write_buf);
1039
1040 let obj = ExtensionObject::from_message(MyUnion::Integer(123));
1041
1042 BinaryEncodable::encode(&obj, &mut cursor, &ctx.context()).unwrap();
1044 cursor.seek(std::io::SeekFrom::Start(0)).unwrap();
1045
1046 let obj2: ExtensionObject = BinaryDecodable::decode(&mut cursor, &ctx.context()).unwrap();
1047
1048 let value = obj2.inner_as::<DynamicStructure>().unwrap();
1050 assert_eq!(value.data.len(), 1);
1051
1052 assert_eq!(value.data[0], Variant::from(123i32));
1053 assert_eq!(value.discriminant, 1);
1054
1055 cursor.seek(std::io::SeekFrom::Start(0)).unwrap();
1056 BinaryEncodable::encode(&obj2, &mut cursor, &ctx.context()).unwrap();
1057
1058 let mut ctx = ContextOwned::new_default(get_namespaces(), DecodingOptions::test());
1060 ctx.loaders_mut().add_type_loader(MyUnionTypeLoader);
1061 cursor.seek(std::io::SeekFrom::Start(0)).unwrap();
1062 let obj3: ExtensionObject = BinaryDecodable::decode(&mut cursor, &ctx.context()).unwrap();
1063
1064 assert_eq!(obj, obj3);
1065 }
1066
1067 #[test]
1068 fn union_null() {
1069 let ctx = get_custom_union();
1070
1071 let mut write_buf = Vec::<u8>::new();
1072 let mut cursor = Cursor::new(&mut write_buf);
1073
1074 let obj = ExtensionObject::from_message(MyUnion::Null);
1075
1076 BinaryEncodable::encode(&obj, &mut cursor, &ctx.context()).unwrap();
1078 cursor.seek(std::io::SeekFrom::Start(0)).unwrap();
1079
1080 let obj2: ExtensionObject = BinaryDecodable::decode(&mut cursor, &ctx.context()).unwrap();
1081
1082 let value = obj2.inner_as::<DynamicStructure>().unwrap();
1084 assert_eq!(value.data.len(), 0);
1085 assert_eq!(value.discriminant, 0);
1086
1087 cursor.seek(std::io::SeekFrom::Start(0)).unwrap();
1088 BinaryEncodable::encode(&obj2, &mut cursor, &ctx.context()).unwrap();
1089
1090 let mut ctx = ContextOwned::new_default(get_namespaces(), DecodingOptions::test());
1092 ctx.loaders_mut().add_type_loader(MyUnionTypeLoader);
1093 cursor.seek(std::io::SeekFrom::Start(0)).unwrap();
1094 let obj3: ExtensionObject = BinaryDecodable::decode(&mut cursor, &ctx.context()).unwrap();
1095
1096 assert_eq!(obj, obj3);
1097 }
1098}