From f0ccb58711ab9440e6b99941fabfafb3b09a159e Mon Sep 17 00:00:00 2001 From: Kenneth Mead Date: Thu, 11 Sep 2025 05:09:39 +0000 Subject: [PATCH 1/7] first iteration of send, register_listener, and unregister_listener Co-authored-by: kwarraich Co-authored-by: Noella Horo Co-authored-by: vidishac2004 <144144085+vidishac2004@users.noreply.github.com> Co-authored-by: arakabCL --- .devcontainer/scripts/bashrc_addition.sh | 1 + .vscode/launch.json | 61 ++++++ Cargo.lock | 223 ++++++++++++---------- Cargo.toml | 9 +- examples/common/helpers.rs | 61 ++++++ examples/common/mod.rs | 1 + examples/subscriber.rs | 64 +++++++ src/lib.rs | 232 +---------------------- src/service_name_mapping.rs | 214 +++++++++++++++++++++ src/transport.rs | 48 +++++ src/types.rs | 34 ++++ src/umessage.rs | 25 +++ src/uprotocolheader.rs | 26 +++ src/utransport.rs | 81 ++++++++ src/workers/command.rs | 35 ++++ src/workers/dispatcher.rs | 119 ++++++++++++ src/workers/mod.rs | 17 ++ src/workers/pubsub_worker.rs | 224 ++++++++++++++++++++++ src/workers/worker.rs | 46 +++++ 19 files changed, 1195 insertions(+), 326 deletions(-) create mode 100644 .vscode/launch.json create mode 100644 examples/common/helpers.rs create mode 100644 examples/common/mod.rs create mode 100644 examples/subscriber.rs create mode 100644 src/service_name_mapping.rs create mode 100644 src/transport.rs create mode 100644 src/types.rs create mode 100644 src/umessage.rs create mode 100644 src/uprotocolheader.rs create mode 100644 src/utransport.rs create mode 100644 src/workers/command.rs create mode 100644 src/workers/dispatcher.rs create mode 100644 src/workers/mod.rs create mode 100644 src/workers/pubsub_worker.rs create mode 100644 src/workers/worker.rs diff --git a/.devcontainer/scripts/bashrc_addition.sh b/.devcontainer/scripts/bashrc_addition.sh index 6cde26d..fdb1296 100644 --- a/.devcontainer/scripts/bashrc_addition.sh +++ b/.devcontainer/scripts/bashrc_addition.sh @@ -35,6 +35,7 @@ Workspace Folder ${SECONDARY_COLOR}$WORKSPACE_FOLDER ${INFO_COLOR}Library Version(s)${PRIMARY_COLOR} Rust Version ${SECONDARY_COLOR}$(rustc --version)${PRIMARY_COLOR} Cargo Version ${SECONDARY_COLOR}$(cargo --version)${PRIMARY_COLOR} +Java Version ${SECONDARY_COLOR}$(java --version)${PRIMARY_COLOR} ${INFO_COLOR}Git${PRIMARY_COLOR} Username ${SECONDARY_COLOR}$(git config user.name)${PRIMARY_COLOR} diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..c296079 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,61 @@ +{ + "version": "0.2.0", + "configurations": [ + { + "type": "lldb", + "request": "launch", + "name": "Debug unit tests in library 'up_transport_iceoryx2_rust'", + "cargo": { + "args": [ + "test", + "--no-run", + "--lib", + "--package=up-transport-iceoryx2-rust" + ], + "filter": { + "name": "up_transport_iceoryx2_rust", + "kind": "lib" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug example 'subscriber'", + "cargo": { + "args": [ + "build", + "--example=subscriber", + "--package=up-transport-iceoryx2-rust" + ], + "filter": { + "name": "subscriber", + "kind": "example" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug unit tests in example 'subscriber'", + "cargo": { + "args": [ + "test", + "--no-run", + "--example=subscriber", + "--package=up-transport-iceoryx2-rust" + ], + "filter": { + "name": "subscriber", + "kind": "example" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + } + ] +} \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock index 2d7d142..83d6cda 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -101,10 +101,11 @@ checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" [[package]] name = "cc" -version = "1.2.26" +version = "1.2.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "956a5e21988b87f372569b66183b78babf23ebc2e744b733e4350a752c4dafac" +checksum = "590f9024a68a8c40351881787f1934dc11afd69090f5edb6831464694d836ea3" dependencies = [ + "find-msvc-tools", "shlex", ] @@ -193,21 +194,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" [[package]] -name = "fnv" -version = "1.0.7" +name = "find-msvc-tools" +version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" +checksum = "e178e4fba8a2726903f6ba98a6d221e76f9c12c650d5dc0e6afdc50677b49650" [[package]] -name = "getrandom" -version = "0.2.16" +name = "fnv" +version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" -dependencies = [ - "cfg-if", - "libc", - "wasi 0.11.0+wasi-snapshot-preview1", -] +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" [[package]] name = "getrandom" @@ -229,9 +225,9 @@ checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" [[package]] name = "glob" -version = "0.3.2" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8d1add55171497b4705a648c6b583acafb01d58050a51727785f0b2c8e0a2b2" +checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280" [[package]] name = "hashbrown" @@ -248,6 +244,17 @@ dependencies = [ "windows-sys 0.59.0", ] +[[package]] +name = "hostname" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a56f203cd1c76362b69e3863fd987520ac36cf70a8c92627449b2f64a8cf7d65" +dependencies = [ + "cfg-if", + "libc", + "windows-link", +] + [[package]] name = "iceoryx2" version = "0.6.1" @@ -442,6 +449,17 @@ dependencies = [ "hashbrown", ] +[[package]] +name = "io-uring" +version = "0.7.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "046fa2d4d00aea763528b4950358d0ead425372445dc8ff86312b3c69ff7727b" +dependencies = [ + "bitflags", + "cfg-if", + "libc", +] + [[package]] name = "itertools" version = "0.12.1" @@ -476,7 +494,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "07033963ba89ebaf1584d767badaa2e8fcec21aedea6b8c0346d487d49c28667" dependencies = [ "cfg-if", - "windows-targets 0.53.0", + "windows-targets 0.52.6", ] [[package]] @@ -499,9 +517,9 @@ checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" [[package]] name = "mediatype" -version = "0.19.20" +version = "0.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33746aadcb41349ec291e7f2f0a3aa6834d1d7c58066fb4b01f68efc4c4b7631" +checksum = "f490ea2ae935dd8ac89c472d4df28c7f6b87cc20767e1b21fd5ed6a16e7f61e4" [[package]] name = "memchr" @@ -524,6 +542,17 @@ dependencies = [ "adler2", ] +[[package]] +name = "mio" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78bed444cc8a2160f01cbcf811ef18cac863ad68ae8ca62092e8db51d51c761c" +dependencies = [ + "libc", + "wasi 0.11.0+wasi-snapshot-preview1", + "windows-sys 0.59.0", +] + [[package]] name = "nom" version = "7.1.3" @@ -572,9 +601,9 @@ dependencies = [ [[package]] name = "prettyplease" -version = "0.2.33" +version = "0.2.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9dee91521343f4c5c6a63edd65e54f31f5c92fe8978c40a4282f8372194c6a7d" +checksum = "6837b9e10d61f45f987d50808f83d1ee3d206c66acf650c3e4ae2e1f6ddedf55" dependencies = [ "proc-macro2", "syn", @@ -715,20 +744,19 @@ checksum = "74765f6d916ee2faa39bc8e68e4f3ed8949b48cccdac59983d287a7cb71ce9c5" [[package]] name = "rand" -version = "0.8.5" +version = "0.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1" dependencies = [ - "libc", "rand_chacha", "rand_core", ] [[package]] name = "rand_chacha" -version = "0.3.1" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" dependencies = [ "ppv-lite86", "rand_core", @@ -736,11 +764,11 @@ dependencies = [ [[package]] name = "rand_core" -version = "0.6.4" +version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38" dependencies = [ - "getrandom 0.2.16", + "getrandom", ] [[package]] @@ -851,6 +879,21 @@ version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" +[[package]] +name = "signal-hook-registry" +version = "1.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2a4719bff48cee6b39d12c020eeb490953ad2443b7055bd0b21fca26bd8c28b" +dependencies = [ + "libc", +] + +[[package]] +name = "slab" +version = "0.4.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a2ae44ef20feb57a68b23d846850f861394c2e02dc425a50098ae8c90267589" + [[package]] name = "syn" version = "2.0.101" @@ -869,7 +912,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e8a64e3985349f2441a1a9ef0b853f869006c3855f2cda6862a94d26ebb9d6a1" dependencies = [ "fastrand", - "getrandom 0.3.3", + "getrandom", "once_cell", "rustix 1.0.7", "windows-sys 0.59.0", @@ -907,18 +950,36 @@ dependencies = [ [[package]] name = "tiny-fn" -version = "0.1.7" +version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2fde9a76dac5751480f711f327371c809d7f8a9f036436e6237d67859adbf3bd" +checksum = "9659b108631d1e1cf3e8e489f894bee40bc9d68fd6cc67ec4d4ce9b72d565228" [[package]] name = "tokio" -version = "1.45.1" +version = "1.47.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75ef51a33ef1da925cea3e4eb122833cb377c61439ca401b770f54902b806779" +checksum = "89e49afdadebb872d3145a5638b59eb0691ea23e46ca484037cfab3b76b95038" dependencies = [ "backtrace", + "io-uring", + "libc", + "mio", "pin-project-lite", + "signal-hook-registry", + "slab", + "tokio-macros", + "windows-sys 0.59.0", +] + +[[package]] +name = "tokio-macros" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8" +dependencies = [ + "proc-macro2", + "quote", + "syn", ] [[package]] @@ -970,9 +1031,21 @@ checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" dependencies = [ "log", "pin-project-lite", + "tracing-attributes", "tracing-core", ] +[[package]] +name = "tracing-attributes" +version = "0.1.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81383ab64e72a7a8b8e13130c49e3dab29def6d0c7d76a03087b3cf71c5c6903" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "tracing-core" version = "0.1.34" @@ -990,9 +1063,9 @@ checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" [[package]] name = "up-rust" -version = "0.5.0" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "616da735a2c488128e67d5ce16113f4303c83a5dbeb7c281ae893da281df2a57" +checksum = "8744bbbd9090e901587401bdceb9b6369da4fe2925480587aa8e89e166552270" dependencies = [ "async-trait", "bytes", @@ -1013,7 +1086,11 @@ name = "up-transport-iceoryx2-rust" version = "0.1.0" dependencies = [ "async-trait", + "hostname", "iceoryx2", + "iceoryx2-bb-container", + "tokio", + "tracing", "up-rust", ] @@ -1092,6 +1169,12 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" +[[package]] +name = "windows-link" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a" + [[package]] name = "windows-sys" version = "0.48.0" @@ -1134,29 +1217,13 @@ dependencies = [ "windows_aarch64_gnullvm 0.52.6", "windows_aarch64_msvc 0.52.6", "windows_i686_gnu 0.52.6", - "windows_i686_gnullvm 0.52.6", + "windows_i686_gnullvm", "windows_i686_msvc 0.52.6", "windows_x86_64_gnu 0.52.6", "windows_x86_64_gnullvm 0.52.6", "windows_x86_64_msvc 0.52.6", ] -[[package]] -name = "windows-targets" -version = "0.53.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1e4c7e8ceaaf9cb7d7507c974735728ab453b67ef8f18febdd7c11fe59dca8b" -dependencies = [ - "windows_aarch64_gnullvm 0.53.0", - "windows_aarch64_msvc 0.53.0", - "windows_i686_gnu 0.53.0", - "windows_i686_gnullvm 0.53.0", - "windows_i686_msvc 0.53.0", - "windows_x86_64_gnu 0.53.0", - "windows_x86_64_gnullvm 0.53.0", - "windows_x86_64_msvc 0.53.0", -] - [[package]] name = "windows_aarch64_gnullvm" version = "0.48.5" @@ -1169,12 +1236,6 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.53.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86b8d5f90ddd19cb4a147a5fa63ca848db3df085e25fee3cc10b39b6eebae764" - [[package]] name = "windows_aarch64_msvc" version = "0.48.5" @@ -1187,12 +1248,6 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" -[[package]] -name = "windows_aarch64_msvc" -version = "0.53.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7651a1f62a11b8cbd5e0d42526e55f2c99886c77e007179efff86c2b137e66c" - [[package]] name = "windows_i686_gnu" version = "0.48.5" @@ -1205,24 +1260,12 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" -[[package]] -name = "windows_i686_gnu" -version = "0.53.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1dc67659d35f387f5f6c479dc4e28f1d4bb90ddd1a5d3da2e5d97b42d6272c3" - [[package]] name = "windows_i686_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" -[[package]] -name = "windows_i686_gnullvm" -version = "0.53.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ce6ccbdedbf6d6354471319e781c0dfef054c81fbc7cf83f338a4296c0cae11" - [[package]] name = "windows_i686_msvc" version = "0.48.5" @@ -1235,12 +1278,6 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" -[[package]] -name = "windows_i686_msvc" -version = "0.53.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "581fee95406bb13382d2f65cd4a908ca7b1e4c2f1917f143ba16efe98a589b5d" - [[package]] name = "windows_x86_64_gnu" version = "0.48.5" @@ -1253,12 +1290,6 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" -[[package]] -name = "windows_x86_64_gnu" -version = "0.53.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e55b5ac9ea33f2fc1716d1742db15574fd6fc8dadc51caab1c16a3d3b4190ba" - [[package]] name = "windows_x86_64_gnullvm" version = "0.48.5" @@ -1271,12 +1302,6 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.53.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0a6e035dd0599267ce1ee132e51c27dd29437f63325753051e71dd9e42406c57" - [[package]] name = "windows_x86_64_msvc" version = "0.48.5" @@ -1289,17 +1314,11 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" -[[package]] -name = "windows_x86_64_msvc" -version = "0.53.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486" - [[package]] name = "winnow" -version = "0.7.10" +version = "0.7.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c06928c8748d81b05c9be96aad92e1b6ff01833332f281e8cfca3be4b35fc9ec" +checksum = "21a0236b59786fed61e2a80582dd500fe61f18b5dca67a4a067d0bc9039339cf" dependencies = [ "memchr", ] diff --git a/Cargo.toml b/Cargo.toml index 9f7e8dd..fb3981a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,8 +15,13 @@ name = "up-transport-iceoryx2-rust" version = "0.1.0" edition = "2024" +keywords = ["uProtocol", "SDK", "communication", "iceoryx", "iceoryx2"] [dependencies] -up-rust = "0.5" -iceoryx2 = "0.6.1" +up-rust = "0.7" async-trait = "0.1" +tokio = { version = "1.47.1", features = ["macros", "rt-multi-thread", "signal"] } +iceoryx2 = "0.6.1" +iceoryx2-bb-container = "0.6.1" +tracing = "0.1.41" +hostname = "0.4.1" diff --git a/examples/common/helpers.rs b/examples/common/helpers.rs new file mode 100644 index 0000000..b01c779 --- /dev/null +++ b/examples/common/helpers.rs @@ -0,0 +1,61 @@ +// ################################################################################ +// Copyright (c) 2025 Contributors to the Eclipse Foundation +// +// See the NOTICE file(s) distributed with this work for additional +// information regarding copyright ownership. +// +// This program and the accompanying materials are made available under the +// terms of the Apache License Version 2.0 which is available at +// https: //www.apache.org/licenses/LICENSE-2.0 +// +// SPDX-License-Identifier: Apache-2.0 +// ################################################################################ + +use std::str::FromStr; +use up_rust::{UMessage, UStatus, UUri}; + +pub struct Helpers {} + +impl Helpers { + pub fn create_uuris(source: &str, sink: Option<&str>) -> (UUri, Option) { + let source_uuri = UUri::from_str(source).expect("Failed to create source UUri"); + let sink_uuri = sink.map(|sink| UUri::from_str(sink).expect("Failed to create sink UUri")); + (source_uuri, sink_uuri) + } + + pub fn print_that_subscriber_service_has_been_registered( + source_uuri: UUri, + sink_filter_uuri: Option, + ) -> Result<(), UStatus> { + let sink_str = match &sink_filter_uuri { + Some(sink) => sink.to_uri(false), + None => "".to_string(), + }; + print!("Listener added with source '{source_uuri}' and sink '{sink_str}' uri's"); + Ok(()) + } + + /// Simply prints the [`UMessage`] instances source uri, sink uri, and payload to STDOUT + pub fn print_umessage(msg: &UMessage) { + let payload_utf8 = msg.payload.as_ref().map(|p| String::from_utf8_lossy(p)); + let (source_uri, sink_uri) = Helpers::get_source_and_sink_uri(msg); + println!("Received a message!"); + println!("Source Uri: {source_uri:?}"); + println!("Sink Uri: {sink_uri:?}"); + println!("Payload: {payload_utf8:?}"); + } + + pub fn get_source_and_sink_uri(msg: &UMessage) -> (Option, Option) { + let source_uri = msg + .attributes + .as_ref() + .and_then(|a| a.source.as_ref()) + .map(|s| s.to_uri(false)); + let sink_uri = msg + .attributes + .as_ref() + .and_then(|a| a.sink.as_ref()) + .map(|s| s.to_uri(false)); + (source_uri, sink_uri) + } +} diff --git a/examples/common/mod.rs b/examples/common/mod.rs new file mode 100644 index 0000000..1630fab --- /dev/null +++ b/examples/common/mod.rs @@ -0,0 +1 @@ +pub mod helpers; diff --git a/examples/subscriber.rs b/examples/subscriber.rs new file mode 100644 index 0000000..41269fb --- /dev/null +++ b/examples/subscriber.rs @@ -0,0 +1,64 @@ +// ################################################################################ +// Copyright (c) 2025 Contributors to the Eclipse Foundation +// +// See the NOTICE file(s) distributed with this work for additional +// information regarding copyright ownership. +// +// This program and the accompanying materials are made available under the +// terms of the Apache License Version 2.0 which is available at +// https: //www.apache.org/licenses/LICENSE-2.0 +// +// SPDX-License-Identifier: Apache-2.0 +// ################################################################################ + +use async_trait::async_trait; +use iceoryx2::service::ipc; +use std::sync::Arc; +use tracing::info; +use up_rust::{UListener, UMessage}; +use up_transport_iceoryx2_rust::{transport::UTransportIceoryx2, workers::command::WorkerCommand}; + +mod common; +use crate::common::helpers::Helpers; + +struct SubscriberListener(tokio::runtime::Runtime); + +#[async_trait] +impl UListener for SubscriberListener { + /// Spawns a thread to process the received message. In this example, we simply print the message contents. + async fn on_receive(&self, msg: UMessage) { + self.0.spawn(async move { + Helpers::print_umessage(&msg); + }); + } +} + +/// This example sets up a single threaded runtime for the UTransport implementation of the Iceoryx2 service to run on +#[tokio::main(flavor = "multi_thread")] +async fn main() -> Result<(), Box> { + // Setup the UMessage, subscriber, and command to register the subscriber on a dedicated thread + // Iceoryx2 ipc::Service is used here, and does not support multi-threaded communication + // See ipc_threadsafe::Service for that use case + info!("uProtocols UTransportIceoryx2 subscriber example"); + // "//*/FFFFB1DA/1/8001" + let (source_filter, sink_filter) = Helpers::create_uuris("up://device1/10AB/3/80CD", None); + let (command_sender, _) = UTransportIceoryx2::::publish_subscribe(); + let runtime = tokio::runtime::Builder::new_multi_thread() + .thread_name("subscriber-example") + .worker_threads(1) + .build()?; + let ulistener = Arc::new(SubscriberListener(runtime)); + + let (result_sender, mut result_receiver) = tokio::sync::oneshot::channel(); + let register_listener_command = WorkerCommand::RegisterListener { + source_filter: source_filter.clone(), + sink_filter: sink_filter.clone(), + listener: ulistener, + result_sender, + }; + command_sender.send(register_listener_command).await?; + Helpers::print_that_subscriber_service_has_been_registered(source_filter, sink_filter)?; + let result = result_receiver.try_recv()?; + info!("Listener registration result: {result:?}"); + tokio::signal::ctrl_c().await.map_err(Box::from) +} diff --git a/src/lib.rs b/src/lib.rs index c9c2ee6..23fd5ce 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,232 +1,20 @@ // ################################################################################ // Copyright (c) 2025 Contributors to the Eclipse Foundation -// +// // See the NOTICE file(s) distributed with this work for additional // information regarding copyright ownership. -// +// // This program and the accompanying materials are made available under the // terms of the Apache License Version 2.0 which is available at // https: //www.apache.org/licenses/LICENSE-2.0 -// +// // SPDX-License-Identifier: Apache-2.0 // ################################################################################ -use async_trait::async_trait; -use std::sync::Arc; -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 { - async fn send(&self, _message: UMessage) -> Result<(), UStatus> { - todo!(); - } - - async fn register_listener( - &self, - _source_filter: &UUri, - _sink_filter: Option<&UUri>, - _listener: Arc, - ) -> Result<(), UStatus> { - todo!() - } - - async fn unregister_listener( - &self, - _source_filter: &UUri, - _sink_filter: Option<&UUri>, - _listener: Arc, - ) -> Result<(), UStatus> { - todo!() - } -} - -#[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)); - } -} +pub mod service_name_mapping; +pub mod transport; +pub mod types; +pub mod umessage; +pub mod uprotocolheader; +pub mod utransport; +pub mod workers; diff --git a/src/service_name_mapping.rs b/src/service_name_mapping.rs new file mode 100644 index 0000000..1d14a0c --- /dev/null +++ b/src/service_name_mapping.rs @@ -0,0 +1,214 @@ +// ################################################################################ +// Copyright (c) 2025 Contributors to the Eclipse Foundation +// +// See the NOTICE file(s) distributed with this work for additional +// information regarding copyright ownership. +// +// This program and the accompanying materials are made available under the +// terms of the Apache License Version 2.0 which is available at +// https: //www.apache.org/licenses/LICENSE-2.0 +// +// SPDX-License-Identifier: Apache-2.0 +// ################################################################################ + +use iceoryx2::prelude::{MessagingPattern, ServiceName}; +use up_rust::{UCode, UMessageType, UStatus, UUri}; + +pub struct ServiceNameMapper; + +/// uProtocol [`UUri`] to Iceoryx2 [`ServiceName`] mapping(s) +impl ServiceNameMapper { + fn encode_uuri_segments(uuri: &UUri) -> Vec { + vec![ + Self::get_authority_name(uuri), + 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!("{value:X}") + } + + fn get_authority_name(source_uuri: &UUri) -> String { + if source_uuri.authority_name.is_empty() { + match hostname::get().unwrap().into_string() { + Ok(hostname) => hostname, + Err(_) => "unknown".to_string(), + } + } else { + source_uuri.authority_name.clone() + } + } + + fn determine_message_type( + source: &UUri, + _sink: Option<&UUri>, + messaging_pattern: MessagingPattern, + ) -> Result { + // let src_id = source.resource_id; + // let sink_id = sink.map(|s| s.resource_id); + + if Self::is_a_publish(source, messaging_pattern) { + return Ok(UMessageType::UMESSAGE_TYPE_PUBLISH); + } + + Err(UStatus::fail_with_code( + UCode::INVALID_ARGUMENT, + "Could not determine a valid UMessageType from the provided UUri(s)", + )) + } + + fn is_a_publish(source: &UUri, messaging_pattern: MessagingPattern) -> bool { + source.is_empty() == false && messaging_pattern == MessagingPattern::PublishSubscribe + } + + pub fn compute_service_name( + source: &UUri, + sink: Option<&UUri>, + messaging_pattern: MessagingPattern, + ) -> Result { + let join_segments = |segments: Vec| segments.join("/"); + let message_type = Self::determine_message_type(source, sink, messaging_pattern)?; + let service_name_str = match message_type { + UMessageType::UMESSAGE_TYPE_REQUEST => { + let Some(sink_uri) = sink else { + return Err(UStatus::fail_with_code( + UCode::INVALID_ARGUMENT, + format!( + "sink required for UMessageType {:?}", + UMessageType::UMESSAGE_TYPE_REQUEST + ), + )); + }; + let segments = Self::encode_uuri_segments(sink_uri); + format!("up/{}", join_segments(segments)) + } + UMessageType::UMESSAGE_TYPE_RESPONSE | UMessageType::UMESSAGE_TYPE_NOTIFICATION => { + let Some(sink_uri) = sink else { + return Err(UStatus::fail_with_code( + UCode::INVALID_ARGUMENT, + format!( + "sink required for UMessageType {:?} or {:?}", + UMessageType::UMESSAGE_TYPE_RESPONSE, + UMessageType::UMESSAGE_TYPE_NOTIFICATION + ), + )); + }; + let source_segments = Self::encode_uuri_segments(source); + let sink_segments = Self::encode_uuri_segments(sink_uri); + format!( + "up/{}/{}", + join_segments(source_segments), + join_segments(sink_segments) + ) + } + UMessageType::UMESSAGE_TYPE_PUBLISH => { + let segments = Self::encode_uuri_segments(source); + format!("up/{}", join_segments(segments)) + } + _ => { + return Err(UStatus::fail_with_code( + UCode::INVALID_ARGUMENT, + "Unsupported UMessageType for service name computation", + )); + } + }; + Ok(ServiceName::new(service_name_str.as_str()).expect("Failed to create service name")) + } +} + +#[cfg(test)] +mod tests { + use crate::service_name_mapping::ServiceNameMapper; + use iceoryx2::prelude::MessagingPattern; + use up_rust::{UCode, UUri}; + + 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 = ServiceNameMapper::compute_service_name( + &source, + None, + MessagingPattern::PublishSubscribe, + ) + .unwrap(); + assert_eq!(name, "up/device1/10AB/0/3/7FFF"); + } + + // 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 = ServiceNameMapper::compute_service_name( + &uuri, + None, + MessagingPattern::PublishSubscribe, + ); + + 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 = ServiceNameMapper::compute_service_name( + &source, + Some(&sink), + MessagingPattern::PublishSubscribe, + ); + 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 = ServiceNameMapper::compute_service_name( + &source, + None, + MessagingPattern::RequestResponse, + ); + 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 = ServiceNameMapper::compute_service_name( + &uuri, + Some(&sink), + MessagingPattern::PublishSubscribe, + ); + assert!(result.is_err_and(|err| err.get_code() == UCode::INVALID_ARGUMENT)); + } +} diff --git a/src/transport.rs b/src/transport.rs new file mode 100644 index 0000000..80eaa0a --- /dev/null +++ b/src/transport.rs @@ -0,0 +1,48 @@ +// ################################################################################ +// Copyright (c) 2025 Contributors to the Eclipse Foundation +// +// See the NOTICE file(s) distributed with this work for additional +// information regarding copyright ownership. +// +// This program and the accompanying materials are made available under the +// terms of the Apache License Version 2.0 which is available at +// https: //www.apache.org/licenses/LICENSE-2.0 +// +// SPDX-License-Identifier: Apache-2.0 +// ################################################################################ + +use crate::workers::{command::WorkerCommand, dispatcher::Iceoryx2WorkerDispatcher}; +use iceoryx2::node::{Node, NodeBuilder}; +use tokio::{sync::mpsc::Sender, task::JoinHandle}; +use up_rust::UStatus; + +pub struct UTransportIceoryx2 { + pub(crate) node: Node, +} + +/// Acts as a uProtocol-specific interface for the Iceoryx2 transport system +impl UTransportIceoryx2 { + pub fn publish_subscribe() -> (Sender, JoinHandle>) { + let (command_sender, worker_thread_handle) = + Iceoryx2WorkerDispatcher::create_pubsub_worker(1024); + (command_sender, worker_thread_handle) + } + + pub(crate) fn default() -> Result, UStatus> { + let configure_fn: Option = None; + Self::create(configure_fn) + } + + fn create( + configure: Option, + ) -> Result, UStatus> { + let node_builder = NodeBuilder::new(); + if let Some(configure) = configure { + configure(&node_builder); + } + let node = node_builder + .create::() + .expect("Failed to create Iceoryx2 Node"); + Ok(UTransportIceoryx2 { node }) + } +} diff --git a/src/types.rs b/src/types.rs new file mode 100644 index 0000000..45248e6 --- /dev/null +++ b/src/types.rs @@ -0,0 +1,34 @@ +// ################################################################################ +// Copyright (c) 2025 Contributors to the Eclipse Foundation +// +// See the NOTICE file(s) distributed with this work for additional +// information regarding copyright ownership. +// +// This program and the accompanying materials are made available under the +// terms of the Apache License Version 2.0 which is available at +// https: //www.apache.org/licenses/LICENSE-2.0 +// +// SPDX-License-Identifier: Apache-2.0 +// ################################################################################ + +use iceoryx2::{ + port::{publisher::Publisher, subscriber::Subscriber}, + prelude::{ServiceName, ZeroCopySend}, + service::ipc, +}; +use std::{ + collections::{HashMap, HashSet}, + fmt::Debug, +}; +use up_rust::ComparableListener; + +use crate::{umessage::UMessageZeroCopy, uprotocolheader::UProtocolHeader}; + +pub trait BaseUserHeader: Debug + ZeroCopySend {} +pub trait BasePayload: Debug + ZeroCopySend {} + +pub(crate) type PublisherSet = + HashMap>; +pub(crate) type SubscriberSet = + HashMap>; +pub(crate) type ListenerMap = HashMap>; diff --git a/src/umessage.rs b/src/umessage.rs new file mode 100644 index 0000000..364a25e --- /dev/null +++ b/src/umessage.rs @@ -0,0 +1,25 @@ +// ################################################################################ +// Copyright (c) 2025 Contributors to the Eclipse Foundation +// +// See the NOTICE file(s) distributed with this work for additional +// information regarding copyright ownership. +// +// This program and the accompanying materials are made available under the +// terms of the Apache License Version 2.0 which is available at +// https: //www.apache.org/licenses/LICENSE-2.0 +// +// SPDX-License-Identifier: Apache-2.0 +// ################################################################################ + +use std::fmt::Debug; +use iceoryx2::prelude::ZeroCopySend; +use up_rust::UMessage; + +#[derive(Debug)] +pub struct UMessageZeroCopy(pub UMessage); + +unsafe impl ZeroCopySend for UMessageZeroCopy { + unsafe fn type_name() -> &'static str { + core::any::type_name::() + } +} diff --git a/src/uprotocolheader.rs b/src/uprotocolheader.rs new file mode 100644 index 0000000..119416f --- /dev/null +++ b/src/uprotocolheader.rs @@ -0,0 +1,26 @@ +// ################################################################################ +// Copyright (c) 2025 Contributors to the Eclipse Foundation +// +// See the NOTICE file(s) distributed with this work for additional +// information regarding copyright ownership. +// +// This program and the accompanying materials are made available under the +// terms of the Apache License Version 2.0 which is available at +// https: //www.apache.org/licenses/LICENSE-2.0 +// +// SPDX-License-Identifier: Apache-2.0 +// ################################################################################ + +use iceoryx2::prelude::ZeroCopySend; +use iceoryx2_bb_container::vec::FixedSizeVec; + +const MAX_FEASIBLE_UATTRIBUTES_SERIALIZED_LENGTH: usize = 1000; // choosing ~1000 u8s +// somewhat arbitrarily +// this should be confirmed + +#[repr(C)] +#[derive(ZeroCopySend, Debug)] +pub struct UProtocolHeader { + uprotocol_major_version: u8, + uattributes_serialized: FixedSizeVec, +} diff --git a/src/utransport.rs b/src/utransport.rs new file mode 100644 index 0000000..e38dbf4 --- /dev/null +++ b/src/utransport.rs @@ -0,0 +1,81 @@ +// ################################################################################ +// Copyright (c) 2025 Contributors to the Eclipse Foundation +// +// See the NOTICE file(s) distributed with this work for additional +// information regarding copyright ownership. +// +// This program and the accompanying materials are made available under the +// terms of the Apache License Version 2.0 which is available at +// https: //www.apache.org/licenses/LICENSE-2.0 +// +// SPDX-License-Identifier: Apache-2.0 +// ################################################################################ + +use std::sync::Arc; + +use async_trait::async_trait; +use tokio::sync::oneshot; +use up_rust::{UCode, UListener, UMessage, UStatus, UTransport, UUri}; + +use crate::workers::{command::WorkerCommand, dispatcher::Iceoryx2WorkerDispatcher}; + +#[async_trait] +impl UTransport for Iceoryx2WorkerDispatcher { + async fn send(&self, message: UMessage) -> Result<(), UStatus> { + let (tx, rx) = oneshot::channel::>(); + let command = WorkerCommand::Send { + message, + result_sender: tx, + }; + self.command_sender + .send(command) + .map_err(|_| UStatus::fail_with_code(UCode::INTERNAL, "Background task has died"))?; + rx.blocking_recv().map_err(|_| { + UStatus::fail_with_code(UCode::INTERNAL, "Background task response failed") + })? + } + + async fn register_listener( + &self, + source_filter: &UUri, + sink_filter: Option<&UUri>, + listener: Arc, + ) -> Result<(), UStatus> { + up_rust::verify_filter_criteria(source_filter, sink_filter)?; + let (tx, rx) = oneshot::channel::>(); + let command = WorkerCommand::RegisterListener { + source_filter: source_filter.clone(), + sink_filter: sink_filter.cloned(), + listener, + result_sender: tx, + }; + self.command_sender + .send(command) + .map_err(|_| UStatus::fail_with_code(UCode::INTERNAL, "Background task has died"))?; + rx.blocking_recv().map_err(|_| { + UStatus::fail_with_code(UCode::INTERNAL, "Background task response failed") + })? + } + + async fn unregister_listener( + &self, + source_filter: &UUri, + sink_filter: Option<&UUri>, + listener: Arc, + ) -> Result<(), UStatus> { + up_rust::verify_filter_criteria(source_filter, sink_filter)?; + let (tx, rx) = oneshot::channel::>(); + let command = WorkerCommand::UnregisterListener { + source_filter: source_filter.clone(), + sink_filter: sink_filter.cloned(), + listener, + result_sender: tx, + }; + self.command_sender + .send(command) + .map_err(|_| UStatus::fail_with_code(UCode::INTERNAL, "Background task has died"))?; + rx.blocking_recv().map_err(|_| { + UStatus::fail_with_code(UCode::INTERNAL, "Background task response failed") + })? + } +} diff --git a/src/workers/command.rs b/src/workers/command.rs new file mode 100644 index 0000000..8e6817e --- /dev/null +++ b/src/workers/command.rs @@ -0,0 +1,35 @@ +// ################################################################################ +// Copyright (c) 2025 Contributors to the Eclipse Foundation +// +// See the NOTICE file(s) distributed with this work for additional +// information regarding copyright ownership. +// +// This program and the accompanying materials are made available under the +// terms of the Apache License Version 2.0 which is available at +// https: //www.apache.org/licenses/LICENSE-2.0 +// +// SPDX-License-Identifier: Apache-2.0 +// ################################################################################ + +use std::sync::Arc; +use tokio::sync::oneshot; +use up_rust::{UListener, UMessage, UStatus, UUri}; + +pub enum WorkerCommand { + Send { + message: UMessage, + result_sender: oneshot::Sender>, + }, + RegisterListener { + source_filter: UUri, + sink_filter: Option, + listener: Arc, + result_sender: oneshot::Sender>, + }, + UnregisterListener { + source_filter: UUri, + sink_filter: Option, + listener: Arc, + result_sender: oneshot::Sender>, + }, +} diff --git a/src/workers/dispatcher.rs b/src/workers/dispatcher.rs new file mode 100644 index 0000000..314bfc4 --- /dev/null +++ b/src/workers/dispatcher.rs @@ -0,0 +1,119 @@ +// ################################################################################ +// Copyright (c) 2025 Contributors to the Eclipse Foundation +// +// See the NOTICE file(s) distributed with this work for additional +// information regarding copyright ownership. +// +// This program and the accompanying materials are made available under the +// terms of the Apache License Version 2.0 which is available at +// https: //www.apache.org/licenses/LICENSE-2.0 +// +// SPDX-License-Identifier: Apache-2.0 +// ################################################################################ + +use iceoryx2::prelude::MessagingPattern; +use std::sync::{Arc, atomic::Ordering}; +use tokio::{ + sync::{ + Mutex, + mpsc::{Receiver, Sender}, + }, + task::JoinHandle, +}; +use up_rust::{UCode, UStatus}; + +use crate::{ + transport::UTransportIceoryx2, + workers::{ + command::WorkerCommand, pubsub_worker::Iceoryx2PubSubWorker, worker::Iceoryx2Worker, + }, +}; + +pub struct Iceoryx2WorkerDispatcher { + pub command_sender: std::sync::mpsc::Sender, + pub messaging_pattern: MessagingPattern, + pub handle: JoinHandle>, +} + +impl Iceoryx2WorkerDispatcher { + pub fn create_pubsub_worker( + buffer_size: usize, + ) -> (Sender, JoinHandle>) { + let (tx, rx) = tokio::sync::mpsc::channel::(buffer_size); + let threadsafe_rx = Arc::new(Mutex::new(rx)); + let handle = tokio::spawn(async { + let future = Iceoryx2WorkerDispatcher::run(threadsafe_rx); + + let current_thread_runtime = tokio::runtime::Builder::new_current_thread() + .enable_all() + .build() + .map_err(|e| { + UStatus::fail_with_code( + UCode::INTERNAL, + format!("Failed to build current_thread runtime: {e}"), + ) + })?; + current_thread_runtime.block_on(future) + }); + (tx, handle) + } + + async fn run(rx: Arc>>) -> Result<(), UStatus> { + // non-threadsafe state goes here + // any iceoryx2 transports that use the `ipc::Service` service implementation is not threadsafe + let transport = UTransportIceoryx2::default()?; + let mut worker = + Iceoryx2PubSubWorker::new(rx, transport, MessagingPattern::PublishSubscribe); + // + while worker.keep_alive().load(Ordering::Relaxed) { + let command_receiver = worker.get_command_receiver().clone(); + let mut command_receiver = command_receiver.lock().await; + if let Ok(command) = command_receiver.try_recv() { + Iceoryx2WorkerDispatcher::process_command(&mut worker, command)?; + } + worker.receive_and_notify_listeners().await?; + // .map_err(|e| UStatus::fail_with_code(UCode::INTERNAL, e.to_string()))?; + } + Ok(()) + } + + fn process_command( + worker: &mut Worker, + command: WorkerCommand, + ) -> Result<(), UStatus> { + let channel_response = match command { + WorkerCommand::Send { + message, + result_sender, + } => { + let result = worker.send(message); + result_sender.send(result) + } + WorkerCommand::RegisterListener { + source_filter, + sink_filter, + listener, + result_sender, + } => { + let result = worker.register_listener(source_filter, sink_filter, listener); + result_sender.send(result) + } + WorkerCommand::UnregisterListener { + source_filter, + sink_filter, + listener, + result_sender, + } => { + let result = worker.unregister_listener(source_filter, sink_filter, listener); + result_sender.send(result) + } + }; + match channel_response { + Err(_) => Err(UStatus::fail_with_code( + UCode::INTERNAL, + "Failed to send result of command through the response channel", + )), + Ok(_) => Ok(()), + } + } +} diff --git a/src/workers/mod.rs b/src/workers/mod.rs new file mode 100644 index 0000000..bce702e --- /dev/null +++ b/src/workers/mod.rs @@ -0,0 +1,17 @@ +// ################################################################################ +// Copyright (c) 2025 Contributors to the Eclipse Foundation +// +// See the NOTICE file(s) distributed with this work for additional +// information regarding copyright ownership. +// +// This program and the accompanying materials are made available under the +// terms of the Apache License Version 2.0 which is available at +// https: //www.apache.org/licenses/LICENSE-2.0 +// +// SPDX-License-Identifier: Apache-2.0 +// ################################################################################ + +pub mod command; +pub mod dispatcher; +pub mod pubsub_worker; +pub mod worker; diff --git a/src/workers/pubsub_worker.rs b/src/workers/pubsub_worker.rs new file mode 100644 index 0000000..f9b3a31 --- /dev/null +++ b/src/workers/pubsub_worker.rs @@ -0,0 +1,224 @@ +// ################################################################################ +// Copyright (c) 2025 Contributors to the Eclipse Foundation +// +// See the NOTICE file(s) distributed with this work for additional +// information regarding copyright ownership. +// +// This program and the accompanying materials are made available under the +// terms of the Apache License Version 2.0 which is available at +// https: //www.apache.org/licenses/LICENSE-2.0 +// +// SPDX-License-Identifier: Apache-2.0 +// ################################################################################ + +use iceoryx2::{ + port::{publisher::Publisher, subscriber::Subscriber}, + prelude::ServiceName, + service::ipc, +}; +use std::{ + collections::HashMap, + sync::{Arc, atomic::AtomicBool}, +}; +use tokio::sync::{Mutex, mpsc::Receiver}; +use up_rust::{ComparableListener, UCode, UStatus, UUri}; + +use crate::{ + service_name_mapping::ServiceNameMapper, + transport::UTransportIceoryx2, + types::{ListenerMap, PublisherSet, SubscriberSet}, + umessage::UMessageZeroCopy, + uprotocolheader::UProtocolHeader, + workers::{command::WorkerCommand, worker::Iceoryx2Worker}, +}; + +pub struct Iceoryx2PubSubWorker { + command_receiver: Arc>>, + keep_alive: Arc, + transport: UTransportIceoryx2, + messaging_pattern: iceoryx2::prelude::MessagingPattern, + publishers: PublisherSet, + subscribers: SubscriberSet, + listeners: ListenerMap, +} + +impl Iceoryx2PubSubWorker { + pub fn new( + rx: Arc>>, + transport: UTransportIceoryx2, + messaging_pattern: iceoryx2::prelude::MessagingPattern, + ) -> Self { + Self { + transport, + command_receiver: rx, + keep_alive: Arc::new(AtomicBool::new(true)), + messaging_pattern, + publishers: HashMap::new(), + subscribers: HashMap::new(), + listeners: HashMap::new(), + } + } +} + +impl Iceoryx2PubSubWorker { + fn create_subscriber( + &self, + ) -> Result, UStatus> { + // Placeholder implementation + let service_name: ServiceName = "example_service".try_into().unwrap(); + let service = self + .transport + .node + .service_builder(&service_name) + .publish_subscribe::() + .user_header::() + .open_or_create() + .map_err(|e| { + UStatus::fail_with_code(UCode::INTERNAL, format!("Failed to create service: {e}")) + })?; + let subscriber = service.subscriber_builder().create().map_err(|e| { + UStatus::fail_with_code(UCode::INTERNAL, format!("Failed to create subscriber: {e}")) + })?; + Ok(subscriber) + } + + fn create_publisher( + &mut self, + service_name: ServiceName, + ) -> Result<&Publisher, UStatus> { + if self.publishers.contains_key(&service_name) { + return Ok(self.publishers.get(&service_name).unwrap()); + } + let service_name_res: Result = service_name.as_str().try_into(); + let service = self + .transport + .node + .service_builder(&service_name_res.unwrap()) + .publish_subscribe::() + .user_header::() + .open_or_create() + .map_err(|e| { + UStatus::fail_with_code(UCode::INTERNAL, format!("Failed to create service: {e}")) + })?; + + let publisher = service.publisher_builder().create().map_err(|e| { + UStatus::fail_with_code(UCode::INTERNAL, format!("Failed to create publisher: {e}")) + })?; + self.publishers.insert(service_name.clone(), publisher); + Ok(self.publishers.get(&service_name).unwrap()) + } +} + +impl Iceoryx2Worker for Iceoryx2PubSubWorker { + fn send(&mut self, message: up_rust::UMessage) -> Result<(), UStatus> { + let service_name = { + let source_filter = &message.attributes.source; + let sink_filter = message.attributes.sink.as_ref(); + ServiceNameMapper::compute_service_name( + source_filter, + sink_filter, + self.messaging_pattern, + )? + }; + let publisher = self.create_publisher(service_name)?; + let message = UMessageZeroCopy(message); + let sample = publisher.loan_uninit().map_err(|e| { + UStatus::fail_with_code(UCode::INTERNAL, format!("Failed to loan sample: {e}")) + })?; + let sample_final = sample.write_payload(message); + sample_final.send().map_err(|e| { + UStatus::fail_with_code(UCode::INTERNAL, format!("Failed to send: {e}")) + })?; + Ok(()) + } + + fn register_listener( + &mut self, + source_filter: UUri, + sink_filter: Option, + listener: Arc, + ) -> Result<(), up_rust::UStatus> { + let service_name = ServiceNameMapper::compute_service_name( + &source_filter, + sink_filter.as_ref(), + self.messaging_pattern, + )?; + if !self.subscribers.contains_key(&service_name) { + let subscriber = self.create_subscriber()?; + self.subscribers.insert(service_name.clone(), subscriber); + } + self.listeners + .entry(service_name) + .or_default() + .insert(ComparableListener::new(listener)); + Ok(()) + } + + fn unregister_listener( + &mut self, + source_filter: UUri, + sink_filter: Option, + listener: Arc, + ) -> Result<(), up_rust::UStatus> { + let service_name = ServiceNameMapper::compute_service_name( + &source_filter, + sink_filter.as_ref(), + self.messaging_pattern, + )?; + let comparable_listener = ComparableListener::new(listener.clone()); + if let Some(existing_listeners) = self.listeners.get_mut(&service_name) { + existing_listeners.retain(|l| !l.eq(&comparable_listener)); + + if existing_listeners.is_empty() { + self.listeners.remove(&service_name); + self.subscribers.remove(&service_name); + } + } + Ok(()) + } + + fn get_command_receiver(&self) -> Arc>> { + self.command_receiver.clone() + } + + fn keep_alive(&self) -> &std::sync::atomic::AtomicBool { + &self.keep_alive + } + + async fn receive_and_notify_listeners(&self) -> Result<(), UStatus> { + let current_thread_runtime = tokio::runtime::Builder::new_current_thread() + .enable_all() + .build() + .map_err(|e| { + UStatus::fail_with_code( + UCode::INTERNAL, + format!("Failed to build current_thread runtime: {e}"), + ) + })?; + for (service_name, subscriber) in self.subscribers.iter() { + match subscriber.receive() { + Ok(Some(sample)) => { + let payload = sample; + if let Some(listeners_to_notify) = self.listeners.get(service_name) { + for listener in listeners_to_notify.iter() { + let listener: &ComparableListener = listener; + // FIXME + // How would this be done without cloning the payload? + // Pretty sure the whole point of using iceoryx2 was to prevent cloning samples + // Maybe I'm misunderstanding the clone function right now :( + current_thread_runtime.block_on(listener.on_receive(payload.0.clone())); + } + } + } + Ok(None) => continue, // No sample available + Err(e) => { + return Err(UStatus::fail_with_code( + UCode::INTERNAL, + format!("Failed to receive sample: {e}"), + )); + } + } + } + Ok(()) + } +} diff --git a/src/workers/worker.rs b/src/workers/worker.rs new file mode 100644 index 0000000..779f37a --- /dev/null +++ b/src/workers/worker.rs @@ -0,0 +1,46 @@ +// ################################################################################ +// Copyright (c) 2025 Contributors to the Eclipse Foundation +// +// See the NOTICE file(s) distributed with this work for additional +// information regarding copyright ownership. +// +// This program and the accompanying materials are made available under the +// terms of the Apache License Version 2.0 which is available at +// https: //www.apache.org/licenses/LICENSE-2.0 +// +// SPDX-License-Identifier: Apache-2.0 +// ################################################################################ + +use std::sync::{Arc, atomic::AtomicBool}; +use tokio::sync::{Mutex, mpsc::Receiver}; +use up_rust::{UListener, UMessage, UStatus, UUri}; + +use crate::workers::command::WorkerCommand; + +pub(crate) trait Iceoryx2Worker { + // begin UTransport wrapper methods + + fn send(&mut self, message: UMessage) -> Result<(), UStatus>; + + fn register_listener( + &mut self, + source_filter: UUri, + sink_filter: Option, + listener: Arc, + ) -> Result<(), UStatus>; + + fn unregister_listener( + &mut self, + source_filter: UUri, + sink_filter: Option, + listener: Arc, + ) -> Result<(), UStatus>; + + // end UTransport wrapper methods + + fn get_command_receiver(&self) -> Arc>>; + + fn keep_alive(&self) -> &AtomicBool; + + async fn receive_and_notify_listeners(&self) -> Result<(), UStatus>; +} From 635835eca4bdfe380b0e846e710adb334a08e5b4 Mon Sep 17 00:00:00 2001 From: Kenneth Mead Date: Sun, 14 Sep 2025 17:39:05 +0000 Subject: [PATCH 2/7] cleaned up launch.json and removed unclear unit test --- .vscode/launch.json | 19 ------------------- src/service_name_mapping.rs | 20 -------------------- 2 files changed, 39 deletions(-) diff --git a/.vscode/launch.json b/.vscode/launch.json index c296079..68554f5 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -37,25 +37,6 @@ }, "args": [], "cwd": "${workspaceFolder}" - }, - { - "type": "lldb", - "request": "launch", - "name": "Debug unit tests in example 'subscriber'", - "cargo": { - "args": [ - "test", - "--no-run", - "--example=subscriber", - "--package=up-transport-iceoryx2-rust" - ], - "filter": { - "name": "subscriber", - "kind": "example" - } - }, - "args": [], - "cwd": "${workspaceFolder}" } ] } \ No newline at end of file diff --git a/src/service_name_mapping.rs b/src/service_name_mapping.rs index 1d14a0c..f5906ec 100644 --- a/src/service_name_mapping.rs +++ b/src/service_name_mapping.rs @@ -48,9 +48,6 @@ impl ServiceNameMapper { _sink: Option<&UUri>, messaging_pattern: MessagingPattern, ) -> Result { - // let src_id = source.resource_id; - // let sink_id = sink.map(|s| s.resource_id); - if Self::is_a_publish(source, messaging_pattern) { return Ok(UMessageType::UMESSAGE_TYPE_PUBLISH); } @@ -165,23 +162,6 @@ mod tests { 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 = ServiceNameMapper::compute_service_name( - &source, - Some(&sink), - MessagingPattern::PublishSubscribe, - ); - 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] From 94f0645ba4f23a2dbd3a3fb851b5b694b9196db3 Mon Sep 17 00:00:00 2001 From: Kenneth Mead Date: Mon, 15 Sep 2025 03:26:27 +0000 Subject: [PATCH 3/7] send, register_listener, and unregister_listener with iceoryx2 0.7.0 --- Cargo.lock | 175 ++++++++++++++------------ Cargo.toml | 4 +- examples/common/helpers.rs | 67 ++++------ examples/subscriber.rs | 46 +++---- src/lib.rs | 36 +++++- src/service_name_mapping.rs | 202 ++++++++++++++---------------- src/transport.rs | 45 +++---- src/types.rs | 34 ------ src/uprotocolheader.rs | 2 +- src/utransport.rs | 81 ------------ src/utransport_pubsub.rs | 230 +++++++++++++++++++++++++++++++++++ src/workers/command.rs | 35 ------ src/workers/dispatcher.rs | 106 ++-------------- src/workers/mod.rs | 6 +- src/workers/pubsub_worker.rs | 224 ---------------------------------- src/workers/worker.rs | 40 ++---- 16 files changed, 530 insertions(+), 803 deletions(-) delete mode 100644 src/types.rs delete mode 100644 src/utransport.rs create mode 100644 src/utransport_pubsub.rs delete mode 100644 src/workers/command.rs delete mode 100644 src/workers/pubsub_worker.rs diff --git a/Cargo.lock b/Cargo.lock index 83d6cda..adb229a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -60,16 +60,14 @@ dependencies = [ [[package]] name = "bindgen" -version = "0.69.5" +version = "0.72.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "271383c67ccabffb7381723dea0672a673f292304fcb45c01cc648c7a8d58088" +checksum = "993776b509cfb49c750f11b8f07a46fa23e0a1386ffc01fb1e7d343efc387895" dependencies = [ "bitflags", "cexpr", "clang-sys", "itertools", - "lazy_static", - "lazycell", "log", "prettyplease", "proc-macro2", @@ -78,7 +76,6 @@ dependencies = [ "rustc-hash", "shlex", "syn", - "which", ] [[package]] @@ -145,12 +142,33 @@ dependencies = [ "libloading", ] +[[package]] +name = "cobs" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fa961b519f0b462e3a3b4a34b64d119eeaca1d59af726fe450bbba07a9fc0a1" +dependencies = [ + "thiserror 2.0.16", +] + [[package]] name = "either" version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" +[[package]] +name = "embedded-io" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef1a6892d9eef45c8fa6b9e0086428a2cca8491aca8f787c534a3d6d0bcb3ced" + +[[package]] +name = "embedded-io" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edd0f118536f44f5ccd48bcb8b111bdc3de888b58c74639dfb034a357d0f206d" + [[package]] name = "enum-iterator" version = "2.1.0" @@ -257,13 +275,14 @@ dependencies = [ [[package]] name = "iceoryx2" -version = "0.6.1" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a462c1baccde41be91001f7b36cb1d1afe313c2afdb5a6c16174ae7ca917b6ef" +checksum = "d2674b41c16478e52a05a4da49555205ccdd2f5fc153faee5bbf58484149a670" dependencies = [ "iceoryx2-bb-container", "iceoryx2-bb-derive-macros", "iceoryx2-bb-elementary", + "iceoryx2-bb-elementary-traits", "iceoryx2-bb-lock-free", "iceoryx2-bb-log", "iceoryx2-bb-memory", @@ -271,6 +290,7 @@ dependencies = [ "iceoryx2-bb-system-types", "iceoryx2-cal", "iceoryx2-pal-concurrency-sync", + "iceoryx2-pal-configuration", "serde", "tiny-fn", "toml", @@ -278,12 +298,13 @@ dependencies = [ [[package]] name = "iceoryx2-bb-container" -version = "0.6.1" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6205871fd23e67215ec4dff6b9895f7526325b498b2eb759b93399ee69d6d89" +checksum = "1436071c05750d19dc6dfe78dae6823cef0fe6d902fc5ed9b3626af0bdcb72a5" dependencies = [ "iceoryx2-bb-derive-macros", "iceoryx2-bb-elementary", + "iceoryx2-bb-elementary-traits", "iceoryx2-bb-log", "iceoryx2-pal-concurrency-sync", "serde", @@ -291,11 +312,12 @@ dependencies = [ [[package]] name = "iceoryx2-bb-derive-macros" -version = "0.6.1" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "468faacface5d0252a090d565d9da86c05dc77e822abebdc0affeaad9021cce6" +checksum = "574ab1305d422a19889ae8382347a3e0281654fa694aacd8e6953b41e0ed95a9" dependencies = [ "iceoryx2-bb-elementary", + "iceoryx2-bb-elementary-traits", "proc-macro2", "quote", "syn", @@ -303,9 +325,9 @@ dependencies = [ [[package]] name = "iceoryx2-bb-elementary" -version = "0.6.1" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9af6479bb3aed7cfb398e54e91a769629d64f5bc2ede8c6a528ee02daa1675a5" +checksum = "df7def5420ba8eac9210c98ca2743fc08b568de5a5802dc83079ebc406d02c12" dependencies = [ "iceoryx2-bb-elementary-traits", "iceoryx2-pal-concurrency-sync", @@ -313,41 +335,42 @@ dependencies = [ [[package]] name = "iceoryx2-bb-elementary-traits" -version = "0.6.1" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dfb55d8c8b866531332904fd5a7041355d67d91d722f2e22850c1364ca779d30" +checksum = "b5baf086b219796879a97414094e3cc4c98a8024cb3e6471e2cf9d3d407b6f1d" dependencies = [ "iceoryx2-pal-concurrency-sync", ] [[package]] name = "iceoryx2-bb-lock-free" -version = "0.6.1" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f9e850529efc04e4f70789fc8a8016883cda0ee9b88a078f0b6d296cc966b11" +checksum = "998e662cd56cd477f0ff3f7f5e3f28dbdf836579de036cfe0ec9edf465ec63d6" dependencies = [ "iceoryx2-bb-elementary", + "iceoryx2-bb-elementary-traits", "iceoryx2-bb-log", "iceoryx2-pal-concurrency-sync", ] [[package]] name = "iceoryx2-bb-log" -version = "0.6.1" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d54d933cadfd6ac326dbce588fa5e1203b1b85234899a1742ad16a64490631dd" +checksum = "d98aca37ed1a534d6ecef046f57be2cc9435bf8f222bae86b83c9f01f3adf7be" dependencies = [ "iceoryx2-pal-concurrency-sync", - "termsize", ] [[package]] name = "iceoryx2-bb-memory" -version = "0.6.1" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "45b03e120e45d869979da01f894c226ed24c49feb3a318acbb7106021962d205" +checksum = "2468bcaa25831c80439403d9673f42689177d4a2d4ebe6cd63dc684a44afe973" dependencies = [ "iceoryx2-bb-elementary", + "iceoryx2-bb-elementary-traits", "iceoryx2-bb-lock-free", "iceoryx2-bb-log", "iceoryx2-bb-posix", @@ -356,14 +379,15 @@ dependencies = [ [[package]] name = "iceoryx2-bb-posix" -version = "0.6.1" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc53c019df6e0aa5df7bf8052cc11064a4ba19fe7bc34671f8ce13c2eb7ac3ab" +checksum = "2f33015988d54a7294e2a184066cb73cd52103d5b6b7ba6dcf1163553ba16763" dependencies = [ "enum-iterator", "iceoryx2-bb-container", "iceoryx2-bb-derive-macros", "iceoryx2-bb-elementary", + "iceoryx2-bb-elementary-traits", "iceoryx2-bb-log", "iceoryx2-bb-system-types", "iceoryx2-pal-concurrency-sync", @@ -376,13 +400,14 @@ dependencies = [ [[package]] name = "iceoryx2-bb-system-types" -version = "0.6.1" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "346e9593393627c2cbc17b8663a0650c8e33377276f2e06e10346816705ae5cb" +checksum = "7ff38eb9362cc3268ad0225d805cf7fba2ac1800123d65b6e951dff8c3b6fd20" dependencies = [ "iceoryx2-bb-container", "iceoryx2-bb-derive-macros", "iceoryx2-bb-elementary", + "iceoryx2-bb-elementary-traits", "iceoryx2-bb-log", "iceoryx2-pal-configuration", "iceoryx2-pal-posix", @@ -391,14 +416,15 @@ dependencies = [ [[package]] name = "iceoryx2-cal" -version = "0.6.1" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c9941d8beb989b80b581d2a6871e4b464b695bc1f86076117be1eb9c74f3dcb" +checksum = "1708090d319ebe74a941c1b0297154a2dfccdf69a139e51fcf930f44326f1be2" dependencies = [ "cdr", "iceoryx2-bb-container", "iceoryx2-bb-derive-macros", "iceoryx2-bb-elementary", + "iceoryx2-bb-elementary-traits", "iceoryx2-bb-lock-free", "iceoryx2-bb-log", "iceoryx2-bb-memory", @@ -406,6 +432,7 @@ dependencies = [ "iceoryx2-bb-system-types", "iceoryx2-pal-concurrency-sync", "once_cell", + "postcard", "serde", "sha1_smol", "tiny-fn", @@ -414,21 +441,21 @@ dependencies = [ [[package]] name = "iceoryx2-pal-concurrency-sync" -version = "0.6.1" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6bc07c3be1bce2afef70dc5597abe14bdadde367f759243e92325a96733163d6" +checksum = "d2d54284a074dea1fd7070eae77b001833a576b39b519d6b0586341c4486a0c8" [[package]] name = "iceoryx2-pal-configuration" -version = "0.6.1" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0a94e78e84f673da8ee4b46cb5df643792b046b7118678da9969fee29cf15c68" +checksum = "3bdb14bb2cda075b3190a2c41b1a0eff85842fe0ac8d1eaa10b68f9cde4a7fe7" [[package]] name = "iceoryx2-pal-posix" -version = "0.6.1" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5616e41d3171df75d1ab9577d5bccdc4fb95102e275db2afd84a9828a0a01918" +checksum = "fd2ff4c48a8b992baabb09a573db9446bff89e39a193500dd5c8f8196c241c63" dependencies = [ "bindgen", "cc", @@ -475,12 +502,6 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" -[[package]] -name = "lazycell" -version = "1.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" - [[package]] name = "libc" version = "0.2.172" @@ -590,6 +611,18 @@ version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" +[[package]] +name = "postcard" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6764c3b5dd454e283a30e6dfe78e9b31096d9e32036b5d1eaac7a6119ccb9a24" +dependencies = [ + "cobs", + "embedded-io 0.4.0", + "embedded-io 0.6.1", + "serde", +] + [[package]] name = "ppv-lite86" version = "0.2.21" @@ -627,7 +660,7 @@ dependencies = [ "bytes", "once_cell", "protobuf-support", - "thiserror", + "thiserror 1.0.69", ] [[package]] @@ -642,7 +675,7 @@ dependencies = [ "protobuf-parse", "regex", "tempfile", - "thiserror", + "thiserror 1.0.69", ] [[package]] @@ -657,7 +690,7 @@ dependencies = [ "protobuf", "protobuf-support", "tempfile", - "thiserror", + "thiserror 1.0.69", "which", ] @@ -667,7 +700,7 @@ version = "3.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3e36c2f31e0a47f9280fb347ef5e461ffcd2c52dd520d8e216b52f93b0b0d7d6" dependencies = [ - "thiserror", + "thiserror 1.0.69", ] [[package]] @@ -808,9 +841,9 @@ checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" [[package]] name = "rustc-hash" -version = "1.1.0" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" +checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d" [[package]] name = "rustix" @@ -919,22 +952,21 @@ dependencies = [ ] [[package]] -name = "termsize" -version = "0.1.9" +name = "thiserror" +version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f11ff5c25c172608d5b85e2fb43ee9a6d683a7f4ab7f96ae07b3d8b590368fd" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" dependencies = [ - "libc", - "winapi", + "thiserror-impl 1.0.69", ] [[package]] name = "thiserror" -version = "1.0.69" +version = "2.0.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" +checksum = "3467d614147380f2e4e374161426ff399c91084acd2363eaf549172b3d5e60c0" dependencies = [ - "thiserror-impl", + "thiserror-impl 2.0.16", ] [[package]] @@ -948,6 +980,17 @@ dependencies = [ "syn", ] +[[package]] +name = "thiserror-impl" +version = "2.0.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c5e1be1c48b9172ee610da68fd9cd2770e7a4056cb3fc98710ee6906f0c7960" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "tiny-fn" version = "0.1.9" @@ -1074,7 +1117,7 @@ dependencies = [ "protobuf-codegen", "protoc-bin-vendored", "rand", - "thiserror", + "thiserror 1.0.69", "tokio", "tracing", "uriparse", @@ -1147,28 +1190,6 @@ dependencies = [ "rustix 0.38.44", ] -[[package]] -name = "winapi" -version = "0.3.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" -dependencies = [ - "winapi-i686-pc-windows-gnu", - "winapi-x86_64-pc-windows-gnu", -] - -[[package]] -name = "winapi-i686-pc-windows-gnu" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" - -[[package]] -name = "winapi-x86_64-pc-windows-gnu" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" - [[package]] name = "windows-link" version = "0.1.3" diff --git a/Cargo.toml b/Cargo.toml index fb3981a..29370b4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -21,7 +21,7 @@ keywords = ["uProtocol", "SDK", "communication", "iceoryx", "iceoryx2"] up-rust = "0.7" async-trait = "0.1" tokio = { version = "1.47.1", features = ["macros", "rt-multi-thread", "signal"] } -iceoryx2 = "0.6.1" -iceoryx2-bb-container = "0.6.1" +iceoryx2 = "0.7.0" +iceoryx2-bb-container = "0.7.0" tracing = "0.1.41" hostname = "0.4.1" diff --git a/examples/common/helpers.rs b/examples/common/helpers.rs index b01c779..f540115 100644 --- a/examples/common/helpers.rs +++ b/examples/common/helpers.rs @@ -11,51 +11,28 @@ // SPDX-License-Identifier: Apache-2.0 // ################################################################################ -use std::str::FromStr; -use up_rust::{UMessage, UStatus, UUri}; +use up_rust::UMessage; -pub struct Helpers {} - -impl Helpers { - pub fn create_uuris(source: &str, sink: Option<&str>) -> (UUri, Option) { - let source_uuri = UUri::from_str(source).expect("Failed to create source UUri"); - let sink_uuri = sink.map(|sink| UUri::from_str(sink).expect("Failed to create sink UUri")); - (source_uuri, sink_uuri) - } - - pub fn print_that_subscriber_service_has_been_registered( - source_uuri: UUri, - sink_filter_uuri: Option, - ) -> Result<(), UStatus> { - let sink_str = match &sink_filter_uuri { - Some(sink) => sink.to_uri(false), - None => "".to_string(), - }; - print!("Listener added with source '{source_uuri}' and sink '{sink_str}' uri's"); - Ok(()) - } - - /// Simply prints the [`UMessage`] instances source uri, sink uri, and payload to STDOUT - pub fn print_umessage(msg: &UMessage) { - let payload_utf8 = msg.payload.as_ref().map(|p| String::from_utf8_lossy(p)); - let (source_uri, sink_uri) = Helpers::get_source_and_sink_uri(msg); - println!("Received a message!"); - println!("Source Uri: {source_uri:?}"); - println!("Sink Uri: {sink_uri:?}"); - println!("Payload: {payload_utf8:?}"); - } +/// Simply prints the [`UMessage`] instances source uri, sink uri, and payload to STDOUT +pub fn print_umessage(msg: &UMessage) { + let payload_utf8 = msg.payload.as_ref().map(|p| String::from_utf8_lossy(p)); + let (source_uri, sink_uri) = get_source_and_sink_uri(msg); + println!("Received a message!"); + println!("Source Uri: {source_uri:?}"); + println!("Sink Uri: {sink_uri:?}"); + println!("Payload: {payload_utf8:?}"); +} - pub fn get_source_and_sink_uri(msg: &UMessage) -> (Option, Option) { - let source_uri = msg - .attributes - .as_ref() - .and_then(|a| a.source.as_ref()) - .map(|s| s.to_uri(false)); - let sink_uri = msg - .attributes - .as_ref() - .and_then(|a| a.sink.as_ref()) - .map(|s| s.to_uri(false)); - (source_uri, sink_uri) - } +pub fn get_source_and_sink_uri(msg: &UMessage) -> (Option, Option) { + let source_uri = msg + .attributes + .as_ref() + .and_then(|a| a.source.as_ref()) + .map(|s| s.to_uri(false)); + let sink_uri = msg + .attributes + .as_ref() + .and_then(|a| a.sink.as_ref()) + .map(|s| s.to_uri(false)); + (source_uri, sink_uri) } diff --git a/examples/subscriber.rs b/examples/subscriber.rs index 41269fb..7b37b7d 100644 --- a/examples/subscriber.rs +++ b/examples/subscriber.rs @@ -12,53 +12,37 @@ // ################################################################################ use async_trait::async_trait; -use iceoryx2::service::ipc; -use std::sync::Arc; +use std::{str::FromStr, sync::Arc}; use tracing::info; -use up_rust::{UListener, UMessage}; -use up_transport_iceoryx2_rust::{transport::UTransportIceoryx2, workers::command::WorkerCommand}; +use up_rust::{UListener, UMessage, UTransport, UUri}; +use up_transport_iceoryx2_rust::{MessagingPattern, transport::UTransportIceoryx2}; + +use crate::common::helpers::*; mod common; -use crate::common::helpers::Helpers; struct SubscriberListener(tokio::runtime::Runtime); #[async_trait] impl UListener for SubscriberListener { - /// Spawns a thread to process the received message. In this example, we simply print the message contents. + /// Spawns a task to process the received message. In this example, we simply print the message contents. async fn on_receive(&self, msg: UMessage) { self.0.spawn(async move { - Helpers::print_umessage(&msg); + print_umessage(&msg); }); } } -/// This example sets up a single threaded runtime for the UTransport implementation of the Iceoryx2 service to run on #[tokio::main(flavor = "multi_thread")] async fn main() -> Result<(), Box> { - // Setup the UMessage, subscriber, and command to register the subscriber on a dedicated thread - // Iceoryx2 ipc::Service is used here, and does not support multi-threaded communication - // See ipc_threadsafe::Service for that use case info!("uProtocols UTransportIceoryx2 subscriber example"); - // "//*/FFFFB1DA/1/8001" - let (source_filter, sink_filter) = Helpers::create_uuris("up://device1/10AB/3/80CD", None); - let (command_sender, _) = UTransportIceoryx2::::publish_subscribe(); - let runtime = tokio::runtime::Builder::new_multi_thread() - .thread_name("subscriber-example") - .worker_threads(1) - .build()?; - let ulistener = Arc::new(SubscriberListener(runtime)); - - let (result_sender, mut result_receiver) = tokio::sync::oneshot::channel(); - let register_listener_command = WorkerCommand::RegisterListener { - source_filter: source_filter.clone(), - sink_filter: sink_filter.clone(), - listener: ulistener, - result_sender, - }; - command_sender.send(register_listener_command).await?; - Helpers::print_that_subscriber_service_has_been_registered(source_filter, sink_filter)?; - let result = result_receiver.try_recv()?; - info!("Listener registration result: {result:?}"); + let source_filter = + UUri::from_str("up://device1/10AB/3/80CD").expect("Failed to create source UUri"); + let transport = UTransportIceoryx2::build(MessagingPattern::PublishSubscribe)?; + let ulistener = Arc::new(SubscriberListener(tokio::runtime::Runtime::new()?)); + transport + .register_listener(&source_filter, None, ulistener) + .await?; + info!("Listener registered. Waiting for messages..."); tokio::signal::ctrl_c().await.map_err(Box::from) } diff --git a/src/lib.rs b/src/lib.rs index 23fd5ce..9661689 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -11,10 +11,34 @@ // SPDX-License-Identifier: Apache-2.0 // ################################################################################ -pub mod service_name_mapping; +use iceoryx2::{ + port::{publisher::Publisher, subscriber::Subscriber}, + prelude::{ServiceName, ZeroCopySend}, +}; +use std::{ + collections::{HashMap, HashSet}, + fmt::Debug, + sync::Arc, +}; +use tokio::sync::RwLock; +use up_rust::ComparableListener; + +use crate::{umessage::UMessageZeroCopy, uprotocolheader::UProtocolHeader}; + +pub(crate) mod service_name_mapping; pub mod transport; -pub mod types; -pub mod umessage; -pub mod uprotocolheader; -pub mod utransport; -pub mod workers; +pub(crate) mod umessage; +pub(crate) mod uprotocolheader; +pub(crate) mod utransport_pubsub; +pub(crate) mod workers; + +pub use iceoryx2::prelude::MessagingPattern; + +pub trait BaseUserHeader: Debug + ZeroCopySend {} +pub trait BasePayload: Debug + ZeroCopySend {} + +pub(crate) type PublisherSet = + RwLock>>>; +pub(crate) type SubscriberSet = + RwLock>>>; +pub(crate) type ListenerMap = RwLock>>; diff --git a/src/service_name_mapping.rs b/src/service_name_mapping.rs index f5906ec..84ddec2 100644 --- a/src/service_name_mapping.rs +++ b/src/service_name_mapping.rs @@ -14,112 +14,107 @@ use iceoryx2::prelude::{MessagingPattern, ServiceName}; use up_rust::{UCode, UMessageType, UStatus, UUri}; -pub struct ServiceNameMapper; - -/// uProtocol [`UUri`] to Iceoryx2 [`ServiceName`] mapping(s) -impl ServiceNameMapper { - fn encode_uuri_segments(uuri: &UUri) -> Vec { - vec![ - Self::get_authority_name(uuri), - 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_uuri_segments(uuri: &UUri) -> Vec { + vec![ + get_authority_name(uuri), + encode_hex(uuri.uentity_type_id() as u32), + encode_hex(uuri.uentity_instance_id() as u32), + encode_hex(uuri.uentity_major_version() as u32), + encode_hex(uuri.resource_id() as u32), + ] +} - fn encode_hex(value: u32) -> String { - format!("{value:X}") - } +fn encode_hex(value: u32) -> String { + format!("{value:X}") +} - fn get_authority_name(source_uuri: &UUri) -> String { - if source_uuri.authority_name.is_empty() { - match hostname::get().unwrap().into_string() { - Ok(hostname) => hostname, - Err(_) => "unknown".to_string(), - } - } else { - source_uuri.authority_name.clone() +fn get_authority_name(source_uuri: &UUri) -> String { + if source_uuri.authority_name.is_empty() { + match hostname::get().unwrap().into_string() { + Ok(hostname) => hostname, + Err(_) => "unknown".to_string(), } + } else { + source_uuri.authority_name.clone() } +} - fn determine_message_type( - source: &UUri, - _sink: Option<&UUri>, - messaging_pattern: MessagingPattern, - ) -> Result { - if Self::is_a_publish(source, messaging_pattern) { - return Ok(UMessageType::UMESSAGE_TYPE_PUBLISH); - } - - Err(UStatus::fail_with_code( - UCode::INVALID_ARGUMENT, - "Could not determine a valid UMessageType from the provided UUri(s)", - )) +fn determine_message_type( + source: &UUri, + _sink: Option<&UUri>, + messaging_pattern: MessagingPattern, +) -> Result { + if is_a_publish(source, messaging_pattern) { + return Ok(UMessageType::UMESSAGE_TYPE_PUBLISH); } - fn is_a_publish(source: &UUri, messaging_pattern: MessagingPattern) -> bool { - source.is_empty() == false && messaging_pattern == MessagingPattern::PublishSubscribe - } + Err(UStatus::fail_with_code( + UCode::INVALID_ARGUMENT, + "Could not determine a valid UMessageType from the provided UUri(s)", + )) +} - pub fn compute_service_name( - source: &UUri, - sink: Option<&UUri>, - messaging_pattern: MessagingPattern, - ) -> Result { - let join_segments = |segments: Vec| segments.join("/"); - let message_type = Self::determine_message_type(source, sink, messaging_pattern)?; - let service_name_str = match message_type { - UMessageType::UMESSAGE_TYPE_REQUEST => { - let Some(sink_uri) = sink else { - return Err(UStatus::fail_with_code( - UCode::INVALID_ARGUMENT, - format!( - "sink required for UMessageType {:?}", - UMessageType::UMESSAGE_TYPE_REQUEST - ), - )); - }; - let segments = Self::encode_uuri_segments(sink_uri); - format!("up/{}", join_segments(segments)) - } - UMessageType::UMESSAGE_TYPE_RESPONSE | UMessageType::UMESSAGE_TYPE_NOTIFICATION => { - let Some(sink_uri) = sink else { - return Err(UStatus::fail_with_code( - UCode::INVALID_ARGUMENT, - format!( - "sink required for UMessageType {:?} or {:?}", - UMessageType::UMESSAGE_TYPE_RESPONSE, - UMessageType::UMESSAGE_TYPE_NOTIFICATION - ), - )); - }; - let source_segments = Self::encode_uuri_segments(source); - let sink_segments = Self::encode_uuri_segments(sink_uri); - format!( - "up/{}/{}", - join_segments(source_segments), - join_segments(sink_segments) - ) - } - UMessageType::UMESSAGE_TYPE_PUBLISH => { - let segments = Self::encode_uuri_segments(source); - format!("up/{}", join_segments(segments)) - } - _ => { +fn is_a_publish(source: &UUri, messaging_pattern: MessagingPattern) -> bool { + source.is_empty() == false && messaging_pattern == MessagingPattern::PublishSubscribe +} + +pub fn compute_service_name( + source: &UUri, + sink: Option<&UUri>, + messaging_pattern: MessagingPattern, +) -> Result { + let join_segments = |segments: Vec| segments.join("/"); + let message_type = determine_message_type(source, sink, messaging_pattern)?; + let service_name_str = match message_type { + UMessageType::UMESSAGE_TYPE_REQUEST => { + let Some(sink_uri) = sink else { return Err(UStatus::fail_with_code( UCode::INVALID_ARGUMENT, - "Unsupported UMessageType for service name computation", + format!( + "sink required for UMessageType {:?}", + UMessageType::UMESSAGE_TYPE_REQUEST + ), )); - } - }; - Ok(ServiceName::new(service_name_str.as_str()).expect("Failed to create service name")) - } + }; + let segments = encode_uuri_segments(sink_uri); + format!("up/{}", join_segments(segments)) + } + UMessageType::UMESSAGE_TYPE_RESPONSE | UMessageType::UMESSAGE_TYPE_NOTIFICATION => { + let Some(sink_uri) = sink else { + return Err(UStatus::fail_with_code( + UCode::INVALID_ARGUMENT, + format!( + "sink required for UMessageType {:?} or {:?}", + UMessageType::UMESSAGE_TYPE_RESPONSE, + UMessageType::UMESSAGE_TYPE_NOTIFICATION + ), + )); + }; + let source_segments = encode_uuri_segments(source); + let sink_segments = encode_uuri_segments(sink_uri); + format!( + "up/{}/{}", + join_segments(source_segments), + join_segments(sink_segments) + ) + } + UMessageType::UMESSAGE_TYPE_PUBLISH => { + let segments = encode_uuri_segments(source); + format!("up/{}", join_segments(segments)) + } + _ => { + return Err(UStatus::fail_with_code( + UCode::INVALID_ARGUMENT, + "Unsupported UMessageType for service name computation", + )); + } + }; + Ok(ServiceName::new(service_name_str.as_str()).expect("Failed to create service name")) } #[cfg(test)] mod tests { - use crate::service_name_mapping::ServiceNameMapper; + use super::*; use iceoryx2::prelude::MessagingPattern; use up_rust::{UCode, UUri}; @@ -135,12 +130,7 @@ mod tests { fn test_publish_service_name() { let source = test_uri("device1", 0x0000, 0x10AB, 0x03, 0x7FFF); - let name = ServiceNameMapper::compute_service_name( - &source, - None, - MessagingPattern::PublishSubscribe, - ) - .unwrap(); + let name = compute_service_name(&source, None, MessagingPattern::PublishSubscribe).unwrap(); assert_eq!(name, "up/device1/10AB/0/3/7FFF"); } @@ -152,11 +142,7 @@ mod tests { // .specitem[dsn~up-attributes-notification-source~1] fn test_missing_uri_error() { let uuri = UUri::new(); - let result = ServiceNameMapper::compute_service_name( - &uuri, - None, - MessagingPattern::PublishSubscribe, - ); + let result = compute_service_name(&uuri, None, MessagingPattern::PublishSubscribe); assert!(result.is_err()); assert_eq!(result.unwrap_err().get_code(), UCode::INVALID_ARGUMENT); @@ -168,11 +154,7 @@ mod tests { // .specitem[dsn~up-attributes-request-source~1] fn test_fail_missing_sink_error() { let source = test_uri("device1", 0x0000, 0x00CD, 0x04, 0x000); - let result = ServiceNameMapper::compute_service_name( - &source, - None, - MessagingPattern::RequestResponse, - ); + let result = compute_service_name(&source, None, MessagingPattern::RequestResponse); assert!(result.is_err_and(|err| err.get_code() == UCode::INVALID_ARGUMENT)); } @@ -184,11 +166,7 @@ mod tests { fn test_fail_missing_source_error() { let uuri = UUri::new(); let sink = test_uri("device1", 0x0004, 0x3AB, 0x3, 0x000); - let result = ServiceNameMapper::compute_service_name( - &uuri, - Some(&sink), - MessagingPattern::PublishSubscribe, - ); + let result = compute_service_name(&uuri, Some(&sink), MessagingPattern::PublishSubscribe); assert!(result.is_err_and(|err| err.get_code() == UCode::INVALID_ARGUMENT)); } } diff --git a/src/transport.rs b/src/transport.rs index 80eaa0a..6cfb69c 100644 --- a/src/transport.rs +++ b/src/transport.rs @@ -11,38 +11,27 @@ // SPDX-License-Identifier: Apache-2.0 // ################################################################################ -use crate::workers::{command::WorkerCommand, dispatcher::Iceoryx2WorkerDispatcher}; -use iceoryx2::node::{Node, NodeBuilder}; -use tokio::{sync::mpsc::Sender, task::JoinHandle}; -use up_rust::UStatus; +use std::sync::Arc; -pub struct UTransportIceoryx2 { - pub(crate) node: Node, -} +use crate::utransport_pubsub::Iceoryx2PubSub; +use iceoryx2::prelude::MessagingPattern; +use up_rust::{UCode, UStatus, UTransport}; -/// Acts as a uProtocol-specific interface for the Iceoryx2 transport system -impl UTransportIceoryx2 { - pub fn publish_subscribe() -> (Sender, JoinHandle>) { - let (command_sender, worker_thread_handle) = - Iceoryx2WorkerDispatcher::create_pubsub_worker(1024); - (command_sender, worker_thread_handle) - } +pub struct UTransportIceoryx2 {} - pub(crate) fn default() -> Result, UStatus> { - let configure_fn: Option = None; - Self::create(configure_fn) +/// Acts as a uProtocol-specific interface for the Iceoryx2 transport system +impl UTransportIceoryx2 { + pub fn build(messaging_pattern: MessagingPattern) -> Result, UStatus> { + match messaging_pattern { + MessagingPattern::PublishSubscribe => Ok(UTransportIceoryx2::build_publish_subscribe()), + _ => Err(UStatus::fail_with_code( + UCode::UNIMPLEMENTED, + "Unimplemented messaging pattern", + )), + } } - fn create( - configure: Option, - ) -> Result, UStatus> { - let node_builder = NodeBuilder::new(); - if let Some(configure) = configure { - configure(&node_builder); - } - let node = node_builder - .create::() - .expect("Failed to create Iceoryx2 Node"); - Ok(UTransportIceoryx2 { node }) + fn build_publish_subscribe() -> Arc { + Iceoryx2PubSub::new() } } diff --git a/src/types.rs b/src/types.rs deleted file mode 100644 index 45248e6..0000000 --- a/src/types.rs +++ /dev/null @@ -1,34 +0,0 @@ -// ################################################################################ -// Copyright (c) 2025 Contributors to the Eclipse Foundation -// -// See the NOTICE file(s) distributed with this work for additional -// information regarding copyright ownership. -// -// This program and the accompanying materials are made available under the -// terms of the Apache License Version 2.0 which is available at -// https: //www.apache.org/licenses/LICENSE-2.0 -// -// SPDX-License-Identifier: Apache-2.0 -// ################################################################################ - -use iceoryx2::{ - port::{publisher::Publisher, subscriber::Subscriber}, - prelude::{ServiceName, ZeroCopySend}, - service::ipc, -}; -use std::{ - collections::{HashMap, HashSet}, - fmt::Debug, -}; -use up_rust::ComparableListener; - -use crate::{umessage::UMessageZeroCopy, uprotocolheader::UProtocolHeader}; - -pub trait BaseUserHeader: Debug + ZeroCopySend {} -pub trait BasePayload: Debug + ZeroCopySend {} - -pub(crate) type PublisherSet = - HashMap>; -pub(crate) type SubscriberSet = - HashMap>; -pub(crate) type ListenerMap = HashMap>; diff --git a/src/uprotocolheader.rs b/src/uprotocolheader.rs index 119416f..b691ac0 100644 --- a/src/uprotocolheader.rs +++ b/src/uprotocolheader.rs @@ -19,7 +19,7 @@ const MAX_FEASIBLE_UATTRIBUTES_SERIALIZED_LENGTH: usize = 1000; // choosing ~100 // this should be confirmed #[repr(C)] -#[derive(ZeroCopySend, Debug)] +#[derive(ZeroCopySend, Debug, Default)] pub struct UProtocolHeader { uprotocol_major_version: u8, uattributes_serialized: FixedSizeVec, diff --git a/src/utransport.rs b/src/utransport.rs deleted file mode 100644 index e38dbf4..0000000 --- a/src/utransport.rs +++ /dev/null @@ -1,81 +0,0 @@ -// ################################################################################ -// Copyright (c) 2025 Contributors to the Eclipse Foundation -// -// See the NOTICE file(s) distributed with this work for additional -// information regarding copyright ownership. -// -// This program and the accompanying materials are made available under the -// terms of the Apache License Version 2.0 which is available at -// https: //www.apache.org/licenses/LICENSE-2.0 -// -// SPDX-License-Identifier: Apache-2.0 -// ################################################################################ - -use std::sync::Arc; - -use async_trait::async_trait; -use tokio::sync::oneshot; -use up_rust::{UCode, UListener, UMessage, UStatus, UTransport, UUri}; - -use crate::workers::{command::WorkerCommand, dispatcher::Iceoryx2WorkerDispatcher}; - -#[async_trait] -impl UTransport for Iceoryx2WorkerDispatcher { - async fn send(&self, message: UMessage) -> Result<(), UStatus> { - let (tx, rx) = oneshot::channel::>(); - let command = WorkerCommand::Send { - message, - result_sender: tx, - }; - self.command_sender - .send(command) - .map_err(|_| UStatus::fail_with_code(UCode::INTERNAL, "Background task has died"))?; - rx.blocking_recv().map_err(|_| { - UStatus::fail_with_code(UCode::INTERNAL, "Background task response failed") - })? - } - - async fn register_listener( - &self, - source_filter: &UUri, - sink_filter: Option<&UUri>, - listener: Arc, - ) -> Result<(), UStatus> { - up_rust::verify_filter_criteria(source_filter, sink_filter)?; - let (tx, rx) = oneshot::channel::>(); - let command = WorkerCommand::RegisterListener { - source_filter: source_filter.clone(), - sink_filter: sink_filter.cloned(), - listener, - result_sender: tx, - }; - self.command_sender - .send(command) - .map_err(|_| UStatus::fail_with_code(UCode::INTERNAL, "Background task has died"))?; - rx.blocking_recv().map_err(|_| { - UStatus::fail_with_code(UCode::INTERNAL, "Background task response failed") - })? - } - - async fn unregister_listener( - &self, - source_filter: &UUri, - sink_filter: Option<&UUri>, - listener: Arc, - ) -> Result<(), UStatus> { - up_rust::verify_filter_criteria(source_filter, sink_filter)?; - let (tx, rx) = oneshot::channel::>(); - let command = WorkerCommand::UnregisterListener { - source_filter: source_filter.clone(), - sink_filter: sink_filter.cloned(), - listener, - result_sender: tx, - }; - self.command_sender - .send(command) - .map_err(|_| UStatus::fail_with_code(UCode::INTERNAL, "Background task has died"))?; - rx.blocking_recv().map_err(|_| { - UStatus::fail_with_code(UCode::INTERNAL, "Background task response failed") - })? - } -} diff --git a/src/utransport_pubsub.rs b/src/utransport_pubsub.rs new file mode 100644 index 0000000..c828949 --- /dev/null +++ b/src/utransport_pubsub.rs @@ -0,0 +1,230 @@ +// // ################################################################################ +// // Copyright (c) 2025 Contributors to the Eclipse Foundation +// // +// // See the NOTICE file(s) distributed with this work for additional +// // information regarding copyright ownership. +// // +// // This program and the accompanying materials are made available under the +// // terms of the Apache License Version 2.0 which is available at +// // https: //www.apache.org/licenses/LICENSE-2.0 +// // +// // SPDX-License-Identifier: Apache-2.0 +// // ################################################################################ + +use async_trait::async_trait; +use iceoryx2::prelude::MessagingPattern; +use iceoryx2::{ + node::{Node, NodeBuilder}, + port::{publisher::Publisher, subscriber::Subscriber}, + prelude::ServiceName, + service::ipc_threadsafe, +}; +use std::collections::HashMap; +use std::sync::Arc; +use tokio::sync::RwLock; +use up_rust::{ComparableListener, UCode, UListener, UMessage, UStatus, UTransport, UUri}; + +use crate::workers::dispatcher::Iceoryx2WorkerDispatcher; +use crate::{ + ListenerMap, PublisherSet, SubscriberSet, service_name_mapping::compute_service_name, + umessage::UMessageZeroCopy, uprotocolheader::UProtocolHeader, +}; + +pub struct Iceoryx2PubSub { + node: Node, + listener_worker_runtime: tokio::runtime::Runtime, + pub publishers: PublisherSet, + pub subscribers: SubscriberSet, + pub listeners: ListenerMap, +} + +impl Iceoryx2PubSub { + pub fn new() -> Arc { + let node = NodeBuilder::new() + .create::() + .expect("Failed to create Iceoryx2 Node"); + let listener_worker_runtime = + tokio::runtime::Runtime::new().expect("Failed to create Tokio runtime"); + let transport = Arc::new(Self { + node, + publishers: RwLock::new(HashMap::new()), + subscribers: RwLock::new(HashMap::new()), + listeners: RwLock::new(HashMap::new()), + listener_worker_runtime, + }); + Iceoryx2WorkerDispatcher::start_listener_worker( + &transport.listener_worker_runtime, + transport.clone(), + ); + transport + } + + pub fn create_subscriber( + &self, + ) -> Result, UStatus> + { + // Placeholder implementation + let service_name: ServiceName = "example_service".try_into().unwrap(); + let service = self + .node + .service_builder(&service_name) + .publish_subscribe::() + .user_header::() + .open_or_create() + .map_err(|e| { + UStatus::fail_with_code(UCode::INTERNAL, format!("Failed to create service: {e}")) + })?; + let subscriber = service.subscriber_builder().create().map_err(|e| { + UStatus::fail_with_code(UCode::INTERNAL, format!("Failed to create subscriber: {e}")) + })?; + Ok(subscriber) + } + + pub async fn get_or_create_publisher( + &self, + service_name: ServiceName, + ) -> Result>, UStatus> + { + let publishers = self.publishers.read().await; + if publishers.contains_key(&service_name) { + let publisher = publishers.get(&service_name).unwrap(); + return Ok(publisher.clone()); + } + let service_name_res: Result = service_name.as_str().try_into(); + let service = self + .node + .service_builder(&service_name_res.unwrap()) + .publish_subscribe::() + .user_header::() + .open_or_create() + .map_err(|e| { + UStatus::fail_with_code(UCode::INTERNAL, format!("Failed to create service: {e}")) + })?; + + let publisher = service.publisher_builder().create().map_err(|e| { + UStatus::fail_with_code(UCode::INTERNAL, format!("Failed to create publisher: {e}")) + })?; + let mut publishers = self.publishers.write().await; + publishers.insert(service_name.clone(), Arc::new(publisher)); + let publisher = publishers.get(&service_name).unwrap(); + Ok(publisher.clone()) + } + + pub async fn relay(&self) -> Result<(), UStatus> { + let current_thread_runtime = tokio::runtime::Builder::new_current_thread() + .enable_all() + .build() + .map_err(|e| { + UStatus::fail_with_code( + UCode::INTERNAL, + format!("Failed to build current_thread runtime: {e}"), + ) + })?; + for (service_name, subscriber) in self.subscribers.read().await.iter() { + match subscriber.receive() { + Ok(Some(sample)) => { + let payload = sample; + if let Some(listeners_to_notify) = self.listeners.read().await.get(service_name) + { + for listener in listeners_to_notify.iter() { + let listener: &ComparableListener = listener; + current_thread_runtime.block_on(listener.on_receive(payload.0.clone())); + } + } + } + Ok(None) => continue, // No sample available + Err(e) => { + return Err(UStatus::fail_with_code( + UCode::INTERNAL, + format!("Failed to receive sample: {e}"), + )); + } + } + } + Ok(()) + } +} + +#[async_trait] +impl UTransport for Iceoryx2PubSub { + async fn send(&self, message: UMessage) -> Result<(), UStatus> { + let service_name = { + let source_filter = &message.attributes.source; + let sink_filter = message.attributes.sink.as_ref(); + compute_service_name( + source_filter, + sink_filter, + MessagingPattern::PublishSubscribe, + )? + }; + let publisher = self + .get_or_create_publisher(service_name) + .await + .map_err(|e| { + UStatus::fail_with_code(UCode::INTERNAL, format!("Failed to get publisher: {e}")) + })?; + + let sample = publisher.loan_uninit().map_err(|e| { + UStatus::fail_with_code(UCode::INTERNAL, format!("Failed to loan sample: {e}")) + })?; + let sample_final = sample.write_payload(UMessageZeroCopy(message)); + sample_final.send().map_err(|e| { + UStatus::fail_with_code(UCode::INTERNAL, format!("Failed to send: {e}")) + })?; + Ok(()) + } + + async fn register_listener( + &self, + source_filter: &UUri, + sink_filter: Option<&UUri>, + listener: Arc, + ) -> Result<(), UStatus> { + up_rust::verify_filter_criteria(source_filter, sink_filter)?; + let service_name = compute_service_name( + &source_filter, + sink_filter, + MessagingPattern::PublishSubscribe, + )?; + let subscribers = self.subscribers.read().await; + // insert subscriber for service name if it does not already exist + if !subscribers.contains_key(&service_name) { + let subscriber = self.create_subscriber()?; + let mut subscribers = self.subscribers.write().await; + subscribers.insert(service_name.clone(), Arc::new(subscriber)); + } + // insert listener for service name if it does not already exist + if !self.listeners.read().await.contains_key(&service_name) { + let mut listeners = self.listeners.write().await; + listeners + .entry(service_name) + .or_default() + .insert(ComparableListener::new(listener)); + } + Ok(()) + } + + async fn unregister_listener( + &self, + source_filter: &UUri, + sink_filter: Option<&UUri>, + listener: Arc, + ) -> Result<(), UStatus> { + up_rust::verify_filter_criteria(source_filter, sink_filter)?; + let service_name = compute_service_name( + &source_filter, + sink_filter, + MessagingPattern::PublishSubscribe, + )?; + let comparable_listener = ComparableListener::new(listener.clone()); + let mut listeners = self.listeners.write().await; + if let Some(existing_listeners) = listeners.get_mut(&service_name) { + existing_listeners.retain(|l| !l.eq(&comparable_listener)); + if existing_listeners.is_empty() { + let mut subscribers = self.subscribers.write().await; + subscribers.remove(&service_name); + } + } + Ok(()) + } +} diff --git a/src/workers/command.rs b/src/workers/command.rs deleted file mode 100644 index 8e6817e..0000000 --- a/src/workers/command.rs +++ /dev/null @@ -1,35 +0,0 @@ -// ################################################################################ -// Copyright (c) 2025 Contributors to the Eclipse Foundation -// -// See the NOTICE file(s) distributed with this work for additional -// information regarding copyright ownership. -// -// This program and the accompanying materials are made available under the -// terms of the Apache License Version 2.0 which is available at -// https: //www.apache.org/licenses/LICENSE-2.0 -// -// SPDX-License-Identifier: Apache-2.0 -// ################################################################################ - -use std::sync::Arc; -use tokio::sync::oneshot; -use up_rust::{UListener, UMessage, UStatus, UUri}; - -pub enum WorkerCommand { - Send { - message: UMessage, - result_sender: oneshot::Sender>, - }, - RegisterListener { - source_filter: UUri, - sink_filter: Option, - listener: Arc, - result_sender: oneshot::Sender>, - }, - UnregisterListener { - source_filter: UUri, - sink_filter: Option, - listener: Arc, - result_sender: oneshot::Sender>, - }, -} diff --git a/src/workers/dispatcher.rs b/src/workers/dispatcher.rs index 314bfc4..af34fce 100644 --- a/src/workers/dispatcher.rs +++ b/src/workers/dispatcher.rs @@ -11,109 +11,25 @@ // SPDX-License-Identifier: Apache-2.0 // ################################################################################ -use iceoryx2::prelude::MessagingPattern; use std::sync::{Arc, atomic::Ordering}; -use tokio::{ - sync::{ - Mutex, - mpsc::{Receiver, Sender}, - }, - task::JoinHandle, -}; -use up_rust::{UCode, UStatus}; +use tokio::runtime::Runtime; +use up_rust::UStatus; -use crate::{ - transport::UTransportIceoryx2, - workers::{ - command::WorkerCommand, pubsub_worker::Iceoryx2PubSubWorker, worker::Iceoryx2Worker, - }, -}; +use crate::{utransport_pubsub::Iceoryx2PubSub, workers::worker::Iceoryx2Worker}; -pub struct Iceoryx2WorkerDispatcher { - pub command_sender: std::sync::mpsc::Sender, - pub messaging_pattern: MessagingPattern, - pub handle: JoinHandle>, -} +pub struct Iceoryx2WorkerDispatcher {} impl Iceoryx2WorkerDispatcher { - pub fn create_pubsub_worker( - buffer_size: usize, - ) -> (Sender, JoinHandle>) { - let (tx, rx) = tokio::sync::mpsc::channel::(buffer_size); - let threadsafe_rx = Arc::new(Mutex::new(rx)); - let handle = tokio::spawn(async { - let future = Iceoryx2WorkerDispatcher::run(threadsafe_rx); - - let current_thread_runtime = tokio::runtime::Builder::new_current_thread() - .enable_all() - .build() - .map_err(|e| { - UStatus::fail_with_code( - UCode::INTERNAL, - format!("Failed to build current_thread runtime: {e}"), - ) - })?; - current_thread_runtime.block_on(future) - }); - (tx, handle) + pub fn start_listener_worker(runtime: &Runtime, transport: Arc) { + let worker = Iceoryx2Worker::new(transport.clone()); + let future = Iceoryx2WorkerDispatcher::run(worker); + runtime.spawn(future); } - async fn run(rx: Arc>>) -> Result<(), UStatus> { - // non-threadsafe state goes here - // any iceoryx2 transports that use the `ipc::Service` service implementation is not threadsafe - let transport = UTransportIceoryx2::default()?; - let mut worker = - Iceoryx2PubSubWorker::new(rx, transport, MessagingPattern::PublishSubscribe); - // - while worker.keep_alive().load(Ordering::Relaxed) { - let command_receiver = worker.get_command_receiver().clone(); - let mut command_receiver = command_receiver.lock().await; - if let Ok(command) = command_receiver.try_recv() { - Iceoryx2WorkerDispatcher::process_command(&mut worker, command)?; - } - worker.receive_and_notify_listeners().await?; - // .map_err(|e| UStatus::fail_with_code(UCode::INTERNAL, e.to_string()))?; + async fn run(worker: Iceoryx2Worker) -> Result<(), UStatus> { + while worker.keep_alive.load(Ordering::Relaxed) { + worker.transport.relay().await?; } Ok(()) } - - fn process_command( - worker: &mut Worker, - command: WorkerCommand, - ) -> Result<(), UStatus> { - let channel_response = match command { - WorkerCommand::Send { - message, - result_sender, - } => { - let result = worker.send(message); - result_sender.send(result) - } - WorkerCommand::RegisterListener { - source_filter, - sink_filter, - listener, - result_sender, - } => { - let result = worker.register_listener(source_filter, sink_filter, listener); - result_sender.send(result) - } - WorkerCommand::UnregisterListener { - source_filter, - sink_filter, - listener, - result_sender, - } => { - let result = worker.unregister_listener(source_filter, sink_filter, listener); - result_sender.send(result) - } - }; - match channel_response { - Err(_) => Err(UStatus::fail_with_code( - UCode::INTERNAL, - "Failed to send result of command through the response channel", - )), - Ok(_) => Ok(()), - } - } } diff --git a/src/workers/mod.rs b/src/workers/mod.rs index bce702e..ffc7de1 100644 --- a/src/workers/mod.rs +++ b/src/workers/mod.rs @@ -11,7 +11,5 @@ // SPDX-License-Identifier: Apache-2.0 // ################################################################################ -pub mod command; -pub mod dispatcher; -pub mod pubsub_worker; -pub mod worker; +pub(crate) mod dispatcher; +pub(crate) mod worker; diff --git a/src/workers/pubsub_worker.rs b/src/workers/pubsub_worker.rs deleted file mode 100644 index f9b3a31..0000000 --- a/src/workers/pubsub_worker.rs +++ /dev/null @@ -1,224 +0,0 @@ -// ################################################################################ -// Copyright (c) 2025 Contributors to the Eclipse Foundation -// -// See the NOTICE file(s) distributed with this work for additional -// information regarding copyright ownership. -// -// This program and the accompanying materials are made available under the -// terms of the Apache License Version 2.0 which is available at -// https: //www.apache.org/licenses/LICENSE-2.0 -// -// SPDX-License-Identifier: Apache-2.0 -// ################################################################################ - -use iceoryx2::{ - port::{publisher::Publisher, subscriber::Subscriber}, - prelude::ServiceName, - service::ipc, -}; -use std::{ - collections::HashMap, - sync::{Arc, atomic::AtomicBool}, -}; -use tokio::sync::{Mutex, mpsc::Receiver}; -use up_rust::{ComparableListener, UCode, UStatus, UUri}; - -use crate::{ - service_name_mapping::ServiceNameMapper, - transport::UTransportIceoryx2, - types::{ListenerMap, PublisherSet, SubscriberSet}, - umessage::UMessageZeroCopy, - uprotocolheader::UProtocolHeader, - workers::{command::WorkerCommand, worker::Iceoryx2Worker}, -}; - -pub struct Iceoryx2PubSubWorker { - command_receiver: Arc>>, - keep_alive: Arc, - transport: UTransportIceoryx2, - messaging_pattern: iceoryx2::prelude::MessagingPattern, - publishers: PublisherSet, - subscribers: SubscriberSet, - listeners: ListenerMap, -} - -impl Iceoryx2PubSubWorker { - pub fn new( - rx: Arc>>, - transport: UTransportIceoryx2, - messaging_pattern: iceoryx2::prelude::MessagingPattern, - ) -> Self { - Self { - transport, - command_receiver: rx, - keep_alive: Arc::new(AtomicBool::new(true)), - messaging_pattern, - publishers: HashMap::new(), - subscribers: HashMap::new(), - listeners: HashMap::new(), - } - } -} - -impl Iceoryx2PubSubWorker { - fn create_subscriber( - &self, - ) -> Result, UStatus> { - // Placeholder implementation - let service_name: ServiceName = "example_service".try_into().unwrap(); - let service = self - .transport - .node - .service_builder(&service_name) - .publish_subscribe::() - .user_header::() - .open_or_create() - .map_err(|e| { - UStatus::fail_with_code(UCode::INTERNAL, format!("Failed to create service: {e}")) - })?; - let subscriber = service.subscriber_builder().create().map_err(|e| { - UStatus::fail_with_code(UCode::INTERNAL, format!("Failed to create subscriber: {e}")) - })?; - Ok(subscriber) - } - - fn create_publisher( - &mut self, - service_name: ServiceName, - ) -> Result<&Publisher, UStatus> { - if self.publishers.contains_key(&service_name) { - return Ok(self.publishers.get(&service_name).unwrap()); - } - let service_name_res: Result = service_name.as_str().try_into(); - let service = self - .transport - .node - .service_builder(&service_name_res.unwrap()) - .publish_subscribe::() - .user_header::() - .open_or_create() - .map_err(|e| { - UStatus::fail_with_code(UCode::INTERNAL, format!("Failed to create service: {e}")) - })?; - - let publisher = service.publisher_builder().create().map_err(|e| { - UStatus::fail_with_code(UCode::INTERNAL, format!("Failed to create publisher: {e}")) - })?; - self.publishers.insert(service_name.clone(), publisher); - Ok(self.publishers.get(&service_name).unwrap()) - } -} - -impl Iceoryx2Worker for Iceoryx2PubSubWorker { - fn send(&mut self, message: up_rust::UMessage) -> Result<(), UStatus> { - let service_name = { - let source_filter = &message.attributes.source; - let sink_filter = message.attributes.sink.as_ref(); - ServiceNameMapper::compute_service_name( - source_filter, - sink_filter, - self.messaging_pattern, - )? - }; - let publisher = self.create_publisher(service_name)?; - let message = UMessageZeroCopy(message); - let sample = publisher.loan_uninit().map_err(|e| { - UStatus::fail_with_code(UCode::INTERNAL, format!("Failed to loan sample: {e}")) - })?; - let sample_final = sample.write_payload(message); - sample_final.send().map_err(|e| { - UStatus::fail_with_code(UCode::INTERNAL, format!("Failed to send: {e}")) - })?; - Ok(()) - } - - fn register_listener( - &mut self, - source_filter: UUri, - sink_filter: Option, - listener: Arc, - ) -> Result<(), up_rust::UStatus> { - let service_name = ServiceNameMapper::compute_service_name( - &source_filter, - sink_filter.as_ref(), - self.messaging_pattern, - )?; - if !self.subscribers.contains_key(&service_name) { - let subscriber = self.create_subscriber()?; - self.subscribers.insert(service_name.clone(), subscriber); - } - self.listeners - .entry(service_name) - .or_default() - .insert(ComparableListener::new(listener)); - Ok(()) - } - - fn unregister_listener( - &mut self, - source_filter: UUri, - sink_filter: Option, - listener: Arc, - ) -> Result<(), up_rust::UStatus> { - let service_name = ServiceNameMapper::compute_service_name( - &source_filter, - sink_filter.as_ref(), - self.messaging_pattern, - )?; - let comparable_listener = ComparableListener::new(listener.clone()); - if let Some(existing_listeners) = self.listeners.get_mut(&service_name) { - existing_listeners.retain(|l| !l.eq(&comparable_listener)); - - if existing_listeners.is_empty() { - self.listeners.remove(&service_name); - self.subscribers.remove(&service_name); - } - } - Ok(()) - } - - fn get_command_receiver(&self) -> Arc>> { - self.command_receiver.clone() - } - - fn keep_alive(&self) -> &std::sync::atomic::AtomicBool { - &self.keep_alive - } - - async fn receive_and_notify_listeners(&self) -> Result<(), UStatus> { - let current_thread_runtime = tokio::runtime::Builder::new_current_thread() - .enable_all() - .build() - .map_err(|e| { - UStatus::fail_with_code( - UCode::INTERNAL, - format!("Failed to build current_thread runtime: {e}"), - ) - })?; - for (service_name, subscriber) in self.subscribers.iter() { - match subscriber.receive() { - Ok(Some(sample)) => { - let payload = sample; - if let Some(listeners_to_notify) = self.listeners.get(service_name) { - for listener in listeners_to_notify.iter() { - let listener: &ComparableListener = listener; - // FIXME - // How would this be done without cloning the payload? - // Pretty sure the whole point of using iceoryx2 was to prevent cloning samples - // Maybe I'm misunderstanding the clone function right now :( - current_thread_runtime.block_on(listener.on_receive(payload.0.clone())); - } - } - } - Ok(None) => continue, // No sample available - Err(e) => { - return Err(UStatus::fail_with_code( - UCode::INTERNAL, - format!("Failed to receive sample: {e}"), - )); - } - } - } - Ok(()) - } -} diff --git a/src/workers/worker.rs b/src/workers/worker.rs index 779f37a..e35e646 100644 --- a/src/workers/worker.rs +++ b/src/workers/worker.rs @@ -12,35 +12,19 @@ // ################################################################################ use std::sync::{Arc, atomic::AtomicBool}; -use tokio::sync::{Mutex, mpsc::Receiver}; -use up_rust::{UListener, UMessage, UStatus, UUri}; -use crate::workers::command::WorkerCommand; +use crate::utransport_pubsub::Iceoryx2PubSub; -pub(crate) trait Iceoryx2Worker { - // begin UTransport wrapper methods - - fn send(&mut self, message: UMessage) -> Result<(), UStatus>; - - fn register_listener( - &mut self, - source_filter: UUri, - sink_filter: Option, - listener: Arc, - ) -> Result<(), UStatus>; - - fn unregister_listener( - &mut self, - source_filter: UUri, - sink_filter: Option, - listener: Arc, - ) -> Result<(), UStatus>; - - // end UTransport wrapper methods - - fn get_command_receiver(&self) -> Arc>>; - - fn keep_alive(&self) -> &AtomicBool; +pub struct Iceoryx2Worker { + pub keep_alive: Arc, + pub transport: Arc, +} - async fn receive_and_notify_listeners(&self) -> Result<(), UStatus>; +impl Iceoryx2Worker { + pub fn new(transport: Arc) -> Self { + Self { + keep_alive: Arc::new(AtomicBool::new(true)), + transport, + } + } } From a8492e55435afcb39e9726d0f15dc02c8aa32384 Mon Sep 17 00:00:00 2001 From: Kenneth Mead Date: Mon, 22 Sep 2025 02:03:10 +0000 Subject: [PATCH 4/7] fixed tokio runtime issue and added publisher example added publisher example !!! now there is a segmentation fault when the subscriber receives a published message. yay, a new error --- .vscode/launch.json | 18 ++++++++++ examples/common/constants.rs | 21 +++++++++++ examples/common/helpers.rs | 3 +- examples/common/mod.rs | 19 ++++++++++ examples/publisher.rs | 48 +++++++++++++++++++++++++ examples/subscriber.rs | 22 ++++++------ src/service_name_mapping.rs | 2 +- src/transport.rs | 2 +- src/utransport_pubsub.rs | 69 ++++++++++++++++++++---------------- src/workers/dispatcher.rs | 6 ++-- 10 files changed, 160 insertions(+), 50 deletions(-) create mode 100644 examples/common/constants.rs create mode 100644 examples/publisher.rs diff --git a/.vscode/launch.json b/.vscode/launch.json index 68554f5..ab72e9d 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -37,6 +37,24 @@ }, "args": [], "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug example 'publisher'", + "cargo": { + "args": [ + "build", + "--example=publisher", + "--package=up-transport-iceoryx2-rust" + ], + "filter": { + "name": "publisher", + "kind": "example" + } + }, + "args": [], + "cwd": "${workspaceFolder}" } ] } \ No newline at end of file diff --git a/examples/common/constants.rs b/examples/common/constants.rs new file mode 100644 index 0000000..9a97beb --- /dev/null +++ b/examples/common/constants.rs @@ -0,0 +1,21 @@ +// ################################################################################ +// Copyright (c) 2025 Contributors to the Eclipse Foundation +// +// See the NOTICE file(s) distributed with this work for additional +// information regarding copyright ownership. +// +// This program and the accompanying materials are made available under the +// terms of the Apache License Version 2.0 which is available at +// https: //www.apache.org/licenses/LICENSE-2.0 +// +// SPDX-License-Identifier: Apache-2.0 +// ################################################################################ + +use std::time::Duration; + +/// The source filter publisher examples will use +pub static SOURCE_FILTER_STR: &str = "up://device1/10AB/3/80CD"; + +/// A UMessage will be published at this frequency +#[allow(dead_code)] +pub static CYCLE_TIME: Duration = Duration::from_secs(1); diff --git a/examples/common/helpers.rs b/examples/common/helpers.rs index f540115..0229c81 100644 --- a/examples/common/helpers.rs +++ b/examples/common/helpers.rs @@ -14,6 +14,7 @@ use up_rust::UMessage; /// Simply prints the [`UMessage`] instances source uri, sink uri, and payload to STDOUT +#[allow(dead_code)] pub fn print_umessage(msg: &UMessage) { let payload_utf8 = msg.payload.as_ref().map(|p| String::from_utf8_lossy(p)); let (source_uri, sink_uri) = get_source_and_sink_uri(msg); @@ -23,7 +24,7 @@ pub fn print_umessage(msg: &UMessage) { println!("Payload: {payload_utf8:?}"); } -pub fn get_source_and_sink_uri(msg: &UMessage) -> (Option, Option) { +fn get_source_and_sink_uri(msg: &UMessage) -> (Option, Option) { let source_uri = msg .attributes .as_ref() diff --git a/examples/common/mod.rs b/examples/common/mod.rs index 1630fab..343a9f0 100644 --- a/examples/common/mod.rs +++ b/examples/common/mod.rs @@ -1 +1,20 @@ +// ################################################################################ +// Copyright (c) 2025 Contributors to the Eclipse Foundation +// +// See the NOTICE file(s) distributed with this work for additional +// information regarding copyright ownership. +// +// This program and the accompanying materials are made available under the +// terms of the Apache License Version 2.0 which is available at +// https: //www.apache.org/licenses/LICENSE-2.0 +// +// SPDX-License-Identifier: Apache-2.0 +// ################################################################################ + +pub mod constants; pub mod helpers; + +#[allow(unused_imports)] +pub use constants::*; +#[allow(unused_imports)] +pub use helpers::*; diff --git a/examples/publisher.rs b/examples/publisher.rs new file mode 100644 index 0000000..b07fc7c --- /dev/null +++ b/examples/publisher.rs @@ -0,0 +1,48 @@ +// ################################################################################ +// Copyright (c) 2025 Contributors to the Eclipse Foundation +// +// See the NOTICE file(s) distributed with this work for additional +// information regarding copyright ownership. +// +// This program and the accompanying materials are made available under the +// terms of the Apache License Version 2.0 which is available at +// https: //www.apache.org/licenses/LICENSE-2.0 +// +// SPDX-License-Identifier: Apache-2.0 +// ################################################################################ + +use std::{error::Error, str::FromStr}; +use up_rust::{UMessage, UMessageBuilder, UPayloadFormat, UTransport, UUri}; +use up_transport_iceoryx2_rust::{MessagingPattern, transport::UTransportIceoryx2}; + +mod common; +use crate::common::*; + +fn create_umessage(source_filter: &UUri, counter: u64) -> Result> { + let payload = format!("Hello, Iceoryx2! Message {counter}"); + let umessage = UMessageBuilder::publish(source_filter.clone()) + .build_with_payload(payload.into_bytes(), UPayloadFormat::UPAYLOAD_FORMAT_TEXT)?; + Ok(umessage) +} + +#[tokio::main(flavor = "multi_thread")] +async fn main() -> Result<(), Box> { + println!("uProtocol UTransportIceoryx2 publisher example"); + let source_filter = UUri::from_str(SOURCE_FILTER_STR).expect("Failed to create source UUri"); + let transport = UTransportIceoryx2::build(MessagingPattern::PublishSubscribe)?; + let mut counter: u64 = 0; + let cycle_time_str = format!("{CYCLE_TIME:?}"); + println!( + "Publishing a UMessage with an incrementing counter every '{cycle_time_str}' second with source filter '{SOURCE_FILTER_STR}'" + ); + loop { + counter += 1; + let umessage = create_umessage(&source_filter, counter)?; + let memory_address = &umessage; + println!( + "Publishing message with source filter '{SOURCE_FILTER_STR}' and memory address {memory_address:p}" + ); + transport.send(umessage).await?; + tokio::time::sleep(CYCLE_TIME).await; + } +} diff --git a/examples/subscriber.rs b/examples/subscriber.rs index 7b37b7d..89f0d69 100644 --- a/examples/subscriber.rs +++ b/examples/subscriber.rs @@ -13,36 +13,34 @@ use async_trait::async_trait; use std::{str::FromStr, sync::Arc}; -use tracing::info; use up_rust::{UListener, UMessage, UTransport, UUri}; use up_transport_iceoryx2_rust::{MessagingPattern, transport::UTransportIceoryx2}; -use crate::common::helpers::*; - mod common; +use crate::common::*; -struct SubscriberListener(tokio::runtime::Runtime); +struct SubscriberListener; #[async_trait] impl UListener for SubscriberListener { /// Spawns a task to process the received message. In this example, we simply print the message contents. async fn on_receive(&self, msg: UMessage) { - self.0.spawn(async move { - print_umessage(&msg); - }); + print_umessage(&msg); } } #[tokio::main(flavor = "multi_thread")] async fn main() -> Result<(), Box> { - info!("uProtocols UTransportIceoryx2 subscriber example"); - let source_filter = - UUri::from_str("up://device1/10AB/3/80CD").expect("Failed to create source UUri"); + println!("uProtocol UTransportIceoryx2 subscriber example"); + let source_filter = UUri::from_str(SOURCE_FILTER_STR).expect("Failed to create source UUri"); let transport = UTransportIceoryx2::build(MessagingPattern::PublishSubscribe)?; - let ulistener = Arc::new(SubscriberListener(tokio::runtime::Runtime::new()?)); + let ulistener = Arc::new(SubscriberListener); transport .register_listener(&source_filter, None, ulistener) .await?; - info!("Listener registered. Waiting for messages..."); + println!( + "Listening to message from source filter '{SOURCE_FILTER_STR}'. Press CTRL+C to kill this subscriber" + ); + println!("Waiting for messages..."); tokio::signal::ctrl_c().await.map_err(Box::from) } diff --git a/src/service_name_mapping.rs b/src/service_name_mapping.rs index 84ddec2..7fbed96 100644 --- a/src/service_name_mapping.rs +++ b/src/service_name_mapping.rs @@ -55,7 +55,7 @@ fn determine_message_type( } fn is_a_publish(source: &UUri, messaging_pattern: MessagingPattern) -> bool { - source.is_empty() == false && messaging_pattern == MessagingPattern::PublishSubscribe + !source.is_empty() && messaging_pattern == MessagingPattern::PublishSubscribe } pub fn compute_service_name( diff --git a/src/transport.rs b/src/transport.rs index 6cfb69c..0367a80 100644 --- a/src/transport.rs +++ b/src/transport.rs @@ -31,7 +31,7 @@ impl UTransportIceoryx2 { } } - fn build_publish_subscribe() -> Arc { + fn build_publish_subscribe() -> Arc { Iceoryx2PubSub::new() } } diff --git a/src/utransport_pubsub.rs b/src/utransport_pubsub.rs index c828949..7f4cab5 100644 --- a/src/utransport_pubsub.rs +++ b/src/utransport_pubsub.rs @@ -32,7 +32,6 @@ use crate::{ pub struct Iceoryx2PubSub { node: Node, - listener_worker_runtime: tokio::runtime::Runtime, pub publishers: PublisherSet, pub subscribers: SubscriberSet, pub listeners: ListenerMap, @@ -43,28 +42,22 @@ impl Iceoryx2PubSub { let node = NodeBuilder::new() .create::() .expect("Failed to create Iceoryx2 Node"); - let listener_worker_runtime = - tokio::runtime::Runtime::new().expect("Failed to create Tokio runtime"); let transport = Arc::new(Self { node, publishers: RwLock::new(HashMap::new()), subscribers: RwLock::new(HashMap::new()), listeners: RwLock::new(HashMap::new()), - listener_worker_runtime, }); - Iceoryx2WorkerDispatcher::start_listener_worker( - &transport.listener_worker_runtime, - transport.clone(), - ); + Iceoryx2WorkerDispatcher::start_listener_worker(transport.clone()); transport } pub fn create_subscriber( &self, + service_name: ServiceName, ) -> Result, UStatus> { // Placeholder implementation - let service_name: ServiceName = "example_service".try_into().unwrap(); let service = self .node .service_builder(&service_name) @@ -85,15 +78,21 @@ impl Iceoryx2PubSub { service_name: ServiceName, ) -> Result>, UStatus> { - let publishers = self.publishers.read().await; - if publishers.contains_key(&service_name) { - let publisher = publishers.get(&service_name).unwrap(); - return Ok(publisher.clone()); + let publisher = self.get_publisher(service_name.clone()).await; + if let Some(publisher) = publisher { + return Ok(publisher); } - let service_name_res: Result = service_name.as_str().try_into(); + self.create_publisher(service_name).await + } + + async fn create_publisher( + &self, + service_name: ServiceName, + ) -> Result>, UStatus> + { let service = self .node - .service_builder(&service_name_res.unwrap()) + .service_builder(&service_name) .publish_subscribe::() .user_header::() .open_or_create() @@ -110,17 +109,21 @@ impl Iceoryx2PubSub { Ok(publisher.clone()) } + async fn get_publisher( + &self, + service_name: ServiceName, + ) -> Option>> { + let publishers = self.publishers.read().await; + if publishers.contains_key(&service_name) { + let publisher = publishers.get(&service_name).unwrap(); + return Some(publisher.clone()); + } + None + } + pub async fn relay(&self) -> Result<(), UStatus> { - let current_thread_runtime = tokio::runtime::Builder::new_current_thread() - .enable_all() - .build() - .map_err(|e| { - UStatus::fail_with_code( - UCode::INTERNAL, - format!("Failed to build current_thread runtime: {e}"), - ) - })?; - for (service_name, subscriber) in self.subscribers.read().await.iter() { + let subscribers = self.subscribers.read().await; + for (service_name, subscriber) in subscribers.iter() { match subscriber.receive() { Ok(Some(sample)) => { let payload = sample; @@ -128,7 +131,8 @@ impl Iceoryx2PubSub { { for listener in listeners_to_notify.iter() { let listener: &ComparableListener = listener; - current_thread_runtime.block_on(listener.on_receive(payload.0.clone())); + let payload_clone = payload.0.clone(); + listener.on_receive(payload_clone).await; } } } @@ -182,14 +186,17 @@ impl UTransport for Iceoryx2PubSub { ) -> Result<(), UStatus> { up_rust::verify_filter_criteria(source_filter, sink_filter)?; let service_name = compute_service_name( - &source_filter, + source_filter, sink_filter, MessagingPattern::PublishSubscribe, )?; - let subscribers = self.subscribers.read().await; + let has_subscriber = { + let subscribers = self.subscribers.read().await; + subscribers.contains_key(&service_name) + }; // insert subscriber for service name if it does not already exist - if !subscribers.contains_key(&service_name) { - let subscriber = self.create_subscriber()?; + if !has_subscriber { + let subscriber = self.create_subscriber(service_name.clone())?; let mut subscribers = self.subscribers.write().await; subscribers.insert(service_name.clone(), Arc::new(subscriber)); } @@ -212,7 +219,7 @@ impl UTransport for Iceoryx2PubSub { ) -> Result<(), UStatus> { up_rust::verify_filter_criteria(source_filter, sink_filter)?; let service_name = compute_service_name( - &source_filter, + source_filter, sink_filter, MessagingPattern::PublishSubscribe, )?; diff --git a/src/workers/dispatcher.rs b/src/workers/dispatcher.rs index af34fce..a1d57d4 100644 --- a/src/workers/dispatcher.rs +++ b/src/workers/dispatcher.rs @@ -12,7 +12,6 @@ // ################################################################################ use std::sync::{Arc, atomic::Ordering}; -use tokio::runtime::Runtime; use up_rust::UStatus; use crate::{utransport_pubsub::Iceoryx2PubSub, workers::worker::Iceoryx2Worker}; @@ -20,10 +19,9 @@ use crate::{utransport_pubsub::Iceoryx2PubSub, workers::worker::Iceoryx2Worker}; pub struct Iceoryx2WorkerDispatcher {} impl Iceoryx2WorkerDispatcher { - pub fn start_listener_worker(runtime: &Runtime, transport: Arc) { + pub fn start_listener_worker(transport: Arc) { let worker = Iceoryx2Worker::new(transport.clone()); - let future = Iceoryx2WorkerDispatcher::run(worker); - runtime.spawn(future); + tokio::spawn(async { Iceoryx2WorkerDispatcher::run(worker).await }); } async fn run(worker: Iceoryx2Worker) -> Result<(), UStatus> { From 1443a568c7186d0e4364ff6eb347ace7a6a9df98 Mon Sep 17 00:00:00 2001 From: Kenneth Mead Date: Mon, 22 Sep 2025 02:08:18 +0000 Subject: [PATCH 5/7] removed tracing package --- Cargo.lock | 13 ------------- Cargo.toml | 1 - 2 files changed, 14 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index adb229a..b180e96 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1074,21 +1074,9 @@ checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" dependencies = [ "log", "pin-project-lite", - "tracing-attributes", "tracing-core", ] -[[package]] -name = "tracing-attributes" -version = "0.1.30" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81383ab64e72a7a8b8e13130c49e3dab29def6d0c7d76a03087b3cf71c5c6903" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - [[package]] name = "tracing-core" version = "0.1.34" @@ -1133,7 +1121,6 @@ dependencies = [ "iceoryx2", "iceoryx2-bb-container", "tokio", - "tracing", "up-rust", ] diff --git a/Cargo.toml b/Cargo.toml index 29370b4..8d4ecb6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -23,5 +23,4 @@ async-trait = "0.1" tokio = { version = "1.47.1", features = ["macros", "rt-multi-thread", "signal"] } iceoryx2 = "0.7.0" iceoryx2-bb-container = "0.7.0" -tracing = "0.1.41" hostname = "0.4.1" From 4f2902d2062a0a8253af1bc1a57df5dc973e16c6 Mon Sep 17 00:00:00 2001 From: Kenneth Mead Date: Tue, 23 Sep 2025 20:15:44 +0000 Subject: [PATCH 6/7] added hacky workaround for UMessage to work with ZeroCopySend --- Cargo.lock | 1 + Cargo.toml | 1 + examples/common/helpers.rs | 1 - examples/publisher.rs | 19 ++++---- examples/subscriber.rs | 15 +++--- src/umessage.rs | 95 +++++++++++++++++++++++++++++++++++--- src/utransport_pubsub.rs | 16 ++++++- 7 files changed, 122 insertions(+), 26 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index b180e96..a4675aa 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1120,6 +1120,7 @@ dependencies = [ "hostname", "iceoryx2", "iceoryx2-bb-container", + "protobuf", "tokio", "up-rust", ] diff --git a/Cargo.toml b/Cargo.toml index 8d4ecb6..d874047 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -24,3 +24,4 @@ tokio = { version = "1.47.1", features = ["macros", "rt-multi-thread", "signal"] iceoryx2 = "0.7.0" iceoryx2-bb-container = "0.7.0" hostname = "0.4.1" +protobuf = "3.7.2" diff --git a/examples/common/helpers.rs b/examples/common/helpers.rs index 0229c81..2e4f8a3 100644 --- a/examples/common/helpers.rs +++ b/examples/common/helpers.rs @@ -18,7 +18,6 @@ use up_rust::UMessage; pub fn print_umessage(msg: &UMessage) { let payload_utf8 = msg.payload.as_ref().map(|p| String::from_utf8_lossy(p)); let (source_uri, sink_uri) = get_source_and_sink_uri(msg); - println!("Received a message!"); println!("Source Uri: {source_uri:?}"); println!("Sink Uri: {sink_uri:?}"); println!("Payload: {payload_utf8:?}"); diff --git a/examples/publisher.rs b/examples/publisher.rs index b07fc7c..dcfbc7c 100644 --- a/examples/publisher.rs +++ b/examples/publisher.rs @@ -18,8 +18,7 @@ use up_transport_iceoryx2_rust::{MessagingPattern, transport::UTransportIceoryx2 mod common; use crate::common::*; -fn create_umessage(source_filter: &UUri, counter: u64) -> Result> { - let payload = format!("Hello, Iceoryx2! Message {counter}"); +fn create_umessage(source_filter: &UUri, payload: String) -> Result> { let umessage = UMessageBuilder::publish(source_filter.clone()) .build_with_payload(payload.into_bytes(), UPayloadFormat::UPAYLOAD_FORMAT_TEXT)?; Ok(umessage) @@ -31,17 +30,15 @@ async fn main() -> Result<(), Box> { let source_filter = UUri::from_str(SOURCE_FILTER_STR).expect("Failed to create source UUri"); let transport = UTransportIceoryx2::build(MessagingPattern::PublishSubscribe)?; let mut counter: u64 = 0; - let cycle_time_str = format!("{CYCLE_TIME:?}"); - println!( - "Publishing a UMessage with an incrementing counter every '{cycle_time_str}' second with source filter '{SOURCE_FILTER_STR}'" - ); loop { counter += 1; - let umessage = create_umessage(&source_filter, counter)?; - let memory_address = &umessage; - println!( - "Publishing message with source filter '{SOURCE_FILTER_STR}' and memory address {memory_address:p}" - ); + let payload = format!("Hello, from uProtocols UTransport with Iceoryx2! Message {counter}"); + let umessage = create_umessage(&source_filter, payload)?; + let payload_memory_address = umessage.payload.as_ref().unwrap(); + println!("Publishing message!"); + print_umessage(&umessage); + println!("Payload Memory address: {payload_memory_address:p}"); + println!(); transport.send(umessage).await?; tokio::time::sleep(CYCLE_TIME).await; } diff --git a/examples/subscriber.rs b/examples/subscriber.rs index 89f0d69..068db87 100644 --- a/examples/subscriber.rs +++ b/examples/subscriber.rs @@ -19,13 +19,16 @@ use up_transport_iceoryx2_rust::{MessagingPattern, transport::UTransportIceoryx2 mod common; use crate::common::*; -struct SubscriberListener; +struct ConsolePrinter; #[async_trait] -impl UListener for SubscriberListener { - /// Spawns a task to process the received message. In this example, we simply print the message contents. - async fn on_receive(&self, msg: UMessage) { - print_umessage(&msg); +impl UListener for ConsolePrinter { + async fn on_receive(&self, message: UMessage) { + let payload_memory_address = message.payload.as_ref().unwrap(); + println!("Received a message!"); + print_umessage(&message); + println!("Payload Memory address: {payload_memory_address:p}"); + println!(); } } @@ -34,7 +37,7 @@ async fn main() -> Result<(), Box> { println!("uProtocol UTransportIceoryx2 subscriber example"); let source_filter = UUri::from_str(SOURCE_FILTER_STR).expect("Failed to create source UUri"); let transport = UTransportIceoryx2::build(MessagingPattern::PublishSubscribe)?; - let ulistener = Arc::new(SubscriberListener); + let ulistener = Arc::new(ConsolePrinter); transport .register_listener(&source_filter, None, ulistener) .await?; diff --git a/src/umessage.rs b/src/umessage.rs index 364a25e..0e6591e 100644 --- a/src/umessage.rs +++ b/src/umessage.rs @@ -1,25 +1,108 @@ // ################################################################################ // Copyright (c) 2025 Contributors to the Eclipse Foundation -// +// // See the NOTICE file(s) distributed with this work for additional // information regarding copyright ownership. -// +// // This program and the accompanying materials are made available under the // terms of the Apache License Version 2.0 which is available at // https: //www.apache.org/licenses/LICENSE-2.0 -// +// // SPDX-License-Identifier: Apache-2.0 // ################################################################################ -use std::fmt::Debug; use iceoryx2::prelude::ZeroCopySend; +use protobuf::Message; +use std::fmt::Debug; use up_rust::UMessage; +const MAX_UMESSAGE_SIZE: usize = 4096; + +/// Zero-copy safe wrapper for UMessage that serializes the protobuf data +/// into a fixed-size byte array to meet ZeroCopySend requirements. +/// +/// See [ZeroCopySend's safety requirements](https://docs.rs/iceoryx2/latest/iceoryx2/prelude/trait.ZeroCopySend.html#safety) for more details. +/// +/// ## DISCLAIMER +/// +/// This code is a prototype to make UMessage work with iceoryx2's ZeroCopySend system +/// +/// UMessage is not ZeroCopySend compatible as-is. If UMessages are sent +/// directly to an iceoryx2 publisher, it will compile. However, the +/// subscriber will receive a segmentation fault when receiving theUMessage +/// +/// This essentially defeats the purpose of using iceoryx2 and +/// ZeroCopySend, as it copies the data into a fixed-size array and then out +/// of the array and back into a UMessage inside the `UTransport.send()` method +/// and the `UListener.on_receive()` method. The UTransport or UMessage +/// definition needs to be adjusted for this to truly be a zero-copy transport. #[derive(Debug)] -pub struct UMessageZeroCopy(pub UMessage); +#[repr(C)] +pub struct UMessageZeroCopy { + data_length: u32, + data: [u8; MAX_UMESSAGE_SIZE], +} + +impl UMessageZeroCopy { + /// Create a new UMessageZeroCopy from a UMessage + pub fn new(message: UMessage) -> Result { + let serialized_data = message + .write_to_bytes() + .map_err(|e| format!("Failed to serialize UMessage: {}", e))?; + if serialized_data.len() > MAX_UMESSAGE_SIZE { + return Err(format!( + "UMessage too large: {} bytes (max: {})", + serialized_data.len(), + MAX_UMESSAGE_SIZE + )); + } + let mut data = [0u8; MAX_UMESSAGE_SIZE]; + data[..serialized_data.len()].copy_from_slice(&serialized_data); + Ok(Self { + data_length: serialized_data.len() as u32, + data, + }) + } + + /// Convert back to UMessage + pub fn to_umessage(&self) -> Result { + let data_slice = &self.data[..self.data_length as usize]; + UMessage::parse_from_bytes(data_slice) + .map_err(|e| format!("Failed to deserialize UMessage: {}", e)) + } + + /// Get the serialized data as a byte slice + pub fn as_bytes(&self) -> &[u8] { + &self.data[..self.data_length as usize] + } + + /// Get the length of the actual data + pub fn len(&self) -> usize { + self.data_length as usize + } + + /// Check if the message is empty + pub fn is_empty(&self) -> bool { + self.data_length == 0 + } +} unsafe impl ZeroCopySend for UMessageZeroCopy { unsafe fn type_name() -> &'static str { - core::any::type_name::() + "UMessageZeroCopy" + } +} + +impl From for UMessageZeroCopy { + fn from(message: UMessage) -> Self { + Self::new(message).expect("Failed to convert UMessage to UMessageZeroCopy") + } +} + +impl TryFrom for UMessage { + type Error = String; + + fn try_from(zero_copy: UMessageZeroCopy) -> Result { + zero_copy.to_umessage() } } diff --git a/src/utransport_pubsub.rs b/src/utransport_pubsub.rs index 7f4cab5..26aff11 100644 --- a/src/utransport_pubsub.rs +++ b/src/utransport_pubsub.rs @@ -127,11 +127,17 @@ impl Iceoryx2PubSub { match subscriber.receive() { Ok(Some(sample)) => { let payload = sample; + let umessage = payload.to_umessage().map_err(|e| { + UStatus::fail_with_code( + UCode::INTERNAL, + format!("Failed to deserialize UMessage: {}", e), + ) + })?; if let Some(listeners_to_notify) = self.listeners.read().await.get(service_name) { for listener in listeners_to_notify.iter() { let listener: &ComparableListener = listener; - let payload_clone = payload.0.clone(); + let payload_clone = umessage.clone(); listener.on_receive(payload_clone).await; } } @@ -171,7 +177,13 @@ impl UTransport for Iceoryx2PubSub { let sample = publisher.loan_uninit().map_err(|e| { UStatus::fail_with_code(UCode::INTERNAL, format!("Failed to loan sample: {e}")) })?; - let sample_final = sample.write_payload(UMessageZeroCopy(message)); + let zero_copy_message = UMessageZeroCopy::new(message).map_err(|e| { + UStatus::fail_with_code( + UCode::INTERNAL, + format!("Failed to create zero-copy message: {}", e), + ) + })?; + let sample_final = sample.write_payload(zero_copy_message); sample_final.send().map_err(|e| { UStatus::fail_with_code(UCode::INTERNAL, format!("Failed to send: {e}")) })?; From 03e99a4a59cf7b3415a08fa4985a85f200600719 Mon Sep 17 00:00:00 2001 From: Kenneth Mead Date: Tue, 23 Sep 2025 23:08:58 +0000 Subject: [PATCH 7/7] now converts UMessages directly into a u8 slice --- src/lib.rs | 9 ++-- src/transport.rs | 1 - src/umessage.rs | 108 --------------------------------------- src/uprotocolheader.rs | 9 ++-- src/utransport_pubsub.rs | 107 ++++++++++++++++++++++++++++---------- 5 files changed, 88 insertions(+), 146 deletions(-) delete mode 100644 src/umessage.rs diff --git a/src/lib.rs b/src/lib.rs index 9661689..2b5755b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -11,6 +11,8 @@ // SPDX-License-Identifier: Apache-2.0 // ################################################################################ +pub const UPROTOCOL_MAJOR_VERSION: u8 = 0; + use iceoryx2::{ port::{publisher::Publisher, subscriber::Subscriber}, prelude::{ServiceName, ZeroCopySend}, @@ -23,11 +25,10 @@ use std::{ use tokio::sync::RwLock; use up_rust::ComparableListener; -use crate::{umessage::UMessageZeroCopy, uprotocolheader::UProtocolHeader}; +use crate::uprotocolheader::UProtocolHeader; pub(crate) mod service_name_mapping; pub mod transport; -pub(crate) mod umessage; pub(crate) mod uprotocolheader; pub(crate) mod utransport_pubsub; pub(crate) mod workers; @@ -38,7 +39,7 @@ pub trait BaseUserHeader: Debug + ZeroCopySend {} pub trait BasePayload: Debug + ZeroCopySend {} pub(crate) type PublisherSet = - RwLock>>>; + RwLock>>>; pub(crate) type SubscriberSet = - RwLock>>>; + RwLock>>>; pub(crate) type ListenerMap = RwLock>>; diff --git a/src/transport.rs b/src/transport.rs index 0367a80..08b3591 100644 --- a/src/transport.rs +++ b/src/transport.rs @@ -19,7 +19,6 @@ use up_rust::{UCode, UStatus, UTransport}; pub struct UTransportIceoryx2 {} -/// Acts as a uProtocol-specific interface for the Iceoryx2 transport system impl UTransportIceoryx2 { pub fn build(messaging_pattern: MessagingPattern) -> Result, UStatus> { match messaging_pattern { diff --git a/src/umessage.rs b/src/umessage.rs deleted file mode 100644 index 0e6591e..0000000 --- a/src/umessage.rs +++ /dev/null @@ -1,108 +0,0 @@ -// ################################################################################ -// Copyright (c) 2025 Contributors to the Eclipse Foundation -// -// See the NOTICE file(s) distributed with this work for additional -// information regarding copyright ownership. -// -// This program and the accompanying materials are made available under the -// terms of the Apache License Version 2.0 which is available at -// https: //www.apache.org/licenses/LICENSE-2.0 -// -// SPDX-License-Identifier: Apache-2.0 -// ################################################################################ - -use iceoryx2::prelude::ZeroCopySend; -use protobuf::Message; -use std::fmt::Debug; -use up_rust::UMessage; - -const MAX_UMESSAGE_SIZE: usize = 4096; - -/// Zero-copy safe wrapper for UMessage that serializes the protobuf data -/// into a fixed-size byte array to meet ZeroCopySend requirements. -/// -/// See [ZeroCopySend's safety requirements](https://docs.rs/iceoryx2/latest/iceoryx2/prelude/trait.ZeroCopySend.html#safety) for more details. -/// -/// ## DISCLAIMER -/// -/// This code is a prototype to make UMessage work with iceoryx2's ZeroCopySend system -/// -/// UMessage is not ZeroCopySend compatible as-is. If UMessages are sent -/// directly to an iceoryx2 publisher, it will compile. However, the -/// subscriber will receive a segmentation fault when receiving theUMessage -/// -/// This essentially defeats the purpose of using iceoryx2 and -/// ZeroCopySend, as it copies the data into a fixed-size array and then out -/// of the array and back into a UMessage inside the `UTransport.send()` method -/// and the `UListener.on_receive()` method. The UTransport or UMessage -/// definition needs to be adjusted for this to truly be a zero-copy transport. -#[derive(Debug)] -#[repr(C)] -pub struct UMessageZeroCopy { - data_length: u32, - data: [u8; MAX_UMESSAGE_SIZE], -} - -impl UMessageZeroCopy { - /// Create a new UMessageZeroCopy from a UMessage - pub fn new(message: UMessage) -> Result { - let serialized_data = message - .write_to_bytes() - .map_err(|e| format!("Failed to serialize UMessage: {}", e))?; - if serialized_data.len() > MAX_UMESSAGE_SIZE { - return Err(format!( - "UMessage too large: {} bytes (max: {})", - serialized_data.len(), - MAX_UMESSAGE_SIZE - )); - } - let mut data = [0u8; MAX_UMESSAGE_SIZE]; - data[..serialized_data.len()].copy_from_slice(&serialized_data); - Ok(Self { - data_length: serialized_data.len() as u32, - data, - }) - } - - /// Convert back to UMessage - pub fn to_umessage(&self) -> Result { - let data_slice = &self.data[..self.data_length as usize]; - UMessage::parse_from_bytes(data_slice) - .map_err(|e| format!("Failed to deserialize UMessage: {}", e)) - } - - /// Get the serialized data as a byte slice - pub fn as_bytes(&self) -> &[u8] { - &self.data[..self.data_length as usize] - } - - /// Get the length of the actual data - pub fn len(&self) -> usize { - self.data_length as usize - } - - /// Check if the message is empty - pub fn is_empty(&self) -> bool { - self.data_length == 0 - } -} - -unsafe impl ZeroCopySend for UMessageZeroCopy { - unsafe fn type_name() -> &'static str { - "UMessageZeroCopy" - } -} - -impl From for UMessageZeroCopy { - fn from(message: UMessage) -> Self { - Self::new(message).expect("Failed to convert UMessage to UMessageZeroCopy") - } -} - -impl TryFrom for UMessage { - type Error = String; - - fn try_from(zero_copy: UMessageZeroCopy) -> Result { - zero_copy.to_umessage() - } -} diff --git a/src/uprotocolheader.rs b/src/uprotocolheader.rs index b691ac0..8598b47 100644 --- a/src/uprotocolheader.rs +++ b/src/uprotocolheader.rs @@ -14,13 +14,12 @@ use iceoryx2::prelude::ZeroCopySend; use iceoryx2_bb_container::vec::FixedSizeVec; -const MAX_FEASIBLE_UATTRIBUTES_SERIALIZED_LENGTH: usize = 1000; // choosing ~1000 u8s -// somewhat arbitrarily -// this should be confirmed +pub const MAX_FEASIBLE_UATTRIBUTES_SERIALIZED_LENGTH: usize = 1000; +/// Also see [uAttributes Mapping to iceoryx2 user header](https://github.com/eclipse-uprotocol/up-spec/blob/0cc43c8afb7d7cbd3169ffe093be761c57308cef/up-l1/iceoryx2.adoc#411-uattributes-mapping-to-iceoryx2-user-header) #[repr(C)] #[derive(ZeroCopySend, Debug, Default)] pub struct UProtocolHeader { - uprotocol_major_version: u8, - uattributes_serialized: FixedSizeVec, + pub(crate) uprotocol_major_version: u8, + pub(crate) uattributes_serialized: FixedSizeVec, } diff --git a/src/utransport_pubsub.rs b/src/utransport_pubsub.rs index 26aff11..e70bb7d 100644 --- a/src/utransport_pubsub.rs +++ b/src/utransport_pubsub.rs @@ -12,24 +12,30 @@ // // ################################################################################ use async_trait::async_trait; -use iceoryx2::prelude::MessagingPattern; +use iceoryx2::prelude::{AllocationStrategy, MessagingPattern}; +use iceoryx2::sample_mut::SampleMut; use iceoryx2::{ node::{Node, NodeBuilder}, port::{publisher::Publisher, subscriber::Subscriber}, prelude::ServiceName, service::ipc_threadsafe, }; +use iceoryx2_bb_container::vec::FixedSizeVec; +use protobuf::Message; use std::collections::HashMap; use std::sync::Arc; use tokio::sync::RwLock; use up_rust::{ComparableListener, UCode, UListener, UMessage, UStatus, UTransport, UUri}; +use crate::UPROTOCOL_MAJOR_VERSION; +use crate::uprotocolheader::MAX_FEASIBLE_UATTRIBUTES_SERIALIZED_LENGTH; use crate::workers::dispatcher::Iceoryx2WorkerDispatcher; use crate::{ ListenerMap, PublisherSet, SubscriberSet, service_name_mapping::compute_service_name, - umessage::UMessageZeroCopy, uprotocolheader::UProtocolHeader, + uprotocolheader::UProtocolHeader, }; +#[derive(Debug)] pub struct Iceoryx2PubSub { node: Node, pub publishers: PublisherSet, @@ -55,13 +61,11 @@ impl Iceoryx2PubSub { pub fn create_subscriber( &self, service_name: ServiceName, - ) -> Result, UStatus> - { - // Placeholder implementation + ) -> Result, UStatus> { let service = self .node .service_builder(&service_name) - .publish_subscribe::() + .publish_subscribe::<[u8]>() .user_header::() .open_or_create() .map_err(|e| { @@ -76,8 +80,7 @@ impl Iceoryx2PubSub { pub async fn get_or_create_publisher( &self, service_name: ServiceName, - ) -> Result>, UStatus> - { + ) -> Result>, UStatus> { let publisher = self.get_publisher(service_name.clone()).await; if let Some(publisher) = publisher { return Ok(publisher); @@ -88,21 +91,24 @@ impl Iceoryx2PubSub { async fn create_publisher( &self, service_name: ServiceName, - ) -> Result>, UStatus> - { + ) -> Result>, UStatus> { let service = self .node .service_builder(&service_name) - .publish_subscribe::() + .publish_subscribe::<[u8]>() .user_header::() .open_or_create() .map_err(|e| { UStatus::fail_with_code(UCode::INTERNAL, format!("Failed to create service: {e}")) })?; - let publisher = service.publisher_builder().create().map_err(|e| { - UStatus::fail_with_code(UCode::INTERNAL, format!("Failed to create publisher: {e}")) - })?; + let publisher = service + .publisher_builder() + .allocation_strategy(AllocationStrategy::PowerOfTwo) + .create() + .map_err(|e| { + UStatus::fail_with_code(UCode::INTERNAL, format!("Failed to create publisher: {e}")) + })?; let mut publishers = self.publishers.write().await; publishers.insert(service_name.clone(), Arc::new(publisher)); let publisher = publishers.get(&service_name).unwrap(); @@ -112,7 +118,7 @@ impl Iceoryx2PubSub { async fn get_publisher( &self, service_name: ServiceName, - ) -> Option>> { + ) -> Option>> { let publishers = self.publishers.read().await; if publishers.contains_key(&service_name) { let publisher = publishers.get(&service_name).unwrap(); @@ -126,8 +132,8 @@ impl Iceoryx2PubSub { for (service_name, subscriber) in subscribers.iter() { match subscriber.receive() { Ok(Some(sample)) => { - let payload = sample; - let umessage = payload.to_umessage().map_err(|e| { + let payload = sample.payload(); + let umessage = UMessage::parse_from_bytes(payload).map_err(|e| { UStatus::fail_with_code( UCode::INTERNAL, format!("Failed to deserialize UMessage: {}", e), @@ -153,10 +159,65 @@ impl Iceoryx2PubSub { } Ok(()) } + + pub fn write_message_to_sample( + &self, + publisher: &Publisher, + message: UMessage, + ) -> Result, UStatus> { + let sample_size = message.compute_size(); + let mut sample = publisher + .loan_slice_uninit(sample_size as usize) + .map_err(|e| { + UStatus::fail_with_code(UCode::INTERNAL, format!("Failed to loan sample: {e}")) + })?; + let message_bytes = message + .write_to_bytes() + .map_err(|e| UStatus::fail_with_code(UCode::INTERNAL, e.to_string()))?; + let serialized_data = message_bytes.as_slice(); + let user_header: &mut UProtocolHeader = sample.user_header_mut(); + self.set_samples_user_header(user_header, message)?; + let sample_final = sample.write_from_slice(serialized_data); + Ok(sample_final) + } + + fn set_samples_user_header( + &self, + user_header: &mut UProtocolHeader, + message: UMessage, + ) -> Result<(), UStatus> { + user_header.uprotocol_major_version = UPROTOCOL_MAJOR_VERSION; + let serialized_uattributes = message + .attributes + .write_to_bytes() + .map_err(|e| UStatus::fail_with_code(UCode::INTERNAL, e.to_string()))?; + let mut fixed_sized_vec: FixedSizeVec = + FixedSizeVec::new(); + for byte in serialized_uattributes.iter() { + fixed_sized_vec.push(*byte); + } + user_header.uattributes_serialized = fixed_sized_vec; + Ok(()) + } } #[async_trait] impl UTransport for Iceoryx2PubSub { + /// ## DISCLAIMER + /// + /// This code is a prototype to make UMessage work with iceoryx2's ZeroCopySend system + /// + /// UMessage is not ZeroCopySend compatible as-is. If UMessages are sent + /// directly to an iceoryx2 publisher, it will compile. However, the + /// subscriber will receive a segmentation fault when receiving theUMessage + /// + /// See [ZeroCopySend's safety requirements](https://docs.rs/iceoryx2/latest/iceoryx2/prelude/trait.ZeroCopySend.html#safety) for more details. + /// + /// This essentially defeats the purpose of using iceoryx2 and + /// ZeroCopySend, as it copies the data into a fixed-size array and then out + /// of the array and back into a UMessage inside the `UTransport.send()` method + /// and the `UListener.on_receive()` method. The UTransport or UMessage + /// definition needs to be adjusted for this to truly be a zero-copy transport. async fn send(&self, message: UMessage) -> Result<(), UStatus> { let service_name = { let source_filter = &message.attributes.source; @@ -173,17 +234,7 @@ impl UTransport for Iceoryx2PubSub { .map_err(|e| { UStatus::fail_with_code(UCode::INTERNAL, format!("Failed to get publisher: {e}")) })?; - - let sample = publisher.loan_uninit().map_err(|e| { - UStatus::fail_with_code(UCode::INTERNAL, format!("Failed to loan sample: {e}")) - })?; - let zero_copy_message = UMessageZeroCopy::new(message).map_err(|e| { - UStatus::fail_with_code( - UCode::INTERNAL, - format!("Failed to create zero-copy message: {}", e), - ) - })?; - let sample_final = sample.write_payload(zero_copy_message); + let sample_final = self.write_message_to_sample(publisher.as_ref(), message)?; sample_final.send().map_err(|e| { UStatus::fail_with_code(UCode::INTERNAL, format!("Failed to send: {e}")) })?;