Preview of the proposed up-rust native-frame-model branch (up-rust b6b99c6d, up-spec f0e9b17) — not released documentation. branch · write-up

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}