From 803fadbcf954a27bde2e375a4b998530e103621f Mon Sep 17 00:00:00 2001 From: Daniel Krippner Date: Mon, 14 Jul 2025 08:28:31 +0000 Subject: [PATCH 1/2] update to use latest up-spec version --- up-spec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/up-spec b/up-spec index e74729b..fb467b0 160000 --- a/up-spec +++ b/up-spec @@ -1 +1 @@ -Subproject commit e74729bd22e14bc6502a0e9b74f42165d98651a5 +Subproject commit fb467b049c97e020c2ce0a9ba0f2859cffd2031a From 55646faf556cbd5362e0e648c4209a16c848bd81 Mon Sep 17 00:00:00 2001 From: Daniel Krippner Date: Mon, 14 Jul 2025 09:07:14 +0000 Subject: [PATCH 2/2] Implement dsn~usubscription-fetch-subscribers-invalid-topic --- up-subscription/src/common/helpers.rs | 28 ++++++++ .../src/handlers/fetch_subscribers.rs | 68 +++++++++++++++++-- up-subscription/src/tests/test_lib.rs | 12 ++-- 3 files changed, 96 insertions(+), 12 deletions(-) diff --git a/up-subscription/src/common/helpers.rs b/up-subscription/src/common/helpers.rs index 40db451..e904b24 100644 --- a/up-subscription/src/common/helpers.rs +++ b/up-subscription/src/common/helpers.rs @@ -89,3 +89,31 @@ pub(crate) fn duration_until_timestamp(future_timestamp_millis: u128) -> Option< None // Timestamp is in the past } } + +// Validate that a topic URI +// * is a valid uProtocol URI and +// * does not contain a _wildcard_ authority and +// * does not contain a _wildcard_ uEntity instance (`ue_id`) and +// * does not contain a _wildcard_ resource ID +pub(crate) fn validate_uri(topic: &UUri) -> Result<(), ServiceInvocationError> { + topic + .check_validity() + .map_err(|e| ServiceInvocationError::InvalidArgument(format!("Invalid topic URI {e}")))?; + if topic.has_wildcard_authority() { + return Err(ServiceInvocationError::InvalidArgument( + "Topic URI with wildcard authority".to_string(), + )); + }; + if topic.has_wildcard_entity_instance() { + return Err(ServiceInvocationError::InvalidArgument( + "Topic URI with wildcard entity instance".to_string(), + )); + }; + if topic.has_wildcard_resource_id() { + return Err(ServiceInvocationError::InvalidArgument( + "Topic URI with wildcard resource id".to_string(), + )); + }; + + Ok(()) +} diff --git a/up-subscription/src/handlers/fetch_subscribers.rs b/up-subscription/src/handlers/fetch_subscribers.rs index cdf87ad..1a897af 100644 --- a/up-subscription/src/handlers/fetch_subscribers.rs +++ b/up-subscription/src/handlers/fetch_subscribers.rs @@ -64,6 +64,10 @@ impl RequestHandler for FetchSubscribersRequestHandler { )); }; + // topic input validation + // [impl->dsn~usubscription-fetch-subscribers-invalid-topic~1] + helpers::validate_uri(&topic)?; + // Interact with subscription manager backend let (respond_to, receive_from) = oneshot::channel::(); let se = SubscriptionEvent::FetchSubscribers { @@ -111,8 +115,11 @@ impl RequestHandler for FetchSubscribersRequestHandler { #[cfg(test)] mod tests { use super::*; + use test_case::test_case; use tokio::sync::mpsc::{self}; + use up_rust::UUri; + use crate::{helpers, tests::test_lib}; // [utest->dsn~usubscription-fetch-subscribers-protobuf~1] @@ -123,7 +130,6 @@ mod tests { // create request and other required object(s) let fetch_subscribers_request = FetchSubscribersRequest { topic: Some(test_lib::helpers::local_topic1_uri()).into(), - offset: Some(42), ..Default::default() }; let request_payload = @@ -156,13 +162,9 @@ mod tests { let subscription_event = subscription_receiver.recv().await.unwrap(); match subscription_event { SubscriptionEvent::FetchSubscribers { - topic, - offset, - respond_to, + topic, respond_to, .. } => { assert_eq!(topic, test_lib::helpers::local_topic1_uri()); - assert_eq!(offset, Some(42)); - let _ = respond_to.send(SubscribersResponse::default()); } _ => panic!("Wrong event type"), @@ -288,4 +290,58 @@ mod tests { _ => panic!("Wrong error type"), } } + + // [utest->dsn~usubscription-fetch-subscribers-invalid-topic~1] + #[test_case(UUri::default(); "Bad topic UUri")] + #[test_case(UUri { + authority_name: String::from("*"), + ue_id: test_lib::helpers::TOPIC_LOCAL1_ID, + ue_version_major: test_lib::helpers::TOPIC_LOCAL1_VERSION as u32, + resource_id: test_lib::helpers::TOPIC_LOCAL1_RESOURCE as u32, + ..Default::default() + }; "Wildcard authority in topic UUri")] + #[test_case(UUri { + authority_name: test_lib::helpers::LOCAL_AUTHORITY.into(), + ue_id: 0xFFFF_0000, + ue_version_major: test_lib::helpers::TOPIC_LOCAL1_VERSION as u32, + resource_id: test_lib::helpers::TOPIC_LOCAL1_RESOURCE as u32, + ..Default::default() + }; "Wildcard entity id in topic UUri")] + #[test_case(UUri { + authority_name: test_lib::helpers::LOCAL_AUTHORITY.into(), + ue_id: test_lib::helpers::TOPIC_LOCAL1_ID, + ue_version_major: test_lib::helpers::TOPIC_LOCAL1_VERSION as u32, + resource_id: 0x0000_FFFF, + ..Default::default() + }; "Wildcard resource id in topic UUri")] + #[tokio::test] + async fn test_invalid_topic_uri(topic: UUri) { + helpers::init_once(); + + // create request and other required object(s) + let subscribe_request = test_lib::helpers::subscription_request(topic, None); + let request_payload = UPayload::try_from_protobuf(subscribe_request.clone()).unwrap(); + let message_attributes = UAttributes { + source: Some(test_lib::helpers::subscriber_uri1()).into(), + ..Default::default() + }; + let (subscription_sender, _) = mpsc::channel::(1); + + // create handler and perform tested operation + let request_handler = FetchSubscribersRequestHandler::new(subscription_sender); + + let result = request_handler + .handle_request( + up_rust::core::usubscription::RESOURCE_ID_FETCH_SUBSCRIBERS, + &message_attributes, + Some(request_payload), + ) + .await; + + assert!(result.is_err()); + match result.unwrap_err() { + ServiceInvocationError::InvalidArgument(_) => {} + _ => panic!("Wrong error type"), + } + } } diff --git a/up-subscription/src/tests/test_lib.rs b/up-subscription/src/tests/test_lib.rs index 21018c3..0aae8dd 100644 --- a/up-subscription/src/tests/test_lib.rs +++ b/up-subscription/src/tests/test_lib.rs @@ -160,9 +160,9 @@ pub(crate) mod helpers { pub(crate) const LOCAL_AUTHORITY: &str = "LOCAL"; pub(crate) const REMOTE_AUTHORITY: &str = "REMOTE"; - const SUBSCRIBER1_ID: u32 = 0x0000_1000; - const SUBSCRIBER1_VERSION: u8 = 0x01; - const SUBSCRIBER1_RESOURCE: u16 = 0x1000; + pub(crate) const SUBSCRIBER1_ID: u32 = 0x0000_1000; + pub(crate) const SUBSCRIBER1_VERSION: u8 = 0x01; + pub(crate) const SUBSCRIBER1_RESOURCE: u16 = 0x1000; const SUBSCRIBER2_ID: u32 = 0x0000_2000; const SUBSCRIBER2_VERSION: u8 = 0x01; @@ -179,9 +179,9 @@ pub(crate) mod helpers { #[allow(dead_code)] // final decision on removing this to happen after functional spec alignment is complete const NOTIFICATION_TOPIC_RESOURCE: u16 = 0x8001; - const TOPIC_LOCAL1_ID: u32 = 0x0010_0000; - const TOPIC_LOCAL1_VERSION: u8 = 0x01; - const TOPIC_LOCAL1_RESOURCE: u16 = 0x8AC7; + pub(crate) const TOPIC_LOCAL1_ID: u32 = 0x0010_0000; + pub(crate) const TOPIC_LOCAL1_VERSION: u8 = 0x01; + pub(crate) const TOPIC_LOCAL1_RESOURCE: u16 = 0x8AC7; const TOPIC_LOCAL2_ID: u32 = 0x0020_0000; const TOPIC_LOCAL2_VERSION: u8 = 0x01;