Skip to main content

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