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
26#[derive(Debug)]
27pub enum UAttributesError {
28    ValidationError(String),
29    ParsingError(String),
30}
31
32impl UAttributesError {
33    pub fn validation_error<T>(message: T) -> UAttributesError
34    where
35        T: Into<String>,
36    {
37        Self::ValidationError(message.into())
38    }
39
40    pub fn parsing_error<T>(message: T) -> UAttributesError
41    where
42        T: Into<String>,
43    {
44        Self::ParsingError(message.into())
45    }
46}
47
48impl std::fmt::Display for UAttributesError {
49    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
50        match self {
51            Self::ValidationError(e) => f.write_fmt(format_args!("Validation failure: {}", e)),
52            Self::ParsingError(e) => f.write_fmt(format_args!("Parsing error: {}", e)),
53        }
54    }
55}
56
57impl std::error::Error for UAttributesError {}
58
59impl UAttributes {
60    /// Checks if these are the attributes for a Publish message.
61    ///
62    /// # Examples
63    ///
64    /// ```rust
65    /// use up_rust::{UAttributes, UMessageType};
66    ///
67    /// let attribs = UAttributes {
68    ///   type_: UMessageType::UMESSAGE_TYPE_PUBLISH.into(),
69    ///   ..Default::default()
70    /// };
71    /// assert!(attribs.is_publish());
72    /// ```
73    pub fn is_publish(&self) -> bool {
74        self.type_.enum_value() == Ok(UMessageType::UMESSAGE_TYPE_PUBLISH)
75    }
76
77    /// Checks if these are the attributes for an RPC Request message.
78    ///
79    /// # Examples
80    ///
81    /// ```rust
82    /// use up_rust::{UAttributes, UMessageType};
83    ///
84    /// let attribs = UAttributes {
85    ///   type_: UMessageType::UMESSAGE_TYPE_REQUEST.into(),
86    ///   ..Default::default()
87    /// };
88    /// assert!(attribs.is_request());
89    /// ```
90    pub fn is_request(&self) -> bool {
91        self.type_.enum_value() == Ok(UMessageType::UMESSAGE_TYPE_REQUEST)
92    }
93
94    /// Checks if these are the attributes for an RPC Response message.
95    ///
96    /// # Examples
97    ///
98    /// ```rust
99    /// use up_rust::{UAttributes, UMessageType};
100    ///
101    /// let attribs = UAttributes {
102    ///   type_: UMessageType::UMESSAGE_TYPE_RESPONSE.into(),
103    ///   ..Default::default()
104    /// };
105    /// assert!(attribs.is_response());
106    /// ```
107    pub fn is_response(&self) -> bool {
108        self.type_.enum_value() == Ok(UMessageType::UMESSAGE_TYPE_RESPONSE)
109    }
110
111    /// Checks if these are the attributes for a Notification message.
112    ///
113    /// # Examples
114    ///
115    /// ```rust
116    /// use up_rust::{UAttributes, UMessageType};
117    ///
118    /// let attribs = UAttributes {
119    ///   type_: UMessageType::UMESSAGE_TYPE_NOTIFICATION.into(),
120    ///   ..Default::default()
121    /// };
122    /// assert!(attribs.is_notification());
123    /// ```
124    pub fn is_notification(&self) -> bool {
125        self.type_.enum_value() == Ok(UMessageType::UMESSAGE_TYPE_NOTIFICATION)
126    }
127
128    /// Checks if the message that is described by these attributes should be considered expired.
129    ///
130    /// # Errors
131    ///
132    /// Returns an error if [`Self::ttl`] (time-to-live) contains a value greater than 0, but
133    /// * the message has expired according to the timestamp extracted from [`Self::id`] and the time-to-live value, or
134    /// * the current system time cannot be determined.
135    pub fn check_expired(&self) -> Result<(), UAttributesError> {
136        let ttl = match self.ttl {
137            Some(t) if t > 0 => u64::from(t),
138            _ => return Ok(()),
139        };
140
141        if let Some(creation_time) = self.id.as_ref().and_then(UUID::get_time) {
142            let delta = match SystemTime::now().duration_since(SystemTime::UNIX_EPOCH) {
143                Ok(duration) => {
144                    if let Ok(duration) = u64::try_from(duration.as_millis()) {
145                        duration - creation_time
146                    } else {
147                        return Err(UAttributesError::validation_error(
148                            "Invalid system time: too far in the future",
149                        ));
150                    }
151                }
152                Err(e) => return Err(UAttributesError::validation_error(e.to_string())),
153            };
154            if delta >= ttl {
155                return Err(UAttributesError::validation_error("message is expired"));
156            }
157        }
158        Ok(())
159    }
160}
161
162#[cfg(test)]
163mod tests {
164    use std::ops::Sub;
165    use std::time::{Duration, UNIX_EPOCH};
166
167    use super::*;
168    use test_case::test_case;
169
170    /// Creates a UUID n ms in the past.
171    ///
172    /// # Note
173    ///
174    /// For internal testing purposes only. For end-users, please use [`UUID::build()`]
175    fn build_n_ms_in_past(n_ms_in_past: u64) -> UUID {
176        let duration_since_unix_epoch = SystemTime::now()
177            .duration_since(UNIX_EPOCH)
178            .expect("current system time is set to a point in time before UNIX Epoch");
179        UUID::build_for_timestamp(
180            duration_since_unix_epoch.sub(Duration::from_millis(n_ms_in_past)),
181        )
182    }
183
184    #[test_case(None, None, false; "for message without ID nor TTL")]
185    #[test_case(None, Some(0), false; "for message without ID with TTL 0")]
186    #[test_case(None, Some(500), false; "for message without ID with TTL")]
187    #[test_case(Some(build_n_ms_in_past(1000)), None, false; "for message with ID without TTL")]
188    #[test_case(Some(build_n_ms_in_past(1000)), Some(0), false; "for message with ID and TTL 0")]
189    #[test_case(Some(build_n_ms_in_past(1000)), Some(500), true; "for message with ID and expired TTL")]
190    #[test_case(Some(build_n_ms_in_past(1000)), Some(2000), false; "for message with ID and non-expired TTL")]
191    fn test_is_expired(id: Option<UUID>, ttl: Option<u32>, should_be_expired: bool) {
192        let attributes = UAttributes {
193            type_: UMessageType::UMESSAGE_TYPE_NOTIFICATION.into(),
194            priority: UPriority::UPRIORITY_CS1.into(),
195            id: id.into(),
196            ttl,
197            ..Default::default()
198        };
199
200        assert!(attributes.check_expired().is_err() == should_be_expired);
201    }
202}