opcua_server/
config.rs

1// OPCUA for Rust
2// SPDX-License-Identifier: MPL-2.0
3// Copyright (C) 2017-2022 Adam Lock
4
5//! Provides configuration settings for the server including serialization and deserialization from file.
6use std::collections::{BTreeMap, BTreeSet};
7use std::path::{Path, PathBuf};
8use std::str::FromStr;
9
10use opcua_core::{comms::url::url_matches_except_host, config::Config};
11use opcua_crypto::{CertificateStore, SecurityPolicy, Thumbprint};
12use opcua_types::{
13    constants as opcua_types_constants, service_types::ApplicationType, DecodingOptions,
14    MessageSecurityMode, UAString,
15};
16
17use crate::constants;
18
19pub const ANONYMOUS_USER_TOKEN_ID: &str = "ANONYMOUS";
20
21#[derive(Debug, PartialEq, Serialize, Deserialize, Clone)]
22pub struct TcpConfig {
23    /// Timeout for hello on a session in seconds
24    pub hello_timeout: u32,
25    /// The hostname to supply in the endpoints
26    pub host: String,
27    /// The port number of the service
28    pub port: u16,
29}
30
31#[derive(Debug, PartialEq, Serialize, Deserialize, Clone)]
32pub struct ServerUserToken {
33    /// User name
34    pub user: String,
35    /// Password
36    #[serde(skip_serializing_if = "Option::is_none")]
37    pub pass: Option<String>,
38    // X509 file path (as a string)
39    #[serde(skip_serializing_if = "Option::is_none")]
40    pub x509: Option<String>,
41    #[serde(skip)]
42    pub thumbprint: Option<Thumbprint>,
43}
44
45impl ServerUserToken {
46    /// Create a user pass token
47    pub fn user_pass<T>(user: T, pass: T) -> Self
48    where
49        T: Into<String>,
50    {
51        ServerUserToken {
52            user: user.into(),
53            pass: Some(pass.into()),
54            x509: None,
55            thumbprint: None,
56        }
57    }
58
59    /// Create an X509 token.
60    pub fn x509<T>(user: T, cert_path: &Path) -> Self
61    where
62        T: Into<String>,
63    {
64        ServerUserToken {
65            user: user.into(),
66            pass: None,
67            x509: Some(cert_path.to_string_lossy().to_string()),
68            thumbprint: None,
69        }
70    }
71
72    /// Read an X509 user token's certificate from disk and then hold onto the thumbprint for it.
73    pub fn read_thumbprint(&mut self) {
74        if self.is_x509() && self.thumbprint.is_none() {
75            // As part of validation, we're going to try and load the x509 certificate from disk, and
76            // obtain its thumbprint. This will be used when a session is activated.
77            if let Some(ref x509_path) = self.x509 {
78                let path = PathBuf::from(x509_path);
79                if let Ok(x509) = CertificateStore::read_cert(&path) {
80                    self.thumbprint = Some(x509.thumbprint());
81                }
82            }
83        }
84    }
85
86    /// Test if the token is valid. This does not care for x509 tokens if the cert is present on
87    /// the disk or not.
88    pub fn is_valid(&self, id: &str) -> bool {
89        let mut valid = true;
90        if id == ANONYMOUS_USER_TOKEN_ID {
91            error!(
92                "User token {} is invalid because id is a reserved value, use another value.",
93                id
94            );
95            valid = false;
96        }
97        if self.user.is_empty() {
98            error!("User token {} has an empty user name.", id);
99            valid = false;
100        }
101        if self.pass.is_some() && self.x509.is_some() {
102            error!(
103                "User token {} holds a password and certificate info - it cannot be both.",
104                id
105            );
106            valid = false;
107        } else if self.pass.is_none() && self.x509.is_none() {
108            error!(
109                "User token {} fails to provide a password or certificate info.",
110                id
111            );
112            valid = false;
113        }
114        valid
115    }
116
117    pub fn is_user_pass(&self) -> bool {
118        self.x509.is_none()
119    }
120
121    pub fn is_x509(&self) -> bool {
122        self.x509.is_some()
123    }
124}
125
126#[derive(Debug, PartialEq, Serialize, Deserialize, Clone)]
127pub struct Limits {
128    /// Indicates if clients are able to modify the address space through the node management service
129    /// set. This is a very broad flag and is likely to require more fine grained per user control
130    /// in a later revision. By default, this value is `false`
131    pub clients_can_modify_address_space: bool,
132    /// Maximum number of subscriptions in a session, 0 for no limit
133    pub max_subscriptions: u32,
134    /// Maximum number of monitored items per subscription, 0 for no limit
135    pub max_monitored_items_per_sub: u32,
136    /// Maximum number of values in a monitored item queue
137    pub max_monitored_item_queue_size: u32,
138    /// Max array length in elements
139    pub max_array_length: u32,
140    /// Max string length in characters
141    pub max_string_length: u32,
142    /// Max bytestring length in bytes
143    pub max_byte_string_length: u32,
144    /// Specifies the minimum sampling interval for this server in seconds.
145    pub min_sampling_interval: f64,
146    /// Specifies the minimum publishing interval for this server in seconds.
147    pub min_publishing_interval: f64,
148}
149
150impl Default for Limits {
151    fn default() -> Self {
152        Self {
153            max_array_length: opcua_types_constants::MAX_ARRAY_LENGTH as u32,
154            max_string_length: opcua_types_constants::MAX_STRING_LENGTH as u32,
155            max_byte_string_length: opcua_types_constants::MAX_BYTE_STRING_LENGTH as u32,
156            max_subscriptions: constants::DEFAULT_MAX_SUBSCRIPTIONS,
157            max_monitored_items_per_sub: constants::DEFAULT_MAX_MONITORED_ITEMS_PER_SUB,
158            max_monitored_item_queue_size: constants::MAX_DATA_CHANGE_QUEUE_SIZE as u32,
159            clients_can_modify_address_space: false,
160            min_sampling_interval: constants::MIN_SAMPLING_INTERVAL,
161            min_publishing_interval: constants::MIN_PUBLISHING_INTERVAL,
162        }
163    }
164}
165
166#[derive(Debug, PartialEq, Serialize, Deserialize, Clone)]
167pub struct CertificateValidation {
168    /// Auto trusts client certificates. For testing/samples only unless you're sure what you're
169    /// doing.
170    pub trust_client_certs: bool,
171    /// Check the valid from/to fields of a certificate
172    pub check_time: bool,
173}
174
175#[derive(Debug, PartialEq, Serialize, Deserialize, Clone)]
176pub struct ServerEndpoint {
177    /// Endpoint path
178    pub path: String,
179    /// Security policy
180    pub security_policy: String,
181    /// Security mode
182    pub security_mode: String,
183    /// Security level, higher being more secure
184    pub security_level: u8,
185    /// Password security policy when a client supplies a user name identity token
186    pub password_security_policy: Option<String>,
187    /// User tokens
188    pub user_token_ids: BTreeSet<String>,
189}
190
191/// Convenience method to make an endpoint from a tuple
192impl<'a> From<(&'a str, SecurityPolicy, MessageSecurityMode, &'a [&'a str])> for ServerEndpoint {
193    fn from(v: (&'a str, SecurityPolicy, MessageSecurityMode, &'a [&'a str])) -> ServerEndpoint {
194        ServerEndpoint {
195            path: v.0.into(),
196            security_policy: v.1.to_string(),
197            security_mode: v.2.to_string(),
198            security_level: Self::security_level(v.1, v.2),
199            password_security_policy: None,
200            user_token_ids: v.3.iter().map(|id| id.to_string()).collect(),
201        }
202    }
203}
204
205impl ServerEndpoint {
206    pub fn new<T>(
207        path: T,
208        security_policy: SecurityPolicy,
209        security_mode: MessageSecurityMode,
210        user_token_ids: &[String],
211    ) -> Self
212    where
213        T: Into<String>,
214    {
215        ServerEndpoint {
216            path: path.into(),
217            security_policy: security_policy.to_string(),
218            security_mode: security_mode.to_string(),
219            security_level: Self::security_level(security_policy, security_mode),
220            password_security_policy: None,
221            user_token_ids: user_token_ids.iter().cloned().collect(),
222        }
223    }
224
225    /// Recommends a security level for the supplied security policy
226    fn security_level(security_policy: SecurityPolicy, security_mode: MessageSecurityMode) -> u8 {
227        let security_level = match security_policy {
228            SecurityPolicy::Basic128Rsa15 => 1,
229            SecurityPolicy::Aes128Sha256RsaOaep => 2,
230            SecurityPolicy::Basic256 => 3,
231            SecurityPolicy::Basic256Sha256 => 4,
232            SecurityPolicy::Aes256Sha256RsaPss => 5,
233            _ => 0,
234        };
235        if security_mode == MessageSecurityMode::SignAndEncrypt {
236            security_level + 10
237        } else {
238            security_level
239        }
240    }
241
242    pub fn new_none<T>(path: T, user_token_ids: &[String]) -> Self
243    where
244        T: Into<String>,
245    {
246        Self::new(
247            path,
248            SecurityPolicy::None,
249            MessageSecurityMode::None,
250            user_token_ids,
251        )
252    }
253
254    pub fn new_basic128rsa15_sign<T>(path: T, user_token_ids: &[String]) -> Self
255    where
256        T: Into<String>,
257    {
258        Self::new(
259            path,
260            SecurityPolicy::Basic128Rsa15,
261            MessageSecurityMode::Sign,
262            user_token_ids,
263        )
264    }
265
266    pub fn new_basic128rsa15_sign_encrypt<T>(path: T, user_token_ids: &[String]) -> Self
267    where
268        T: Into<String>,
269    {
270        Self::new(
271            path,
272            SecurityPolicy::Basic128Rsa15,
273            MessageSecurityMode::SignAndEncrypt,
274            user_token_ids,
275        )
276    }
277
278    pub fn new_basic256_sign<T>(path: T, user_token_ids: &[String]) -> Self
279    where
280        T: Into<String>,
281    {
282        Self::new(
283            path,
284            SecurityPolicy::Basic256,
285            MessageSecurityMode::Sign,
286            user_token_ids,
287        )
288    }
289
290    pub fn new_basic256_sign_encrypt<T>(path: T, user_token_ids: &[String]) -> Self
291    where
292        T: Into<String>,
293    {
294        Self::new(
295            path,
296            SecurityPolicy::Basic256,
297            MessageSecurityMode::SignAndEncrypt,
298            user_token_ids,
299        )
300    }
301
302    pub fn new_basic256sha256_sign<T>(path: T, user_token_ids: &[String]) -> Self
303    where
304        T: Into<String>,
305    {
306        Self::new(
307            path,
308            SecurityPolicy::Basic256Sha256,
309            MessageSecurityMode::Sign,
310            user_token_ids,
311        )
312    }
313
314    pub fn new_basic256sha256_sign_encrypt<T>(path: T, user_token_ids: &[String]) -> Self
315    where
316        T: Into<String>,
317    {
318        Self::new(
319            path,
320            SecurityPolicy::Basic256Sha256,
321            MessageSecurityMode::SignAndEncrypt,
322            user_token_ids,
323        )
324    }
325
326    pub fn new_aes128_sha256_rsaoaep_sign<T>(path: T, user_token_ids: &[String]) -> Self
327    where
328        T: Into<String>,
329    {
330        Self::new(
331            path,
332            SecurityPolicy::Aes128Sha256RsaOaep,
333            MessageSecurityMode::Sign,
334            user_token_ids,
335        )
336    }
337
338    pub fn new_aes128_sha256_rsaoaep_sign_encrypt<T>(path: T, user_token_ids: &[String]) -> Self
339    where
340        T: Into<String>,
341    {
342        Self::new(
343            path,
344            SecurityPolicy::Aes128Sha256RsaOaep,
345            MessageSecurityMode::SignAndEncrypt,
346            user_token_ids,
347        )
348    }
349
350    pub fn new_aes256_sha256_rsapss_sign<T>(path: T, user_token_ids: &[String]) -> Self
351    where
352        T: Into<String>,
353    {
354        Self::new(
355            path,
356            SecurityPolicy::Aes256Sha256RsaPss,
357            MessageSecurityMode::Sign,
358            user_token_ids,
359        )
360    }
361
362    pub fn new_aes256_sha256_rsapss_sign_encrypt<T>(path: T, user_token_ids: &[String]) -> Self
363    where
364        T: Into<String>,
365    {
366        Self::new(
367            path,
368            SecurityPolicy::Aes256Sha256RsaPss,
369            MessageSecurityMode::SignAndEncrypt,
370            user_token_ids,
371        )
372    }
373
374    pub fn is_valid(&self, id: &str, user_tokens: &BTreeMap<String, ServerUserToken>) -> bool {
375        let mut valid = true;
376
377        // Validate that the user token ids exist
378        for id in &self.user_token_ids {
379            // Skip anonymous
380            if id == ANONYMOUS_USER_TOKEN_ID {
381                continue;
382            }
383            if !user_tokens.contains_key(id) {
384                error!("Cannot find user token with id {}", id);
385                valid = false;
386            }
387        }
388
389        if let Some(ref password_security_policy) = self.password_security_policy {
390            let password_security_policy =
391                SecurityPolicy::from_str(password_security_policy).unwrap();
392            if password_security_policy == SecurityPolicy::Unknown {
393                error!("Endpoint {} is invalid. Password security policy \"{}\" is invalid. Valid values are None, Basic128Rsa15, Basic256, Basic256Sha256", id, password_security_policy);
394                valid = false;
395            }
396        }
397
398        // Validate the security policy and mode
399        let security_policy = SecurityPolicy::from_str(&self.security_policy).unwrap();
400        let security_mode = MessageSecurityMode::from(self.security_mode.as_ref());
401        if security_policy == SecurityPolicy::Unknown {
402            error!("Endpoint {} is invalid. Security policy \"{}\" is invalid. Valid values are None, Basic128Rsa15, Basic256, Basic256Sha256, Aes128Sha256RsaOaep, Aes256Sha256RsaPss,", id, self.security_policy);
403            valid = false;
404        } else if security_mode == MessageSecurityMode::Invalid {
405            error!("Endpoint {} is invalid. Security mode \"{}\" is invalid. Valid values are None, Sign, SignAndEncrypt", id, self.security_mode);
406            valid = false;
407        } else if (security_policy == SecurityPolicy::None
408            && security_mode != MessageSecurityMode::None)
409            || (security_policy != SecurityPolicy::None
410                && security_mode == MessageSecurityMode::None)
411        {
412            error!("Endpoint {} is invalid. Security policy and security mode must both contain None or neither of them should (1).", id);
413            valid = false;
414        } else if security_policy != SecurityPolicy::None
415            && security_mode == MessageSecurityMode::None
416        {
417            error!("Endpoint {} is invalid. Security policy and security mode must both contain None or neither of them should (2).", id);
418            valid = false;
419        }
420        valid
421    }
422
423    pub fn security_policy(&self) -> SecurityPolicy {
424        SecurityPolicy::from_str(&self.security_policy).unwrap()
425    }
426
427    pub fn message_security_mode(&self) -> MessageSecurityMode {
428        MessageSecurityMode::from(self.security_mode.as_ref())
429    }
430
431    pub fn endpoint_url(&self, base_endpoint: &str) -> String {
432        format!("{}{}", base_endpoint, self.path)
433    }
434
435    /// Returns the effective password security policy for the endpoint. This is the explicitly set password
436    /// security policy, or just the regular security policy.
437    pub fn password_security_policy(&self) -> SecurityPolicy {
438        let mut password_security_policy = self.security_policy();
439        if let Some(ref security_policy) = self.password_security_policy {
440            match SecurityPolicy::from_str(security_policy).unwrap() {
441                SecurityPolicy::Unknown => {
442                    panic!(
443                        "Password security policy {} is unrecognized",
444                        security_policy
445                    );
446                }
447                security_policy => {
448                    password_security_policy = security_policy;
449                }
450            }
451        }
452        password_security_policy
453    }
454
455    /// Test if the endpoint supports anonymous users
456    pub fn supports_anonymous(&self) -> bool {
457        self.supports_user_token_id(ANONYMOUS_USER_TOKEN_ID)
458    }
459
460    /// Tests if this endpoint supports user pass tokens. It does this by looking to see
461    /// if any of the users allowed to access this endpoint are user pass users.
462    pub fn supports_user_pass(&self, server_tokens: &BTreeMap<String, ServerUserToken>) -> bool {
463        for user_token_id in &self.user_token_ids {
464            if user_token_id != ANONYMOUS_USER_TOKEN_ID {
465                if let Some(user_token) = server_tokens.get(user_token_id) {
466                    if user_token.is_user_pass() {
467                        return true;
468                    }
469                }
470            }
471        }
472        false
473    }
474
475    /// Tests if this endpoint supports x509 tokens.  It does this by looking to see
476    /// if any of the users allowed to access this endpoint are x509 users.
477    pub fn supports_x509(&self, server_tokens: &BTreeMap<String, ServerUserToken>) -> bool {
478        for user_token_id in &self.user_token_ids {
479            if user_token_id != ANONYMOUS_USER_TOKEN_ID {
480                if let Some(user_token) = server_tokens.get(user_token_id) {
481                    if user_token.is_x509() {
482                        return true;
483                    }
484                }
485            }
486        }
487        false
488    }
489
490    pub fn supports_user_token_id(&self, id: &str) -> bool {
491        self.user_token_ids.contains(id)
492    }
493}
494
495#[derive(Debug, PartialEq, Serialize, Deserialize, Clone)]
496pub struct Performance {
497    /// Use a single-threaded executor. The default executor uses a thread pool with a worker
498    /// thread for each CPU core available on the system.
499    pub single_threaded_executor: bool,
500}
501
502#[derive(Debug, PartialEq, Serialize, Deserialize, Clone)]
503pub struct ServerConfig {
504    /// An id for this server
505    pub application_name: String,
506    /// A description for this server
507    pub application_uri: String,
508    /// Product url
509    pub product_uri: String,
510    /// Autocreates public / private keypair if they don't exist. For testing/samples only
511    /// since you do not have control of the values
512    pub create_sample_keypair: bool,
513    /// Path to a custom certificate, to be used instead of the default .der certificate
514    pub certificate_path: Option<PathBuf>,
515    /// Path to a custom private key, to be used instead of the default private key
516    pub private_key_path: Option<PathBuf>,
517    /// Checks the certificate's time validity
518    pub certificate_validation: CertificateValidation,
519    /// PKI folder, either absolute or relative to executable
520    pub pki_dir: PathBuf,
521    /// Url to a discovery server - adding this string causes the server to assume you wish to
522    /// register the server with a discovery server.
523    pub discovery_server_url: Option<String>,
524    /// tcp configuration information
525    pub tcp_config: TcpConfig,
526    /// Server OPA UA limits
527    pub limits: Limits,
528    /// Server Performance
529    pub performance: Performance,
530    /// Supported locale ids
531    pub locale_ids: Vec<String>,
532    /// User tokens
533    pub user_tokens: BTreeMap<String, ServerUserToken>,
534    /// discovery endpoint url which may or may not be the same as the service endpoints below.
535    pub discovery_urls: Vec<String>,
536    /// Default endpoint id
537    pub default_endpoint: Option<String>,
538    /// Endpoints supported by the server
539    pub endpoints: BTreeMap<String, ServerEndpoint>,
540}
541
542impl Config for ServerConfig {
543    fn is_valid(&self) -> bool {
544        let mut valid = true;
545        if self.application_name.is_empty() {
546            warn!("No application was set");
547        }
548        if self.application_uri.is_empty() {
549            warn!("No application uri was set");
550        }
551        if self.product_uri.is_empty() {
552            warn!("No product uri was set");
553        }
554        if self.endpoints.is_empty() {
555            error!("Server configuration is invalid. It defines no endpoints");
556            valid = false;
557        }
558        for (id, endpoint) in &self.endpoints {
559            if !endpoint.is_valid(id, &self.user_tokens) {
560                valid = false;
561            }
562        }
563        if let Some(ref default_endpoint) = self.default_endpoint {
564            if !self.endpoints.contains_key(default_endpoint) {
565                valid = false;
566            }
567        }
568        for (id, user_token) in &self.user_tokens {
569            if !user_token.is_valid(id) {
570                valid = false;
571            }
572        }
573        if self.limits.max_array_length == 0 {
574            error!("Server configuration is invalid. Max array length is invalid");
575            valid = false;
576        }
577        if self.limits.max_string_length == 0 {
578            error!("Server configuration is invalid. Max string length is invalid");
579            valid = false;
580        }
581        if self.limits.max_byte_string_length == 0 {
582            error!("Server configuration is invalid. Max byte string length is invalid");
583            valid = false;
584        }
585        if self.discovery_urls.is_empty() {
586            error!("Server configuration is invalid. Discovery urls not set");
587            valid = false;
588        }
589        valid
590    }
591
592    fn application_name(&self) -> UAString {
593        UAString::from(&self.application_name)
594    }
595
596    fn application_uri(&self) -> UAString {
597        UAString::from(&self.application_uri)
598    }
599
600    fn product_uri(&self) -> UAString {
601        UAString::from(&self.product_uri)
602    }
603
604    fn application_type(&self) -> ApplicationType {
605        ApplicationType::Server
606    }
607
608    fn discovery_urls(&self) -> Option<Vec<UAString>> {
609        let discovery_urls: Vec<UAString> =
610            self.discovery_urls.iter().map(UAString::from).collect();
611        Some(discovery_urls)
612    }
613}
614
615impl Default for ServerConfig {
616    fn default() -> Self {
617        let mut pki_dir = std::env::current_dir().unwrap();
618        pki_dir.push(Self::PKI_DIR);
619
620        ServerConfig {
621            application_name: String::new(),
622            application_uri: String::new(),
623            product_uri: String::new(),
624            create_sample_keypair: false,
625            certificate_path: None,
626            private_key_path: None,
627            pki_dir,
628            certificate_validation: CertificateValidation {
629                trust_client_certs: false,
630                check_time: true,
631            },
632            discovery_server_url: None,
633            tcp_config: TcpConfig {
634                host: "127.0.0.1".to_string(),
635                port: constants::DEFAULT_RUST_OPC_UA_SERVER_PORT,
636                hello_timeout: constants::DEFAULT_HELLO_TIMEOUT_SECONDS,
637            },
638            limits: Limits::default(),
639            user_tokens: BTreeMap::new(),
640            locale_ids: vec!["en".to_string()],
641            discovery_urls: Vec::new(),
642            default_endpoint: None,
643            endpoints: BTreeMap::new(),
644            performance: Performance {
645                single_threaded_executor: false,
646            },
647        }
648    }
649}
650
651impl ServerConfig {
652    /// The default PKI directory
653    pub const PKI_DIR: &'static str = "pki";
654
655    pub fn new<T>(
656        application_name: T,
657        user_tokens: BTreeMap<String, ServerUserToken>,
658        endpoints: BTreeMap<String, ServerEndpoint>,
659    ) -> Self
660    where
661        T: Into<String>,
662    {
663        let host = "127.0.0.1".to_string();
664        let port = constants::DEFAULT_RUST_OPC_UA_SERVER_PORT;
665
666        let application_name = application_name.into();
667        let application_uri = format!("urn:{}", application_name);
668        let product_uri = format!("urn:{}", application_name);
669        let discovery_server_url = Some(constants::DEFAULT_DISCOVERY_SERVER_URL.to_string());
670        let discovery_urls = vec![format!("opc.tcp://{}:{}/", host, port)];
671        let locale_ids = vec!["en".to_string()];
672
673        let mut pki_dir = std::env::current_dir().unwrap();
674        pki_dir.push(Self::PKI_DIR);
675
676        ServerConfig {
677            application_name,
678            application_uri,
679            product_uri,
680            create_sample_keypair: false,
681            certificate_path: None,
682            private_key_path: None,
683            certificate_validation: CertificateValidation {
684                trust_client_certs: false,
685                check_time: true,
686            },
687            pki_dir,
688            discovery_server_url,
689            tcp_config: TcpConfig {
690                host,
691                port,
692                hello_timeout: constants::DEFAULT_HELLO_TIMEOUT_SECONDS,
693            },
694            limits: Limits::default(),
695            locale_ids,
696            user_tokens,
697            discovery_urls,
698            default_endpoint: None,
699            endpoints,
700            performance: Performance {
701                single_threaded_executor: false,
702            },
703        }
704    }
705
706    pub fn decoding_options(&self) -> DecodingOptions {
707        DecodingOptions {
708            client_offset: chrono::Duration::zero(),
709            max_chunk_count: 0,
710            max_string_length: self.limits.max_string_length as usize,
711            max_byte_string_length: self.limits.max_byte_string_length as usize,
712            max_array_length: self.limits.max_array_length as usize,
713        }
714    }
715
716    pub fn add_endpoint(&mut self, id: &str, endpoint: ServerEndpoint) {
717        self.endpoints.insert(id.to_string(), endpoint);
718    }
719
720    pub fn read_x509_thumbprints(&mut self) {
721        self.user_tokens
722            .iter_mut()
723            .for_each(|(_, token)| token.read_thumbprint());
724    }
725
726    /// Returns a opc.tcp://server:port url that paths can be appended onto
727    pub fn base_endpoint_url(&self) -> String {
728        format!(
729            "opc.tcp://{}:{}",
730            self.tcp_config.host, self.tcp_config.port
731        )
732    }
733
734    /// Find the default endpoint
735    pub fn default_endpoint(&self) -> Option<&ServerEndpoint> {
736        if let Some(ref default_endpoint) = self.default_endpoint {
737            self.endpoints.get(default_endpoint)
738        } else {
739            None
740        }
741    }
742
743    /// Find the first endpoint that matches the specified url, security policy and message
744    /// security mode.
745    pub fn find_endpoint(
746        &self,
747        endpoint_url: &str,
748        security_policy: SecurityPolicy,
749        security_mode: MessageSecurityMode,
750    ) -> Option<&ServerEndpoint> {
751        let base_endpoint_url = self.base_endpoint_url();
752        let endpoint = self.endpoints.iter().find(|&(_, e)| {
753            // Test end point's security_policy_uri and matching url
754            if url_matches_except_host(&e.endpoint_url(&base_endpoint_url), endpoint_url) {
755                if e.security_policy() == security_policy
756                    && e.message_security_mode() == security_mode
757                {
758                    trace!("Found matching endpoint for url {} - {:?}", endpoint_url, e);
759                    true
760                } else {
761                    false
762                }
763            } else {
764                false
765            }
766        });
767        endpoint.map(|endpoint| endpoint.1)
768    }
769}