1use std::{
8 any::{Any, TypeId},
9 fmt,
10 io::{Read, Write},
11};
12
13use crate::{write_i32, write_u8, Error, ExpandedMessageInfo, ExpandedNodeId, UaNullable};
14
15use super::{
16 encoding::{BinaryDecodable, BinaryEncodable, EncodingResult},
17 node_id::NodeId,
18 ObjectId,
19};
20
21#[derive(Debug)]
22pub struct ExtensionObjectError;
24
25impl fmt::Display for ExtensionObjectError {
26 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
27 write!(f, "ExtensionObjectError")
28 }
29}
30
31pub trait DynEncodable: Any + Send + Sync + std::fmt::Debug {
41 fn encode_binary(
43 &self,
44 stream: &mut dyn std::io::Write,
45 ctx: &crate::Context<'_>,
46 ) -> EncodingResult<()>;
47
48 #[cfg(feature = "json")]
49 fn encode_json(
51 &self,
52 stream: &mut crate::json::JsonStreamWriter<&mut dyn std::io::Write>,
53 ctx: &crate::Context<'_>,
54 ) -> EncodingResult<()>;
55
56 #[cfg(feature = "xml")]
57 fn encode_xml(
59 &self,
60 stream: &mut crate::xml::XmlStreamWriter<&mut dyn std::io::Write>,
61 ctx: &crate::Context<'_>,
62 ) -> EncodingResult<()>;
63
64 #[cfg(feature = "xml")]
65 fn xml_tag_name(&self) -> &str;
67
68 fn byte_len_dyn(&self, ctx: &crate::Context<'_>) -> usize;
70
71 fn binary_type_id(&self) -> ExpandedNodeId;
73
74 #[cfg(feature = "json")]
75 fn json_type_id(&self) -> ExpandedNodeId;
77
78 #[cfg(feature = "xml")]
79 fn xml_type_id(&self) -> ExpandedNodeId;
81
82 fn data_type_id(&self) -> ExpandedNodeId;
84
85 fn as_dyn_any(self: Box<Self>) -> Box<dyn Any + Send + Sync + 'static>;
87
88 fn as_dyn_any_ref(&self) -> &(dyn Any + Send + Sync);
90
91 fn clone_box(&self) -> Box<dyn DynEncodable>;
93
94 fn dyn_eq(&self, other: &dyn DynEncodable) -> bool;
97
98 fn type_name(&self) -> &'static str;
101
102 fn override_encoding(&self) -> Option<crate::encoding::BuiltInDataEncoding>;
106}
107
108macro_rules! blanket_dyn_encodable {
109 ($bound:tt $(+ $others:tt)*) => {
110 impl<T> DynEncodable for T
111 where
112 T: $bound $(+ $others)* + ExpandedMessageInfo + Any + std::fmt::Debug + Send + Sync + Clone + PartialEq,
113 {
114 fn encode_binary(&self, stream: &mut dyn std::io::Write, ctx: &crate::Context<'_>) -> EncodingResult<()> {
115 BinaryEncodable::encode(self, stream, ctx)
116 }
117
118 #[cfg(feature = "json")]
119 fn encode_json(
120 &self,
121 stream: &mut crate::json::JsonStreamWriter<&mut dyn std::io::Write>,
122 ctx: &crate::Context<'_>
123 ) -> EncodingResult<()> {
124 JsonEncodable::encode(self, stream, ctx)
125 }
126
127 #[cfg(feature = "xml")]
128 fn encode_xml(
129 &self,
130 stream: &mut crate::xml::XmlStreamWriter<&mut dyn std::io::Write>,
131 ctx: &crate::Context<'_>,
132 ) -> EncodingResult<()> {
133 XmlEncodable::encode(self, stream, ctx)
134 }
135
136 #[cfg(feature = "xml")]
137 fn xml_tag_name(&self,) -> &str {
138 self.tag()
139 }
140
141 fn byte_len_dyn(&self, ctx: &crate::Context<'_>,) -> usize {
142 BinaryEncodable::byte_len(self, ctx)
143 }
144
145 fn binary_type_id(&self) -> ExpandedNodeId {
146 self.full_type_id()
147 }
148
149 #[cfg(feature = "json")]
150 fn json_type_id(&self) -> ExpandedNodeId {
151 self.full_json_type_id()
152 }
153
154 #[cfg(feature = "xml")]
155 fn xml_type_id(&self) -> ExpandedNodeId {
156 self.full_xml_type_id()
157 }
158
159 fn data_type_id(&self) -> ExpandedNodeId {
160 self.full_data_type_id()
161 }
162
163 fn as_dyn_any(self: Box<Self>) -> Box<dyn Any + Send + Sync + 'static> {
164 self
165 }
166
167 fn as_dyn_any_ref(&self) -> &(dyn Any + Send + Sync) {
168 self
169 }
170
171 fn clone_box(&self) -> Box<dyn DynEncodable> {
172 Box::new(self.clone())
173 }
174
175 fn dyn_eq(&self, other: &dyn DynEncodable) -> bool {
176 if let Some(o) = other.as_dyn_any_ref().downcast_ref::<Self>() {
177 o == self
178 } else {
179 false
180 }
181 }
182
183 fn type_name(&self) -> &'static str {
184 std::any::type_name::<Self>()
185 }
186
187 fn override_encoding(&self) -> Option<crate::encoding::BuiltInDataEncoding> {
188 BinaryEncodable::override_encoding(self)
189 }
190 }
191 };
192}
193
194#[cfg(feature = "json")]
195use crate::json::JsonEncodable;
196
197#[cfg(feature = "xml")]
198use crate::xml::XmlEncodable;
199
200#[cfg(feature = "xml")]
201macro_rules! blanket_call_2 {
202 ($($res:tt)*) => {
203 blanket_dyn_encodable!($($res)* + XmlEncodable);
204 }
205}
206#[cfg(not(feature = "xml"))]
207macro_rules! blanket_call_2 {
208 ($($res:tt)*) => {
209 blanket_dyn_encodable!($($res)*);
210 }
211}
212
213#[cfg(feature = "json")]
214macro_rules! blanket_call_1 {
215 ($($res:tt)*) => {
216 blanket_call_2!($($res)* + JsonEncodable);
217 }
218}
219#[cfg(not(feature = "json"))]
220macro_rules! blanket_call_1 {
221 ($($res:tt)*) => {
222 blanket_call_2!($($res)*);
223 }
224}
225
226blanket_call_1!(BinaryEncodable);
227
228impl PartialEq for dyn DynEncodable {
229 fn eq(&self, other: &dyn DynEncodable) -> bool {
230 self.dyn_eq(other)
231 }
232}
233
234impl std::error::Error for ExtensionObjectError {}
235
236impl UaNullable for ExtensionObject {}
237
238#[cfg(feature = "json")]
239mod json {
240 use std::io::{Cursor, Read};
241
242 use crate::{json::*, ByteString, Error, NodeId};
243
244 use super::ExtensionObject;
245
246 impl JsonEncodable for ExtensionObject {
247 fn encode(
248 &self,
249 stream: &mut JsonStreamWriter<&mut dyn std::io::Write>,
250 ctx: &crate::Context<'_>,
251 ) -> super::EncodingResult<()> {
252 let Some(body) = &self.body else {
253 stream.null_value()?;
254 return Ok(());
255 };
256
257 let type_id = body.json_type_id();
258
259 let id = type_id.try_resolve(ctx.namespaces()).ok_or_else(|| {
260 Error::encoding(format!("Missing namespace for encoding ID: {type_id}"))
261 })?;
262
263 stream.begin_object()?;
264
265 stream.name("UaTypeId")?;
266 JsonEncodable::encode(id.as_ref(), stream, ctx)?;
267
268 if let Some(encoding) = body.override_encoding() {
269 match encoding {
270 crate::BuiltInDataEncoding::Binary => {
271 stream.name("UaEncoding")?;
272 stream.number_value(1)?;
273 }
274 crate::BuiltInDataEncoding::XML => {
275 stream.name("UaEncoding")?;
276 stream.number_value(2)?;
277 }
278 crate::BuiltInDataEncoding::JSON => (),
279 }
280 }
281
282 stream.name("UaBody")?;
283 body.encode_json(stream, ctx)?;
284
285 stream.end_object()?;
286
287 Ok(())
288 }
289 }
290
291 impl JsonDecodable for ExtensionObject {
292 fn decode(
293 stream: &mut JsonStreamReader<&mut dyn std::io::Read>,
294 ctx: &Context<'_>,
295 ) -> super::EncodingResult<Self> {
296 if stream.peek()? == ValueType::Null {
297 stream.next_null()?;
298 return Ok(Self::null());
299 }
300
301 let mut type_id: Option<NodeId> = None;
302 let mut encoding: Option<u32> = None;
303 let mut raw_body = None;
304 let mut raw_string_body: Option<String> = None;
305 let mut body = None;
306
307 stream.begin_object()?;
308
309 while stream.has_next()? {
310 match stream.next_name()? {
311 "UaTypeId" => type_id = Some(JsonDecodable::decode(stream, ctx)?),
312 "UaEncoding" => encoding = Some(JsonDecodable::decode(stream, ctx)?),
313 "UaBody" => match stream.peek()? {
314 ValueType::Object => {
315 if encoding.is_some_and(|e| e != 0) {
316 return Err(Error::decoding(format!(
317 "Invalid encoding, expected 0 or null, got {encoding:?}"
318 )));
319 }
320 if let Some(type_id) = &type_id {
321 body = Some(ctx.load_from_json(type_id, stream)?);
322 } else {
323 raw_body = Some(consume_raw_value(stream)?);
324 }
325 }
326 _ => {
327 raw_string_body = Some(JsonDecodable::decode(stream, ctx)?);
328 }
329 },
330 _ => stream.skip_value()?,
331 }
332 }
333
334 stream.end_object()?;
335
336 let Some(type_id) = type_id else {
337 return Err(Error::decoding("Missing type ID in extension object"));
338 };
339
340 let encoding = encoding.unwrap_or_default();
341
342 if let Some(body) = body {
343 Ok(body)
344 } else if let Some(raw_body) = raw_body {
345 if encoding != 0 {
346 return Err(Error::decoding(format!(
347 "Invalid encoding, expected 0 or null, got {encoding}"
348 )));
349 }
350 let mut cursor = Cursor::new(raw_body);
351 let mut inner_stream = JsonStreamReader::new(&mut cursor as &mut dyn Read);
352 Ok(ctx.load_from_json(&type_id, &mut inner_stream)?)
353 } else if let Some(string_body) = raw_string_body {
354 if encoding == 1 {
355 let bytes = ByteString::from_base64_ignore_whitespace(string_body).ok_or_else(
356 || Error::decoding("Invalid base64 string is JSON extension object"),
357 )?;
358 let Some(raw) = bytes.value else {
359 return Err(Error::decoding("Missing extension object body"));
360 };
361 let len = raw.len();
362 let mut cursor = Cursor::new(raw);
363 Ok(ctx.load_from_binary(&type_id, &mut cursor as &mut dyn Read, Some(len))?)
364 } else if encoding == 2 {
365 #[cfg(feature = "xml")]
366 {
367 let mut cursor = Cursor::new(string_body.as_bytes());
368 let mut inner_stream =
369 crate::xml::XmlStreamReader::new(&mut cursor as &mut dyn Read);
370 if let Some(name) = crate::xml::enter_first_tag(&mut inner_stream)? {
371 Ok(ctx.load_from_xml(&type_id, &mut inner_stream, &name)?)
372 } else {
373 Ok(ExtensionObject::null())
374 }
375 }
376 #[cfg(not(feature = "xml"))]
377 {
378 tracing::warn!("XML feature is not enabled, deserializing XML payloads in JSON extension objects is not supported");
379 Ok(ExtensionObject::null())
380 }
381 } else {
382 Err(Error::decoding(format!("Unsupported extension object encoding, expected 1 or 2 for string, got {encoding}")))
383 }
384 } else {
385 Err(Error::decoding("Missing extension object body"))
386 }
387 }
388 }
389}
390
391#[cfg(feature = "xml")]
392mod xml {
393 use opcua_xml::events::Event;
394
395 use crate::{xml::*, ByteString, NodeId};
396 use std::{
397 io::{Read, Write},
398 str::from_utf8,
399 };
400
401 use super::ExtensionObject;
402
403 impl XmlType for ExtensionObject {
404 const TAG: &'static str = "ExtensionObject";
405 }
406
407 impl XmlEncodable for ExtensionObject {
408 fn encode(
409 &self,
410 writer: &mut XmlStreamWriter<&mut dyn Write>,
411 ctx: &crate::Context<'_>,
412 ) -> super::EncodingResult<()> {
413 let Some(body) = &self.body else {
414 return Ok(());
416 };
417 let type_id = body.xml_type_id();
418 let id = type_id.try_resolve(ctx.namespaces()).ok_or_else(|| {
419 Error::encoding(format!("Missing namespace for encoding ID: {type_id}"))
420 })?;
421
422 writer.encode_child("TypeId", id.as_ref(), ctx)?;
423 writer.write_start("Body")?;
424 writer.write_start(body.xml_tag_name())?;
425 body.encode_xml(writer, ctx)?;
426 writer.write_end(body.xml_tag_name())?;
427 writer.write_end("Body")?;
428
429 Ok(())
430 }
431 }
432
433 impl XmlDecodable for ExtensionObject {
434 fn decode(
435 read: &mut XmlStreamReader<&mut dyn Read>,
436 ctx: &Context<'_>,
437 ) -> Result<Self, Error> {
438 let mut type_id = None;
439 let mut body = None;
440 read.iter_children(
441 |key, reader, ctx| {
442 match key.as_str() {
443 "TypeId" => type_id = Some(NodeId::decode(reader, ctx)?),
444 "Body" => {
445 let top_level = loop {
447 match reader.next_event()? {
448 Event::Start(s) => break s,
449 Event::End(_) => {
450 return Ok(()); }
452 _ => (),
453 }
454 };
455 let Some(type_id) = type_id.take() else {
456 return Err(Error::decoding("Missing type ID in extension object"));
457 };
458
459 if top_level.local_name().as_ref() == b"ByteString" {
460 let val = ByteString::decode(reader, ctx)?;
462 if let Some(raw) = val.value {
463 let len = raw.len();
464 let mut cursor = std::io::Cursor::new(raw);
465 body = Some(ctx.load_from_binary(
466 &type_id,
467 &mut cursor,
468 Some(len),
469 )?);
470 }
471 } else {
472 let local_name = top_level.local_name();
473 let name = from_utf8(local_name.as_ref())?.to_owned();
474 body = Some(ctx.load_from_xml(&type_id, reader, &name)?);
476 }
477 reader.skip_value()?;
479 }
480 _ => reader.skip_value()?,
481 };
482 Ok(())
483 },
484 ctx,
485 )?;
486 Ok(body.unwrap_or_else(ExtensionObject::null))
487 }
488 }
489}
490
491#[derive(PartialEq, Debug)]
499pub struct ExtensionObject {
500 pub body: Option<Box<dyn DynEncodable>>,
502}
503
504impl Clone for ExtensionObject {
505 fn clone(&self) -> Self {
506 Self {
507 body: self.body.as_ref().map(|b| b.clone_box()),
508 }
509 }
510}
511
512impl Default for ExtensionObject {
513 fn default() -> Self {
514 Self::null()
515 }
516}
517
518impl BinaryEncodable for ExtensionObject {
519 fn byte_len(&self, ctx: &crate::Context<'_>) -> usize {
520 let type_id = self.binary_type_id();
521 let id = type_id.try_resolve(ctx.namespaces());
522
523 let mut size = id.map(|n| n.byte_len(ctx)).unwrap_or(2usize);
525 size += match &self.body {
526 Some(b) => 5 + b.byte_len_dyn(ctx),
527 None => 1,
528 };
529
530 size
531 }
532
533 fn encode<S: Write + ?Sized>(
534 &self,
535 mut stream: &mut S,
536 ctx: &crate::Context<'_>,
537 ) -> EncodingResult<()> {
538 let type_id = self.binary_type_id();
539 let id = type_id.try_resolve(ctx.namespaces());
540 let Some(id) = id else {
541 return Err(Error::encoding(format!("Unknown encoding ID: {type_id}")));
542 };
543
544 BinaryEncodable::encode(id.as_ref(), stream, ctx)?;
545
546 match &self.body {
547 Some(b) => {
548 if matches!(b.override_encoding(), Some(crate::BuiltInDataEncoding::XML)) {
549 write_u8(stream, 0x2)?;
550 } else {
551 write_u8(stream, 0x1)?;
552 }
553 write_i32(stream, b.byte_len_dyn(ctx) as i32)?;
554 b.encode_binary(&mut stream as &mut dyn Write, ctx)
555 }
556 None => write_u8(stream, 0x0),
557 }
558 }
559}
560impl BinaryDecodable for ExtensionObject {
561 fn decode<S: Read + ?Sized>(
562 mut stream: &mut S,
563 ctx: &crate::Context<'_>,
564 ) -> EncodingResult<Self> {
565 let _depth_lock = ctx.options().depth_lock()?;
567 let node_id = NodeId::decode(stream, ctx)?;
568 let encoding_type = u8::decode(stream, ctx)?;
569 let body = match encoding_type {
570 0x0 => None,
571 0x1 => {
572 let size = i32::decode(stream, ctx)?;
573 if size <= 0 {
574 None
575 } else {
576 Some(ctx.load_from_binary(&node_id, &mut stream, Some(size as usize))?)
577 }
578 }
579 0x2 => {
580 #[cfg(feature = "xml")]
581 {
582 let body = crate::UAString::decode(stream, ctx)?;
583 let Some(body) = body.value() else {
584 return Ok(ExtensionObject::null());
585 };
586 let mut cursor = std::io::Cursor::new(body.as_bytes());
587 let mut inner_stream =
588 crate::xml::XmlStreamReader::new(&mut cursor as &mut dyn Read);
589 if let Some(name) = crate::xml::enter_first_tag(&mut inner_stream)? {
590 Some(ctx.load_from_xml(&node_id, &mut inner_stream, &name)?)
591 } else {
592 None
593 }
594 }
595
596 #[cfg(not(feature = "xml"))]
597 {
598 let size = i32::decode(stream, ctx)?;
599 if size > 0 {
600 let mut bytes = vec![0u8; size as usize];
601 stream.read_exact(&mut bytes)?;
602 Some(ExtensionObject::new(crate::type_loader::XmlBody::new(
603 bytes,
604 node_id,
605 "XmlElement".to_owned(),
608 )))
609 } else {
610 None
611 }
612 }
613 }
614 _ => {
615 return Err(Error::decoding(format!(
616 "Invalid encoding type {encoding_type} in stream"
617 )));
618 }
619 };
620 Ok(body.unwrap_or_else(ExtensionObject::null))
621 }
622}
623
624impl ExtensionObject {
625 pub fn new<T>(encodable: T) -> ExtensionObject
627 where
628 T: DynEncodable,
629 {
630 Self {
631 body: Some(Box::new(encodable)),
632 }
633 }
634
635 pub fn null() -> ExtensionObject {
637 ExtensionObject { body: None }
638 }
639
640 pub fn is_null(&self) -> bool {
642 self.body.is_none()
643 }
644
645 pub fn binary_type_id(&self) -> ExpandedNodeId {
647 self.body
648 .as_ref()
649 .map(|b| b.binary_type_id())
650 .unwrap_or_else(ExpandedNodeId::null)
651 }
652
653 pub fn object_id(&self) -> Result<ObjectId, ExtensionObjectError> {
656 self.body
657 .as_ref()
658 .ok_or(ExtensionObjectError)?
659 .binary_type_id()
660 .node_id
661 .as_object_id()
662 .map_err(|_| ExtensionObjectError)
663 }
664
665 pub fn from_message<T>(encodable: T) -> ExtensionObject
667 where
668 T: DynEncodable,
669 {
670 Self {
671 body: Some(Box::new(encodable)),
672 }
673 }
674
675 pub fn into_inner_as<T: Send + Sync + 'static>(self) -> Option<Box<T>> {
680 self.body.and_then(|b| b.as_dyn_any().downcast().ok())
681 }
682
683 pub fn inner_as<T: Send + Sync + 'static>(&self) -> Option<&T> {
688 self.body
689 .as_ref()
690 .and_then(|b| b.as_dyn_any_ref().downcast_ref())
691 }
692
693 pub fn type_id(&self) -> Option<TypeId> {
695 self.body.as_ref().map(|b| (**b).type_id())
696 }
697
698 pub fn inner_is<T: 'static>(&self) -> bool {
700 self.type_id() == Some(TypeId::of::<T>())
701 }
702
703 pub fn type_name(&self) -> Option<&'static str> {
705 self.body.as_ref().map(|b| b.type_name())
706 }
707
708 pub fn data_type(&self) -> Option<ExpandedNodeId> {
712 self.body.as_ref().map(|b| b.data_type_id())
713 }
714}
715
716#[macro_export]
735macro_rules! match_extension_object_owned {
736 (_final { $($nom:tt)* }) => {
737 $($nom)*
738 };
739 (_inner $obj:ident, { $($nom:tt)* }, _ => $t:expr $(,)?) => {
740 match_extension_object_owned!(_final {
741 $($nom)*
742 else {
743 $t
744 }
745 })
746 };
747 (_inner $obj:ident, { $($nom:tt)* }, $tok:ident: $typ:ty => $t:expr $(,)?) => {
748 match_extension_object_owned!(_final {
749 $($nom)*
750 else if $obj.inner_is::<$typ>() {
751 let $tok: $typ = *$obj.into_inner_as::<$typ>().unwrap();
752 $t
753 }
754 })
755 };
756 (_inner $obj:ident, { $($nom:tt)* }, $tok:ident: $typ:ty => $t:expr, $($r:tt)*) => {
757 match_extension_object_owned!(_inner $obj, {
758 $($nom)*
759 else if $obj.inner_is::<$typ>() {
760 let $tok: $typ = *$obj.into_inner_as::<$typ>().unwrap();
761 $t
762 }
763 }, $($r)*)
764 };
765 ($obj:ident, $tok:ident: $typ:ty => $t:expr, $($r:tt)*) => {
766 match_extension_object_owned!(_inner $obj, {
767 if $obj.inner_is::<$typ>() {
768 let $tok: $typ = *$obj.into_inner_as::<$typ>().unwrap();
769 $t
770 }
771 }, $($r)*)
772 };
773}
774
775pub use match_extension_object_owned;
776
777#[macro_export]
796macro_rules! match_extension_object {
797 (_final { $($nom:tt)* }) => {
798 $($nom)*
799 };
800 (_inner $obj:ident, { $($nom:tt)* }, _ => $t:expr $(,)?) => {
801 match_extension_object!(_final {
802 $($nom)*
803 else {
804 $t
805 }
806 })
807 };
808 (_inner $obj:ident, { $($nom:tt)* }, $tok:ident: $typ:ty => $t:expr $(,)?) => {
809 match_extension_object!(_final {
810 $($nom)*
811 else if let Some($tok) = $obj.inner_as::<$typ>() {
812 $t
813 }
814 })
815 };
816 (_inner $obj:ident, { $($nom:tt)* }, $tok:ident: $typ:ty => $t:expr, $($r:tt)*) => {
817 match_extension_object!(_inner $obj, {
818 $($nom)*
819 else if let Some($tok) = $obj.inner_as::<$typ>() {
820 $t
821 }
822 }, $($r)*)
823 };
824 ($obj:ident, $tok:ident: $typ:ty => $t:expr, $($r:tt)*) => {
825 match_extension_object!(_inner $obj, {
826 if let Some($tok) = $obj.inner_as::<$typ>() {
827 $t
828 }
829 }, $($r)*)
830 };
831}
832
833pub use match_extension_object;