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 umessagetype;
16mod upayloadformat;
17mod upriority;
18
19use std::time::SystemTime;
20
21pub use uattributesvalidator::*;
22pub use umessagetype::UMessageType;
23pub use upayloadformat::*;
24pub use upriority::*;
25
26use crate::uuid::UuidConversionError;
27use crate::{UCode, UUri, UUriError, UUID};
28
29pub(crate) const UPRIORITY_DEFAULT: UPriority = UPriority::CS1;
30pub(crate) type TokenString = String;
31pub(crate) type TraceparentString = String;
32
33#[derive(Debug)]
34/// Errors produced when building or validating [`UAttributes`].
35#[non_exhaustive]
36pub enum UAttributesError {
37 /// The attributes violate a validation rule for their message type.
38 ValidationError(String),
39 /// The attributes could not be parsed from their serialized form.
40 ParsingError(String),
41}
42
43impl UAttributesError {
44 /// Creates a validation error with the given message.
45 pub fn validation_error<T>(message: T) -> UAttributesError
46 where
47 T: Into<String>,
48 {
49 Self::ValidationError(message.into())
50 }
51
52 /// Creates a parsing error with the given message.
53 pub fn parsing_error<T>(message: T) -> UAttributesError
54 where
55 T: Into<String>,
56 {
57 Self::ParsingError(message.into())
58 }
59}
60
61impl std::fmt::Display for UAttributesError {
62 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
63 match self {
64 Self::ValidationError(e) => f.write_fmt(format_args!("Validation failure: {e}")),
65 Self::ParsingError(e) => f.write_fmt(format_args!("Parsing error: {e}")),
66 }
67 }
68}
69
70impl std::error::Error for UAttributesError {}
71
72impl From<UUriError> for UAttributesError {
73 fn from(value: UUriError) -> Self {
74 UAttributesError::parsing_error(value.to_string())
75 }
76}
77
78impl From<UuidConversionError> for UAttributesError {
79 fn from(value: UuidConversionError) -> Self {
80 UAttributesError::parsing_error(value.to_string())
81 }
82}
83
84impl From<UPayloadError> for UAttributesError {
85 fn from(value: UPayloadError) -> Self {
86 UAttributesError::parsing_error(value.to_string())
87 }
88}
89
90#[derive(Debug, Clone, PartialEq)]
91#[repr(C)]
92/// Message metadata: addressing, identity, type, and delivery options for one [`UMessage`](crate::UMessage).
93pub struct UAttributes {
94 pub(crate) type_: UMessageType,
95 pub(crate) id: UUID,
96 pub(crate) source: UUri,
97 pub(crate) sink: Option<UUri>,
98 pub(crate) priority: Option<UPriority>,
99 pub(crate) commstatus: Option<UCode>,
100 pub(crate) ttl: Option<u32>,
101 pub(crate) permission_level: Option<u32>,
102 pub(crate) token: Option<TokenString>,
103 pub(crate) traceparent: Option<TraceparentString>,
104 pub(crate) reqid: Option<UUID>,
105 pub(crate) payload_format: Option<UPayloadFormat>,
106 pub(crate) payload_encoding_registry_id: Option<u32>,
107 pub(crate) payload_encoding: Option<String>,
108 pub(crate) payload_content_type: Option<String>,
109}
110
111impl UAttributes {
112 /// Gets the type of message these are the attributes of.
113 ///
114 /// # Example
115 ///
116 /// ```rust
117 /// use up_rust::{UMessageBuilder, UMessageType, UUri};
118 ///
119 /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
120 /// let topic = UUri::try_from("//my-vehicle/D45/23/A001")?;
121 /// let msg = UMessageBuilder::publish(topic).build()?;
122 /// let attribs = msg.attributes();
123 /// assert_eq!(attribs.type_(), UMessageType::Publish);
124 /// # Ok(())
125 /// # }
126 /// ```
127 #[must_use]
128 pub fn type_(&self) -> UMessageType {
129 self.type_
130 }
131
132 /// Gets the identifier of the message these attributes belong to.
133 ///
134 /// # Example
135 ///
136 /// ```rust
137 /// use up_rust::{UMessageBuilder, UMessageType, UUri, UUID};
138 ///
139 /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
140 /// let topic = UUri::try_from("//my-vehicle/D45/23/A001")?;
141 /// let msg_id = UUID::build();
142 /// let msg = UMessageBuilder::publish(topic).with_message_id(msg_id.clone()).build()?;
143 /// let attribs = msg.attributes();
144 /// assert_eq!(attribs.id(), &msg_id);
145 /// # Ok(())
146 /// # }
147 /// ```
148 #[must_use]
149 pub fn id(&self) -> &UUID {
150 &self.id
151 }
152
153 /// Gets the source address of the message these attributes belong to.
154 ///
155 /// # Example
156 ///
157 /// ```rust
158 /// use up_rust::{UMessageBuilder, UMessageType, UUri};
159 ///
160 /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
161 /// let topic = UUri::try_from("//my-vehicle/D45/23/A001")?;
162 /// let msg = UMessageBuilder::publish(topic.clone()).build()?;
163 /// let attribs = msg.attributes();
164 /// assert_eq!(attribs.source(), &topic);
165 /// # Ok(())
166 /// # }
167 /// ```
168 #[must_use]
169 pub fn source(&self) -> &UUri {
170 &self.source
171 }
172
173 /// Gets the sink address of the message these attributes belong to.
174 ///
175 /// # Example
176 ///
177 /// ```rust
178 /// use up_rust::{UMessageBuilder, UMessageType, UUri};
179 ///
180 /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
181 /// let origin = UUri::try_from("//my-vehicle/D45/23/A001")?;
182 /// let dest = UUri::try_from("//other-vehicle/D10/3/0")?;
183 /// let msg = UMessageBuilder::notification(origin, dest.clone()).build()?;
184 /// let attribs = msg.attributes();
185 /// assert_eq!(attribs.sink(), Some(&dest));
186 /// # Ok(())
187 /// # }
188 /// ```
189 #[must_use]
190 pub fn sink(&self) -> Option<&UUri> {
191 self.sink.as_ref()
192 }
193
194 /// Gets the sink address of the message these attributes belong to.
195 ///
196 /// # Panics
197 ///
198 /// if the property has no value.
199 ///
200 /// # Example
201 ///
202 /// ```rust
203 /// use up_rust::{UMessageBuilder, UMessageType, UUri};
204 ///
205 /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
206 /// let origin = UUri::try_from("//my-vehicle/D45/23/A001")?;
207 /// let dest = UUri::try_from("//other-vehicle/D10/3/0")?;
208 /// let msg = UMessageBuilder::notification(origin, dest.clone()).build()?;
209 /// let attribs = msg.attributes();
210 /// assert_eq!(attribs.sink_unchecked(), &dest);
211 /// # Ok(())
212 /// # }
213 /// ```
214 #[must_use]
215 pub fn sink_unchecked(&self) -> &UUri {
216 self.sink().expect("message has no sink")
217 }
218
219 /// Gets the priority of the message these attributes belong to.
220 ///
221 /// # Example
222 ///
223 /// ```rust
224 /// use up_rust::{UMessageBuilder, UMessageType, UPriority, UUri};
225 ///
226 /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
227 /// let topic = UUri::try_from("//my-vehicle/D45/23/A001")?;
228 /// let msg = UMessageBuilder::publish(topic).with_priority(UPriority::CS3).build()?;
229 /// let attribs = msg.attributes();
230 /// assert_eq!(attribs.priority(), Some(UPriority::CS3));
231 /// # Ok(())
232 /// # }
233 /// ```
234 #[must_use]
235 pub fn priority(&self) -> Option<UPriority> {
236 self.priority
237 }
238
239 /// Gets the priority of the message these attributes belong to.
240 ///
241 /// # Panics
242 ///
243 /// if the property has no value.
244 ///
245 /// # Example
246 ///
247 /// ```rust
248 /// use up_rust::{UMessageBuilder, UMessageType, UPriority, UUri};
249 ///
250 /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
251 /// let topic = UUri::try_from("//my-vehicle/D45/23/A001")?;
252 /// let msg = UMessageBuilder::publish(topic).with_priority(UPriority::CS3).build()?;
253 /// let attribs = msg.attributes();
254 /// assert_eq!(attribs.priority_unchecked(), UPriority::CS3);
255 /// # Ok(())
256 /// # }
257 /// ```
258 #[must_use]
259 pub fn priority_unchecked(&self) -> UPriority {
260 self.priority().expect("message has no priority")
261 }
262
263 /// Gets the commstatus of the message these attributes belong to.
264 ///
265 /// # Example
266 ///
267 /// ```rust
268 /// use up_rust::{UCode, UMessageBuilder, UMessageType, UUID, UUri};
269 ///
270 /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
271 /// let invoked_method = UUri::try_from("//my-vehicle/D45/2/101")?;
272 /// let reply_to = UUri::try_from("//other-vehicle/D10/3/0")?;
273 /// let msg = UMessageBuilder::response(reply_to, UUID::build(), invoked_method)
274 /// .with_comm_status(UCode::Ok)
275 /// .build()?;
276 /// let attribs = msg.attributes();
277 /// assert_eq!(attribs.commstatus(), Some(UCode::Ok));
278 /// # Ok(())
279 /// # }
280 /// ```
281 #[must_use]
282 pub fn commstatus(&self) -> Option<UCode> {
283 self.commstatus
284 }
285
286 /// Gets the commstatus of the message these attributes belong to.
287 ///
288 /// # Panics
289 ///
290 /// if the property has no value.
291 ///
292 /// # Example
293 ///
294 /// ```rust
295 /// use up_rust::{UCode, UMessageBuilder, UMessageType, UUID, UUri};
296 ///
297 /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
298 /// let invoked_method = UUri::try_from("//my-vehicle/D45/2/101")?;
299 /// let reply_to = UUri::try_from("//other-vehicle/D10/3/0")?;
300 /// let msg = UMessageBuilder::response(reply_to, UUID::build(), invoked_method)
301 /// .with_comm_status(UCode::Internal)
302 /// .build()?;
303 /// let attribs = msg.attributes();
304 /// assert_eq!(attribs.commstatus_unchecked(), UCode::Internal);
305 /// # Ok(())
306 /// # }
307 /// ```
308 #[must_use]
309 pub fn commstatus_unchecked(&self) -> UCode {
310 self.commstatus().expect("message has no commstatus")
311 }
312
313 /// Gets the time-to-live of the message these attributes belong to.
314 ///
315 /// # Returns
316 ///
317 /// the time-to-live in milliseconds.
318 ///
319 /// # Example
320 ///
321 /// ```rust
322 /// use up_rust::{UMessageBuilder, UUri};
323 ///
324 /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
325 /// let invoked_method = UUri::try_from("//my-vehicle/D45/2/101")?;
326 /// let reply_to = UUri::try_from("//other-vehicle/D10/3/0")?;
327 /// let msg = UMessageBuilder::request(invoked_method, reply_to, 5000)
328 /// .build()?;
329 /// let attribs = msg.attributes();
330 /// assert_eq!(attribs.ttl(), Some(5000));
331 /// # Ok(())
332 /// # }
333 /// ```
334 #[must_use]
335 pub fn ttl(&self) -> Option<u32> {
336 self.ttl
337 }
338
339 /// Gets the time-to-live of the message these attributes belong to.
340 ///
341 /// # Returns
342 ///
343 /// the time-to-live in milliseconds.
344 ///
345 /// # Panics
346 ///
347 /// if the property has no value.
348 ///
349 /// # Example
350 ///
351 /// ```rust
352 /// use up_rust::{UMessageBuilder, UUri};
353 ///
354 /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
355 /// let invoked_method = UUri::try_from("//my-vehicle/D45/2/101")?;
356 /// let reply_to = UUri::try_from("//other-vehicle/D10/3/0")?;
357 /// let msg = UMessageBuilder::request(invoked_method, reply_to, 5000)
358 /// .build()?;
359 /// let attribs = msg.attributes();
360 /// assert_eq!(attribs.ttl_unchecked(), 5000);
361 /// # Ok(())
362 /// # }
363 /// ```
364 #[must_use]
365 pub fn ttl_unchecked(&self) -> u32 {
366 self.ttl().expect("message has no time-to-live")
367 }
368
369 /// Gets the permission level of the message these attributes belong to.
370 ///
371 /// # Example
372 ///
373 /// ```rust
374 /// use up_rust::{UMessageBuilder, UUri};
375 ///
376 /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
377 /// let invoked_method = UUri::try_from("//my-vehicle/D45/2/101")?;
378 /// let reply_to = UUri::try_from("//other-vehicle/D10/3/0")?;
379 /// let msg = UMessageBuilder::request(invoked_method, reply_to, 5000)
380 /// .with_permission_level(3)
381 /// .build()?;
382 /// let attribs = msg.attributes();
383 /// assert_eq!(attribs.permission_level(), Some(3));
384 /// # Ok(())
385 /// # }
386 /// ```
387 #[must_use]
388 pub fn permission_level(&self) -> Option<u32> {
389 self.permission_level
390 }
391
392 /// Gets the token of the message these attributes belong to.
393 ///
394 /// # Example
395 ///
396 /// ```rust
397 /// use up_rust::{UMessageBuilder, UUri};
398 ///
399 /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
400 /// let token = "my_token";
401 /// let invoked_method = UUri::try_from("//my-vehicle/D45/2/101")?;
402 /// let reply_to = UUri::try_from("//other-vehicle/D10/3/0")?;
403 /// let msg = UMessageBuilder::request(invoked_method, reply_to, 5000)
404 /// .with_token(token)
405 /// .build()?;
406 /// let attribs = msg.attributes();
407 /// assert_eq!(attribs.token(), Some(token));
408 /// # Ok(())
409 /// # }
410 /// ```
411 #[must_use]
412 pub fn token(&self) -> Option<&str> {
413 self.token.as_deref()
414 }
415
416 /// Gets the traceparent of the message these attributes belong to.
417 ///
418 /// # Example
419 ///
420 /// ```rust
421 /// use up_rust::{UMessageBuilder, UUri};
422 ///
423 /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
424 /// let traceparent = "my_traceparent";
425 /// let invoked_method = UUri::try_from("//my-vehicle/D45/2/101")?;
426 /// let reply_to = UUri::try_from("//other-vehicle/D10/3/0")?;
427 /// let msg = UMessageBuilder::request(invoked_method, reply_to, 5000)
428 /// .with_traceparent(traceparent)
429 /// .build()?;
430 /// let attribs = msg.attributes();
431 /// assert_eq!(attribs.traceparent(), Some(traceparent));
432 /// # Ok(())
433 /// # }
434 /// ```
435 #[must_use]
436 pub fn traceparent(&self) -> Option<&str> {
437 self.traceparent.as_deref()
438 }
439
440 /// Gets the request identifier of the message these attributes belong to.
441 ///
442 /// # Example
443 ///
444 /// ```rust
445 /// use up_rust::{UCode, UMessageBuilder, UUID, UUri};
446 ///
447 /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
448 /// let request_id = UUID::build();
449 /// let invoked_method = UUri::try_from("//my-vehicle/D45/2/101")?;
450 /// let reply_to = UUri::try_from("//other-vehicle/D10/3/0")?;
451 /// let msg = UMessageBuilder::response(reply_to, request_id.clone(), invoked_method)
452 /// .build()?;
453 /// let attribs = msg.attributes();
454 /// assert_eq!(attribs.request_id(), Some(&request_id));
455 /// # Ok(())
456 /// # }
457 /// ```
458 #[must_use]
459 pub fn request_id(&self) -> Option<&UUID> {
460 self.reqid.as_ref()
461 }
462
463 /// Gets the request identifier of the message these attributes belong to.
464 ///
465 /// # Panics
466 ///
467 /// if the property has no value.
468 ///
469 /// # Example
470 ///
471 /// ```rust
472 /// use up_rust::{UCode, UMessageBuilder, UUID, UUri};
473 ///
474 /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
475 /// let request_id = UUID::build();
476 /// let invoked_method = UUri::try_from("//my-vehicle/D45/2/101")?;
477 /// let reply_to = UUri::try_from("//other-vehicle/D10/3/0")?;
478 /// let msg = UMessageBuilder::response(reply_to, request_id.clone(), invoked_method)
479 /// .build()?;
480 /// let attribs = msg.attributes();
481 /// assert_eq!(attribs.request_id_unchecked(), &request_id);
482 /// # Ok(())
483 /// # }
484 /// ```
485 #[must_use]
486 pub fn request_id_unchecked(&self) -> &UUID {
487 self.request_id().expect("message has no request ID")
488 }
489
490 /// Gets the payload format of the message these attributes belong to.
491 ///
492 /// # Example
493 ///
494 /// ```rust
495 /// use up_rust::{UMessageBuilder, UPayloadFormat, UUri};
496 ///
497 /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
498 /// let topic = UUri::try_from("//my-vehicle/D45/23/A001")?;
499 /// let msg = UMessageBuilder::publish(topic)
500 /// .build_with_payload("hello".as_bytes(), UPayloadFormat::Text)?;
501 /// let attribs = msg.attributes();
502 /// assert_eq!(attribs.payload_format(), Some(UPayloadFormat::Text));
503 /// # Ok(())
504 /// # }
505 /// ```
506 #[must_use]
507 pub fn payload_format(&self) -> Option<UPayloadFormat> {
508 self.payload_format
509 }
510
511 /// Gets the open payload-encoding identity components for encodings that
512 /// do not have a legacy [`UPayloadFormat`] equivalent.
513 #[must_use]
514 pub fn open_payload_encoding_parts(&self) -> (Option<u32>, Option<&str>, Option<&str>) {
515 (
516 self.payload_encoding_registry_id,
517 self.payload_encoding.as_deref(),
518 self.payload_content_type.as_deref(),
519 )
520 }
521
522 /// Gets the payload format of the message these attributes belong to.
523 ///
524 /// # Panics
525 ///
526 /// if the property has no value.
527 ///
528 /// # Example
529 ///
530 /// ```rust
531 /// use up_rust::{UMessageBuilder, UPayloadFormat, UUri};
532 ///
533 /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
534 /// let topic = UUri::try_from("//my-vehicle/D45/23/A001")?;
535 /// let msg = UMessageBuilder::publish(topic)
536 /// .build_with_payload("hello".as_bytes(), UPayloadFormat::Text)?;
537 /// let attribs = msg.attributes();
538 /// assert_eq!(attribs.payload_format_unchecked(), UPayloadFormat::Text);
539 /// # Ok(())
540 /// # }
541 /// ```
542 #[must_use]
543 pub fn payload_format_unchecked(&self) -> UPayloadFormat {
544 self.payload_format()
545 .expect("message has no payload format")
546 }
547
548 /// Checks if a given priority class is the default priority class.
549 ///
550 /// Messages that do not have a priority class set explicity, are assigned to
551 /// the default priority class.
552 pub(crate) fn is_default_priority(prio: UPriority) -> bool {
553 prio == UPRIORITY_DEFAULT
554 }
555
556 /// Checks if these are the attributes for a Publish message.
557 ///
558 /// # Examples
559 ///
560 /// ```rust
561 /// use up_rust::{UMessageBuilder, UUri};
562 ///
563 /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
564 /// let topic = UUri::try_from("//my-vehicle/D45/23/A001")?;
565 /// let msg = UMessageBuilder::publish(topic).build()?;
566 /// let attribs = msg.attributes();
567 /// assert!(attribs.is_publish());
568 /// # Ok(())
569 /// # }
570 /// ```
571 #[must_use]
572 pub fn is_publish(&self) -> bool {
573 self.type_ == UMessageType::Publish
574 }
575
576 /// Checks if these are the attributes for an RPC Request message.
577 ///
578 /// # Examples
579 ///
580 /// ```rust
581 /// use up_rust::{UMessageBuilder, UUri};
582 ///
583 /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
584 /// let invoked_method = UUri::try_from("//my-vehicle/D45/2/101")?;
585 /// let reply_to = UUri::try_from("//other-vehicle/D10/3/0")?;
586 /// let msg = UMessageBuilder::request(invoked_method, reply_to, 5000)
587 /// .build()?;
588 /// let attribs = msg.attributes();
589 /// assert!(attribs.is_request());
590 /// # Ok(())
591 /// # }
592 /// ```
593 #[must_use]
594 pub fn is_request(&self) -> bool {
595 self.type_ == UMessageType::Request
596 }
597
598 /// Checks if these are the attributes for an RPC Response message.
599 ///
600 /// # Examples
601 ///
602 /// ```rust
603 /// use up_rust::{UCode, UMessageBuilder, UUID, UUri};
604 ///
605 /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
606 /// let invoked_method = UUri::try_from("//my-vehicle/D45/2/101")?;
607 /// let reply_to = UUri::try_from("//other-vehicle/D10/3/0")?;
608 /// let msg = UMessageBuilder::response(reply_to, UUID::build(), invoked_method)
609 /// .build()?;
610 /// let attribs = msg.attributes();
611 /// assert!(attribs.is_response());
612 /// # Ok(())
613 /// # }
614 /// ```
615 #[must_use]
616 pub fn is_response(&self) -> bool {
617 self.type_ == UMessageType::Response
618 }
619
620 /// Checks if these are the attributes for a Notification message.
621 ///
622 /// # Examples
623 ///
624 /// ```rust
625 /// use up_rust::{UMessageBuilder, UUri};
626 ///
627 /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
628 /// let origin = UUri::try_from("//my-vehicle/D45/23/A001")?;
629 /// let dest = UUri::try_from("//other-vehicle/D10/3/0")?;
630 /// let msg = UMessageBuilder::notification(origin, dest).build()?;
631 /// let attribs = msg.attributes();
632 /// assert!(attribs.is_notification());
633 /// # Ok(())
634 /// # }
635 /// ```
636 #[must_use]
637 pub fn is_notification(&self) -> bool {
638 self.type_ == UMessageType::Notification
639 }
640
641 /// Checks if the message that is described by these attributes should be considered expired.
642 ///
643 /// # Errors
644 ///
645 /// Returns an error if [`Self::ttl`] (time-to-live) contains a value greater than 0, but
646 /// * the current system time cannot be determined, or
647 /// * the message has expired according to the timestamp extracted from [`Self::id`] and the time-to-live value.
648 pub fn check_expired(&self) -> Result<(), UAttributesError> {
649 if let Some(ttl) = self.ttl {
650 if ttl == 0 {
651 return Ok(());
652 }
653 }
654 SystemTime::now()
655 .duration_since(SystemTime::UNIX_EPOCH)
656 .map_err(|_e| {
657 UAttributesError::validation_error("Cannot determine current system time")
658 })
659 .and_then(|duration_since_epoch| {
660 self.check_expired_for_reference(duration_since_epoch.as_millis())
661 })
662 }
663
664 /// Checks if the message that is described by these attributes should be considered expired.
665 ///
666 /// # Arguments
667 /// * `reference_time` - The reference time as a `Duration` since UNIX epoch. The check will be performed in relation to this point in time.
668 ///
669 /// # Errors
670 ///
671 /// Returns an error if [`Self::ttl`] (time-to-live) contains a value greater than 0, but
672 /// the message has expired according to the timestamp extracted from [`Self::id`], the
673 /// time-to-live value and the provided reference time.
674 pub fn check_expired_for_reference(
675 &self,
676 reference_time: u128,
677 ) -> Result<(), UAttributesError> {
678 let ttl = match self.ttl {
679 Some(t) if t > 0 => u128::from(t),
680 _ => return Ok(()),
681 };
682
683 if (self.id.time() as u128).saturating_add(ttl) <= reference_time {
684 return Err(UAttributesError::validation_error("Message has expired"));
685 }
686 Ok(())
687 }
688}
689
690#[cfg(feature = "up-core-api")]
691mod core_types_support {
692 use protobuf::{well_known_types::any::Any, Message};
693
694 use super::*;
695 use crate::up_core_api::uattributes::{
696 UAttributes as UAttributesProto, UMessageType as UMessageTypeProto,
697 UPayloadFormat as UPayloadFormatProto, UPriority as UPriorityProto,
698 };
699 use crate::up_core_api::{
700 ucode::UCode as UCodeProto, uri::UUri as UUriProto, uuid::UUID as UUIDProto,
701 };
702 use crate::ProtobufMappable;
703
704 impl From<&UAttributes> for UAttributesProto {
705 fn from(attribs: &UAttributes) -> Self {
706 UAttributesProto {
707 id: Some(UUIDProto::from(&attribs.id)).into(),
708 type_: UMessageTypeProto::from(&attribs.type_).into(),
709 source: Some(UUriProto::from(&attribs.source)).into(),
710 sink: attribs.sink.as_ref().map(UUriProto::from).into(),
711 priority: attribs
712 .priority
713 .map_or(UPriorityProto::UPRIORITY_UNSPECIFIED, |p| {
714 UPriorityProto::from(&p)
715 })
716 .into(),
717 ttl: attribs.ttl,
718 permission_level: attribs.permission_level,
719 reqid: attribs.reqid.as_ref().map(UUIDProto::from).into(),
720 commstatus: attribs.commstatus.map(|cs| UCodeProto::from(cs).into()),
721 token: attribs.token().map(|t| t.to_string()),
722 traceparent: attribs.traceparent().map(|t| t.to_string()),
723 payload_format: attribs
724 .payload_format
725 .map_or(UPayloadFormatProto::UPAYLOAD_FORMAT_UNSPECIFIED, |pf| {
726 UPayloadFormatProto::from(&pf)
727 })
728 .into(),
729 payload_encoding_registry_id: attribs.payload_encoding_registry_id,
730 payload_encoding: attribs.payload_encoding.clone(),
731 payload_content_type: attribs.payload_content_type.clone(),
732 ..Default::default()
733 }
734 }
735 }
736
737 impl TryFrom<&UAttributesProto> for UAttributes {
738 type Error = UAttributesError;
739
740 fn try_from(attribs_proto: &UAttributesProto) -> Result<Self, Self::Error> {
741 Ok(UAttributes {
742 commstatus: match attribs_proto.commstatus {
743 None => None,
744 Some(cs) => cs
745 .enum_value()
746 .map_err(|e| {
747 UAttributesError::parsing_error(format!(
748 "unknown commstatus value: {e}"
749 ))
750 })
751 .map(UCode::from)
752 .map(Some)?,
753 },
754 id: UUID::try_from(
755 attribs_proto
756 .id
757 .as_ref()
758 .ok_or_else(|| UAttributesError::parsing_error("missing message ID"))?,
759 )?,
760 type_: attribs_proto
761 .type_
762 .enum_value()
763 .map_err(|e| {
764 UAttributesError::parsing_error(format!("unknown message type value: {e}"))
765 })
766 .and_then(UMessageType::try_from)?,
767 source: UUri::try_from(
768 attribs_proto
769 .source
770 .as_ref()
771 .ok_or_else(|| UAttributesError::parsing_error("missing source URI"))?,
772 )?,
773 sink: match attribs_proto.sink.as_ref() {
774 Some(s) => Some(UUri::try_from(s)?),
775 None => None,
776 },
777 priority: match attribs_proto.priority.enum_value() {
778 Ok(UPriorityProto::UPRIORITY_UNSPECIFIED) => None,
779 Ok(p) => Some(UPriority::try_from(p)?),
780 Err(e) => {
781 return Err(UAttributesError::parsing_error(format!(
782 "unknown priority value: {e}"
783 )))
784 }
785 },
786 ttl: attribs_proto.ttl,
787 permission_level: attribs_proto.permission_level,
788 token: attribs_proto.token.as_ref().map(|t| t.to_owned()),
789 traceparent: attribs_proto.traceparent.as_ref().map(|t| t.to_owned()),
790 reqid: match attribs_proto.reqid.as_ref() {
791 Some(r) => Some(UUID::try_from(r)?),
792 None => None,
793 },
794 payload_format: match attribs_proto.payload_format.enum_value() {
795 Ok(pf) => Some(UPayloadFormat::from(pf)),
796 Err(e) => {
797 return Err(UAttributesError::parsing_error(format!(
798 "unknown payload format value: {e}"
799 )))
800 }
801 },
802 payload_encoding_registry_id: attribs_proto.payload_encoding_registry_id,
803 payload_encoding: attribs_proto.payload_encoding.clone(),
804 payload_content_type: attribs_proto.payload_content_type.clone(),
805 })
806 }
807 }
808
809 impl ProtobufMappable for UAttributes {
810 fn parse_from_protobuf_bytes(proto: &[u8]) -> Result<Self, crate::SerializationError> {
811 let uattributes_proto = UAttributesProto::parse_from_bytes(proto)?;
812 UAttributes::try_from(&uattributes_proto).map_err(|e| {
813 crate::SerializationError::new(format!("UAttributes conversion error: {e}"))
814 })
815 }
816 fn parse_from_packed_protobuf_bytes(
817 proto: &[u8],
818 ) -> Result<Self, crate::SerializationError> {
819 Any::parse_from_bytes(proto)
820 .map_err(|err| crate::SerializationError::new(err.to_string()))
821 .and_then(|any| match any.unpack::<UAttributesProto>() {
822 Ok(Some(uattributes_proto)) => UAttributes::try_from(&uattributes_proto)
823 .map_err(|e| crate::SerializationError::new(e.to_string())),
824 Ok(None) => Err(crate::SerializationError::new(
825 "Protobuf Any does not contain UAttributes".to_string(),
826 )),
827 Err(e) => Err(crate::SerializationError::new(format!(
828 "Protobuf Any unpack error: {e}"
829 ))),
830 })
831 }
832 fn write_to_protobuf_bytes(&self) -> Result<Vec<u8>, crate::SerializationError> {
833 Ok(UAttributesProto::from(self).write_to_bytes()?)
834 }
835 fn write_to_packed_protobuf_bytes(&self) -> Result<Vec<u8>, crate::SerializationError> {
836 Any::pack(&UAttributesProto::from(self))
837 .map_err(|e| {
838 crate::SerializationError::new(format!("Failed to pack UAttributes: {e}"))
839 })
840 .and_then(|any| any.write_to_protobuf_bytes())
841 }
842 }
843
844 #[cfg(test)]
845 mod tests {
846 use super::*;
847 use protobuf::EnumOrUnknown;
848
849 #[test_case::test_case(|attribs| attribs => matches Ok(()); "succeeds for valid values")]
850 #[test_case::test_case(|mut attribs| { attribs.type_ = EnumOrUnknown::from_i32(-1); attribs } => matches Err(UAttributesError::ParsingError(_)); "fails for invalid message type")]
851 #[test_case::test_case(|mut attribs| { attribs.id = None.into(); attribs } => matches Err(UAttributesError::ParsingError(_)); "fails for missing message id")]
852 #[test_case::test_case(|mut attribs| { attribs.id.as_mut().unwrap().msb = 0x0000000000018000_u64; attribs } => matches Err(UAttributesError::ParsingError(_)); "fails for invalid message id")]
853 #[test_case::test_case(|mut attribs| { attribs.source = None.into(); attribs } => matches Err(UAttributesError::ParsingError(_)); "fails for missing source")]
854 #[test_case::test_case(|mut attribs| { attribs.source.as_mut().unwrap().authority_name = "INVALID".to_string(); attribs } => matches Err(UAttributesError::ParsingError(_)); "fails for invalid source")]
855 #[test_case::test_case(|mut attribs| { attribs.sink.as_mut().unwrap().authority_name = "INVALID".to_string(); attribs } => matches Err(UAttributesError::ParsingError(_)); "fails for invalid sink")]
856 #[test_case::test_case(|mut attribs| { attribs.priority = EnumOrUnknown::from_i32(-1); attribs } => matches Err(UAttributesError::ParsingError(_)); "fails for invalid priority")]
857 #[test_case::test_case(|mut attribs| { attribs.commstatus = Some(EnumOrUnknown::from_i32(-1)); attribs } => matches Err(UAttributesError::ParsingError(_)); "fails for invalid commstatus")]
858 #[test_case::test_case(|mut attribs| { attribs.reqid.as_mut().unwrap().msb = 0x0000000000018000_u64; attribs } => matches Err(UAttributesError::ParsingError(_)); "fails for invalid request id")]
859 #[test_case::test_case(|mut attribs| { attribs.payload_format = EnumOrUnknown::from_i32(-1); attribs } => matches Err(UAttributesError::ParsingError(_)); "fails for invalid payload format")]
860 fn test_try_from_attributes<F: FnOnce(UAttributesProto) -> UAttributesProto>(
861 mutator: F,
862 ) -> Result<(), UAttributesError> {
863 let valid_attribs_proto = UAttributesProto {
864 type_: EnumOrUnknown::from_i32(3), // corresponds to Response
865 id: Some(crate::up_core_api::uuid::UUID {
866 msb: 0x0000000000017000_u64,
867 lsb: 0x8010101010101a1a_u64,
868 ..Default::default()
869 })
870 .into(),
871 source: Some(crate::up_core_api::uri::UUri {
872 authority_name: "source".to_string(),
873 ue_id: 0x0001,
874 ue_version_major: 0x01,
875 resource_id: 0x0001,
876 ..Default::default()
877 })
878 .into(),
879 sink: Some(crate::up_core_api::uri::UUri {
880 ue_id: 0x1000,
881 ue_version_major: 0x01,
882 resource_id: 0x0000,
883 ..Default::default()
884 })
885 .into(),
886 priority: EnumOrUnknown::from_i32(4), // CS4
887 commstatus: Some(EnumOrUnknown::from_i32(5)), // NOT_FOUND
888 ttl: None,
889 permission_level: None,
890 token: None,
891 traceparent: None,
892 reqid: Some(crate::up_core_api::uuid::UUID {
893 msb: 0x0000000000017000_u64,
894 lsb: 0x8010101010101a1b_u64,
895 ..Default::default()
896 })
897 .into(),
898 payload_format: EnumOrUnknown::from_i32(3), // JSON
899 ..Default::default()
900 };
901 let attribs_proto = mutator(valid_attribs_proto);
902 UAttributes::try_from(&attribs_proto).map(|_| ())
903 }
904 }
905}
906
907#[cfg(test)]
908mod tests {
909 use std::time::UNIX_EPOCH;
910
911 use super::*;
912 use test_case::test_case;
913
914 /// Creates a UUID for a given creation time offset.
915 ///
916 /// # Note
917 ///
918 /// For internal testing purposes only. For end-users, please use [`UUID::build()`]
919 fn build_for_time_offset(offset_millis: i64) -> UUID {
920 let duration_since_unix_epoch = SystemTime::now()
921 .duration_since(UNIX_EPOCH)
922 .expect("current system time is set to a point in time before UNIX Epoch");
923 let now_as_millis_since_epoch: u64 = u64::try_from(duration_since_unix_epoch.as_millis())
924 .expect("current system time is too far in the future");
925 let creation_timestamp = now_as_millis_since_epoch
926 .checked_add_signed(offset_millis)
927 .unwrap();
928 UUID::build_for_timestamp_millis(creation_timestamp)
929 }
930
931 #[test_case(build_for_time_offset(-1000), None, false; "for past message without TTL")]
932 #[test_case(build_for_time_offset(-1000), Some(0), false; "for past message with TTL 0")]
933 #[test_case(build_for_time_offset(-1000), Some(500), true; "for past message with expired TTL")]
934 #[test_case(build_for_time_offset(-1000), Some(2000), false; "for past message with non-expired TTL")]
935 #[test_case(build_for_time_offset(1000), Some(2000), false; "for future message with TTL")]
936 #[test_case(build_for_time_offset(1000), None, false; "for future message without TTL")]
937 fn test_is_expired(id: UUID, ttl: Option<u32>, should_be_expired: bool) {
938 let attributes = UAttributes {
939 type_: UMessageType::Notification,
940 id,
941 ttl,
942 priority: None,
943 commstatus: None,
944 source: UUri::try_from_parts("source", 0x01, 0x02, 0x9000).unwrap(),
945 sink: None,
946 permission_level: None,
947 token: None,
948 traceparent: None,
949 reqid: None,
950 payload_format: None,
951 payload_encoding_registry_id: None,
952 payload_encoding: None,
953 payload_content_type: None,
954 };
955
956 assert!(attributes.check_expired().is_err() == should_be_expired);
957 }
958}