Skip to content

fix(rmw-zenoh-rs): publish on the DDS-mangled type name, as the wire requires - #324

Merged
YuanYuYuan merged 1 commit into
mainfrom
fix/rmw-zenoh-rs-dds-type-name
Aug 25, 2026
Merged

fix(rmw-zenoh-rs): publish on the DDS-mangled type name, as the wire requires#324
YuanYuYuan merged 1 commit into
mainfrom
fix/rmw-zenoh-rs-dds-type-name

Conversation

@YuanYuYuan

@YuanYuYuan YuanYuYuan commented Aug 24, 2026

Copy link
Copy Markdown
Collaborator

Summary

rmw_zenoh_rs derives the topic key expression from a slash-separated type name. The hiroz library, and upstream rmw_zenoh_cpp, use the DDS-mangled form. They differ in one segment, so a hiroz library node and a ROS 2 node running rmw_zenoh_rs match no topic and exchange nothing. Neither side reports an error.

side key expression
hiroz library 0/chatter/std_msgs::msg::dds_::String_/RIHS01_df668c74…
rmw_zenoh_rs 0/chatter/std_msgs/msg/String/RIHS01_df668c74…

The domain matches. The type hash matches byte for byte. Only the type name differs.

get_type_info() feeds both the key expression and the liveliness token. It read get_ros_type_name(), while get_type_prefix() sat beside it in the same file and produced the mangled form.

Which side is wrong was measured

A demo_nodes_cpp talker ran under RMW_IMPLEMENTATION=rmw_zenoh_cpp. A raw zenoh subscriber on ** recorded the key it declared:

0/chatter/std_msgs::msg::dds_::String_/RIHS01_df668c740482bbd48fb39d76a70dfd4bd59db1288021743503259e948f6b1a18

The library matches the wire. rmw_zenoh_rs is the outlier, so rmw_zenoh_rs is what changes here. Patching the library instead would have broken the test.yml leg that passes today.

The key expression is also the only difference. Raw CDR bytes published on the rmw_zenoh_rs key, produced by no ROS code and carrying no attachment, reached a demo_nodes_cpp listener. The two stacks already agree on domain, type hash, encapsulation and envelope.

Why CI is green

A missing edge, not a weak test. test.yml pairs the library with rmw_zenoh_cpp. rmw-zenoh-rs.yml pairs rmw_zenoh_rs with ROS packages. No leg pairs the library with rmw_zenoh_rs.

What this changes

today with this change
get_type_info() reads get_ros_type_name() reads get_type_prefix()
get_ros_type_name() exists, called twice deleted
type_name_dyn() vs type_info_dyn() disagree agree
the key on the wire …/std_msgs/msg/String/… …/std_msgs::msg::dds_::String_/…
a hiroz node and an rmw_zenoh_rs node exchange nothing, silently exchange messages
ros2 topic list -t, local topic right, from the wrong name right, demangled at the boundary
ros2 topic list -t, remote topic std_msgs::msg::dds_::String_ std_msgs/msg/String

The graph leak on the last row is older than this change. hiroz builds the graph from liveliness tokens. Those tokens already carry the mangled form for every hiroz peer and every rmw_zenoh_cpp peer. So rmw_get_topic_names_and_types already reports the mangled name for a remote publisher on main today. On its own this change would extend that to local topics as well.

ros_from_dds_strict closes both halves. It is a port of _demangle_if_ros_type in rmw_zenoh_cpp's graph_cache.cpp, applied at all eight sites that copy a graph type name into an RMW result.

One rule, ten call sites

Review asked why several derivations existed at all. The rule now lives once, in hiroz_schema::type_name, and ten production sites call it: hiroz-protocol, both hiroz-codegen generators, hiroz-derive, two hiroz::dynamic paths, two action blocks in hiroz::node, hiroz-py, and hiroz-union. Four of those were independent inverses. dds_:: now appears in no production format! or replace outside that module.

hiroz-schema is the home because it already owns the canonical form, and because it is a leaf crate. Nothing pulls zenoh into a build script or a proc macro.

Two inverses remain, on purpose. ros_from_dds_strict matches the reference byte for byte, which an RMW boundary requires. ros_from_dds falls back for a name that has no dds_:: segment, because such a name must still resolve against the schema registry. Both differences were accidental before. A test now pins each.

What fails without this

