diff --git a/docs/compatibility.md b/docs/compatibility.md index b93e0e3b9..a825dafb8 100644 --- a/docs/compatibility.md +++ b/docs/compatibility.md @@ -14,9 +14,9 @@ The server shall implement the OPC UA capabilities: * http://opcfoundation.org/UA-Profile/Server/Behaviour - base server profile * http://opcfoundation.org/UA-Profile/Server/EmbeddedUA - embedded UA profile -### Server services +### Services -The following services are supported: +The following services are supported in the server: * Discovery service set * GetEndpoints diff --git a/server/src/services/view.rs b/server/src/services/view.rs index 990ccac86..a79597930 100644 --- a/server/src/services/view.rs +++ b/server/src/services/view.rs @@ -1,3 +1,6 @@ +use std::result::Result; +use std::sync::{Arc, Mutex, RwLock}; + use opcua_core::supported_message::SupportedMessage; use opcua_crypto::random; use opcua_types::{ @@ -5,8 +8,6 @@ use opcua_types::{ node_ids::ReferenceTypeId, status_code::StatusCode, }; -use std::result::Result; -use std::sync::{Arc, Mutex, RwLock}; use crate::{ address_space::{AddressSpace, relative_path}, @@ -35,7 +36,8 @@ impl ViewService { let mut session = trace_write_lock_unwrap!(session); let address_space = trace_read_lock_unwrap!(address_space); - if !request.view.view_id.is_null() { + let view = &request.view; + if !view.view_id.is_null() || !view.timestamp.is_null() { // Views are not supported info!("Browse request ignored because view was specified (views not supported)"); self.service_fault(&request.request_header, StatusCode::BadViewIdUnknown) diff --git a/server/src/tests/services/view.rs b/server/src/tests/services/view.rs index 71a65bea2..2611304ec 100644 --- a/server/src/tests/services/view.rs +++ b/server/src/tests/services/view.rs @@ -26,7 +26,7 @@ fn make_browse_request(nodes: &[NodeId], node_class_mask: NodeClassMask, max_ request_header, view: ViewDescription { view_id: NodeId::null(), - timestamp: DateTime::now(), + timestamp: DateTime::null(), view_version: 0, }, requested_max_references_per_node: max_references_per_node as u32, @@ -101,6 +101,28 @@ fn browse() { }); } +// Test the response of supplying an unsupported view to the browse request +#[test] +fn browse_non_null_view() { + do_view_service_test(|_server_state, session, address_space, vs| { + let nodes: Vec = vec![ObjectId::RootFolder.into()]; + + // Expect a non-null view to be rejected + let mut request = make_browse_request(&nodes, NodeClassMask::empty(), 1000, BrowseDirection::Forward, ReferenceTypeId::Organizes); + request.view.view_id = NodeId::new(1, "FakeView"); + let response = vs.browse(session.clone(), address_space.clone(), &request); + let response = supported_message_as!(response, ServiceFault); + assert_eq!(response.response_header.service_result, StatusCode::BadViewIdUnknown); + + // Expect a non-0 timestamp to be rejected + request.view.view_id = NodeId::null(); + request.view.timestamp = DateTime::now(); + let response = vs.browse(session, address_space, &request); + let response = supported_message_as!(response, ServiceFault); + assert_eq!(response.response_header.service_result, StatusCode::BadViewIdUnknown); + }); +} + // This test applies a class mask to the browse so only nodes of types in the mask should come back #[test] fn browse_node_class_mask() { diff --git a/types/src/date_time.rs b/types/src/date_time.rs index 0af21c217..a4d9ec2fc 100644 --- a/types/src/date_time.rs +++ b/types/src/date_time.rs @@ -167,11 +167,17 @@ impl DateTime { DateTime::from(Utc::now()) } + /// Creates a null date time (i.e. the epoch) pub fn null() -> DateTime { // The epoch is 0, so effectively null DateTime::epoch() } + /// Tests if the date time is null (i.e. equal to epoch) + pub fn is_null(&self) -> bool { + self.ticks() == 0i64 + } + /// Constructs a date time for the epoch pub fn epoch() -> DateTime { DateTime::from(Self::epoch_chrono())