1mod 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 pub(crate) fn is_default_priority(prio: UPriority) -> bool {
67 prio == UPRIORITY_DEFAULT
68 }
69
70 pub fn is_publish(&self) -> bool {
84 self.type_.enum_value() == Ok(UMessageType::UMESSAGE_TYPE_PUBLISH)
85 }
86
87 pub fn is_request(&self) -> bool {
101 self.type_.enum_value() == Ok(UMessageType::UMESSAGE_TYPE_REQUEST)
102 }
103
104 pub fn is_response(&self) -> bool {
118 self.type_.enum_value() == Ok(UMessageType::UMESSAGE_TYPE_RESPONSE)
119 }
120
121 pub fn is_notification(&self) -> bool {
135 self.type_.enum_value() == Ok(UMessageType::UMESSAGE_TYPE_NOTIFICATION)
136 }
137
138 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 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}