stage result
RED library_and_rmw_derive_the_same_topic_key fails on the assertion: left: "std_msgs/msg/String", right: "std_msgs::msg::dds_::String_"
GREEN the crate's tests pass, cargo fmt --check clean
end to end library talker to demo_nodes_cpp listener under rmw_zenoh_rs: 35 received. Reverse: 29 received. The sniffed key matches the rmw_zenoh_cpp measurement
re-falsified reverting the mangling turns the tests red and drops the listener to 0 received of 22 published

Tests

All 15 tests this change adds live in dedicated files, not an inline mod tests. Both crates already carried a tests/ directory.

file tests covers
crates/hiroz-schema/tests/type_name.rs 10 the forward rule, both inverses, every kind, the empty namespace, action sub-types, and the two cases where the inverses must disagree
crates/hiroz-protocol/tests/type_name_agreement.rs 5 the topic key, the service key, the liveliness token, the three action interfaces, and the service-name rule

The pre-existing inline module in rmw_zenoh.rs keeps its own 33 tests. Moving those too would add churn to a defect fix; that belongs in its own pull request.

Breaking changes

tag what changes who is affected before → after action
BC1 MessageTypeSupport::get_ros_type_name() is removed any external caller std_msgs/msg/String → call get_type_prefix() call get_type_prefix()
BC2 the key rmw_zenoh_rs publishes on changes a peer matching the old slash-form key …/std_msgs/msg/String/……/std_msgs::msg::dds_::String_/… none — the new key is the one rmw_zenoh_cpp and the library already use
BC3 RMW graph queries demangle a caller expecting the mangled form from a remote peer std_msgs::msg::dds_::String_std_msgs/msg/String none — this is the form ROS tooling expects

No caller of get_ros_type_name() exists in this repository beyond the two this change rewrites.

Coverage

what where it runs
the derivation, both inverses, service and action names, the liveliness token 15 unit tests in two dedicated files
the library against rmw_zenoh_cpp, four distros Interop Tests, which runs hiroz-tests with ros-interop
the Humble no-type-hash configuration the same job's humble leg, --no-default-features --features ros-interop,humble
rmw_zenoh_rs against ROS packages rmw_zenoh_rs with ROS 2 Jazzy
the RMW graph reporting the ROS type name a new test-ros.nu check, run by that same workflow

The check lives in scripts/test-ros.nu, which already registers this project's ROS-dependent tests. It starts a talker under RMW_IMPLEMENTATION=rmw_zenoh_rs, then asserts ros2 topic list -t prints std_msgs/msg/String and no dds_::. It also asserts /chatter appeared at all, so a probe that saw nothing fails instead of passing.

It stays out of the default pipeline, because it needs rmw_zenoh_rs built and on the RMW search path. Only the rmw_zenoh_rs workflow sets that up, and it runs the check by name.

That required one fix to the script. main validated a requested test against the pipeline, so any test registered in the map but left out of the default order was unrunnable. It now validates against the map, and --list marks which tests the default run skips.

The detector was proven to detect, against a stub ros2:

the listing says result
std_msgs/msg/String passes
std_msgs::msg::dds_::String_ fails: "the graph did not report std_msgs/msg/String"
nothing, talker dead fails: "/chatter never appeared, so this run proves nothing"

The check starts its own rmw_zenohd, because rmw_zenoh peers need a router and nothing else in that step starts one. It guards both job kill calls: a talker that already exited makes an unguarded kill throw "Job N not found", which hides the real diagnosis behind a Nushell error.

Not verified

The end-to-end runs used std_msgs/String only. Tests cover the service path, the action interfaces and the liveliness token at the formatter. No live ROS service exercises them.

This touches no file that #325 touches, so the two can land in either order.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Aligns rmw-zenoh-rs topic and service keys with DDS-mangled type names used by hiroz and rmw_zenoh_cpp.

Changes:

  • Adds a shared DDS type-name formatter.
  • Uses mangled names for message and service wire metadata.
  • Adds topic/service formatting tests.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 3 comments.

