use std::time::SystemTime;
use protobuf::Enum;
use crate::{UAttributes, UMessageType, UPriority, UUri, UUID};
use crate::UAttributesError;
pub trait UAttributesValidator: Send {
fn validate(&self, attributes: &UAttributes) -> Result<(), UAttributesError>;
fn validate_type(&self, attributes: &UAttributes) -> Result<(), UAttributesError> {
let expected_type = self.message_type();
match attributes.type_.enum_value() {
Ok(mt) if mt == expected_type => Ok(()),
Ok(mt) => Err(UAttributesError::validation_error(format!(
"Wrong Message Type [{}]",
mt.to_cloudevent_type()
))),
Err(unknown_code) => Err(UAttributesError::validation_error(format!(
"Unknown Message Type code [{}]",
unknown_code
))),
}
}
fn validate_id(&self, attributes: &UAttributes) -> Result<(), UAttributesError> {
if attributes
.id
.as_ref()
.map_or(false, |id| id.is_uprotocol_uuid())
{
Ok(())
} else {
Err(UAttributesError::validation_error(
"Attributes must contain valid uProtocol UUID in id property",
))
}
}
fn message_type(&self) -> UMessageType;
fn is_expired(&self, attributes: &UAttributes) -> Result<(), UAttributesError> {
let ttl = match attributes.ttl {
Some(t) if t > 0 => u64::from(t),
_ => return Ok(()),
};
if let Some(time) = attributes.id.as_ref().and_then(UUID::get_time) {
let delta = match SystemTime::now().duration_since(SystemTime::UNIX_EPOCH) {
Ok(duration) => {
if let Ok(duration) = u64::try_from(duration.as_millis()) {
duration - time
} else {
return Err(UAttributesError::validation_error("Invalid duration"));
}
}
Err(e) => return Err(UAttributesError::validation_error(e.to_string())),
};
if delta >= ttl {
return Err(UAttributesError::validation_error("Payload is expired"));
}
}
Ok(())
}
fn validate_source(&self, attributes: &UAttributes) -> Result<(), UAttributesError>;
fn validate_sink(&self, attributes: &UAttributes) -> Result<(), UAttributesError>;
}
pub fn validate_rpc_priority(attributes: &UAttributes) -> Result<(), UAttributesError> {
attributes
.priority
.enum_value()
.map_err(|unknown_code| {
UAttributesError::ValidationError(format!(
"RPC message must have a valid priority [{}]",
unknown_code
))
})
.and_then(|prio| {
if prio.value() < UPriority::UPRIORITY_CS4.value() {
Err(UAttributesError::ValidationError(
"RPC message must have a priority of at least CS4".to_string(),
))
} else {
Ok(())
}
})
}
pub enum UAttributesValidators {
Publish,
Notification,
Request,
Response,
}
impl UAttributesValidators {
pub fn validator(&self) -> Box<dyn UAttributesValidator> {
match self {
UAttributesValidators::Publish => Box::new(PublishValidator),
UAttributesValidators::Notification => Box::new(NotificationValidator),
UAttributesValidators::Request => Box::new(RequestValidator),
UAttributesValidators::Response => Box::new(ResponseValidator),
}
}
pub fn get_validator_for_attributes(attributes: &UAttributes) -> Box<dyn UAttributesValidator> {
Self::get_validator(attributes.type_.enum_value_or_default())
}
pub fn get_validator(message_type: UMessageType) -> Box<dyn UAttributesValidator> {
match message_type {
UMessageType::UMESSAGE_TYPE_REQUEST => Box::new(RequestValidator),
UMessageType::UMESSAGE_TYPE_RESPONSE => Box::new(ResponseValidator),
UMessageType::UMESSAGE_TYPE_NOTIFICATION => Box::new(NotificationValidator),
_ => Box::new(PublishValidator),
}
}
}
pub struct PublishValidator;
impl UAttributesValidator for PublishValidator {
fn message_type(&self) -> UMessageType {
UMessageType::UMESSAGE_TYPE_PUBLISH
}
fn validate(&self, attributes: &UAttributes) -> Result<(), UAttributesError> {
let error_message = vec![
self.validate_type(attributes),
self.validate_id(attributes),
self.validate_source(attributes),
self.validate_sink(attributes),
]
.into_iter()
.filter_map(Result::err)
.map(|e| e.to_string())
.collect::<Vec<_>>()
.join("; ");
if error_message.is_empty() {
Ok(())
} else {
Err(UAttributesError::validation_error(error_message))
}
}
fn validate_source(&self, attributes: &UAttributes) -> Result<(), UAttributesError> {
if let Some(source) = attributes.source.as_ref() {
source.verify_event().map_err(|e| {
UAttributesError::validation_error(format!("Invalid source URI: {}", e))
})
} else {
Err(UAttributesError::validation_error(
"Attributes for a publish message must contain a source URI",
))
}
}
fn validate_sink(&self, attributes: &UAttributes) -> Result<(), UAttributesError> {
if attributes.sink.as_ref().is_some() {
Err(UAttributesError::validation_error(
"Attributes for a publish message must not contain a sink URI",
))
} else {
Ok(())
}
}
}
pub struct NotificationValidator;
impl UAttributesValidator for NotificationValidator {
fn message_type(&self) -> UMessageType {
UMessageType::UMESSAGE_TYPE_NOTIFICATION
}
fn validate(&self, attributes: &UAttributes) -> Result<(), UAttributesError> {
let error_message = vec![
self.validate_type(attributes),
self.validate_id(attributes),
self.validate_source(attributes),
self.validate_sink(attributes),
]
.into_iter()
.filter_map(Result::err)
.map(|e| e.to_string())
.collect::<Vec<_>>()
.join("; ");
if error_message.is_empty() {
Ok(())
} else {
Err(UAttributesError::validation_error(error_message))
}
}
fn validate_source(&self, attributes: &UAttributes) -> Result<(), UAttributesError> {
if let Some(source) = attributes.source.as_ref() {
if source.is_rpc_response() {
Err(UAttributesError::validation_error(
"Origin must not be an RPC response URI",
))
} else {
source.verify_no_wildcards().map_err(|e| {
UAttributesError::validation_error(format!("Invalid source URI: {}", e))
})
}
} else {
Err(UAttributesError::validation_error(
"Attributes must contain a source URI",
))
}
}
fn validate_sink(&self, attributes: &UAttributes) -> Result<(), UAttributesError> {
if let Some(sink) = attributes.sink.as_ref() {
if !sink.is_notification_destination() {
Err(UAttributesError::validation_error(
"Destination's resource ID must be 0",
))
} else {
sink.verify_no_wildcards().map_err(|e| {
UAttributesError::validation_error(format!("Invalid sink URI: {}", e))
})
}
} else {
Err(UAttributesError::validation_error(
"Attributes for a notification message must contain a sink URI",
))
}
}
}
pub struct RequestValidator;
impl RequestValidator {
pub fn validate_ttl(&self, attributes: &UAttributes) -> Result<(), UAttributesError> {
match attributes.ttl {
Some(ttl) if ttl > 0 => Ok(()),
Some(invalid_ttl) => Err(UAttributesError::validation_error(format!(
"RPC request message's TTL must be a positive integer [{invalid_ttl}]"
))),
None => Err(UAttributesError::validation_error(
"RPC request message must contain a TTL",
)),
}
}
}
impl UAttributesValidator for RequestValidator {
fn message_type(&self) -> UMessageType {
UMessageType::UMESSAGE_TYPE_REQUEST
}
fn validate(&self, attributes: &UAttributes) -> Result<(), UAttributesError> {
let error_message = vec![
self.validate_type(attributes),
self.validate_id(attributes),
self.validate_ttl(attributes),
self.validate_source(attributes),
self.validate_sink(attributes),
validate_rpc_priority(attributes),
]
.into_iter()
.filter_map(Result::err)
.map(|e| e.to_string())
.collect::<Vec<_>>()
.join("; ");
if error_message.is_empty() {
Ok(())
} else {
Err(UAttributesError::validation_error(error_message))
}
}
fn validate_source(&self, attributes: &UAttributes) -> Result<(), UAttributesError> {
if let Some(source) = attributes.source.as_ref() {
UUri::verify_rpc_response(source).map_err(|e| {
UAttributesError::validation_error(format!("Invalid source URI: {}", e))
})
} else {
Err(UAttributesError::validation_error("Attributes for a request message must contain a reply-to address in the source property"))
}
}
fn validate_sink(&self, attributes: &UAttributes) -> Result<(), UAttributesError> {
if let Some(sink) = attributes.sink.as_ref() {
UUri::verify_rpc_method(sink)
.map_err(|e| UAttributesError::validation_error(format!("Invalid sink URI: {}", e)))
} else {
Err(UAttributesError::validation_error("Attributes for a request message must contain a method-to-invoke in the sink property"))
}
}
}
pub struct ResponseValidator;
impl ResponseValidator {
pub fn validate_reqid(&self, attributes: &UAttributes) -> Result<(), UAttributesError> {
if !attributes
.reqid
.as_ref()
.map_or(false, |id| id.is_uprotocol_uuid())
{
Err(UAttributesError::validation_error(
"Request ID is not a valid uProtocol UUID",
))
} else {
Ok(())
}
}
pub fn validate_commstatus(&self, attributes: &UAttributes) -> Result<(), UAttributesError> {
if let Some(status) = attributes.commstatus {
match status.enum_value() {
Ok(_) => {
return Ok(());
}
Err(e) => {
return Err(UAttributesError::validation_error(format!(
"Invalid Communication Status code: {e}"
)));
}
}
}
Ok(())
}
}
impl UAttributesValidator for ResponseValidator {
fn message_type(&self) -> UMessageType {
UMessageType::UMESSAGE_TYPE_RESPONSE
}
fn validate(&self, attributes: &UAttributes) -> Result<(), UAttributesError> {
let error_message = vec![
self.validate_type(attributes),
self.validate_id(attributes),
self.validate_source(attributes),
self.validate_sink(attributes),
self.validate_reqid(attributes),
self.validate_commstatus(attributes),
validate_rpc_priority(attributes),
]
.into_iter()
.filter_map(Result::err)
.map(|e| e.to_string())
.collect::<Vec<_>>()
.join("; ");
if error_message.is_empty() {
Ok(())
} else {
Err(UAttributesError::validation_error(error_message))
}
}
fn validate_source(&self, attributes: &UAttributes) -> Result<(), UAttributesError> {
if let Some(source) = attributes.source.as_ref() {
UUri::verify_rpc_method(source).map_err(|e| {
UAttributesError::validation_error(format!("Invalid source URI: {}", e))
})
} else {
Err(UAttributesError::validation_error("Missing Source"))
}
}
fn validate_sink(&self, attributes: &UAttributes) -> Result<(), UAttributesError> {
if let Some(sink) = &attributes.sink.as_ref() {
UUri::verify_rpc_response(sink)
.map_err(|e| UAttributesError::validation_error(format!("Invalid sink URI: {}", e)))
} else {
Err(UAttributesError::validation_error("Missing Sink"))
}
}
}
#[cfg(test)]
mod tests {
use std::{
ops::Sub,
time::{Duration, UNIX_EPOCH},
};
use protobuf::EnumOrUnknown;
use test_case::test_case;
use super::*;
use crate::{UCode, UPriority, UUri, UUID};
fn build_n_ms_in_past(n_ms_in_past: u64) -> UUID {
let duration_since_unix_epoch = SystemTime::now()
.duration_since(UNIX_EPOCH)
.expect("current system time is set to a point in time before UNIX Epoch");
UUID::build_for_timestamp(
duration_since_unix_epoch.sub(Duration::from_millis(n_ms_in_past)),
)
}
#[test]
fn test_validate_type_fails_for_unknown_type_code() {
let attributes = UAttributes {
type_: EnumOrUnknown::from_i32(20),
..Default::default()
};
assert!(UAttributesValidators::Publish
.validator()
.validate_type(&attributes)
.is_err());
assert!(UAttributesValidators::Notification
.validator()
.validate_type(&attributes)
.is_err());
assert!(UAttributesValidators::Request
.validator()
.validate_type(&attributes)
.is_err());
assert!(UAttributesValidators::Response
.validator()
.validate_type(&attributes)
.is_err());
}
#[test_case(UMessageType::UMESSAGE_TYPE_UNSPECIFIED, UMessageType::UMESSAGE_TYPE_PUBLISH; "succeeds for Unspecified message")]
#[test_case(UMessageType::UMESSAGE_TYPE_PUBLISH, UMessageType::UMESSAGE_TYPE_PUBLISH; "succeeds for Publish message")]
#[test_case(UMessageType::UMESSAGE_TYPE_NOTIFICATION, UMessageType::UMESSAGE_TYPE_NOTIFICATION; "succeeds for Notification message")]
#[test_case(UMessageType::UMESSAGE_TYPE_REQUEST, UMessageType::UMESSAGE_TYPE_REQUEST; "succeeds for Request message")]
#[test_case(UMessageType::UMESSAGE_TYPE_RESPONSE, UMessageType::UMESSAGE_TYPE_RESPONSE; "succeeds for Response message")]
fn test_get_validator_returns_matching_validator(
message_type: UMessageType,
expected_validator_type: UMessageType,
) {
let validator: Box<dyn UAttributesValidator> =
UAttributesValidators::get_validator(message_type);
assert_eq!(validator.message_type(), expected_validator_type);
}
#[test_case(UMessageType::UMESSAGE_TYPE_PUBLISH, None, None, false; "for Publish message without ID nor TTL")]
#[test_case(UMessageType::UMESSAGE_TYPE_PUBLISH, None, Some(0), false; "for Publish message without ID with TTL 0")]
#[test_case(UMessageType::UMESSAGE_TYPE_PUBLISH, None, Some(500), false; "for Publish message without ID with TTL")]
#[test_case(UMessageType::UMESSAGE_TYPE_PUBLISH, Some(build_n_ms_in_past(1000)), None, false; "for Publish message with ID without TTL")]
#[test_case(UMessageType::UMESSAGE_TYPE_PUBLISH, Some(build_n_ms_in_past(1000)), Some(0), false; "for Publish message with ID and TTL 0")]
#[test_case(UMessageType::UMESSAGE_TYPE_PUBLISH, Some(build_n_ms_in_past(1000)), Some(500), true; "for Publish message with ID and expired TTL")]
#[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")]
#[test_case(UMessageType::UMESSAGE_TYPE_NOTIFICATION, None, None, false; "for Notification message without ID nor TTL")]
#[test_case(UMessageType::UMESSAGE_TYPE_NOTIFICATION, None, Some(0), false; "for Notification message without ID with TTL 0")]
#[test_case(UMessageType::UMESSAGE_TYPE_NOTIFICATION, None, Some(500), false; "for Notification message without ID with TTL")]
#[test_case(UMessageType::UMESSAGE_TYPE_NOTIFICATION, Some(build_n_ms_in_past(1000)), None, false; "for Notification message with ID without TTL")]
#[test_case(UMessageType::UMESSAGE_TYPE_NOTIFICATION, Some(build_n_ms_in_past(1000)), Some(0), false; "for Notification message with ID and TTL 0")]
#[test_case(UMessageType::UMESSAGE_TYPE_NOTIFICATION, Some(build_n_ms_in_past(1000)), Some(500), true; "for Notification message with ID and expired TTL")]
#[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")]
#[test_case(UMessageType::UMESSAGE_TYPE_REQUEST, None, None, false; "for Request message without ID nor TTL")]
#[test_case(UMessageType::UMESSAGE_TYPE_REQUEST, None, Some(0), false; "for Request message without ID with TTL 0")]
#[test_case(UMessageType::UMESSAGE_TYPE_REQUEST, None, Some(500), false; "for Request message without ID with TTL")]
#[test_case(UMessageType::UMESSAGE_TYPE_REQUEST, Some(build_n_ms_in_past(1000)), None, false; "for Request message with ID without TTL")]
#[test_case(UMessageType::UMESSAGE_TYPE_REQUEST, Some(build_n_ms_in_past(1000)), Some(0), false; "for Request message with ID and TTL 0")]
#[test_case(UMessageType::UMESSAGE_TYPE_REQUEST, Some(build_n_ms_in_past(1000)), Some(500), true; "for Request message with ID and expired TTL")]
#[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")]
#[test_case(UMessageType::UMESSAGE_TYPE_RESPONSE, None, None, false; "for Response message without ID nor TTL")]
#[test_case(UMessageType::UMESSAGE_TYPE_RESPONSE, None, Some(0), false; "for Response message without ID with TTL 0")]
#[test_case(UMessageType::UMESSAGE_TYPE_RESPONSE, None, Some(500), false; "for Response message without ID with TTL")]
#[test_case(UMessageType::UMESSAGE_TYPE_RESPONSE, Some(build_n_ms_in_past(1000)), None, false; "for Response message with ID without TTL")]
#[test_case(UMessageType::UMESSAGE_TYPE_RESPONSE, Some(build_n_ms_in_past(1000)), Some(0), false; "for Response message with ID and TTL 0")]
#[test_case(UMessageType::UMESSAGE_TYPE_RESPONSE, Some(build_n_ms_in_past(1000)), Some(500), true; "for Response message with ID and expired TTL")]
#[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")]
fn test_is_expired(
message_type: UMessageType,
id: Option<UUID>,
ttl: Option<u32>,
should_be_expired: bool,
) {
let attributes = UAttributes {
type_: message_type.into(),
priority: UPriority::UPRIORITY_CS1.into(),
id: id.into(),
ttl,
..Default::default()
};
let validator = UAttributesValidators::get_validator(message_type);
assert!(validator.is_expired(&attributes).is_err() == should_be_expired);
}
#[test_case(Some(UUID::build()), Some(publish_topic()), None, None, true; "succeeds for topic only")]
#[test_case(Some(UUID::build()), Some(publish_topic()), Some(destination()), None, false; "fails for message containing destination")]
#[test_case(Some(UUID::build()), Some(publish_topic()), None, Some(100), true; "succeeds for valid attributes")]
#[test_case(Some(UUID::build()), None, None, None, false; "fails for missing topic")]
#[test_case(Some(UUID::build()), Some(UUri { resource_id: 0x54, ..Default::default()}), None, None, false; "fails for invalid topic")]
#[test_case(None, Some(publish_topic()), None, None, false; "fails for missing message ID")]
#[test_case(
Some(UUID {
msb: 0x000000000001C000u64,
lsb: 0x8000000000000000u64,
..Default::default()
}),
Some(publish_topic()),
None,
None,
false;
"fails for invalid message id")]
fn test_validate_attributes_for_publish_message(
id: Option<UUID>,
source: Option<UUri>,
sink: Option<UUri>,
ttl: Option<u32>,
expected_result: bool,
) {
let attributes = UAttributes {
type_: UMessageType::UMESSAGE_TYPE_PUBLISH.into(),
id: id.into(),
priority: UPriority::UPRIORITY_CS1.into(),
source: source.into(),
sink: sink.into(),
ttl,
..Default::default()
};
let validator = UAttributesValidators::Publish.validator();
let status = validator.validate(&attributes);
assert!(status.is_ok() == expected_result);
if status.is_ok() {
assert!(UAttributesValidators::Notification
.validator()
.validate(&attributes)
.is_err());
assert!(UAttributesValidators::Request
.validator()
.validate(&attributes)
.is_err());
assert!(UAttributesValidators::Response
.validator()
.validate(&attributes)
.is_err());
}
}
#[test_case(Some(UUID::build()), Some(origin()), None, None, false; "fails for missing destination")]
#[test_case(Some(UUID::build()), Some(origin()), Some(destination()), None, true; "succeeds for both origin and destination")]
#[test_case(Some(UUID::build()), Some(origin()), Some(destination()), Some(100), true; "succeeds for valid attributes")]
#[test_case(Some(UUID::build()), None, Some(destination()), None, false; "fails for missing origin")]
#[test_case(Some(UUID::build()), Some(UUri::default()), Some(destination()), None, false; "fails for invalid origin")]
#[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")]
#[test_case(Some(UUID::build()), None, None, None, false; "fails for neither origin nor destination")]
#[test_case(None, Some(origin()), Some(destination()), None, false; "fails for missing message ID")]
#[test_case(
Some(UUID {
msb: 0x000000000001C000u64,
lsb: 0x8000000000000000u64,
..Default::default()
}),
Some(origin()),
Some(destination()),
None,
false;
"fails for invalid message id")]
fn test_validate_attributes_for_notification_message(
id: Option<UUID>,
source: Option<UUri>,
sink: Option<UUri>,
ttl: Option<u32>,
expected_result: bool,
) {
let attributes = UAttributes {
type_: UMessageType::UMESSAGE_TYPE_NOTIFICATION.into(),
id: id.into(),
priority: UPriority::UPRIORITY_CS1.into(),
source: source.into(),
sink: sink.into(),
ttl,
..Default::default()
};
let validator = UAttributesValidators::Notification.validator();
let status = validator.validate(&attributes);
assert!(status.is_ok() == expected_result);
if status.is_ok() {
assert!(UAttributesValidators::Publish
.validator()
.validate(&attributes)
.is_err());
assert!(UAttributesValidators::Request
.validator()
.validate(&attributes)
.is_err());
assert!(UAttributesValidators::Response
.validator()
.validate(&attributes)
.is_err());
}
}
#[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")]
#[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")]
#[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")]
#[test_case(
Some(UUID {
msb: 0x000000000001C000u64,
lsb: 0x8000000000000000u64,
..Default::default()
}),
Some(method_to_invoke()),
Some(reply_to_address()),
None,
Some(2000),
Some(UPriority::UPRIORITY_CS4),
None,
false;
"fails for invalid message id")]
#[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")]
#[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")]
#[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")]
#[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")]
#[test_case(Some(UUID::build()), Some(method_to_invoke()), Some(reply_to_address()), Some(1), Some(2000), None, None, false; "fails for missing priority")]
#[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")]
#[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")]
#[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")]
#[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")]
#[allow(clippy::too_many_arguments)]
fn test_validate_attributes_for_rpc_request_message(
id: Option<UUID>,
method_to_invoke: Option<UUri>,
reply_to_address: Option<UUri>,
perm_level: Option<u32>,
ttl: Option<u32>,
priority: Option<UPriority>,
token: Option<String>,
expected_result: bool,
) {
let attributes = UAttributes {
type_: UMessageType::UMESSAGE_TYPE_REQUEST.into(),
id: id.into(),
priority: priority.unwrap_or(UPriority::UPRIORITY_UNSPECIFIED).into(),
source: reply_to_address.into(),
sink: method_to_invoke.into(),
permission_level: perm_level,
ttl,
token,
..Default::default()
};
let status = UAttributesValidators::Request
.validator()
.validate(&attributes);
assert!(status.is_ok() == expected_result);
if status.is_ok() {
assert!(UAttributesValidators::Publish
.validator()
.validate(&attributes)
.is_err());
assert!(UAttributesValidators::Notification
.validator()
.validate(&attributes)
.is_err());
assert!(UAttributesValidators::Response
.validator()
.validate(&attributes)
.is_err());
}
}
#[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")]
#[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")]
#[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")]
#[test_case(
Some(UUID {
msb: 0x000000000001C000u64,
lsb: 0x8000000000000000u64,
..Default::default()
}),
Some(reply_to_address()),
Some(method_to_invoke()),
Some(UUID::build()),
None,
None,
Some(UPriority::UPRIORITY_CS4),
false;
"fails for invalid message id")]
#[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")]
#[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")]
#[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")]
#[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")]
#[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")]
#[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")]
#[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)")]
#[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")]
#[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")]
#[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")]
#[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")]
#[test_case(
Some(UUID::build()),
Some(reply_to_address()),
Some(method_to_invoke()),
Some(UUID {
msb: 0x000000000001C000u64,
lsb: 0x8000000000000000u64,
..Default::default()
}),
None,
None,
Some(UPriority::UPRIORITY_CS4),
false;
"fails for invalid request id")]
#[allow(clippy::too_many_arguments)]
fn test_validate_attributes_for_rpc_response_message(
id: Option<UUID>,
reply_to_address: Option<UUri>,
invoked_method: Option<UUri>,
reqid: Option<UUID>,
commstatus: Option<EnumOrUnknown<UCode>>,
ttl: Option<u32>,
priority: Option<UPriority>,
expected_result: bool,
) {
let attributes = UAttributes {
type_: UMessageType::UMESSAGE_TYPE_RESPONSE.into(),
id: id.into(),
priority: priority.unwrap_or(UPriority::UPRIORITY_UNSPECIFIED).into(),
reqid: reqid.into(),
source: invoked_method.into(),
sink: reply_to_address.into(),
commstatus,
ttl,
..Default::default()
};
let status = UAttributesValidators::Response
.validator()
.validate(&attributes);
assert!(status.is_ok() == expected_result);
if status.is_ok() {
assert!(UAttributesValidators::Publish
.validator()
.validate(&attributes)
.is_err());
assert!(UAttributesValidators::Notification
.validator()
.validate(&attributes)
.is_err());
assert!(UAttributesValidators::Request
.validator()
.validate(&attributes)
.is_err());
}
}
fn publish_topic() -> UUri {
UUri {
authority_name: String::from("vcu.someVin"),
ue_id: 0x0000_5410,
ue_version_major: 0x01,
resource_id: 0xa010,
..Default::default()
}
}
fn origin() -> UUri {
UUri {
authority_name: String::from("vcu.someVin"),
ue_id: 0x0000_3c00,
ue_version_major: 0x02,
resource_id: 0x9a00,
..Default::default()
}
}
fn destination() -> UUri {
UUri {
authority_name: String::from("vcu.someVin"),
ue_id: 0x0000_3d07,
ue_version_major: 0x01,
resource_id: 0x0000,
..Default::default()
}
}
fn reply_to_address() -> UUri {
UUri {
authority_name: String::from("vcu.someVin"),
ue_id: 0x0000_010b,
ue_version_major: 0x01,
resource_id: 0x0000,
..Default::default()
}
}
fn method_to_invoke() -> UUri {
UUri {
authority_name: String::from("vcu.someVin"),
ue_id: 0x0000_03ae,
ue_version_major: 0x01,
resource_id: 0x00e2,
..Default::default()
}
}
}