1use std::time::SystemTime;
15
16use protobuf::Enum;
17
18use crate::{UAttributes, UMessageType, UPriority, UUri, UUID};
19
20use crate::UAttributesError;
21
22pub trait UAttributesValidator: Send {
30 fn validate(&self, attributes: &UAttributes) -> Result<(), UAttributesError>;
37
38 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 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 fn message_type(&self) -> UMessageType;
79
80 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 fn validate_source(&self, attributes: &UAttributes) -> Result<(), UAttributesError>;
117
118 fn validate_sink(&self, attributes: &UAttributes) -> Result<(), UAttributesError>;
120}
121
122pub 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
148pub enum UAttributesValidators {
150 Publish,
151 Notification,
152 Request,
153 Response,
154}
155
156impl UAttributesValidators {
157 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 pub fn get_validator_for_attributes(attributes: &UAttributes) -> Box<dyn UAttributesValidator> {
207 Self::get_validator(attributes.type_.enum_value_or_default())
208 }
209
210 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
240pub struct PublishValidator;
242
243impl UAttributesValidator for PublishValidator {
244 fn message_type(&self) -> UMessageType {
245 UMessageType::UMESSAGE_TYPE_PUBLISH
246 }
247
248 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 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 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
316pub struct NotificationValidator;
318
319impl UAttributesValidator for NotificationValidator {
320 fn message_type(&self) -> UMessageType {
321 UMessageType::UMESSAGE_TYPE_NOTIFICATION
322 }
323
324 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 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 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
410pub struct RequestValidator;
412
413impl RequestValidator {
414 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 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 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 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
504pub struct ResponseValidator;
506
507impl ResponseValidator {
508 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 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 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 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 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 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 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 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 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 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 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}