diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..6313b56 --- /dev/null +++ b/.gitattributes @@ -0,0 +1 @@ +* text=auto eol=lf diff --git a/README.md b/README.md index 24159af..3812ca6 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,2 @@ # up-transport-iceoryx2-rust -Rust uTransport implementation for iceoryx2 +Rust uTransport implementation for iceoryx2 \ No newline at end of file diff --git a/src/lib.rs b/src/lib.rs index e7dfc60..b03e38a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,12 +1,18 @@ use async_trait::async_trait; use std::sync::Arc; -use up_rust::{UListener, UMessage, UStatus, UTransport, UUri}; +use up_rust::{UCode, UListener, UMessage, UStatus, UTransport, UUri}; /// This will be the main struct for our uProtocol transport. /// It will hold the state necessary to communicate with iceoryx2, /// such as the service connection and active listeners. pub struct Iceoryx2Transport {} +enum MessageType { + RpcRequest, + RpcResponseOrNotification, + Publish, +} + // The #[async_trait] attribute enables async functions in our trait impl. #[async_trait] impl UTransport for Iceoryx2Transport { @@ -33,7 +39,181 @@ impl UTransport for Iceoryx2Transport { } } +#[allow(dead_code)] +impl Iceoryx2Transport { + fn encode_uuri_segments(uuri: &UUri) -> Vec { + vec![ + uuri.authority_name.clone(), + Self::encode_hex(uuri.uentity_type_id() as u32), + Self::encode_hex(uuri.uentity_instance_id() as u32), + Self::encode_hex(uuri.uentity_major_version() as u32), + Self::encode_hex(uuri.resource_id() as u32), + ] + } + + fn encode_hex(value: u32) -> String { + format!("{:X}", value) + } + + /// Assumption: valid source and sink URIs provided: + /// send() makes use of UAttributesValidator + /// register_listener() and unregister_listener() use verify_filter_criteria() + /// Criteria for identification of message types can be found here: https://github.com/eclipse-uprotocol/up-spec/blob/main/basics/uattributes.adoc + fn determine_message_type(source: &UUri, sink: Option<&UUri>) -> Result { + let src_id = source.resource_id; + let sink_id = sink.map(|s| s.resource_id); + + if src_id == 0 { + if let Some(id) = sink_id { + if id >= 1 && id <= 0x7FFF { + return Ok(MessageType::RpcRequest); + } + } + } else if sink_id == Some(0) && src_id >= 1 && src_id <= 0xFFFE { + return Ok(MessageType::RpcResponseOrNotification); + } else if src_id >= 1 && src_id <= 0x7FFF { + return Ok(MessageType::Publish); + } + + Err(UStatus::fail_with_code( + UCode::INVALID_ARGUMENT, + "Unsupported UMessageType", + )) + } + + /// Called in send(), register_listener() and unregister_listener() + fn compute_service_name(source: &UUri, sink: Option<&UUri>) -> Result { + let join_segments = |segments: Vec| segments.join("/"); + + match Self::determine_message_type(source, sink)? { + MessageType::RpcRequest => { + let Some(sink_uri) = sink else { + return Err(UStatus::fail_with_code( + UCode::INVALID_ARGUMENT, + "sink required for RpcRequest", + )); + }; + let segments = Self::encode_uuri_segments(sink_uri); + Ok(format!("up/{}", join_segments(segments))) + } + MessageType::RpcResponseOrNotification => { + let Some(sink_uri) = sink else { + return Err(UStatus::fail_with_code( + UCode::INVALID_ARGUMENT, + "sink required for ResponseOrNotification", + )); + }; + let source_segments = Self::encode_uuri_segments(source); + let sink_segments = Self::encode_uuri_segments(sink_uri); + Ok(format!( + "up/{}/{}", + join_segments(source_segments), + join_segments(sink_segments) + )) + } + MessageType::Publish => { + let segments = Self::encode_uuri_segments(source); + Ok(format!("up/{}", join_segments(segments))) + } + } + } +} + #[cfg(test)] mod tests { use super::*; + + fn test_uri(authority: &str, instance: u16, typ: u16, version: u8, resource: u16) -> UUri { + let entity_id = ((instance as u32) << 16) | (typ as u32); + UUri::try_from_parts(authority, entity_id, version, resource).unwrap() + } + + // performing successful tests for service name computation + + #[test] + // [specitem,oft-sid="dsn~up-transport-iceoryx2-service-name~1",oft-needs="utest"] + fn test_publish_service_name() { + let source = test_uri("device1", 0x0000, 0x10AB, 0x03, 0x7FFF); + + let name = Iceoryx2Transport::compute_service_name(&source, None).unwrap(); + assert_eq!(name, "up/device1/10AB/0/3/7FFF"); + } + + #[test] + // [specitem,oft-sid="dsn~up-transport-iceoryx2-service-name~1",oft-needs="utest"] + fn test_notification_service_name() { + let source = test_uri("device1", 0x0000, 0x10AB, 0x03, 0x80CD); + let sink = test_uri("device1", 0x0000, 0x30EF, 0x04, 0x0000); + let name = Iceoryx2Transport::compute_service_name(&source, Some(&sink)).unwrap(); + assert_eq!(name, "up/device1/10AB/0/3/80CD/device1/30EF/0/4/0"); + } + + #[test] + // [specitem,oft-sid="dsn~up-transport-iceoryx2-service-name~1",oft-needs="utest"] + fn test_rpc_request_service_name() { + let sink = test_uri("device1", 0x0004, 0x03AB, 0x03, 0x0000); + let reply_to = test_uri("device1", 0x0000, 0x00CD, 0x04, 0xB); + + let name = Iceoryx2Transport::compute_service_name(&sink, Some(&reply_to)).unwrap(); + assert_eq!(name, "up/device1/CD/0/4/B"); + } + + #[test] + // [specitem,oft-sid="dsn~up-transport-iceoryx2-service-name~1",oft-needs="utest"] + fn test_rpc_response_service_name() { + let source = test_uri("device1", 0x0000, 0x00CD, 0x04, 0xB); + let sink = test_uri("device1", 0x0004, 0x3AB, 0x3, 0x0000); + + let name = Iceoryx2Transport::compute_service_name(&source, Some(&sink)).unwrap(); + assert_eq!(name, "up/device1/CD/0/4/B/device1/3AB/4/3/0"); + } + + // performing failing tests for service name computation + + #[test] + // .specitem[dsn~up-attributes-request-source~1] + // .specitem[dsn~up-attributes-response-source~1] + // .specitem[dsn~up-attributes-notification-source~1] + fn test_missing_uri_error() { + let uuri = UUri::new(); + let result = Iceoryx2Transport::compute_service_name(&uuri, None); + + assert!(result.is_err()); + assert_eq!(result.unwrap_err().get_code(), UCode::INVALID_ARGUMENT); + } + + #[test] + //both source and sink have resource ID equal to 0 + // .specitem[dsn~up-attributes-request-source~1] + // .specitem[dsn~up-attributes-request-sink~1] + // .specitem[dsn~up-attributes-response-source~1] + // .specitem[dsn~up-attributes-response-sink~1] + fn test_fail_resource_id_error() { + let source = test_uri("device1", 0x0000, 0x00CD, 0x04, 0x000); + let sink = test_uri("device1", 0x0004, 0x3AB, 0x3, 0x0000); + let result = Iceoryx2Transport::compute_service_name(&source, Some(&sink)); + assert!(result.is_err_and(|err| err.get_code() == UCode::INVALID_ARGUMENT)); + } + + #[test] + //source has resource id=0 but missing sink + // .specitem[dsn~up-attributes-request-sink~1] + // .specitem[dsn~up-attributes-request-source~1] + fn test_fail_missing_sink_error() { + let source = test_uri("device1", 0x0000, 0x00CD, 0x04, 0x000); + let result = Iceoryx2Transport::compute_service_name(&source, None); + assert!(result.is_err_and(|err| err.get_code() == UCode::INVALID_ARGUMENT)); + } + + #[test] + //missing source URI + // .specitem[dsn~up-attributes-request-source~1] + // .specitem[dsn~up-attributes-response-source~1] + // .specitem[dsn~up-attributes-notification-source~1] + fn test_fail_missing_source_error() { + let uuri = UUri::new(); + let sink = test_uri("device1", 0x0004, 0x3AB, 0x3, 0x000); + let result = Iceoryx2Transport::compute_service_name(&uuri, Some(&sink)); + assert!(result.is_err_and(|err| err.get_code() == UCode::INVALID_ARGUMENT)); + } }