diff --git a/examples/async_call.rs b/examples/async_call.rs index c47d15b0..0539ac74 100644 --- a/examples/async_call.rs +++ b/examples/async_call.rs @@ -223,7 +223,7 @@ fn get_arguments(value: &DataValue) -> anyhow::Result Option<&str> { // TODO: Add methods for these checks? - (reference.node_class() == &ua::NodeClass::VARIABLE + (reference.node_class() == ua::NodeClass::VARIABLE && reference.reference_type_id().as_ns0() == Some(UA_NS0ID_HASPROPERTY) && reference.type_definition().node_id().as_ns0() == Some(UA_NS0ID_PROPERTYTYPE)) .then(|| { diff --git a/src/async_client.rs b/src/async_client.rs index 8106c2c2..b5b49497 100644 --- a/src/async_client.rs +++ b/src/async_client.rs @@ -121,7 +121,7 @@ impl AsyncClient { self.client.as_ptr().cast_mut(), ) }); - if let Err(error) = Error::verify_good(&status_code) { + if let Err(error) = Error::verify_good(status_code) { log::warn!("Error while disconnecting client: {error}"); } @@ -251,9 +251,9 @@ impl AsyncClient { .with_timestamps_to_return(&ua::TimestampsToReturn::BOTH) .with_nodes_to_read(&nodes_to_read); - let response = self.service_request(request).await?; + let mut response = self.service_request(request).await?; - let Some(mut results) = response.results() else { + let Some(mut results) = response.take_results() else { return Err(Error::internal("read should return results")); }; @@ -290,11 +290,11 @@ impl AsyncClient { return Err(Error::internal("write should return results")); }; - let Some(result) = results.as_slice().first() else { + let Some(result) = results.first() else { return Err(Error::internal("write should return a result")); }; - Error::verify_good(result)?; + Error::verify_good(*result)?; Ok(()) } @@ -317,19 +317,19 @@ impl AsyncClient { .with_method_id(method_id) .with_input_arguments(input_arguments)]); - let response = self.service_request(request).await?; + let mut response = self.service_request(request).await?; - let Some(results) = response.results() else { + let Some(mut results) = response.take_results() else { return Err(Error::internal("call should return results")); }; - let Some(result) = results.as_slice().first() else { + let Some(result) = results.as_slice_mut().first_mut() else { return Err(Error::internal("call should return a result")); }; - Error::verify_good(&result.status_code())?; + Error::verify_good(result.status_code())?; - let output_arguments = if let Some(output_arguments) = result.output_arguments() { + let output_arguments = if let Some(output_arguments) = result.take_output_arguments() { output_arguments.into_vec() } else { log::debug!("Calling {method_id} returned unset output arguments, assuming none exist"); @@ -364,9 +364,9 @@ impl AsyncClient { let request = ua::BrowseRequest::init().with_nodes_to_browse(slice::from_ref(browse_description)); - let response = self.service_request(request).await?; + let mut response = self.service_request(request).await?; - let Some(results) = response.results() else { + let Some(results) = response.take_results() else { return Err(Error::internal("browse should return results")); }; @@ -547,7 +547,7 @@ impl AsyncClient { // The request itself fails when the client is not connected (or the secure session has not been // established). In all other cases, `open62541` processes the request first and then may reject // it only through the response when executing our callback above. - Error::verify_good(&status_code).inspect_err(|_| { + Error::verify_good(status_code).inspect_err(|_| { log::warn!("{} failed: {status_code:?}", R::type_name()); })?; @@ -827,9 +827,9 @@ fn background_task(client: &ua::Client, state: &AtomicU8) { let time_taken = start_of_iteration.elapsed(); log::trace!("Iterate run took {time_taken:?}"); - if let Err(error) = Error::verify_good(&connect_status) { + if let Err(error) = Error::verify_good(connect_status) { let not_connected = matches!( - connect_status.clone().into_raw(), + connect_status.code(), UA_STATUSCODE_BADDISCONNECT | UA_STATUSCODE_BADCONNECTIONCLOSED | UA_STATUSCODE_BADCONNECTIONREJECTED @@ -870,10 +870,10 @@ fn background_task(client: &ua::Client, state: &AtomicU8) { fn to_browse_result(result: &ua::BrowseResult, node_id: Option<&ua::NodeId>) -> BrowseResult { // Make sure to verify the inner status code inside `BrowseResult`. The service request finishes // without error, even when browsing the node has failed. - Error::verify_good(&result.status_code())?; + Error::verify_good(result.status_code())?; let references = if let Some(references) = result.references() { - references.into_vec() + references.to_vec() } else { // When no references exist, some OPC UA servers do not return an empty references array but // an invalid (unset) one instead, e.g. Siemens SIMOTION. We treat it as an empty array, and diff --git a/src/async_subscription.rs b/src/async_subscription.rs index c78deaea..f7a73fc7 100644 --- a/src/async_subscription.rs +++ b/src/async_subscription.rs @@ -319,7 +319,7 @@ async fn create_subscription( ) } }); - Error::verify_good(&status_code)?; + Error::verify_good(status_code)?; // PANIC: When `callback` is called (which owns `tx`), we always call `tx.send()`. So the sender // is only dropped after placing a value into the channel and `rx.await` always finds this value @@ -342,7 +342,7 @@ fn delete_subscriptions(client: &ua::Client, request: &ua::DeleteSubscriptionsRe let response = unsafe { response.as_ref() }.expect("response should be set"); let status_code = ua::StatusCode::new(response.responseHeader.serviceResult); - if let Err(error) = Error::verify_good(&status_code) { + if let Err(error) = Error::verify_good(status_code) { log::warn!("Error in response when deleting subscriptions: {error}"); } } @@ -365,7 +365,7 @@ fn delete_subscriptions(client: &ua::Client, request: &ua::DeleteSubscriptionsRe ) } }); - if let Err(error) = Error::verify_good(&status_code) { + if let Err(error) = Error::verify_good(status_code) { log::warn!("Error in request when deleting subscriptions: {error}"); } } diff --git a/src/client.rs b/src/client.rs index d58b9382..0a590c54 100644 --- a/src/client.rs +++ b/src/client.rs @@ -291,7 +291,7 @@ impl ClientBuilder { ); result }); - Error::verify_good(&status_code)?; + Error::verify_good(status_code)?; let Some(endpoint_descriptions) = endpoint_descriptions else { return Err(Error::internal("expected array of endpoint descriptions")); @@ -400,7 +400,7 @@ impl Client { // SAFETY: The method does not take ownership of `client`. UA_Client_connect(self.0.as_mut_ptr(), endpoint_url.as_ptr()) }); - Error::verify_good(&status_code) + Error::verify_good(status_code) } /// Disconnects from endpoint. diff --git a/src/data_type.rs b/src/data_type.rs index d357deb1..2dd87226 100644 --- a/src/data_type.rs +++ b/src/data_type.rs @@ -100,29 +100,13 @@ pub unsafe trait DataType: Debug + Clone { /// Creates wrapper reference from value. #[must_use] fn raw_ref(src: &Self::Inner) -> &Self { - let src: *const Self::Inner = src; - // This transmutes between the inner type and `Self` through `cast()`. Types that implement - // `DataType` guarantee that we can transmute between them and their inner type, so this is - // okay. - let ptr = src.cast::(); - // SAFETY: `DataType` guarantees that we can transmute between `Self` and the inner type. - let ptr = unsafe { ptr.as_ref() }; - // SAFETY: Pointer is valid (non-zero) because it comes from a reference. - unsafe { ptr.unwrap_unchecked() } + data_type_ref(src) } /// Creates mutable wrapper reference from value. #[must_use] fn raw_mut(src: &mut Self::Inner) -> &mut Self { - let src: *mut Self::Inner = src; - // This transmutes between the inner type and `Self` through `cast()`. Types that implement - // `DataType` guarantee that we can transmute between them and their inner type, so this is - // okay. - let ptr = src.cast::(); - // SAFETY: `DataType` guarantees that we can transmute between `Self` and the inner type. - let ptr = unsafe { ptr.as_mut() }; - // SAFETY: Pointer is valid (non-zero) because it comes from a reference. - unsafe { ptr.unwrap_unchecked() } + data_type_ref_mut(src) } /// Creates wrapper by cloning value from `src`. @@ -345,33 +329,63 @@ pub unsafe trait DataType: Debug + Clone { } } -/// Defines wrapper for OPC UA data type from [`open62541_sys`]. +pub(crate) const fn data_type_ref(value: &::Inner) -> &T { + let src: *const T::Inner = value; + // This transmutes between the inner type and `Self` through `cast()`. Types that implement + // `DataType` guarantee that we can transmute between them and their inner type, so this is + // okay. + let ptr = src.cast::(); + // SAFETY: `DataType` guarantees that we can transmute between `Self` and the inner type. + let ptr = unsafe { ptr.as_ref() }; + // SAFETY: Pointer is valid (non-zero) because it comes from a reference. + unsafe { ptr.unwrap_unchecked() } +} + +pub(crate) const fn data_type_ref_mut(value: &mut ::Inner) -> &mut T { + let src: *mut T::Inner = value; + // This transmutes between the inner type and `Self` through `cast()`. Types that implement + // `DataType` guarantee that we can transmute between them and their inner type, so this is + // okay. + let ptr = src.cast::(); + // SAFETY: `DataType` guarantees that we can transmute between `Self` and the inner type. + let ptr = unsafe { ptr.as_mut() }; + // SAFETY: Pointer is valid (non-zero) because it comes from a reference. + unsafe { ptr.unwrap_unchecked() } +} + +/// Defines the wrappers for OPC UA data types from [`open62541_sys`], as well as their conversions +/// to/from those wrapped types. /// -/// This provides the basic interface to convert from and back into the [`open62541_sys`] types. Use -/// another `impl` block to add additional methods to each type if necessary. -macro_rules! data_type { - ($name:ident) => { +/// Implements basic traits by mapping them to [`open62541_sys`] calls. If the type holds any +/// dynamically-allocated resources, the type should use the [`data_type!`] macro instead. +macro_rules! data_type_baseline { + ($(#[$derives:meta])* $name:ident) => { paste::paste! { - $crate::data_type!($name, [], []); + $crate::data_type::data_type_baseline!($(#[$derives])* $name, [], []); } }; - ($name:ident, $inner:ident) => { + ($(#[$derives:meta])* $name:ident, $inner:ident) => { paste::paste! { - $crate::data_type!($name, [], []); + $crate::data_type::data_type_baseline!($(#[$derives])* $name, [], []); } }; - ($name:ident, $inner:ident, $index:ident) => { - /// Wrapper for - #[doc = concat!("[`", stringify!($inner), "`](open62541_sys::", stringify!($inner), ")")] - /// from [`open62541_sys`]. + ($(#[$derives:meta])* $name:ident, #[uses_inner] $inner:ident) => { + paste::paste! { + $crate::data_type::data_type_baseline!($(#[$derives])* $name, [], []); + } + }; + + ($(#[$derives:meta])* $name:ident, $inner:ident, $index:ident) => { + #[doc = concat!("Wrapper for [`", stringify!($inner), "`](open62541_sys::", stringify!($inner), ") from [`open62541_sys`].")] /// - /// This owns the wrapped data. When the wrapper is dropped, the inner value is cleaned up - /// with [`UA_clear()`] to release dynamically allocated memory held by the value. + /// This owns the wrapped data. If the wrapped value holds any allocations that need to be + /// released, [`UA_clear()`] will be called on drop. /// /// [`UA_clear()`]: open62541_sys::UA_clear #[repr(transparent)] + $(#[$derives])* pub struct $name( /// Inner value. open62541_sys::$inner, @@ -388,19 +402,6 @@ macro_rules! data_type { // types in this case which is only implemented for owned values. unsafe impl Sync for $name {} - impl Drop for $name { - fn drop(&mut self) { - // `UA_clear()` resets the data structure, freeing any dynamically allocated memory - // in it, no matter how deeply nested. - unsafe { - open62541_sys::UA_clear( - (&raw mut self.0).cast::(), - ::data_type(), - ) - } - } - } - // SAFETY: We can transmute between our wrapper type and the inner type. This is ensured by // using `#[repr(transparent)]` on the type definition. unsafe impl $crate::DataType for $name { @@ -426,12 +427,6 @@ macro_rules! data_type { } } - impl Clone for $name { - fn clone(&self) -> Self { - ::clone_raw(&self.0) - } - } - impl std::fmt::Debug for $name { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { let output = ::print(self); @@ -473,8 +468,161 @@ macro_rules! data_type { }; } +pub(crate) use data_type_baseline; + +/// Safe catch-all for any UA data type. Needs to be implemented for types that have resource +/// allocations, but can and should be swapped out for [`data_type_baseline`] for primitive, +/// trivially-copyable where not calling [`UA_clear`] remains sound. +macro_rules! data_type { + ($name:ident $(, $($tok:tt)+)?) => { + $crate::data_type::data_type_baseline!($name $(, $($tok)+)?); + + impl Clone for $name { + fn clone(&self) -> Self { + ::clone_raw(&self.0) + } + } + + // only + impl Drop for $name { + fn drop(&mut self) { + // `UA_clear()` resets the data structure, freeing any dynamically allocated memory + // in it, no matter how deeply nested. + unsafe { + open62541_sys::UA_clear( + (&raw mut self.0).cast::(), + ::data_type(), + ) + } + } + } + }; +} + pub(crate) use data_type; +/// TT muncher that takes a struct-declaration-like syntax and expands into auto-implemented +/// memory-sound accessor methods for fields. +/// +/// # Example +/// ```ignore +/// # use crate::ua; +/// # crate::data_type!(ReadRequest); +/// crate::member_accessors!(ReadRequest { +/// #[enum] +/// timestampsToReturn: ua::TimestampsToReturn, +/// nodesToRead: [ua::ReadValueId], +/// requestHeader: &mut ua::RequestHeader +/// }); +/// +/// impl ReadRequest { +/// pub const fn timestamps_to_return(&self) -> ua::TimestampsToReturn { /* ... */ } +/// pub fn nodes_to_read(&self) -> Option<&[ua::ReadValueId]> { /* ... */ } +/// pub fn take_nodes_to_read(&self) -> Option> { /* ... */ } +/// pub const fn request_header(&self) -> &ua::RequestHeader { /* ... */ } +/// pub const fn request_header_mut(&mut self) -> &mut ua::RequestHeader { /* ... */ } +/// } +/// ``` +macro_rules! member_accessors { + // munch munch munch + ($t:ty { $($rest:tt)+ }) => { + impl $t { + $crate::member_accessors!($($rest)+); + } + }; + + // create array accessors, with convenience method to move items from arrays contained in the + // type + ($raw_name:ident: [$arr_t:ty] $(, $($rest:tt)+)?) => { + paste::paste! { + #[must_use] + pub fn [<$raw_name:snake>](&self) -> Option<&[$arr_t]> { + unsafe { $crate::ua::Array::slice_from_raw_parts(self.0.[<$raw_name Size>], self.0.$raw_name) } + } + + #[must_use] + pub fn [](&mut self) -> Option> { + unsafe { $crate::ua::Array::move_from_raw_parts(&mut self.0.[<$raw_name Size>], &mut self.0.$raw_name) } + } + } + + $($crate::member_accessors!($($rest)+);)? + }; + + // document the presence of the member on the C type, optionally document the trivial cast + // override like `#[skip(uses = Foo::bar)]`. Asserts that the field is present on the native + // struct, and if an accessor override is specified, the presence of the fn is asserted. + (#[skip$((uses = $fn_name:path))?] $raw_name:ident: $field_t:ty $(, $($rest:tt)+)?) => { + paste::paste! { + const [<__ $raw_name:upper _ASSERT>]: () = { + $(let _ = $fn_name;)? + let _ = ::std::mem::offset_of!(::Inner, $raw_name); + }; + } + + $($crate::member_accessors!($($rest)+);)? + }; + + // create an immutable reference accessor method for the field + ($raw_name:ident: &$wrapper_t:ty $(, $($rest:tt)+)?) => { + paste::paste! { + pub const fn [<$raw_name:snake>](&self) -> &$wrapper_t { + $crate::data_type::data_type_ref(&self.0.$raw_name) + } + } + + $($crate::member_accessors!($($rest)+);)? + }; + + // create both an immutable _and_ mutable reference accessor for the field + ($raw_name:ident: &mut $wrapper_t:ty $(, $($rest:tt)+)?) => { + $crate::member_accessors!($raw_name: &$wrapper_t); + + paste::paste! { + pub const fn [<$raw_name:snake _mut>](&mut self) -> &mut $wrapper_t { + $crate::data_type::data_type_ref_mut(&mut self.0.$raw_name) + } + } + + $($crate::member_accessors!($($rest)+);)? + }; + + // create a by-value copy of `$wrapper_t` with an implicit copy of its inner type + (#[from_inner] $raw_name:ident: $wrapper_t:ty $(, $($rest:tt)+)?) => { + paste::paste! { + pub const fn [<$raw_name:snake>](&self) -> $wrapper_t { + *$crate::data_type::data_type_ref(&self.0.$raw_name) + } + } + + $($crate::member_accessors!($($rest)+);)? + }; + + // Copy a bindgen enum through its specific raw type, checking that the field and wrapper match. + (#[enum] $raw_name:ident: $wrapper_t:ty $(, $($rest:tt)+)?) => { + paste::paste! { + pub const fn [<$raw_name:snake>](&self) -> $wrapper_t { + $wrapper_t::from_ua(&self.0.$raw_name) + } + } + + $($crate::member_accessors!($($rest)+);)? + }; + + // create a by-value copy of the field that doesn't have a wrapper + ($raw_name:ident: $field_t:ty $(, $($rest:tt)+)?) => { + paste::paste! { + pub const fn [<$raw_name:snake>](&self) -> $field_t { + self.0.$raw_name + } + } + + $($crate::member_accessors!($($rest)+);)? + }; +} + +pub(crate) use member_accessors; + /// Defines known enum variants for wrapper. /// /// This allows implementing data types that wrap an enum type from [`open62541_sys`]. This provides @@ -504,16 +652,32 @@ macro_rules! enum_variants { #[expect(clippy::allow_attributes, reason = "not required for all variants")] #[allow(dead_code, reason = "not used for all variants")] - pub(crate) fn from_u32(value: u32) -> Self { - // This cast is necessary on Windows builds with inner type `i32`. - Self(open62541_sys::$inner(value.try_into().expect("should convert from u32"))) + pub(crate) const fn from_ua(value: &open62541_sys::$inner) -> Self { + Self(open62541_sys::$inner(value.0)) + } + + #[expect(clippy::allow_attributes, reason = "not required for all variants")] + #[allow(dead_code, reason = "not used for all variants")] + pub(crate) const fn from_u32(value: u32) -> Self { + // positive i32s are bit-compatible with u32. + // all we need to check here is if the value exceeds i32::MAX + #[cfg(target_os = "windows")] + let value = if value > i32::MAX as u32 { + panic!("u32 should've been convertible to i32, but value exceeded i32::MAX") + } else { + value as i32 + }; + + Self(open62541_sys::$inner(value)) } - pub(crate) fn as_u32(&self) -> u32 { - // This cast is necessary on Windows builds with inner type `i32`. - #[expect(clippy::allow_attributes, reason = "dynamic condition")] - #[allow(clippy::useless_conversion, reason = "bindgen i32")] - u32::try_from((self.0).0).expect("should convert to u32") + pub(crate) const fn as_u32(&self) -> u32 { + let value = self.0.0; + + #[cfg(target_os = "windows")] + let value = value as u32; + + value } } @@ -559,6 +723,136 @@ macro_rules! bitmask_ops { pub(crate) use bitmask_ops; +macro_rules! enum_bitmask { + // need this to disambiguate literal `enum` + (enum $($rest:tt)+) => { + $crate::data_type::enum_bitmask!($($rest)+); + }; + + ($name:ty : $inner_t:ty { $($(#[doc = $doc:literal])* $variant:ident),+ $(,)?}) => { + paste::paste! { + impl std::ops::BitAnd for $name { + type Output = Self; + + fn bitand(self, rhs: Self) -> Self { + Self(self.0 & rhs.0) + } + } + + impl std::ops::BitOr for $name { + type Output = Self; + + fn bitor(self, rhs: Self) -> Self { + Self(self.0 | rhs.0) + } + } + + impl std::ops::Not for $name { + type Output = Self; + + fn not(self) -> Self { + Self(!self.0) + } + } + + #[expect(clippy::allow_attributes, reason = "some implementers may be u32")] + impl $name { + #[allow(unused, reason = "not used by all implementers")] + #[must_use] + pub(crate) const fn [](self) -> $inner_t { + self.0 + } + + #[allow(unused, reason = "not used by all implementers")] + #[must_use] + pub(crate) const fn [](inner: $inner_t) -> Self { + Self(inner) + } + + #[doc = "Indicates that no fields are set."] + #[allow(trivial_numeric_casts, reason = "some implementers may be u32")] + #[must_use] + pub const fn none(self) -> bool { + self.0 == Self::NONE.0 + } + + #[allow(trivial_numeric_casts, reason = "some implementers may be u32")] + #[allow(clippy::cast_possible_truncation, clippy::as_conversions, reason = "variant `" UA_ $name:upper _NONE "` must be representable as `" $inner_t "`")] + pub const NONE: Self = Self(::open62541_sys::[] as $inner_t); + + #[allow(trivial_numeric_casts, reason = "some implementers may be u32")] + #[allow(clippy::cast_possible_truncation, clippy::as_conversions, reason = "variant `" UA_ $name:upper _NONE "` must be representable as `u32`")] + pub const NONE_U32: u32 = Self::NONE.0 as u32; + + #[doc = "Determines if `self`'s bit pattern contains the exact bit pattern of `other`."] + #[must_use] + pub const fn contains(self, other: Self) -> bool { + self.0 & other.0 == other.0 + } + + #[doc = "Yields the bitwise-or of `self` with `rhs`."] + #[must_use] + pub const fn or(self, rhs: Self) -> Self { + Self(self.0 | rhs.0) + } + + #[doc = "Yields the bitwise-and of `self` with `rhs`."] + #[must_use] + pub const fn and(self, rhs: Self) -> Self { + Self(self.0 & rhs.0) + } + + #[doc = "Yields the bitwise-not of `self`."] + #[must_use] + pub const fn not(self) -> Self { + Self(!self.0) + } + + $( + #[allow(trivial_numeric_casts, reason = "some implementers may be u32")] + #[allow(clippy::cast_possible_truncation, clippy::as_conversions, reason = "variant `" UA_ $name:upper _ $variant:upper "` must be representable as `" $inner_t "`")] + pub const [<$variant:upper>]: Self = Self(::open62541_sys::[] as $inner_t); + + #[allow(trivial_numeric_casts, reason = "some implementers may be u32")] + #[allow(clippy::cast_possible_truncation, clippy::as_conversions, reason = "variant `" UA_ $name:upper _ $variant:upper "` must be representable as `u32`")] + pub const [<$variant:upper _U32>]: u32 = Self::[<$variant:upper>].0 as u32; + + #[must_use] + $( + #[doc = $doc] + )* + pub const fn [<$variant:snake>](self) -> bool { + self.contains(Self::[<$variant:upper>]) + } + + #[must_use] + pub const fn [](self, value: bool) -> Self { + if value { + self.or(Self::[<$variant:upper>]) + } else { + self.and(Self::not(Self::[<$variant:upper>])) + } + } + )+ + } + + #[cfg(test)] + #[expect(clippy::allow_attributes, reason = "not all variants are u32")] + mod [<$name:snake _bitmask_variant_assertions>] { + $( + #[test] + fn []() { + #[allow(clippy::cast_possible_truncation, clippy::as_conversions, trivial_numeric_casts, reason = "some implementers may be u32")] + $inner_t::try_from(::open62541_sys::[] as $inner_t).expect(&("variant ".to_string() + stringify!([]) + " must be representable as " + stringify!($inner_t))); + } + )+ + } + } + } +} + +pub(crate) use enum_bitmask; + #[cfg(test)] mod tests { use std::thread; diff --git a/src/error.rs b/src/error.rs index d9e4fd82..1d8372c5 100644 --- a/src/error.rs +++ b/src/error.rs @@ -34,11 +34,11 @@ impl Error { Self::Server(status_code) } - pub(crate) fn verify_good(status_code: &ua::StatusCode) -> Result<()> { + pub(crate) fn verify_good(status_code: ua::StatusCode) -> Result<()> { if status_code.is_good() { Ok(()) } else { - Err(Self::new(status_code.clone())) + Err(Self::new(status_code)) } } @@ -47,10 +47,10 @@ impl Error { /// This returns the original status code except for internal errors where the generic status /// code [`ua::StatusCode::BAD`] is returned instead. #[must_use] - pub fn status_code(&self) -> ua::StatusCode { + pub const fn status_code(&self) -> ua::StatusCode { match self { // TODO: Avoid clone and make `ua::StatusCode` derive `Copy`. - Error::Server(status_code) => status_code.clone(), + Error::Server(status_code) => *status_code, Error::Internal(_) => ua::StatusCode::BAD, } } diff --git a/src/lib.rs b/src/lib.rs index 2d45e270..481bce7b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -263,7 +263,9 @@ pub use self::{ use self::{ callback_fn::CallbackMut, client::ClientContext, - data_type::{bitmask_ops, data_type, enum_variants}, + data_type::{ + bitmask_ops, data_type, data_type_baseline, enum_bitmask, enum_variants, member_accessors, + }, monitored_item::create_monitored_items_callback, ua::UaIterExt, value::{ArrayValue, NonScalarValue}, diff --git a/src/monitored_item.rs b/src/monitored_item.rs index 6644dfaf..03641390 100644 --- a/src/monitored_item.rs +++ b/src/monitored_item.rs @@ -330,10 +330,10 @@ where { let request = request_builder.build(subscription_id); let result_count = request.items_to_create().map_or(0, <[_]>::len); - let response = + let mut response = create_monitored_items::call::(client, &request, create_value_callback_fn).await?; - let Some(mut results) = response.into_results() else { + let Some(mut results) = response.take_results() else { return Err(crate::Error::internal("expected monitoring item results")); }; @@ -363,7 +363,7 @@ where let results = results .drain_all() .filter_map(|result| { - if let Err(err) = crate::Error::verify_good(&result.status_code()) { + if let Err(err) = crate::Error::verify_good(result.status_code()) { return Some(Err(err)); } diff --git a/src/monitored_item/create_monitored_items.rs b/src/monitored_item/create_monitored_items.rs index 228cb318..34d86da3 100644 --- a/src/monitored_item/create_monitored_items.rs +++ b/src/monitored_item/create_monitored_items.rs @@ -111,7 +111,8 @@ where ) } }); - Error::verify_good(&status_code)?; + + Error::verify_good(status_code)?; // PANIC: When `callback` is called (which owns `tx`), we always call `tx.send()`. So the sender // is only dropped after placing a value into the channel and `rx.await` always finds this value diff --git a/src/monitored_item/delete_monitored_items.rs b/src/monitored_item/delete_monitored_items.rs index 6b75285e..44c94e67 100644 --- a/src/monitored_item/delete_monitored_items.rs +++ b/src/monitored_item/delete_monitored_items.rs @@ -43,7 +43,7 @@ pub(super) async fn call( ) } }); - Error::verify_good(&status_code)?; + Error::verify_good(status_code)?; // PANIC: When `callback` is called (which owns `tx`), we always call `tx.send()`. So the sender // is only dropped after placing a value into the channel and `rx.await` always finds this value @@ -104,7 +104,7 @@ pub(super) fn send_request( ) } }); - Error::verify_good(&status_code)?; + Error::verify_good(status_code)?; Ok(()) } @@ -122,7 +122,7 @@ unsafe extern "C" fn callback_log_response_c( let response = unsafe { response.as_ref() }.expect("response should be set"); let status_code = ua::StatusCode::new(response.responseHeader.serviceResult); - if let Err(error) = Error::verify_good(&status_code) { + if let Err(error) = Error::verify_good(status_code) { log::warn!("Error in response when deleting monitored items: {error}"); } } diff --git a/src/server.rs b/src/server.rs index 400644b1..0df26421 100644 --- a/src/server.rs +++ b/src/server.rs @@ -189,6 +189,7 @@ impl ServerBuilder { .map(|server_url| ua::String::new(server_url).unwrap()) .collect_ua_array() .move_into_raw(&mut config.serverUrlsSize, &mut config.serverUrls); + self } @@ -688,7 +689,7 @@ impl Server { out_new_node_id.as_mut_ptr(), ) }); - Error::verify_good(&status_code)?; + Error::verify_good(status_code)?; let status_code = ua::StatusCode::new(unsafe { UA_Server_addNode_finish( @@ -698,7 +699,7 @@ impl Server { DataType::to_raw_copy(&out_new_node_id), ) }); - Error::verify_good(&status_code)?; + Error::verify_good(status_code)?; Ok(out_new_node_id) } @@ -747,7 +748,7 @@ impl Server { out_new_node_id.as_mut_ptr(), ) }); - Error::verify_good(&status_code)?; + Error::verify_good(status_code)?; let status_code = ua::StatusCode::new(unsafe { UA_Server_addNode_finish( @@ -757,7 +758,7 @@ impl Server { DataType::to_raw_copy(&out_new_node_id), ) }); - Error::verify_good(&status_code)?; + Error::verify_good(status_code)?; Ok(out_new_node_id) } @@ -806,7 +807,7 @@ impl Server { out_new_node_id.as_mut_ptr(), ) }); - Error::verify_good(&status_code)?; + Error::verify_good(status_code)?; let status_code = ua::StatusCode::new(unsafe { UA_Server_addNode_finish( @@ -816,7 +817,7 @@ impl Server { DataType::to_raw_copy(&out_new_node_id), ) }); - Error::verify_good(&status_code)?; + Error::verify_good(status_code)?; Ok(out_new_node_id) } @@ -875,7 +876,7 @@ impl Server { // In case of an error, the node context has already been freed by the destructor. We must // not consume it ourselves (to avoid double-freeing). In case of success, the node context // will be consumed when the node is eventually deleted (`UA_ServerConfig::nodeLifecycle`). - Error::verify_good(&status_code)?; + Error::verify_good(status_code)?; Ok(out_new_node_id) } @@ -953,7 +954,7 @@ impl Server { out_new_node_id.as_mut_ptr(), ) }); - Error::verify_good(&status_code)?; + Error::verify_good(status_code)?; Ok(( out_new_node_id, @@ -1005,7 +1006,7 @@ impl Server { out_new_node_id.as_mut_ptr(), ) }); - Error::verify_good(&status_code)?; + Error::verify_good(status_code)?; Ok(out_new_node_id) } @@ -1029,7 +1030,7 @@ impl Server { true, ) }); - Error::verify_good(&status_code) + Error::verify_good(status_code) } /// Adds a reference from one node to another. @@ -1111,7 +1112,7 @@ impl Server { is_forward, ) }); - Error::verify_good(&status_code) + Error::verify_good(status_code) } /// Deletes a reference between two nodes. @@ -1141,7 +1142,7 @@ impl Server { delete_bidirectional, ) }); - Error::verify_good(&status_code) + Error::verify_good(status_code) } /// Creates an event. @@ -1179,7 +1180,7 @@ impl Server { out_event_id.as_mut_ptr(), ) }); - Error::verify_good(&status_code)?; + Error::verify_good(status_code)?; let Some(event_id) = ua::EventId::new(out_event_id) else { return Err(Error::internal( "UA_Server_createEvent should return event ID", @@ -1225,8 +1226,8 @@ impl Server { browse_description.as_ptr(), )) }; - Error::verify_good(&result.status_code())?; - to_browse_result(&result) + Error::verify_good(result.status_code())?; + to_browse_result(result) } /// Browses continuation point for more references. @@ -1249,8 +1250,8 @@ impl Server { continuation_point.as_byte_string().as_ptr(), )) }; - Error::verify_good(&result.status_code())?; - to_browse_result(&result) + Error::verify_good(result.status_code())?; + to_browse_result(result) } /// Browses nodes recursively. @@ -1327,7 +1328,7 @@ impl Server { &raw mut result_ptr, ) }); - Error::verify_good(&status_code)?; + Error::verify_good(status_code)?; let Some(result) = ua::Array::from_raw_parts(result_size, result_ptr) else { return Err(Error::internal("recursive browse should return result")); }; @@ -1394,7 +1395,7 @@ impl Server { // until the end of this function. let (browse_path_size, browse_path_ptr) = unsafe { ua::Array::raw_parts_from_slice(browse_path) }; - let result = unsafe { + let mut result = unsafe { ua::BrowsePathResult::from_raw(UA_Server_browseSimplifiedBrowsePath( // SAFETY: Cast to `mut` pointer, function is marked `UA_THREADSAFE`. self.as_mut_ptr(), @@ -1405,11 +1406,11 @@ impl Server { browse_path_ptr, )) }; - Error::verify_good(&result.status_code())?; - let targets = result - .targets() - .ok_or(Error::internal("browse should return targets"))?; - Ok(targets) + Error::verify_good(result.status_code())?; + + result + .take_targets() + .ok_or(Error::internal("browse should return targets")) } /// Translates browse path to node IDs. @@ -1464,18 +1465,18 @@ impl Server { &self, browse_path: &ua::BrowsePath, ) -> Result> { - let result = unsafe { + let mut result = unsafe { ua::BrowsePathResult::from_raw(UA_Server_translateBrowsePathToNodeIds( // SAFETY: Cast to `mut` pointer, function is marked `UA_THREADSAFE`. self.as_mut_ptr(), browse_path.as_ptr(), )) }; - Error::verify_good(&result.status_code())?; - let targets = result - .targets() - .ok_or(Error::internal("translation should return targets"))?; - Ok(targets) + Error::verify_good(result.status_code())?; + + result + .take_targets() + .ok_or(Error::internal("translation should return targets")) } /// Reads node attribute. @@ -1570,7 +1571,7 @@ impl Server { DataType::to_raw_copy(value), ) }); - Error::verify_good(&status_code) + Error::verify_good(status_code) } /// Writes a `DataValue` to a node. @@ -1589,7 +1590,7 @@ impl Server { DataType::to_raw_copy(value), ) }); - Error::verify_good(&status_code) + Error::verify_good(status_code) } /// Reads object property. @@ -1660,7 +1661,7 @@ impl Server { value.as_mut_ptr(), ) }); - Error::verify_good(&status_code)?; + Error::verify_good(status_code)?; Ok(value) } @@ -1727,7 +1728,7 @@ impl Server { DataType::to_raw_copy(value), ) }); - Error::verify_good(&status_code) + Error::verify_good(status_code) } /// Gets server statistics. @@ -1842,7 +1843,7 @@ impl ServerRunner { server.as_ptr().cast_mut(), ) }); - match Error::verify_good(&status_code) { + match Error::verify_good(status_code) { Ok(()) => state_guard.set_state(RunnerStateInner::Shutdown), Err(err) => { state_guard.set_state(RunnerStateInner::Error); @@ -1880,7 +1881,7 @@ impl ServerRunner { server.as_ptr().cast_mut(), ) }); - match Error::verify_good(&status_code) { + match Error::verify_good(status_code) { Ok(()) => state_guard.set_state(RunnerStateInner::Startup), Err(err) => { state_guard.set_state(RunnerStateInner::Error); @@ -1924,7 +1925,7 @@ impl ServerRunner { server.as_ptr().cast_mut(), ) }); - match Error::verify_good(&status_code) { + match Error::verify_good(status_code) { Ok(()) => state_guard.set_state(RunnerStateInner::Shutdown), Err(err) => { state_guard.set_state(RunnerStateInner::Error); @@ -1952,12 +1953,12 @@ impl Drop for ServerRunner { } /// Converts [`ua::BrowseResult`] to our public result type. -fn to_browse_result(result: &ua::BrowseResult) -> BrowseResult { +fn to_browse_result(mut result: ua::BrowseResult) -> BrowseResult { // Make sure to verify the inner status code inside `BrowseResult`. The service request finishes // without error, even when browsing the node has failed. - Error::verify_good(&result.status_code())?; + Error::verify_good(result.status_code())?; - let Some(references) = result.references() else { + let Some(references) = result.take_references() else { return Err(Error::internal("browse should return references")); }; diff --git a/src/server/access_control.rs b/src/server/access_control.rs index 698f7f52..0fa16984 100644 --- a/src/server/access_control.rs +++ b/src/server/access_control.rs @@ -107,7 +107,7 @@ unsafe impl AccessControl for DefaultAccessControl<'_> { username_password_login.as_ptr(), ) }); - Error::verify_good(&status_code) + Error::verify_good(status_code) } } @@ -221,7 +221,7 @@ where login_callback_sentinel.as_ptr(), ) }); - Error::verify_good(&status_code)?; + Error::verify_good(status_code)?; // Compile-time assertion to make sure that the strings were still alive at this point. This // includes the branch above when exiting early with `?`. diff --git a/src/server/data_source.rs b/src/server/data_source.rs index d4f496d4..d21122ea 100644 --- a/src/server/data_source.rs +++ b/src/server/data_source.rs @@ -37,7 +37,7 @@ impl DataSourceError { }) } - pub(crate) fn into_status_code(self) -> ua::StatusCode { + pub(crate) const fn into_status_code(self) -> ua::StatusCode { match self { DataSourceError::StatusCode(status_code) => status_code, DataSourceError::Error(err) => err.status_code(), diff --git a/src/server/method_callback.rs b/src/server/method_callback.rs index 0cec8516..e2d66c30 100644 --- a/src/server/method_callback.rs +++ b/src/server/method_callback.rs @@ -37,7 +37,7 @@ impl MethodCallbackError { }) } - pub(crate) fn into_status_code(self) -> ua::StatusCode { + pub(crate) const fn into_status_code(self) -> ua::StatusCode { match self { MethodCallbackError::StatusCode(status_code) => status_code, MethodCallbackError::Error(err) => err.status_code(), diff --git a/src/ssl.rs b/src/ssl.rs index bc270848..005840b9 100644 --- a/src/ssl.rs +++ b/src/ssl.rs @@ -239,7 +239,7 @@ pub fn create_certificate( certificate.as_mut_ptr(), ) }); - Error::verify_good(&status_code)?; + Error::verify_good(status_code)?; // SAFETY: The function is expected to return valid strings in its output arguments. let certificate = unsafe { Certificate::from_string_unchecked(certificate) }; diff --git a/src/ua/access_level_ex_type.rs b/src/ua/access_level_ex_type.rs index 57f0af87..36124afc 100644 --- a/src/ua/access_level_ex_type.rs +++ b/src/ua/access_level_ex_type.rs @@ -1,125 +1,49 @@ -use open62541_sys::{ - UA_ACCESSLEVELEXTYPE_CONSTANT, UA_ACCESSLEVELEXTYPE_CURRENTREAD, - UA_ACCESSLEVELEXTYPE_CURRENTWRITE, UA_ACCESSLEVELEXTYPE_HISTORYREAD, - UA_ACCESSLEVELEXTYPE_HISTORYWRITE, UA_ACCESSLEVELEXTYPE_NONATOMICREAD, - UA_ACCESSLEVELEXTYPE_NONATOMICWRITE, UA_ACCESSLEVELEXTYPE_NONVOLATILE, - UA_ACCESSLEVELEXTYPE_NOSUBDATATYPES, UA_ACCESSLEVELEXTYPE_SEMANTICCHANGE, - UA_ACCESSLEVELEXTYPE_STATUSWRITE, UA_ACCESSLEVELEXTYPE_TIMESTAMPWRITE, - UA_ACCESSLEVELEXTYPE_WRITEFULLARRAYONLY, UA_AccessLevelExType, -}; - -use crate::{DataTypeExt, Result, ua}; - -/// Wrapper for [`UA_AccessLevelExType`] from [`open62541_sys`]. -#[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub struct AccessLevelExType(UA_AccessLevelExType); +crate::data_type_baseline!( + #[expect( + clippy::derived_hash_with_manual_eq, + reason = "open62541 numeric equality impl satisfies `Hash`s identity invariant" + )] + #[derive(Clone, Copy, Hash)] + AccessLevelExType +); // See for bit values. -impl AccessLevelExType { - pub(crate) const fn from_u32(mask: u32) -> Self { - Self(mask) - } - - pub(crate) const fn as_u32(&self) -> u32 { - self.0 - } - - /// Indicates if the current value is readable. - /// - /// It also indicates if the current value of the variable is available. - #[must_use] - pub const fn current_read(&self) -> bool { - self.0 & UA_ACCESSLEVELEXTYPE_CURRENTREAD != 0 - } - - /// Indicates if the current value is writable. - /// - /// It also indicates if the current value of the variable is available. - #[must_use] - pub const fn current_write(&self) -> bool { - self.0 & UA_ACCESSLEVELEXTYPE_CURRENTWRITE != 0 - } - - /// Indicates if the history of the value is readable. - /// - /// It also indicates if the history of the variable is available via the OPC UA Server. - #[must_use] - pub const fn history_read(&self) -> bool { - self.0 & UA_ACCESSLEVELEXTYPE_HISTORYREAD != 0 - } - - /// Indicates if the history of the value is writable. - /// - /// It also indicates if the history of the variable is available via the OPC UA Server. - #[must_use] - pub const fn history_write(&self) -> bool { - self.0 & UA_ACCESSLEVELEXTYPE_HISTORYWRITE != 0 - } - - /// This flag is set for properties that define semantic aspects of the parent node of the - /// property and where the property value, and thus the semantic, may change during operation. - #[must_use] - pub const fn semantic_change(&self) -> bool { - self.0 & UA_ACCESSLEVELEXTYPE_SEMANTICCHANGE != 0 - } - - /// Indicates if the current status code of the value is writable. - #[must_use] - pub const fn status_write(&self) -> bool { - self.0 & UA_ACCESSLEVELEXTYPE_STATUSWRITE != 0 - } - - /// Indicates if the current source timestamp is writable. - #[must_use] - pub const fn timestamp_write(&self) -> bool { - self.0 & UA_ACCESSLEVELEXTYPE_TIMESTAMPWRITE != 0 - } - - /// Indicates non-atomicity for read access. - #[must_use] - pub const fn nonatomic_read(&self) -> bool { - self.0 & UA_ACCESSLEVELEXTYPE_NONATOMICREAD != 0 - } - - /// Indicates non-atomicity for write access. - #[must_use] - pub const fn nonatomic_write(&self) -> bool { - self.0 & UA_ACCESSLEVELEXTYPE_NONATOMICWRITE != 0 - } - - /// Indicates if write of index range is supported. - #[must_use] - pub const fn write_full_array_only(&self) -> bool { - self.0 & UA_ACCESSLEVELEXTYPE_WRITEFULLARRAYONLY != 0 - } - - /// Indicates if the variable doesn’t allow its data type to be subtyped. - #[must_use] - pub const fn no_sub_data_types(&self) -> bool { - self.0 & UA_ACCESSLEVELEXTYPE_NOSUBDATATYPES != 0 - } - - /// Indicates if the variable is non-volatile. - #[must_use] - pub const fn non_volatile(&self) -> bool { - self.0 & UA_ACCESSLEVELEXTYPE_NONVOLATILE != 0 - } - - /// Indicates if the value of the variable can be considered constant. - #[must_use] - pub const fn constant(&self) -> bool { - self.0 & UA_ACCESSLEVELEXTYPE_CONSTANT != 0 - } -} - -impl DataTypeExt for AccessLevelExType { - type Inner = ua::UInt32; - - fn from_inner(value: Self::Inner) -> Result { - Ok(Self::from_u32(value.value())) - } - - fn into_inner(self) -> Self::Inner { - Self::Inner::new(self.as_u32()) - } -} +crate::enum_bitmask!( + enum AccessLevelExType : u32 { + /// Indicates if the value of the variable can be considered constant. + Constant, + /// Indicates if the current value is readable. + /// + /// It also indicates if the current value of the variable is available. + CurrentRead, + /// Indicates if the current value is writable. + /// + /// It also indicates if the current value of the variable is available. + CurrentWrite, + /// Indicates if the history of the value is readable. + /// + /// It also indicates if the history of the variable is available via the OPC UA Server. + HistoryRead, + /// Indicates if the history of the value is writable. + /// + /// It also indicates if the history of the variable is available via the OPC UA Server. + HistoryWrite, + /// This flag is set for properties that define semantic aspects of the parent node of the + /// property and where the property value, and thus the semantic, may change during operation. + SemanticChange, + /// Indicates if the current status code of the value is writable. + StatusWrite, + /// Indicates if the current source timestamp is writable. + TimestampWrite, + /// Indicates non-atomicity for read access. + NonatomicRead, + /// Indicates non-atomicity for write access. + NonatomicWrite, + /// Indicates if write of index range is supported. + WriteFullArrayOnly, + /// Indicates if the variable doesn’t allow its data type to be subtyped. + NoSubDataTypes, + /// Indicates if the variable is non-volatile. + NonVolatile, + } +); diff --git a/src/ua/access_level_type.rs b/src/ua/access_level_type.rs index aff92417..7abd2bfd 100644 --- a/src/ua/access_level_type.rs +++ b/src/ua/access_level_type.rs @@ -1,112 +1,40 @@ -use open62541_sys::{ - UA_ACCESSLEVELTYPE_CURRENTREAD, UA_ACCESSLEVELTYPE_CURRENTWRITE, - UA_ACCESSLEVELTYPE_HISTORYREAD, UA_ACCESSLEVELTYPE_HISTORYWRITE, - UA_ACCESSLEVELTYPE_SEMANTICCHANGE, UA_ACCESSLEVELTYPE_STATUSWRITE, - UA_ACCESSLEVELTYPE_TIMESTAMPWRITE, UA_AccessLevelType, -}; - -use crate::{DataTypeExt, Result, ua}; - -/// Wrapper for [`UA_AccessLevelType`] from [`open62541_sys`]. -#[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub struct AccessLevelType(UA_AccessLevelType); +crate::data_type_baseline!( + #[expect( + clippy::derived_hash_with_manual_eq, + reason = "open62541 numeric equality impl satisfies `Hash`s identity invariant" + )] + #[derive(Clone, Copy, Hash)] + AccessLevelType +); // See for bit values. -impl AccessLevelType { - pub const NONE: Self = Self(0); - - pub(crate) const fn from_u8(mask: u8) -> Self { - Self(mask) - } - - #[must_use] - pub fn with_current_read(self, current_read: bool) -> Self { - self.apply_mask(UA_ACCESSLEVELTYPE_CURRENTREAD, current_read) - } - - #[must_use] - pub fn with_current_write(self, current_write: bool) -> Self { - self.apply_mask(UA_ACCESSLEVELTYPE_CURRENTWRITE, current_write) - } - - fn apply_mask(mut self, mask: u32, flag: bool) -> Self { - // PANIC: Mask is always in range of `u8`. - let mask = u8::try_from(mask).unwrap_or(0); - if flag { - self.0 |= mask; - } else { - self.0 &= !mask; - } - self - } - - pub(crate) const fn as_u8(&self) -> u8 { - self.0 - } - - /// Indicates if the current value is readable. - /// - /// It also indicates if the current value of the variable is available. - #[must_use] - pub fn current_read(&self) -> bool { - u32::from(self.0) & UA_ACCESSLEVELTYPE_CURRENTREAD != 0 - } - - /// Indicates if the current value is writable. - /// - /// It also indicates if the current value of the variable is available. - #[must_use] - pub fn current_write(&self) -> bool { - u32::from(self.0) & UA_ACCESSLEVELTYPE_CURRENTWRITE != 0 - } - - /// Indicates if the history of the value is readable. - /// - /// It also indicates if the history of the variable is available via the OPC UA Server. - #[must_use] - pub fn history_read(&self) -> bool { - u32::from(self.0) & UA_ACCESSLEVELTYPE_HISTORYREAD != 0 - } - - /// Indicates if the history of the value is writable. - /// - /// It also indicates if the history of the variable is available via the OPC UA Server. - #[must_use] - pub fn history_write(&self) -> bool { - u32::from(self.0) & UA_ACCESSLEVELTYPE_HISTORYWRITE != 0 - } - - /// This flag is set for properties that define semantic aspects of the parent node of the - /// property and where the property value, and thus the semantic, may change during operation. - #[must_use] - pub fn semantic_change(&self) -> bool { - u32::from(self.0) & UA_ACCESSLEVELTYPE_SEMANTICCHANGE != 0 - } - - /// Indicates if the current status code of the value is writable. - #[must_use] - pub fn status_write(&self) -> bool { - u32::from(self.0) & UA_ACCESSLEVELTYPE_STATUSWRITE != 0 - } - - /// Indicates if the current source timestamp is writable. - #[must_use] - pub fn timestamp_write(&self) -> bool { - u32::from(self.0) & UA_ACCESSLEVELTYPE_TIMESTAMPWRITE != 0 - } -} - -impl DataTypeExt for AccessLevelType { - type Inner = ua::Byte; - - fn from_inner(value: Self::Inner) -> Result { - Ok(Self::from_u8(value.value())) - } - - fn into_inner(self) -> Self::Inner { - Self::Inner::new(self.as_u8()) - } -} +crate::enum_bitmask!( + enum AccessLevelType : u8 { + /// Indicates if the current value is readable. + /// + /// It also indicates if the current value of the variable is available. + CurrentRead, + /// Indicates if the current value is writable. + /// + /// It also indicates if the current value of the variable is available. + CurrentWrite, + /// Indicates if the history of the value is readable. + /// + /// It also indicates if the history of the variable is available via the OPC UA Server. + HistoryRead, + /// Indicates if the history of the value is writable. + /// + /// It also indicates if the history of the variable is available via the OPC UA Server. + HistoryWrite, + /// This flag is set for properties that define semantic aspects of the parent node of the + /// property and where the property value, and thus the semantic, may change during operation. + SemanticChange, + /// Indicates if the current status code of the value is writable. + StatusWrite, + /// Indicates if the current source timestamp is writable. + TimestampWrite, + } +); #[deprecated = "Use AccessLevelType instead."] pub type AccessLevel = AccessLevelType; diff --git a/src/ua/access_restriction_type.rs b/src/ua/access_restriction_type.rs index 7f0a45e1..a6e22228 100644 --- a/src/ua/access_restriction_type.rs +++ b/src/ua/access_restriction_type.rs @@ -1,64 +1,28 @@ -use open62541_sys::{ - UA_ACCESSRESTRICTIONTYPE_APPLYRESTRICTIONSTOBROWSE, - UA_ACCESSRESTRICTIONTYPE_ENCRYPTIONREQUIRED, UA_ACCESSRESTRICTIONTYPE_SESSIONREQUIRED, - UA_ACCESSRESTRICTIONTYPE_SIGNINGREQUIRED, UA_AccessRestrictionType, -}; - -use crate::{DataTypeExt, Result, ua}; - -/// Wrapper for [`UA_AccessRestrictionType`] from [`open62541_sys`]. -#[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub struct AccessRestrictionType(UA_AccessRestrictionType); +crate::data_type_baseline!( + #[expect( + clippy::derived_hash_with_manual_eq, + reason = "open62541 numeric equality impl satisfies `Hash`s identity invariant" + )] + #[derive(Clone, Copy, Hash)] + AccessRestrictionType +); // See for bit values. -impl AccessRestrictionType { - pub(crate) const fn from_u16(mask: u16) -> Self { - Self(mask) - } - - pub(crate) const fn as_u16(&self) -> u16 { - self.0 - } - - /// The client can only access the node when using a secure channel which digitally signs all - /// messages. - /// - /// This does not apply to the browse permission if the `ApplyRestrictionsToBrowse` is not set. - #[must_use] - pub fn signing_required(&self) -> bool { - u32::from(self.as_u16()) & UA_ACCESSRESTRICTIONTYPE_SIGNINGREQUIRED != 0 - } - - /// The client can only access the node when using a secure channel which encrypts all messages. - /// - /// This does not apply to the browse permission if the `ApplyRestrictionsToBrowse` is not set. - #[must_use] - pub fn encryption_required(&self) -> bool { - u32::from(self.as_u16()) & UA_ACCESSRESTRICTIONTYPE_ENCRYPTIONREQUIRED != 0 - } - - /// The client cannot access the node when using `SessionlessInvoke` service invocation. - #[must_use] - pub fn session_required(&self) -> bool { - u32::from(self.as_u16()) & UA_ACCESSRESTRICTIONTYPE_SESSIONREQUIRED != 0 - } - - /// If this bit is set, the access restrictions `SigningRequired` and `EncryptionRequired` are - /// also applied to the browse permission. - #[must_use] - pub fn apply_restrictions_to_browse(&self) -> bool { - u32::from(self.as_u16()) & UA_ACCESSRESTRICTIONTYPE_APPLYRESTRICTIONSTOBROWSE != 0 - } -} - -impl DataTypeExt for AccessRestrictionType { - type Inner = ua::UInt16; - - fn from_inner(value: Self::Inner) -> Result { - Ok(Self::from_u16(value.value())) - } - - fn into_inner(self) -> Self::Inner { - Self::Inner::new(self.as_u16()) - } -} +crate::enum_bitmask!( + enum AccessRestrictionType : u16 { + /// If this bit is set, the access restrictions `SigningRequired` and `EncryptionRequired` are + /// also applied to the browse permission. + ApplyRestrictionsToBrowse, + /// The client can only access the node when using a secure channel which encrypts all messages. + /// + /// This does not apply to the browse permission if the `ApplyRestrictionsToBrowse` is not set. + EncryptionRequired, + /// The client cannot access the node when using `SessionlessInvoke` service invocation. + SessionRequired, + /// The client can only access the node when using a secure channel which digitally signs all + /// messages. + /// + /// This does not apply to the browse permission if the `ApplyRestrictionsToBrowse` is not set. + SigningRequired, + } +); diff --git a/src/ua/array.rs b/src/ua/array.rs index e0e3bc18..81fcf35f 100644 --- a/src/ua/array.rs +++ b/src/ua/array.rs @@ -159,7 +159,6 @@ impl Array { /// /// Ownership is transferred. There must not be any aliased references to the array elements, as /// they will be owned and freed when the returned value is dropped. - #[must_use] pub(crate) unsafe fn move_from_raw_parts( size: &mut usize, ptr: &mut *mut T::Inner, @@ -378,7 +377,10 @@ impl Array { (size, ptr.cast_mut()) } - /// Moves array into `dst`, giving up ownership. + /// Moves array into `dst`, giving up ownership. Soundness is contingent on the source and + /// destination pointers being produced by [`UA_Array_new`]/ + /// [`UA_Array_copy`](open62541_sys::UA_Array_copy). Attempting to move from/to pointers with + /// any other provenance is UB, and likely will result in a crash. /// /// Existing data in `dst` is cleared with [`UA_Array_delete()`] before moving the value; it is /// safe to use this operation on already initialized target values. diff --git a/src/ua/attribute_write_mask.rs b/src/ua/attribute_write_mask.rs index 84d5fe13..0792c6f9 100644 --- a/src/ua/attribute_write_mask.rs +++ b/src/ua/attribute_write_mask.rs @@ -1,203 +1,69 @@ -use open62541_sys::{ - UA_ATTRIBUTEWRITEMASK_ACCESSLEVEL, UA_ATTRIBUTEWRITEMASK_ACCESSLEVELEX, - UA_ATTRIBUTEWRITEMASK_ACCESSRESTRICTIONS, UA_ATTRIBUTEWRITEMASK_ARRAYDIMENSIONS, - UA_ATTRIBUTEWRITEMASK_BROWSENAME, UA_ATTRIBUTEWRITEMASK_CONTAINSNOLOOPS, - UA_ATTRIBUTEWRITEMASK_DATATYPE, UA_ATTRIBUTEWRITEMASK_DATATYPEDEFINITION, - UA_ATTRIBUTEWRITEMASK_DESCRIPTION, UA_ATTRIBUTEWRITEMASK_DISPLAYNAME, - UA_ATTRIBUTEWRITEMASK_EVENTNOTIFIER, UA_ATTRIBUTEWRITEMASK_EXECUTABLE, - UA_ATTRIBUTEWRITEMASK_HISTORIZING, UA_ATTRIBUTEWRITEMASK_INVERSENAME, - UA_ATTRIBUTEWRITEMASK_ISABSTRACT, UA_ATTRIBUTEWRITEMASK_MINIMUMSAMPLINGINTERVAL, - UA_ATTRIBUTEWRITEMASK_NODECLASS, UA_ATTRIBUTEWRITEMASK_NODEID, - UA_ATTRIBUTEWRITEMASK_ROLEPERMISSIONS, UA_ATTRIBUTEWRITEMASK_SYMMETRIC, - UA_ATTRIBUTEWRITEMASK_USERACCESSLEVEL, UA_ATTRIBUTEWRITEMASK_USEREXECUTABLE, - UA_ATTRIBUTEWRITEMASK_USERWRITEMASK, UA_ATTRIBUTEWRITEMASK_VALUEFORVARIABLETYPE, - UA_ATTRIBUTEWRITEMASK_VALUERANK, UA_ATTRIBUTEWRITEMASK_WRITEMASK, UA_AttributeWriteMask, -}; - -use crate::{DataTypeExt, Result, ua}; - -/// Wrapper for [`UA_AttributeWriteMask`] from [`open62541_sys`]. -#[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub struct AttributeWriteMask(UA_AttributeWriteMask); - -// See for bit values. -impl AttributeWriteMask { - pub(crate) const fn from_u32(mask: u32) -> Self { - Self(mask) - } - - pub(crate) const fn as_u32(&self) -> u32 { - self.0 - } - - /// Indicates if the `AccessLevel` attribute is writeable. - #[must_use] - pub const fn access_level(&self) -> bool { - self.0 & UA_ATTRIBUTEWRITEMASK_ACCESSLEVEL != 0 - } - - /// Indicates if the `ArrayDimensions` attribute is writeable. - #[must_use] - pub const fn array_dimensions(&self) -> bool { - self.0 & UA_ATTRIBUTEWRITEMASK_ARRAYDIMENSIONS != 0 - } - - /// Indicates if the `BrowseName` attribute is writeable. - #[must_use] - pub const fn browse_name(&self) -> bool { - self.0 & UA_ATTRIBUTEWRITEMASK_BROWSENAME != 0 - } - - /// Indicates if the `ContainsNoLoops` attribute is writeable. - #[must_use] - pub const fn contains_no_loops(&self) -> bool { - self.0 & UA_ATTRIBUTEWRITEMASK_CONTAINSNOLOOPS != 0 - } - - /// Indicates if the `DataType` attribute is writeable. - #[must_use] - pub const fn data_type(&self) -> bool { - self.0 & UA_ATTRIBUTEWRITEMASK_DATATYPE != 0 - } - - /// Indicates if the `Description` attribute is writeable. - #[must_use] - pub const fn description(&self) -> bool { - self.0 & UA_ATTRIBUTEWRITEMASK_DESCRIPTION != 0 - } - - /// Indicates if the `DisplayName` attribute is writeable. - #[must_use] - pub const fn display_name(&self) -> bool { - self.0 & UA_ATTRIBUTEWRITEMASK_DISPLAYNAME != 0 - } - - /// Indicates if the `EventNotifier` attribute is writeable. - #[must_use] - pub const fn event_notifier(&self) -> bool { - self.0 & UA_ATTRIBUTEWRITEMASK_EVENTNOTIFIER != 0 - } - - /// Indicates if the `Executable` attribute is writeable. - #[must_use] - pub const fn executable(&self) -> bool { - self.0 & UA_ATTRIBUTEWRITEMASK_EXECUTABLE != 0 - } - - /// Indicates if the `Historizing` attribute is writeable. - #[must_use] - pub const fn historizing(&self) -> bool { - self.0 & UA_ATTRIBUTEWRITEMASK_HISTORIZING != 0 - } - - /// Indicates if the `InverseName` attribute is writeable. - #[must_use] - pub const fn inverse_name(&self) -> bool { - self.0 & UA_ATTRIBUTEWRITEMASK_INVERSENAME != 0 - } - - /// Indicates if the `IsAbstract` attribute is writeable. - #[must_use] - pub const fn is_abstract(&self) -> bool { - self.0 & UA_ATTRIBUTEWRITEMASK_ISABSTRACT != 0 - } - - /// Indicates if the `MinimumSamplingInterval` attribute is writeable. - #[must_use] - pub const fn minimum_sampling_interval(&self) -> bool { - self.0 & UA_ATTRIBUTEWRITEMASK_MINIMUMSAMPLINGINTERVAL != 0 - } - - /// Indicates if the `NodeClass` attribute is writeable. - #[must_use] - pub const fn node_class(&self) -> bool { - self.0 & UA_ATTRIBUTEWRITEMASK_NODECLASS != 0 - } - - /// Indicates if the `NodeId` attribute is writeable. - #[must_use] - pub const fn node_id(&self) -> bool { - self.0 & UA_ATTRIBUTEWRITEMASK_NODEID != 0 - } - - /// Indicates if the `Symmetric` attribute is writeable. - #[must_use] - pub const fn symmetric(&self) -> bool { - self.0 & UA_ATTRIBUTEWRITEMASK_SYMMETRIC != 0 - } - - /// Indicates if the `UserAccessLevel` attribute is writeable. - #[must_use] - pub const fn user_access_level(&self) -> bool { - self.0 & UA_ATTRIBUTEWRITEMASK_USERACCESSLEVEL != 0 - } - - /// Indicates if the `UserExecutable` attribute is writeable. - #[must_use] - pub const fn user_executable(&self) -> bool { - self.0 & UA_ATTRIBUTEWRITEMASK_USEREXECUTABLE != 0 - } - - /// Indicates if the `UserWriteMask` attribute is writeable. - #[must_use] - pub const fn user_write_mask(&self) -> bool { - self.0 & UA_ATTRIBUTEWRITEMASK_USERWRITEMASK != 0 - } - - /// Indicates if the `ValueRank` attribute is writeable. - #[must_use] - pub const fn value_rank(&self) -> bool { - self.0 & UA_ATTRIBUTEWRITEMASK_VALUERANK != 0 - } - - /// Indicates if the `WriteMask` attribute is writeable. - #[must_use] - pub const fn write_mask(&self) -> bool { - self.0 & UA_ATTRIBUTEWRITEMASK_WRITEMASK != 0 - } - - /// Indicates if the `Value` Attribute is writeable for a `VariableType`. - /// - /// Note: It does not apply for variables since this is handled by the `AccessLevel` and - /// `UserAccessLevel` attributes for the Variable. For variables this bit shall be set to 0. - #[must_use] - pub const fn value_for_variable_type(&self) -> bool { - self.0 & UA_ATTRIBUTEWRITEMASK_VALUEFORVARIABLETYPE != 0 - } - - /// Indicates if the `DataTypeDefinition` attribute is writeable. - #[must_use] - pub const fn data_type_definition(&self) -> bool { - self.0 & UA_ATTRIBUTEWRITEMASK_DATATYPEDEFINITION != 0 - } - - /// Indicates if the `RolePermissions` attribute is writeable. - #[must_use] - pub const fn role_permissions(&self) -> bool { - self.0 & UA_ATTRIBUTEWRITEMASK_ROLEPERMISSIONS != 0 - } - - /// Indicates if the `AccessRestrictions` attribute is writeable. - #[must_use] - pub const fn access_restrictions(&self) -> bool { - self.0 & UA_ATTRIBUTEWRITEMASK_ACCESSRESTRICTIONS != 0 - } - - /// Indicates if the `AccessLevelEx` attribute is writeable. - #[must_use] - pub const fn access_level_ex(&self) -> bool { - self.0 & UA_ATTRIBUTEWRITEMASK_ACCESSLEVELEX != 0 - } +crate::data_type_baseline! { + #[expect( + clippy::derived_hash_with_manual_eq, + reason = "open62541 numeric equality impl satisfies `Hash`s identity invariant" + )] + #[derive(Clone, Copy, Hash)] + AttributeWriteMask } -impl DataTypeExt for AttributeWriteMask { - type Inner = ua::UInt32; - - fn from_inner(value: Self::Inner) -> Result { - Ok(Self::from_u32(value.value())) - } - - fn into_inner(self) -> Self::Inner { - Self::Inner::new(self.as_u32()) - } -} +// See for bit values. +crate::enum_bitmask!( + enum AttributeWriteMask : u32 { + /// Indicates if the `AccessLevel` attribute is writeable. + AccessLevel, + /// Indicates if the `AccessLevelEx` attribute is writeable. + AccessLevelEx, + /// Indicates if the `AccessRestrictions` attribute is writeable. + AccessRestrictions, + /// Indicates if the `ArrayDimensions` attribute is writeable. + ArrayDimensions, + /// Indicates if the `BrowseName` attribute is writeable. + BrowseName, + /// Indicates if the `ContainsNoLoops` attribute is writeable. + ContainsNoLoops, + /// Indicates if the `DataType` attribute is writeable. + DataType, + /// Indicates if the `DataTypeDefinition` attribute is writeable. + DataTypeDefinition, + /// Indicates if the `Description` attribute is writeable. + Description, + /// Indicates if the `DisplayName` attribute is writeable. + DisplayName, + /// Indicates if the `EventNotifier` attribute is writeable. + EventNotifier, + /// Indicates if the `Executable` attribute is writeable. + Executable, + /// Indicates if the `Historizing` attribute is writeable. + Historizing, + /// Indicates if the `InverseName` attribute is writeable. + InverseName, + /// Indicates if the `IsAbstract` attribute is writeable. + IsAbstract, + /// Indicates if the `MinimumSamplingInterval` attribute is writeable. + MinimumSamplingInterval, + /// Indicates if the `NodeClass` attribute is writeable. + NodeClass, + /// Indicates if the `NodeId` attribute is writeable. + NodeId, + /// Indicates if the `RolePermissions` attribute is writeable. + RolePermissions, + /// Indicates if the `Symmetric` attribute is writeable. + Symmetric, + /// Indicates if the `UserAccessLevel` attribute is writeable. + UserAccessLevel, + /// Indicates if the `UserExecutable` attribute is writeable. + UserExecutable, + /// Indicates if the `UserWriteMask` attribute is writeable. + UserWriteMask, + /// Indicates if the `ValueForVariableType` attribute is writeable. + /// + /// Note: It does not apply for variables since this is handled by the `AccessLevel` and + /// `UserAccessLevel` attributes for the Variable. For variables this bit shall be set to 0. + ValueForVariableType, + /// Indicates if the `ValueRank` attribute is writeable. + ValueRank, + /// Indicates if the `WriteMask` attribute is writeable. + WriteMask, + } +); diff --git a/src/ua/browse_result_mask.rs b/src/ua/browse_result_mask.rs index f13fd352..8616e710 100644 --- a/src/ua/browse_result_mask.rs +++ b/src/ua/browse_result_mask.rs @@ -23,9 +23,16 @@ mod inner { ); } -/// Wrapper for browse result mask from [`open62541_sys`]. -#[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub struct BrowseResultMask(u32); +crate::data_type::data_type_baseline! { + #[expect( + clippy::derived_hash_with_manual_eq, + reason = "open62541 numeric equality impl satisfies `Hash`s identity invariant" + )] + #[derive(Clone, Copy, Hash)] + BrowseResultMask, + #[uses_inner] + UInt32 +} crate::bitmask_ops!(BrowseResultMask); @@ -45,7 +52,7 @@ impl BrowseResultMask { Self(mask) } - pub(crate) const fn as_u32(&self) -> u32 { + pub(crate) const fn as_u32(self) -> u32 { self.0 } } diff --git a/src/ua/client.rs b/src/ua/client.rs index efc88dbd..08b8bf3f 100644 --- a/src/ua/client.rs +++ b/src/ua/client.rs @@ -106,7 +106,7 @@ impl Client { // SAFETY: We retain ownership of `self`. UA_Client_disconnect(self.as_mut_ptr()) }); - if let Err(error) = Error::verify_good(&status_code) { + if let Err(error) = Error::verify_good(status_code) { log::warn!("Error while disconnecting client: {error}"); } } diff --git a/src/ua/client_config.rs b/src/ua/client_config.rs index 27da3905..c848a7bc 100644 --- a/src/ua/client_config.rs +++ b/src/ua/client_config.rs @@ -103,7 +103,7 @@ impl ClientConfig { let status_code = ua::StatusCode::new(unsafe { UA_ClientConfig_setDefault(config.as_mut_ptr()) }); // PANIC: The only possible errors here are out-of-memory. - Error::verify_good(&status_code).expect("should set default client config"); + Error::verify_good(status_code).expect("should set default client config"); config } @@ -134,7 +134,7 @@ impl ClientConfig { 0, ) }); - Error::verify_good(&status_code)?; + Error::verify_good(status_code)?; Ok(config) } diff --git a/src/ua/continuation_point.rs b/src/ua/continuation_point.rs index bae2e738..7444f31c 100644 --- a/src/ua/continuation_point.rs +++ b/src/ua/continuation_point.rs @@ -1,8 +1,6 @@ -use crate::ua; +use crate::{DataType, ua}; -/// Wrapper for continuation point from [`open62541_sys`]. -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct ContinuationPoint(ua::ByteString); +crate::data_type!(ContinuationPoint); impl ContinuationPoint { /// Creates continuation point from raw string. @@ -23,17 +21,18 @@ impl ContinuationPoint { // this is not an invalid string (as defined by OPC UA) but it might indicate an error. debug_assert!(!continuation_point.is_empty()); - Some(Self(continuation_point)) + Some(Self(continuation_point.into_raw())) } /// Gets underlying representation. #[must_use] pub(crate) const fn as_byte_string(&self) -> &ua::ByteString { - &self.0 + // SAFETY: `repr(transparent)` means that we can transmute safely between wrapper types + unsafe { std::mem::transmute(self) } } /// Gets underlying representation. pub(crate) fn to_byte_string(&self) -> ua::ByteString { - self.0.clone() + self.as_byte_string().clone() } } diff --git a/src/ua/data_type_description.rs b/src/ua/data_type_description.rs index fd5545f7..49f399af 100644 --- a/src/ua/data_type_description.rs +++ b/src/ua/data_type_description.rs @@ -9,7 +9,7 @@ pub enum DataTypeDescription { impl DataTypeDescription { #[must_use] - pub fn data_type_id(&self) -> &ua::NodeId { + pub const fn data_type_id(&self) -> &ua::NodeId { match self { Self::Structure(description) => description.data_type_id(), Self::Enum(description) => description.data_type_id(), @@ -17,7 +17,7 @@ impl DataTypeDescription { } #[must_use] - pub fn name(&self) -> &ua::QualifiedName { + pub const fn name(&self) -> &ua::QualifiedName { match self { Self::Structure(description) => description.name(), Self::Enum(description) => description.name(), diff --git a/src/ua/data_types.rs b/src/ua/data_types.rs index 077a0919..6947414a 100644 --- a/src/ua/data_types.rs +++ b/src/ua/data_types.rs @@ -30,12 +30,14 @@ mod create_monitored_items_response; mod create_subscription_request; mod create_subscription_respones; mod data_change_filter; +mod data_change_trigger; mod data_value; mod date_time; mod delete_monitored_items_request; mod delete_monitored_items_response; mod delete_subscriptions_request; mod delete_subscriptions_response; +mod diagnostic_info; mod element_operand; mod endpoint_description; mod enum_definition; @@ -46,6 +48,7 @@ mod eu_information; mod event_filter; mod expanded_node_id; mod extension_object; +mod extension_object_encoding; mod filter_operator; mod guid; mod issued_identity_token; @@ -80,9 +83,14 @@ mod structure_description; mod structure_field; mod structure_type; mod timestamps_to_return; +mod translate_browse_paths_to_node_ids_request; +mod translate_browse_paths_to_node_ids_response; mod trust_list_data_type; mod user_name_identity_token; +mod user_token_policy; +mod user_token_type; mod variant; +mod view_description; mod write_request; mod write_response; mod write_value; @@ -119,12 +127,14 @@ pub use self::{ create_subscription_request::CreateSubscriptionRequest, create_subscription_respones::CreateSubscriptionResponse, data_change_filter::DataChangeFilter, + data_change_trigger::DataChangeTrigger, data_value::DataValue, date_time::DateTime, delete_monitored_items_request::DeleteMonitoredItemsRequest, delete_monitored_items_response::DeleteMonitoredItemsResponse, delete_subscriptions_request::DeleteSubscriptionsRequest, delete_subscriptions_response::DeleteSubscriptionsResponse, + diagnostic_info::DiagnosticInfo, element_operand::ElementOperand, endpoint_description::EndpointDescription, enum_definition::EnumDefinition, @@ -135,6 +145,7 @@ pub use self::{ event_filter::EventFilter, expanded_node_id::ExpandedNodeId, extension_object::ExtensionObject, + extension_object_encoding::ExtensionObjectEncoding, filter_operator::FilterOperator, guid::Guid, issued_identity_token::IssuedIdentityToken, @@ -173,9 +184,14 @@ pub use self::{ structure_field::StructureField, structure_type::StructureType, timestamps_to_return::TimestampsToReturn, + translate_browse_paths_to_node_ids_request::TranslateBrowsePathsToNodeIdsRequest, + translate_browse_paths_to_node_ids_response::TranslateBrowsePathsToNodeIdsResponse, trust_list_data_type::TrustListDataType, user_name_identity_token::UserNameIdentityToken, + user_token_policy::UserTokenPolicy, + user_token_type::UserTokenType, variant::Variant, + view_description::ViewDescription, write_request::WriteRequest, write_response::WriteResponse, write_value::WriteValue, @@ -185,7 +201,7 @@ pub use self::{ macro_rules! primitive { ($( ($name:ident, $type:ty) ),* $(,)?) => { $( - $crate::data_type!($name); + $crate::data_type::data_type_baseline!(#[derive(Clone, Copy)] $name); impl $name { #[must_use] @@ -210,7 +226,34 @@ macro_rules! primitive { } } } + )* + + #[cfg(test)] + mod primitive_assertions { + $( + paste::paste! { + #[test] + fn []() { + #[expect(clippy::as_conversions, reason = "run in tests only, not in production code")] + let typedef_index = open62541_sys::[] as usize; + + #[expect(clippy::indexing_slicing, reason = "run in tests only, not in production code")] + let typedef = unsafe { &open62541_sys::UA_TYPES[typedef_index] }; + assert_eq!(typedef.pointerFree(), 1u32, "UA primitives must be pointer free"); + assert_eq!(typedef.membersSize(), 0u32, "UA primitives must have no members"); + + #[expect(clippy::as_conversions, reason = "run in tests only, not in production code")] + let mem_size = typedef.memSize() as usize; + assert_eq!( + mem_size, + ::std::mem::size_of::<$type>(), + "UA primitive types should have same bit-width as Rust wrapped types" + ); + } + } + )* + } }; } @@ -224,6 +267,6 @@ primitive!( (UInt32, u32), // Data type ns=0;i=7 (Int64, i64), // Data type ns=0;i=8 (UInt64, u64), // Data type ns=0;i=9 - (Float, f32), // Data type ns=0;i=10 + (Float, f32), // Data type ns=0;i=195888800 (Double, f64), // Data type ns=0;i=11 ); diff --git a/src/ua/data_types/aggregate_filter.rs b/src/ua/data_types/aggregate_filter.rs index 861bd7f3..061356e3 100644 --- a/src/ua/data_types/aggregate_filter.rs +++ b/src/ua/data_types/aggregate_filter.rs @@ -1,6 +1,13 @@ use crate::{MonitoringFilter, ua}; crate::data_type!(AggregateFilter); +crate::member_accessors!(AggregateFilter { + aggregateType: &ua::NodeId, + #[from_inner] + startTime: ua::DateTime, + #[skip] + aggregateConfiguration: &ua::AggregateConfiguration +}); impl MonitoringFilter for AggregateFilter { fn to_extension_object(&self) -> ua::ExtensionObject { diff --git a/src/ua/data_types/anonymous_identity_token.rs b/src/ua/data_types/anonymous_identity_token.rs index cdfa3893..8a0b984a 100644 --- a/src/ua/data_types/anonymous_identity_token.rs +++ b/src/ua/data_types/anonymous_identity_token.rs @@ -1,6 +1,9 @@ use crate::{DataType as _, ua}; crate::data_type!(AnonymousIdentityToken); +crate::member_accessors!(AnonymousIdentityToken { + policyId: &ua::String +}); impl AnonymousIdentityToken { /// Sets policy ID. diff --git a/src/ua/data_types/application_description.rs b/src/ua/data_types/application_description.rs index 27a28787..6525206f 100644 --- a/src/ua/data_types/application_description.rs +++ b/src/ua/data_types/application_description.rs @@ -1,6 +1,15 @@ use crate::{DataType as _, UaIterExt as _, ua}; crate::data_type!(ApplicationDescription); +crate::member_accessors!(ApplicationDescription { + applicationUri: &ua::String, + productUri: &ua::String, + applicationName: &ua::LocalizedText, + applicationType: &ua::ApplicationType, + gatewayServerUri: &ua::String, + discoveryProfileUri: &ua::String, + discoveryUrls: [ua::String] +}); /// Typically, `ApplicationDescription` is generated from static data. Therefore, the methods below /// do not return `Result` but panic instead when the given strings are invalid (when they contain @@ -91,41 +100,7 @@ impl ApplicationDescription { .map(|discovery_url| ua::String::new(discovery_url).unwrap()) .collect_ua_array() .move_into_raw(&mut self.0.discoveryUrlsSize, &mut self.0.discoveryUrls); - self - } - - #[must_use] - pub fn application_uri(&self) -> &ua::String { - ua::String::raw_ref(&self.0.applicationUri) - } - - #[must_use] - pub fn product_uri(&self) -> &ua::String { - ua::String::raw_ref(&self.0.productUri) - } - - #[must_use] - pub fn application_name(&self) -> &ua::LocalizedText { - ua::LocalizedText::raw_ref(&self.0.applicationName) - } - #[must_use] - pub fn application_type(&self) -> &ua::ApplicationType { - ua::ApplicationType::raw_ref(&self.0.applicationType) - } - - #[must_use] - pub fn gateway_server_uri(&self) -> &ua::String { - ua::String::raw_ref(&self.0.gatewayServerUri) - } - - #[must_use] - pub fn discovery_profile_uri(&self) -> &ua::String { - ua::String::raw_ref(&self.0.discoveryProfileUri) - } - - #[must_use] - pub fn discovery_urls(&self) -> Option<&[ua::String]> { - unsafe { ua::Array::slice_from_raw_parts(self.0.discoveryUrlsSize, self.0.discoveryUrls) } + self } } diff --git a/src/ua/data_types/argument.rs b/src/ua/data_types/argument.rs index a1c5b98c..7c487a0a 100644 --- a/src/ua/data_types/argument.rs +++ b/src/ua/data_types/argument.rs @@ -1,6 +1,13 @@ use crate::{DataType, ValueType, ua}; crate::data_type!(Argument); +crate::member_accessors!(Argument { + dataType: &ua::NodeId, + name: &ua::String, + description: &ua::LocalizedText, + valueRank: i32, + arrayDimensions: [ua::UInt32] +}); impl Argument { #[must_use] @@ -27,32 +34,6 @@ impl Argument { self } - #[must_use] - pub fn name(&self) -> &ua::String { - ua::String::raw_ref(&self.0.name) - } - - #[must_use] - pub fn data_type(&self) -> &ua::NodeId { - ua::NodeId::raw_ref(&self.0.dataType) - } - - #[must_use] - pub const fn value_rank(&self) -> i32 { - self.0.valueRank - } - - #[must_use] - pub fn array_dimensions(&self) -> Option> { - // TODO: Adjust signature to return non-owned value instead. - ua::Array::from_raw_parts(self.0.arrayDimensionsSize, self.0.arrayDimensions) - } - - #[must_use] - pub fn description(&self) -> &ua::LocalizedText { - ua::LocalizedText::raw_ref(&self.0.description) - } - #[must_use] pub fn value_type(&self) -> ValueType { ValueType::from_data_type(self.data_type()) diff --git a/src/ua/data_types/attribute_operand.rs b/src/ua/data_types/attribute_operand.rs index 0b46fdbc..f758bfd0 100644 --- a/src/ua/data_types/attribute_operand.rs +++ b/src/ua/data_types/attribute_operand.rs @@ -1,6 +1,21 @@ use crate::{FilterOperand, ua}; crate::data_type!(AttributeOperand); +crate::member_accessors!(AttributeOperand { + indexRange: &ua::String, + nodeId: &ua::NodeId, + browsePath: &ua::RelativePath, + alias: &ua::String, + #[skip(uses = AttributeOperand::attribute_id)] + attributeId: ua::AttributeId +}); + +impl AttributeOperand { + #[must_use] + pub const fn attribute_id(&self) -> ua::AttributeId { + ua::AttributeId::from_u32(self.0.attributeId) + } +} impl FilterOperand for AttributeOperand { fn to_extension_object(&self) -> ua::ExtensionObject { diff --git a/src/ua/data_types/browse_description.rs b/src/ua/data_types/browse_description.rs index 36c1dbe3..34769c03 100644 --- a/src/ua/data_types/browse_description.rs +++ b/src/ua/data_types/browse_description.rs @@ -3,6 +3,17 @@ use open62541_sys::UA_NS0ID_HIERARCHICALREFERENCES; use crate::{DataType, ua}; crate::data_type!(BrowseDescription); +crate::member_accessors!(BrowseDescription { + nodeId: &ua::NodeId, + referenceTypeId: &ua::NodeId, + includeSubtypes: bool, + #[from_inner] + nodeClassMask: ua::NodeClassMask, + #[from_inner] + resultMask: ua::BrowseResultMask, + #[enum] + browseDirection: ua::BrowseDirection +}); impl BrowseDescription { #[must_use] @@ -40,11 +51,6 @@ impl BrowseDescription { self.0.resultMask = result_mask.as_u32(); self } - - #[must_use] - pub(crate) fn node_id(&self) -> &ua::NodeId { - ua::NodeId::raw_ref(&self.0.nodeId) - } } impl Default for BrowseDescription { diff --git a/src/ua/data_types/browse_next_request.rs b/src/ua/data_types/browse_next_request.rs index bfe7a09c..0ed2a699 100644 --- a/src/ua/data_types/browse_next_request.rs +++ b/src/ua/data_types/browse_next_request.rs @@ -1,6 +1,11 @@ use crate::{DataType as _, ServiceRequest, UaIterExt as _, ua}; crate::data_type!(BrowseNextRequest); +crate::member_accessors!(BrowseNextRequest { + continuationPoints: [ua::ContinuationPoint], + requestHeader: &mut ua::RequestHeader, + releaseContinuationPoints: bool +}); impl BrowseNextRequest { #[must_use] @@ -16,6 +21,7 @@ impl BrowseNextRequest { &mut self.0.continuationPointsSize, &mut self.0.continuationPoints, ); + self } diff --git a/src/ua/data_types/browse_next_response.rs b/src/ua/data_types/browse_next_response.rs index d40ae95c..181ec859 100644 --- a/src/ua/data_types/browse_next_response.rs +++ b/src/ua/data_types/browse_next_response.rs @@ -1,14 +1,10 @@ use crate::{DataType as _, ServiceResponse, ua}; crate::data_type!(BrowseNextResponse); - -impl BrowseNextResponse { - #[must_use] - pub fn results(&self) -> Option> { - // TODO: Adjust signature to return non-owned value instead. - ua::Array::from_raw_parts(self.0.resultsSize, self.0.results) - } -} +crate::member_accessors!(BrowseNextResponse { + results: [ua::BrowseResult], + responseHeader: &ua::ResponseHeader +}); impl ServiceResponse for BrowseNextResponse { type Request = ua::BrowseNextRequest; diff --git a/src/ua/data_types/browse_path.rs b/src/ua/data_types/browse_path.rs index 0184cde9..ebdae930 100644 --- a/src/ua/data_types/browse_path.rs +++ b/src/ua/data_types/browse_path.rs @@ -1,6 +1,10 @@ use crate::{DataType as _, ua}; crate::data_type!(BrowsePath); +crate::member_accessors!(BrowsePath { + startingNode: &ua::NodeId, + relativePath: &ua::RelativePath +}); impl BrowsePath { #[must_use] @@ -14,14 +18,4 @@ impl BrowsePath { relative_path.clone_into_raw(&mut self.0.relativePath); self } - - #[must_use] - pub fn starting_node(&self) -> &ua::NodeId { - ua::NodeId::raw_ref(&self.0.startingNode) - } - - #[must_use] - pub fn relative_path(&self) -> &ua::RelativePath { - ua::RelativePath::raw_ref(&self.0.relativePath) - } } diff --git a/src/ua/data_types/browse_path_result.rs b/src/ua/data_types/browse_path_result.rs index 886be61e..abc6f1b1 100644 --- a/src/ua/data_types/browse_path_result.rs +++ b/src/ua/data_types/browse_path_result.rs @@ -2,15 +2,8 @@ use crate::ua; crate::data_type!(BrowsePathResult); -impl BrowsePathResult { - #[must_use] - pub const fn status_code(&self) -> ua::StatusCode { - ua::StatusCode::new(self.0.statusCode) - } - - #[must_use] - pub fn targets(&self) -> Option> { - // TODO: Adjust signature to return non-owned value instead. - ua::Array::from_raw_parts(self.0.targetsSize, self.0.targets) - } -} +crate::member_accessors!(BrowsePathResult { + targets: [ua::BrowsePathTarget], + #[from_inner] + statusCode: ua::StatusCode +}); diff --git a/src/ua/data_types/browse_path_target.rs b/src/ua/data_types/browse_path_target.rs index e9b8d007..06c50732 100644 --- a/src/ua/data_types/browse_path_target.rs +++ b/src/ua/data_types/browse_path_target.rs @@ -1,13 +1,14 @@ -use crate::{DataType as _, ua}; +use crate::ua; crate::data_type!(BrowsePathTarget); +crate::member_accessors!(BrowsePathTarget { + targetId: &ua::ExpandedNodeId, -impl BrowsePathTarget { - #[must_use] - pub fn target_id(&self) -> &ua::ExpandedNodeId { - ua::ExpandedNodeId::raw_ref(&self.0.targetId) - } + #[skip(uses = BrowsePathTarget::remaining_path_index)] + remainingPathIndex: usize +}); +impl BrowsePathTarget { /// Returns the index of the first unprocessed element in the [`ua::RelativePath`]. /// /// This returns `None` if all elements were processed. diff --git a/src/ua/data_types/browse_request.rs b/src/ua/data_types/browse_request.rs index 3d101fe8..73170863 100644 --- a/src/ua/data_types/browse_request.rs +++ b/src/ua/data_types/browse_request.rs @@ -1,12 +1,19 @@ -use crate::{DataType as _, ServiceRequest, ua}; +use crate::{DataType, ServiceRequest, ua}; crate::data_type!(BrowseRequest); +crate::member_accessors!(BrowseRequest { + nodesToBrowse: [ua::BrowseDescription], + requestedMaxReferencesPerNode: u32, + view: &ua::ViewDescription, + requestHeader: &mut ua::RequestHeader +}); impl BrowseRequest { #[must_use] pub fn with_nodes_to_browse(mut self, nodes_to_browse: &[ua::BrowseDescription]) -> Self { - let array = ua::Array::from_slice(nodes_to_browse); - array.move_into_raw(&mut self.0.nodesToBrowseSize, &mut self.0.nodesToBrowse); + ua::Array::from_slice(nodes_to_browse) + .move_into_raw(&mut self.0.nodesToBrowseSize, &mut self.0.nodesToBrowse); + self } @@ -18,6 +25,12 @@ impl BrowseRequest { self.0.requestedMaxReferencesPerNode = requested_max_references_per_node; self } + + #[must_use] + pub fn with_view(mut self, view: &ua::ViewDescription) -> Self { + view.clone_into_raw(&mut self.0.view); + self + } } impl ServiceRequest for BrowseRequest { diff --git a/src/ua/data_types/browse_response.rs b/src/ua/data_types/browse_response.rs index 76025f05..a17909a3 100644 --- a/src/ua/data_types/browse_response.rs +++ b/src/ua/data_types/browse_response.rs @@ -1,14 +1,11 @@ use crate::{DataType as _, ServiceResponse, ua}; crate::data_type!(BrowseResponse); - -impl BrowseResponse { - #[must_use] - pub fn results(&self) -> Option> { - // TODO: Adjust signature to return non-owned value instead. - ua::Array::from_raw_parts(self.0.resultsSize, self.0.results) - } -} +crate::member_accessors!(BrowseResponse { + results: [ua::BrowseResult], + responseHeader: &ua::ResponseHeader, + diagnosticInfos: [ua::DiagnosticInfo] +}); impl ServiceResponse for BrowseResponse { type Request = ua::BrowseRequest; diff --git a/src/ua/data_types/browse_result.rs b/src/ua/data_types/browse_result.rs index bcf4a8de..e22e5ccc 100644 --- a/src/ua/data_types/browse_result.rs +++ b/src/ua/data_types/browse_result.rs @@ -1,19 +1,16 @@ use crate::{DataType as _, ua}; crate::data_type!(BrowseResult); +crate::member_accessors!(BrowseResult { + #[from_inner] + statusCode: ua::StatusCode, + references: [ua::ReferenceDescription], -impl BrowseResult { - #[must_use] - pub const fn status_code(&self) -> ua::StatusCode { - ua::StatusCode::new(self.0.statusCode) - } - - #[must_use] - pub fn references(&self) -> Option> { - // TODO: Adjust signature to return non-owned value instead. - ua::Array::from_raw_parts(self.0.referencesSize, self.0.references) - } + #[skip(uses = BrowseResult::continuation_point)] + continuationPoint: &ua::ContinuationPoint +}); +impl BrowseResult { /// Gets continuation point. /// /// Browse results include a continuation point when not all references could be returned. Pass diff --git a/src/ua/data_types/build_info.rs b/src/ua/data_types/build_info.rs index 777f053f..c484a545 100644 --- a/src/ua/data_types/build_info.rs +++ b/src/ua/data_types/build_info.rs @@ -1,36 +1,12 @@ -use crate::{DataType as _, ua}; +use crate::ua; crate::data_type!(BuildInfo); - -impl BuildInfo { - #[must_use] - pub fn build_date(&self) -> ua::DateTime { - // SAFETY: The i64 value represents a valid UtcTime. - unsafe { ua::DateTime::from_raw(self.0.buildDate) } - } - - #[must_use] - pub fn build_number(&self) -> &ua::String { - ua::String::raw_ref(&self.0.buildNumber) - } - - #[must_use] - pub fn manufacturer_name(&self) -> &ua::String { - ua::String::raw_ref(&self.0.manufacturerName) - } - - #[must_use] - pub fn product_name(&self) -> &ua::String { - ua::String::raw_ref(&self.0.productName) - } - - #[must_use] - pub fn product_uri(&self) -> &ua::String { - ua::String::raw_ref(&self.0.productUri) - } - - #[must_use] - pub fn software_version(&self) -> &ua::String { - ua::String::raw_ref(&self.0.softwareVersion) - } -} +crate::member_accessors!(BuildInfo { + #[from_inner] + buildDate: ua::DateTime, + buildNumber: &ua::String, + manufacturerName: &ua::String, + productName: &ua::String, + productUri: &ua::String, + softwareVersion: &ua::String +}); diff --git a/src/ua/data_types/call_method_request.rs b/src/ua/data_types/call_method_request.rs index 52cb9e57..62db4796 100644 --- a/src/ua/data_types/call_method_request.rs +++ b/src/ua/data_types/call_method_request.rs @@ -1,6 +1,11 @@ use crate::{DataType as _, ua}; crate::data_type!(CallMethodRequest); +crate::member_accessors!(CallMethodRequest { + objectId: &ua::NodeId, + methodId: &ua::NodeId, + inputArguments: [ua::Variant] +}); impl CallMethodRequest { #[must_use] @@ -17,8 +22,8 @@ impl CallMethodRequest { #[must_use] pub fn with_input_arguments(mut self, input_arguments: &[ua::Variant]) -> Self { - let array = ua::Array::from_slice(input_arguments); - array.move_into_raw(&mut self.0.inputArgumentsSize, &mut self.0.inputArguments); + ua::Array::from_slice(input_arguments) + .move_into_raw(&mut self.0.inputArgumentsSize, &mut self.0.inputArguments); self } } diff --git a/src/ua/data_types/call_method_result.rs b/src/ua/data_types/call_method_result.rs index 0ca27f99..bd4eca16 100644 --- a/src/ua/data_types/call_method_result.rs +++ b/src/ua/data_types/call_method_result.rs @@ -1,22 +1,9 @@ use crate::ua; crate::data_type!(CallMethodResult); - -impl CallMethodResult { - #[must_use] - pub const fn status_code(&self) -> ua::StatusCode { - ua::StatusCode::new(self.0.statusCode) - } - - #[must_use] - pub fn input_argument_results(&self) -> Option> { - // TODO: Adjust signature to return non-owned value instead. - ua::Array::from_raw_parts(self.0.inputArgumentResultsSize, self.0.inputArgumentResults) - } - - #[must_use] - pub fn output_arguments(&self) -> Option> { - // TODO: Adjust signature to return non-owned value instead. - ua::Array::from_raw_parts(self.0.outputArgumentsSize, self.0.outputArguments) - } -} +crate::member_accessors!(CallMethodResult { + #[from_inner] + statusCode: ua::StatusCode, + inputArgumentResults: [ua::StatusCode], + outputArguments: [ua::Variant] +}); diff --git a/src/ua/data_types/call_request.rs b/src/ua/data_types/call_request.rs index a8e2ff1b..ff128025 100644 --- a/src/ua/data_types/call_request.rs +++ b/src/ua/data_types/call_request.rs @@ -1,12 +1,17 @@ use crate::{DataType as _, ServiceRequest, ua}; crate::data_type!(CallRequest); +crate::member_accessors!(CallRequest { + methodsToCall: [ua::CallMethodRequest], + requestHeader: &mut ua::RequestHeader +}); impl CallRequest { #[must_use] pub fn with_methods_to_call(mut self, methods_to_call: &[ua::CallMethodRequest]) -> Self { - let array = ua::Array::from_slice(methods_to_call); - array.move_into_raw(&mut self.0.methodsToCallSize, &mut self.0.methodsToCall); + ua::Array::from_slice(methods_to_call) + .move_into_raw(&mut self.0.methodsToCallSize, &mut self.0.methodsToCall); + self } } diff --git a/src/ua/data_types/call_response.rs b/src/ua/data_types/call_response.rs index 576734d4..9c92923b 100644 --- a/src/ua/data_types/call_response.rs +++ b/src/ua/data_types/call_response.rs @@ -1,19 +1,15 @@ -use crate::{DataType as _, ServiceResponse, ua}; +use crate::{ServiceResponse, ua}; crate::data_type!(CallResponse); - -impl CallResponse { - #[must_use] - pub fn results(&self) -> Option> { - // TODO: Adjust signature to return non-owned value instead. - ua::Array::from_raw_parts(self.0.resultsSize, self.0.results) - } -} +crate::member_accessors!(CallResponse { + results: [ua::CallMethodResult], + responseHeader: &ua::ResponseHeader +}); impl ServiceResponse for CallResponse { type Request = ua::CallRequest; fn response_header(&self) -> &ua::ResponseHeader { - ua::ResponseHeader::raw_ref(&self.0.responseHeader) + Self::response_header(self) } } diff --git a/src/ua/data_types/content_filter.rs b/src/ua/data_types/content_filter.rs index c96a8994..113dabd9 100644 --- a/src/ua/data_types/content_filter.rs +++ b/src/ua/data_types/content_filter.rs @@ -1,12 +1,16 @@ use crate::ua; crate::data_type!(ContentFilter); +crate::member_accessors!(ContentFilter { + elements: [ua::ContentFilterElement] +}); impl ContentFilter { #[must_use] pub fn with_elements(mut self, elements: &[ua::ContentFilterElement]) -> Self { - let array = ua::Array::from_slice(elements); - array.move_into_raw(&mut self.0.elementsSize, &mut self.0.elements); + ua::Array::from_slice(elements) + .move_into_raw(&mut self.0.elementsSize, &mut self.0.elements); + self } } diff --git a/src/ua/data_types/content_filter_element.rs b/src/ua/data_types/content_filter_element.rs index dbf5db28..dfb33375 100644 --- a/src/ua/data_types/content_filter_element.rs +++ b/src/ua/data_types/content_filter_element.rs @@ -1,6 +1,11 @@ use crate::{DataType, FilterOperand, UaIterExt as _, ua}; crate::data_type!(ContentFilterElement); +crate::member_accessors!(ContentFilterElement { + #[enum] + filterOperator: ua::FilterOperator, + filterOperands: [ua::ExtensionObject] +}); impl ContentFilterElement { #[must_use] @@ -16,6 +21,7 @@ impl ContentFilterElement { .map(FilterOperand::to_extension_object) .collect_ua_array() .move_into_raw(&mut self.0.filterOperandsSize, &mut self.0.filterOperands); + self } } diff --git a/src/ua/data_types/create_monitored_items_request.rs b/src/ua/data_types/create_monitored_items_request.rs index 6e45acf4..c6716d0e 100644 --- a/src/ua/data_types/create_monitored_items_request.rs +++ b/src/ua/data_types/create_monitored_items_request.rs @@ -1,6 +1,13 @@ use crate::ua; crate::data_type!(CreateMonitoredItemsRequest); +crate::member_accessors!(CreateMonitoredItemsRequest { + itemsToCreate: [ua::MonitoredItemCreateRequest], + #[enum] + timestampsToReturn: ua::TimestampsToReturn, + #[skip(uses = Self::subscription_id)] + subscriptionId: ua::SubscriptionId +}); impl CreateMonitoredItemsRequest { #[must_use] @@ -14,13 +21,18 @@ impl CreateMonitoredItemsRequest { mut self, items_to_create: &[ua::MonitoredItemCreateRequest], ) -> Self { - let array = ua::Array::from_slice(items_to_create); - array.move_into_raw(&mut self.0.itemsToCreateSize, &mut self.0.itemsToCreate); + ua::Array::from_slice(items_to_create) + .move_into_raw(&mut self.0.itemsToCreateSize, &mut self.0.itemsToCreate); + self } #[must_use] - pub(crate) fn items_to_create(&self) -> Option<&[ua::MonitoredItemCreateRequest]> { - unsafe { ua::Array::slice_from_raw_parts(self.0.itemsToCreateSize, self.0.itemsToCreate) } + pub const fn subscription_id(&self) -> Option { + if let Some(id) = ua::IntegerId::from_u32(self.0.subscriptionId) { + Some(ua::SubscriptionId::new(id)) + } else { + None + } } } diff --git a/src/ua/data_types/create_monitored_items_response.rs b/src/ua/data_types/create_monitored_items_response.rs index 0a28c841..7c1cd1e0 100644 --- a/src/ua/data_types/create_monitored_items_response.rs +++ b/src/ua/data_types/create_monitored_items_response.rs @@ -1,14 +1,8 @@ use crate::ua; crate::data_type!(CreateMonitoredItemsResponse); - -impl CreateMonitoredItemsResponse { - #[expect(dead_code, reason = "unused for now")] - pub(crate) fn results(&self) -> Option<&[ua::MonitoredItemCreateResult]> { - unsafe { ua::Array::slice_from_raw_parts(self.0.resultsSize, self.0.results) } - } - - pub(crate) fn into_results(mut self) -> Option> { - unsafe { ua::Array::move_from_raw_parts(&mut self.0.resultsSize, &mut self.0.results) } - } -} +crate::member_accessors!(CreateMonitoredItemsResponse { + results: [ua::MonitoredItemCreateResult], + diagnosticInfos: [ua::DiagnosticInfo], + responseHeader: &ua::ResponseHeader +}); diff --git a/src/ua/data_types/create_subscription_request.rs b/src/ua/data_types/create_subscription_request.rs index fca2e4dc..f4cafc5b 100644 --- a/src/ua/data_types/create_subscription_request.rs +++ b/src/ua/data_types/create_subscription_request.rs @@ -2,7 +2,18 @@ use std::{num::NonZeroU32, time::Duration}; use open62541_sys::UA_CreateSubscriptionRequest_default; +use crate::ua; + crate::data_type!(CreateSubscriptionRequest); +crate::member_accessors!(CreateSubscriptionRequest { + requestHeader: &ua::RequestHeader, + requestedPublishingInterval: f64, + requestedLifetimeCount: u32, + requestedMaxKeepAliveCount: u32, + maxNotificationsPerPublish: u32, + publishingEnabled: bool, + priority: u8 +}); impl CreateSubscriptionRequest { /// Sets requested publishing interval. diff --git a/src/ua/data_types/create_subscription_respones.rs b/src/ua/data_types/create_subscription_respones.rs index 8fea40e6..7f8069d4 100644 --- a/src/ua/data_types/create_subscription_respones.rs +++ b/src/ua/data_types/create_subscription_respones.rs @@ -3,6 +3,15 @@ use std::time::Duration; use crate::{Error, Result, ua}; crate::data_type!(CreateSubscriptionResponse); +crate::member_accessors!(CreateSubscriptionResponse { + #[skip(uses = Self::subscription_id)] + subscriptionId: ua::SubscriptionId, + revisedLifetimeCount: u32, + revisedMaxKeepAliveCount: u32, + #[skip(uses = Self::revised_publishing_interval)] + revisedPublishingInterval: f64, + responseHeader: &ua::ResponseHeader +}); impl CreateSubscriptionResponse { #[must_use] @@ -23,16 +32,4 @@ impl CreateSubscriptionResponse { Duration::try_from_secs_f64(self.0.revisedPublishingInterval / 1e3) .map_err(|_| Error::internal("invalid revised publishing interval")) } - - /// Gets revised lifetime count. - #[must_use] - pub const fn revised_lifetime_count(&self) -> u32 { - self.0.revisedLifetimeCount - } - - /// Gets revised maximum keep-alive count. - #[must_use] - pub const fn revised_max_keep_alive_count(&self) -> u32 { - self.0.revisedMaxKeepAliveCount - } } diff --git a/src/ua/data_types/data_change_filter.rs b/src/ua/data_types/data_change_filter.rs index ec55a84f..0e74bc9d 100644 --- a/src/ua/data_types/data_change_filter.rs +++ b/src/ua/data_types/data_change_filter.rs @@ -1,6 +1,12 @@ use crate::{MonitoringFilter, ua}; crate::data_type!(DataChangeFilter); +crate::member_accessors!(DataChangeFilter { + deadbandType: u32, + deadbandValue: f64, + #[enum] + trigger: ua::DataChangeTrigger +}); impl MonitoringFilter for DataChangeFilter { fn to_extension_object(&self) -> ua::ExtensionObject { diff --git a/src/ua/data_types/data_change_trigger.rs b/src/ua/data_types/data_change_trigger.rs new file mode 100644 index 00000000..e22ef994 --- /dev/null +++ b/src/ua/data_types/data_change_trigger.rs @@ -0,0 +1,7 @@ +crate::data_type!(DataChangeTrigger, UInt32); + +crate::enum_variants!( + DataChangeTrigger, + UA_DataChangeTrigger, + [STATUS, STATUSVALUE, STATUSVALUETIMESTAMP,] +); diff --git a/src/ua/data_types/data_value.rs b/src/ua/data_types/data_value.rs index 540cf332..2f3aa7f5 100644 --- a/src/ua/data_types/data_value.rs +++ b/src/ua/data_types/data_value.rs @@ -1,6 +1,20 @@ use crate::{DataType, DataTypeExt, ua}; crate::data_type!(DataValue); +crate::member_accessors!(DataValue { + #[skip(uses = Self::value)] + value: &ua::Variant, + #[skip(uses = Self::server_timestamp)] + serverTimestamp: ua::DateTime, + #[skip(uses = Self::source_timestamp)] + sourceTimestamp: ua::DateTime, + #[skip(uses = Self::server_picoseconds)] + serverPicoseconds: u16, + #[skip(uses = Self::source_picoseconds)] + sourcePicoseconds: u16, + #[skip(uses = Self::status)] + status: ua::StatusCode +}); impl DataValue { #[must_use] diff --git a/src/ua/data_types/date_time.rs b/src/ua/data_types/date_time.rs index 081e1395..8bc0f0f5 100644 --- a/src/ua/data_types/date_time.rs +++ b/src/ua/data_types/date_time.rs @@ -2,7 +2,10 @@ use open62541_sys::{UA_DATETIME_UNIX_EPOCH, UA_DATETIME_USEC}; use crate::Error; -crate::data_type!(DateTime); +crate::data_type::data_type_baseline! { + #[derive(Copy, Clone)] + DateTime +} impl DateTime { /// Creates [`DateTime`] from a UNIX timestamp with nanosecond precision. @@ -42,6 +45,11 @@ impl DateTime { unix_ticks * i128::from(1000 / UA_DATETIME_USEC) } + + #[must_use] + pub(crate) const fn as_i64(self) -> i64 { + self.0 + } } #[cfg(feature = "time")] diff --git a/src/ua/data_types/delete_monitored_items_request.rs b/src/ua/data_types/delete_monitored_items_request.rs index 56988417..db36b48f 100644 --- a/src/ua/data_types/delete_monitored_items_request.rs +++ b/src/ua/data_types/delete_monitored_items_request.rs @@ -1,6 +1,11 @@ use crate::{UaIterExt as _, ua}; crate::data_type!(DeleteMonitoredItemsRequest); +crate::member_accessors!(DeleteMonitoredItemsRequest { + #[skip(uses = Self::subscription_id)] + subscriptionId: ua::SubscriptionId, + monitoredItemIds: [ua::UInt32] +}); impl DeleteMonitoredItemsRequest { #[must_use] @@ -19,6 +24,16 @@ impl DeleteMonitoredItemsRequest { &mut self.0.monitoredItemIdsSize, &mut self.0.monitoredItemIds, ); + self } + + #[must_use] + pub const fn subscription_id(&self) -> Option { + if let Some(id) = ua::IntegerId::from_u32(self.0.subscriptionId) { + Some(ua::SubscriptionId::new(id)) + } else { + None + } + } } diff --git a/src/ua/data_types/delete_subscriptions_request.rs b/src/ua/data_types/delete_subscriptions_request.rs index 1a7466b6..7da30513 100644 --- a/src/ua/data_types/delete_subscriptions_request.rs +++ b/src/ua/data_types/delete_subscriptions_request.rs @@ -1,6 +1,10 @@ use crate::{UaIterExt as _, ua}; crate::data_type!(DeleteSubscriptionsRequest); +crate::member_accessors!(DeleteSubscriptionsRequest { + subscriptionIds: [ua::UInt32], + requestHeader: &mut ua::RequestHeader +}); impl DeleteSubscriptionsRequest { #[must_use] @@ -10,6 +14,7 @@ impl DeleteSubscriptionsRequest { .map(|subscription_id| subscription_id.as_id().to_uint32()) .collect_ua_array() .move_into_raw(&mut self.0.subscriptionIdsSize, &mut self.0.subscriptionIds); + self } } diff --git a/src/ua/data_types/diagnostic_info.rs b/src/ua/data_types/diagnostic_info.rs new file mode 100644 index 00000000..3a393b97 --- /dev/null +++ b/src/ua/data_types/diagnostic_info.rs @@ -0,0 +1,2 @@ +crate::data_type!(DiagnosticInfo); +// TODO: come back and manually impl safe tagged union field access diff --git a/src/ua/data_types/element_operand.rs b/src/ua/data_types/element_operand.rs index f08a0b79..344c0946 100644 --- a/src/ua/data_types/element_operand.rs +++ b/src/ua/data_types/element_operand.rs @@ -1,6 +1,7 @@ use crate::{FilterOperand, ua}; crate::data_type!(ElementOperand); +crate::member_accessors!(ElementOperand { index: u32 }); impl FilterOperand for ElementOperand { fn to_extension_object(&self) -> ua::ExtensionObject { diff --git a/src/ua/data_types/endpoint_description.rs b/src/ua/data_types/endpoint_description.rs index 437288ea..cfb0305a 100644 --- a/src/ua/data_types/endpoint_description.rs +++ b/src/ua/data_types/endpoint_description.rs @@ -1,40 +1,15 @@ -use crate::{DataType, ua}; +use crate::ua; crate::data_type!(EndpointDescription); - -impl EndpointDescription { - #[must_use] - pub fn endpoint_url(&self) -> &ua::String { - ua::String::raw_ref(&self.0.endpointUrl) - } - - #[must_use] - pub fn server(&self) -> &ua::ApplicationDescription { - ua::ApplicationDescription::raw_ref(&self.0.server) - } - - #[must_use] - pub fn server_certificate(&self) -> &ua::ByteString { - ua::ByteString::raw_ref(&self.0.serverCertificate) - } - - #[must_use] - pub fn security_mode(&self) -> &ua::MessageSecurityMode { - ua::MessageSecurityMode::raw_ref(&self.0.securityMode) - } - - #[must_use] - pub fn security_policy_uri(&self) -> &ua::String { - ua::String::raw_ref(&self.0.securityPolicyUri) - } - - #[must_use] - pub fn transport_profile_uri(&self) -> &ua::String { - ua::String::raw_ref(&self.0.transportProfileUri) - } - - #[must_use] - pub const fn security_level(&self) -> ua::SecurityLevel { - ua::SecurityLevel::new(self.0.securityLevel) - } -} +crate::member_accessors!(EndpointDescription { + endpointUrl: &ua::String, + securityPolicyUri: &ua::String, + transportProfileUri: &ua::String, + server: &ua::ApplicationDescription, + serverCertificate: &ua::ByteString, + #[enum] + securityMode: ua::MessageSecurityMode, + #[from_inner] + securityLevel: ua::SecurityLevel, + userIdentityTokens: [ua::UserTokenPolicy] +}); diff --git a/src/ua/data_types/enum_definition.rs b/src/ua/data_types/enum_definition.rs index 5f5bf97f..147f6ce6 100644 --- a/src/ua/data_types/enum_definition.rs +++ b/src/ua/data_types/enum_definition.rs @@ -1,11 +1,6 @@ use crate::ua; crate::data_type!(EnumDefinition); - -impl EnumDefinition { - #[must_use] - pub fn fields(&self) -> Option> { - // TODO: Adjust signature to return non-owned value instead. - ua::Array::from_raw_parts(self.0.fieldsSize, self.0.fields) - } -} +crate::member_accessors!(EnumDefinition { + fields: [ua::EnumField] +}); diff --git a/src/ua/data_types/enum_description.rs b/src/ua/data_types/enum_description.rs index 5672ce5e..690ef638 100644 --- a/src/ua/data_types/enum_description.rs +++ b/src/ua/data_types/enum_description.rs @@ -3,6 +3,12 @@ use open62541_sys::UA_EnumDescription; use crate::{DataType as _, ua}; crate::data_type!(EnumDescription); +crate::member_accessors!(EnumDescription { + dataTypeId: &ua::NodeId, + name: &ua::QualifiedName, + enumDefinition: &ua::EnumDefinition, + builtInType: u8 +}); impl EnumDescription { // TODO: Find abstraction for `built_in_type`. @@ -20,26 +26,4 @@ impl EnumDescription { builtInType: built_in_type, }) } - - #[must_use] - pub fn data_type_id(&self) -> &ua::NodeId { - ua::NodeId::raw_ref(&self.0.dataTypeId) - } - - #[must_use] - pub fn name(&self) -> &ua::QualifiedName { - ua::QualifiedName::raw_ref(&self.0.name) - } - - #[must_use] - pub fn enum_definition(&self) -> &ua::EnumDefinition { - ua::EnumDefinition::raw_ref(&self.0.enumDefinition) - } - - // TODO: Encapsulate in better return type. - #[expect(dead_code, reason = "unused for now")] - #[must_use] - pub(crate) const fn built_in_type(&self) -> u8 { - self.0.builtInType - } } diff --git a/src/ua/data_types/enum_field.rs b/src/ua/data_types/enum_field.rs index 4a66c4b8..f5222836 100644 --- a/src/ua/data_types/enum_field.rs +++ b/src/ua/data_types/enum_field.rs @@ -1,25 +1,9 @@ -use crate::{DataType as _, ua}; +use crate::ua; crate::data_type!(EnumField); - -impl EnumField { - #[must_use] - pub fn description(&self) -> &ua::LocalizedText { - ua::LocalizedText::raw_ref(&self.0.description) - } - - #[must_use] - pub fn display_name(&self) -> &ua::LocalizedText { - ua::LocalizedText::raw_ref(&self.0.displayName) - } - - #[must_use] - pub fn name(&self) -> &ua::String { - ua::String::raw_ref(&self.0.name) - } - - #[must_use] - pub const fn value(&self) -> i64 { - self.0.value - } -} +crate::member_accessors!(EnumField { + description: &ua::LocalizedText, + displayName: &ua::LocalizedText, + name: &ua::String, + value: i64 +}); diff --git a/src/ua/data_types/eu_information.rs b/src/ua/data_types/eu_information.rs index 24d47f81..da122042 100644 --- a/src/ua/data_types/eu_information.rs +++ b/src/ua/data_types/eu_information.rs @@ -2,23 +2,13 @@ //! //! See also: -use crate::{DataType as _, ua}; +use crate::ua; crate::data_type!(EUInformation); - -impl EUInformation { - #[must_use] - pub const fn unit_id(&self) -> ua::UnitId { - ua::UnitId::new(self.0.unitId) - } - - #[must_use] - pub fn display_name(&self) -> &ua::LocalizedText { - ua::LocalizedText::raw_ref(&self.0.displayName) - } - - #[must_use] - pub fn description(&self) -> &ua::LocalizedText { - ua::LocalizedText::raw_ref(&self.0.description) - } -} +crate::member_accessors!(EUInformation { + #[from_inner] + unitId: ua::UnitId, + displayName: &ua::LocalizedText, + description: &ua::LocalizedText, + namespaceUri: &ua::String +}); diff --git a/src/ua/data_types/event_filter.rs b/src/ua/data_types/event_filter.rs index 463fba6d..966f6d06 100644 --- a/src/ua/data_types/event_filter.rs +++ b/src/ua/data_types/event_filter.rs @@ -1,12 +1,17 @@ use crate::{DataType as _, MonitoringFilter, ua}; crate::data_type!(EventFilter); +crate::member_accessors!(EventFilter { + selectClauses: [ua::SimpleAttributeOperand], + whereClause: &ua::ContentFilter +}); impl EventFilter { #[must_use] pub fn with_select_clauses(mut self, select_clauses: &[ua::SimpleAttributeOperand]) -> Self { - let array = ua::Array::from_slice(select_clauses); - array.move_into_raw(&mut self.0.selectClausesSize, &mut self.0.selectClauses); + ua::Array::from_slice(select_clauses) + .move_into_raw(&mut self.0.selectClausesSize, &mut self.0.selectClauses); + self } diff --git a/src/ua/data_types/expanded_node_id.rs b/src/ua/data_types/expanded_node_id.rs index f6b798c1..ced9244a 100644 --- a/src/ua/data_types/expanded_node_id.rs +++ b/src/ua/data_types/expanded_node_id.rs @@ -8,6 +8,11 @@ use open62541_sys::{ use crate::{DataType as _, Error, ua}; crate::data_type!(ExpandedNodeId); +crate::member_accessors!(ExpandedNodeId { + namespaceUri: &ua::String, + serverIndex: u32, + nodeId: &ua::NodeId +}); impl ExpandedNodeId { /// Creates expanded node ID. @@ -54,21 +59,6 @@ impl ExpandedNodeId { Self(inner) } - - #[must_use] - pub fn node_id(&self) -> &ua::NodeId { - ua::NodeId::raw_ref(&self.0.nodeId) - } - - #[must_use] - pub fn namespace_uri(&self) -> &ua::String { - ua::String::raw_ref(&self.0.namespaceUri) - } - - #[must_use] - pub const fn server_index(&self) -> u32 { - self.0.serverIndex - } } impl From for ExpandedNodeId { @@ -106,7 +96,7 @@ impl str::FromStr for ExpandedNodeId { let str = unsafe { ua::String::to_raw_copy(&str) }; unsafe { UA_ExpandedNodeId_parse(node_id.as_mut_ptr(), str) } }); - Error::verify_good(&status_code)?; + Error::verify_good(status_code)?; Ok(node_id) } @@ -116,7 +106,7 @@ impl fmt::Display for ExpandedNodeId { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let mut output = ua::String::init(); - let status_code = &ua::StatusCode::new({ + let status_code = ua::StatusCode::new({ // This mirrors the behavior of `UA_ExpandedNodeId_parse()` above. unsafe { UA_ExpandedNodeId_print(self.as_ptr(), output.as_mut_ptr()) } }); diff --git a/src/ua/data_types/extension_object.rs b/src/ua/data_types/extension_object.rs index f653780d..9535b2ea 100644 --- a/src/ua/data_types/extension_object.rs +++ b/src/ua/data_types/extension_object.rs @@ -2,9 +2,16 @@ use std::ffi::c_void; use open62541_sys::{UA_ExtensionObject_setValueCopy, UA_ExtensionObjectEncoding}; -use crate::{DataType, ua}; +use crate::{ + DataType, + ua::{self, data_types::extension_object_encoding::ExtensionObjectEncoding}, +}; crate::data_type!(ExtensionObject); +crate::member_accessors!(ExtensionObject { + #[enum] + encoding: ua::ExtensionObjectEncoding +}); impl ExtensionObject { /// Creates extension object from value. @@ -29,12 +36,12 @@ impl ExtensionObject { /// Gets encoded type ID. #[must_use] - pub fn encoded_type_id(&self) -> Option<&ua::NodeId> { + pub const fn encoded_type_id(&self) -> Option<&ua::NodeId> { if !matches!( - self.0.encoding, - UA_ExtensionObjectEncoding::UA_EXTENSIONOBJECT_ENCODED_NOBODY - | UA_ExtensionObjectEncoding::UA_EXTENSIONOBJECT_ENCODED_BYTESTRING - | UA_ExtensionObjectEncoding::UA_EXTENSIONOBJECT_ENCODED_XML + self.encoding().as_u32(), + ExtensionObjectEncoding::ENCODED_NOBODY_U32 + | ExtensionObjectEncoding::ENCODED_BYTESTRING_U32 + | ExtensionObjectEncoding::ENCODED_XML_U32 ) { return None; } @@ -42,7 +49,7 @@ impl ExtensionObject { // SAFETY: Encoding indicates encoded object, union variant is valid. let encoded_content = unsafe { self.0.content.encoded.as_ref() }; - Some(ua::NodeId::raw_ref(&encoded_content.typeId)) + Some(crate::data_type::data_type_ref(&encoded_content.typeId)) } /// Gets encoded byte string content. diff --git a/src/ua/data_types/extension_object_encoding.rs b/src/ua/data_types/extension_object_encoding.rs new file mode 100644 index 00000000..f843fa56 --- /dev/null +++ b/src/ua/data_types/extension_object_encoding.rs @@ -0,0 +1,65 @@ +use open62541_sys::UA_ExtensionObjectEncoding; + +crate::data_type::data_type_baseline!( + #[derive(Clone, Copy)] + ExtensionObjectEncoding, + #[uses_inner] + UInt32 +); + +impl ExtensionObjectEncoding { + #[must_use] + pub const fn from_ua(value: &UA_ExtensionObjectEncoding) -> Self { + let value = value.0; + + #[cfg(target_os = "windows")] + let value = value as u32; + + Self(value) + } + + #[must_use] + pub const fn as_u32(&self) -> u32 { + self.0 + } +} + +macro_rules! impl_variants { + ($t:ty, $($rest:tt)+) => { + impl $t { + impl_variants!($($rest)+); + } + }; + + ($variant:ident = $discriminant:expr $(, $($rest:tt)+)?) => { + paste::paste! { + pub const $variant: Self = Self(Self::[<$variant _U32>]); + #[expect(clippy::as_conversions, reason = "cross-platform enum implementation, all values are >= 0")] + #[cfg_attr(not(target_os = "windows"), expect(trivial_numeric_casts, reason = "windows enum repr is i32"))] + pub const [<$variant _U32>]: u32 = $discriminant as u32; + #[must_use] + pub const fn [](&self) -> bool { + self.0 == Self::[<$variant _U32>] + } + } + + $(impl_variants!($($rest)+);)? + }; +} + +impl_variants!( + ExtensionObjectEncoding, + ENCODED_NOBODY = UA_ExtensionObjectEncoding::UA_EXTENSIONOBJECT_ENCODED_NOBODY.0, + ENCODED_BYTESTRING = UA_ExtensionObjectEncoding::UA_EXTENSIONOBJECT_ENCODED_BYTESTRING.0, + ENCODED_XML = UA_ExtensionObjectEncoding::UA_EXTENSIONOBJECT_ENCODED_XML.0, + DECODED = UA_ExtensionObjectEncoding::UA_EXTENSIONOBJECT_DECODED.0, + DECODED_NODELETE = UA_ExtensionObjectEncoding::UA_EXTENSIONOBJECT_DECODED_NODELETE.0 +); + +const _: () = { + if std::mem::size_of::() + != std::mem::size_of::() + { + panic!("native `UA_ExtensionObjectEncoding` repr doesn't match Rust repr") + } +}; diff --git a/src/ua/data_types/issued_identity_token.rs b/src/ua/data_types/issued_identity_token.rs index e12c73e3..955ce543 100644 --- a/src/ua/data_types/issued_identity_token.rs +++ b/src/ua/data_types/issued_identity_token.rs @@ -1,6 +1,11 @@ use crate::{DataType as _, ua}; crate::data_type!(IssuedIdentityToken); +crate::member_accessors!(IssuedIdentityToken { + policyId: &ua::String, + tokenData: &ua::ByteString, + encryptionAlgorithm: &ua::String +}); impl IssuedIdentityToken { /// Sets policy ID. diff --git a/src/ua/data_types/literal_operand.rs b/src/ua/data_types/literal_operand.rs index 6ba06c1d..2f5c0d2f 100644 --- a/src/ua/data_types/literal_operand.rs +++ b/src/ua/data_types/literal_operand.rs @@ -1,6 +1,9 @@ use crate::{DataType as _, FilterOperand, ua}; crate::data_type!(LiteralOperand); +crate::member_accessors!(LiteralOperand { + value: &ua::Variant +}); impl LiteralOperand { #[must_use] diff --git a/src/ua/data_types/localized_text.rs b/src/ua/data_types/localized_text.rs index 904229ed..66167c4f 100644 --- a/src/ua/data_types/localized_text.rs +++ b/src/ua/data_types/localized_text.rs @@ -1,6 +1,10 @@ use crate::{DataType as _, Error, ua}; crate::data_type!(LocalizedText); +crate::member_accessors!(LocalizedText { + locale: &ua::String, + text: &ua::String +}); impl LocalizedText { /// Creates localized text from locale and text. @@ -27,14 +31,4 @@ impl LocalizedText { ua::String::new(text)?.move_into_raw(&mut self.0.text); Ok(self) } - - #[must_use] - pub fn locale(&self) -> &ua::String { - ua::String::raw_ref(&self.0.locale) - } - - #[must_use] - pub fn text(&self) -> &ua::String { - ua::String::raw_ref(&self.0.text) - } } diff --git a/src/ua/data_types/monitored_item_create_request.rs b/src/ua/data_types/monitored_item_create_request.rs index 3eab9304..4d35b745 100644 --- a/src/ua/data_types/monitored_item_create_request.rs +++ b/src/ua/data_types/monitored_item_create_request.rs @@ -5,6 +5,12 @@ use open62541_sys::{UA_MonitoredItemCreateRequest_default, UA_NODEID_NUMERIC}; use crate::{DataType as _, MonitoringFilter, ua}; crate::data_type!(MonitoredItemCreateRequest); +crate::member_accessors!(MonitoredItemCreateRequest { + itemToMonitor: &ua::ReadValueId, + #[enum] + monitoringMode: ua::MonitoringMode, + requestedParameters: &ua::MonitoringParameters +}); impl MonitoredItemCreateRequest { /// Sets item to monitor. @@ -27,7 +33,7 @@ impl MonitoredItemCreateRequest { /// /// See [`ua::ReadValueId::with_attribute_id()`]. #[must_use] - pub fn with_attribute_id(mut self, attribute_id: &ua::AttributeId) -> Self { + pub const fn with_attribute_id(mut self, attribute_id: &ua::AttributeId) -> Self { self.0.itemToMonitor.attributeId = attribute_id.as_u32(); self } @@ -91,7 +97,7 @@ impl MonitoredItemCreateRequest { } #[must_use] - pub(crate) fn attribute_id(&self) -> ua::AttributeId { + pub(crate) const fn attribute_id(&self) -> ua::AttributeId { ua::AttributeId::from_u32(self.0.itemToMonitor.attributeId) } } diff --git a/src/ua/data_types/monitored_item_create_result.rs b/src/ua/data_types/monitored_item_create_result.rs index 9498aa01..c3f83873 100644 --- a/src/ua/data_types/monitored_item_create_result.rs +++ b/src/ua/data_types/monitored_item_create_result.rs @@ -3,13 +3,18 @@ use std::time::Duration; use crate::{Error, Result, ua}; crate::data_type!(MonitoredItemCreateResult); +crate::member_accessors!(MonitoredItemCreateResult { + #[from_inner] + statusCode: ua::StatusCode, + #[skip(uses = Self::monitored_item_id)] + monitoredItemId: ua::MonitoredItemId, + revisedQueueSize: u32, + #[skip(uses = Self::revised_sampling_interval)] + revisedSamplingInterval: f64, + filterResult: &ua::ExtensionObject +}); impl MonitoredItemCreateResult { - #[must_use] - pub(crate) const fn status_code(&self) -> ua::StatusCode { - ua::StatusCode::new(self.0.statusCode) - } - #[must_use] pub(crate) const fn monitored_item_id(&self) -> Option { if let Some(id) = ua::IntegerId::from_u32(self.0.monitoredItemId) { @@ -28,10 +33,4 @@ impl MonitoredItemCreateResult { Duration::try_from_secs_f64(self.0.revisedSamplingInterval / 1e3) .map_err(|_| Error::internal("invalid revised sampling interval")) } - - /// Gets revised queue size. - #[must_use] - pub const fn revised_queue_size(&self) -> u32 { - self.0.revisedQueueSize - } } diff --git a/src/ua/data_types/monitoring_parameters.rs b/src/ua/data_types/monitoring_parameters.rs index 76954621..e212f148 100644 --- a/src/ua/data_types/monitoring_parameters.rs +++ b/src/ua/data_types/monitoring_parameters.rs @@ -1,8 +1,16 @@ use std::time::Duration; -use crate::{DataType as _, MonitoringFilter}; +use crate::{DataType as _, MonitoringFilter, ua}; crate::data_type!(MonitoringParameters); +crate::member_accessors!(MonitoringParameters { + filter: &ua::ExtensionObject, + #[skip(uses = Self::sampling_interval)] + samplingInterval: f64, + queueSize: u32, + discardOldest: bool, + clientHandle: u32 +}); impl MonitoringParameters { /// Sets sampling interval. @@ -48,4 +56,9 @@ impl MonitoringParameters { self.0.discardOldest = discard_oldest; self } + + #[must_use] + pub fn sampling_interval(&self) -> Option { + Duration::try_from_secs_f64(self.0.samplingInterval / 1e3).ok() + } } diff --git a/src/ua/data_types/node_attributes.rs b/src/ua/data_types/node_attributes.rs index 2a63b9b7..62188060 100644 --- a/src/ua/data_types/node_attributes.rs +++ b/src/ua/data_types/node_attributes.rs @@ -6,6 +6,15 @@ use open62541_sys::{UA_DataType, UA_NodeAttributes}; use crate::{DataType as _, ua}; crate::data_type!(NodeAttributes); +crate::member_accessors!(NodeAttributes { + description: &ua::LocalizedText, + displayName: &ua::LocalizedText, + #[from_inner] + writeMask: ua::AttributeWriteMask, + #[from_inner] + userWriteMask: ua::AttributeWriteMask, + specifiedAttributes: u32 +}); macro_rules! derived { ($( $name:ident ),* $(,)?) => { diff --git a/src/ua/data_types/node_attributes/variable_attributes.rs b/src/ua/data_types/node_attributes/variable_attributes.rs index b4abeac9..88d90bd0 100644 --- a/src/ua/data_types/node_attributes/variable_attributes.rs +++ b/src/ua/data_types/node_attributes/variable_attributes.rs @@ -23,7 +23,9 @@ impl super::VariableAttributes { .map(ua::UInt32::new) .collect_ua_array() .move_into_raw(&mut self.0.arrayDimensionsSize, &mut self.0.arrayDimensions); + self.0.specifiedAttributes |= ua::SpecifiedAttributes::ARRAYDIMENSIONS.as_u32(); + self } diff --git a/src/ua/data_types/node_id.rs b/src/ua/data_types/node_id.rs index 3282efbc..75bd748a 100644 --- a/src/ua/data_types/node_id.rs +++ b/src/ua/data_types/node_id.rs @@ -8,6 +8,11 @@ use open62541_sys::{ use crate::{DataType, Error, ua}; crate::data_type!(NodeId); +crate::member_accessors!(NodeId { + namespaceIndex: u16, + #[enum] + identifierType: ua::NodeIdType +}); impl NodeId { /// Creates numeric node ID in namespace 0. @@ -99,12 +104,6 @@ impl NodeId { Self::clone_raw(unsafe { &UA_NODEID_NULL }) } - /// Gets namespace index. - #[must_use] - pub const fn namespace_index(&self) -> u16 { - self.0.namespaceIndex - } - /// Checks if this node ID is in namespace 0. /// /// Namespace 0 is always the UA namespace `http://opcfoundation.org/UA/` itself and is used for @@ -140,12 +139,6 @@ impl NodeId { self == Self::raw_ref(unsafe { &UA_NODEID_NULL }) } - /// Gets node ID type. - #[must_use] - pub fn identifier_type(&self) -> &ua::NodeIdType { - ua::NodeIdType::raw_ref(&self.0.identifierType) - } - /// Gets identifier of numeric node ID in namespace 0. /// /// Namespace 0 is always the UA namespace `http://opcfoundation.org/UA/` itself and is used for @@ -226,7 +219,7 @@ impl str::FromStr for NodeId { let str = unsafe { ua::String::to_raw_copy(&str) }; unsafe { UA_NodeId_parse(node_id.as_mut_ptr(), str) } }); - Error::verify_good(&status_code)?; + Error::verify_good(status_code)?; Ok(node_id) } @@ -236,7 +229,7 @@ impl fmt::Display for NodeId { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let mut output = ua::String::init(); - let status_code = &ua::StatusCode::new({ + let status_code = ua::StatusCode::new({ // This mirrors the behavior of `UA_NodeId_parse()` above. unsafe { UA_NodeId_print(self.as_ptr(), output.as_mut_ptr()) } }); diff --git a/src/ua/data_types/qualified_name.rs b/src/ua/data_types/qualified_name.rs index 14549e59..281be1d2 100644 --- a/src/ua/data_types/qualified_name.rs +++ b/src/ua/data_types/qualified_name.rs @@ -5,6 +5,10 @@ use open62541_sys::{UA_QUALIFIEDNAME_ALLOC, UA_QualifiedName_hash}; use crate::{DataType as _, ua}; crate::data_type!(QualifiedName); +crate::member_accessors!(QualifiedName { + namespaceIndex: u16, + name: &ua::String +}); impl QualifiedName { /// Creates qualified name. @@ -34,18 +38,6 @@ impl QualifiedName { Self::new(0, name) } - /// Gets namespace index. - #[must_use] - pub const fn namespace_index(&self) -> u16 { - self.0.namespaceIndex - } - - /// Gets name. - #[must_use] - pub fn name(&self) -> &ua::String { - ua::String::raw_ref(&self.0.name) - } - /// Gets name in namespace 0. /// /// Namespace 0 is always the UA namespace `http://opcfoundation.org/UA/` itself and is used for diff --git a/src/ua/data_types/range.rs b/src/ua/data_types/range.rs index 8a04e1d4..35dc0efb 100644 --- a/src/ua/data_types/range.rs +++ b/src/ua/data_types/range.rs @@ -1,13 +1,5 @@ crate::data_type!(Range); - -impl Range { - #[must_use] - pub const fn low(&self) -> f64 { - self.0.low - } - - #[must_use] - pub const fn high(&self) -> f64 { - self.0.high - } -} +crate::member_accessors!(Range { + low: f64, + high: f64 +}); diff --git a/src/ua/data_types/read_request.rs b/src/ua/data_types/read_request.rs index a9ce7506..eac2fa22 100644 --- a/src/ua/data_types/read_request.rs +++ b/src/ua/data_types/read_request.rs @@ -1,6 +1,12 @@ use crate::{DataType as _, ServiceRequest, ua}; crate::data_type!(ReadRequest); +crate::member_accessors!(ReadRequest { + #[enum] + timestampsToReturn: ua::TimestampsToReturn, + nodesToRead: [ua::ReadValueId], + requestHeader: &mut ua::RequestHeader +}); impl ReadRequest { #[must_use] @@ -14,8 +20,9 @@ impl ReadRequest { #[must_use] pub fn with_nodes_to_read(mut self, nodes_to_read: &[ua::ReadValueId]) -> Self { - let array = ua::Array::from_slice(nodes_to_read); - array.move_into_raw(&mut self.0.nodesToReadSize, &mut self.0.nodesToRead); + ua::Array::from_slice(nodes_to_read) + .move_into_raw(&mut self.0.nodesToReadSize, &mut self.0.nodesToRead); + self } } diff --git a/src/ua/data_types/read_response.rs b/src/ua/data_types/read_response.rs index 77655f38..ff214fbc 100644 --- a/src/ua/data_types/read_response.rs +++ b/src/ua/data_types/read_response.rs @@ -1,14 +1,10 @@ use crate::{DataType as _, ServiceResponse, ua}; crate::data_type!(ReadResponse); - -impl ReadResponse { - #[must_use] - pub fn results(&self) -> Option> { - // TODO: Adjust signature to return non-owned value instead. - ua::Array::from_raw_parts(self.0.resultsSize, self.0.results) - } -} +crate::member_accessors!(ReadResponse { + results: [ua::DataValue], + responseHeader: &ua::ResponseHeader +}); impl ServiceResponse for ReadResponse { type Request = ua::ReadRequest; diff --git a/src/ua/data_types/read_value_id.rs b/src/ua/data_types/read_value_id.rs index c1ed432d..f69f8fdf 100644 --- a/src/ua/data_types/read_value_id.rs +++ b/src/ua/data_types/read_value_id.rs @@ -1,6 +1,13 @@ use crate::{DataType as _, ua}; crate::data_type!(ReadValueId); +crate::member_accessors!(ReadValueId { + nodeId: &ua::NodeId, + #[skip(uses = Self::attribute_id)] + attributeId: ua::AttributeId, + dataEncoding: &ua::QualifiedName, + indexRange: &ua::String +}); impl ReadValueId { #[must_use] @@ -10,8 +17,18 @@ impl ReadValueId { } #[must_use] - pub fn with_attribute_id(mut self, attribute_id: &ua::AttributeId) -> Self { + pub const fn with_attribute_id(mut self, attribute_id: &ua::AttributeId) -> Self { self.0.attributeId = attribute_id.as_u32(); self } + + #[must_use] + pub const fn attribute_id(&self) -> ua::AttributeId { + let value = self.0.attributeId; + + #[cfg(target_os = "windows")] + let value = value as u32; + + ua::AttributeId::from_u32(value) + } } diff --git a/src/ua/data_types/reference_description.rs b/src/ua/data_types/reference_description.rs index 55f8caec..5fbdac99 100644 --- a/src/ua/data_types/reference_description.rs +++ b/src/ua/data_types/reference_description.rs @@ -1,40 +1,13 @@ -use crate::{DataType as _, ua}; +use crate::ua; crate::data_type!(ReferenceDescription); - -impl ReferenceDescription { - #[must_use] - pub fn reference_type_id(&self) -> &ua::NodeId { - ua::NodeId::raw_ref(&self.0.referenceTypeId) - } - - #[must_use] - pub const fn is_forward(&self) -> bool { - self.0.isForward - } - - #[must_use] - pub fn node_id(&self) -> &ua::ExpandedNodeId { - ua::ExpandedNodeId::raw_ref(&self.0.nodeId) - } - - #[must_use] - pub fn browse_name(&self) -> &ua::QualifiedName { - ua::QualifiedName::raw_ref(&self.0.browseName) - } - - #[must_use] - pub fn display_name(&self) -> &ua::LocalizedText { - ua::LocalizedText::raw_ref(&self.0.displayName) - } - - #[must_use] - pub fn node_class(&self) -> &ua::NodeClass { - ua::NodeClass::raw_ref(&self.0.nodeClass) - } - - #[must_use] - pub fn type_definition(&self) -> &ua::ExpandedNodeId { - ua::ExpandedNodeId::raw_ref(&self.0.typeDefinition) - } -} +crate::member_accessors!(ReferenceDescription { + referenceTypeId: &ua::NodeId, + isForward: bool, + nodeId: &ua::ExpandedNodeId, + browseName: &ua::QualifiedName, + displayName: &ua::LocalizedText, + typeDefinition: &ua::ExpandedNodeId, + #[enum] + nodeClass: ua::NodeClass +}); diff --git a/src/ua/data_types/relative_path.rs b/src/ua/data_types/relative_path.rs index 9d6aa024..cb9b7cf8 100644 --- a/src/ua/data_types/relative_path.rs +++ b/src/ua/data_types/relative_path.rs @@ -5,33 +5,17 @@ use open62541_sys::{UA_RelativePath_parse, UA_RelativePath_print}; use crate::{DataType as _, Error, ua}; crate::data_type!(RelativePath); +crate::member_accessors!(RelativePath { + elements: [ua::RelativePathElement] +}); impl RelativePath { #[must_use] pub fn with_elements(mut self, elements: &[ua::RelativePathElement]) -> Self { - let array = ua::Array::from_slice(elements); - array.move_into_raw(&mut self.0.elementsSize, &mut self.0.elements); - self - } - - /// Returns a view of the elements that make up this relative path. - /// - /// Returns [`Some`] if the relative path element array is valid, otherwise [`None`]. - #[must_use] - pub fn elements(&self) -> Option<&[ua::RelativePathElement]> { - // SAFETY: Lifetime of the slice is implicitly bound to the lifetime of the reference to self. - // Pointer validity is checked in `Array::slice_from_raw_parts()`. - unsafe { ua::Array::slice_from_raw_parts(self.0.elementsSize, self.0.elements) } - } + ua::Array::from_slice(elements) + .move_into_raw(&mut self.0.elementsSize, &mut self.0.elements); - /// Returns a mutable view of the elements that make up this relative path. - /// - /// Returns [`Some`] if the relative path element array is valid, otherwise [`None`]. - #[must_use] - pub fn elements_mut(&mut self) -> Option<&mut [ua::RelativePathElement]> { - // SAFETY: Lifetime of the slice is implicitly bound to the lifetime of the reference to self. - // Pointer validity is checked in `Array::slice_from_raw_parts_mut()`. - unsafe { ua::Array::slice_from_raw_parts_mut(self.0.elementsSize, self.0.elements) } + self } /// Attempts to access the element at `index`. @@ -45,18 +29,6 @@ impl RelativePath { self.elements().and_then(|elements| elements.get(index)) } - /// Attempts to access the element at `index` mutably. - /// - /// Returns [`Some`] if the array is valid and `index` < [`RelativePath::len()`], otherwise [`None`] - #[must_use] - pub fn get_mut(&mut self, index: I) -> Option<&mut I::Output> - where - I: SliceIndex<[ua::RelativePathElement]>, - { - self.elements_mut() - .and_then(|elements| elements.get_mut(index)) - } - /// Returns the number of elements in the relative path. #[must_use] pub const fn len(&self) -> usize { @@ -94,10 +66,6 @@ impl RelativePath { pub fn iter(&self) -> impl ExactSizeIterator { self.elements().unwrap_or(&[]).iter() } - - pub fn iter_mut(&mut self) -> impl ExactSizeIterator { - self.elements_mut().unwrap_or(&mut []).iter_mut() - } } impl FromStr for RelativePath { diff --git a/src/ua/data_types/relative_path_element.rs b/src/ua/data_types/relative_path_element.rs index 094f4f02..32e49392 100644 --- a/src/ua/data_types/relative_path_element.rs +++ b/src/ua/data_types/relative_path_element.rs @@ -1,6 +1,12 @@ use crate::{DataType as _, ua}; crate::data_type!(RelativePathElement); +crate::member_accessors!(RelativePathElement { + referenceTypeId: &ua::NodeId, + isInverse: bool, + includeSubtypes: bool, + targetName: &ua::QualifiedName +}); impl RelativePathElement { #[must_use] @@ -26,24 +32,4 @@ impl RelativePathElement { target_name.clone_into_raw(&mut self.0.targetName); self } - - #[must_use] - pub fn target_name(&self) -> &ua::QualifiedName { - ua::QualifiedName::raw_ref(&self.0.targetName) - } - - #[must_use] - pub const fn is_inverse(&self) -> bool { - self.0.isInverse - } - - #[must_use] - pub const fn include_subtypes(&self) -> bool { - self.0.includeSubtypes - } - - #[must_use] - pub fn reference_type_id(&self) -> &ua::NodeId { - ua::NodeId::raw_ref(&self.0.referenceTypeId) - } } diff --git a/src/ua/data_types/request_header.rs b/src/ua/data_types/request_header.rs index 87ab9b90..755ab9f3 100644 --- a/src/ua/data_types/request_header.rs +++ b/src/ua/data_types/request_header.rs @@ -1,6 +1,19 @@ use std::time::Duration; +use crate::ua; + crate::data_type!(RequestHeader); +crate::member_accessors!(RequestHeader { + auditEntryId: &ua::String, + #[skip(uses = Self::timeout_hint)] + timeoutHint: Duration, + requestHandle: u32, + #[from_inner] + timestamp: ua::DateTime, + authenticationToken: &ua::NodeId, + additionalHeader: &ua::ExtensionObject, + returnDiagnostics: u32 +}); impl RequestHeader { /// Gets the timeout hint. diff --git a/src/ua/data_types/response_header.rs b/src/ua/data_types/response_header.rs index 30ab5e12..1e1d8e1a 100644 --- a/src/ua/data_types/response_header.rs +++ b/src/ua/data_types/response_header.rs @@ -1,11 +1,13 @@ use crate::ua; crate::data_type!(ResponseHeader); - -impl ResponseHeader { - /// Returns the resulting status. - #[must_use] - pub const fn service_result(&self) -> ua::StatusCode { - ua::StatusCode::new(self.0.serviceResult) - } -} +crate::member_accessors!(ResponseHeader { + additionalHeader: &ua::ExtensionObject, + requestHandle: u32, + #[from_inner] + timestamp: ua::DateTime, + serviceDiagnostics: &ua::DiagnosticInfo, + #[from_inner] + serviceResult: ua::StatusCode, + stringTable: [ua::String] +}); diff --git a/src/ua/data_types/server_state.rs b/src/ua/data_types/server_state.rs index ea847296..6e3c224d 100644 --- a/src/ua/data_types/server_state.rs +++ b/src/ua/data_types/server_state.rs @@ -1 +1,15 @@ crate::data_type!(ServerState); +crate::enum_variants!( + ServerState, + UA_ServerState, + [ + FAILED, + NOCONFIGURATION, + SUSPENDED, + RUNNING, + SHUTDOWN, + TEST, + COMMUNICATIONFAULT, + UNKNOWN, + ] +); diff --git a/src/ua/data_types/server_status_data_type.rs b/src/ua/data_types/server_status_data_type.rs index 77579fbf..f3f92453 100644 --- a/src/ua/data_types/server_status_data_type.rs +++ b/src/ua/data_types/server_status_data_type.rs @@ -1,37 +1,14 @@ -use crate::{DataType as _, ua}; +use crate::ua; crate::data_type!(ServerStatusDataType); - -impl ServerStatusDataType { - #[must_use] - pub fn build_info(&self) -> &ua::BuildInfo { - ua::BuildInfo::raw_ref(&self.0.buildInfo) - } - - #[must_use] - pub fn state(&self) -> &ua::ServerState { - ua::ServerState::raw_ref(&self.0.state) - } - - #[must_use] - pub fn start_time(&self) -> ua::DateTime { - // SAFETY: The i64 value represents a valid UtcTime. - unsafe { ua::DateTime::from_raw(self.0.startTime) } - } - - #[must_use] - pub fn current_time(&self) -> ua::DateTime { - // SAFETY: The i64 value represents a valid UtcTime. - unsafe { ua::DateTime::from_raw(self.0.currentTime) } - } - - #[must_use] - pub const fn seconds_till_shutdown(&self) -> u32 { - self.0.secondsTillShutdown - } - - #[must_use] - pub fn shutdown_reason(&self) -> &ua::LocalizedText { - ua::LocalizedText::raw_ref(&self.0.shutdownReason) - } -} +crate::member_accessors!(ServerStatusDataType { + buildInfo: &ua::BuildInfo, + #[enum] + state: ua::ServerState, + #[from_inner] + startTime: ua::DateTime, + #[from_inner] + currentTime: ua::DateTime, + secondsTillShutdown: u32, + shutdownReason: &ua::LocalizedText +}); diff --git a/src/ua/data_types/simple_attribute_operand.rs b/src/ua/data_types/simple_attribute_operand.rs index e88fe689..cbae415d 100644 --- a/src/ua/data_types/simple_attribute_operand.rs +++ b/src/ua/data_types/simple_attribute_operand.rs @@ -1,6 +1,13 @@ use crate::{DataType, FilterOperand, ua}; crate::data_type!(SimpleAttributeOperand); +crate::member_accessors!(SimpleAttributeOperand { + typeDefinitionId: &ua::NodeId, + browsePath: [ua::QualifiedName], + #[skip(uses = Self::attribute_id)] + attributeId: ua::AttributeId, + indexRange: &ua::String +}); impl SimpleAttributeOperand { #[must_use] @@ -11,13 +18,14 @@ impl SimpleAttributeOperand { #[must_use] pub fn with_browse_path(mut self, browse_path: &[ua::QualifiedName]) -> Self { - let array = ua::Array::from_slice(browse_path); - array.move_into_raw(&mut self.0.browsePathSize, &mut self.0.browsePath); + ua::Array::from_slice(browse_path) + .move_into_raw(&mut self.0.browsePathSize, &mut self.0.browsePath); + self } #[must_use] - pub fn with_attribute_id(mut self, attribute_id: &ua::AttributeId) -> Self { + pub const fn with_attribute_id(mut self, attribute_id: &ua::AttributeId) -> Self { self.0.attributeId = attribute_id.as_u32(); self } @@ -27,6 +35,16 @@ impl SimpleAttributeOperand { index_range.move_into_raw(&mut self.0.indexRange); self } + + #[must_use] + pub const fn attribute_id(&self) -> ua::AttributeId { + let value = self.0.attributeId; + + #[cfg(target_os = "windows")] + let value = value as u32; + + ua::AttributeId::from_u32(value) + } } impl FilterOperand for SimpleAttributeOperand { diff --git a/src/ua/data_types/status_code.rs b/src/ua/data_types/status_code.rs index 231a4440..da9768f2 100644 --- a/src/ua/data_types/status_code.rs +++ b/src/ua/data_types/status_code.rs @@ -5,7 +5,10 @@ use open62541_sys::{ UA_StatusCode_name, }; -crate::data_type!(StatusCode); +crate::data_type::data_type_baseline!( + #[derive(Copy, Clone)] + StatusCode +); impl StatusCode { /// Creates wrapper by taking ownership of `src`. @@ -31,7 +34,7 @@ impl StatusCode { /// } /// ``` #[must_use] - pub fn is_good(&self) -> bool { + pub fn is_good(self) -> bool { unsafe { UA_StatusCode_isGood(self.0) } } @@ -40,7 +43,7 @@ impl StatusCode { /// Uncertain status codes indicate that the operation was partially successful and that /// associated results might not be suitable for some purposes. #[must_use] - pub fn is_uncertain(&self) -> bool { + pub fn is_uncertain(self) -> bool { unsafe { UA_StatusCode_isUncertain(self.0) } } @@ -49,7 +52,7 @@ impl StatusCode { /// Bad status codes indicate that the operation failed and any associated results cannot be /// used. #[must_use] - pub fn is_bad(&self) -> bool { + pub fn is_bad(self) -> bool { unsafe { UA_StatusCode_isBad(self.0) } } @@ -65,12 +68,12 @@ impl StatusCode { /// assert_eq!(ua::StatusCode::GOOD.name(), "Good"); /// ``` #[must_use] - pub fn name(&self) -> &'static str { + pub fn name(self) -> &'static str { status_code_name(self.0) } /// Gets status code. - pub(crate) const fn code(&self) -> UA_StatusCode { + pub(crate) const fn code(self) -> UA_StatusCode { self.0 } } diff --git a/src/ua/data_types/structure_definition.rs b/src/ua/data_types/structure_definition.rs index c5e434bc..5d67312c 100644 --- a/src/ua/data_types/structure_definition.rs +++ b/src/ua/data_types/structure_definition.rs @@ -1,29 +1,15 @@ -use crate::{DataType, ua}; +use crate::ua; crate::data_type!(StructureDefinition); +crate::member_accessors!(StructureDefinition { + defaultEncodingId: &ua::NodeId, + baseDataType: &ua::NodeId, + #[enum] + structureType: ua::StructureType, + fields: [ua::StructureField] +}); impl StructureDefinition { - #[must_use] - pub fn default_encoding_id(&self) -> &ua::NodeId { - ua::NodeId::raw_ref(&self.0.defaultEncodingId) - } - - #[must_use] - pub fn base_data_type(&self) -> &ua::NodeId { - ua::NodeId::raw_ref(&self.0.baseDataType) - } - - #[must_use] - pub fn structure_type(&self) -> &ua::StructureType { - ua::StructureType::raw_ref(&self.0.structureType) - } - - #[must_use] - pub fn fields(&self) -> Option> { - // TODO: Adjust signature to return non-owned value instead. - ua::Array::from_raw_parts(self.0.fieldsSize, self.0.fields) - } - #[must_use] pub fn into_description( self, diff --git a/src/ua/data_types/structure_description.rs b/src/ua/data_types/structure_description.rs index c79ba27c..b43bfe2e 100644 --- a/src/ua/data_types/structure_description.rs +++ b/src/ua/data_types/structure_description.rs @@ -3,6 +3,11 @@ use open62541_sys::UA_StructureDescription; use crate::{DataType as _, ua}; crate::data_type!(StructureDescription); +crate::member_accessors!(StructureDescription { + dataTypeId: &ua::NodeId, + name: &ua::QualifiedName, + structureDefinition: &ua::StructureDefinition +}); impl StructureDescription { pub(crate) fn new( @@ -16,19 +21,4 @@ impl StructureDescription { structureDefinition: definition.into_raw(), }) } - - #[must_use] - pub fn data_type_id(&self) -> &ua::NodeId { - ua::NodeId::raw_ref(&self.0.dataTypeId) - } - - #[must_use] - pub fn name(&self) -> &ua::QualifiedName { - ua::QualifiedName::raw_ref(&self.0.name) - } - - #[must_use] - pub fn structure_definition(&self) -> &ua::StructureDefinition { - ua::StructureDefinition::raw_ref(&self.0.structureDefinition) - } } diff --git a/src/ua/data_types/structure_field.rs b/src/ua/data_types/structure_field.rs index 1d6c5fd8..1a8aefd1 100644 --- a/src/ua/data_types/structure_field.rs +++ b/src/ua/data_types/structure_field.rs @@ -1,42 +1,12 @@ -use crate::{DataType, ua}; +use crate::ua; crate::data_type!(StructureField); - -impl StructureField { - #[must_use] - pub fn name(&self) -> &ua::String { - ua::String::raw_ref(&self.0.name) - } - - #[must_use] - pub fn description(&self) -> &ua::LocalizedText { - ua::LocalizedText::raw_ref(&self.0.description) - } - - #[must_use] - pub fn data_type(&self) -> &ua::NodeId { - ua::NodeId::raw_ref(&self.0.dataType) - } - - #[must_use] - pub const fn value_rank(&self) -> i32 { - self.0.valueRank - } - - #[must_use] - pub fn array_dimensions(&self) -> Option> { - // TODO: Adjust signature to return non-owned value instead. - ua::Array::::from_raw_parts(self.0.arrayDimensionsSize, self.0.arrayDimensions) - .map(|array_dimensions| array_dimensions.iter().map(ua::UInt32::value).collect()) - } - - #[must_use] - pub const fn max_string_length(&self) -> u32 { - self.0.maxStringLength - } - - #[must_use] - pub const fn is_optional(&self) -> bool { - self.0.isOptional - } -} +crate::member_accessors!(StructureField { + name: &ua::String, + description: &ua::LocalizedText, + dataType: &ua::NodeId, + valueRank: i32, + arrayDimensions: [ua::UInt32], + maxStringLength: u32, + isOptional: bool +}); diff --git a/src/ua/data_types/translate_browse_paths_to_node_ids_request.rs b/src/ua/data_types/translate_browse_paths_to_node_ids_request.rs new file mode 100644 index 00000000..79c67549 --- /dev/null +++ b/src/ua/data_types/translate_browse_paths_to_node_ids_request.rs @@ -0,0 +1,29 @@ +use crate::{ServiceRequest, ua}; + +crate::data_type!(TranslateBrowsePathsToNodeIdsRequest); +crate::member_accessors!(TranslateBrowsePathsToNodeIdsRequest { + browsePaths: [ua::BrowsePath], + requestHeader: &mut ua::RequestHeader +}); + +impl TranslateBrowsePathsToNodeIdsRequest { + #[must_use] + pub fn with_browse_paths(mut self, paths: &[ua::BrowsePath]) -> Self { + ua::Array::from_slice(paths) + .move_into_raw(&mut self.0.browsePathsSize, &mut self.0.browsePaths); + + self + } +} + +impl ServiceRequest for TranslateBrowsePathsToNodeIdsRequest { + type Response = ua::TranslateBrowsePathsToNodeIdsResponse; + + fn request_header(&self) -> &ua::RequestHeader { + Self::request_header(self) + } + + fn request_header_mut(&mut self) -> &mut ua::RequestHeader { + Self::request_header_mut(self) + } +} diff --git a/src/ua/data_types/translate_browse_paths_to_node_ids_response.rs b/src/ua/data_types/translate_browse_paths_to_node_ids_response.rs new file mode 100644 index 00000000..947bdaf7 --- /dev/null +++ b/src/ua/data_types/translate_browse_paths_to_node_ids_response.rs @@ -0,0 +1,16 @@ +use crate::{ServiceResponse, ua}; + +crate::data_type!(TranslateBrowsePathsToNodeIdsResponse); +crate::member_accessors!(TranslateBrowsePathsToNodeIdsResponse { + responseHeader: &ua::ResponseHeader, + results: [ua::BrowsePathResult], + diagnosticInfos: [ua::DiagnosticInfo] +}); + +impl ServiceResponse for TranslateBrowsePathsToNodeIdsResponse { + type Request = ua::TranslateBrowsePathsToNodeIdsRequest; + + fn response_header(&self) -> &ua::ResponseHeader { + Self::response_header(self) + } +} diff --git a/src/ua/data_types/trust_list_data_type.rs b/src/ua/data_types/trust_list_data_type.rs index 823c8c81..f886a998 100644 --- a/src/ua/data_types/trust_list_data_type.rs +++ b/src/ua/data_types/trust_list_data_type.rs @@ -1,59 +1,78 @@ use crate::ua; crate::data_type!(TrustListDataType); +crate::member_accessors!(TrustListDataType { + trustedCertificates: [ua::ByteString], + trustedCrls: [ua::ByteString], + issuerCertificates: [ua::ByteString], + issuerCrls: [ua::ByteString], + specifiedLists: u32 +}); impl TrustListDataType { #[must_use] - pub fn with_trusted_certificates(mut self, trusted_certificates: &[ua::String]) -> Self { + pub fn with_trusted_certificates(mut self, trusted_certificates: &[ua::ByteString]) -> Self { let array = ua::Array::from_slice(trusted_certificates); + if array.is_empty() { self.0.specifiedLists &= !ua::TrustListMasks::TRUSTEDCERTIFICATES.as_u32(); } else { self.0.specifiedLists |= ua::TrustListMasks::TRUSTEDCERTIFICATES.as_u32(); } + array.move_into_raw( &mut self.0.trustedCertificatesSize, &mut self.0.trustedCertificates, ); + self } #[must_use] - pub fn with_trusted_crls(mut self, trusted_crls: &[ua::String]) -> Self { + pub fn with_trusted_crls(mut self, trusted_crls: &[ua::ByteString]) -> Self { let array = ua::Array::from_slice(trusted_crls); + if array.is_empty() { self.0.specifiedLists &= !ua::TrustListMasks::TRUSTEDCRLS.as_u32(); } else { self.0.specifiedLists |= ua::TrustListMasks::TRUSTEDCRLS.as_u32(); } + array.move_into_raw(&mut self.0.trustedCrlsSize, &mut self.0.trustedCrls); + self } #[must_use] - pub fn with_issuer_certificates(mut self, issuer_certificates: &[ua::String]) -> Self { + pub fn with_issuer_certificates(mut self, issuer_certificates: &[ua::ByteString]) -> Self { let array = ua::Array::from_slice(issuer_certificates); + if array.is_empty() { self.0.specifiedLists &= !ua::TrustListMasks::ISSUERCERTIFICATES.as_u32(); } else { self.0.specifiedLists |= ua::TrustListMasks::ISSUERCERTIFICATES.as_u32(); } + array.move_into_raw( &mut self.0.issuerCertificatesSize, &mut self.0.issuerCertificates, ); + self } #[must_use] - pub fn with_issuer_crls(mut self, issuer_crls: &[ua::String]) -> Self { + pub fn with_issuer_crls(mut self, issuer_crls: &[ua::ByteString]) -> Self { let array = ua::Array::from_slice(issuer_crls); + if array.is_empty() { self.0.specifiedLists &= !ua::TrustListMasks::ISSUERCRLS.as_u32(); } else { self.0.specifiedLists |= ua::TrustListMasks::ISSUERCRLS.as_u32(); } + array.move_into_raw(&mut self.0.issuerCrlsSize, &mut self.0.issuerCrls); + self } } diff --git a/src/ua/data_types/user_name_identity_token.rs b/src/ua/data_types/user_name_identity_token.rs index 64dde846..f6463b30 100644 --- a/src/ua/data_types/user_name_identity_token.rs +++ b/src/ua/data_types/user_name_identity_token.rs @@ -1,6 +1,12 @@ use crate::{DataType as _, ua}; crate::data_type!(UserNameIdentityToken); +crate::member_accessors!(UserNameIdentityToken { + policyId: &ua::String, + userName: &ua::String, + password: &ua::ByteString, + encryptionAlgorithm: &ua::String +}); impl UserNameIdentityToken { /// Creates token with user name and password. diff --git a/src/ua/data_types/user_token_policy.rs b/src/ua/data_types/user_token_policy.rs new file mode 100644 index 00000000..bd33bce0 --- /dev/null +++ b/src/ua/data_types/user_token_policy.rs @@ -0,0 +1,11 @@ +use crate::ua; + +crate::data_type!(UserTokenPolicy); +crate::member_accessors!(UserTokenPolicy { + policyId: &ua::String, + #[enum] + tokenType: ua::UserTokenType, + issuedTokenType: &ua::String, + issuerEndpointUrl: &ua::String, + securityPolicyUri: &ua::String +}); diff --git a/src/ua/data_types/user_token_type.rs b/src/ua/data_types/user_token_type.rs new file mode 100644 index 00000000..34b51c8b --- /dev/null +++ b/src/ua/data_types/user_token_type.rs @@ -0,0 +1,6 @@ +crate::data_type!(UserTokenType, UInt32); +crate::enum_variants!( + UserTokenType, + UA_UserTokenType, + [ANONYMOUS, USERNAME, CERTIFICATE, ISSUEDTOKEN,] +); diff --git a/src/ua/data_types/view_description.rs b/src/ua/data_types/view_description.rs new file mode 100644 index 00000000..d26f8deb --- /dev/null +++ b/src/ua/data_types/view_description.rs @@ -0,0 +1,29 @@ +use crate::{DataType, ua}; + +crate::data_type!(ViewDescription); +crate::member_accessors!(ViewDescription { + viewId: &ua::NodeId, + viewVersion: u32, + #[from_inner] + timestamp: ua::DateTime +}); + +impl ViewDescription { + #[must_use] + pub fn with_view_id(mut self, view_id: &ua::NodeId) -> Self { + view_id.clone_into_raw(&mut self.0.viewId); + self + } + + #[must_use] + pub const fn with_view_version(mut self, view_version: u32) -> Self { + self.0.viewVersion = view_version; + self + } + + #[must_use] + pub const fn with_timestamp(mut self, ts: ua::DateTime) -> Self { + self.0.timestamp = ts.as_i64(); + self + } +} diff --git a/src/ua/data_types/write_request.rs b/src/ua/data_types/write_request.rs index 5b0f0d91..d9b0a6dd 100644 --- a/src/ua/data_types/write_request.rs +++ b/src/ua/data_types/write_request.rs @@ -1,12 +1,16 @@ -use crate::{DataType as _, ServiceRequest, ua}; +use crate::{ServiceRequest, ua}; crate::data_type!(WriteRequest); +crate::member_accessors!(WriteRequest { + nodesToWrite: [ua::WriteValue], + requestHeader: &mut ua::RequestHeader +}); impl WriteRequest { #[must_use] pub fn with_nodes_to_write(mut self, nodes_to_write: &[ua::WriteValue]) -> Self { - let array = ua::Array::from_slice(nodes_to_write); - array.move_into_raw(&mut self.0.nodesToWriteSize, &mut self.0.nodesToWrite); + ua::Array::from_slice(nodes_to_write) + .move_into_raw(&mut self.0.nodesToWriteSize, &mut self.0.nodesToWrite); self } } @@ -15,10 +19,10 @@ impl ServiceRequest for WriteRequest { type Response = ua::WriteResponse; fn request_header(&self) -> &ua::RequestHeader { - ua::RequestHeader::raw_ref(&self.0.requestHeader) + Self::request_header(self) } fn request_header_mut(&mut self) -> &mut ua::RequestHeader { - ua::RequestHeader::raw_mut(&mut self.0.requestHeader) + Self::request_header_mut(self) } } diff --git a/src/ua/data_types/write_response.rs b/src/ua/data_types/write_response.rs index 6e173f05..a61521c8 100644 --- a/src/ua/data_types/write_response.rs +++ b/src/ua/data_types/write_response.rs @@ -1,28 +1,15 @@ -use crate::{DataType as _, ServiceResponse, ua}; +use crate::{ServiceResponse, ua}; crate::data_type!(WriteResponse); - -impl WriteResponse { - #[must_use] - pub fn results(&self) -> Option> { - // TODO: Adjust signature to return non-owned value instead. - let array: ua::Array = - ua::Array::from_raw_parts(self.0.resultsSize, self.0.results)?; - // TODO: Simplify this. Think about what should be in `ua` and what should not. - Some( - array - .as_slice() - .iter() - .map(|status_code| ua::StatusCode::new(status_code.clone().into_raw())) - .collect(), - ) - } -} +crate::member_accessors!(WriteResponse { + results: [ua::StatusCode], + responseHeader: &ua::ResponseHeader +}); impl ServiceResponse for WriteResponse { type Request = ua::WriteRequest; fn response_header(&self) -> &ua::ResponseHeader { - ua::ResponseHeader::raw_ref(&self.0.responseHeader) + Self::response_header(self) } } diff --git a/src/ua/data_types/write_value.rs b/src/ua/data_types/write_value.rs index 31127038..99e10e16 100644 --- a/src/ua/data_types/write_value.rs +++ b/src/ua/data_types/write_value.rs @@ -1,6 +1,13 @@ use crate::{DataType as _, ua}; crate::data_type!(WriteValue); +crate::member_accessors!(WriteValue { + nodeId: &ua::NodeId, + #[skip(uses = Self::attribute_id)] + attributeId: ua::AttributeId, + value: &ua::DataValue, + indexRange: &ua::String +}); impl WriteValue { #[must_use] @@ -10,7 +17,7 @@ impl WriteValue { } #[must_use] - pub fn with_attribute_id(mut self, attribute_id: &ua::AttributeId) -> Self { + pub const fn with_attribute_id(mut self, attribute_id: &ua::AttributeId) -> Self { self.0.attributeId = attribute_id.as_u32(); self } @@ -20,4 +27,9 @@ impl WriteValue { value.clone_into_raw(&mut self.0.value); self } + + #[must_use] + pub const fn attribute_id(&self) -> ua::AttributeId { + ua::AttributeId::from_u32(self.0.attributeId) + } } diff --git a/src/ua/data_types/x509_identity_token.rs b/src/ua/data_types/x509_identity_token.rs index e3b6ca27..9979423b 100644 --- a/src/ua/data_types/x509_identity_token.rs +++ b/src/ua/data_types/x509_identity_token.rs @@ -1,6 +1,10 @@ use crate::{DataType as _, ua}; crate::data_type!(X509IdentityToken); +crate::member_accessors!(X509IdentityToken { + policyId: &ua::String, + certificateData: &ua::ByteString +}); impl X509IdentityToken { /// Sets policy ID. diff --git a/src/ua/event_notifier_type.rs b/src/ua/event_notifier_type.rs old mode 100644 new mode 100755 index 0023d701..39b0b1c0 --- a/src/ua/event_notifier_type.rs +++ b/src/ua/event_notifier_type.rs @@ -1,51 +1,19 @@ -use open62541_sys::{ - UA_EVENTNOTIFIERTYPE_HISTORYREAD, UA_EVENTNOTIFIERTYPE_HISTORYWRITE, - UA_EVENTNOTIFIERTYPE_SUBSCRIBETOEVENTS, UA_EventNotifierType, -}; - -use crate::{DataTypeExt, Result, ua}; - -/// Wrapper for [`UA_EventNotifierType`] from [`open62541_sys`]. -#[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub struct EventNotifierType(UA_EventNotifierType); - -// See for bit values. -impl EventNotifierType { - pub(crate) const fn from_u8(mask: u8) -> Self { - Self(mask) - } - - pub(crate) const fn as_u8(&self) -> u8 { - self.0 - } - - /// Indicates if it can be used to subscribe to events. - #[must_use] - pub fn subscribe_to_events(&self) -> bool { - u32::from(self.0) & UA_EVENTNOTIFIERTYPE_SUBSCRIBETOEVENTS != 0 - } - - /// Indicates if the history of the events is readable. - #[must_use] - pub fn history_read(&self) -> bool { - u32::from(self.0) & UA_EVENTNOTIFIERTYPE_HISTORYREAD != 0 - } - - /// Indicates if the history of the events is writeable. - #[must_use] - pub fn history_write(&self) -> bool { - u32::from(self.0) & UA_EVENTNOTIFIERTYPE_HISTORYWRITE != 0 - } -} - -impl DataTypeExt for EventNotifierType { - type Inner = ua::Byte; - - fn from_inner(value: Self::Inner) -> Result { - Ok(Self::from_u8(value.value())) - } - - fn into_inner(self) -> Self::Inner { - Self::Inner::new(self.as_u8()) +crate::data_type::data_type_baseline!( + #[expect( + clippy::derived_hash_with_manual_eq, + reason = "open62541 numeric equality impl satisfies `Hash`s identity invariant" + )] + #[derive(Clone, Copy, Hash)] + EventNotifierType +); + +crate::enum_bitmask!( + enum EventNotifierType : u8 { + /// Indicates if it can be used to subscribe to events. + SubscribeToEvents, + /// Indicates if the history of the events is readable. + HistoryRead, + /// Indicates if the history of the events is writeable. + HistoryWrite, } -} +); diff --git a/src/ua/key_value_map.rs b/src/ua/key_value_map.rs index 4aba3429..93bea96e 100644 --- a/src/ua/key_value_map.rs +++ b/src/ua/key_value_map.rs @@ -88,7 +88,7 @@ impl KeyValueMap { ) }); - Error::verify_good(&status_code).expect("should add value"); + Error::verify_good(status_code).expect("should add value"); } /// Removes key's value from map. diff --git a/src/ua/logger/rust_log.rs b/src/ua/logger/rust_log.rs index 7da29108..266f8603 100644 --- a/src/ua/logger/rust_log.rs +++ b/src/ua/logger/rust_log.rs @@ -117,7 +117,7 @@ fn format_message(msg: *const c_char, args: open62541_sys::va_list_) -> Result u32 { + pub(crate) const fn as_u32(self) -> u32 { self.0 } } diff --git a/src/ua/security_level.rs b/src/ua/security_level.rs index 13ea68f2..bac13da8 100644 --- a/src/ua/security_level.rs +++ b/src/ua/security_level.rs @@ -1,14 +1,22 @@ +//! Wrapper for security level from [`open62541_sys`]. use open62541_sys::UA_Byte; use crate::ua; -/// Wrapper for security level from [`open62541_sys`]. -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] -pub struct SecurityLevel(UA_Byte); +crate::data_type_baseline! { + #[expect( + clippy::derived_hash_with_manual_eq, + reason = "open62541 numeric equality impl satisfies `Hash`s identity invariant" + )] + #[derive(Clone, Copy, Hash)] + SecurityLevel, + #[uses_inner] + Byte +} impl SecurityLevel { #[must_use] - pub(crate) const fn new(security_level: UA_Byte) -> Self { + pub const fn new(security_level: UA_Byte) -> Self { Self(security_level) } diff --git a/src/ua/server.rs b/src/ua/server.rs index 70ec9c3f..5612e5da 100644 --- a/src/ua/server.rs +++ b/src/ua/server.rs @@ -65,7 +65,7 @@ impl Drop for Server { // SAFETY: We retain ownership of `self`. UA_Server_delete(self.as_mut_ptr()) }); - if let Err(error) = Error::verify_good(&status_code) { + if let Err(error) = Error::verify_good(status_code) { log::warn!("Error while dropping server: {error}"); } } diff --git a/src/ua/server_config.rs b/src/ua/server_config.rs index 531b6caa..1a741c95 100644 --- a/src/ua/server_config.rs +++ b/src/ua/server_config.rs @@ -49,7 +49,7 @@ impl ServerConfig { ) }); // PANIC: The only possible errors here are out-of-memory. - Error::verify_good(&status_code).expect("should set minimal server config"); + Error::verify_good(status_code).expect("should set minimal server config"); config } @@ -82,7 +82,7 @@ impl ServerConfig { 0, ) }); - Error::verify_good(&status_code)?; + Error::verify_good(status_code)?; Ok(config) } @@ -115,7 +115,7 @@ impl ServerConfig { 0, ) }); - Error::verify_good(&status_code)?; + Error::verify_good(status_code)?; Ok(config) } diff --git a/src/ua/unit_id.rs b/src/ua/unit_id.rs index e58e82cd..b26de28c 100644 --- a/src/ua/unit_id.rs +++ b/src/ua/unit_id.rs @@ -1,12 +1,17 @@ +//! Wrapper for [`ua::EUInformation::unit_id`](crate::ua::EUInformation::unit_id). use std::fmt; -/// Wrapper for [`ua::EUInformation::unit_id`](crate::ua::EUInformation::unit_id). -#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] -pub struct UnitId(i32); +crate::data_type_baseline!( + #[expect(clippy::derived_hash_with_manual_eq)] + #[derive(Clone, Copy, Hash)] + UnitId, + #[uses_inner] + Int32 +); impl UnitId { #[must_use] - pub(crate) const fn new(unit_id: i32) -> Self { + pub const fn new(unit_id: i32) -> Self { Self(unit_id) } diff --git a/src/ua/user_identity_token.rs b/src/ua/user_identity_token.rs index 398e1001..1c103013 100644 --- a/src/ua/user_identity_token.rs +++ b/src/ua/user_identity_token.rs @@ -1,6 +1,6 @@ use crate::ua; -#[derive(Debug)] +#[derive(Debug, Clone)] pub enum UserIdentityToken { /// No user information is available. Anonymous(ua::AnonymousIdentityToken),