Skip to main content

opcua_client/
builder.rs

1use std::{path::PathBuf, time::Duration};
2
3use opcua_core::config::{Config, ConfigError};
4use tracing::error;
5
6use super::{Client, ClientConfig, ClientEndpoint, ClientUserToken, ANONYMOUS_USER_TOKEN_ID};
7
8#[derive(Default)]
9/// Client builder.
10pub struct ClientBuilder {
11    config: ClientConfig,
12}
13
14impl ClientBuilder {
15    /// Creates a `ClientBuilder`
16    pub fn new() -> ClientBuilder {
17        ClientBuilder::default()
18    }
19
20    /// Creates a `ClientBuilder` using a configuration file as the initial state.
21    pub fn from_config(path: impl Into<PathBuf>) -> Result<ClientBuilder, ConfigError> {
22        Ok(ClientBuilder {
23            config: ClientConfig::load(&path.into())?,
24        })
25    }
26
27    /// Yields a [`Client`] from the values set by the builder. If the builder is not in a valid state
28    /// it will return a list of errors.
29    ///
30    /// [`Client`]: client/struct.Client.html
31    pub fn client(self) -> Result<Client, Vec<String>> {
32        if let Err(e) = self.config.validate() {
33            for err in &e {
34                error!("{err}");
35            }
36            Err(e)
37        } else {
38            Ok(Client::new(self.config))
39        }
40    }
41
42    /// Get a reference to the internal [`ClientConfig`].
43    pub fn config(&self) -> &ClientConfig {
44        &self.config
45    }
46
47    /// Get a mutable reference to the internal [`ClientConfig`].
48    pub fn config_mut(&mut self) -> &mut ClientConfig {
49        &mut self.config
50    }
51
52    /// Yields a [`ClientConfig`] from the values set by the builder.
53    pub fn into_config(self) -> ClientConfig {
54        self.config
55    }
56
57    /// Tests if the builder is in a valid state to be able to yield a `Client`.
58    pub fn is_valid(&self) -> bool {
59        self.config.validate().is_ok()
60    }
61
62    /// Sets the application name.
63    pub fn application_name(mut self, application_name: impl Into<String>) -> Self {
64        self.config.application_name = application_name.into();
65        self
66    }
67
68    /// Sets the application uri
69    pub fn application_uri(mut self, application_uri: impl Into<String>) -> Self {
70        self.config.application_uri = application_uri.into();
71        self
72    }
73
74    /// Sets the product uri.
75    pub fn product_uri(mut self, product_uri: impl Into<String>) -> Self {
76        self.config.product_uri = product_uri.into();
77        self
78    }
79
80    /// Sets whether the client should generate its own key pair if there is none found in the pki
81    /// directory.
82    pub fn create_sample_keypair(mut self, create_sample_keypair: bool) -> Self {
83        self.config.create_sample_keypair = create_sample_keypair;
84        self
85    }
86
87    /// Sets a custom client certificate path. The path is required to be provided as a partial
88    /// path relative to the PKI directory. If set, this path will be used to read the client
89    /// certificate from disk. The certificate can be in either the .der or .pem format.
90    pub fn certificate_path(mut self, certificate_path: impl Into<PathBuf>) -> Self {
91        self.config.certificate_path = Some(certificate_path.into());
92        self
93    }
94
95    /// Sets a custom private key path. The path is required to be provided as a partial path
96    /// relative to the PKI directory. If set, this path will be used to read the private key
97    /// from disk.
98    pub fn private_key_path(mut self, private_key_path: impl Into<PathBuf>) -> Self {
99        self.config.private_key_path = Some(private_key_path.into());
100        self
101    }
102
103    /// Sets whether the client should automatically trust servers. If this is not set then
104    /// the client will reject the server upon first connect and the server's certificate
105    /// must be manually moved from pki's `/rejected` folder to the `/trusted` folder. If it is
106    /// set, then the server cert will automatically be stored in the `/trusted` folder.
107    pub fn trust_server_certs(mut self, trust_server_certs: bool) -> Self {
108        self.config.trust_server_certs = trust_server_certs;
109        self
110    }
111
112    /// Sets whether the client should verify server certificates. Regardless of this setting,
113    /// server certificates are always checked to see if they are trusted and have a valid key
114    /// length. In addition (if `verify_server_certs` is unset or is set to `true`) it will
115    /// verify the hostname, application uri and the not before / after values to ensure validity.
116    pub fn verify_server_certs(mut self, verify_server_certs: bool) -> Self {
117        self.config.verify_server_certs = verify_server_certs;
118        self
119    }
120
121    /// Sets the pki directory where client's own key pair is stored and where `/trusted` and
122    /// `/rejected` server certificates are stored.
123    pub fn pki_dir(mut self, pki_dir: impl Into<PathBuf>) -> Self {
124        self.config.pki_dir = pki_dir.into();
125        self
126    }
127
128    /// Sets the preferred locales of the client. These are passed to the server during session
129    /// creation to ensure localized strings are in the preferred language.
130    pub fn preferred_locales(mut self, preferred_locales: Vec<String>) -> Self {
131        self.config.preferred_locales = preferred_locales;
132        self
133    }
134
135    /// Sets the id of the default endpoint to connect to.
136    pub fn default_endpoint(mut self, endpoint_id: impl Into<String>) -> Self {
137        self.config.default_endpoint = endpoint_id.into();
138        self
139    }
140
141    /// Adds an endpoint to the list of endpoints the client knows of.
142    pub fn endpoint(mut self, endpoint_id: impl Into<String>, endpoint: ClientEndpoint) -> Self {
143        self.config.endpoints.insert(endpoint_id.into(), endpoint);
144        self
145    }
146
147    /// Adds multiple endpoints to the list of endpoints the client knows of.
148    pub fn endpoints(mut self, endpoints: Vec<(impl Into<String>, ClientEndpoint)>) -> Self {
149        for e in endpoints {
150            self.config.endpoints.insert(e.0.into(), e.1);
151        }
152        self
153    }
154
155    /// Adds a user token to the list supported by the client.
156    pub fn user_token(
157        mut self,
158        user_token_id: impl Into<String>,
159        user_token: ClientUserToken,
160    ) -> Self {
161        let user_token_id = user_token_id.into();
162        if user_token_id == ANONYMOUS_USER_TOKEN_ID {
163            panic!("User token id {user_token_id} is reserved");
164        }
165        self.config.user_tokens.insert(user_token_id, user_token);
166        self
167    }
168
169    /// Requested secure channel token lifetime, in milliseconds.
170    /// The channel will be renewed once 3/4 of the
171    /// lifetime has elapsed. Setting this too low is likely to cause issues.
172    pub fn channel_lifetime(mut self, channel_lifetime: u32) -> Self {
173        self.config.channel_lifetime = channel_lifetime;
174        self
175    }
176
177    /// Sets the session retry limit.
178    ///
179    /// # Panics
180    ///
181    /// Panics if `session_retry_limit` is less -1.
182    pub fn session_retry_limit(mut self, session_retry_limit: i32) -> Self {
183        if session_retry_limit < 0 && session_retry_limit != -1 {
184            panic!("Session retry limit must be -1, 0 or a positive number");
185        }
186        self.config.session_retry_limit = session_retry_limit;
187        self
188    }
189
190    /// Initial time between retries when backing off on session reconnects.
191    pub fn session_retry_initial(mut self, session_retry_initial: Duration) -> Self {
192        self.config.session_retry_initial = session_retry_initial;
193        self
194    }
195
196    /// Maximum time between retries when backing off on session reconnects.
197    pub fn session_retry_max(mut self, session_retry_max: Duration) -> Self {
198        self.config.session_retry_max = session_retry_max;
199        self
200    }
201
202    /// Time between making simple Read requests to the server to check for liveness
203    /// and avoid session timeouts.
204    pub fn keep_alive_interval(mut self, keep_alive_interval: Duration) -> Self {
205        self.config.keep_alive_interval = keep_alive_interval;
206        self
207    }
208
209    /// Maximum number of array elements. 0 actually means 0, i.e. no array permitted
210    pub fn max_array_length(mut self, max_array_length: usize) -> Self {
211        self.config.decoding_options.max_array_length = max_array_length;
212        self
213    }
214
215    /// Maximum length in bytes of a byte string. 0 actually means 0, i.e. no byte strings permitted.
216    pub fn max_byte_string_length(mut self, max_byte_string_length: usize) -> Self {
217        self.config.decoding_options.max_byte_string_length = max_byte_string_length;
218        self
219    }
220
221    /// Sets the maximum number of chunks in an outgoing message. 0 means no limit.
222    pub fn max_chunk_count(mut self, max_chunk_count: usize) -> Self {
223        self.config.decoding_options.max_chunk_count = max_chunk_count;
224        self
225    }
226
227    /// Maximum size of each individual outgoing message chunk.
228    pub fn max_chunk_size(mut self, max_chunk_size: usize) -> Self {
229        self.config.decoding_options.max_chunk_size = max_chunk_size;
230        self
231    }
232
233    /// Maximum size of each incoming chunk.
234    pub fn max_incoming_chunk_size(mut self, max_incoming_chunk_size: usize) -> Self {
235        self.config.decoding_options.max_incoming_chunk_size = max_incoming_chunk_size;
236        self
237    }
238
239    /// Sets the maximum outgoing message size in bytes. 0 means no limit.
240    pub fn max_message_size(mut self, max_message_size: usize) -> Self {
241        self.config.decoding_options.max_message_size = max_message_size;
242        self
243    }
244
245    /// Maximum length in bytes of a string. 0 actually means 0, i.e. no string permitted.
246    pub fn max_string_length(mut self, max_string_length: usize) -> Self {
247        self.config.decoding_options.max_string_length = max_string_length;
248        self
249    }
250
251    /// Maximum number of failed keep alives before the client will be forcibly closed.
252    /// Set this to zero to never close the connection due to failed keepalives.
253    ///
254    /// Note that this should not be necessary to set if the server is compliant,
255    /// only if it ends up in a bad state that cannot be recovered from easily.
256    pub fn max_failed_keep_alive_count(mut self, max_failed_keep_alive_count: u64) -> Self {
257        self.config.max_failed_keep_alive_count = max_failed_keep_alive_count;
258        self
259    }
260
261    /// Set the timeout on requests sent to the server.
262    pub fn request_timeout(mut self, request_timeout: Duration) -> Self {
263        self.config.request_timeout = request_timeout;
264        self
265    }
266
267    /// Set the timeout on publish requests sent to the server.
268    pub fn publish_timeout(mut self, publish_timeout: Duration) -> Self {
269        self.config.publish_timeout = publish_timeout;
270        self
271    }
272
273    /// Set the lowest allowed publishing interval by the client.
274    /// The server may also enforce its own minimum.
275    pub fn min_publish_interval(mut self, min_publish_interval: Duration) -> Self {
276        self.config.min_publish_interval = min_publish_interval;
277        self
278    }
279
280    /// Sets whether the client should ignore clock skew so the client can make a successful
281    /// connection to the server, even when the client and server clocks are out of sync.
282    pub fn ignore_clock_skew(mut self, ignore_clock_skew: bool) -> Self {
283        self.config.performance.ignore_clock_skew = ignore_clock_skew;
284        self
285    }
286
287    /// When a session is recreated on the server, the client will attempt to
288    /// transfer monitored subscriptions from the old session to the new.
289    /// This is the maximum number of monitored items to create per request.
290    pub fn recreate_monitored_items_chunk(mut self, recreate_monitored_items_chunk: usize) -> Self {
291        self.config.performance.recreate_monitored_items_chunk = recreate_monitored_items_chunk;
292        self
293    }
294
295    /// Automatically recreate subscriptions on reconnect, by first calling
296    /// [`crate::Session::transfer_subscriptions`], then attempting to recreate
297    /// subscriptions if that fails.
298    ///
299    /// Defaults to `true`. Note that if you disable this feature, you will need to
300    /// handle cleanup of the subscriptions in the session yourself.
301    pub fn recreate_subscriptions(mut self, recreate_subscriptions: bool) -> Self {
302        self.config.recreate_subscriptions = recreate_subscriptions;
303        self
304    }
305
306    /// Session name - the default name to use for a new session
307    pub fn session_name(mut self, session_name: impl Into<String>) -> Self {
308        self.config.session_name = session_name.into();
309        self
310    }
311
312    /// Sets the session timeout period, in milliseconds.
313    pub fn session_timeout(mut self, session_timeout: u32) -> Self {
314        self.config.session_timeout = session_timeout;
315        self
316    }
317
318    /// Set the length of the nonce generated for CreateSession requests.
319    pub fn session_nonce_length(mut self, session_nonce_length: usize) -> Self {
320        self.config.session_nonce_length = session_nonce_length;
321        self
322    }
323}