Skip to main content

opcua_types/service_types/
impls.rs

1use std::{self, fmt};
2
3use crate::{
4    attribute::AttributeId,
5    byte_string::ByteString,
6    constants,
7    data_value::DataValue,
8    extension_object::ExtensionObject,
9    localized_text::LocalizedText,
10    node_id::NodeId,
11    node_ids::{DataTypeId, ObjectId},
12    profiles,
13    qualified_name::QualifiedName,
14    request_header::RequestHeader,
15    response_header::ResponseHeader,
16    service_types::{
17        AnonymousIdentityToken, ApplicationDescription, ApplicationType, Argument, CallMethodRequest,
18        DataChangeFilter, DataChangeTrigger, EndpointDescription, enums::DeadbandType, MessageSecurityMode, MonitoredItemCreateRequest, MonitoringMode,
19        MonitoringParameters, ReadValueId, ServerDiagnosticsSummaryDataType, ServiceCounterDataType, ServiceFault,
20        SignatureData, UserNameIdentityToken, UserTokenPolicy, UserTokenType,
21    },
22    status_codes::StatusCode,
23    string::UAString,
24    variant::Variant,
25};
26
27/// Implemented by messages
28pub trait MessageInfo {
29    /// The object id associated with the message
30    fn object_id(&self) -> ObjectId;
31}
32
33impl ServiceFault {
34    pub fn new(request_header: &RequestHeader, service_result: StatusCode) -> ServiceFault {
35        ServiceFault {
36            response_header: ResponseHeader::new_service_result(request_header, service_result)
37        }
38    }
39}
40
41impl UserTokenPolicy {
42    pub fn anonymous() -> UserTokenPolicy {
43        UserTokenPolicy {
44            policy_id: UAString::from("anonymous"),
45            token_type: UserTokenType::Anonymous,
46            issued_token_type: UAString::null(),
47            issuer_endpoint_url: UAString::null(),
48            security_policy_uri: UAString::null(),
49        }
50    }
51}
52
53impl DataChangeFilter {
54    /// Compares one data value to another and returns true if they differ, according to their trigger
55    /// type of status, status/value or status/value/timestamp
56    pub fn compare(&self, v1: &DataValue, v2: &DataValue, eu_range: Option<(f64, f64)>) -> bool {
57        match self.trigger {
58            DataChangeTrigger::Status => {
59                v1.status == v2.status
60            }
61            DataChangeTrigger::StatusValue => {
62                v1.status == v2.status &&
63                    self.compare_value_option(&v1.value, &v2.value, eu_range)
64            }
65            DataChangeTrigger::StatusValueTimestamp => {
66                v1.status == v2.status &&
67                    self.compare_value_option(&v1.value, &v2.value, eu_range) &&
68                    v1.server_timestamp == v2.server_timestamp
69            }
70        }
71    }
72
73    /// Compares two variant values to each other. Returns true if they are considered the "same".
74    pub fn compare_value_option(&self, v1: &Option<Variant>, v2: &Option<Variant>, eu_range: Option<(f64, f64)>) -> bool {
75        match (v1, v2) {
76            (Some(_), None) | (None, Some(_)) => {
77                false
78            }
79            (None, None) => {
80                // If it's always none then it hasn't changed
81                true
82            }
83            (Some(v1), Some(v2)) => {
84                // Otherwise test the filter
85                self.compare_value(v1, v2, eu_range).unwrap_or(true)
86            }
87        }
88    }
89
90    /// Compares two values, either a straight value compare or a numeric comparison against the
91    /// deadband settings. If deadband is asked for and the values are not convertible into a numeric
92    /// value, the result is false. The value is true if the values are the same within the limits
93    /// set.
94    ///
95    /// The eu_range is the engineering unit range and represents the range that the value should
96    /// typically operate between. It's used for percentage change operations and ignored otherwise.
97    ///
98    /// # Errors
99    ///
100    /// BadDeadbandFilterInvalid indicates the deadband settings were invalid, e.g. an invalid
101    /// type, or the args were invalid. A (low, high) range must be supplied for a percentage deadband compare.
102    pub fn compare_value(&self, v1: &Variant, v2: &Variant, eu_range: Option<(f64, f64)>) -> std::result::Result<bool, StatusCode> {
103        // TODO be able to compare arrays of numbers
104        if self.deadband_type == DeadbandType::None as u32 {
105            // Straight comparison of values
106            Ok(v1 == v2)
107        } else {
108            // Absolute
109            match (v1.as_f64(), v2.as_f64()) {
110                (None, _) | (_, None) => Ok(false),
111                (Some(v1), Some(v2)) => {
112                    if self.deadband_value < 0f64 {
113                        Err(StatusCode::BadDeadbandFilterInvalid)
114                    } else if self.deadband_type == DeadbandType::Absolute as u32 {
115                        Ok(DataChangeFilter::abs_compare(v1, v2, self.deadband_value))
116                    } else if self.deadband_type == DeadbandType::Percent as u32 {
117                        match eu_range {
118                            None => Err(StatusCode::BadDeadbandFilterInvalid),
119                            Some((low, high)) => {
120                                if low >= high {
121                                    Err(StatusCode::BadDeadbandFilterInvalid)
122                                } else {
123                                    Ok(DataChangeFilter::pct_compare(v1, v2, low, high, self.deadband_value))
124                                }
125                            }
126                        }
127                    } else {
128                        // Type is not recognized
129                        Err(StatusCode::BadDeadbandFilterInvalid)
130                    }
131                }
132            }
133        }
134    }
135
136    /// Compares the difference between v1 and v2 to the threshold. The two values are considered equal
137    /// if their difference is less than or equal to the threshold.
138    pub fn abs_compare(v1: f64, v2: f64, threshold_diff: f64) -> bool {
139        let diff = (v1 - v2).abs();
140        diff <= threshold_diff
141    }
142
143    /// Compares the percentage difference between v1 and v2 using the low-high range as the comparison.
144    /// The two values are considered equal if their perentage difference is less than or equal to the
145    /// threshold.
146    pub fn pct_compare(v1: f64, v2: f64, low: f64, high: f64, threshold_pct_change: f64) -> bool {
147        let v1_pct = 100f64 * (v1 - low) / (high - low);
148        let v2_pct = 100f64 * (v2 - low) / (high - low);
149        let pct_change = (v1_pct - v2_pct).abs();
150        // Comparison is equal if the % change of v1 - v2 < the threshold
151        pct_change <= threshold_pct_change
152    }
153}
154
155impl EndpointDescription {
156    /// Returns a reference to a policy that matches the supplied token type, otherwise None
157    pub fn find_policy(&self, token_type: UserTokenType) -> Option<&UserTokenPolicy> {
158        if let Some(ref policies) = self.user_identity_tokens {
159            policies.iter().find(|t| t.token_type == token_type)
160        } else {
161            None
162        }
163    }
164
165    /// Returns a reference to a policy that matches the supplied policy id
166    pub fn find_policy_by_id(&self, policy_id: &str) -> Option<&UserTokenPolicy> {
167        if let Some(ref policies) = self.user_identity_tokens {
168            policies.iter().find(|t| t.policy_id.as_ref() == policy_id)
169        } else {
170            None
171        }
172    }
173}
174
175impl UserNameIdentityToken {
176    /// Ensures the token is valid
177    pub fn is_valid(&self) -> bool {
178        !self.user_name.is_null() && !self.password.is_null()
179    }
180
181    // Get the plaintext password as a string, if possible.
182    pub fn plaintext_password(&self) -> Result<String, StatusCode> {
183        if !self.encryption_algorithm.is_empty() {
184            // Should not be calling this function at all encryption is applied
185            panic!();
186        }
187        String::from_utf8(self.password.as_ref().to_vec()).map_err(|_| StatusCode::BadDecodingError)
188    }
189
190    /// Authenticates the token against the supplied username and password.
191    pub fn authenticate(&self, username: &str, password: &[u8]) -> Result<(), StatusCode> {
192        // No comparison will be made unless user and pass are explicitly set to something in the token
193        // Even if someone has a blank password, client should pass an empty string, not null.
194        let valid = if self.is_valid() {
195            // Plaintext encryption
196            if self.encryption_algorithm.is_null() {
197                // Password shall be a UTF-8 encoded string
198                let id_user = self.user_name.as_ref();
199                let id_pass = self.password.value.as_ref().unwrap();
200                if username == id_user {
201                    if password == id_pass.as_slice() {
202                        true
203                    } else {
204                        error!("Authentication error: User name {} supplied by client is recognised but password is not", username);
205                        false
206                    }
207                } else {
208                    error!("Authentication error: User name supplied by client is unrecognised");
209                    false
210                }
211            } else {
212                // TODO See 7.36.3. UserTokenPolicy and SecurityPolicy should be used to provide
213                //  a means to encrypt a password and not send it plain text. Sending a plaintext
214                //  password over unsecured network is a bad thing!!!
215                error!("Authentication error: Unsupported encryption algorithm {}", self.encryption_algorithm.as_ref());
216                false
217            }
218        } else {
219            error!("Authentication error: User / pass credentials not supplied in token");
220            false
221        };
222        if valid {
223            Ok(())
224        } else {
225            Err(StatusCode::BadIdentityTokenRejected)
226        }
227    }
228}
229
230impl<'a> From<&'a NodeId> for ReadValueId {
231    fn from(node_id: &'a NodeId) -> Self {
232        Self::from(node_id.clone())
233    }
234}
235
236impl From<NodeId> for ReadValueId {
237    fn from(node_id: NodeId) -> Self {
238        ReadValueId {
239            node_id,
240            attribute_id: AttributeId::Value as u32,
241            index_range: UAString::null(),
242            data_encoding: QualifiedName::null(),
243        }
244    }
245}
246
247impl<'a> From<(u16, &'a str)> for ReadValueId {
248    fn from(v: (u16, &'a str)) -> Self {
249        Self::from(NodeId::from(v))
250    }
251}
252
253impl Default for AnonymousIdentityToken {
254    fn default() -> Self {
255        AnonymousIdentityToken {
256            policy_id: UAString::from(profiles::SECURITY_USER_TOKEN_POLICY_ANONYMOUS)
257        }
258    }
259}
260
261impl SignatureData {
262    pub fn null() -> SignatureData {
263        SignatureData {
264            algorithm: UAString::null(),
265            signature: ByteString::null(),
266        }
267    }
268}
269
270impl Into<MonitoredItemCreateRequest> for NodeId {
271    fn into(self) -> MonitoredItemCreateRequest {
272        MonitoredItemCreateRequest::new(self.into(), MonitoringMode::Reporting, MonitoringParameters::default())
273    }
274}
275
276impl MonitoredItemCreateRequest {
277    /// Adds an item to monitor to the subscription
278    pub fn new(item_to_monitor: ReadValueId, monitoring_mode: MonitoringMode, requested_parameters: MonitoringParameters) -> MonitoredItemCreateRequest {
279        MonitoredItemCreateRequest {
280            item_to_monitor,
281            monitoring_mode,
282            requested_parameters,
283        }
284    }
285}
286
287impl Default for ApplicationDescription {
288    fn default() -> Self {
289        Self {
290            application_uri: UAString::null(),
291            product_uri: UAString::null(),
292            application_name: LocalizedText::null(),
293            application_type: ApplicationType::Server,
294            gateway_server_uri: UAString::null(),
295            discovery_profile_uri: UAString::null(),
296            discovery_urls: None,
297        }
298    }
299}
300
301impl Default for MonitoringParameters {
302    fn default() -> Self {
303        MonitoringParameters {
304            client_handle: 0,
305            sampling_interval: -1f64,
306            filter: ExtensionObject::null(),
307            queue_size: 1,
308            discard_oldest: true,
309        }
310    }
311}
312
313impl Into<CallMethodRequest> for (NodeId, NodeId, Option<Vec<Variant>>) {
314    fn into(self) -> CallMethodRequest {
315        CallMethodRequest {
316            object_id: self.0,
317            method_id: self.1,
318            input_arguments: self.2,
319        }
320    }
321}
322
323impl Default for ServerDiagnosticsSummaryDataType {
324    fn default() -> Self {
325        ServerDiagnosticsSummaryDataType {
326            server_view_count: 0,
327            current_session_count: 0,
328            cumulated_session_count: 0,
329            security_rejected_session_count: 0,
330            rejected_session_count: 0,
331            session_timeout_count: 0,
332            session_abort_count: 0,
333            current_subscription_count: 0,
334            cumulated_subscription_count: 0,
335            publishing_interval_count: 0,
336            security_rejected_requests_count: 0,
337            rejected_requests_count: 0,
338        }
339    }
340}
341
342impl<'a> From<&'a str> for EndpointDescription {
343    fn from(v: &'a str) -> Self {
344        EndpointDescription::from((v, constants::SECURITY_POLICY_NONE_URI, MessageSecurityMode::None))
345    }
346}
347
348impl<'a> From<(&'a str, &'a str, MessageSecurityMode)> for EndpointDescription {
349    fn from(v: (&'a str, &'a str, MessageSecurityMode)) -> Self {
350        EndpointDescription::from((v.0, v.1, v.2, None))
351    }
352}
353
354impl<'a> From<(&'a str, &'a str, MessageSecurityMode, UserTokenPolicy)> for EndpointDescription {
355    fn from(v: (&'a str, &'a str, MessageSecurityMode, UserTokenPolicy)) -> Self {
356        EndpointDescription::from((v.0, v.1, v.2, Some(vec![v.3])))
357    }
358}
359
360impl<'a> From<(&'a str, &'a str, MessageSecurityMode, Vec<UserTokenPolicy>)> for EndpointDescription {
361    fn from(v: (&'a str, &'a str, MessageSecurityMode, Vec<UserTokenPolicy>)) -> Self {
362        EndpointDescription::from((v.0, v.1, v.2, Some(v.3)))
363    }
364}
365
366impl<'a> From<(&'a str, &'a str, MessageSecurityMode, Option<Vec<UserTokenPolicy>>)> for EndpointDescription {
367    fn from(v: (&'a str, &'a str, MessageSecurityMode, Option<Vec<UserTokenPolicy>>)) -> Self {
368        EndpointDescription {
369            endpoint_url: UAString::from(v.0),
370            security_policy_uri: UAString::from(v.1),
371            security_mode: v.2,
372            server: ApplicationDescription::default(),
373            security_level: 0,
374            server_certificate: ByteString::null(),
375            transport_profile_uri: UAString::null(),
376            user_identity_tokens: v.3,
377        }
378    }
379}
380
381const MESSAGE_SECURITY_MODE_NONE: &str = "None";
382const MESSAGE_SECURITY_MODE_SIGN: &str = "Sign";
383const MESSAGE_SECURITY_MODE_SIGN_AND_ENCRYPT: &str = "SignAndEncrypt";
384
385impl fmt::Display for MessageSecurityMode {
386    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
387        let name = match self {
388            MessageSecurityMode::None => MESSAGE_SECURITY_MODE_NONE,
389            MessageSecurityMode::Sign => MESSAGE_SECURITY_MODE_SIGN,
390            MessageSecurityMode::SignAndEncrypt => MESSAGE_SECURITY_MODE_SIGN_AND_ENCRYPT,
391            _ => "",
392        };
393        write!(f, "{}", name)
394    }
395}
396
397impl From<MessageSecurityMode> for String {
398    fn from(security_mode: MessageSecurityMode) -> Self {
399        String::from(
400            match security_mode {
401                MessageSecurityMode::None => MESSAGE_SECURITY_MODE_NONE,
402                MessageSecurityMode::Sign => MESSAGE_SECURITY_MODE_SIGN,
403                MessageSecurityMode::SignAndEncrypt => MESSAGE_SECURITY_MODE_SIGN_AND_ENCRYPT,
404                _ => "",
405            }
406        )
407    }
408}
409
410impl<'a> From<&'a str> for MessageSecurityMode {
411    fn from(str: &'a str) -> Self {
412        match str {
413            MESSAGE_SECURITY_MODE_NONE => MessageSecurityMode::None,
414            MESSAGE_SECURITY_MODE_SIGN => MessageSecurityMode::Sign,
415            MESSAGE_SECURITY_MODE_SIGN_AND_ENCRYPT => MessageSecurityMode::SignAndEncrypt,
416            _ => {
417                error!("Specified security mode \"{}\" is not recognized", str);
418                MessageSecurityMode::Invalid
419            }
420        }
421    }
422}
423
424impl From<(&str, DataTypeId)> for Argument {
425    fn from(v: (&str, DataTypeId)) -> Self {
426        Argument {
427            name: UAString::from(v.0),
428            data_type: v.1.into(),
429            value_rank: -1,
430            array_dimensions: None,
431            description: LocalizedText::new("", ""),
432        }
433    }
434}
435
436impl Default for ServiceCounterDataType {
437    fn default() -> Self {
438        Self {
439            total_count: 0,
440            error_count: 0,
441        }
442    }
443}
444
445impl ServiceCounterDataType {
446    pub fn success(&mut self) {
447        self.total_count += 1;
448    }
449
450    pub fn error(&mut self) {
451        self.total_count += 1;
452        self.error_count += 1;
453    }
454}