1use std::{str::FromStr, sync::Arc};
2
3use chrono::Duration;
4use tokio::{pin, select};
5use tracing::error;
6
7use crate::{
8 transport::{
9 tcp::TransportConfiguration, Connector, ConnectorBuilder, TcpConnector, TransportPollResult,
10 },
11 AsyncSecureChannel, ClientConfig, ClientEndpoint, IdentityToken,
12};
13use opcua_core::{
14 comms::url::{
15 hostname_from_url, server_url_from_endpoint_url, url_matches_except_host,
16 url_with_replaced_hostname,
17 },
18 config::Config,
19 sync::RwLock,
20 ResponseMessage,
21};
22use opcua_crypto::{CertificateStore, SecurityPolicy};
23use opcua_types::{
24 ApplicationDescription, ContextOwned, DecodingOptions, EndpointDescription, Error,
25 FindServersOnNetworkRequest, FindServersOnNetworkResponse, FindServersRequest,
26 GetEndpointsRequest, MessageSecurityMode, NamespaceMap, RegisterServerRequest,
27 RegisteredServer, StatusCode, UAString,
28};
29
30use super::{
31 connection::SessionBuilder, process_service_result, process_unexpected_response, EndpointInfo,
32 Session, SessionEventLoop,
33};
34
35pub struct Client {
38 pub(super) config: ClientConfig,
40 certificate_store: Arc<RwLock<CertificateStore>>,
42}
43
44impl Client {
45 pub fn new(config: ClientConfig) -> Self {
53 let application_description = if config.create_sample_keypair {
54 Some(config.application_description())
55 } else {
56 None
57 };
58
59 let (mut certificate_store, client_certificate, client_pkey) =
60 CertificateStore::new_with_x509_data(
61 &config.pki_dir,
62 false,
63 config.certificate_path.as_deref(),
64 config.private_key_path.as_deref(),
65 application_description,
66 );
67 if client_certificate.is_none() || client_pkey.is_none() {
68 error!("Client is missing its application instance certificate and/or its private key. Encrypted endpoints will not function correctly.")
69 }
70
71 certificate_store.set_skip_verify_certs(!config.verify_server_certs);
73
74 certificate_store.set_trust_unknown_certs(config.trust_server_certs);
76
77 Self {
81 config,
82 certificate_store: Arc::new(RwLock::new(certificate_store)),
83 }
84 }
85
86 pub fn session_builder(&self) -> SessionBuilder<'_> {
88 SessionBuilder::<'_>::new(&self.config)
89 }
90
91 pub async fn connect_to_endpoint_id(
101 &mut self,
102 endpoint_id: impl Into<String>,
103 ) -> Result<(Arc<Session>, SessionEventLoop<TcpConnector>), Error> {
104 self.session_builder()
105 .with_endpoints(self.get_server_endpoints().await?)
106 .connect_to_endpoint_id(endpoint_id)?
107 .build(self.certificate_store.clone())
108 }
109
110 pub async fn connect_to_matching_endpoint(
130 &mut self,
131 endpoint: impl Into<EndpointDescription>,
132 user_identity_token: IdentityToken,
133 ) -> Result<(Arc<Session>, SessionEventLoop<TcpConnector>), Error> {
134 let endpoint = endpoint.into();
135
136 let server_url = endpoint.endpoint_url.as_ref();
138
139 self.session_builder()
140 .with_endpoints(self.get_server_endpoints_from_url(server_url).await?)
141 .connect_to_matching_endpoint(endpoint)?
142 .user_identity_token(user_identity_token)
143 .build(self.certificate_store.clone())
144 }
145
146 pub fn connect_to_endpoint_directly(
165 &mut self,
166 endpoint: impl Into<EndpointDescription>,
167 identity_token: IdentityToken,
168 ) -> Result<(Arc<Session>, SessionEventLoop<TcpConnector>), Error> {
169 self.session_builder()
170 .connect_to_endpoint_directly(endpoint)?
171 .user_identity_token(identity_token)
172 .build(self.certificate_store.clone())
173 }
174
175 pub async fn connect_to_default_endpoint(
194 &mut self,
195 ) -> Result<(Arc<Session>, SessionEventLoop<TcpConnector>), Error> {
196 self.session_builder()
197 .with_endpoints(self.get_server_endpoints().await?)
198 .connect_to_default_endpoint()?
199 .build(self.certificate_store.clone())
200 }
201
202 fn channel_from_endpoint_info(
207 &self,
208 endpoint_info: EndpointInfo,
209 channel_lifetime: u32,
210 ) -> AsyncSecureChannel {
211 AsyncSecureChannel::new(
212 self.certificate_store.clone(),
213 endpoint_info,
214 self.config.session_retry_policy(),
215 self.config.performance.ignore_clock_skew,
216 Arc::default(),
217 TransportConfiguration {
218 send_buffer_size: self.config.decoding_options.max_chunk_size,
219 recv_buffer_size: self.config.decoding_options.max_incoming_chunk_size,
220 max_message_size: self.config.decoding_options.max_message_size,
221 max_chunk_count: self.config.decoding_options.max_chunk_count,
222 },
223 channel_lifetime,
224 Arc::new(RwLock::new(ContextOwned::new_default(
226 NamespaceMap::new(),
227 self.decoding_options(),
228 ))),
229 )
230 }
231
232 pub fn default_endpoint(&self) -> Result<ClientEndpoint, String> {
240 let default_endpoint_id = self.config.default_endpoint.clone();
241 if default_endpoint_id.is_empty() {
242 Err("No default endpoint has been specified".to_string())
243 } else if let Some(endpoint) = self.config.endpoints.get(&default_endpoint_id) {
244 Ok(endpoint.clone())
245 } else {
246 Err(format!(
247 "Cannot find default endpoint with id {default_endpoint_id}"
248 ))
249 }
250 }
251
252 pub async fn get_server_endpoints(&self) -> Result<Vec<EndpointDescription>, Error> {
259 let default_endpoint = self
260 .default_endpoint()
261 .map_err(|e| Error::new(StatusCode::BadConfigurationError, e))?;
262 if let Ok(server_url) = server_url_from_endpoint_url(&default_endpoint.url) {
263 self.get_server_endpoints_from_url(server_url).await
264 } else {
265 error!(
266 "Cannot create a server url from the specified endpoint url {}",
267 default_endpoint.url
268 );
269 Err(Error::new(
270 StatusCode::BadUnexpectedError,
271 format!(
272 "Cannot create a server url from the specified endpoint url {}",
273 default_endpoint.url
274 ),
275 ))
276 }
277 }
278
279 fn decoding_options(&self) -> DecodingOptions {
280 let decoding_options = &self.config.decoding_options;
281 DecodingOptions {
282 max_chunk_count: decoding_options.max_chunk_count,
283 max_message_size: decoding_options.max_message_size,
284 max_string_length: decoding_options.max_string_length,
285 max_byte_string_length: decoding_options.max_byte_string_length,
286 max_array_length: decoding_options.max_array_length,
287 client_offset: Duration::zero(),
288 ..Default::default()
289 }
290 }
291
292 async fn get_server_endpoints_inner(
293 &self,
294 endpoint: &EndpointDescription,
295 channel: &AsyncSecureChannel,
296 locale_ids: Option<Vec<UAString>>,
297 profile_uris: Option<Vec<UAString>>,
298 ) -> Result<Vec<EndpointDescription>, Error> {
299 let request = GetEndpointsRequest {
300 request_header: channel.make_request_header(self.config.request_timeout),
301 endpoint_url: endpoint.endpoint_url.clone(),
302 locale_ids,
303 profile_uris,
304 };
305 let response = channel.send(request, self.config.request_timeout).await?;
307 if let ResponseMessage::GetEndpoints(response) = response {
308 process_service_result(&response.response_header)?;
309 match response.endpoints {
310 None => Ok(Vec::new()),
311 Some(endpoints) => Ok(endpoints),
312 }
313 } else {
314 Err(process_unexpected_response(response))
315 }
316 }
317
318 pub async fn get_server_endpoints_from_url(
330 &self,
331 server: impl ConnectorBuilder,
332 ) -> Result<Vec<EndpointDescription>, Error> {
333 self.get_endpoints(server, &[], &[]).await
334 }
335
336 pub async fn get_endpoints(
350 &self,
351 server: impl ConnectorBuilder,
352 locale_ids: &[&str],
353 profile_uris: &[&str],
354 ) -> Result<Vec<EndpointDescription>, Error> {
355 let server = server.build()?;
356 let preferred_locales = Vec::new();
357 let endpoint = server.default_endpoint();
359 let endpoint_info = EndpointInfo {
360 endpoint: endpoint.clone(),
361 user_identity_token: IdentityToken::Anonymous,
362 preferred_locales,
363 };
364 let channel = self.channel_from_endpoint_info(endpoint_info, self.config.channel_lifetime);
365
366 let mut evt_loop = channel.connect(&server).await?;
367
368 let send_fut = self.get_server_endpoints_inner(
369 &endpoint,
370 &channel,
371 if locale_ids.is_empty() {
372 None
373 } else {
374 Some(locale_ids.iter().map(|i| (*i).into()).collect())
375 },
376 if profile_uris.is_empty() {
377 None
378 } else {
379 Some(profile_uris.iter().map(|i| (*i).into()).collect())
380 },
381 );
382 pin!(send_fut);
383
384 let res = loop {
385 select! {
386 r = evt_loop.poll() => {
387 if let TransportPollResult::Closed(e) = r {
388 return Err(Error::new(e, "Transport closed unexpectedly"));
389 }
390 },
391 res = &mut send_fut => break res,
392 }
393 };
394
395 channel.close_channel().await;
396
397 loop {
398 if matches!(evt_loop.poll().await, TransportPollResult::Closed(_)) {
399 break;
400 }
401 }
402
403 res
404 }
405
406 async fn find_servers_inner(
407 &self,
408 endpoint_url: String,
409 channel: &AsyncSecureChannel,
410 locale_ids: Option<Vec<UAString>>,
411 server_uris: Option<Vec<UAString>>,
412 ) -> Result<Vec<ApplicationDescription>, Error> {
413 let request = FindServersRequest {
414 request_header: channel.make_request_header(self.config.request_timeout),
415 endpoint_url: endpoint_url.into(),
416 locale_ids,
417 server_uris,
418 };
419
420 let response = channel.send(request, self.config.request_timeout).await?;
421 if let ResponseMessage::FindServers(response) = response {
422 process_service_result(&response.response_header)?;
423 Ok(response.servers.unwrap_or_default())
424 } else {
425 Err(process_unexpected_response(response))
426 }
427 }
428
429 pub async fn find_servers(
443 &self,
444 discovery_endpoint: impl ConnectorBuilder,
445 locale_ids: Option<Vec<UAString>>,
446 server_uris: Option<Vec<UAString>>,
447 ) -> Result<Vec<ApplicationDescription>, Error> {
448 let discovery_endpoint = discovery_endpoint.build()?;
449 let endpoint = discovery_endpoint.default_endpoint();
450 let session_info = EndpointInfo {
451 endpoint: endpoint.clone(),
452 user_identity_token: IdentityToken::Anonymous,
453 preferred_locales: Vec::new(),
454 };
455 let channel = self.channel_from_endpoint_info(session_info, self.config.channel_lifetime);
456
457 let mut evt_loop = channel.connect(&discovery_endpoint).await?;
458
459 let send_fut = self.find_servers_inner(
460 evt_loop.connected_url().to_owned(),
461 &channel,
462 locale_ids,
463 server_uris,
464 );
465 pin!(send_fut);
466
467 let res = loop {
468 select! {
469 r = evt_loop.poll() => {
470 if let TransportPollResult::Closed(e) = r {
471 return Err(Error::new(e, "Connection closed unexpectedly"));
472 }
473 },
474 res = &mut send_fut => break res
475 }
476 };
477
478 channel.close_channel().await;
479
480 loop {
481 if matches!(evt_loop.poll().await, TransportPollResult::Closed(_)) {
482 break;
483 }
484 }
485
486 res
487 }
488
489 async fn find_servers_on_network_inner(
490 &self,
491 starting_record_id: u32,
492 max_records_to_return: u32,
493 server_capability_filter: Option<Vec<UAString>>,
494 channel: &AsyncSecureChannel,
495 ) -> Result<FindServersOnNetworkResponse, Error> {
496 let request = FindServersOnNetworkRequest {
497 request_header: channel.make_request_header(self.config.request_timeout),
498 starting_record_id,
499 max_records_to_return,
500 server_capability_filter,
501 };
502
503 let response = channel.send(request, self.config.request_timeout).await?;
504 if let ResponseMessage::FindServersOnNetwork(response) = response {
505 process_service_result(&response.response_header)?;
506 Ok(*response)
507 } else {
508 Err(process_unexpected_response(response))
509 }
510 }
511
512 pub async fn find_servers_on_network(
531 &self,
532 discovery_endpoint: impl ConnectorBuilder,
533 starting_record_id: u32,
534 max_records_to_return: u32,
535 server_capability_filter: Option<Vec<UAString>>,
536 ) -> Result<FindServersOnNetworkResponse, Error> {
537 let discovery_endpoint = discovery_endpoint.build()?;
538 let endpoint = discovery_endpoint.default_endpoint();
539 let session_info = EndpointInfo {
540 endpoint: endpoint.clone(),
541 user_identity_token: IdentityToken::Anonymous,
542 preferred_locales: Vec::new(),
543 };
544 let channel = self.channel_from_endpoint_info(session_info, self.config.channel_lifetime);
545
546 let mut evt_loop = channel.connect(&discovery_endpoint).await?;
547
548 let send_fut = self.find_servers_on_network_inner(
549 starting_record_id,
550 max_records_to_return,
551 server_capability_filter,
552 &channel,
553 );
554 pin!(send_fut);
555
556 let res = loop {
557 select! {
558 r = evt_loop.poll() => {
559 if let TransportPollResult::Closed(e) = r {
560 return Err(Error::new(e, "Connection closed unexpectedly"));
561 }
562 },
563 res = &mut send_fut => break res
564 }
565 };
566
567 channel.close_channel().await;
568
569 loop {
570 if matches!(evt_loop.poll().await, TransportPollResult::Closed(_)) {
571 break;
572 }
573 }
574
575 res
576 }
577
578 pub fn find_matching_endpoint(
592 endpoints: &[EndpointDescription],
593 endpoint_url: &str,
594 security_policy: SecurityPolicy,
595 security_mode: MessageSecurityMode,
596 ) -> Option<EndpointDescription> {
597 if security_policy == SecurityPolicy::Unknown {
598 return None;
599 }
600
601 let mut matching_endpoint = endpoints
602 .iter()
603 .find(|e| {
604 security_mode == e.security_mode
606 && security_policy == SecurityPolicy::from_uri(e.security_policy_uri.as_ref())
607 && url_matches_except_host(endpoint_url, e.endpoint_url.as_ref())
608 })
609 .cloned()?;
610
611 let hostname = hostname_from_url(endpoint_url).ok()?;
612 let new_endpoint_url =
613 url_with_replaced_hostname(matching_endpoint.endpoint_url.as_ref(), &hostname).ok()?;
614
615 matching_endpoint.endpoint_url = new_endpoint_url.into();
618 Some(matching_endpoint)
619 }
620
621 pub fn is_supported_endpoint(&self, endpoint: &EndpointDescription) -> bool {
631 if let Ok(security_policy) = SecurityPolicy::from_str(endpoint.security_policy_uri.as_ref())
632 {
633 !matches!(security_policy, SecurityPolicy::Unknown)
634 } else {
635 false
636 }
637 }
638
639 async fn register_server_inner(
640 &self,
641 server: RegisteredServer,
642 channel: &AsyncSecureChannel,
643 ) -> Result<(), Error> {
644 let request = RegisterServerRequest {
645 request_header: channel.make_request_header(self.config.request_timeout),
646 server,
647 };
648 let response = channel.send(request, self.config.request_timeout).await?;
649 if let ResponseMessage::RegisterServer(response) = response {
650 process_service_result(&response.response_header)?;
651 Ok(())
652 } else {
653 Err(process_unexpected_response(response))
654 }
655 }
656
657 pub async fn get_best_endpoint(
663 &self,
664 discovery_endpoint: impl ConnectorBuilder,
665 ) -> Result<EndpointDescription, Error> {
666 let discovery_endpoint = discovery_endpoint.build()?;
667 let endpoints = self
668 .get_server_endpoints_from_url(
669 discovery_endpoint.default_endpoint().endpoint_url.as_ref(),
670 )
671 .await?;
672 if endpoints.is_empty() {
673 return Err(Error::new(
674 StatusCode::BadUnexpectedError,
675 "No endpoints returned from server",
676 ));
677 }
678
679 let Some(endpoint) = endpoints
680 .into_iter()
681 .filter(|e| self.is_supported_endpoint(e))
682 .max_by(|a, b| a.security_level.cmp(&b.security_level))
683 else {
684 error!("Cannot find an endpoint that we can use");
685 return Err(Error::new(
686 StatusCode::BadUnexpectedError,
687 "No supported endpoints returned from server",
688 ));
689 };
690
691 Ok(endpoint)
692 }
693
694 pub async fn register_server(
715 &self,
716 connector: impl ConnectorBuilder,
717 server_endpoint: &EndpointDescription,
718 server: RegisteredServer,
719 ) -> Result<(), Error> {
720 let endpoint_info = EndpointInfo {
721 endpoint: server_endpoint.clone(),
722 user_identity_token: IdentityToken::Anonymous,
723 preferred_locales: Vec::new(),
724 };
725 let connector = connector.build()?;
726 let channel = self.channel_from_endpoint_info(endpoint_info, self.config.channel_lifetime);
727
728 let mut evt_loop = channel.connect(&connector).await?;
729
730 let send_fut = self.register_server_inner(server, &channel);
731 pin!(send_fut);
732
733 let res = loop {
734 select! {
735 r = evt_loop.poll() => {
736 if let TransportPollResult::Closed(e) = r {
737 return Err(Error::new(e, "Connection closed unexpectedly"));
738 }
739 },
740 res = &mut send_fut => break res
741 }
742 };
743
744 channel.close_channel().await;
745
746 loop {
747 if matches!(evt_loop.poll().await, TransportPollResult::Closed(_)) {
748 break;
749 }
750 }
751
752 res
753 }
754
755 pub fn certificate_store(&self) -> &Arc<RwLock<CertificateStore>> {
757 &self.certificate_store
758 }
759}