up_rust/uattributes/
uattributesvalidator.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
14use std::time::SystemTime;
15
16use protobuf::Enum;
17
18use crate::{UAttributes, UMessageType, UPriority, UUri, UUID};
19
20use crate::UAttributesError;
21
22/// `UAttributes` is the struct that defines the Payload. It serves as the configuration for various aspects
23/// like time to live, priority, security tokens, and more. Each variant of `UAttributes` defines a different
24/// type of message payload. The payload could represent a simple published payload with some state change,
25/// an RPC request payload, or an RPC response payload.
26///
27/// `UAttributesValidator` is a trait implemented by all validators for `UAttributes`. It provides functionality
28/// to help validate that a given `UAttributes` instance is correctly configured to define the Payload.
29pub trait UAttributesValidator: Send {
30    /// Checks if a given set of attributes complies with the rules specified for
31    /// the type of message they describe.
32    ///
33    /// # Errors
34    ///
35    /// Returns an error if the attributes are not consistent with the rules specified for the message type.
36    fn validate(&self, attributes: &UAttributes) -> Result<(), UAttributesError>;
37
38    /// Verifies that this validator is appropriate for a set of attributes.
39    ///
40    /// # Errors
41    ///
42    /// Returns an error if [`UAttributes::type_`] does not match the type returned by [`UAttributesValidator::message_type`].
43    fn validate_type(&self, attributes: &UAttributes) -> Result<(), UAttributesError> {
44        let expected_type = self.message_type();
45        match attributes.type_.enum_value() {
46            Ok(mt) if mt == expected_type => Ok(()),
47            Ok(mt) => Err(UAttributesError::validation_error(format!(
48                "Wrong Message Type [{}]",
49                mt.to_cloudevent_type()
50            ))),
51            Err(unknown_code) => Err(UAttributesError::validation_error(format!(
52                "Unknown Message Type code [{}]",
53                unknown_code
54            ))),
55        }
56    }
57
58    /// Verifies that a set of attributes contains a valid message ID.
59    ///
60    /// # Errors
61    ///
62    /// Returns an error if [`UAttributes::id`] does not contain a [valid uProtocol UUID](`UUID::is_uprotocol_uuid`).
63    fn validate_id(&self, attributes: &UAttributes) -> Result<(), UAttributesError> {
64        if attributes
65            .id
66            .as_ref()
67            .is_some_and(|id| id.is_uprotocol_uuid())
68        {
69            Ok(())
70        } else {
71            Err(UAttributesError::validation_error(
72                "Attributes must contain valid uProtocol UUID in id property",
73            ))
74        }
75    }
76
77    /// Returns the type of message that this validator can be used with.
78    fn message_type(&self) -> UMessageType;
79
80    /// Checks if the message that is described by these attributes should be considered expired.
81    ///
82    /// # Errors
83    ///
84    /// Returns an error if [`UAttributes::ttl`] (time-to-live) contains a value greater than 0, but
85    /// * the message has expired according to the timestamp extracted from [`UAttributes::id`] and the time-to-live value, or
86    /// * the current system time cannot be determined.
87    fn is_expired(&self, attributes: &UAttributes) -> Result<(), UAttributesError> {
88        let ttl = match attributes.ttl {
89            Some(t) if t > 0 => u64::from(t),
90            _ => return Ok(()),
91        };
92
93        if let Some(time) = attributes.id.as_ref().and_then(UUID::get_time) {
94            let delta = match SystemTime::now().duration_since(SystemTime::UNIX_EPOCH) {
95                Ok(duration) => {
96                    if let Ok(duration) = u64::try_from(duration.as_millis()) {
97                        duration - time
98                    } else {
99                        return Err(UAttributesError::validation_error("Invalid duration"));
100                    }
101                }
102                Err(e) => return Err(UAttributesError::validation_error(e.to_string())),
103            };
104            if delta >= ttl {
105                return Err(UAttributesError::validation_error("Payload is expired"));
106            }
107        }
108        Ok(())
109    }
110
111    /// Verifies that a set of attributes contains a valid source URI.
112    ///
113    /// # Errors
114    ///
115    /// If the [`UAttributes::source`] property does not contain a valid URI as required by the type of message, an error is returned.
116    fn validate_source(&self, attributes: &UAttributes) -> Result<(), UAttributesError>;
117
118    /// Verifies that a set of attributes contains a valid sink URI.
119    fn validate_sink(&self, attributes: &UAttributes) -> Result<(), UAttributesError>;
120}
121
122/// Verifies that a set of attributes contains a priority that is appropriate for an RPC request message.
123///
124/// # Errors
125///
126/// If [`UAttributes::priority`] contains a value that is less [`UPriority::UPRIORITY_CS4`].
127pub fn validate_rpc_priority(attributes: &UAttributes) -> Result<(), UAttributesError> {
128    attributes
129        .priority
130        .enum_value()
131        .map_err(|unknown_code| {
132            UAttributesError::ValidationError(format!(
133                "RPC message must have a valid priority [{}]",
134                unknown_code
135            ))
136        })
137        .and_then(|prio| {
138            if prio.value() < UPriority::UPRIORITY_CS4.value() {
139                Err(UAttributesError::ValidationError(
140                    "RPC message must have a priority of at least CS4".to_string(),
141                ))
142            } else {
143                Ok(())
144            }
145        })
146}
147
148/// Enum that hold the implementations of uattributesValidator according to type.
149pub enum UAttributesValidators {
150    Publish,
151    Notification,
152    Request,
153    Response,
154}
155
156impl UAttributesValidators {
157    /// Gets the validator corresponding to this enum value.
158    ///
159    /// # Examples
160    ///
161    /// ```rust
162    /// use up_rust::{UAttributes, UAttributesValidators, UMessageBuilder, UMessageType, UUID, UUri};
163    ///
164    /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
165    /// let topic = UUri::try_from("//my-vehicle/D45/23/A001")?;
166    /// let attributes = UAttributes {
167    ///    type_: UMessageType::UMESSAGE_TYPE_PUBLISH.into(),
168    ///    id: Some(UUID::build()).into(),
169    ///    source: Some(topic).into(),
170    ///    ..Default::default()
171    /// };
172    /// let validator = UAttributesValidators::Publish.validator();
173    /// assert!(validator.validate(&attributes).is_ok());
174    /// # Ok(())
175    /// # }
176    /// ```
177    pub fn validator(&self) -> Box<dyn UAttributesValidator> {
178        match self {
179            UAttributesValidators::Publish => Box::new(PublishValidator),
180            UAttributesValidators::Notification => Box::new(NotificationValidator),
181            UAttributesValidators::Request => Box::new(RequestValidator),
182            UAttributesValidators::Response => Box::new(ResponseValidator),
183        }
184    }
185
186    /// Gets a validator that can be used to check a given set of attributes.
187    ///
188    /// # Examples
189    ///
190    /// ```rust
191    /// use up_rust::{UAttributes, UAttributesValidators, UMessageBuilder, UMessageType, UUID, UUri};
192    ///
193    /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
194    /// let topic = UUri::try_from("//my-vehicle/D45/23/A001")?;
195    /// let attributes = UAttributes {
196    ///    type_: UMessageType::UMESSAGE_TYPE_PUBLISH.into(),
197    ///    id: Some(UUID::build()).into(),
198    ///    source: Some(topic).into(),
199    ///    ..Default::default()
200    /// };
201    /// let validator = UAttributesValidators::get_validator_for_attributes(&attributes);
202    /// assert!(validator.validate(&attributes).is_ok());
203    /// # Ok(())
204    /// # }
205    /// ```
206    pub fn get_validator_for_attributes(attributes: &UAttributes) -> Box<dyn UAttributesValidator> {
207        Self::get_validator(attributes.type_.enum_value_or_default())
208    }
209
210    /// Gets a validator that can be used to check attributes of a given type of message.
211    ///
212    /// # Examples
213    ///
214    /// ```rust
215    /// use up_rust::{UAttributes, UAttributesValidators, UMessageBuilder, UMessageType, UUID, UUri};
216    ///
217    /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
218    /// let topic = UUri::try_from("//my-vehicle/D45/23/A001")?;
219    /// let attributes = UAttributes {
220    ///    type_: UMessageType::UMESSAGE_TYPE_PUBLISH.into(),
221    ///    id: Some(UUID::build()).into(),
222    ///    source: Some(topic).into(),
223    ///    ..Default::default()
224    /// };
225    /// let validator = UAttributesValidators::get_validator(UMessageType::UMESSAGE_TYPE_PUBLISH);
226    /// assert!(validator.validate(&attributes).is_ok());
227    /// # Ok(())
228    /// # }
229    /// ```
230    pub fn get_validator(message_type: UMessageType) -> Box<dyn UAttributesValidator> {
231        match message_type {
232            UMessageType::UMESSAGE_TYPE_REQUEST => Box::new(RequestValidator),
233            UMessageType::UMESSAGE_TYPE_RESPONSE => Box::new(ResponseValidator),
234            UMessageType::UMESSAGE_TYPE_NOTIFICATION => Box::new(NotificationValidator),
235            _ => Box::new(PublishValidator),
236        }
237    }
238}
239
240/// Validates attributes describing a Publish message.
241pub struct PublishValidator;
242
243impl UAttributesValidator for PublishValidator {
244    fn message_type(&self) -> UMessageType {
245        UMessageType::UMESSAGE_TYPE_PUBLISH
246    }
247
248    /// Checks if a given set of attributes complies with the rules specified for
249    /// publish messages.
250    ///
251    /// # Errors
252    ///
253    /// Returns an error if any of the following checks fail for the given attributes:
254    ///
255    /// * [`UAttributesValidator::validate_type`]
256    /// * [`UAttributesValidator::validate_id`]
257    /// * [`UAttributesValidator::validate_source`]
258    /// * [`UAttributesValidator::validate_sink`]
259    fn validate(&self, attributes: &UAttributes) -> Result<(), UAttributesError> {
260        let error_message = vec![
261            self.validate_type(attributes),
262            self.validate_id(attributes),
263            self.validate_source(attributes),
264            self.validate_sink(attributes),
265        ]
266        .into_iter()
267        .filter_map(Result::err)
268        .map(|e| e.to_string())
269        .collect::<Vec<_>>()
270        .join("; ");
271
272        if error_message.is_empty() {
273            Ok(())
274        } else {
275            Err(UAttributesError::validation_error(error_message))
276        }
277    }
278
279    /// Verifies that attributes for a publish message contain a valid source URI.
280    ///
281    /// # Errors
282    ///
283    /// Returns an error
284    ///
285    /// * if the attributes do not contain a source URI, or
286    /// * if the source URI contains any wildcards, or
287    /// * if the source URI has a resource ID of 0.
288    fn validate_source(&self, attributes: &UAttributes) -> Result<(), UAttributesError> {
289        if let Some(source) = attributes.source.as_ref() {
290            source.verify_event().map_err(|e| {
291                UAttributesError::validation_error(format!("Invalid source URI: {}", e))
292            })
293        } else {
294            Err(UAttributesError::validation_error(
295                "Attributes for a publish message must contain a source URI",
296            ))
297        }
298    }
299
300    /// Verifies that attributes for a publish message do not contain a sink URI.
301    ///
302    /// # Errors
303    ///
304    /// If the [`UAttributes::sink`] property contains any URI, an error is returned.
305    fn validate_sink(&self, attributes: &UAttributes) -> Result<(), UAttributesError> {
306        if attributes.sink.as_ref().is_some() {
307            Err(UAttributesError::validation_error(
308                "Attributes for a publish message must not contain a sink URI",
309            ))
310        } else {
311            Ok(())
312        }
313    }
314}
315
316/// Validates attributes describing a Notification message.
317pub struct NotificationValidator;
318
319impl UAttributesValidator for NotificationValidator {
320    fn message_type(&self) -> UMessageType {
321        UMessageType::UMESSAGE_TYPE_NOTIFICATION
322    }
323
324    /// Checks if a given set of attributes complies with the rules specified for
325    /// notification messages.
326    ///
327    /// # Errors
328    ///
329    /// Returns an error if any of the following checks fail for the given attributes:
330    ///
331    /// * [`UAttributesValidator::validate_type`]
332    /// * [`UAttributesValidator::validate_id`]
333    /// * [`UAttributesValidator::validate_source`]
334    /// * [`UAttributesValidator::validate_sink`]
335    fn validate(&self, attributes: &UAttributes) -> Result<(), UAttributesError> {
336        let error_message = vec![
337            self.validate_type(attributes),
338            self.validate_id(attributes),
339            self.validate_source(attributes),
340            self.validate_sink(attributes),
341        ]
342        .into_iter()
343        .filter_map(Result::err)
344        .map(|e| e.to_string())
345        .collect::<Vec<_>>()
346        .join("; ");
347
348        if error_message.is_empty() {
349            Ok(())
350        } else {
351            Err(UAttributesError::validation_error(error_message))
352        }
353    }
354
355    /// Verifies that attributes for a notification message contain a source URI.
356    ///
357    /// # Errors
358    ///
359    /// Returns an error
360    ///
361    /// * if the attributes do not contain a source URI, or
362    /// * if the source URI is an RPC response URI, or
363    /// * if the source URI contains any wildcards.
364    fn validate_source(&self, attributes: &UAttributes) -> Result<(), UAttributesError> {
365        if let Some(source) = attributes.source.as_ref() {
366            if source.is_rpc_response() {
367                Err(UAttributesError::validation_error(
368                    "Origin must not be an RPC response URI",
369                ))
370            } else {
371                source.verify_no_wildcards().map_err(|e| {
372                    UAttributesError::validation_error(format!("Invalid source URI: {}", e))
373                })
374            }
375        } else {
376            Err(UAttributesError::validation_error(
377                "Attributes must contain a source URI",
378            ))
379        }
380    }
381
382    /// Verifies that attributes for a notification message contain a sink URI.
383    ///
384    /// # Errors
385    ///
386    /// Returns an error
387    ///
388    /// * if the attributes do not contain a sink URI, or
389    /// * if the sink URI's resource ID is != 0, or
390    /// * if the sink URI contains any wildcards.
391    fn validate_sink(&self, attributes: &UAttributes) -> Result<(), UAttributesError> {
392        if let Some(sink) = attributes.sink.as_ref() {
393            if !sink.is_notification_destination() {
394                Err(UAttributesError::validation_error(
395                    "Destination's resource ID must be 0",
396                ))
397            } else {
398                sink.verify_no_wildcards().map_err(|e| {
399                    UAttributesError::validation_error(format!("Invalid sink URI: {}", e))
400                })
401            }
402        } else {
403            Err(UAttributesError::validation_error(
404                "Attributes for a notification message must contain a sink URI",
405            ))
406        }
407    }
408}
409
410/// Validate `UAttributes` with type `UMessageType::Request`
411pub struct RequestValidator;
412
413impl RequestValidator {
414    /// Verifies that a set of attributes representing an RPC request contain a valid time-to-live.
415    ///
416    /// # Errors
417    ///
418    /// Returns an error if [`UAttributes::ttl`] (time-to-live) is empty or contains a value less than 1.
419    pub fn validate_ttl(&self, attributes: &UAttributes) -> Result<(), UAttributesError> {
420        match attributes.ttl {
421            Some(ttl) if ttl > 0 => Ok(()),
422            Some(invalid_ttl) => Err(UAttributesError::validation_error(format!(
423                "RPC request message's TTL must be a positive integer [{invalid_ttl}]"
424            ))),
425            None => Err(UAttributesError::validation_error(
426                "RPC request message must contain a TTL",
427            )),
428        }
429    }
430}
431
432impl UAttributesValidator for RequestValidator {
433    fn message_type(&self) -> UMessageType {
434        UMessageType::UMESSAGE_TYPE_REQUEST
435    }
436
437    /// Checks if a given set of attributes complies with the rules specified for
438    /// RPC request messages.
439    ///
440    /// # Errors
441    ///
442    /// Returns an error if any of the following checks fail for the given attributes:
443    ///
444    /// * [`UAttributesValidator::validate_type`]
445    /// * [`UAttributesValidator::validate_id`]
446    /// * [`RequestValidator::validate_ttl`]
447    /// * [`UAttributesValidator::validate_source`]
448    /// * [`UAttributesValidator::validate_sink`]
449    /// * `validate_rpc_priority`
450    fn validate(&self, attributes: &UAttributes) -> Result<(), UAttributesError> {
451        let error_message = vec![
452            self.validate_type(attributes),
453            self.validate_id(attributes),
454            self.validate_ttl(attributes),
455            self.validate_source(attributes),
456            self.validate_sink(attributes),
457            validate_rpc_priority(attributes),
458        ]
459        .into_iter()
460        .filter_map(Result::err)
461        .map(|e| e.to_string())
462        .collect::<Vec<_>>()
463        .join("; ");
464
465        if error_message.is_empty() {
466            Ok(())
467        } else {
468            Err(UAttributesError::validation_error(error_message))
469        }
470    }
471
472    /// Verifies that attributes for a message representing an RPC request contain a reply-to-address.
473    ///
474    /// # Errors
475    ///
476    /// Returns an error if the [`UAttributes::source`] property does not contain a valid reply-to-address according to
477    /// [`UUri::verify_rpc_response`].
478    fn validate_source(&self, attributes: &UAttributes) -> Result<(), UAttributesError> {
479        if let Some(source) = attributes.source.as_ref() {
480            UUri::verify_rpc_response(source).map_err(|e| {
481                UAttributesError::validation_error(format!("Invalid source URI: {}", e))
482            })
483        } else {
484            Err(UAttributesError::validation_error("Attributes for a request message must contain a reply-to address in the source property"))
485        }
486    }
487
488    /// Verifies that attributes for a message representing an RPC request indicate the method to invoke.
489    ///
490    /// # Errors
491    ///
492    /// Returns an erro if the [`UAttributes::sink`] property does not contain a URI representing a method according to
493    /// [`UUri::verify_rpc_method`].
494    fn validate_sink(&self, attributes: &UAttributes) -> Result<(), UAttributesError> {
495        if let Some(sink) = attributes.sink.as_ref() {
496            UUri::verify_rpc_method(sink)
497                .map_err(|e| UAttributesError::validation_error(format!("Invalid sink URI: {}", e)))
498        } else {
499            Err(UAttributesError::validation_error("Attributes for a request message must contain a method-to-invoke in the sink property"))
500        }
501    }
502}
503
504/// Validate `UAttributes` with type `UMessageType::Response`
505pub struct ResponseValidator;
506
507impl ResponseValidator {
508    /// Verifies that the attributes contain a valid request ID.
509    ///
510    /// # Errors
511    ///
512    /// Returns an error if [`UAttributes::reqid`] is empty or contains a value which is not
513    /// a [valid uProtocol UUID](`UUID::is_uprotocol_uuid`).
514    pub fn validate_reqid(&self, attributes: &UAttributes) -> Result<(), UAttributesError> {
515        if !attributes
516            .reqid
517            .as_ref()
518            .is_some_and(|id| id.is_uprotocol_uuid())
519        {
520            Err(UAttributesError::validation_error(
521                "Request ID is not a valid uProtocol UUID",
522            ))
523        } else {
524            Ok(())
525        }
526    }
527
528    /// Verifies that a set of attributes contains a valid communication status.
529    ///
530    /// # Errors
531    ///
532    /// Returns an error if [`UAttributes::commstatus`] does not contain a value that is a `UCode`.
533    pub fn validate_commstatus(&self, attributes: &UAttributes) -> Result<(), UAttributesError> {
534        if let Some(status) = attributes.commstatus {
535            match status.enum_value() {
536                Ok(_) => {
537                    return Ok(());
538                }
539                Err(e) => {
540                    return Err(UAttributesError::validation_error(format!(
541                        "Invalid Communication Status code: {e}"
542                    )));
543                }
544            }
545        }
546        Ok(())
547    }
548}
549
550impl UAttributesValidator for ResponseValidator {
551    fn message_type(&self) -> UMessageType {
552        UMessageType::UMESSAGE_TYPE_RESPONSE
553    }
554
555    /// Checks if a given set of attributes complies with the rules specified for
556    /// RPC response messages.
557    ///
558    /// # Errors
559    ///
560    /// Returns an error if any of the following checks fail for the given attributes:
561    ///
562    /// * [`UAttributesValidator::validate_type`]
563    /// * [`UAttributesValidator::validate_id`]
564    /// * [`UAttributesValidator::validate_source`]
565    /// * [`UAttributesValidator::validate_sink`]
566    /// * [`ResponseValidator::validate_reqid`]
567    /// * [`ResponseValidator::validate_commstatus`]
568    /// * `validate_rpc_priority`
569    fn validate(&self, attributes: &UAttributes) -> Result<(), UAttributesError> {
570        let error_message = vec![
571            self.validate_type(attributes),
572            self.validate_id(attributes),
573            self.validate_source(attributes),
574            self.validate_sink(attributes),
575            self.validate_reqid(attributes),
576            self.validate_commstatus(attributes),
577            validate_rpc_priority(attributes),
578        ]
579        .into_iter()
580        .filter_map(Result::err)
581        .map(|e| e.to_string())
582        .collect::<Vec<_>>()
583        .join("; ");
584
585        if error_message.is_empty() {
586            Ok(())
587        } else {
588            Err(UAttributesError::validation_error(error_message))
589        }
590    }
591
592    /// Verifies that attributes for a message representing an RPC response indicate the method that has
593    /// been invoked.
594    ///  
595    /// # Errors
596    ///
597    /// Returns an error if the [`UAttributes::source`] property does not contain a URI representing a method according to
598    /// [`UUri::verify_rpc_method`].
599    fn validate_source(&self, attributes: &UAttributes) -> Result<(), UAttributesError> {
600        if let Some(source) = attributes.source.as_ref() {
601            UUri::verify_rpc_method(source).map_err(|e| {
602                UAttributesError::validation_error(format!("Invalid source URI: {}", e))
603            })
604        } else {
605            Err(UAttributesError::validation_error("Missing Source"))
606        }
607    }
608
609    /// Verifies that attributes for a message representing an RPC response contain a valid
610    /// reply-to-address.
611    ///
612    /// # Errors
613    ///
614    /// Returns an error if the [`UAttributes::sink`] property does not contain a valid reply-to-address according to
615    /// [`UUri::verify_rpc_response`].
616    fn validate_sink(&self, attributes: &UAttributes) -> Result<(), UAttributesError> {
617        if let Some(sink) = &attributes.sink.as_ref() {
618            UUri::verify_rpc_response(sink)
619                .map_err(|e| UAttributesError::validation_error(format!("Invalid sink URI: {}", e)))
620        } else {
621            Err(UAttributesError::validation_error("Missing Sink"))
622        }
623    }
624}
625
626#[cfg(test)]
627mod tests {
628    use std::{
629        ops::Sub,
630        time::{Duration, UNIX_EPOCH},
631    };
632
633    use protobuf::EnumOrUnknown;
634    use test_case::test_case;
635
636    use super::*;
637    use crate::{UCode, UPriority, UUri, UUID};
638
639    /// Creates a UUID n ms in the past.
640    ///
641    /// # Note
642    ///
643    /// For internal testing purposes only. For end-users, please use [`UUID::build()`]
644    fn build_n_ms_in_past(n_ms_in_past: u64) -> UUID {
645        let duration_since_unix_epoch = SystemTime::now()
646            .duration_since(UNIX_EPOCH)
647            .expect("current system time is set to a point in time before UNIX Epoch");
648        UUID::build_for_timestamp(
649            duration_since_unix_epoch.sub(Duration::from_millis(n_ms_in_past)),
650        )
651    }
652
653    #[test]
654    fn test_validate_type_fails_for_unknown_type_code() {
655        let attributes = UAttributes {
656            type_: EnumOrUnknown::from_i32(20),
657            ..Default::default()
658        };
659        assert!(UAttributesValidators::Publish
660            .validator()
661            .validate_type(&attributes)
662            .is_err());
663        assert!(UAttributesValidators::Notification
664            .validator()
665            .validate_type(&attributes)
666            .is_err());
667        assert!(UAttributesValidators::Request
668            .validator()
669            .validate_type(&attributes)
670            .is_err());
671        assert!(UAttributesValidators::Response
672            .validator()
673            .validate_type(&attributes)
674            .is_err());
675    }
676
677    #[test_case(UMessageType::UMESSAGE_TYPE_UNSPECIFIED, UMessageType::UMESSAGE_TYPE_PUBLISH; "succeeds for Unspecified message")]
678    #[test_case(UMessageType::UMESSAGE_TYPE_PUBLISH, UMessageType::UMESSAGE_TYPE_PUBLISH; "succeeds for Publish message")]
679    #[test_case(UMessageType::UMESSAGE_TYPE_NOTIFICATION, UMessageType::UMESSAGE_TYPE_NOTIFICATION; "succeeds for Notification message")]
680    #[test_case(UMessageType::UMESSAGE_TYPE_REQUEST, UMessageType::UMESSAGE_TYPE_REQUEST; "succeeds for Request message")]
681    #[test_case(UMessageType::UMESSAGE_TYPE_RESPONSE, UMessageType::UMESSAGE_TYPE_RESPONSE; "succeeds for Response message")]
682    fn test_get_validator_returns_matching_validator(
683        message_type: UMessageType,
684        expected_validator_type: UMessageType,
685    ) {
686        let validator: Box<dyn UAttributesValidator> =
687            UAttributesValidators::get_validator(message_type);
688        assert_eq!(validator.message_type(), expected_validator_type);
689    }
690
691    #[test_case(UMessageType::UMESSAGE_TYPE_PUBLISH, None, None, false; "for Publish message without ID nor TTL")]
692    #[test_case(UMessageType::UMESSAGE_TYPE_PUBLISH, None, Some(0), false; "for Publish message without ID with TTL 0")]
693    #[test_case(UMessageType::UMESSAGE_TYPE_PUBLISH, None, Some(500), false; "for Publish message without ID with TTL")]
694    #[test_case(UMessageType::UMESSAGE_TYPE_PUBLISH, Some(build_n_ms_in_past(1000)), None, false; "for Publish message with ID without TTL")]
695    #[test_case(UMessageType::UMESSAGE_TYPE_PUBLISH, Some(build_n_ms_in_past(1000)), Some(0), false; "for Publish message with ID and TTL 0")]
696    #[test_case(UMessageType::UMESSAGE_TYPE_PUBLISH, Some(build_n_ms_in_past(1000)), Some(500), true; "for Publish message with ID and expired TTL")]
697    #[test_case(UMessageType::UMESSAGE_TYPE_PUBLISH, Some(build_n_ms_in_past(1000)), Some(2000), false; "for Publish message with ID and non-expired TTL")]
698    #[test_case(UMessageType::UMESSAGE_TYPE_NOTIFICATION, None, None, false; "for Notification message without ID nor TTL")]
699    #[test_case(UMessageType::UMESSAGE_TYPE_NOTIFICATION, None, Some(0), false; "for Notification message without ID with TTL 0")]
700    #[test_case(UMessageType::UMESSAGE_TYPE_NOTIFICATION, None, Some(500), false; "for Notification message without ID with TTL")]
701    #[test_case(UMessageType::UMESSAGE_TYPE_NOTIFICATION, Some(build_n_ms_in_past(1000)), None, false; "for Notification message with ID without TTL")]
702    #[test_case(UMessageType::UMESSAGE_TYPE_NOTIFICATION, Some(build_n_ms_in_past(1000)), Some(0), false; "for Notification message with ID and TTL 0")]
703    #[test_case(UMessageType::UMESSAGE_TYPE_NOTIFICATION, Some(build_n_ms_in_past(1000)), Some(500), true; "for Notification message with ID and expired TTL")]
704    #[test_case(UMessageType::UMESSAGE_TYPE_NOTIFICATION, Some(build_n_ms_in_past(1000)), Some(2000), false; "for Notification message with ID and non-expired TTL")]
705    #[test_case(UMessageType::UMESSAGE_TYPE_REQUEST, None, None, false; "for Request message without ID nor TTL")]
706    #[test_case(UMessageType::UMESSAGE_TYPE_REQUEST, None, Some(0), false; "for Request message without ID with TTL 0")]
707    #[test_case(UMessageType::UMESSAGE_TYPE_REQUEST, None, Some(500), false; "for Request message without ID with TTL")]
708    #[test_case(UMessageType::UMESSAGE_TYPE_REQUEST, Some(build_n_ms_in_past(1000)), None, false; "for Request message with ID without TTL")]
709    #[test_case(UMessageType::UMESSAGE_TYPE_REQUEST, Some(build_n_ms_in_past(1000)), Some(0), false; "for Request message with ID and TTL 0")]
710    #[test_case(UMessageType::UMESSAGE_TYPE_REQUEST, Some(build_n_ms_in_past(1000)), Some(500), true; "for Request message with ID and expired TTL")]
711    #[test_case(UMessageType::UMESSAGE_TYPE_REQUEST, Some(build_n_ms_in_past(1000)), Some(2000), false; "for Request message with ID and non-expired TTL")]
712    #[test_case(UMessageType::UMESSAGE_TYPE_RESPONSE, None, None, false; "for Response message without ID nor TTL")]
713    #[test_case(UMessageType::UMESSAGE_TYPE_RESPONSE, None, Some(0), false; "for Response message without ID with TTL 0")]
714    #[test_case(UMessageType::UMESSAGE_TYPE_RESPONSE, None, Some(500), false; "for Response message without ID with TTL")]
715    #[test_case(UMessageType::UMESSAGE_TYPE_RESPONSE, Some(build_n_ms_in_past(1000)), None, false; "for Response message with ID without TTL")]
716    #[test_case(UMessageType::UMESSAGE_TYPE_RESPONSE, Some(build_n_ms_in_past(1000)), Some(0), false; "for Response message with ID and TTL 0")]
717    #[test_case(UMessageType::UMESSAGE_TYPE_RESPONSE, Some(build_n_ms_in_past(1000)), Some(500), true; "for Response message with ID and expired TTL")]
718    #[test_case(UMessageType::UMESSAGE_TYPE_RESPONSE, Some(build_n_ms_in_past(1000)), Some(2000), false; "for Response message with ID and non-expired TTL")]
719    fn test_is_expired(
720        message_type: UMessageType,
721        id: Option<UUID>,
722        ttl: Option<u32>,
723        should_be_expired: bool,
724    ) {
725        let attributes = UAttributes {
726            type_: message_type.into(),
727            priority: UPriority::UPRIORITY_CS1.into(),
728            id: id.into(),
729            ttl,
730            ..Default::default()
731        };
732
733        let validator = UAttributesValidators::get_validator(message_type);
734        assert!(validator.is_expired(&attributes).is_err() == should_be_expired);
735    }
736
737    #[test_case(Some(UUID::build()), Some(publish_topic()), None, None, true; "succeeds for topic only")]
738    #[test_case(Some(UUID::build()), Some(publish_topic()), Some(destination()), None, false; "fails for message containing destination")]
739    #[test_case(Some(UUID::build()), Some(publish_topic()), None, Some(100), true; "succeeds for valid attributes")]
740    #[test_case(Some(UUID::build()), None, None, None, false; "fails for missing topic")]
741    #[test_case(Some(UUID::build()), Some(UUri { resource_id: 0x54, ..Default::default()}), None, None, false; "fails for invalid topic")]
742    #[test_case(None, Some(publish_topic()), None, None, false; "fails for missing message ID")]
743    #[test_case(
744        Some(UUID {
745            // invalid UUID version (not 0b1000 but 0b1010)
746            msb: 0x000000000001C000u64,
747            lsb: 0x8000000000000000u64,
748            ..Default::default()
749        }),
750        Some(publish_topic()),
751        None,
752        None,
753        false;
754        "fails for invalid message id")]
755    fn test_validate_attributes_for_publish_message(
756        id: Option<UUID>,
757        source: Option<UUri>,
758        sink: Option<UUri>,
759        ttl: Option<u32>,
760        expected_result: bool,
761    ) {
762        let attributes = UAttributes {
763            type_: UMessageType::UMESSAGE_TYPE_PUBLISH.into(),
764            id: id.into(),
765            priority: UPriority::UPRIORITY_CS1.into(),
766            source: source.into(),
767            sink: sink.into(),
768            ttl,
769            ..Default::default()
770        };
771        let validator = UAttributesValidators::Publish.validator();
772        let status = validator.validate(&attributes);
773        assert!(status.is_ok() == expected_result);
774        if status.is_ok() {
775            assert!(UAttributesValidators::Notification
776                .validator()
777                .validate(&attributes)
778                .is_err());
779            assert!(UAttributesValidators::Request
780                .validator()
781                .validate(&attributes)
782                .is_err());
783            assert!(UAttributesValidators::Response
784                .validator()
785                .validate(&attributes)
786                .is_err());
787        }
788    }
789
790    #[test_case(Some(UUID::build()), Some(origin()), None, None, false; "fails for missing destination")]
791    #[test_case(Some(UUID::build()), Some(origin()), Some(destination()), None, true; "succeeds for both origin and destination")]
792    #[test_case(Some(UUID::build()), Some(origin()), Some(destination()), Some(100), true; "succeeds for valid attributes")]
793    #[test_case(Some(UUID::build()), None, Some(destination()), None, false; "fails for missing origin")]
794    #[test_case(Some(UUID::build()), Some(UUri::default()), Some(destination()), None, false; "fails for invalid origin")]
795    #[test_case(Some(UUID::build()), Some(origin()), Some(UUri { ue_id: 0xabcd, ue_version_major: 0x01, resource_id: 0x0011, ..Default::default() }), None, false; "fails for invalid destination")]
796    #[test_case(Some(UUID::build()), None, None, None, false; "fails for neither origin nor destination")]
797    #[test_case(None, Some(origin()), Some(destination()), None, false; "fails for missing message ID")]
798    #[test_case(
799        Some(UUID {
800            // invalid UUID version (not 0b1000 but 0b1010)
801            msb: 0x000000000001C000u64,
802            lsb: 0x8000000000000000u64,
803            ..Default::default()
804        }),
805        Some(origin()),
806        Some(destination()),
807        None,
808        false;
809        "fails for invalid message id")]
810    fn test_validate_attributes_for_notification_message(
811        id: Option<UUID>,
812        source: Option<UUri>,
813        sink: Option<UUri>,
814        ttl: Option<u32>,
815        expected_result: bool,
816    ) {
817        let attributes = UAttributes {
818            type_: UMessageType::UMESSAGE_TYPE_NOTIFICATION.into(),
819            id: id.into(),
820            priority: UPriority::UPRIORITY_CS1.into(),
821            source: source.into(),
822            sink: sink.into(),
823            ttl,
824            ..Default::default()
825        };
826        let validator = UAttributesValidators::Notification.validator();
827        let status = validator.validate(&attributes);
828        assert!(status.is_ok() == expected_result);
829        if status.is_ok() {
830            assert!(UAttributesValidators::Publish
831                .validator()
832                .validate(&attributes)
833                .is_err());
834            assert!(UAttributesValidators::Request
835                .validator()
836                .validate(&attributes)
837                .is_err());
838            assert!(UAttributesValidators::Response
839                .validator()
840                .validate(&attributes)
841                .is_err());
842        }
843    }
844
845    #[test_case(Some(UUID::build()), Some(method_to_invoke()), Some(reply_to_address()), None, Some(2000), Some(UPriority::UPRIORITY_CS4), None, true; "succeeds for mandatory attributes")]
846    #[test_case(Some(UUID::build()), Some(method_to_invoke()), Some(reply_to_address()), Some(1), Some(2000), Some(UPriority::UPRIORITY_CS4), Some(String::from("token")), true; "succeeds for valid attributes")]
847    #[test_case(None, Some(method_to_invoke()), Some(reply_to_address()), Some(1), Some(2000), Some(UPriority::UPRIORITY_CS4), Some(String::from("token")), false; "fails for missing message ID")]
848    #[test_case(
849        Some(UUID {
850            // invalid UUID version (not 0b1000 but 0b1010)
851            msb: 0x000000000001C000u64,
852            lsb: 0x8000000000000000u64,
853            ..Default::default()
854        }),
855        Some(method_to_invoke()),
856        Some(reply_to_address()),
857        None,
858        Some(2000),
859        Some(UPriority::UPRIORITY_CS4),
860        None,
861        false;
862        "fails for invalid message id")]
863    #[test_case(Some(UUID::build()), Some(method_to_invoke()), None, None, Some(2000), Some(UPriority::UPRIORITY_CS4), None, false; "fails for missing reply-to-address")]
864    #[test_case(Some(UUID::build()), Some(method_to_invoke()), Some(UUri { resource_id: 0x0001, ..Default::default()}), None, Some(2000), Some(UPriority::UPRIORITY_CS4), None, false; "fails for invalid reply-to-address")]
865    #[test_case(Some(UUID::build()), None, Some(reply_to_address()), None, Some(2000), Some(UPriority::UPRIORITY_CS4), None, false; "fails for missing method-to-invoke")]
866    #[test_case(Some(UUID::build()), Some(UUri::default()), Some(reply_to_address()), None, Some(2000), Some(UPriority::UPRIORITY_CS4), None, false; "fails for invalid method-to-invoke")]
867    #[test_case(Some(UUID::build()), Some(method_to_invoke()), Some(reply_to_address()), Some(1), Some(2000), None, None, false; "fails for missing priority")]
868    #[test_case(Some(UUID::build()), Some(method_to_invoke()), Some(reply_to_address()), Some(1), Some(2000), Some(UPriority::UPRIORITY_CS3), None, false; "fails for invalid priority")]
869    #[test_case(Some(UUID::build()), Some(method_to_invoke()), Some(reply_to_address()), None, None, Some(UPriority::UPRIORITY_CS4), None, false; "fails for missing ttl")]
870    #[test_case(Some(UUID::build()), Some(method_to_invoke()), Some(reply_to_address()), None, Some(0), Some(UPriority::UPRIORITY_CS4), None, false; "fails for ttl < 1")]
871    #[test_case(Some(UUID::build()), Some(method_to_invoke()), Some(reply_to_address()), Some(1), Some(2000), Some(UPriority::UPRIORITY_CS4), None, true; "succeeds for valid permission level")]
872    #[allow(clippy::too_many_arguments)]
873    fn test_validate_attributes_for_rpc_request_message(
874        id: Option<UUID>,
875        method_to_invoke: Option<UUri>,
876        reply_to_address: Option<UUri>,
877        perm_level: Option<u32>,
878        ttl: Option<u32>,
879        priority: Option<UPriority>,
880        token: Option<String>,
881        expected_result: bool,
882    ) {
883        let attributes = UAttributes {
884            type_: UMessageType::UMESSAGE_TYPE_REQUEST.into(),
885            id: id.into(),
886            priority: priority.unwrap_or(UPriority::UPRIORITY_UNSPECIFIED).into(),
887            source: reply_to_address.into(),
888            sink: method_to_invoke.into(),
889            permission_level: perm_level,
890            ttl,
891            token,
892            ..Default::default()
893        };
894        let status = UAttributesValidators::Request
895            .validator()
896            .validate(&attributes);
897        assert!(status.is_ok() == expected_result);
898        if status.is_ok() {
899            assert!(UAttributesValidators::Publish
900                .validator()
901                .validate(&attributes)
902                .is_err());
903            assert!(UAttributesValidators::Notification
904                .validator()
905                .validate(&attributes)
906                .is_err());
907            assert!(UAttributesValidators::Response
908                .validator()
909                .validate(&attributes)
910                .is_err());
911        }
912    }
913
914    #[test_case(Some(UUID::build()), Some(reply_to_address()), Some(method_to_invoke()), Some(UUID::build()), None, None, Some(UPriority::UPRIORITY_CS4), true; "succeeds for mandatory attributes")]
915    #[test_case(Some(UUID::build()), Some(reply_to_address()), Some(method_to_invoke()), Some(UUID::build()), Some(EnumOrUnknown::from(UCode::CANCELLED)), Some(100), Some(UPriority::UPRIORITY_CS4), true; "succeeds for valid attributes")]
916    #[test_case(None, Some(reply_to_address()), Some(method_to_invoke()), Some(UUID::build()), Some(EnumOrUnknown::from(UCode::CANCELLED)), Some(100), Some(UPriority::UPRIORITY_CS4), false; "fails for missing message ID")]
917    #[test_case(
918        Some(UUID {
919            // invalid UUID version (not 0b1000 but 0b1010)
920            msb: 0x000000000001C000u64,
921            lsb: 0x8000000000000000u64,
922            ..Default::default()
923        }),
924        Some(reply_to_address()),
925        Some(method_to_invoke()),
926        Some(UUID::build()),
927        None,
928        None,
929        Some(UPriority::UPRIORITY_CS4),
930        false;
931        "fails for invalid message id")]
932    #[test_case(Some(UUID::build()), None, Some(method_to_invoke()), Some(UUID::build()), None, None, Some(UPriority::UPRIORITY_CS4), false; "fails for missing reply-to-address")]
933    #[test_case(Some(UUID::build()), Some(UUri { resource_id: 0x0001, ..Default::default()}), Some(method_to_invoke()), Some(UUID::build()), None, None, Some(UPriority::UPRIORITY_CS4), false; "fails for invalid reply-to-address")]
934    #[test_case(Some(UUID::build()), Some(reply_to_address()), None, Some(UUID::build()), None, None, Some(UPriority::UPRIORITY_CS4), false; "fails for missing invoked-method")]
935    #[test_case(Some(UUID::build()), Some(reply_to_address()), Some(UUri::default()), Some(UUID::build()), None, None, Some(UPriority::UPRIORITY_CS4), false; "fails for invalid invoked-method")]
936    #[test_case(Some(UUID::build()), Some(reply_to_address()), Some(method_to_invoke()), Some(UUID::build()), Some(EnumOrUnknown::from(UCode::CANCELLED)), None, Some(UPriority::UPRIORITY_CS4), true; "succeeds for valid commstatus")]
937    #[test_case(Some(UUID::build()), Some(reply_to_address()), Some(method_to_invoke()), Some(UUID::build()), Some(EnumOrUnknown::from_i32(-42)), None, Some(UPriority::UPRIORITY_CS4), false; "fails for invalid commstatus")]
938    #[test_case(Some(UUID::build()), Some(reply_to_address()), Some(method_to_invoke()), Some(UUID::build()), None, Some(100), Some(UPriority::UPRIORITY_CS4), true; "succeeds for ttl > 0)")]
939    #[test_case(Some(UUID::build()), Some(reply_to_address()), Some(method_to_invoke()), Some(UUID::build()), None, Some(0), Some(UPriority::UPRIORITY_CS4), true; "succeeds for ttl = 0")]
940    #[test_case(Some(UUID::build()), Some(reply_to_address()), Some(method_to_invoke()), Some(UUID::build()), Some(EnumOrUnknown::from(UCode::CANCELLED)), Some(100), None, false; "fails for missing priority")]
941    #[test_case(Some(UUID::build()), Some(reply_to_address()), Some(method_to_invoke()), Some(UUID::build()), Some(EnumOrUnknown::from(UCode::CANCELLED)), Some(100), Some(UPriority::UPRIORITY_CS3), false; "fails for invalid priority")]
942    #[test_case(Some(UUID::build()), Some(reply_to_address()), Some(method_to_invoke()), None, None, None, Some(UPriority::UPRIORITY_CS4), false; "fails for missing request id")]
943    #[test_case(
944        Some(UUID::build()),
945        Some(reply_to_address()),
946        Some(method_to_invoke()),
947        Some(UUID {
948            // invalid UUID version (not 0b1000 but 0b1010)
949            msb: 0x000000000001C000u64,
950            lsb: 0x8000000000000000u64,
951            ..Default::default()
952        }),
953        None,
954        None,
955        Some(UPriority::UPRIORITY_CS4),
956        false;
957        "fails for invalid request id")]
958    #[allow(clippy::too_many_arguments)]
959    fn test_validate_attributes_for_rpc_response_message(
960        id: Option<UUID>,
961        reply_to_address: Option<UUri>,
962        invoked_method: Option<UUri>,
963        reqid: Option<UUID>,
964        commstatus: Option<EnumOrUnknown<UCode>>,
965        ttl: Option<u32>,
966        priority: Option<UPriority>,
967        expected_result: bool,
968    ) {
969        let attributes = UAttributes {
970            type_: UMessageType::UMESSAGE_TYPE_RESPONSE.into(),
971            id: id.into(),
972            priority: priority.unwrap_or(UPriority::UPRIORITY_UNSPECIFIED).into(),
973            reqid: reqid.into(),
974            source: invoked_method.into(),
975            sink: reply_to_address.into(),
976            commstatus,
977            ttl,
978            ..Default::default()
979        };
980        let status = UAttributesValidators::Response
981            .validator()
982            .validate(&attributes);
983        assert!(status.is_ok() == expected_result);
984        if status.is_ok() {
985            assert!(UAttributesValidators::Publish
986                .validator()
987                .validate(&attributes)
988                .is_err());
989            assert!(UAttributesValidators::Notification
990                .validator()
991                .validate(&attributes)
992                .is_err());
993            assert!(UAttributesValidators::Request
994                .validator()
995                .validate(&attributes)
996                .is_err());
997        }
998    }
999
1000    fn publish_topic() -> UUri {
1001        UUri {
1002            authority_name: String::from("vcu.someVin"),
1003            ue_id: 0x0000_5410,
1004            ue_version_major: 0x01,
1005            resource_id: 0xa010,
1006            ..Default::default()
1007        }
1008    }
1009
1010    fn origin() -> UUri {
1011        UUri {
1012            authority_name: String::from("vcu.someVin"),
1013            ue_id: 0x0000_3c00,
1014            ue_version_major: 0x02,
1015            resource_id: 0x9a00,
1016            ..Default::default()
1017        }
1018    }
1019
1020    fn destination() -> UUri {
1021        UUri {
1022            authority_name: String::from("vcu.someVin"),
1023            ue_id: 0x0000_3d07,
1024            ue_version_major: 0x01,
1025            resource_id: 0x0000,
1026            ..Default::default()
1027        }
1028    }
1029
1030    fn reply_to_address() -> UUri {
1031        UUri {
1032            authority_name: String::from("vcu.someVin"),
1033            ue_id: 0x0000_010b,
1034            ue_version_major: 0x01,
1035            resource_id: 0x0000,
1036            ..Default::default()
1037        }
1038    }
1039
1040    fn method_to_invoke() -> UUri {
1041        UUri {
1042            authority_name: String::from("vcu.someVin"),
1043            ue_id: 0x0000_03ae,
1044            ue_version_major: 0x01,
1045            resource_id: 0x00e2,
1046            ..Default::default()
1047        }
1048    }
1049}