From e565568508ef4392fcc95396a86b8bd357a6dbc2 Mon Sep 17 00:00:00 2001 From: Carter Canedy Date: Sun, 4 Oct 2026 13:45:15 -0500 Subject: [PATCH 01/14] baseline service type impl --- src/ua/data_types.rs | 4 +++ src/ua/data_types/browse_path_result.rs | 12 ++++++-- ...nslate_browse_paths_to_node_ids_request.rs | 30 +++++++++++++++++++ ...slate_browse_paths_to_node_ids_response.rs | 17 +++++++++++ 4 files changed, 60 insertions(+), 3 deletions(-) create mode 100644 src/ua/data_types/translate_browse_paths_to_node_ids_request.rs create mode 100644 src/ua/data_types/translate_browse_paths_to_node_ids_response.rs diff --git a/src/ua/data_types.rs b/src/ua/data_types.rs index 077a0919..1b053ba2 100644 --- a/src/ua/data_types.rs +++ b/src/ua/data_types.rs @@ -80,6 +80,8 @@ 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 variant; @@ -173,6 +175,8 @@ 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, variant::Variant, diff --git a/src/ua/data_types/browse_path_result.rs b/src/ua/data_types/browse_path_result.rs index 886be61e..09197ba6 100644 --- a/src/ua/data_types/browse_path_result.rs +++ b/src/ua/data_types/browse_path_result.rs @@ -1,3 +1,5 @@ +use open62541_sys::UA_BrowsePathResult; + use crate::ua; crate::data_type!(BrowsePathResult); @@ -9,8 +11,12 @@ impl BrowsePathResult { } #[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) + pub fn into_targets(mut self) -> Option> { + unsafe { ua::Array::move_from_raw_parts(&mut self.0.targetsSize, &mut self.0.targets) } + } + + #[must_use] + pub fn targets(&self) -> Option<&[ua::BrowsePathTarget]> { + unsafe { ua::Array::slice_from_raw_parts(self.0.targetsSize, self.0.targets) } } } 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..9a0d0426 --- /dev/null +++ b/src/ua/data_types/translate_browse_paths_to_node_ids_request.rs @@ -0,0 +1,30 @@ +use crate::{DataType as _, ServiceRequest, ua}; + +crate::data_type!(TranslateBrowsePathsToNodeIdsRequest); + +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 + } + + #[must_use] + pub fn browse_paths(&self) -> Option<&[ua::BrowsePath]> { + unsafe { ua::Array::slice_from_raw_parts(self.0.browsePathsSize, self.0.browsePaths) } + } +} + +impl ServiceRequest for TranslateBrowsePathsToNodeIdsRequest { + type Response = ua::TranslateBrowsePathsToNodeIdsResponse; + + fn request_header(&self) -> &ua::RequestHeader { + ua::RequestHeader::raw_ref(&self.0.requestHeader) + } + + fn request_header_mut(&mut self) -> &mut ua::RequestHeader { + ua::RequestHeader::raw_mut(&mut self.0.requestHeader) + } +} 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..0c2ea4e8 --- /dev/null +++ b/src/ua/data_types/translate_browse_paths_to_node_ids_response.rs @@ -0,0 +1,17 @@ +use crate::{DataType as _, ServiceResponse, ua}; + +crate::data_type!(TranslateBrowsePathsToNodeIdsResponse); + +impl TranslateBrowsePathsToNodeIdsResponse { + pub fn browse_paths(&self) -> Option<&[ua::BrowsePathResult]> { + unsafe { ua::Array::slice_from_raw_parts(self.0.resultsSize, self.0.results) } + } +} + +impl ServiceResponse for TranslateBrowsePathsToNodeIdsResponse { + type Request = ua::TranslateBrowsePathsToNodeIdsRequest; + + fn response_header(&self) -> &ua::ResponseHeader { + ua::ResponseHeader::raw_ref(&self.0.responseHeader) + } +} From 2b51ce5c8e3e67b11caa61f274933c414bd18e8c Mon Sep 17 00:00:00 2001 From: Carter Canedy Date: Tue, 6 Oct 2026 09:52:25 -0500 Subject: [PATCH 02/14] update `targets()` calls to `into_targets` calls --- src/server.rs | 4 ++-- src/ua/data_types/browse_path_result.rs | 2 -- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/src/server.rs b/src/server.rs index 400644b1..2d9f10fe 100644 --- a/src/server.rs +++ b/src/server.rs @@ -1407,7 +1407,7 @@ impl Server { }; Error::verify_good(&result.status_code())?; let targets = result - .targets() + .into_targets() .ok_or(Error::internal("browse should return targets"))?; Ok(targets) } @@ -1473,7 +1473,7 @@ impl Server { }; Error::verify_good(&result.status_code())?; let targets = result - .targets() + .into_targets() .ok_or(Error::internal("translation should return targets"))?; Ok(targets) } diff --git a/src/ua/data_types/browse_path_result.rs b/src/ua/data_types/browse_path_result.rs index 09197ba6..3aeb33ae 100644 --- a/src/ua/data_types/browse_path_result.rs +++ b/src/ua/data_types/browse_path_result.rs @@ -1,5 +1,3 @@ -use open62541_sys::UA_BrowsePathResult; - use crate::ua; crate::data_type!(BrowsePathResult); From 5f9b8199f453bf4885099fe1d8289654fa2b4e4e Mon Sep 17 00:00:00 2001 From: Carter Canedy Date: Wed, 7 Oct 2026 22:59:56 -0500 Subject: [PATCH 03/14] member accessors --- examples/async_call.rs | 2 +- src/async_client.rs | 12 +- src/data_type.rs | 140 +++++++++++++++--- src/lib.rs | 2 +- src/monitored_item.rs | 4 +- src/server.rs | 22 +-- src/ua/array.rs | 1 - src/ua/attribute_write_mask.rs | 2 +- src/ua/browse_result_mask.rs | 7 +- src/ua/continuation_point.rs | 13 +- src/ua/data_type_description.rs | 4 +- src/ua/data_types.rs | 8 + src/ua/data_types/aggregate_filter.rs | 7 + src/ua/data_types/anonymous_identity_token.rs | 3 + src/ua/data_types/application_description.rs | 44 ++---- src/ua/data_types/argument.rs | 33 +---- src/ua/data_types/attribute_operand.rs | 15 ++ src/ua/data_types/browse_description.rs | 16 +- src/ua/data_types/browse_next_request.rs | 3 + src/ua/data_types/browse_path_result.rs | 21 +-- src/ua/data_types/browse_path_target.rs | 13 +- src/ua/data_types/browse_request.rs | 14 +- src/ua/data_types/browse_result.rs | 19 +-- src/ua/data_types/build_info.rs | 44 ++---- src/ua/data_types/call_method_request.rs | 5 + src/ua/data_types/call_method_result.rs | 25 +--- src/ua/data_types/call_request.rs | 4 + src/ua/data_types/call_response.rs | 16 +- src/ua/data_types/content_filter.rs | 3 + src/ua/data_types/content_filter_element.rs | 7 + .../create_monitored_items_request.rs | 17 ++- .../create_monitored_items_response.rs | 16 +- .../data_types/create_subscription_request.rs | 11 ++ .../create_subscription_respones.rs | 21 ++- src/ua/data_types/data_change_filter.rs | 7 + src/ua/data_types/data_change_trigger.rs | 7 + src/ua/data_types/data_value.rs | 14 ++ .../delete_monitored_items_request.rs | 14 ++ src/ua/data_types/diagnostic_info.rs | 2 + src/ua/data_types/element_operand.rs | 1 + src/ua/data_types/endpoint_description.rs | 52 ++----- src/ua/data_types/enum_definition.rs | 11 +- src/ua/data_types/enum_description.rs | 28 +--- src/ua/data_types/enum_field.rs | 30 +--- src/ua/data_types/eu_information.rs | 26 +--- src/ua/data_types/event_filter.rs | 4 + src/ua/data_types/expanded_node_id.rs | 20 +-- src/ua/data_types/extension_object.rs | 22 ++- .../data_types/extension_object_encoding.rs | 42 ++++++ src/ua/data_types/issued_identity_token.rs | 5 + src/ua/data_types/literal_operand.rs | 3 + src/ua/data_types/localized_text.rs | 14 +- .../monitored_item_create_request.rs | 8 +- .../monitored_item_create_result.rs | 21 ++- src/ua/data_types/monitoring_parameters.rs | 15 +- src/ua/data_types/node_attributes.rs | 9 ++ src/ua/data_types/node_id.rs | 16 +- src/ua/data_types/qualified_name.rs | 16 +- src/ua/data_types/range.rs | 16 +- src/ua/data_types/read_request.rs | 8 + src/ua/data_types/read_response.rs | 12 +- src/ua/data_types/read_value_id.rs | 14 ++ src/ua/data_types/reference_description.rs | 51 ++----- src/ua/data_types/relative_path.rs | 23 +-- src/ua/data_types/relative_path_element.rs | 26 +--- src/ua/data_types/request_header.rs | 12 ++ src/ua/data_types/response_header.rs | 17 ++- src/ua/data_types/server_state.rs | 13 ++ src/ua/data_types/server_status_data_type.rs | 49 ++---- src/ua/data_types/simple_attribute_operand.rs | 14 ++ src/ua/data_types/structure_definition.rs | 32 ++-- src/ua/data_types/structure_description.rs | 20 +-- src/ua/data_types/structure_field.rs | 50 ++----- ...nslate_browse_paths_to_node_ids_request.rs | 15 +- ...slate_browse_paths_to_node_ids_response.rs | 15 +- src/ua/data_types/trust_list_data_type.rs | 7 + src/ua/data_types/user_name_identity_token.rs | 6 + src/ua/data_types/view_description.rs | 28 ++++ src/ua/data_types/write_request.rs | 10 +- src/ua/data_types/write_response.rs | 25 +--- src/ua/data_types/write_value.rs | 14 ++ src/ua/data_types/x509_identity_token.rs | 4 + src/ua/integer_id.rs | 2 +- src/ua/node_class_mask.rs | 3 +- src/ua/security_level.rs | 4 +- src/ua/subscription_id.rs | 2 +- src/ua/unit_id.rs | 4 +- 87 files changed, 782 insertions(+), 675 deletions(-) create mode 100644 src/ua/data_types/data_change_trigger.rs create mode 100644 src/ua/data_types/diagnostic_info.rs create mode 100644 src/ua/data_types/extension_object_encoding.rs create mode 100644 src/ua/data_types/view_description.rs 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..127fb458 100644 --- a/src/async_client.rs +++ b/src/async_client.rs @@ -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,7 +290,7 @@ 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")); }; @@ -323,14 +323,14 @@ impl AsyncClient { return Err(Error::internal("call should return results")); }; - let Some(result) = results.as_slice().first() else { + let Some(result) = results.first() else { return Err(Error::internal("call should return a result")); }; Error::verify_good(&result.status_code())?; let output_arguments = if let Some(output_arguments) = result.output_arguments() { - output_arguments.into_vec() + output_arguments.to_vec() } else { log::debug!("Calling {method_id} returned unset output arguments, assuming none exist"); Vec::new() @@ -873,7 +873,7 @@ fn to_browse_result(result: &ua::BrowseResult, node_id: Option<&ua::NodeId>) -> 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/data_type.rs b/src/data_type.rs index d357deb1..9060b05a 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,6 +329,30 @@ pub unsafe trait DataType: Debug + Clone { } } +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 wrapper for OPC UA data type from [`open62541_sys`]. /// /// This provides the basic interface to convert from and back into the [`open62541_sys`] types. Use @@ -374,7 +382,7 @@ macro_rules! data_type { #[repr(transparent)] pub struct $name( /// Inner value. - open62541_sys::$inner, + pub(crate) open62541_sys::$inner, ); // SAFETY: The types in `open62541` can be sent across thread boundaries. They contain @@ -475,6 +483,100 @@ macro_rules! data_type { pub(crate) use data_type; +macro_rules! member_accessors { + ($t:ty { $($rest:tt)+ }) => { + impl $t { + $crate::member_accessors!($($rest)+); + } + }; + + ($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 [<$raw_name:snake _mut>](&mut self) -> Option<&mut [$arr_t]> { + unsafe { $crate::ua::Array::slice_from_raw_parts_mut(self.0.[<$raw_name Size>], self.0.$raw_name) } + } + + #[must_use] + pub fn [](&mut self) -> Option> { + unsafe { ua::Array::move_from_raw_parts(&mut self.0.[<$raw_name Size>], &mut self.0.$raw_name) } + } + } + + $($crate::member_accessors!($($rest)+);)? + }; + + (#[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)+);)? + }; + + ($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)+);)? + }; + + ($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)+);)? + }; + + (#[from_inner] $raw_name:ident: $wrapper_t:ty $(, $($rest:tt)+)?) => { + paste::paste! { + pub const fn [<$raw_name:snake>](&self) -> $wrapper_t { + $wrapper_t(self.0.$raw_name) + } + } + + $($crate::member_accessors!($($rest)+);)? + }; + + (#[enum($inner_t:ty)] $raw_name:ident: $wrapper_t:ty $(, $($rest:tt)+)?) => { + paste::paste! { + pub const fn [<$raw_name:snake>](&self) -> $wrapper_t { + $wrapper_t($inner_t(self.0.$raw_name.0)) + } + } + + $($crate::member_accessors!($($rest)+);)? + }; + + ($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 diff --git a/src/lib.rs b/src/lib.rs index 2d45e270..54b5ede4 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -263,7 +263,7 @@ pub use self::{ use self::{ callback_fn::CallbackMut, client::ClientContext, - data_type::{bitmask_ops, data_type, enum_variants}, + data_type::{bitmask_ops, data_type, 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..1f2f7b1c 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")); }; diff --git a/src/server.rs b/src/server.rs index 2d9f10fe..9e3ede79 100644 --- a/src/server.rs +++ b/src/server.rs @@ -1394,7 +1394,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(), @@ -1406,10 +1406,10 @@ impl Server { )) }; Error::verify_good(&result.status_code())?; - let targets = result - .into_targets() - .ok_or(Error::internal("browse should return targets"))?; - Ok(targets) + + result + .take_targets() + .ok_or(Error::internal("browse should return targets")) } /// Translates browse path to node IDs. @@ -1464,7 +1464,7 @@ 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(), @@ -1472,10 +1472,10 @@ impl Server { )) }; Error::verify_good(&result.status_code())?; - let targets = result - .into_targets() - .ok_or(Error::internal("translation should return targets"))?; - Ok(targets) + + result + .take_targets() + .ok_or(Error::internal("translation should return targets")) } /// Reads node attribute. @@ -1961,7 +1961,7 @@ fn to_browse_result(result: &ua::BrowseResult) -> BrowseResult { return Err(Error::internal("browse should return references")); }; - Ok((references.into_vec(), result.continuation_point())) + Ok((references.to_vec(), result.continuation_point())) } /// Sets `config.nodeLifecycle` to the given value. diff --git a/src/ua/array.rs b/src/ua/array.rs index e0e3bc18..72743e1b 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, diff --git a/src/ua/attribute_write_mask.rs b/src/ua/attribute_write_mask.rs index 84d5fe13..1358cf94 100644 --- a/src/ua/attribute_write_mask.rs +++ b/src/ua/attribute_write_mask.rs @@ -18,7 +18,7 @@ use crate::{DataTypeExt, Result, ua}; /// Wrapper for [`UA_AttributeWriteMask`] from [`open62541_sys`]. #[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub struct AttributeWriteMask(UA_AttributeWriteMask); +pub struct AttributeWriteMask(pub(crate) UA_AttributeWriteMask); // See for bit values. impl AttributeWriteMask { diff --git a/src/ua/browse_result_mask.rs b/src/ua/browse_result_mask.rs index f13fd352..d2288414 100644 --- a/src/ua/browse_result_mask.rs +++ b/src/ua/browse_result_mask.rs @@ -24,8 +24,9 @@ mod inner { } /// Wrapper for browse result mask from [`open62541_sys`]. -#[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub struct BrowseResultMask(u32); +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +#[repr(transparent)] +pub struct BrowseResultMask(pub(crate) u32); crate::bitmask_ops!(BrowseResultMask); @@ -45,7 +46,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/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 1b053ba2..cf583f20 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; @@ -85,6 +88,7 @@ mod translate_browse_paths_to_node_ids_response; mod trust_list_data_type; mod user_name_identity_token; mod variant; +mod view_description; mod write_request; mod write_response; mod write_value; @@ -121,12 +125,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, @@ -137,6 +143,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, @@ -180,6 +187,7 @@ pub use self::{ trust_list_data_type::TrustListDataType, user_name_identity_token::UserNameIdentityToken, variant::Variant, + view_description::ViewDescription, write_request::WriteRequest, write_response::WriteResponse, write_value::WriteValue, 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..0b07a668 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 @@ -93,39 +102,4 @@ impl ApplicationDescription { .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) } - } } 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..5503a6f3 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(open62541_sys::UA_AttributeId(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..abb25257 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, + #[skip(uses = BrowseDescription::browse_direction)] + browseDirection: ua::BrowseDirection +}); impl BrowseDescription { #[must_use] @@ -42,8 +53,9 @@ impl BrowseDescription { } #[must_use] - pub(crate) fn node_id(&self) -> &ua::NodeId { - ua::NodeId::raw_ref(&self.0.nodeId) + pub const fn browse_direction(&self) -> ua::BrowseDirection { + // this is cursed + ua::BrowseDirection(open62541_sys::UA_BrowseDirection(self.0.browseDirection.0)) } } diff --git a/src/ua/data_types/browse_next_request.rs b/src/ua/data_types/browse_next_request.rs index bfe7a09c..5544f66e 100644 --- a/src/ua/data_types/browse_next_request.rs +++ b/src/ua/data_types/browse_next_request.rs @@ -1,6 +1,9 @@ use crate::{DataType as _, ServiceRequest, UaIterExt as _, ua}; crate::data_type!(BrowseNextRequest); +crate::member_accessors!(BrowseNextRequest { + continuationPoints: [ua::ContinuationPoint] +}); impl BrowseNextRequest { #[must_use] diff --git a/src/ua/data_types/browse_path_result.rs b/src/ua/data_types/browse_path_result.rs index 3aeb33ae..abc6f1b1 100644 --- a/src/ua/data_types/browse_path_result.rs +++ b/src/ua/data_types/browse_path_result.rs @@ -2,19 +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 into_targets(mut self) -> Option> { - unsafe { ua::Array::move_from_raw_parts(&mut self.0.targetsSize, &mut self.0.targets) } - } - - #[must_use] - pub fn targets(&self) -> Option<&[ua::BrowsePathTarget]> { - unsafe { ua::Array::slice_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..23c87eb1 100644 --- a/src/ua/data_types/browse_request.rs +++ b/src/ua/data_types/browse_request.rs @@ -1,6 +1,12 @@ -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] @@ -18,6 +24,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_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..d072fe52 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] 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..27809b9c 100644 --- a/src/ua/data_types/call_request.rs +++ b/src/ua/data_types/call_request.rs @@ -1,6 +1,10 @@ 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] 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..e3118507 100644 --- a/src/ua/data_types/content_filter.rs +++ b/src/ua/data_types/content_filter.rs @@ -1,6 +1,9 @@ use crate::ua; crate::data_type!(ContentFilter); +crate::member_accessors!(ContentFilter { + elements: [ua::ContentFilterElement] +}); impl ContentFilter { #[must_use] diff --git a/src/ua/data_types/content_filter_element.rs b/src/ua/data_types/content_filter_element.rs index dbf5db28..bb8a5380 100644 --- a/src/ua/data_types/content_filter_element.rs +++ b/src/ua/data_types/content_filter_element.rs @@ -1,6 +1,13 @@ +use open62541_sys::UA_FilterOperator; + use crate::{DataType, FilterOperand, UaIterExt as _, ua}; crate::data_type!(ContentFilterElement); +crate::member_accessors!(ContentFilterElement { + #[enum(UA_FilterOperator)] + filterOperator: ua::FilterOperator, + filterOperands: [ua::ExtensionObject] +}); impl ContentFilterElement { #[must_use] diff --git a/src/ua/data_types/create_monitored_items_request.rs b/src/ua/data_types/create_monitored_items_request.rs index 6e45acf4..004415ff 100644 --- a/src/ua/data_types/create_monitored_items_request.rs +++ b/src/ua/data_types/create_monitored_items_request.rs @@ -1,6 +1,15 @@ +use open62541_sys::UA_TimestampsToReturn; + use crate::ua; crate::data_type!(CreateMonitoredItemsRequest); +crate::member_accessors!(CreateMonitoredItemsRequest { + itemsToCreate: [ua::MonitoredItemCreateRequest], + #[enum(UA_TimestampsToReturn)] + timestampsToReturn: ua::TimestampsToReturn, + #[skip(uses = Self::subscription_id)] + subscriptionId: ua::SubscriptionId +}); impl CreateMonitoredItemsRequest { #[must_use] @@ -20,7 +29,11 @@ impl CreateMonitoredItemsRequest { } #[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(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..5671738e 100644 --- a/src/ua/data_types/data_change_filter.rs +++ b/src/ua/data_types/data_change_filter.rs @@ -1,6 +1,13 @@ use crate::{MonitoringFilter, ua}; +use open62541_sys::UA_DataChangeTrigger; crate::data_type!(DataChangeFilter); +crate::member_accessors!(DataChangeFilter { + deadbandType: u32, + deadbandValue: f64, + #[enum(UA_DataChangeTrigger)] + 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/delete_monitored_items_request.rs b/src/ua/data_types/delete_monitored_items_request.rs index 56988417..6f98519a 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] @@ -21,4 +26,13 @@ impl DeleteMonitoredItemsRequest { ); 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/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..a88d2a13 100644 --- a/src/ua/data_types/endpoint_description.rs +++ b/src/ua/data_types/endpoint_description.rs @@ -1,40 +1,16 @@ -use crate::{DataType, ua}; +use open62541_sys::UA_MessageSecurityMode; -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) - } +use crate::ua; - #[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::data_type!(EndpointDescription); +crate::member_accessors!(EndpointDescription { + endpointUrl: &ua::String, + securityPolicyUri: &ua::String, + transportProfileUri: &ua::String, + server: &ua::ApplicationDescription, + serverCertificate: &ua::ByteString, + #[enum(UA_MessageSecurityMode)] + securityMode: ua::MessageSecurityMode, + #[from_inner] + securityLevel: ua::SecurityLevel +}); 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..732bfaf1 100644 --- a/src/ua/data_types/event_filter.rs +++ b/src/ua/data_types/event_filter.rs @@ -1,6 +1,10 @@ use crate::{DataType as _, MonitoringFilter, ua}; crate::data_type!(EventFilter); +crate::member_accessors!(EventFilter { + selectClauses: [ua::SimpleAttributeOperand], + whereClause: &ua::ContentFilter +}); impl EventFilter { #[must_use] diff --git a/src/ua/data_types/expanded_node_id.rs b/src/ua/data_types/expanded_node_id.rs index f6b798c1..5f1f1b87 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 { diff --git a/src/ua/data_types/extension_object.rs b/src/ua/data_types/extension_object.rs index f653780d..3549f2f7 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 { + #[skip(uses = Self::encoding)] + encoding: ua::ExtensionObjectEncoding +}); impl ExtensionObject { /// Creates extension object from value. @@ -27,14 +34,19 @@ impl ExtensionObject { extension_object } + #[must_use] + pub const fn encoding(&self) -> ua::ExtensionObjectEncoding { + ua::ExtensionObjectEncoding(self.0.encoding.0) + } + /// Gets encoded type ID. #[must_use] pub 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.0.encoding.0, + ExtensionObjectEncoding::ENCODED_NOBODY_U32 + | ExtensionObjectEncoding::ENCODED_BYTESTRING_U32 + | ExtensionObjectEncoding::ENCODED_XML_U32 ) { return None; } 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..73f54354 --- /dev/null +++ b/src/ua/data_types/extension_object_encoding.rs @@ -0,0 +1,42 @@ +use open62541_sys::UA_ExtensionObjectEncoding; + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct ExtensionObjectEncoding(pub(crate) u32); + +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($discriminant); + pub const [<$variant _U32>]: u32 = $discriminant; + #[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..8794f6d1 100644 --- a/src/ua/data_types/monitored_item_create_request.rs +++ b/src/ua/data_types/monitored_item_create_request.rs @@ -1,10 +1,16 @@ use std::time::Duration; -use open62541_sys::{UA_MonitoredItemCreateRequest_default, UA_NODEID_NUMERIC}; +use open62541_sys::{UA_MonitoredItemCreateRequest_default, UA_MonitoringMode, UA_NODEID_NUMERIC}; use crate::{DataType as _, MonitoringFilter, ua}; crate::data_type!(MonitoredItemCreateRequest); +crate::member_accessors!(MonitoredItemCreateRequest { + itemToMonitor: &ua::ReadValueId, + #[enum(UA_MonitoringMode)] + monitoringMode: ua::MonitoringMode, + requestedParameters: &ua::MonitoringParameters +}); impl MonitoredItemCreateRequest { /// Sets item to monitor. 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_id.rs b/src/ua/data_types/node_id.rs index 3282efbc..bcdaae9d 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, + #[skip(uses = Self::identifier_type)] + 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,10 +139,9 @@ 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) + pub const fn identifier_type(&self) -> ua::NodeIdType { + ua::NodeIdType(UA_NodeIdType(self.0.identifierType.0)) } /// Gets identifier of numeric node ID in namespace 0. 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..f6b6102e 100644 --- a/src/ua/data_types/read_request.rs +++ b/src/ua/data_types/read_request.rs @@ -1,6 +1,14 @@ +use open62541_sys::UA_TimestampsToReturn; + use crate::{DataType as _, ServiceRequest, ua}; crate::data_type!(ReadRequest); +crate::member_accessors!(ReadRequest { + #[enum(UA_TimestampsToReturn)] + timestampsToReturn: ua::TimestampsToReturn, + nodesToRead: [ua::ReadValueId], + requestHeader: &mut ua::RequestHeader +}); impl ReadRequest { #[must_use] 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..30be4c66 100644 --- a/src/ua/data_types/read_value_id.rs +++ b/src/ua/data_types/read_value_id.rs @@ -1,6 +1,15 @@ +use open62541_sys::UA_AttributeId; + 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] @@ -14,4 +23,9 @@ impl ReadValueId { self.0.attributeId = attribute_id.as_u32(); self } + + #[must_use] + pub const fn attribute_id(&self) -> ua::AttributeId { + ua::AttributeId(UA_AttributeId(self.0.attributeId)) + } } diff --git a/src/ua/data_types/reference_description.rs b/src/ua/data_types/reference_description.rs index 55f8caec..5a9b8096 100644 --- a/src/ua/data_types/reference_description.rs +++ b/src/ua/data_types/reference_description.rs @@ -1,40 +1,15 @@ -use crate::{DataType as _, ua}; +use open62541_sys::UA_NodeClass; -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) - } +use crate::ua; - #[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::data_type!(ReferenceDescription); +crate::member_accessors!(ReferenceDescription { + referenceTypeId: &ua::NodeId, + isForward: bool, + nodeId: &ua::ExpandedNodeId, + browseName: &ua::QualifiedName, + displayName: &ua::LocalizedText, + typeDefinition: &ua::ExpandedNodeId, + #[enum(UA_NodeClass)] + nodeClass: ua::NodeClass +}); diff --git a/src/ua/data_types/relative_path.rs b/src/ua/data_types/relative_path.rs index 9d6aa024..d6f019cf 100644 --- a/src/ua/data_types/relative_path.rs +++ b/src/ua/data_types/relative_path.rs @@ -5,6 +5,9 @@ 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] @@ -14,26 +17,6 @@ impl RelativePath { 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) } - } - - /// 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) } - } - /// Attempts to access the element at `index`. /// /// Returns [`Some`] if the array is valid and `index` < [`RelativePath::len()`], otherwise [`None`] 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..3210cf50 100644 --- a/src/ua/data_types/request_header.rs +++ b/src/ua/data_types/request_header.rs @@ -1,6 +1,18 @@ use std::time::Duration; +use crate::ua; + crate::data_type!(RequestHeader); +crate::member_accessors!(RequestHeader { + timestamp: i64, + auditEntryId: &ua::String, + #[skip(uses = Self::timeout_hint)] + timeoutHint: Duration, + requestHandle: u32, + 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..6b3707cf 100644 --- a/src/ua/data_types/response_header.rs +++ b/src/ua/data_types/response_header.rs @@ -1,11 +1,12 @@ 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, + timestamp: i64, + 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..0b23ddf9 100644 --- a/src/ua/data_types/server_state.rs +++ b/src/ua/data_types/server_state.rs @@ -1 +1,14 @@ crate::data_type!(ServerState); +crate::enum_variants!( + ServerState, + UA_ServerState, + [ + FAILED, + NOCONFIGURATION, + SUSPENDED, + 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..54f4eaf7 100644 --- a/src/ua/data_types/server_status_data_type.rs +++ b/src/ua/data_types/server_status_data_type.rs @@ -1,37 +1,16 @@ -use crate::{DataType as _, ua}; +use open62541_sys::UA_ServerState; -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) } - } +use crate::ua; - #[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::data_type!(ServerStatusDataType); +crate::member_accessors!(ServerStatusDataType { + buildInfo: &ua::BuildInfo, + #[enum(UA_ServerState)] + 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..b14cc525 100644 --- a/src/ua/data_types/simple_attribute_operand.rs +++ b/src/ua/data_types/simple_attribute_operand.rs @@ -1,6 +1,15 @@ +use open62541_sys::UA_AttributeId; + 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] @@ -27,6 +36,11 @@ impl SimpleAttributeOperand { index_range.move_into_raw(&mut self.0.indexRange); self } + + #[must_use] + pub const fn attribute_id(&self) -> ua::AttributeId { + ua::AttributeId(UA_AttributeId(self.0.attributeId)) + } } impl FilterOperand for SimpleAttributeOperand { diff --git a/src/ua/data_types/structure_definition.rs b/src/ua/data_types/structure_definition.rs index c5e434bc..a7833d35 100644 --- a/src/ua/data_types/structure_definition.rs +++ b/src/ua/data_types/structure_definition.rs @@ -1,29 +1,17 @@ -use crate::{DataType, ua}; +use open62541_sys::UA_StructureType; + +use crate::ua; crate::data_type!(StructureDefinition); +crate::member_accessors!(StructureDefinition { + defaultEncodingId: &ua::NodeId, + baseDataType: &ua::NodeId, + #[enum(UA_StructureType)] + 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 index 9a0d0426..79c67549 100644 --- 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 @@ -1,6 +1,10 @@ -use crate::{DataType as _, ServiceRequest, ua}; +use crate::{ServiceRequest, ua}; crate::data_type!(TranslateBrowsePathsToNodeIdsRequest); +crate::member_accessors!(TranslateBrowsePathsToNodeIdsRequest { + browsePaths: [ua::BrowsePath], + requestHeader: &mut ua::RequestHeader +}); impl TranslateBrowsePathsToNodeIdsRequest { #[must_use] @@ -10,21 +14,16 @@ impl TranslateBrowsePathsToNodeIdsRequest { self } - - #[must_use] - pub fn browse_paths(&self) -> Option<&[ua::BrowsePath]> { - unsafe { ua::Array::slice_from_raw_parts(self.0.browsePathsSize, self.0.browsePaths) } - } } impl ServiceRequest for TranslateBrowsePathsToNodeIdsRequest { type Response = ua::TranslateBrowsePathsToNodeIdsResponse; 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/translate_browse_paths_to_node_ids_response.rs b/src/ua/data_types/translate_browse_paths_to_node_ids_response.rs index 0c2ea4e8..947bdaf7 100644 --- 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 @@ -1,17 +1,16 @@ -use crate::{DataType as _, ServiceResponse, ua}; +use crate::{ServiceResponse, ua}; crate::data_type!(TranslateBrowsePathsToNodeIdsResponse); - -impl TranslateBrowsePathsToNodeIdsResponse { - pub fn browse_paths(&self) -> Option<&[ua::BrowsePathResult]> { - unsafe { ua::Array::slice_from_raw_parts(self.0.resultsSize, self.0.results) } - } -} +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 { - ua::ResponseHeader::raw_ref(&self.0.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..11385415 100644 --- a/src/ua/data_types/trust_list_data_type.rs +++ b/src/ua/data_types/trust_list_data_type.rs @@ -1,6 +1,13 @@ use crate::ua; crate::data_type!(TrustListDataType); +crate::member_accessors!(TrustListDataType { + trustedCertificates: [ua::String], + trustedCrls: [ua::String], + issuerCertificates: [ua::String], + issuerCrls: [ua::String], + specifiedLists: u32 +}); impl TrustListDataType { #[must_use] 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/view_description.rs b/src/ua/data_types/view_description.rs new file mode 100644 index 00000000..b71feed4 --- /dev/null +++ b/src/ua/data_types/view_description.rs @@ -0,0 +1,28 @@ +use crate::{DataType, ua}; + +crate::data_type!(ViewDescription); +crate::member_accessors!(ViewDescription { + viewId: &ua::NodeId, + viewVersion: u32, + timestamp: i64 +}); + +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: i64) -> Self { + self.0.timestamp = ts; + self + } +} diff --git a/src/ua/data_types/write_request.rs b/src/ua/data_types/write_request.rs index 5b0f0d91..e2a877e0 100644 --- a/src/ua/data_types/write_request.rs +++ b/src/ua/data_types/write_request.rs @@ -1,6 +1,10 @@ -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] @@ -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..5c886e71 100644 --- a/src/ua/data_types/write_value.rs +++ b/src/ua/data_types/write_value.rs @@ -1,6 +1,15 @@ +use open62541_sys::UA_AttributeId; + 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] @@ -20,4 +29,9 @@ impl WriteValue { value.clone_into_raw(&mut self.0.value); self } + + #[must_use] + pub const fn attribute_id(&self) -> ua::AttributeId { + ua::AttributeId(UA_AttributeId(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/integer_id.rs b/src/ua/integer_id.rs index 87e6ff82..ae03a403 100644 --- a/src/ua/integer_id.rs +++ b/src/ua/integer_id.rs @@ -6,7 +6,7 @@ use crate::ua; /// /// Only stores valid, non-zero values. #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] -pub struct IntegerId(NonZeroU32); +pub struct IntegerId(pub(crate) NonZeroU32); impl IntegerId { #[must_use] diff --git a/src/ua/node_class_mask.rs b/src/ua/node_class_mask.rs index ac148bd0..bed32146 100644 --- a/src/ua/node_class_mask.rs +++ b/src/ua/node_class_mask.rs @@ -2,7 +2,8 @@ use crate::ua; /// Wrapper for node class mask from [`open62541_sys`]. #[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub struct NodeClassMask(u32); +#[repr(transparent)] +pub struct NodeClassMask(pub(crate) u32); crate::bitmask_ops!(NodeClassMask); diff --git a/src/ua/security_level.rs b/src/ua/security_level.rs index 13ea68f2..b9e1d59a 100644 --- a/src/ua/security_level.rs +++ b/src/ua/security_level.rs @@ -4,11 +4,11 @@ use crate::ua; /// Wrapper for security level from [`open62541_sys`]. #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] -pub struct SecurityLevel(UA_Byte); +pub struct SecurityLevel(pub(crate) UA_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/subscription_id.rs b/src/ua/subscription_id.rs index 83a098cd..b5ed4821 100644 --- a/src/ua/subscription_id.rs +++ b/src/ua/subscription_id.rs @@ -6,7 +6,7 @@ use crate::ua; // // Newtype wrapper for [`ua::IntegerId`]. #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] -pub struct SubscriptionId(ua::IntegerId); +pub struct SubscriptionId(pub(crate) ua::IntegerId); impl SubscriptionId { #[must_use] diff --git a/src/ua/unit_id.rs b/src/ua/unit_id.rs index e58e82cd..0bb323a9 100644 --- a/src/ua/unit_id.rs +++ b/src/ua/unit_id.rs @@ -2,11 +2,11 @@ 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); +pub struct UnitId(pub(crate) i32); impl UnitId { #[must_use] - pub(crate) const fn new(unit_id: i32) -> Self { + pub const fn new(unit_id: i32) -> Self { Self(unit_id) } From 4be59ea5d17508de1b7124e5bf4277fd801f18d9 Mon Sep 17 00:00:00 2001 From: Carter Canedy Date: Thu, 8 Oct 2026 14:53:41 -0500 Subject: [PATCH 04/14] fix leaky `move_into_raw` implementation --- src/data_type.rs | 39 ++++++++++++++++--- src/server.rs | 1 + src/ua/array.rs | 5 ++- src/ua/data_types/application_description.rs | 1 + src/ua/data_types/browse_next_request.rs | 4 +- src/ua/data_types/browse_request.rs | 5 ++- src/ua/data_types/call_method_request.rs | 4 +- src/ua/data_types/call_request.rs | 5 ++- src/ua/data_types/content_filter.rs | 5 ++- src/ua/data_types/content_filter_element.rs | 1 + .../create_monitored_items_request.rs | 5 ++- .../delete_monitored_items_request.rs | 1 + .../delete_subscriptions_request.rs | 5 +++ src/ua/data_types/event_filter.rs | 5 ++- .../node_attributes/variable_attributes.rs | 2 + src/ua/data_types/read_request.rs | 5 ++- src/ua/data_types/relative_path.rs | 21 ++-------- src/ua/data_types/simple_attribute_operand.rs | 5 ++- src/ua/data_types/trust_list_data_type.rs | 22 ++++++++++- src/ua/data_types/write_request.rs | 4 +- 20 files changed, 100 insertions(+), 45 deletions(-) diff --git a/src/data_type.rs b/src/data_type.rs index 9060b05a..4859d45d 100644 --- a/src/data_type.rs +++ b/src/data_type.rs @@ -483,13 +483,38 @@ macro_rules! 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(UA_TimestampsToReturn)] +/// 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] @@ -497,11 +522,6 @@ macro_rules! member_accessors { unsafe { $crate::ua::Array::slice_from_raw_parts(self.0.[<$raw_name Size>], self.0.$raw_name) } } - #[must_use] - pub fn [<$raw_name:snake _mut>](&mut self) -> Option<&mut [$arr_t]> { - unsafe { $crate::ua::Array::slice_from_raw_parts_mut(self.0.[<$raw_name Size>], self.0.$raw_name) } - } - #[must_use] pub fn [](&mut self) -> Option> { unsafe { ua::Array::move_from_raw_parts(&mut self.0.[<$raw_name Size>], &mut self.0.$raw_name) } @@ -511,6 +531,9 @@ macro_rules! member_accessors { $($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>]: () = { @@ -522,6 +545,7 @@ macro_rules! member_accessors { $($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 { @@ -532,6 +556,7 @@ macro_rules! member_accessors { $($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); @@ -544,6 +569,7 @@ macro_rules! member_accessors { $($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 { @@ -554,6 +580,8 @@ macro_rules! member_accessors { $($crate::member_accessors!($($rest)+);)? }; + // same thing as above, except bindgen enums are #[repr(transparent)] tuple structs containing + // their inner repr, so we need an additional access (#[enum($inner_t:ty)] $raw_name:ident: $wrapper_t:ty $(, $($rest:tt)+)?) => { paste::paste! { pub const fn [<$raw_name:snake>](&self) -> $wrapper_t { @@ -564,6 +592,7 @@ macro_rules! member_accessors { $($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 { diff --git a/src/server.rs b/src/server.rs index 9e3ede79..1bb219ca 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 } diff --git a/src/ua/array.rs b/src/ua/array.rs index 72743e1b..81fcf35f 100644 --- a/src/ua/array.rs +++ b/src/ua/array.rs @@ -377,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/data_types/application_description.rs b/src/ua/data_types/application_description.rs index 0b07a668..6525206f 100644 --- a/src/ua/data_types/application_description.rs +++ b/src/ua/data_types/application_description.rs @@ -100,6 +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 } } diff --git a/src/ua/data_types/browse_next_request.rs b/src/ua/data_types/browse_next_request.rs index 5544f66e..e73cc68b 100644 --- a/src/ua/data_types/browse_next_request.rs +++ b/src/ua/data_types/browse_next_request.rs @@ -2,7 +2,8 @@ use crate::{DataType as _, ServiceRequest, UaIterExt as _, ua}; crate::data_type!(BrowseNextRequest); crate::member_accessors!(BrowseNextRequest { - continuationPoints: [ua::ContinuationPoint] + continuationPoints: [ua::ContinuationPoint], + releaseContinuationPoints: bool }); impl BrowseNextRequest { @@ -19,6 +20,7 @@ impl BrowseNextRequest { &mut self.0.continuationPointsSize, &mut self.0.continuationPoints, ); + self } diff --git a/src/ua/data_types/browse_request.rs b/src/ua/data_types/browse_request.rs index 23c87eb1..73170863 100644 --- a/src/ua/data_types/browse_request.rs +++ b/src/ua/data_types/browse_request.rs @@ -11,8 +11,9 @@ crate::member_accessors!(BrowseRequest { 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 } diff --git a/src/ua/data_types/call_method_request.rs b/src/ua/data_types/call_method_request.rs index d072fe52..62db4796 100644 --- a/src/ua/data_types/call_method_request.rs +++ b/src/ua/data_types/call_method_request.rs @@ -22,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_request.rs b/src/ua/data_types/call_request.rs index 27809b9c..ff128025 100644 --- a/src/ua/data_types/call_request.rs +++ b/src/ua/data_types/call_request.rs @@ -9,8 +9,9 @@ crate::member_accessors!(CallRequest { 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/content_filter.rs b/src/ua/data_types/content_filter.rs index e3118507..113dabd9 100644 --- a/src/ua/data_types/content_filter.rs +++ b/src/ua/data_types/content_filter.rs @@ -8,8 +8,9 @@ crate::member_accessors!(ContentFilter { 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 bb8a5380..c4eef491 100644 --- a/src/ua/data_types/content_filter_element.rs +++ b/src/ua/data_types/content_filter_element.rs @@ -23,6 +23,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 004415ff..ea540369 100644 --- a/src/ua/data_types/create_monitored_items_request.rs +++ b/src/ua/data_types/create_monitored_items_request.rs @@ -23,8 +23,9 @@ 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 } diff --git a/src/ua/data_types/delete_monitored_items_request.rs b/src/ua/data_types/delete_monitored_items_request.rs index 6f98519a..db36b48f 100644 --- a/src/ua/data_types/delete_monitored_items_request.rs +++ b/src/ua/data_types/delete_monitored_items_request.rs @@ -24,6 +24,7 @@ impl DeleteMonitoredItemsRequest { &mut self.0.monitoredItemIdsSize, &mut self.0.monitoredItemIds, ); + self } 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/event_filter.rs b/src/ua/data_types/event_filter.rs index 732bfaf1..966f6d06 100644 --- a/src/ua/data_types/event_filter.rs +++ b/src/ua/data_types/event_filter.rs @@ -9,8 +9,9 @@ crate::member_accessors!(EventFilter { 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/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/read_request.rs b/src/ua/data_types/read_request.rs index f6b6102e..e8ba279f 100644 --- a/src/ua/data_types/read_request.rs +++ b/src/ua/data_types/read_request.rs @@ -22,8 +22,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/relative_path.rs b/src/ua/data_types/relative_path.rs index d6f019cf..cb9b7cf8 100644 --- a/src/ua/data_types/relative_path.rs +++ b/src/ua/data_types/relative_path.rs @@ -12,8 +12,9 @@ crate::member_accessors!(RelativePath { 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); + ua::Array::from_slice(elements) + .move_into_raw(&mut self.0.elementsSize, &mut self.0.elements); + self } @@ -28,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 { @@ -77,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/simple_attribute_operand.rs b/src/ua/data_types/simple_attribute_operand.rs index b14cc525..8633fa9b 100644 --- a/src/ua/data_types/simple_attribute_operand.rs +++ b/src/ua/data_types/simple_attribute_operand.rs @@ -20,8 +20,9 @@ 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 } diff --git a/src/ua/data_types/trust_list_data_type.rs b/src/ua/data_types/trust_list_data_type.rs index 11385415..91a35758 100644 --- a/src/ua/data_types/trust_list_data_type.rs +++ b/src/ua/data_types/trust_list_data_type.rs @@ -13,54 +13,72 @@ impl TrustListDataType { #[must_use] pub fn with_trusted_certificates(mut self, trusted_certificates: &[ua::String]) -> 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 { 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); + + 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 { 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 { 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); + + array.move_into_raw( + &mut self.0.issuerCrlsSize, + &mut self.0.issuerCrls + ); + self } } diff --git a/src/ua/data_types/write_request.rs b/src/ua/data_types/write_request.rs index e2a877e0..d9b0a6dd 100644 --- a/src/ua/data_types/write_request.rs +++ b/src/ua/data_types/write_request.rs @@ -9,8 +9,8 @@ crate::member_accessors!(WriteRequest { 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 } } From fa2b5192f6c42f2680bd479540d4567d978302c1 Mon Sep 17 00:00:00 2001 From: Carter Canedy Date: Thu, 15 Oct 2026 11:58:13 -0400 Subject: [PATCH 05/14] composable data type declaration/scaffolding --- src/async_client.rs | 12 +- src/async_subscription.rs | 6 +- src/client.rs | 4 +- src/data_type.rs | 128 +++++++++++------- src/error.rs | 2 +- src/monitored_item.rs | 2 +- src/monitored_item/create_monitored_items.rs | 3 +- src/monitored_item/delete_monitored_items.rs | 6 +- src/server.rs | 52 +++---- src/server/access_control.rs | 4 +- src/ssl.rs | 2 +- src/ua/attribute_write_mask.rs | 33 ++--- src/ua/browse_result_mask.rs | 14 +- src/ua/client.rs | 2 +- src/ua/client_config.rs | 4 +- src/ua/data_types.rs | 23 +++- src/ua/data_types/attribute_operand.rs | 2 +- src/ua/data_types/browse_description.rs | 8 +- src/ua/data_types/content_filter_element.rs | 4 +- .../create_monitored_items_request.rs | 6 +- src/ua/data_types/data_change_filter.rs | 3 +- src/ua/data_types/date_time.rs | 5 +- src/ua/data_types/endpoint_description.rs | 4 +- src/ua/data_types/expanded_node_id.rs | 4 +- .../monitored_item_create_request.rs | 4 +- src/ua/data_types/node_id.rs | 11 +- src/ua/data_types/read_request.rs | 4 +- src/ua/data_types/read_value_id.rs | 9 +- src/ua/data_types/reference_description.rs | 4 +- src/ua/data_types/request_header.rs | 3 +- src/ua/data_types/response_header.rs | 3 +- src/ua/data_types/server_state.rs | 1 + src/ua/data_types/server_status_data_type.rs | 4 +- src/ua/data_types/simple_attribute_operand.rs | 9 +- src/ua/data_types/status_code.rs | 12 +- src/ua/data_types/structure_definition.rs | 4 +- src/ua/data_types/trust_list_data_type.rs | 16 +-- src/ua/data_types/view_description.rs | 8 +- src/ua/data_types/write_value.rs | 4 +- src/ua/key_value_map.rs | 2 +- src/ua/logger/rust_log.rs | 2 +- src/ua/node_class_mask.rs | 16 ++- src/ua/security_level.rs | 14 +- src/ua/server.rs | 2 +- src/ua/server_config.rs | 6 +- src/ua/subscription_id.rs | 2 +- src/ua/unit_id.rs | 11 +- 47 files changed, 272 insertions(+), 212 deletions(-) diff --git a/src/async_client.rs b/src/async_client.rs index 127fb458..fcfbbf1c 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}"); } @@ -294,7 +294,7 @@ impl AsyncClient { return Err(Error::internal("write should return a result")); }; - Error::verify_good(result)?; + Error::verify_good(*result)?; Ok(()) } @@ -327,7 +327,7 @@ impl AsyncClient { 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() { output_arguments.to_vec() @@ -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,7 +827,7 @@ 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(), UA_STATUSCODE_BADDISCONNECT @@ -870,7 +870,7 @@ 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.to_vec() 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 4859d45d..56de2fa0 100644 --- a/src/data_type.rs +++ b/src/data_type.rs @@ -353,36 +353,43 @@ pub(crate) const fn data_type_ref_mut(value: &mut :: unsafe { ptr.unwrap_unchecked() } } -/// Defines wrapper for OPC UA data type from [`open62541_sys`]. +/// Defines the wrapper for OPC UA data type from [`open62541_sys`]. 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. /// /// 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) => { +macro_rules! data_type_baseline { + ($(#[$derives:meta])* $name:ident) => { + paste::paste! { + $crate::data_type::data_type_baseline!($(#[$derives])* $name, [], []); + } + }; + + ($(#[$derives:meta])* $name:ident, $inner:ident) => { paste::paste! { - $crate::data_type!($name, [], []); + $crate::data_type::data_type_baseline!($(#[$derives])* $name, [], []); } }; - ($name:ident, $inner:ident) => { + ($(#[$derives:meta])* $name:ident, #[uses_inner] $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, $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. - pub(crate) open62541_sys::$inner, + open62541_sys::$inner, ); // SAFETY: The types in `open62541` can be sent across thread boundaries. They contain @@ -396,19 +403,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 { @@ -434,12 +428,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); @@ -481,6 +469,37 @@ 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 @@ -491,7 +510,7 @@ pub(crate) use data_type; /// # use crate::ua; /// # crate::data_type!(ReadRequest); /// crate::member_accessors!(ReadRequest { -/// #[enum(UA_TimestampsToReturn)] +/// #[enum] /// timestampsToReturn: ua::TimestampsToReturn, /// nodesToRead: [ua::ReadValueId], /// requestHeader: &mut ua::RequestHeader @@ -524,7 +543,7 @@ macro_rules! member_accessors { #[must_use] pub fn [](&mut self) -> Option> { - unsafe { ua::Array::move_from_raw_parts(&mut self.0.[<$raw_name Size>], &mut self.0.$raw_name) } + unsafe { $crate::ua::Array::move_from_raw_parts(&mut self.0.[<$raw_name Size>], &mut self.0.$raw_name) } } } @@ -573,19 +592,18 @@ macro_rules! member_accessors { (#[from_inner] $raw_name:ident: $wrapper_t:ty $(, $($rest:tt)+)?) => { paste::paste! { pub const fn [<$raw_name:snake>](&self) -> $wrapper_t { - $wrapper_t(self.0.$raw_name) + *$crate::data_type::data_type_ref(&self.0.$raw_name) } } $($crate::member_accessors!($($rest)+);)? }; - // same thing as above, except bindgen enums are #[repr(transparent)] tuple structs containing - // their inner repr, so we need an additional access - (#[enum($inner_t:ty)] $raw_name:ident: $wrapper_t:ty $(, $($rest:tt)+)?) => { + // 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($inner_t(self.0.$raw_name.0)) + $wrapper_t::from_ua(&self.0.$raw_name) } } @@ -635,16 +653,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_attr(not(target_os = "windows"), expect(trivial_numeric_casts))] + let value = value as u32; + + value } } diff --git a/src/error.rs b/src/error.rs index d9e4fd82..a34f88b1 100644 --- a/src/error.rs +++ b/src/error.rs @@ -34,7 +34,7 @@ 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 { diff --git a/src/monitored_item.rs b/src/monitored_item.rs index 1f2f7b1c..03641390 100644 --- a/src/monitored_item.rs +++ b/src/monitored_item.rs @@ -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 1bb219ca..392596a7 100644 --- a/src/server.rs +++ b/src/server.rs @@ -689,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( @@ -699,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) } @@ -748,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( @@ -758,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) } @@ -807,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( @@ -817,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) } @@ -876,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) } @@ -954,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, @@ -1006,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) } @@ -1030,7 +1030,7 @@ impl Server { true, ) }); - Error::verify_good(&status_code) + Error::verify_good(status_code) } /// Adds a reference from one node to another. @@ -1112,7 +1112,7 @@ impl Server { is_forward, ) }); - Error::verify_good(&status_code) + Error::verify_good(status_code) } /// Deletes a reference between two nodes. @@ -1142,7 +1142,7 @@ impl Server { delete_bidirectional, ) }); - Error::verify_good(&status_code) + Error::verify_good(status_code) } /// Creates an event. @@ -1180,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", @@ -1226,7 +1226,7 @@ impl Server { browse_description.as_ptr(), )) }; - Error::verify_good(&result.status_code())?; + Error::verify_good(result.status_code())?; to_browse_result(&result) } @@ -1250,7 +1250,7 @@ impl Server { continuation_point.as_byte_string().as_ptr(), )) }; - Error::verify_good(&result.status_code())?; + Error::verify_good(result.status_code())?; to_browse_result(&result) } @@ -1328,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")); }; @@ -1406,7 +1406,7 @@ impl Server { browse_path_ptr, )) }; - Error::verify_good(&result.status_code())?; + Error::verify_good(result.status_code())?; result .take_targets() @@ -1472,7 +1472,7 @@ impl Server { browse_path.as_ptr(), )) }; - Error::verify_good(&result.status_code())?; + Error::verify_good(result.status_code())?; result .take_targets() @@ -1571,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. @@ -1590,7 +1590,7 @@ impl Server { DataType::to_raw_copy(value), ) }); - Error::verify_good(&status_code) + Error::verify_good(status_code) } /// Reads object property. @@ -1661,7 +1661,7 @@ impl Server { value.as_mut_ptr(), ) }); - Error::verify_good(&status_code)?; + Error::verify_good(status_code)?; Ok(value) } @@ -1728,7 +1728,7 @@ impl Server { DataType::to_raw_copy(value), ) }); - Error::verify_good(&status_code) + Error::verify_good(status_code) } /// Gets server statistics. @@ -1843,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); @@ -1881,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); @@ -1925,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); @@ -1956,7 +1956,7 @@ impl Drop for ServerRunner { fn to_browse_result(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 { 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/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/attribute_write_mask.rs b/src/ua/attribute_write_mask.rs index 1358cf94..beca0c16 100644 --- a/src/ua/attribute_write_mask.rs +++ b/src/ua/attribute_write_mask.rs @@ -11,22 +11,27 @@ use open62541_sys::{ 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, + UA_ATTRIBUTEWRITEMASK_VALUERANK, UA_ATTRIBUTEWRITEMASK_WRITEMASK, }; -use crate::{DataTypeExt, Result, ua}; - -/// Wrapper for [`UA_AttributeWriteMask`] from [`open62541_sys`]. -#[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub struct AttributeWriteMask(pub(crate) UA_AttributeWriteMask); +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)] + AttributeWriteMask, + #[uses_inner] + UInt32 +} // See for bit values. impl AttributeWriteMask { - pub(crate) const fn from_u32(mask: u32) -> Self { + pub const fn from_u32(mask: u32) -> Self { Self(mask) } - pub(crate) const fn as_u32(&self) -> u32 { + pub const fn as_u32(&self) -> u32 { self.0 } @@ -189,15 +194,3 @@ impl AttributeWriteMask { self.0 & UA_ATTRIBUTEWRITEMASK_ACCESSLEVELEX != 0 } } - -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()) - } -} diff --git a/src/ua/browse_result_mask.rs b/src/ua/browse_result_mask.rs index d2288414..8616e710 100644 --- a/src/ua/browse_result_mask.rs +++ b/src/ua/browse_result_mask.rs @@ -23,10 +23,16 @@ mod inner { ); } -/// Wrapper for browse result mask from [`open62541_sys`]. -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] -#[repr(transparent)] -pub struct BrowseResultMask(pub(crate) 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); 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/data_types.rs b/src/ua/data_types.rs index cf583f20..dee6b86d 100644 --- a/src/ua/data_types.rs +++ b/src/ua/data_types.rs @@ -197,7 +197,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] @@ -222,7 +222,28 @@ macro_rules! primitive { } } } + )* + + #[cfg(test)] + mod primitive_assertions { + $( + paste::paste! { + #[test] + fn []() { + let typedef_index = open62541_sys::[]; + let typedef = unsafe { &open62541_sys::UA_TYPES[typedef_index as usize] }; + assert_eq!(typedef.pointerFree(), 1u32, "UA primitives must be pointer free"); + assert_eq!(typedef.membersSize(), 0u32, "UA primitives must have no members"); + assert_eq!( + typedef.memSize() as usize, + ::std::mem::size_of::<$type>(), + "UA primitive types should have same bit-width as Rust wrapped types" + ); + } + } + )* + } }; } diff --git a/src/ua/data_types/attribute_operand.rs b/src/ua/data_types/attribute_operand.rs index 5503a6f3..f758bfd0 100644 --- a/src/ua/data_types/attribute_operand.rs +++ b/src/ua/data_types/attribute_operand.rs @@ -13,7 +13,7 @@ crate::member_accessors!(AttributeOperand { impl AttributeOperand { #[must_use] pub const fn attribute_id(&self) -> ua::AttributeId { - ua::AttributeId(open62541_sys::UA_AttributeId(self.0.attributeId)) + ua::AttributeId::from_u32(self.0.attributeId) } } diff --git a/src/ua/data_types/browse_description.rs b/src/ua/data_types/browse_description.rs index abb25257..34769c03 100644 --- a/src/ua/data_types/browse_description.rs +++ b/src/ua/data_types/browse_description.rs @@ -11,7 +11,7 @@ crate::member_accessors!(BrowseDescription { nodeClassMask: ua::NodeClassMask, #[from_inner] resultMask: ua::BrowseResultMask, - #[skip(uses = BrowseDescription::browse_direction)] + #[enum] browseDirection: ua::BrowseDirection }); @@ -51,12 +51,6 @@ impl BrowseDescription { self.0.resultMask = result_mask.as_u32(); self } - - #[must_use] - pub const fn browse_direction(&self) -> ua::BrowseDirection { - // this is cursed - ua::BrowseDirection(open62541_sys::UA_BrowseDirection(self.0.browseDirection.0)) - } } impl Default for BrowseDescription { diff --git a/src/ua/data_types/content_filter_element.rs b/src/ua/data_types/content_filter_element.rs index c4eef491..dfb33375 100644 --- a/src/ua/data_types/content_filter_element.rs +++ b/src/ua/data_types/content_filter_element.rs @@ -1,10 +1,8 @@ -use open62541_sys::UA_FilterOperator; - use crate::{DataType, FilterOperand, UaIterExt as _, ua}; crate::data_type!(ContentFilterElement); crate::member_accessors!(ContentFilterElement { - #[enum(UA_FilterOperator)] + #[enum] filterOperator: ua::FilterOperator, filterOperands: [ua::ExtensionObject] }); diff --git a/src/ua/data_types/create_monitored_items_request.rs b/src/ua/data_types/create_monitored_items_request.rs index ea540369..c6716d0e 100644 --- a/src/ua/data_types/create_monitored_items_request.rs +++ b/src/ua/data_types/create_monitored_items_request.rs @@ -1,11 +1,9 @@ -use open62541_sys::UA_TimestampsToReturn; - use crate::ua; crate::data_type!(CreateMonitoredItemsRequest); crate::member_accessors!(CreateMonitoredItemsRequest { itemsToCreate: [ua::MonitoredItemCreateRequest], - #[enum(UA_TimestampsToReturn)] + #[enum] timestampsToReturn: ua::TimestampsToReturn, #[skip(uses = Self::subscription_id)] subscriptionId: ua::SubscriptionId @@ -32,7 +30,7 @@ impl CreateMonitoredItemsRequest { #[must_use] pub const fn subscription_id(&self) -> Option { if let Some(id) = ua::IntegerId::from_u32(self.0.subscriptionId) { - Some(ua::SubscriptionId(id)) + Some(ua::SubscriptionId::new(id)) } else { None } diff --git a/src/ua/data_types/data_change_filter.rs b/src/ua/data_types/data_change_filter.rs index 5671738e..0e74bc9d 100644 --- a/src/ua/data_types/data_change_filter.rs +++ b/src/ua/data_types/data_change_filter.rs @@ -1,11 +1,10 @@ use crate::{MonitoringFilter, ua}; -use open62541_sys::UA_DataChangeTrigger; crate::data_type!(DataChangeFilter); crate::member_accessors!(DataChangeFilter { deadbandType: u32, deadbandValue: f64, - #[enum(UA_DataChangeTrigger)] + #[enum] trigger: ua::DataChangeTrigger }); diff --git a/src/ua/data_types/date_time.rs b/src/ua/data_types/date_time.rs index 081e1395..e7bc5b05 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. diff --git a/src/ua/data_types/endpoint_description.rs b/src/ua/data_types/endpoint_description.rs index a88d2a13..cd3c39bb 100644 --- a/src/ua/data_types/endpoint_description.rs +++ b/src/ua/data_types/endpoint_description.rs @@ -1,5 +1,3 @@ -use open62541_sys::UA_MessageSecurityMode; - use crate::ua; crate::data_type!(EndpointDescription); @@ -9,7 +7,7 @@ crate::member_accessors!(EndpointDescription { transportProfileUri: &ua::String, server: &ua::ApplicationDescription, serverCertificate: &ua::ByteString, - #[enum(UA_MessageSecurityMode)] + #[enum] securityMode: ua::MessageSecurityMode, #[from_inner] securityLevel: ua::SecurityLevel diff --git a/src/ua/data_types/expanded_node_id.rs b/src/ua/data_types/expanded_node_id.rs index 5f1f1b87..ced9244a 100644 --- a/src/ua/data_types/expanded_node_id.rs +++ b/src/ua/data_types/expanded_node_id.rs @@ -96,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) } @@ -106,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/monitored_item_create_request.rs b/src/ua/data_types/monitored_item_create_request.rs index 8794f6d1..801c8558 100644 --- a/src/ua/data_types/monitored_item_create_request.rs +++ b/src/ua/data_types/monitored_item_create_request.rs @@ -1,13 +1,13 @@ use std::time::Duration; -use open62541_sys::{UA_MonitoredItemCreateRequest_default, UA_MonitoringMode, UA_NODEID_NUMERIC}; +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(UA_MonitoringMode)] + #[enum] monitoringMode: ua::MonitoringMode, requestedParameters: &ua::MonitoringParameters }); diff --git a/src/ua/data_types/node_id.rs b/src/ua/data_types/node_id.rs index bcdaae9d..75bd748a 100644 --- a/src/ua/data_types/node_id.rs +++ b/src/ua/data_types/node_id.rs @@ -10,7 +10,7 @@ use crate::{DataType, Error, ua}; crate::data_type!(NodeId); crate::member_accessors!(NodeId { namespaceIndex: u16, - #[skip(uses = Self::identifier_type)] + #[enum] identifierType: ua::NodeIdType }); @@ -139,11 +139,6 @@ impl NodeId { self == Self::raw_ref(unsafe { &UA_NODEID_NULL }) } - #[must_use] - pub const fn identifier_type(&self) -> ua::NodeIdType { - ua::NodeIdType(UA_NodeIdType(self.0.identifierType.0)) - } - /// 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 @@ -224,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) } @@ -234,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/read_request.rs b/src/ua/data_types/read_request.rs index e8ba279f..eac2fa22 100644 --- a/src/ua/data_types/read_request.rs +++ b/src/ua/data_types/read_request.rs @@ -1,10 +1,8 @@ -use open62541_sys::UA_TimestampsToReturn; - use crate::{DataType as _, ServiceRequest, ua}; crate::data_type!(ReadRequest); crate::member_accessors!(ReadRequest { - #[enum(UA_TimestampsToReturn)] + #[enum] timestampsToReturn: ua::TimestampsToReturn, nodesToRead: [ua::ReadValueId], requestHeader: &mut ua::RequestHeader diff --git a/src/ua/data_types/read_value_id.rs b/src/ua/data_types/read_value_id.rs index 30be4c66..922a0253 100644 --- a/src/ua/data_types/read_value_id.rs +++ b/src/ua/data_types/read_value_id.rs @@ -1,5 +1,3 @@ -use open62541_sys::UA_AttributeId; - use crate::{DataType as _, ua}; crate::data_type!(ReadValueId); @@ -26,6 +24,11 @@ impl ReadValueId { #[must_use] pub const fn attribute_id(&self) -> ua::AttributeId { - ua::AttributeId(UA_AttributeId(self.0.attributeId)) + let value = self.0.attributeId; + + #[cfg_attr(not(target_os = "windows"), expect(trivial_numeric_casts))] + 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 5a9b8096..5fbdac99 100644 --- a/src/ua/data_types/reference_description.rs +++ b/src/ua/data_types/reference_description.rs @@ -1,5 +1,3 @@ -use open62541_sys::UA_NodeClass; - use crate::ua; crate::data_type!(ReferenceDescription); @@ -10,6 +8,6 @@ crate::member_accessors!(ReferenceDescription { browseName: &ua::QualifiedName, displayName: &ua::LocalizedText, typeDefinition: &ua::ExpandedNodeId, - #[enum(UA_NodeClass)] + #[enum] nodeClass: ua::NodeClass }); diff --git a/src/ua/data_types/request_header.rs b/src/ua/data_types/request_header.rs index 3210cf50..755ab9f3 100644 --- a/src/ua/data_types/request_header.rs +++ b/src/ua/data_types/request_header.rs @@ -4,11 +4,12 @@ use crate::ua; crate::data_type!(RequestHeader); crate::member_accessors!(RequestHeader { - timestamp: i64, 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 diff --git a/src/ua/data_types/response_header.rs b/src/ua/data_types/response_header.rs index 6b3707cf..1e1d8e1a 100644 --- a/src/ua/data_types/response_header.rs +++ b/src/ua/data_types/response_header.rs @@ -4,7 +4,8 @@ crate::data_type!(ResponseHeader); crate::member_accessors!(ResponseHeader { additionalHeader: &ua::ExtensionObject, requestHandle: u32, - timestamp: i64, + #[from_inner] + timestamp: ua::DateTime, serviceDiagnostics: &ua::DiagnosticInfo, #[from_inner] serviceResult: ua::StatusCode, diff --git a/src/ua/data_types/server_state.rs b/src/ua/data_types/server_state.rs index 0b23ddf9..6e3c224d 100644 --- a/src/ua/data_types/server_state.rs +++ b/src/ua/data_types/server_state.rs @@ -6,6 +6,7 @@ crate::enum_variants!( FAILED, NOCONFIGURATION, SUSPENDED, + RUNNING, SHUTDOWN, TEST, COMMUNICATIONFAULT, diff --git a/src/ua/data_types/server_status_data_type.rs b/src/ua/data_types/server_status_data_type.rs index 54f4eaf7..f3f92453 100644 --- a/src/ua/data_types/server_status_data_type.rs +++ b/src/ua/data_types/server_status_data_type.rs @@ -1,11 +1,9 @@ -use open62541_sys::UA_ServerState; - use crate::ua; crate::data_type!(ServerStatusDataType); crate::member_accessors!(ServerStatusDataType { buildInfo: &ua::BuildInfo, - #[enum(UA_ServerState)] + #[enum] state: ua::ServerState, #[from_inner] startTime: ua::DateTime, diff --git a/src/ua/data_types/simple_attribute_operand.rs b/src/ua/data_types/simple_attribute_operand.rs index 8633fa9b..73c040ef 100644 --- a/src/ua/data_types/simple_attribute_operand.rs +++ b/src/ua/data_types/simple_attribute_operand.rs @@ -1,5 +1,3 @@ -use open62541_sys::UA_AttributeId; - use crate::{DataType, FilterOperand, ua}; crate::data_type!(SimpleAttributeOperand); @@ -40,7 +38,12 @@ impl SimpleAttributeOperand { #[must_use] pub const fn attribute_id(&self) -> ua::AttributeId { - ua::AttributeId(UA_AttributeId(self.0.attributeId)) + let value = self.0.attributeId; + + #[cfg_attr(not(target_os = "windows"), expect(trivial_numeric_casts))] + let value = value as u32; + + ua::AttributeId::from_u32(value) } } diff --git a/src/ua/data_types/status_code.rs b/src/ua/data_types/status_code.rs index 231a4440..f306fe29 100644 --- a/src/ua/data_types/status_code.rs +++ b/src/ua/data_types/status_code.rs @@ -5,7 +5,7 @@ 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 +31,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 +40,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 +49,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 +65,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 a7833d35..5d67312c 100644 --- a/src/ua/data_types/structure_definition.rs +++ b/src/ua/data_types/structure_definition.rs @@ -1,12 +1,10 @@ -use open62541_sys::UA_StructureType; - use crate::ua; crate::data_type!(StructureDefinition); crate::member_accessors!(StructureDefinition { defaultEncodingId: &ua::NodeId, baseDataType: &ua::NodeId, - #[enum(UA_StructureType)] + #[enum] structureType: ua::StructureType, fields: [ua::StructureField] }); diff --git a/src/ua/data_types/trust_list_data_type.rs b/src/ua/data_types/trust_list_data_type.rs index 91a35758..e7058d13 100644 --- a/src/ua/data_types/trust_list_data_type.rs +++ b/src/ua/data_types/trust_list_data_type.rs @@ -2,16 +2,16 @@ use crate::ua; crate::data_type!(TrustListDataType); crate::member_accessors!(TrustListDataType { - trustedCertificates: [ua::String], - trustedCrls: [ua::String], - issuerCertificates: [ua::String], - issuerCrls: [ua::String], + 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() { @@ -29,7 +29,7 @@ impl TrustListDataType { } #[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() { @@ -47,7 +47,7 @@ impl TrustListDataType { } #[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() { @@ -65,7 +65,7 @@ impl TrustListDataType { } #[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() { diff --git a/src/ua/data_types/view_description.rs b/src/ua/data_types/view_description.rs index b71feed4..3bf1e60c 100644 --- a/src/ua/data_types/view_description.rs +++ b/src/ua/data_types/view_description.rs @@ -4,7 +4,8 @@ crate::data_type!(ViewDescription); crate::member_accessors!(ViewDescription { viewId: &ua::NodeId, viewVersion: u32, - timestamp: i64 + #[from_inner] + timestamp: ua::DateTime }); impl ViewDescription { @@ -21,8 +22,9 @@ impl ViewDescription { } #[must_use] - pub const fn with_timestamp(mut self, ts: i64) -> Self { - self.0.timestamp = ts; + pub const fn with_timestamp(mut self, ts: ua::DateTime) -> Self { + // SAFETY: repr(transparent) guarantees that DateTime has identical bit-pattern + self.0.timestamp = unsafe { std::mem::transmute(ts) }; self } } diff --git a/src/ua/data_types/write_value.rs b/src/ua/data_types/write_value.rs index 5c886e71..1dc0770e 100644 --- a/src/ua/data_types/write_value.rs +++ b/src/ua/data_types/write_value.rs @@ -1,5 +1,3 @@ -use open62541_sys::UA_AttributeId; - use crate::{DataType as _, ua}; crate::data_type!(WriteValue); @@ -32,6 +30,6 @@ impl WriteValue { #[must_use] pub const fn attribute_id(&self) -> ua::AttributeId { - ua::AttributeId(UA_AttributeId(self.0.attributeId)) + ua::AttributeId::from_u32(self.0.attributeId) } } 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 b9e1d59a..07ff252e 100644 --- a/src/ua/security_level.rs +++ b/src/ua/security_level.rs @@ -1,10 +1,18 @@ +//! 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(pub(crate) UA_Byte); +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)] + SecurityLevel, + #[uses_inner] + Byte +} impl SecurityLevel { #[must_use] 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/subscription_id.rs b/src/ua/subscription_id.rs index b5ed4821..83a098cd 100644 --- a/src/ua/subscription_id.rs +++ b/src/ua/subscription_id.rs @@ -6,7 +6,7 @@ use crate::ua; // // Newtype wrapper for [`ua::IntegerId`]. #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] -pub struct SubscriptionId(pub(crate) ua::IntegerId); +pub struct SubscriptionId(ua::IntegerId); impl SubscriptionId { #[must_use] diff --git a/src/ua/unit_id.rs b/src/ua/unit_id.rs index 0bb323a9..65a1ac70 100644 --- a/src/ua/unit_id.rs +++ b/src/ua/unit_id.rs @@ -1,8 +1,13 @@ +//! 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(pub(crate) i32); +crate::data_type::data_type_baseline!( + #[expect(clippy::derived_hash_with_manual_eq)] + #[derive(Clone, Copy, Hash)] + UnitId, + #[uses_inner] + Int32 +); impl UnitId { #[must_use] From 28de5af7892da3047c337c53f5580a6dc5c7a5b5 Mon Sep 17 00:00:00 2001 From: Carter Canedy Date: Thu, 15 Oct 2026 16:17:06 -0400 Subject: [PATCH 06/14] fmt + clippy --- src/async_client.rs | 16 ++++++++-------- src/data_type.rs | 13 ++++++------- src/error.rs | 6 +++--- src/server.rs | 10 +++++----- src/server/data_source.rs | 2 +- src/server/method_callback.rs | 2 +- src/ua/attribute_write_mask.rs | 2 ++ src/ua/data_types.rs | 14 ++++++++++---- src/ua/data_types/browse_next_request.rs | 1 + src/ua/data_types/browse_next_response.rs | 12 ++++-------- src/ua/data_types/browse_path.rs | 14 ++++---------- src/ua/data_types/browse_response.rs | 13 +++++-------- src/ua/data_types/date_time.rs | 5 +++++ .../data_types/monitored_item_create_request.rs | 4 ++-- src/ua/data_types/read_value_id.rs | 4 ++-- src/ua/data_types/simple_attribute_operand.rs | 4 ++-- src/ua/data_types/status_code.rs | 5 ++++- src/ua/data_types/trust_list_data_type.rs | 10 ++-------- src/ua/data_types/view_description.rs | 3 +-- src/ua/data_types/write_value.rs | 2 +- 20 files changed, 69 insertions(+), 73 deletions(-) diff --git a/src/async_client.rs b/src/async_client.rs index fcfbbf1c..b5b49497 100644 --- a/src/async_client.rs +++ b/src/async_client.rs @@ -317,20 +317,20 @@ 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.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())?; - let output_arguments = if let Some(output_arguments) = result.output_arguments() { - output_arguments.to_vec() + 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"); Vec::new() @@ -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")); }; @@ -829,7 +829,7 @@ fn background_task(client: &ua::Client, state: &AtomicU8) { 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 diff --git a/src/data_type.rs b/src/data_type.rs index 56de2fa0..7e0881cf 100644 --- a/src/data_type.rs +++ b/src/data_type.rs @@ -353,12 +353,11 @@ pub(crate) const fn data_type_ref_mut(value: &mut :: unsafe { ptr.unwrap_unchecked() } } -/// Defines the wrapper for OPC UA data type from [`open62541_sys`]. 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. +/// 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. +/// 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! { @@ -484,7 +483,7 @@ macro_rules! data_type { } } - // only + // only impl Drop for $name { fn drop(&mut self) { // `UA_clear()` resets the data structure, freeing any dynamically allocated memory @@ -675,7 +674,7 @@ macro_rules! enum_variants { pub(crate) const fn as_u32(&self) -> u32 { let value = self.0.0; - #[cfg_attr(not(target_os = "windows"), expect(trivial_numeric_casts))] + #[cfg(target_os = "windows")] let value = value as u32; value diff --git a/src/error.rs b/src/error.rs index a34f88b1..1d8372c5 100644 --- a/src/error.rs +++ b/src/error.rs @@ -38,7 +38,7 @@ impl Error { 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/server.rs b/src/server.rs index 392596a7..0df26421 100644 --- a/src/server.rs +++ b/src/server.rs @@ -1227,7 +1227,7 @@ impl Server { )) }; Error::verify_good(result.status_code())?; - to_browse_result(&result) + to_browse_result(result) } /// Browses continuation point for more references. @@ -1251,7 +1251,7 @@ impl Server { )) }; Error::verify_good(result.status_code())?; - to_browse_result(&result) + to_browse_result(result) } /// Browses nodes recursively. @@ -1953,16 +1953,16 @@ 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())?; - let Some(references) = result.references() else { + let Some(references) = result.take_references() else { return Err(Error::internal("browse should return references")); }; - Ok((references.to_vec(), result.continuation_point())) + Ok((references.into_vec(), result.continuation_point())) } /// Sets `config.nodeLifecycle` to the given value. 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/ua/attribute_write_mask.rs b/src/ua/attribute_write_mask.rs index beca0c16..e320ab6f 100644 --- a/src/ua/attribute_write_mask.rs +++ b/src/ua/attribute_write_mask.rs @@ -27,10 +27,12 @@ crate::data_type::data_type_baseline! { // See for bit values. impl AttributeWriteMask { + #[must_use] pub const fn from_u32(mask: u32) -> Self { Self(mask) } + #[must_use] pub const fn as_u32(&self) -> u32 { self.0 } diff --git a/src/ua/data_types.rs b/src/ua/data_types.rs index dee6b86d..bbae9638 100644 --- a/src/ua/data_types.rs +++ b/src/ua/data_types.rs @@ -231,12 +231,18 @@ macro_rules! primitive { paste::paste! { #[test] fn []() { - let typedef_index = open62541_sys::[]; - let typedef = unsafe { &open62541_sys::UA_TYPES[typedef_index as usize] }; + #[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!( - typedef.memSize() as usize, + mem_size, ::std::mem::size_of::<$type>(), "UA primitive types should have same bit-width as Rust wrapped types" ); @@ -257,6 +263,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/browse_next_request.rs b/src/ua/data_types/browse_next_request.rs index e73cc68b..0ed2a699 100644 --- a/src/ua/data_types/browse_next_request.rs +++ b/src/ua/data_types/browse_next_request.rs @@ -3,6 +3,7 @@ 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 }); 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_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/date_time.rs b/src/ua/data_types/date_time.rs index e7bc5b05..8bc0f0f5 100644 --- a/src/ua/data_types/date_time.rs +++ b/src/ua/data_types/date_time.rs @@ -45,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/monitored_item_create_request.rs b/src/ua/data_types/monitored_item_create_request.rs index 801c8558..4d35b745 100644 --- a/src/ua/data_types/monitored_item_create_request.rs +++ b/src/ua/data_types/monitored_item_create_request.rs @@ -33,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 } @@ -97,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/read_value_id.rs b/src/ua/data_types/read_value_id.rs index 922a0253..f69f8fdf 100644 --- a/src/ua/data_types/read_value_id.rs +++ b/src/ua/data_types/read_value_id.rs @@ -17,7 +17,7 @@ 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 } @@ -26,7 +26,7 @@ impl ReadValueId { pub const fn attribute_id(&self) -> ua::AttributeId { let value = self.0.attributeId; - #[cfg_attr(not(target_os = "windows"), expect(trivial_numeric_casts))] + #[cfg(target_os = "windows")] let value = value as u32; ua::AttributeId::from_u32(value) diff --git a/src/ua/data_types/simple_attribute_operand.rs b/src/ua/data_types/simple_attribute_operand.rs index 73c040ef..cbae415d 100644 --- a/src/ua/data_types/simple_attribute_operand.rs +++ b/src/ua/data_types/simple_attribute_operand.rs @@ -25,7 +25,7 @@ impl SimpleAttributeOperand { } #[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 } @@ -40,7 +40,7 @@ impl SimpleAttributeOperand { pub const fn attribute_id(&self) -> ua::AttributeId { let value = self.0.attributeId; - #[cfg_attr(not(target_os = "windows"), expect(trivial_numeric_casts))] + #[cfg(target_os = "windows")] let value = value as u32; ua::AttributeId::from_u32(value) diff --git a/src/ua/data_types/status_code.rs b/src/ua/data_types/status_code.rs index f306fe29..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::data_type_baseline!(#[derive(Copy, Clone)] StatusCode); +crate::data_type::data_type_baseline!( + #[derive(Copy, Clone)] + StatusCode +); impl StatusCode { /// Creates wrapper by taking ownership of `src`. diff --git a/src/ua/data_types/trust_list_data_type.rs b/src/ua/data_types/trust_list_data_type.rs index e7058d13..f886a998 100644 --- a/src/ua/data_types/trust_list_data_type.rs +++ b/src/ua/data_types/trust_list_data_type.rs @@ -38,10 +38,7 @@ impl TrustListDataType { self.0.specifiedLists |= ua::TrustListMasks::TRUSTEDCRLS.as_u32(); } - array.move_into_raw( - &mut self.0.trustedCrlsSize, - &mut self.0.trustedCrls - ); + array.move_into_raw(&mut self.0.trustedCrlsSize, &mut self.0.trustedCrls); self } @@ -74,10 +71,7 @@ impl TrustListDataType { self.0.specifiedLists |= ua::TrustListMasks::ISSUERCRLS.as_u32(); } - array.move_into_raw( - &mut self.0.issuerCrlsSize, - &mut self.0.issuerCrls - ); + array.move_into_raw(&mut self.0.issuerCrlsSize, &mut self.0.issuerCrls); self } diff --git a/src/ua/data_types/view_description.rs b/src/ua/data_types/view_description.rs index 3bf1e60c..d26f8deb 100644 --- a/src/ua/data_types/view_description.rs +++ b/src/ua/data_types/view_description.rs @@ -23,8 +23,7 @@ impl ViewDescription { #[must_use] pub const fn with_timestamp(mut self, ts: ua::DateTime) -> Self { - // SAFETY: repr(transparent) guarantees that DateTime has identical bit-pattern - self.0.timestamp = unsafe { std::mem::transmute(ts) }; + self.0.timestamp = ts.as_i64(); self } } diff --git a/src/ua/data_types/write_value.rs b/src/ua/data_types/write_value.rs index 1dc0770e..99e10e16 100644 --- a/src/ua/data_types/write_value.rs +++ b/src/ua/data_types/write_value.rs @@ -17,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 } From 252e45c10ab8110dcb355f44fa0aad0d765e9411 Mon Sep 17 00:00:00 2001 From: Carter Canedy Date: Wed, 21 Oct 2026 12:51:53 -0500 Subject: [PATCH 07/14] fix extension object encoding enum impl macro --- src/ua/data_types/extension_object.rs | 13 +++----- .../data_types/extension_object_encoding.rs | 31 ++++++++++++++++--- src/ua/integer_id.rs | 2 +- 3 files changed, 32 insertions(+), 14 deletions(-) diff --git a/src/ua/data_types/extension_object.rs b/src/ua/data_types/extension_object.rs index 3549f2f7..9535b2ea 100644 --- a/src/ua/data_types/extension_object.rs +++ b/src/ua/data_types/extension_object.rs @@ -9,7 +9,7 @@ use crate::{ crate::data_type!(ExtensionObject); crate::member_accessors!(ExtensionObject { - #[skip(uses = Self::encoding)] + #[enum] encoding: ua::ExtensionObjectEncoding }); @@ -34,16 +34,11 @@ impl ExtensionObject { extension_object } - #[must_use] - pub const fn encoding(&self) -> ua::ExtensionObjectEncoding { - ua::ExtensionObjectEncoding(self.0.encoding.0) - } - /// 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.0, + self.encoding().as_u32(), ExtensionObjectEncoding::ENCODED_NOBODY_U32 | ExtensionObjectEncoding::ENCODED_BYTESTRING_U32 | ExtensionObjectEncoding::ENCODED_XML_U32 @@ -54,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 index 73f54354..f843fa56 100644 --- a/src/ua/data_types/extension_object_encoding.rs +++ b/src/ua/data_types/extension_object_encoding.rs @@ -1,7 +1,28 @@ use open62541_sys::UA_ExtensionObjectEncoding; -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub struct ExtensionObjectEncoding(pub(crate) u32); +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)+) => { @@ -12,8 +33,10 @@ macro_rules! impl_variants { ($variant:ident = $discriminant:expr $(, $($rest:tt)+)?) => { paste::paste! { - pub const $variant: Self = Self($discriminant); - pub const [<$variant _U32>]: u32 = $discriminant; + 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>] diff --git a/src/ua/integer_id.rs b/src/ua/integer_id.rs index ae03a403..87e6ff82 100644 --- a/src/ua/integer_id.rs +++ b/src/ua/integer_id.rs @@ -6,7 +6,7 @@ use crate::ua; /// /// Only stores valid, non-zero values. #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] -pub struct IntegerId(pub(crate) NonZeroU32); +pub struct IntegerId(NonZeroU32); impl IntegerId { #[must_use] From e5351d6c06f2339e078591d769045ba08c1416ee Mon Sep 17 00:00:00 2001 From: Carter Canedy Date: Fri, 23 Oct 2026 11:16:40 -0500 Subject: [PATCH 08/14] fix incomplete member accessor impl --- src/ua/data_types.rs | 4 ++++ src/ua/data_types/endpoint_description.rs | 3 ++- src/ua/data_types/user_token_policy.rs | 11 +++++++++++ src/ua/data_types/user_token_type.rs | 6 ++++++ src/ua/user_identity_token.rs | 2 +- 5 files changed, 24 insertions(+), 2 deletions(-) create mode 100644 src/ua/data_types/user_token_policy.rs create mode 100644 src/ua/data_types/user_token_type.rs diff --git a/src/ua/data_types.rs b/src/ua/data_types.rs index bbae9638..6947414a 100644 --- a/src/ua/data_types.rs +++ b/src/ua/data_types.rs @@ -87,6 +87,8 @@ 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; @@ -186,6 +188,8 @@ pub use self::{ 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, diff --git a/src/ua/data_types/endpoint_description.rs b/src/ua/data_types/endpoint_description.rs index cd3c39bb..cfb0305a 100644 --- a/src/ua/data_types/endpoint_description.rs +++ b/src/ua/data_types/endpoint_description.rs @@ -10,5 +10,6 @@ crate::member_accessors!(EndpointDescription { #[enum] securityMode: ua::MessageSecurityMode, #[from_inner] - securityLevel: ua::SecurityLevel + securityLevel: ua::SecurityLevel, + userIdentityTokens: [ua::UserTokenPolicy] }); 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/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), From 133620e6c4ff58a53c895f80f4795007e844cd99 Mon Sep 17 00:00:00 2001 From: Carter Canedy Date: Tue, 3 Nov 2026 10:21:53 -0600 Subject: [PATCH 09/14] baseline for `EventNotifierType` --- src/ua/event_notifier_type.rs | 87 ++++++++++++++++++++++++----------- 1 file changed, 60 insertions(+), 27 deletions(-) mode change 100644 => 100755 src/ua/event_notifier_type.rs diff --git a/src/ua/event_notifier_type.rs b/src/ua/event_notifier_type.rs old mode 100644 new mode 100755 index 0023d701..9b11ac7e --- a/src/ua/event_notifier_type.rs +++ b/src/ua/event_notifier_type.rs @@ -1,13 +1,56 @@ -use open62541_sys::{ - UA_EVENTNOTIFIERTYPE_HISTORYREAD, UA_EVENTNOTIFIERTYPE_HISTORYWRITE, - UA_EVENTNOTIFIERTYPE_SUBSCRIBETOEVENTS, UA_EventNotifierType, -}; +use std::ops::{BitAnd, BitOr}; -use crate::{DataTypeExt, Result, ua}; +crate::data_type::data_type_baseline!( + #[derive(Clone, Copy, Hash)] + EventNotifierType +); -/// Wrapper for [`UA_EventNotifierType`] from [`open62541_sys`]. -#[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub struct EventNotifierType(UA_EventNotifierType); +impl BitAnd for EventNotifierType { + type Output = Self; + + fn bitand(self, rhs: Self) -> Self { + Self(self.0 & rhs.0) + } +} + +impl BitOr for EventNotifierType { + type Output = Self; + + fn bitor(self, rhs: Self) -> Self { + Self(self.0 | rhs.0) + } +} + +macro_rules! impl_event_type { + ($($event_t:ident),+ $(,)?) => { + paste::paste! { + impl EventNotifierType { + $( + #[expect(clippy::as_conversion, reason = "well known variant must be representable as u8")] + pub const $event_t: Self = Self::from_u8(::open62541_sys::[] as u8); + )+ + } + + + #[cfg(test)] + mod tests { + $( + #[test] + fn []() { + u8::try_from(::open62541_sys::[]).expect("EventNotifierType variant must be convertible to u8"); + } + )+ + } + } + } +} + +impl_event_type! { + NONE, + SUBSCRIBETOEVENTS, + HISTORYREAD, + HISTORYWRITE, +} // See for bit values. impl EventNotifierType { @@ -15,37 +58,27 @@ impl EventNotifierType { Self(mask) } - pub(crate) const fn as_u8(&self) -> u8 { - self.0 + #[must_use] + pub const fn none(self) -> bool { + self.0 == Self::NONE.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 + pub const fn subscribe_to_events(self) -> bool { + self.0 & Self::SUBSCRIBETOEVENTS.0 != 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 + pub const fn history_read(self) -> bool { + self.0 & Self::HISTORYREAD.0 != 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 + pub const fn history_write(self) -> bool { + self.0 & Self::HISTORYWRITE.0 != 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()) - } -} From 1cf219ac751b4afb39450bbd34d7b9c7dfc91904 Mon Sep 17 00:00:00 2001 From: Carter Canedy Date: Tue, 3 Nov 2026 10:38:45 -0600 Subject: [PATCH 10/14] add u32 members to `EventNotifierType` --- src/ua/event_notifier_type.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ua/event_notifier_type.rs b/src/ua/event_notifier_type.rs index 9b11ac7e..3934833d 100755 --- a/src/ua/event_notifier_type.rs +++ b/src/ua/event_notifier_type.rs @@ -28,6 +28,7 @@ macro_rules! impl_event_type { $( #[expect(clippy::as_conversion, reason = "well known variant must be representable as u8")] pub const $event_t: Self = Self::from_u8(::open62541_sys::[] as u8); + pub const [<$event_t _U32>]: u32 = Self::$event_t.0 as u32; )+ } @@ -81,4 +82,3 @@ impl EventNotifierType { self.0 & Self::HISTORYWRITE.0 != 0 } } - From 879e91ee4ecfcf991d2044cb03023ccfc66b7efb Mon Sep 17 00:00:00 2001 From: Carter Canedy Date: Tue, 3 Nov 2026 11:07:02 -0600 Subject: [PATCH 11/14] fix lints --- src/ua/attribute_write_mask.rs | 42 ++++++++++++++++++++++++++++++++++ src/ua/event_notifier_type.rs | 8 ++++++- 2 files changed, 49 insertions(+), 1 deletion(-) diff --git a/src/ua/attribute_write_mask.rs b/src/ua/attribute_write_mask.rs index e320ab6f..2aa604cb 100644 --- a/src/ua/attribute_write_mask.rs +++ b/src/ua/attribute_write_mask.rs @@ -25,6 +25,48 @@ crate::data_type::data_type_baseline! { UInt32 } +macro_rules! impl_variant { + ($($mask:ident),+ $(,)?) => { + paste::paste! { + impl AttributeWriteMask { + $( + pub const $mask: Self = Self([]); + pub const [<$mask _U32>]: u32 = Self::$mask.0; + )+ + } + } + } +} + +impl_variant! { + ACCESSLEVEL, + ACCESSLEVELEX, + ACCESSRESTRICTIONS, + ARRAYDIMENSIONS, + BROWSENAME, + CONTAINSNOLOOPS, + DATATYPE, + DATATYPEDEFINITION, + DESCRIPTION, + DISPLAYNAME, + EVENTNOTIFIER, + EXECUTABLE, + HISTORIZING, + INVERSENAME, + ISABSTRACT, + MINIMUMSAMPLINGINTERVAL, + NODECLASS, + NODEID, + ROLEPERMISSIONS, + SYMMETRIC, + USERACCESSLEVEL, + USEREXECUTABLE, + USERWRITEMASK, + VALUEFORVARIABLETYPE, + VALUERANK, + WRITEMASK, +} + // See for bit values. impl AttributeWriteMask { #[must_use] diff --git a/src/ua/event_notifier_type.rs b/src/ua/event_notifier_type.rs index 3934833d..03655ce7 100755 --- a/src/ua/event_notifier_type.rs +++ b/src/ua/event_notifier_type.rs @@ -1,6 +1,10 @@ use std::ops::{BitAnd, BitOr}; 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 ); @@ -26,8 +30,10 @@ macro_rules! impl_event_type { paste::paste! { impl EventNotifierType { $( - #[expect(clippy::as_conversion, reason = "well known variant must be representable as u8")] + #[expect(clippy::cast_possible_truncation, reason = "well known variant must be representable as u8")] + #[expect(clippy::as_conversions, reason = "safe u8 to u32 cast")] pub const $event_t: Self = Self::from_u8(::open62541_sys::[] as u8); + #[expect(clippy::as_conversions, reason = "safe u8 to u32 cast")] pub const [<$event_t _U32>]: u32 = Self::$event_t.0 as u32; )+ } From fa7818b6c44c16a37e3b7f2254fe6033f0989c69 Mon Sep 17 00:00:00 2001 From: Carter Canedy Date: Tue, 3 Nov 2026 13:33:37 -0600 Subject: [PATCH 12/14] coalesce bitmask enum impls into macro --- src/data_type.rs | 93 +++++++++++ src/lib.rs | 4 +- src/ua/access_level_ex_type.rs | 170 ++++++------------- src/ua/access_level_type.rs | 142 ++++------------ src/ua/attribute_write_mask.rs | 293 +++++++-------------------------- src/ua/event_notifier_type.rs | 89 +--------- src/ua/security_level.rs | 2 +- src/ua/unit_id.rs | 2 +- 8 files changed, 250 insertions(+), 545 deletions(-) diff --git a/src/data_type.rs b/src/data_type.rs index 7e0881cf..7ca5dd58 100644 --- a/src/data_type.rs +++ b/src/data_type.rs @@ -723,6 +723,99 @@ 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) + } + } + + #[expect(clippy::allow_attributes, reason = "some implementers may be u32")] + impl $name { + #[allow(unused, reason = "not used by all implementers")] + pub(crate) const fn [](self) -> $inner_t { + self.0 + } + + #[allow(unused, reason = "not used by all implementers")] + 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")] + 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; + + $( + #[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.0 & Self::[<$variant:upper>].0 == 0 + } + + #[must_use] + pub const fn [](self, value: bool) -> Self { + Self(if value { + self.0 | Self::[<$variant:upper>].0 + } else { + self.0 & !Self::[<$variant:upper>].0 + }) + } + )+ + } + + #[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/lib.rs b/src/lib.rs index 54b5ede4..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, member_accessors}, + 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/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/attribute_write_mask.rs b/src/ua/attribute_write_mask.rs index 2aa604cb..0792c6f9 100644 --- a/src/ua/attribute_write_mask.rs +++ b/src/ua/attribute_write_mask.rs @@ -1,240 +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, -}; - -crate::data_type::data_type_baseline! { +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, - #[uses_inner] - UInt32 -} - -macro_rules! impl_variant { - ($($mask:ident),+ $(,)?) => { - paste::paste! { - impl AttributeWriteMask { - $( - pub const $mask: Self = Self([]); - pub const [<$mask _U32>]: u32 = Self::$mask.0; - )+ - } - } - } -} - -impl_variant! { - ACCESSLEVEL, - ACCESSLEVELEX, - ACCESSRESTRICTIONS, - ARRAYDIMENSIONS, - BROWSENAME, - CONTAINSNOLOOPS, - DATATYPE, - DATATYPEDEFINITION, - DESCRIPTION, - DISPLAYNAME, - EVENTNOTIFIER, - EXECUTABLE, - HISTORIZING, - INVERSENAME, - ISABSTRACT, - MINIMUMSAMPLINGINTERVAL, - NODECLASS, - NODEID, - ROLEPERMISSIONS, - SYMMETRIC, - USERACCESSLEVEL, - USEREXECUTABLE, - USERWRITEMASK, - VALUEFORVARIABLETYPE, - VALUERANK, - WRITEMASK, + AttributeWriteMask } // See for bit values. -impl AttributeWriteMask { - #[must_use] - pub const fn from_u32(mask: u32) -> Self { - Self(mask) - } - - #[must_use] - pub 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::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/event_notifier_type.rs b/src/ua/event_notifier_type.rs index 03655ce7..39b0b1c0 100755 --- a/src/ua/event_notifier_type.rs +++ b/src/ua/event_notifier_type.rs @@ -1,5 +1,3 @@ -use std::ops::{BitAnd, BitOr}; - crate::data_type::data_type_baseline!( #[expect( clippy::derived_hash_with_manual_eq, @@ -9,82 +7,13 @@ crate::data_type::data_type_baseline!( EventNotifierType ); -impl BitAnd for EventNotifierType { - type Output = Self; - - fn bitand(self, rhs: Self) -> Self { - Self(self.0 & rhs.0) - } -} - -impl BitOr for EventNotifierType { - type Output = Self; - - fn bitor(self, rhs: Self) -> Self { - Self(self.0 | rhs.0) +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, } -} - -macro_rules! impl_event_type { - ($($event_t:ident),+ $(,)?) => { - paste::paste! { - impl EventNotifierType { - $( - #[expect(clippy::cast_possible_truncation, reason = "well known variant must be representable as u8")] - #[expect(clippy::as_conversions, reason = "safe u8 to u32 cast")] - pub const $event_t: Self = Self::from_u8(::open62541_sys::[] as u8); - #[expect(clippy::as_conversions, reason = "safe u8 to u32 cast")] - pub const [<$event_t _U32>]: u32 = Self::$event_t.0 as u32; - )+ - } - - - #[cfg(test)] - mod tests { - $( - #[test] - fn []() { - u8::try_from(::open62541_sys::[]).expect("EventNotifierType variant must be convertible to u8"); - } - )+ - } - } - } -} - -impl_event_type! { - NONE, - SUBSCRIBETOEVENTS, - HISTORYREAD, - HISTORYWRITE, -} - -// See for bit values. -impl EventNotifierType { - pub(crate) const fn from_u8(mask: u8) -> Self { - Self(mask) - } - - #[must_use] - pub const fn none(self) -> bool { - self.0 == Self::NONE.0 - } - - /// Indicates if it can be used to subscribe to events. - #[must_use] - pub const fn subscribe_to_events(self) -> bool { - self.0 & Self::SUBSCRIBETOEVENTS.0 != 0 - } - - /// Indicates if the history of the events is readable. - #[must_use] - pub const fn history_read(self) -> bool { - self.0 & Self::HISTORYREAD.0 != 0 - } - - /// Indicates if the history of the events is writeable. - #[must_use] - pub const fn history_write(self) -> bool { - self.0 & Self::HISTORYWRITE.0 != 0 - } -} +); diff --git a/src/ua/security_level.rs b/src/ua/security_level.rs index 07ff252e..bac13da8 100644 --- a/src/ua/security_level.rs +++ b/src/ua/security_level.rs @@ -3,7 +3,7 @@ use open62541_sys::UA_Byte; use crate::ua; -crate::data_type::data_type_baseline! { +crate::data_type_baseline! { #[expect( clippy::derived_hash_with_manual_eq, reason = "open62541 numeric equality impl satisfies `Hash`s identity invariant" diff --git a/src/ua/unit_id.rs b/src/ua/unit_id.rs index 65a1ac70..b26de28c 100644 --- a/src/ua/unit_id.rs +++ b/src/ua/unit_id.rs @@ -1,7 +1,7 @@ //! Wrapper for [`ua::EUInformation::unit_id`](crate::ua::EUInformation::unit_id). use std::fmt; -crate::data_type::data_type_baseline!( +crate::data_type_baseline!( #[expect(clippy::derived_hash_with_manual_eq)] #[derive(Clone, Copy, Hash)] UnitId, From d96c494a423fc5dea0fb691947f0feda9220321a Mon Sep 17 00:00:00 2001 From: Carter Canedy Date: Tue, 3 Nov 2026 13:46:41 -0600 Subject: [PATCH 13/14] additional impl for `AccessRestrictionsType` --- src/ua/access_restriction_type.rs | 88 +++++++++---------------------- 1 file changed, 26 insertions(+), 62 deletions(-) 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, + } +); From 10c3edb33309b0735ce87cfd1aec4cc6688d25f7 Mon Sep 17 00:00:00 2001 From: Carter Canedy Date: Tue, 3 Nov 2026 14:07:44 -0600 Subject: [PATCH 14/14] add const bitwise ops for bitmask enum macro --- src/data_type.rs | 47 ++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 42 insertions(+), 5 deletions(-) diff --git a/src/data_type.rs b/src/data_type.rs index 7ca5dd58..2dd87226 100644 --- a/src/data_type.rs +++ b/src/data_type.rs @@ -747,20 +747,31 @@ macro_rules! enum_bitmask { } } + 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 } @@ -768,14 +779,40 @@ macro_rules! enum_bitmask { #[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; @@ -785,16 +822,16 @@ macro_rules! enum_bitmask { #[doc = $doc] )* pub const fn [<$variant:snake>](self) -> bool { - self.0 & Self::[<$variant:upper>].0 == 0 + self.contains(Self::[<$variant:upper>]) } #[must_use] pub const fn [](self, value: bool) -> Self { - Self(if value { - self.0 | Self::[<$variant:upper>].0 + if value { + self.or(Self::[<$variant:upper>]) } else { - self.0 & !Self::[<$variant:upper>].0 - }) + self.and(Self::not(Self::[<$variant:upper>])) + } } )+ }