From 5d5bafd00c5ce113fa6d439faf6a2ebd4b31ed04 Mon Sep 17 00:00:00 2001 From: Adam Lock Date: Tue, 29 Mar 2022 13:55:29 +0100 Subject: [PATCH] Add an integration test that calls a method on a server from a client. --- integration/src/harness.rs | 86 ++++++++++++-- integration/src/tests.rs | 229 +++++++++++++++++++++++-------------- 2 files changed, 218 insertions(+), 97 deletions(-) diff --git a/integration/src/harness.rs b/integration/src/harness.rs index 763e62485..a9979d820 100644 --- a/integration/src/harness.rs +++ b/integration/src/harness.rs @@ -15,12 +15,23 @@ use log::*; use opcua_client::prelude::*; use opcua_console_logging; use opcua_core::{self, runtime_components}; -use opcua_server::{self, builder::ServerBuilder, config::ServerEndpoint, prelude::*}; +use opcua_server::{ + self, builder::ServerBuilder, callbacks, config::ServerEndpoint, prelude::*, + session::SessionManager, +}; use crate::*; const TEST_TIMEOUT: i64 = 30000; +pub fn functions_object_id() -> NodeId { + NodeId::new(2, "Functions") +} + +pub fn hellox_method_id() -> NodeId { + NodeId::new(2, "HelloX") +} + static NEXT_PORT_OFFSET: AtomicUsize = AtomicUsize::new(0); pub fn next_port() -> u16 { @@ -46,9 +57,9 @@ fn port_from_offset(port_offset: u16) -> u16 { 4855u16 + port_offset } -pub fn endpoint_url(port: u16, path: &str) -> String { +pub fn endpoint_url(port: u16, path: &str) -> UAString { // To avoid certificate trouble, use the computer's own name for tne endpoint - format!("opc.tcp://{}:{}{}", hostname(), port, path) + format!("opc.tcp://{}:{}{}", hostname(), port, path).into() } fn v1_node_id() -> NodeId { @@ -103,7 +114,7 @@ pub fn new_server(port: u16) -> Server { let server = ServerBuilder::new() .application_name("integration_server") .application_uri("urn:integration_server") - .discovery_urls(vec![endpoint_url(port, endpoint_path)]) + .discovery_urls(vec![endpoint_url(port, endpoint_path).to_string()]) .create_sample_keypair(true) .pki_dir("./pki-server") .discovery_server_url(None) @@ -254,11 +265,72 @@ pub fn new_server(port: u16) -> Server { .organized_by(&folder_id) .insert(&mut address_space); }); + + let functions_object_id = functions_object_id(); + ObjectBuilder::new(&functions_object_id, "Functions", "Functions") + .event_notifier(EventNotifier::SUBSCRIBE_TO_EVENTS) + .organized_by(ObjectId::ObjectsFolder) + .insert(&mut address_space); + + MethodBuilder::new(&hellox_method_id(), "HelloX", "HelloX") + .component_of(functions_object_id) + .input_args( + &mut address_space, + &[("YourName", DataTypeId::String).into()], + ) + .output_args(&mut address_space, &[("Result", DataTypeId::String).into()]) + .callback(Box::new(HelloX)) + .insert(&mut address_space); } server } +struct HelloX; + +impl callbacks::Method for HelloX { + fn call( + &mut self, + _session_id: &NodeId, + _session_map: Arc>, + request: &CallMethodRequest, + ) -> Result { + debug!("HelloX method called"); + // Validate input to be a string + let mut out1 = Variant::Empty; + let in1_status = if let Some(ref input_arguments) = request.input_arguments { + if let Some(in1) = input_arguments.get(0) { + if let Variant::String(in1) = in1 { + out1 = Variant::from(format!("Hello {}!", &in1)); + StatusCode::Good + } else { + StatusCode::BadTypeMismatch + } + } else if input_arguments.len() == 0 { + return Err(StatusCode::BadArgumentsMissing); + } else { + // Shouldn't get here because there is 1 argument + return Err(StatusCode::BadTooManyArguments); + } + } else { + return Err(StatusCode::BadArgumentsMissing); + }; + + let status_code = if in1_status.is_good() { + StatusCode::Good + } else { + StatusCode::BadInvalidArgument + }; + + Ok(CallMethodResult { + status_code, + input_argument_results: Some(vec![in1_status]), + input_argument_diagnostic_infos: None, + output_arguments: Some(vec![out1]), + }) + } +} + fn new_client(_port: u16) -> Client { ClientBuilder::new() .application_name("integration_client") @@ -599,7 +671,7 @@ pub fn connect_with_get_endpoints(port: u16) { port, move |rx_client_command: mpsc::Receiver, client: Client| { get_endpoints_client_test( - &endpoint_url(port, "/"), + &endpoint_url(port, "/").as_ref(), IdentityToken::Anonymous, rx_client_command, client, @@ -613,8 +685,6 @@ pub fn connect_with_invalid_token( mut client_endpoint: EndpointDescription, identity_token: IdentityToken, ) { - client_endpoint.endpoint_url = - UAString::from(endpoint_url(port, client_endpoint.endpoint_url.as_ref())); connect_with_client_test( port, move |rx_client_command: mpsc::Receiver, client: Client| { @@ -628,8 +698,6 @@ pub fn connect_with( mut client_endpoint: EndpointDescription, identity_token: IdentityToken, ) { - client_endpoint.endpoint_url = - UAString::from(endpoint_url(port, client_endpoint.endpoint_url.as_ref())); connect_with_client_test( port, move |rx_client_command: mpsc::Receiver, client: Client| { diff --git a/integration/src/tests.rs b/integration/src/tests.rs index 4a5955edb..0429949a6 100644 --- a/integration/src/tests.rs +++ b/integration/src/tests.rs @@ -1,112 +1,120 @@ +use std::{ + sync::{mpsc, mpsc::channel, Arc, RwLock}, + thread, +}; + use chrono::Utc; use log::*; use opcua_client::prelude::*; use opcua_console_logging; use opcua_server::{self, prelude::*}; -use std::{ - sync::{mpsc, mpsc::channel, Arc, RwLock}, - thread, -}; use crate::harness::*; -fn endpoint_none() -> EndpointDescription { - ( - "/", - SecurityPolicy::None.to_str(), - MessageSecurityMode::None, - ) - .into() +fn endpoint( + port: u16, + path: &str, + security_policy: SecurityPolicy, + message_security_mode: MessageSecurityMode, +) -> EndpointDescription { + let mut endpoint = + EndpointDescription::from(("", SecurityPolicy::None.to_str(), MessageSecurityMode::None)); + endpoint.endpoint_url = endpoint_url(port, path); + endpoint +} + +fn endpoint_none(port: u16) -> EndpointDescription { + endpoint(port, "/", SecurityPolicy::None, MessageSecurityMode::None) } -fn endpoint_basic128rsa15_sign() -> EndpointDescription { - ( +fn endpoint_basic128rsa15_sign(port: u16) -> EndpointDescription { + endpoint( + port, "/", - SecurityPolicy::Basic128Rsa15.to_str(), + SecurityPolicy::Basic128Rsa15, MessageSecurityMode::Sign, ) - .into() } -fn endpoint_basic128rsa15_sign_encrypt() -> EndpointDescription { - ( +fn endpoint_basic128rsa15_sign_encrypt(port: u16) -> EndpointDescription { + endpoint( + port, "/", - SecurityPolicy::Basic128Rsa15.to_str(), + SecurityPolicy::Basic128Rsa15, MessageSecurityMode::SignAndEncrypt, ) - .into() } -fn endpoint_basic256_sign() -> EndpointDescription { - ( +fn endpoint_basic256_sign(port: u16) -> EndpointDescription { + endpoint( + port, "/", - SecurityPolicy::Basic256.to_str(), + SecurityPolicy::Basic256, MessageSecurityMode::Sign, ) - .into() } -fn endpoint_basic256_sign_encrypt() -> EndpointDescription { - ( +fn endpoint_basic256_sign_encrypt(port: u16) -> EndpointDescription { + endpoint( + port, "/", - SecurityPolicy::Basic256.to_str(), + SecurityPolicy::Basic256, MessageSecurityMode::SignAndEncrypt, ) - .into() } -fn endpoint_basic256sha256_sign() -> EndpointDescription { - ( +fn endpoint_basic256sha256_sign(port: u16) -> EndpointDescription { + endpoint( + port, "/", - SecurityPolicy::Basic256Sha256.to_str(), + SecurityPolicy::Basic256Sha256, MessageSecurityMode::Sign, ) - .into() } -fn endpoint_basic256sha256_sign_encrypt() -> EndpointDescription { - ( +fn endpoint_basic256sha256_sign_encrypt(port: u16) -> EndpointDescription { + endpoint( + port, "/", - SecurityPolicy::Basic256Sha256.to_str(), + SecurityPolicy::Basic256Sha256, MessageSecurityMode::SignAndEncrypt, ) - .into() } -fn endpoint_aes128sha256rsaoaep_sign() -> EndpointDescription { - ( +fn endpoint_aes128sha256rsaoaep_sign(port: u16) -> EndpointDescription { + endpoint( + port, "/", - SecurityPolicy::Aes128Sha256RsaOaep.to_str(), + SecurityPolicy::Aes128Sha256RsaOaep, MessageSecurityMode::Sign, ) - .into() } -fn endpoint_aes128sha256rsaoaep_sign_encrypt() -> EndpointDescription { - ( +fn endpoint_aes128sha256rsaoaep_sign_encrypt(port: u16) -> EndpointDescription { + endpoint( + port, "/", - SecurityPolicy::Aes128Sha256RsaOaep.to_str(), + SecurityPolicy::Aes128Sha256RsaOaep, MessageSecurityMode::SignAndEncrypt, ) - .into() } -fn endpoint_aes256sha256rsapss_sign() -> EndpointDescription { - ( +fn endpoint_aes256sha256rsapss_sign(port: u16) -> EndpointDescription { + endpoint( + port, "/", - SecurityPolicy::Aes256Sha256RsaPss.to_str(), + SecurityPolicy::Aes256Sha256RsaPss, MessageSecurityMode::Sign, ) - .into() } -fn endpoint_aes256sha256rsapss_sign_encrypt() -> EndpointDescription { - ( +fn endpoint_aes256sha256rsapss_sign_encrypt(port: u16) -> EndpointDescription { + endpoint( + port, "/", - SecurityPolicy::Aes256Sha256RsaPss.to_str(), + SecurityPolicy::Aes256Sha256RsaPss, MessageSecurityMode::SignAndEncrypt, ) - .into() } /// This is the most basic integration test starting the server on a thread, setting an abort flag @@ -231,7 +239,8 @@ fn get_endpoints() { #[ignore] fn connect_none() { // Connect a session using None security policy and anonymous token. - connect_with(next_port(), endpoint_none(), IdentityToken::Anonymous); + let port = next_port(); + connect_with(port, endpoint_none(port), IdentityToken::Anonymous); } /// Connect to the server using Basic128Rsa15 + Sign @@ -239,9 +248,10 @@ fn connect_none() { #[ignore] fn connect_basic128rsa15_sign() { // Connect a session with Basic128Rsa and Sign + let port = next_port(); connect_with( - next_port(), - endpoint_basic128rsa15_sign(), + port, + endpoint_basic128rsa15_sign(port), IdentityToken::Anonymous, ); } @@ -251,9 +261,10 @@ fn connect_basic128rsa15_sign() { #[ignore] fn connect_basic128rsa15_sign_and_encrypt() { // Connect a session with Basic128Rsa and SignAndEncrypt + let port = next_port(); connect_with( - next_port(), - endpoint_basic128rsa15_sign_encrypt(), + port, + endpoint_basic128rsa15_sign_encrypt(port), IdentityToken::Anonymous, ); } @@ -263,11 +274,8 @@ fn connect_basic128rsa15_sign_and_encrypt() { #[ignore] fn connect_basic256_sign() { // Connect a session with Basic256 and Sign - connect_with( - next_port(), - endpoint_basic256_sign(), - IdentityToken::Anonymous, - ); + let port = next_port(); + connect_with(port, endpoint_basic256_sign(port), IdentityToken::Anonymous); } /// Connect to the server using Basic256 + SignEncrypt @@ -275,9 +283,10 @@ fn connect_basic256_sign() { #[ignore] fn connect_basic256_sign_and_encrypt() { // Connect a session with Basic256 and SignAndEncrypt + let port = next_port(); connect_with( - next_port(), - endpoint_basic256_sign_encrypt(), + port, + endpoint_basic256_sign_encrypt(port), IdentityToken::Anonymous, ); } @@ -287,9 +296,10 @@ fn connect_basic256_sign_and_encrypt() { #[ignore] fn connect_basic256sha256_sign() { // Connect a session with Basic256Sha256 and Sign + let port = next_port(); connect_with( - next_port(), - endpoint_basic256sha256_sign(), + port, + endpoint_basic256sha256_sign(port), IdentityToken::Anonymous, ); } @@ -298,9 +308,10 @@ fn connect_basic256sha256_sign() { #[test] #[ignore] fn connect_basic256sha256_sign_and_encrypt() { + let port = next_port(); connect_with( - next_port(), - endpoint_basic256sha256_sign_encrypt(), + port, + endpoint_basic256sha256_sign_encrypt(port), IdentityToken::Anonymous, ); } @@ -309,9 +320,10 @@ fn connect_basic256sha256_sign_and_encrypt() { #[test] #[ignore] fn connect_aes128sha256rsaoaep_sign() { + let port = next_port(); connect_with( - next_port(), - endpoint_aes128sha256rsaoaep_sign(), + port, + endpoint_aes128sha256rsaoaep_sign(port), IdentityToken::Anonymous, ); } @@ -320,9 +332,10 @@ fn connect_aes128sha256rsaoaep_sign() { #[test] #[ignore] fn connect_aes128sha256rsaoaep_sign_encrypt() { + let port = next_port(); connect_with( - next_port(), - endpoint_aes128sha256rsaoaep_sign_encrypt(), + port, + endpoint_aes128sha256rsaoaep_sign_encrypt(port), IdentityToken::Anonymous, ); } @@ -331,9 +344,10 @@ fn connect_aes128sha256rsaoaep_sign_encrypt() { #[test] #[ignore] fn connect_aes256sha256rsapss_sign() { + let port = next_port(); connect_with( - next_port(), - endpoint_aes256sha256rsapss_sign(), + port, + endpoint_aes256sha256rsapss_sign(port), IdentityToken::Anonymous, ); } @@ -342,9 +356,10 @@ fn connect_aes256sha256rsapss_sign() { #[test] #[ignore] fn connect_aes256sha256rsapss_sign_encrypt() { + let port = next_port(); connect_with( - next_port(), - endpoint_aes256sha256rsapss_sign_encrypt(), + port, + endpoint_aes256sha256rsapss_sign_encrypt(port), IdentityToken::Anonymous, ); } @@ -354,9 +369,10 @@ fn connect_aes256sha256rsapss_sign_encrypt() { #[ignore] fn connect_basic128rsa15_with_username_password() { // Connect a session using username/password token + let port = next_port(); connect_with( - next_port(), - endpoint_basic128rsa15_sign_encrypt(), + port, + endpoint_basic128rsa15_sign_encrypt(port), client_user_token(), ); } @@ -365,9 +381,10 @@ fn connect_basic128rsa15_with_username_password() { #[test] #[ignore] fn connect_basic128rsa15_with_invalid_username_password() { + let port = next_port(); connect_with_invalid_token( - next_port(), - endpoint_basic128rsa15_sign_encrypt(), + port, + endpoint_basic128rsa15_sign_encrypt(port), client_invalid_user_token(), ); } @@ -376,9 +393,10 @@ fn connect_basic128rsa15_with_invalid_username_password() { #[test] #[ignore] fn connect_basic128rsa15_with_x509_token() { + let port = next_port(); connect_with( - next_port(), - endpoint_basic128rsa15_sign_encrypt(), + port, + endpoint_basic128rsa15_sign_encrypt(port), client_x509_token(), ); } @@ -387,12 +405,9 @@ fn connect_basic128rsa15_with_x509_token() { #[test] #[ignore] fn read_write_read() { - let mut client_endpoint = endpoint_basic128rsa15_sign_encrypt(); let port = next_port(); + let client_endpoint = endpoint_basic128rsa15_sign_encrypt(port); let identity_token = client_x509_token(); - - client_endpoint.endpoint_url = - UAString::from(endpoint_url(port, client_endpoint.endpoint_url.as_ref())); connect_with_client_test( port, move |_rx_client_command: mpsc::Receiver, mut client: Client| { @@ -452,12 +467,10 @@ fn read_write_read() { #[test] #[ignore] fn subscribe_1000() { - let mut client_endpoint = endpoint_basic128rsa15_sign_encrypt(); let port = next_port(); + let client_endpoint = endpoint_basic128rsa15_sign_encrypt(port); let identity_token = client_x509_token(); - client_endpoint.endpoint_url = - UAString::from(endpoint_url(port, client_endpoint.endpoint_url.as_ref())); connect_with_client_test( port, move |_rx_client_command: mpsc::Receiver, mut client: Client| { @@ -527,3 +540,43 @@ fn subscribe_1000() { }, ); } + +#[test] +#[ignore] +fn method_call() { + // Call a method on the server, one exercising some parameters in and out + let port = next_port(); + let client_endpoint = endpoint_none(port); + + connect_with_client_test( + port, + move |_rx_client_command: mpsc::Receiver, mut client: Client| { + info!( + "Client will try to connect to endpoint {:?}", + client_endpoint + ); + let session = client + .connect_to_endpoint(client_endpoint, IdentityToken::Anonymous) + .unwrap(); + let session = session.read().unwrap(); + + // Call the method + let input_arguments = Some(vec![Variant::from("Foo")]); + let method = CallMethodRequest { + object_id: functions_object_id(), + method_id: hellox_method_id(), + input_arguments, + }; + let result = session.call(method).unwrap(); + + // Result should say "Hello Foo" + assert!(result.status_code.is_good()); + let output_args = result.output_arguments.unwrap(); + assert_eq!(output_args.len(), 1); + let msg = output_args.get(0).unwrap(); + assert_eq!(msg.to_string(), "Hello Foo!"); + + session.disconnect(); + }, + ); +}