up_rust/
uattributes.rs

1/********************************************************************************
2 * Copyright (c) 2023 Contributors to the Eclipse Foundation
3 *
4 * See the NOTICE file(s) distributed with this work for additional
5 * information regarding copyright ownership.
6 *
7 * This program and the accompanying materials are made available under the
8 * terms of the Apache License Version 2.0 which is available at
9 * https://www.apache.org/licenses/LICENSE-2.0
10 *
11 * SPDX-License-Identifier: Apache-2.0
12 ********************************************************************************/
13
14mod uattributesvalidator;
15mod upayloadformat;
16mod upriority;
17
18use std::time::SystemTime;
19
20pub use uattributesvalidator::*;
21pub use upriority::*;
22
23pub use crate::up_core_api::uattributes::*;
24use crate::UUID;
25
26pub(crate) const UPRIORITY_DEFAULT: UPriority = UPriority::UPRIORITY_CS1;
27
28#[derive(Debug)]
29pub enum UAttributesError {
30    ValidationError(String),
31    ParsingError(String),
32}
33
34impl UAttributesError {
35    pub fn validation_error<T>(message: T) -> UAttributesError
36    where
37        T: Into<String>,
38    {
39        Self::ValidationError(message.into())
40    }
41
42    pub fn parsing_error<T>(message: T) -> UAttributesError
43    where
44        T: Into<String>,
45    {
46        Self::ParsingError(message.into())
47    }
48}
49
50impl std::fmt::Display for UAttributesError {
51    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
52        match self {
53            Self::ValidationError(e) => f.write_fmt(format_args!("Validation failure: {}", e)),
54            Self::ParsingError(e) => f.write_fmt(format_args!("Parsing error: {}", e)),
55        }
56    }
57}
58
59impl std::error::Error for UAttributesError {}
60
61impl UAttributes {
62    /// Checks if a given priority class is the default priority class.
63    ///
64    /// Messages that do not have a priority class set explicity, are assigned to
65    /// the default priority class.
66    pub(crate) fn is_default_priority(prio: UPriority) -> bool {
67        prio == UPRIORITY_DEFAULT
68    }
69
70    /// Checks if these are the attributes for a Publish message.
71    ///
72    /// # Examples
73    ///
74    /// ```rust
75    /// use up_rust::{UAttributes, UMessageType};
76    ///
77    /// let attribs = UAttributes {
78    ///   type_: UMessageType::UMESSAGE_TYPE_PUBLISH.into(),
79    ///   ..Default::default()
80    /// };
81    /// assert!(attribs.is_publish());
82    /// ```
83    pub fn is_publish(&self) -> bool {
84        self.type_.enum_value() == Ok(UMessageType::UMESSAGE_TYPE_PUBLISH)
85    }
86
87    /// Checks if these are the attributes for an RPC Request message.
88    ///
89    /// # Examples
90    ///
91    /// ```rust
92    /// use up_rust::{UAttributes, UMessageType};
93    ///
94    /// let attribs = UAttributes {
95    ///   type_: UMessageType::UMESSAGE_TYPE_REQUEST.into(),
96    ///   ..Default::default()
97    /// };
98    /// assert!(attribs.is_request());
99    /// ```
100    pub fn is_request(&self) -> bool {
101        self.type_.enum_value() == Ok(UMessageType::UMESSAGE_TYPE_REQUEST)
102    }
103
104    /// Checks if these are the attributes for an RPC Response message.
105    ///
106    /// # Examples
107    ///
108    /// ```rust
109    /// use up_rust::{UAttributes, UMessageType};
110    ///
111    /// let attribs = UAttributes {
112    ///   type_: UMessageType::UMESSAGE_TYPE_RESPONSE.into(),
113    ///   ..Default::default()
114    /// };
115    /// assert!(attribs.is_response());
116    /// ```
117    pub fn is_response(&self) -> bool {
118        self.type_.enum_value() == Ok(UMessageType::UMESSAGE_TYPE_RESPONSE)
119    }
120
121    /// Checks if these are the attributes for a Notification message.
122    ///
123    /// # Examples
124    ///
125    /// ```rust
126    /// use up_rust::{UAttributes, UMessageType};
127    ///
128    /// let attribs = UAttributes {
129    ///   type_: UMessageType::UMESSAGE_TYPE_NOTIFICATION.into(),
130    ///   ..Default::default()
131    /// };
132    /// assert!(attribs.is_notification());
133    /// ```
134    pub fn is_notification(&self) -> bool {
135        self.type_.enum_value() == Ok(UMessageType::UMESSAGE_TYPE_NOTIFICATION)
136    }
137
138    /// Checks if the message that is described by these attributes should be considered expired.
139    ///
140    /// # Errors
141    ///
142    /// Returns an error if [`Self::ttl`] (time-to-live) contains a value greater than 0, but
143    /// * the message has expired according to the timestamp extracted from [`Self::id`] and the time-to-live value, or
144    /// * the current system time cannot be determined.
145    pub fn check_expired(&self) -> Result<(), UAttributesError> {
146        let ttl = match self.ttl {
147            Some(t) if t > 0 => u64::from(t),
148            _ => return Ok(()),
149        };
150
151        if let Some(creation_time) = self.id.as_ref().and_then(UUID::get_time) {
152            let delta = match SystemTime::now().duration_since(SystemTime::UNIX_EPOCH) {
153                Ok(duration) => {
154                    if let Ok(duration) = u64::try_from(duration.as_millis()) {
155                        duration - creation_time
156                    } else {
157                        return Err(UAttributesError::validation_error(
158                            "Invalid system time: too far in the future",
159                        ));
160                    }
161                }
162                Err(e) => return Err(UAttributesError::validation_error(e.to_string())),
163            };
164            if delta >= ttl {
165                return Err(UAttributesError::validation_error("message is expired"));
166            }
167        }
168        Ok(())
169    }
170}
171
172#[cfg(test)]
173mod tests {
174    use std::ops::Sub;
175    use std::time::{Duration, UNIX_EPOCH};
176
177    use super::*;
178    use test_case::test_case;
179
180    /// Creates a UUID n ms in the past.
181    ///
182    /// # Note
183    ///
184    /// For internal testing purposes only. For end-users, please use [`UUID::build()`]
185    fn build_n_ms_in_past(n_ms_in_past: u64) -> UUID {
186        let duration_since_unix_epoch = SystemTime::now()
187            .duration_since(UNIX_EPOCH)
188            .expect("current system time is set to a point in time before UNIX Epoch");
189        UUID::build_for_timestamp(
190            duration_since_unix_epoch.sub(Duration::from_millis(n_ms_in_past)),
191        )
192    }
193
194    #[test_case(None, None, false; "for message without ID nor TTL")]
195    #[test_case(None, Some(0), false; "for message without ID with TTL 0")]
196    #[test_case(None, Some(500), false; "for message without ID with TTL")]
197    #[test_case(Some(build_n_ms_in_past(1000)), None, false; "for message with ID without TTL")]
198    #[test_case(Some(build_n_ms_in_past(1000)), Some(0), false; "for message with ID and TTL 0")]
199    #[test_case(Some(build_n_ms_in_past(1000)), Some(500), true; "for message with ID and expired TTL")]
200    #[test_case(Some(build_n_ms_in_past(1000)), Some(2000), false; "for message with ID and non-expired TTL")]
201    fn test_is_expired(id: Option<UUID>, ttl: Option<u32>, should_be_expired: bool) {
202        let attributes = UAttributes {
203            type_: UMessageType::UMESSAGE_TYPE_NOTIFICATION.into(),
204            priority: UPriority::UPRIORITY_CS1.into(),
205            id: id.into(),
206            ttl,
207            ..Default::default()
208        };
209
210        assert!(attributes.check_expired().is_err() == should_be_expired);
211    }
212}