1use std::{
8 self,
9 collections::BTreeMap,
10 path::{Path, PathBuf},
11 str::FromStr,
12};
13
14use crate::{
15 core::config::Config,
16 crypto::SecurityPolicy,
17 types::{ApplicationType, MessageSecurityMode, UAString},
18};
19
20use super::session_retry_policy::SessionRetryPolicy;
21
22pub const ANONYMOUS_USER_TOKEN_ID: &str = "ANONYMOUS";
23
24#[derive(Debug, PartialEq, Serialize, Deserialize, Clone)]
25pub struct ClientUserToken {
26 pub user: String,
28 #[serde(skip_serializing_if = "Option::is_none")]
30 pub password: Option<String>,
31 #[serde(skip_serializing_if = "Option::is_none")]
32 pub cert_path: Option<String>,
33 #[serde(skip_serializing_if = "Option::is_none")]
34 pub private_key_path: Option<String>,
35}
36
37impl ClientUserToken {
38 pub fn user_pass<S, T>(user: S, password: T) -> Self
40 where
41 S: Into<String>,
42 T: Into<String>,
43 {
44 ClientUserToken {
45 user: user.into(),
46 password: Some(password.into()),
47 cert_path: None,
48 private_key_path: None,
49 }
50 }
51
52 pub fn x509<S>(user: S, cert_path: &Path, private_key_path: &Path) -> Self
54 where
55 S: Into<String>,
56 {
57 ClientUserToken {
60 user: user.into(),
61 password: None,
62 cert_path: Some(cert_path.to_string_lossy().to_string()),
63 private_key_path: Some(private_key_path.to_string_lossy().to_string()),
64 }
65 }
66
67 pub fn is_valid(&self) -> bool {
70 let mut valid = true;
71 if self.user.is_empty() {
72 error!("User token has an empty name.");
73 valid = false;
74 }
75 if self.password.is_some() {
77 if self.cert_path.is_some() || self.private_key_path.is_some() {
78 error!(
79 "User token {} holds a password and certificate info - it cannot be both.",
80 self.user
81 );
82 valid = false;
83 }
84 } else {
85 if self.cert_path.is_none() && self.private_key_path.is_none() {
86 error!(
87 "User token {} fails to provide a password or certificate info.",
88 self.user
89 );
90 valid = false;
91 } else if self.cert_path.is_none() || self.private_key_path.is_none() {
92 error!("User token {} fails to provide both a certificate path and a private key path.", self.user);
93 valid = false;
94 }
95 }
96 valid
97 }
98}
99
100#[derive(Debug, PartialEq, Serialize, Deserialize, Clone)]
102pub struct ClientEndpoint {
103 pub url: String,
105 pub security_policy: String,
107 pub security_mode: String,
109 #[serde(default = "ClientEndpoint::anonymous_id")]
111 pub user_token_id: String,
112}
113
114impl ClientEndpoint {
115 pub fn new<T>(url: T) -> Self
117 where
118 T: Into<String>,
119 {
120 ClientEndpoint {
121 url: url.into(),
122 security_policy: SecurityPolicy::None.to_str().into(),
123 security_mode: MessageSecurityMode::None.into(),
124 user_token_id: Self::anonymous_id(),
125 }
126 }
127
128 fn anonymous_id() -> String {
129 ANONYMOUS_USER_TOKEN_ID.to_string()
130 }
131
132 pub fn security_policy(&self) -> SecurityPolicy {
134 SecurityPolicy::from_str(&self.security_policy).unwrap()
135 }
136}
137
138#[derive(Debug, PartialEq, Serialize, Deserialize, Clone)]
139pub struct DecodingOptions {
140 pub max_message_size: usize,
142 pub max_chunk_count: usize,
144 pub max_string_length: usize,
146 pub max_byte_string_length: usize,
148 pub max_array_length: usize,
150}
151
152#[derive(Debug, PartialEq, Serialize, Deserialize, Clone)]
153pub struct Performance {
154 pub ignore_clock_skew: bool,
157 pub single_threaded_executor: bool,
160}
161
162#[derive(Debug, PartialEq, Serialize, Deserialize, Clone)]
164pub struct ClientConfig {
165 pub application_name: String,
167 pub application_uri: String,
169 pub product_uri: String,
171 pub create_sample_keypair: bool,
174 pub certificate_path: Option<PathBuf>,
176 pub private_key_path: Option<PathBuf>,
178 pub trust_server_certs: bool,
181 pub verify_server_certs: bool,
184 pub pki_dir: PathBuf,
186 pub preferred_locales: Vec<String>,
188 pub default_endpoint: String,
190 pub user_tokens: BTreeMap<String, ClientUserToken>,
192 pub endpoints: BTreeMap<String, ClientEndpoint>,
194 pub decoding_options: DecodingOptions,
196 pub session_retry_limit: i32,
198 pub session_retry_interval: u32,
200 pub session_timeout: u32,
202 pub performance: Performance,
204 pub session_name: String,
206}
207
208impl Config for ClientConfig {
209 fn is_valid(&self) -> bool {
211 let mut valid = true;
212
213 if self.application_name.is_empty() {
214 error!("Application name is empty");
215 valid = false;
216 }
217 if self.application_uri.is_empty() {
218 error!("Application uri is empty");
219 valid = false;
220 }
221 if self.user_tokens.contains_key(ANONYMOUS_USER_TOKEN_ID) {
222 error!(
223 "User tokens contains the reserved \"{}\" id",
224 ANONYMOUS_USER_TOKEN_ID
225 );
226 valid = false;
227 }
228 if self.user_tokens.contains_key("") {
229 error!("User tokens contains an endpoint with an empty id");
230 valid = false;
231 }
232 self.user_tokens.iter().for_each(|(_, token)| {
233 if !token.is_valid() {
234 valid = false;
235 }
236 });
237 if self.endpoints.is_empty() {
238 warn!("Endpoint config contains no endpoints");
239 } else {
240 if self.endpoints.contains_key("") {
242 error!("Endpoints contains an endpoint with an empty id");
243 valid = false;
244 }
245 if !self.default_endpoint.is_empty()
246 && !self.endpoints.contains_key(&self.default_endpoint)
247 {
248 error!(
249 "Default endpoint id {} does not exist in list of endpoints",
250 self.default_endpoint
251 );
252 valid = false;
253 }
254 self.endpoints.iter().for_each(|(id, e)| {
256 if SecurityPolicy::from_str(&e.security_policy).unwrap() != SecurityPolicy::Unknown
257 {
258 if MessageSecurityMode::Invalid
259 == MessageSecurityMode::from(e.security_mode.as_ref())
260 {
261 error!(
262 "Endpoint {} security mode {} is invalid",
263 id, e.security_mode
264 );
265 valid = false;
266 }
267 } else {
268 error!(
269 "Endpoint {} security policy {} is invalid",
270 id, e.security_policy
271 );
272 valid = false;
273 }
274 });
275 }
276 if self.session_retry_limit < 0 && self.session_retry_limit != -1 {
277 error!("Session retry limit of {} is invalid - must be -1 (infinite), 0 (never) or a positive value", self.session_retry_limit);
278 valid = false;
279 }
280 valid
281 }
282
283 fn application_name(&self) -> UAString {
284 UAString::from(&self.application_name)
285 }
286
287 fn application_uri(&self) -> UAString {
288 UAString::from(&self.application_uri)
289 }
290
291 fn product_uri(&self) -> UAString {
292 UAString::from(&self.product_uri)
293 }
294
295 fn application_type(&self) -> ApplicationType {
296 ApplicationType::Client
297 }
298}
299
300impl Default for ClientConfig {
301 fn default() -> Self {
302 Self::new("", "")
303 }
304}
305
306impl ClientConfig {
307 pub const PKI_DIR: &'static str = "pki";
309
310 pub fn new<T>(application_name: T, application_uri: T) -> Self
311 where
312 T: Into<String>,
313 {
314 let mut pki_dir = std::env::current_dir().unwrap();
315 pki_dir.push(Self::PKI_DIR);
316
317 let decoding_options = crate::types::DecodingOptions::default();
318 ClientConfig {
319 application_name: application_name.into(),
320 application_uri: application_uri.into(),
321 create_sample_keypair: false,
322 certificate_path: None,
323 private_key_path: None,
324 trust_server_certs: false,
325 verify_server_certs: true,
326 product_uri: String::new(),
327 pki_dir,
328 preferred_locales: Vec::new(),
329 default_endpoint: String::new(),
330 user_tokens: BTreeMap::new(),
331 endpoints: BTreeMap::new(),
332 session_retry_limit: SessionRetryPolicy::DEFAULT_RETRY_LIMIT as i32,
333 session_retry_interval: SessionRetryPolicy::DEFAULT_RETRY_INTERVAL_MS,
334 session_timeout: 0,
335 decoding_options: DecodingOptions {
336 max_array_length: decoding_options.max_array_length,
337 max_string_length: decoding_options.max_string_length,
338 max_byte_string_length: decoding_options.max_byte_string_length,
339 max_chunk_count: decoding_options.max_chunk_count,
340 max_message_size: decoding_options.max_message_size,
341 },
342 performance: Performance {
343 ignore_clock_skew: false,
344 single_threaded_executor: true,
345 },
346 session_name: "Rust OPC UA Client".into(),
347 }
348 }
349}