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