File Description
crates/rmw-zenoh-rs/src/type_support.rs Applies DDS names to runtime type information.
crates/hiroz-protocol/src/format/rmw_zenoh.rs Adds the formatter and regression tests.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread crates/rmw-zenoh-rs/src/type_support.rs
Comment thread crates/rmw-zenoh-rs/src/type_support.rs
Comment thread crates/hiroz-protocol/src/format/rmw_zenoh.rs Outdated
@YuanYuYuan
YuanYuYuan force-pushed the fix/rmw-zenoh-rs-dds-type-name branch 3 times, most recently from b43d336 to bccecb3 Compare August 25, 2026 03:14
@YuanYuYuan
YuanYuYuan requested a balanced review from Copilot August 25, 2026 03:39

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 17 out of 17 changed files in this pull request and generated no new comments.

@YuanYuYuan
YuanYuYuan force-pushed the fix/rmw-zenoh-rs-dds-type-name branch 6 times, most recently from 14f59ed to a67f7f9 Compare August 25, 2026 07:24
…requires

A hiroz library node and a ROS 2 node running hiroz's own rmw_zenoh_rs could
not exchange a single message, in either direction, silently. Same repository,
same revision, same domain, byte-identical type hash. Only the type-name
segment of the key expression differed:

  hiroz library : 0/chatter/std_msgs::msg::dds_::String_/RIHS01_df668c74...
  rmw_zenoh_rs  : 0/chatter/std_msgs/msg/String/RIHS01_df668c74...

Measured which form is correct rather than inferring it. Upstream
rmw_zenoh_cpp (ros-jazzy-rmw-zenoh-cpp) running demo_nodes_cpp talker, with a
raw zenoh subscriber on `**` attached to its router, declares:

  0/chatter/std_msgs::msg::dds_::String_/RIHS01_df668c740482bbd48fb39d76a70dfd4bd59db1288021743503259e948f6b1a18

The DDS-mangled form. The library agrees with the wire; rmw_zenoh_rs was the
outlier, so rmw_zenoh_rs is what changes here.

The cause was two type-name derivations sitting next to each other in
type_support.rs. get_type_prefix() produced the DDS-mangled name and
get_ros_type_name() produced the slash-separated one; get_type_info(), which
feeds the key expression and the liveliness token, called the second. The same
file was already internally inconsistent -- MessageTypeInfo::type_name_dyn()
returned the mangled form while type_info_dyn() returned the slash form.

The mangling rule now lives once, in hiroz-protocol's rmw_zenoh format module
next to the formatter that consumes it, as dds_type_name(). hiroz-codegen bakes
the same string into generated messages, so the regression test can compare the
two derivations directly. get_ros_type_name() is deleted rather than left as a
second source of truth. The service key strips the response suffix from the
mangled name, so AddTwoInts_Response_ yields AddTwoInts_.

Why CI was green: two workflows that never meet. test.yml exercises the library
against rmw_zenoh_cpp; rmw-zenoh-rs.yml exercises rmw_zenoh_rs against ROS
packages. Nothing tested the library against rmw_zenoh_rs. The added unit test
closes that gap without needing an integration harness.

Evidence, all on the remote worker:

- RED: library_and_rmw_derive_the_same_topic_key failed on the assertion
  (left "std_msgs/msg/String", right "std_msgs::msg::dds_::String_") with the
  other 33 tests in the module passing, so it was a real failure and not a
  compile error.
- GREEN: 35 passed, 0 failed.
- End to end through one router, both directions: a hiroz library talker was
  heard 35 times by demo_nodes_cpp listener under rmw_zenoh_rs, and a
  demo_nodes_cpp talker under rmw_zenoh_rs was heard 29 times by a hiroz
  library listener. The sniffed key was
  0/dir_b/std_msgs::msg::dds_::String_/RIHS01_df668c74..., matching the
  rmw_zenoh_cpp measurement exactly.
- Re-falsified: reverting dds_type_name to the slash form turned the unit tests
  red again and dropped the end-to-end listener to 0 received out of 22
  published. Restored.

Not fixed here: hiroz peers are not brokered by a router, so the end-to-end
runs used the library's client mode (--mode client --endpoint tcp/...) to work
around it. That is a separate defect and is being handled separately.
@YuanYuYuan
YuanYuYuan force-pushed the fix/rmw-zenoh-rs-dds-type-name branch from a67f7f9 to e85e1e9 Compare August 25, 2026 07:34
@YuanYuYuan
YuanYuYuan merged commit c7bf42d into main Aug 25, 2026
31 checks passed
@YuanYuYuan
YuanYuYuan deleted the fix/rmw-zenoh-rs-dds-type-name branch August 25, 2026 08:17
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants