use std::{
any::{Any, TypeId},
fmt,
io::{Read, Write},
};
use crate::{write_i32, write_u8, Error, ExpandedMessageInfo, ExpandedNodeId, UaNullable};
use super::{
encoding::{BinaryDecodable, BinaryEncodable, EncodingResult},
node_id::NodeId,
ObjectId,
};
#[derive(Debug)]
pub struct ExtensionObjectError;
impl fmt::Display for ExtensionObjectError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "ExtensionObjectError")
}
}
pub trait DynEncodable: Any + Send + Sync + std::fmt::Debug {
fn encode_binary(
&self,
stream: &mut dyn std::io::Write,
ctx: &crate::Context<'_>,
) -> EncodingResult<()>;
#[cfg(feature = "json")]
fn encode_json(
&self,
stream: &mut crate::json::JsonStreamWriter<&mut dyn std::io::Write>,
ctx: &crate::Context<'_>,
) -> EncodingResult<()>;
#[cfg(feature = "xml")]
fn encode_xml(
&self,
stream: &mut crate::xml::XmlStreamWriter<&mut dyn std::io::Write>,
ctx: &crate::Context<'_>,
) -> EncodingResult<()>;
#[cfg(feature = "xml")]
fn xml_tag_name(&self) -> &str;
fn byte_len_dyn(&self, ctx: &crate::Context<'_>) -> usize;
fn binary_type_id(&self) -> ExpandedNodeId;
#[cfg(feature = "json")]
fn json_type_id(&self) -> ExpandedNodeId;
#[cfg(feature = "xml")]
fn xml_type_id(&self) -> ExpandedNodeId;
fn data_type_id(&self) -> ExpandedNodeId;
fn as_dyn_any(self: Box<Self>) -> Box<dyn Any + Send + Sync + 'static>;
fn as_dyn_any_ref(&self) -> &(dyn Any + Send + Sync);
fn clone_box(&self) -> Box<dyn DynEncodable>;
fn dyn_eq(&self, other: &dyn DynEncodable) -> bool;
fn type_name(&self) -> &'static str;
fn override_encoding(&self) -> Option<crate::encoding::BuiltInDataEncoding>;
}
macro_rules! blanket_dyn_encodable {
($bound:tt $(+ $others:tt)*) => {
impl<T> DynEncodable for T
where
T: $bound $(+ $others)* + ExpandedMessageInfo + Any + std::fmt::Debug + Send + Sync + Clone + PartialEq,
{
fn encode_binary(&self, stream: &mut dyn std::io::Write, ctx: &crate::Context<'_>) -> EncodingResult<()> {
BinaryEncodable::encode(self, stream, ctx)
}
#[cfg(feature = "json")]
fn encode_json(
&self,
stream: &mut crate::json::JsonStreamWriter<&mut dyn std::io::Write>,
ctx: &crate::Context<'_>
) -> EncodingResult<()> {
JsonEncodable::encode(self, stream, ctx)
}
#[cfg(feature = "xml")]
fn encode_xml(
&self,
stream: &mut crate::xml::XmlStreamWriter<&mut dyn std::io::Write>,
ctx: &crate::Context<'_>,
) -> EncodingResult<()> {
XmlEncodable::encode(self, stream, ctx)
}
#[cfg(feature = "xml")]
fn xml_tag_name(&self,) -> &str {
self.tag()
}
fn byte_len_dyn(&self, ctx: &crate::Context<'_>,) -> usize {
BinaryEncodable::byte_len(self, ctx)
}
fn binary_type_id(&self) -> ExpandedNodeId {
self.full_type_id()
}
#[cfg(feature = "json")]
fn json_type_id(&self) -> ExpandedNodeId {
self.full_json_type_id()
}
#[cfg(feature = "xml")]
fn xml_type_id(&self) -> ExpandedNodeId {
self.full_xml_type_id()
}
fn data_type_id(&self) -> ExpandedNodeId {
self.full_data_type_id()
}
fn as_dyn_any(self: Box<Self>) -> Box<dyn Any + Send + Sync + 'static> {
self
}
fn as_dyn_any_ref(&self) -> &(dyn Any + Send + Sync) {
self
}
fn clone_box(&self) -> Box<dyn DynEncodable> {
Box::new(self.clone())
}
fn dyn_eq(&self, other: &dyn DynEncodable) -> bool {
if let Some(o) = other.as_dyn_any_ref().downcast_ref::<Self>() {
o == self
} else {
false
}
}
fn type_name(&self) -> &'static str {
std::any::type_name::<Self>()
}
fn override_encoding(&self) -> Option<crate::encoding::BuiltInDataEncoding> {
BinaryEncodable::override_encoding(self)
}
}
};
}
#[cfg(feature = "json")]
use crate::json::JsonEncodable;
#[cfg(feature = "xml")]
use crate::xml::XmlEncodable;
#[cfg(feature = "xml")]
macro_rules! blanket_call_2 {
($($res:tt)*) => {
blanket_dyn_encodable!($($res)* + XmlEncodable);
}
}
#[cfg(not(feature = "xml"))]
macro_rules! blanket_call_2 {
($($res:tt)*) => {
blanket_dyn_encodable!($($res)*);
}
}
#[cfg(feature = "json")]
macro_rules! blanket_call_1 {
($($res:tt)*) => {
blanket_call_2!($($res)* + JsonEncodable);
}
}
#[cfg(not(feature = "json"))]
macro_rules! blanket_call_1 {
($($res:tt)*) => {
blanket_call_2!($($res)*);
}
}
blanket_call_1!(BinaryEncodable);
impl PartialEq for dyn DynEncodable {
fn eq(&self, other: &dyn DynEncodable) -> bool {
self.dyn_eq(other)
}
}
impl std::error::Error for ExtensionObjectError {}
impl UaNullable for ExtensionObject {}
#[cfg(feature = "json")]
mod json {
use std::io::{Cursor, Read};
use crate::{json::*, ByteString, Error, NodeId};
use super::ExtensionObject;
impl JsonEncodable for ExtensionObject {
fn encode(
&self,
stream: &mut JsonStreamWriter<&mut dyn std::io::Write>,
ctx: &crate::Context<'_>,
) -> super::EncodingResult<()> {
let Some(body) = &self.body else {
stream.null_value()?;
return Ok(());
};
let type_id = body.json_type_id();
let id = type_id.try_resolve(ctx.namespaces()).ok_or_else(|| {
Error::encoding(format!("Missing namespace for encoding ID: {type_id}"))
})?;
stream.begin_object()?;
stream.name("UaTypeId")?;
JsonEncodable::encode(id.as_ref(), stream, ctx)?;
if let Some(encoding) = body.override_encoding() {
match encoding {
crate::BuiltInDataEncoding::Binary => {
stream.name("UaEncoding")?;
stream.number_value(1)?;
}
crate::BuiltInDataEncoding::XML => {
stream.name("UaEncoding")?;
stream.number_value(2)?;
}
crate::BuiltInDataEncoding::JSON => (),
}
}
stream.name("UaBody")?;
body.encode_json(stream, ctx)?;
stream.end_object()?;
Ok(())
}
}
impl JsonDecodable for ExtensionObject {
fn decode(
stream: &mut JsonStreamReader<&mut dyn std::io::Read>,
ctx: &Context<'_>,
) -> super::EncodingResult<Self> {
if stream.peek()? == ValueType::Null {
stream.next_null()?;
return Ok(Self::null());
}
let mut type_id: Option<NodeId> = None;
let mut encoding: Option<u32> = None;
let mut raw_body = None;
let mut raw_string_body: Option<String> = None;
let mut body = None;
stream.begin_object()?;
while stream.has_next()? {
match stream.next_name()? {
"UaTypeId" => type_id = Some(JsonDecodable::decode(stream, ctx)?),
"UaEncoding" => encoding = Some(JsonDecodable::decode(stream, ctx)?),
"UaBody" => match stream.peek()? {
ValueType::Object => {
if encoding.is_some_and(|e| e != 0) {
return Err(Error::decoding(format!(
"Invalid encoding, expected 0 or null, got {encoding:?}"
)));
}
if let Some(type_id) = &type_id {
body = Some(ctx.load_from_json(type_id, stream)?);
} else {
raw_body = Some(consume_raw_value(stream)?);
}
}
_ => {
raw_string_body = Some(JsonDecodable::decode(stream, ctx)?);
}
},
_ => stream.skip_value()?,
}
}
stream.end_object()?;
let Some(type_id) = type_id else {
return Err(Error::decoding("Missing type ID in extension object"));
};
let encoding = encoding.unwrap_or_default();
if let Some(body) = body {
Ok(body)
} else if let Some(raw_body) = raw_body {
if encoding != 0 {
return Err(Error::decoding(format!(
"Invalid encoding, expected 0 or null, got {encoding}"
)));
}
let mut cursor = Cursor::new(raw_body);
let mut inner_stream = JsonStreamReader::new(&mut cursor as &mut dyn Read);
Ok(ctx.load_from_json(&type_id, &mut inner_stream)?)
} else if let Some(string_body) = raw_string_body {
if encoding == 1 {
let bytes = ByteString::from_base64_ignore_whitespace(string_body).ok_or_else(
|| Error::decoding("Invalid base64 string is JSON extension object"),
)?;
let Some(raw) = bytes.value else {
return Err(Error::decoding("Missing extension object body"));
};
let len = raw.len();
let mut cursor = Cursor::new(raw);
Ok(ctx.load_from_binary(&type_id, &mut cursor as &mut dyn Read, Some(len))?)
} else if encoding == 2 {
#[cfg(feature = "xml")]
{
let mut cursor = Cursor::new(string_body.as_bytes());
let mut inner_stream =
crate::xml::XmlStreamReader::new(&mut cursor as &mut dyn Read);
if let Some(name) = crate::xml::enter_first_tag(&mut inner_stream)? {
Ok(ctx.load_from_xml(&type_id, &mut inner_stream, &name)?)
} else {
Ok(ExtensionObject::null())
}
}
#[cfg(not(feature = "xml"))]
{
tracing::warn!("XML feature is not enabled, deserializing XML payloads in JSON extension objects is not supported");
Ok(ExtensionObject::null())
}
} else {
Err(Error::decoding(format!("Unsupported extension object encoding, expected 1 or 2 for string, got {encoding}")))
}
} else {
Err(Error::decoding("Missing extension object body"))
}
}
}
}
#[cfg(feature = "xml")]
mod xml {
use opcua_xml::events::Event;
use crate::{xml::*, ByteString, NodeId};
use std::{
io::{Read, Write},
str::from_utf8,
};
use super::ExtensionObject;
impl XmlType for ExtensionObject {
const TAG: &'static str = "ExtensionObject";
}
impl XmlEncodable for ExtensionObject {
fn encode(
&self,
writer: &mut XmlStreamWriter<&mut dyn Write>,
ctx: &crate::Context<'_>,
) -> super::EncodingResult<()> {
let Some(body) = &self.body else {
return Ok(());
};
let type_id = body.xml_type_id();
let id = type_id.try_resolve(ctx.namespaces()).ok_or_else(|| {
Error::encoding(format!("Missing namespace for encoding ID: {type_id}"))
})?;
writer.encode_child("TypeId", id.as_ref(), ctx)?;
writer.write_start("Body")?;
writer.write_start(body.xml_tag_name())?;
body.encode_xml(writer, ctx)?;
writer.write_end(body.xml_tag_name())?;
writer.write_end("Body")?;
Ok(())
}
}
impl XmlDecodable for ExtensionObject {
fn decode(
read: &mut XmlStreamReader<&mut dyn Read>,
ctx: &Context<'_>,
) -> Result<Self, Error> {
let mut type_id = None;
let mut body = None;
read.iter_children(
|key, reader, ctx| {
match key.as_str() {
"TypeId" => type_id = Some(NodeId::decode(reader, ctx)?),
"Body" => {
let top_level = loop {
match reader.next_event()? {
Event::Start(s) => break s,
Event::End(_) => {
return Ok(()); }
_ => (),
}
};
let Some(type_id) = type_id.take() else {
return Err(Error::decoding("Missing type ID in extension object"));
};
if top_level.local_name().as_ref() == b"ByteString" {
let val = ByteString::decode(reader, ctx)?;
if let Some(raw) = val.value {
let len = raw.len();
let mut cursor = std::io::Cursor::new(raw);
body = Some(ctx.load_from_binary(
&type_id,
&mut cursor,
Some(len),
)?);
}
} else {
let local_name = top_level.local_name();
let name = from_utf8(local_name.as_ref())?.to_owned();
body = Some(ctx.load_from_xml(&type_id, reader, &name)?);
}
reader.skip_value()?;
}
_ => reader.skip_value()?,
};
Ok(())
},
ctx,
)?;
Ok(body.unwrap_or_else(ExtensionObject::null))
}
}
}
#[derive(PartialEq, Debug)]
pub struct ExtensionObject {
pub body: Option<Box<dyn DynEncodable>>,
}
impl Clone for ExtensionObject {
fn clone(&self) -> Self {
Self {
body: self.body.as_ref().map(|b| b.clone_box()),
}
}
}
impl Default for ExtensionObject {
fn default() -> Self {
Self::null()
}
}
impl BinaryEncodable for ExtensionObject {
fn byte_len(&self, ctx: &crate::Context<'_>) -> usize {
let type_id = self.binary_type_id();
let id = type_id.try_resolve(ctx.namespaces());
let mut size = id.map(|n| n.byte_len(ctx)).unwrap_or(2usize);
size += match &self.body {
Some(b) => 5 + b.byte_len_dyn(ctx),
None => 1,
};
size
}
fn encode<S: Write + ?Sized>(
&self,
mut stream: &mut S,
ctx: &crate::Context<'_>,
) -> EncodingResult<()> {
let type_id = self.binary_type_id();
let id = type_id.try_resolve(ctx.namespaces());
let Some(id) = id else {
return Err(Error::encoding(format!("Unknown encoding ID: {type_id}")));
};
BinaryEncodable::encode(id.as_ref(), stream, ctx)?;
match &self.body {
Some(b) => {
if matches!(b.override_encoding(), Some(crate::BuiltInDataEncoding::XML)) {
write_u8(stream, 0x2)?;
} else {
write_u8(stream, 0x1)?;
}
write_i32(stream, b.byte_len_dyn(ctx) as i32)?;
b.encode_binary(&mut stream as &mut dyn Write, ctx)
}
None => write_u8(stream, 0x0),
}
}
}
impl BinaryDecodable for ExtensionObject {
fn decode<S: Read + ?Sized>(
mut stream: &mut S,
ctx: &crate::Context<'_>,
) -> EncodingResult<Self> {
let _depth_lock = ctx.options().depth_lock()?;
let node_id = NodeId::decode(stream, ctx)?;
let encoding_type = u8::decode(stream, ctx)?;
let body = match encoding_type {
0x0 => None,
0x1 => {
let size = i32::decode(stream, ctx)?;
if size <= 0 {
None
} else {
Some(ctx.load_from_binary(&node_id, &mut stream, Some(size as usize))?)
}
}
0x2 => {
#[cfg(feature = "xml")]
{
let body = crate::UAString::decode(stream, ctx)?;
let Some(body) = body.value() else {
return Ok(ExtensionObject::null());
};
let mut cursor = std::io::Cursor::new(body.as_bytes());
let mut inner_stream =
crate::xml::XmlStreamReader::new(&mut cursor as &mut dyn Read);
if let Some(name) = crate::xml::enter_first_tag(&mut inner_stream)? {
Some(ctx.load_from_xml(&node_id, &mut inner_stream, &name)?)
} else {
None
}
}
#[cfg(not(feature = "xml"))]
{
let size = i32::decode(stream, ctx)?;
if size > 0 {
let mut bytes = vec![0u8; size as usize];
stream.read_exact(&mut bytes)?;
Some(ExtensionObject::new(crate::type_loader::XmlBody::new(
bytes,
node_id,
"XmlElement".to_owned(),
)))
} else {
None
}
}
}
_ => {
return Err(Error::decoding(format!(
"Invalid encoding type {encoding_type} in stream"
)));
}
};
Ok(body.unwrap_or_else(ExtensionObject::null))
}
}
impl ExtensionObject {
pub fn new<T>(encodable: T) -> ExtensionObject
where
T: DynEncodable,
{
Self {
body: Some(Box::new(encodable)),
}
}
pub fn null() -> ExtensionObject {
ExtensionObject { body: None }
}
pub fn is_null(&self) -> bool {
self.body.is_none()
}
pub fn binary_type_id(&self) -> ExpandedNodeId {
self.body
.as_ref()
.map(|b| b.binary_type_id())
.unwrap_or_else(ExpandedNodeId::null)
}
pub fn object_id(&self) -> Result<ObjectId, ExtensionObjectError> {
self.body
.as_ref()
.ok_or(ExtensionObjectError)?
.binary_type_id()
.node_id
.as_object_id()
.map_err(|_| ExtensionObjectError)
}
pub fn from_message<T>(encodable: T) -> ExtensionObject
where
T: DynEncodable,
{
Self {
body: Some(Box::new(encodable)),
}
}
pub fn into_inner_as<T: Send + Sync + 'static>(self) -> Option<Box<T>> {
self.body.and_then(|b| b.as_dyn_any().downcast().ok())
}
pub fn inner_as<T: Send + Sync + 'static>(&self) -> Option<&T> {
self.body
.as_ref()
.and_then(|b| b.as_dyn_any_ref().downcast_ref())
}
pub fn type_id(&self) -> Option<TypeId> {
self.body.as_ref().map(|b| (**b).type_id())
}
pub fn inner_is<T: 'static>(&self) -> bool {
self.type_id() == Some(TypeId::of::<T>())
}
pub fn type_name(&self) -> Option<&'static str> {
self.body.as_ref().map(|b| b.type_name())
}
pub fn data_type(&self) -> Option<ExpandedNodeId> {
self.body.as_ref().map(|b| b.data_type_id())
}
}
#[macro_export]
macro_rules! match_extension_object_owned {
(_final { $($nom:tt)* }) => {
$($nom)*
};
(_inner $obj:ident, { $($nom:tt)* }, _ => $t:expr $(,)?) => {
match_extension_object_owned!(_final {
$($nom)*
else {
$t
}
})
};
(_inner $obj:ident, { $($nom:tt)* }, $tok:ident: $typ:ty => $t:expr $(,)?) => {
match_extension_object_owned!(_final {
$($nom)*
else if $obj.inner_is::<$typ>() {
let $tok: $typ = *$obj.into_inner_as::<$typ>().unwrap();
$t
}
})
};
(_inner $obj:ident, { $($nom:tt)* }, $tok:ident: $typ:ty => $t:expr, $($r:tt)*) => {
match_extension_object_owned!(_inner $obj, {
$($nom)*
else if $obj.inner_is::<$typ>() {
let $tok: $typ = *$obj.into_inner_as::<$typ>().unwrap();
$t
}
}, $($r)*)
};
($obj:ident, $tok:ident: $typ:ty => $t:expr, $($r:tt)*) => {
match_extension_object_owned!(_inner $obj, {
if $obj.inner_is::<$typ>() {
let $tok: $typ = *$obj.into_inner_as::<$typ>().unwrap();
$t
}
}, $($r)*)
};
}
pub use match_extension_object_owned;
#[macro_export]
macro_rules! match_extension_object {
(_final { $($nom:tt)* }) => {
$($nom)*
};
(_inner $obj:ident, { $($nom:tt)* }, _ => $t:expr $(,)?) => {
match_extension_object!(_final {
$($nom)*
else {
$t
}
})
};
(_inner $obj:ident, { $($nom:tt)* }, $tok:ident: $typ:ty => $t:expr $(,)?) => {
match_extension_object!(_final {
$($nom)*
else if let Some($tok) = $obj.inner_as::<$typ>() {
$t
}
})
};
(_inner $obj:ident, { $($nom:tt)* }, $tok:ident: $typ:ty => $t:expr, $($r:tt)*) => {
match_extension_object!(_inner $obj, {
$($nom)*
else if let Some($tok) = $obj.inner_as::<$typ>() {
$t
}
}, $($r)*)
};
($obj:ident, $tok:ident: $typ:ty => $t:expr, $($r:tt)*) => {
match_extension_object!(_inner $obj, {
if let Some($tok) = $obj.inner_as::<$typ>() {
$t
}
}, $($r)*)
};
}
pub use match_extension_object;