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 if let Some(ttl) = self.ttl {
147 if ttl == 0 {
148 return Ok(());
149 }
150 }
151 SystemTime::now()
152 .duration_since(SystemTime::UNIX_EPOCH)
153 .map_err(|_e| {
154 UAttributesError::validation_error("Cannot determine current system time")
155 })
156 .and_then(|duration_since_epoch| {
157 self.check_expired_for_reference(duration_since_epoch.as_millis())
158 })
159 }
160
161 pub fn check_expired_for_reference(
172 &self,
173 reference_time: u128,
174 ) -> Result<(), UAttributesError> {
175 let ttl = match self.ttl {
176 Some(t) if t > 0 => u128::from(t),
177 _ => return Ok(()),
178 };
179
180 if let Some(creation_time) = self.id.as_ref().and_then(UUID::get_time) {
181 if (creation_time as u128).saturating_add(ttl) <= reference_time {
182 return Err(UAttributesError::validation_error("Message has expired"));
183 }
184 }
185 Ok(())
186 }
187}
188
189#[cfg(test)]
190mod tests {
191 use std::time::UNIX_EPOCH;
192
193 use super::*;
194 use test_case::test_case;
195
196 fn build_for_time_offset(offset_millis: i64) -> UUID {
202 let duration_since_unix_epoch = SystemTime::now()
203 .duration_since(UNIX_EPOCH)
204 .expect("current system time is set to a point in time before UNIX Epoch");
205 let now_as_millis_since_epoch: u64 = u64::try_from(duration_since_unix_epoch.as_millis())
206 .expect("current system time is too far in the future");
207 let creation_timestamp = now_as_millis_since_epoch
208 .checked_add_signed(offset_millis)
209 .unwrap();
210 UUID::build_for_timestamp_millis(creation_timestamp)
211 }
212
213 #[test_case(None, None, false; "for message without ID nor TTL")]
214 #[test_case(None, Some(0), false; "for message without ID with TTL 0")]
215 #[test_case(None, Some(500), false; "for message without ID with TTL")]
216 #[test_case(Some(build_for_time_offset(-1000)), None, false; "for past message without TTL")]
217 #[test_case(Some(build_for_time_offset(-1000)), Some(0), false; "for past message with TTL 0")]
218 #[test_case(Some(build_for_time_offset(-1000)), Some(500), true; "for past message with expired TTL")]
219 #[test_case(Some(build_for_time_offset(-1000)), Some(2000), false; "for past message with non-expired TTL")]
220 #[test_case(Some(build_for_time_offset(1000)), Some(2000), false; "for future message with TTL")]
221 #[test_case(Some(build_for_time_offset(1000)), None, false; "for future message without TTL")]
222 fn test_is_expired(id: Option<UUID>, ttl: Option<u32>, should_be_expired: bool) {
223 let attributes = UAttributes {
224 type_: UMessageType::UMESSAGE_TYPE_NOTIFICATION.into(),
225 priority: UPriority::UPRIORITY_CS1.into(),
226 id: id.into(),
227 ttl,
228 ..Default::default()
229 };
230
231 assert!(attributes.check_expired().is_err() == should_be_expired);
232 }
233}