ROS 2 RMW Bridge — Spec Coverage

Sources:

  • ROS Enhancement Proposals (REPs) — internal/standards/cache/ros2/rep-{2003,2004,2005,2007,2008,2009}.html.
  • RMW C-API headers — rmw/rmw.h and rmw/qos_profiles.h from rmw 4.x (ROS 2 Iron/Jazzy distribution); they live in the upstream ros2/rmw GitHub repository, not in internal/standards/cache/, as they are a standalone distribution. A de-facto specification without its own REP, normative via the header definitions.
  • ROS 2 IDL subset — the design article design.ros2.org/articles/idl_interface_definition.html (not cached; live source) + the wire-naming convention from rosidl_typesupport_fastrtps_cpp (de-facto, in the ros2/rosidl GitHub repository).
  • Topic/service naming convention — the design article design.ros2.org/articles/topic_and_service_names.html (not cached; live source). Implemented in rmw_dds_common.

Context: ROS 2 robot-middleware wrappers build on DDS. The RMW API + the RMW-QoS-profile mapping + the topic-mangling convention + the ROS-IDL→DDS-XTypes wire convention are the central wire mappings. Spread across:

Crate mapping:

Spec area Crate file
REP-2003 Sensor/Map QoS crates/ros2-rmw/src/qos_profiles.rs (profile constants)
REP-2004 quality levels crates/ros2-rmw/src/quality.rs
topic-name mangling convention crates/ros2-rmw/src/topic_mangling.rs
standard RMW QoS profiles crates/ros2-rmw/src/qos_profiles.rs::profiles::*
RMW C-API (rmw/rmw.h) crates/ros2-rmw/src/ffi_api.rs
RMW-QoS mapping (rmw/qos_profiles.h) crates/ros2-rmw/src/rmw_qos_mapping.rs
ROS-IDL → DDS-XTypes wire mapping crates/ros2-rmw/src/type_mapping.rs
REP-2007 type adaptation / REP-2008 accelerator loan memory crates/rmw-zerodds-shim/rmw_c/rmw_zerodds_ros2_ext.h

Implementation: crates/ros2-rmw/ (11 modules, 81 tests green via cargo test -p zerodds-ros2-rmw).


REP-2003 Sensor Data and Map QoS Settings

Map QoS

Spec: REP-2003 §“Map Quality of Service” — “Map providers […] are expected to provide all maps over a reliable transient-local topic. […] The depth of the transient-local storage depth is left to the designer, however a single map depth is a reasonable choice”.

Repo: crates/ros2-rmw/src/qos_profiles.rs::profiles::MAP — Reliable + TransientLocal + KeepLast(1).

Tests: qos_profiles::tests::map_profile_matches_rep_2003_specification.

Status: done

Sensor Data QoS (consumer-side)

Spec: REP-2003 §“Sensor Driver Quality of Service” — verbatim: “Sensor data provided by a sensor driver from a camera, inertial measurement unit, laser scanner, GPS, depth, range finder, or other sensors are expected to be provided over a SystemDefaultsQoS quality of service as provided by the implemented ROS 2 version API. Consumers of sensor data are to use SensorDataQoS quality of service as provided by the implemented ROS 2 version API.”

Important: REP-2003 requires SystemDefaultsQoS for the driver-side (= Reliable+Volatile+KeepLast(10), see the DEFAULT item under “Standard RMW QoS Profiles” further below). The consumer-side uses SensorDataQoS (= BestEffort+Volatile+KeepLast(5)). This item describes the consumer-side; the driver-side is covered via profiles::DEFAULT.

Repo: crates/ros2-rmw/src/qos_profiles.rs::profiles::SENSOR_DATA — BestEffort + Volatile + KeepLast(5) (corresponds to rmw_qos_profile_sensor_data from rmw/qos_profiles.h).

Tests: qos_profiles::tests::sensor_data_profile_matches_rep_2003_specification checks the consumer-side constant (BestEffort+Volatile+KeepLast(5)).

Status: done — consumer-side covered; the driver-side is equally covered via the DEFAULT profile (see below).


REP-2004 Package Quality Categories

Quality levels Q1-Q5

Spec: REP-2004 — five levels with the following definitions (quoted from REP-2004 §“Quality Level Categories”):

  • Quality Level 1 — “highest quality level; packages that are needed for production systems”.
  • Quality Level 2 — “high quality packages that are either: needed for production systems or commonly used”.
  • Quality Level 3 — “tooling quality packages”.
  • Quality Level 4 — “demos, tutorials, and experiments”.
  • Quality Level 5 — “default quality level” (packages without explicit quality claims).

Numeric representation 1..5; Q1 is the highest, Q5 is the default for packages without an explicitly declared quality level.

Repo: crates/ros2-rmw/src/quality.rs::QualityLevel with numeric()/from_numeric() converters.

Tests: quality::tests::quality_level_numeric_round_trip, quality::tests::quality_level_ordering_q1_is_highest, quality::tests::quality_level_from_numeric_rejects_out_of_range.

Status: done — the classification model is exposed; the actual quality audit is a caller task (e.g. a package.xml tag).


REP-2005 ROS 2 Common Packages

Common-package list

Spec: REP-2005 — informational; a list of the ROS-2 common packages.

Repo: —

Tests: —

Status: n/a (informative) — REP-2005 marks itself as informational; the common-package list has no normative requirement on the RMW bridge.


REP-2007 Type Adaptation Feature

REP-2007 deliberately places the user-facing API (rclcpp::TypeAdapter) in rclcpp, and in the same section names what is left to the middleware: “Perhaps we can support a form of this feature in other languages in rcl or rmw in the future. One challenge in doing this is that it may require custom type support, which may be middleware specific.” The four items below are that custom type support. The seam to rclcpp is the C++ template syntax (TypeAdapter<Custom, Ros>, adapt_type::as, RCLCPP_USING_...) and the ownership mechanics over unique_ptr/shared_ptr; everything below it — wire form, type identity, copy count — lives here.

Defining a type adapter — rmw-side custom type support

Spec: REP-2007 §“Defining a Type Adapter”: an adapter names custom_type, ros_message_type and the conversion functions. §“Adding this feature in rclcpp”: “it may require custom type support, which may be middleware specific”.

Repo: crates/rmw-zerodds-shim/rmw_c/rmw_zerodds_ros2_ext.h (rmw_zerodds_type_adapter_t — ros_type_name, serialized_size, serialize, deserialize) + rmw_c/rmw_zerodds.c (rmw_zerodds_publisher_set_type_adapter, rmw_zerodds_subscription_set_type_adapter; adapter field in zerodds_pub_data_t/zerodds_sub_data_t). The endpoint itself is built unchanged from the ros_message_type’s rosidl type support, so the DDS topic name, the ROS type name and the RIHS type hash stay those of the unadapted endpoint.

Tests: rmw_c/type_adaptation_test.cpp (rclcpp e2e, bound to the rmw handles of live rclcpp entities; the check “endpoint topic type unchanged by adaptation” compares get_publishers_info_by_topic before and after binding) via rmw_c/run_type_adaptation_test.sh; crates/rmw-zerodds-shim/tests/ros2_ext_surface.rs::every_declared_extension_symbol_is_defined.

Status: done

Type adaptation in topics

Spec: REP-2007 §“Type Adaptation in Topics” — an adapted publisher or subscription exchanges the custom_type while the ros_message_type is what appears on the wire.

Repo: rmw_c/rmw_zerodds.c (rmw_zerodds_publish_adapted, rmw_zerodds_take_adapted, zerodds_adapted_wire — the 4-byte CDR_LE encapsulation is identical to the introspection publish path). As part of this, rmw_take_with_info, rmw_take_loaned_message_with_info and rmw_take_serialized_message_with_info now fill rmw_message_info_t (zerodds_fill_message_info: received_timestamp, reception_sequence_number) instead of returning an all-zero struct — rclcpp hands that struct to the adapted callback.

Tests: crates/rmw-zerodds-shim/rmw_c/type_adaptation_test.cpp: "2) adapted publish -> plain subscription" (adapted publisher, plain rclcpp subscription of the ROS message type), "3) plain publish -> adapted take" (the other direction), " adapted take fills rmw_message_info_t"; driven by crates/rmw-zerodds-shim/rmw_c/run_type_adaptation_test.sh.

Status: done

Type adaptation in services and actions

Spec: REP-2007 §“Type Adaptation in Services”: request and response half are adapted independently (“either the request or the response can be adapted”). §“Type Adaptation in Actions”: the same structure for goal, feedback and result.

Repo: rmw_c/rmw_zerodds.c (rmw_zerodds_client_set_type_adapters, rmw_zerodds_service_set_type_adapters, rmw_zerodds_send_request_adapted, rmw_zerodds_take_request_adapted, rmw_zerodds_send_response_adapted, rmw_zerodds_take_response_adapted; the adapted payload carries the same 24-byte correlation header as the unadapted path). A ROS 2 action is composed by rcl from three services (goal, cancel, result) and two topics (feedback, status) — there is no separate action rmw API — so the service plus topic surface covers action adaptation.

Tests: crates/rmw-zerodds-shim/rmw_c/type_adaptation_test.cpp — "5) adapted service request/response roundtrip" over example_interfaces/srv/AddTwoInts with std::pair<int64_t,int64_t> as the request and int64_t as the response custom type (neither is a ROS message); driven by crates/rmw-zerodds-shim/rmw_c/run_type_adaptation_test.sh.

Status: done

(De)serialize instead of convert — the avoided copy

Spec: REP-2007 §“Comparison to ROS 1’s Type Adaptation”: “This feature will support both convert and (de)serialize functions […] convert is superior for intra-process communication and the (de)serialization functions are better for inter-process communication.” And: “An advantage of following the ROS 1 approach is that an extra copy can be avoided; although it is likely much more challenging to implement this feature the ROS 1 way because of the middleware.”

Repo: rmw_c/rmw_zerodds.c (zerodds_adapted_wire): the wire buffer is allocated to serialized_size(custom) and the adapter writes the CDR body straight into it. No ros_message_type instance is created, so the second copy the REP describes as avoidable is not made. The reverse direction likewise in rmw_zerodds_take_adapted: the body is read straight into the custom_type.

Tests: rmw_c/type_adaptation_test.cpp measures three quantities: “adapted wire bytes == rmw_serialize bytes” (memcmp against the rmw_serialize output of the same message — wire identity is measured, not asserted by construction), “exactly one serialization per adapted publish” (counter in the adapter’s serialize) and “ros_message_type never materialized (no extra copy)” (the test’s convert_to_ros_message counter has to stay 0). Via rmw_c/run_type_adaptation_test.sh. Wire-identity regression: crates/rmw-zerodds-shim/tests/ros2_ext_surface.rs::adapted_publish_writes_the_same_encapsulation_header_as_plain_publish.

Status: done


REP-2008 Hardware Acceleration

REP-2008 assigns the layers itself: §“Kernel levels in ROS 2” puts rmw and the rmw_adapters on level 2 and the communication middleware (“including the communication middleware (e.g. DDS)”) on level 3, and §“Methodology” step 3.2 asks to accelerate “inter-Node exchanges” and to reduce “the overhead of the ROS 2 message-passing system across all its abstraction layers”. The seam is the kernel languages (CUDA, HLS, OpenCL) and the firmware — both of which the REP explicitly assigns to the silicon vendor and the application architect. What lives here is the memory provenance of the buffers that travel between the accelerator and the wire.

Kernel-level assignment

Spec: REP-2008 §“Kernel levels in ROS 2” — level 2: “we consider rclcpp, rcl, rmw, and the corresponding rmw_adapters for each supported communication middleware”. Level 3: “Groups together all accelerators below the ROS 2 core layers […] including the communication middleware (e.g. DDS)”. And: “Hardware acceleration solutions complying with this REP should aspire to support multiple kernel levels in ROS 2”.

Repo: crates/rmw-zerodds-shim/rmw_c/rmw_zerodds_ros2_ext.h (RMW_ZERODDS_ACCEL_LEVEL_1/2/3) + rmw_c/rmw_zerodds.c (rmw_zerodds_acceleration_kernel_levels → level 2 | level 3). Level 1 (ROS 2 applications and libraries) stays with the application.

Tests: rmw_c/accel_loan_memory_test.cpp (“REP-2008 kernel levels 2 and 3 declared”) via rmw_c/run_accel_loan_memory_test.sh; crates/rmw-zerodds-shim/tests/ros2_ext_surface.rs::acceleration_kernel_levels_agree_across_header_impl_and_readme (header, implementation and the vendor template in the README must name the same number).

Status: done

Pillar I — extensions to the ament build system

Spec: REP-2008 §“Pillar I - Extensions to ament” + §Specification: “Package maintainers are encouraged to integrate hardware acceleration in their ROS 2 packages by using the CMake hardware acceleration variables […] These CMake macros are meant to be used by maintainers to conditionally compile their ROS 2 packages”. Variables: ROS_ACCELERATION (ament_hardware_acceleration), ROS_VITIS (ament_vitis).

Repo: crates/rmw-zerodds-shim/ament/CMakeLists.txt (find_package(ament_hardware_acceleration QUIET), find_package(ament_vitis QUIET), evaluation of ROS_ACCELERATION/ROS_VITIS, RMW_ZERODDS_ACCELERATION_KERNEL_LEVELS, RMW_ZERODDS_LOAN_ALIGNMENT=page in the accelerator case; installation of rmw_zerodds_ros2_ext.h) + crates/rmw-zerodds-shim/ament/package.xml (conditional ament_hardware_acceleration build dependency). Both find_package calls are QUIET: a workspace without an accelerator configures unchanged.

Tests: crates/rmw-zerodds-shim/rmw_c/accel_loan_memory_test.cpp covers the runtime side of the same decision: "2) set_loan_alignment(page) accepted", "2) portable loan starts on a page boundary" (with ZERODDS_LOAN_ALIGNMENT=page) and " non-power-of-two alignment rejected"; driven by crates/rmw-zerodds-shim/rmw_c/run_accel_loan_memory_test.sh.

Status: done

Pillar II and Template for Vendors — declared support level

Spec: REP-2008 §“Pillar II - firmware”: “each vendor should provide and maintain an acceleration_firmware_<solution> package specialization that delivers the corresponding artifacts” — firmware artifacts per silicon solution. §“Template for Vendors”: “Silicon vendors and solution manufacturers can help set the expectations of the level of support their hardware acceleration technology provides in alignment with this REP by providing a template in the README.md files of their firmware and/or ament extensions.”

Repo: crates/rmw-zerodds-shim/README.md §“Template for Vendors (REP-2008 §”Template for Vendors”)” — the table the REP asks for: kernel levels served, Pillar I consumed, Pillar II not provided (firmware is the silicon vendor’s acceleration_firmware_<solution> package; ZeroDDS ships no firmware artifacts), technology agnosticism, the accelerator-facing API and the environment switches.

Tests: crates/rmw-zerodds-shim/tests/ros2_ext_surface.rs::acceleration_kernel_levels_agree_across_header_impl_and_readme (the template may not diverge from header and implementation).

Status: done

Methodology §3.2 — accelerator-registerable loan memory

Spec: REP-2008 §“Methodology for developing ROS 2 packages that integrate hardware acceleration”, step 3.2: “accelerate inter-Node exchanges and reduce the overhead of the ROS 2 message-passing system across all its abstraction layers. To do so, consider leveraging REP-2007 and REP-2009.”

CUDA, ROCm and XRT can only DMA out of host memory they have registered (pinned), and they pin whole pages. Without a statement from the middleware about where a loaned message buffer lives and which page-aligned extent covers it, an accelerated node needs its own pinned staging buffer and copies on every publish — exactly the copy step 3.2 wants removed.

Repo: crates/rmw-zerodds-shim/rmw_c/rmw_zerodds_ros2_ext.h (rmw_zerodds_loan_memory_t: kind, host_ptr, size, registration_base, registration_size, page_size, cross_process, segment_link) + rmw_c/rmw_zerodds.c (rmw_zerodds_publisher_get_loan_memory, rmw_zerodds_subscription_get_loan_memory, zerodds_fill_registration, rmw_zerodds_publisher_set_loan_alignment, zerodds_default_loan_alignment via ZERODDS_LOAN_ALIGNMENT, aligned Portable loan in rmw_borrow_loaned_message). registration_base/registration_size are the arguments for cudaHostRegister, hipHostRegister or xrt::bo.

Tests: crates/rmw-zerodds-shim/rmw_c/accel_loan_memory_test.cpp via crates/rmw-zerodds-shim/rmw_c/run_accel_loan_memory_test.sh, in both delivery modes: "3) reported page size == sysconf(_SC_PAGESIZE)", "3) registration extent is page aligned on both ends", "3) registration extent covers the loaned buffer", "4) reported extent is pinnable (mlock)" — the call performs the same kernel operation an accelerator runtime uses to pin host memory, so pinnability is measured rather than computed —, " the described buffer is the buffer that was delivered" and, in mode raw-same-host, "5) forked child maps the segment and finds the payload" and "5) subscriber loan reports the same shared segment".

Status: done


REP-2009 Type Negotiation Feature

REP-2009 places the negotiation state machine itself and, in the same breath, states what it expects of the layers below — §“Implementation in the ROS 2 Core or in a Separate Project”: “While there are some advantages to having this project in the ROS 2 core, all of the needed APIs are available to make this a separate package. Thus, in the spirit of keeping the ROS 2 core smaller, this should be implemented in a separate project.”

The rmw obligation is therefore not to run the algorithm but to make that sentence true. The five items below are the APIs §“Negotiation Algorithm”, §“Defining Supported Message Types” and §“Renegotiating Selected Message Types” name by hand; each is measured against a run that drives REP-2009’s algorithm over rmw_zerodds. The seam is the state machine itself (priority arithmetic, user-defined selection functions, deadlock timeout) — which the REP explicitly assigns to a separate package above rmw.

Endpoint info by topic — rmw_get_publishers/subscriptions_info_by_topic

Spec: REP-2009 §“Negotiation Algorithm”, negotiating publisher, step 1: “Use discovery to find all the connected subscriptions that are using the user specified negotiation topic.” The rmw surface for that is rmw/get_topic_endpoint_info.h: per topic, the endpoint list with node name/namespace, type, endpoint type, 16-byte GUID and QoS (ros2 topic info -v), plus rmw_count_publishers/rmw_count_subscribers.

Repo: rmw_c/rmw_zerodds.c (rmw_get_publishers_info_by_topic/ _subscriptions_info_by_topic → zerodds_get_endpoint_info_by_topic): enumerates per-endpoint info via rmw_zerodds_node_for_each_publication/subscription_endpoint, filters by the demangled topic, resolves each endpoint’s node identity via rmw_zerodds_node_resolve_endpoint (endpoint GUID → node), and fills rmw_topic_endpoint_info_array_t. Data path: crates/dcps/src/runtime.rs (discovered_publication/subscription_endpoints, local + SEDP-remote) → c-api (zerodds_runtime_for_each_*_endpoint + ZeroDdsEndpointInfo) → shim. The endpoint→node link relies on the ros_discovery_info participant gid being the real DDS participant GUID, with ParticipantEntitiesInfo carrying per-node reader/writer gid sequences (the endpoint GUID prefix matches the participant). QoS is best-effort from discovery (history/depth aren’t on the wire → UNKNOWN).

Tests: crates/py/python/tests/ros2/test_rmw_zerodds_interop.py::test_rclpy_endpoint_info_by_topic (rclpy get_publishers_info_by_topic: type + QoS + the node name/namespace resolved from the endpoint GUID) on ROS 2 Humble; shim roundtrip units crates/rmw-zerodds-shim/src/lib.rs::tests::participant_info_roundtrips_endpoint_gids, crates/rmw-zerodds-shim/src/lib.rs::tests::participant_info_roundtrips_without_endpoints; rmw_c/type_negotiation_test.cpp (“negotiation subscription found through discovery”, “endpoint info names the negotiation message type”) via rmw_c/run_type_negotiation_test.sh.

Status: done

Supported message type identity — RIHS type hash

Spec: REP-2009 §“Defining Supported Message Types”: “The combination of the ROS message type and the string identifier must form a unique tuple, since multiple supported message types may be sent with the same ROS 2 message type.” The rmw half of that tuple is the type on the wire: type name and RIHS type hash per endpoint (the string identifier lives in the negotiation payload, see the next item).

Repo: crates/rmw-zerodds-shim/src/lib.rs::rmw_zerodds_compute_type_hash (RIHS SHA-256) + the endpoint info path above.

Tests: type_hash_sha256_is_deterministic + 2 more shim units; test_rclpy_endpoint_info_by_topic (Humble); rmw_c/type_negotiation_test.cpp (“discovery reports the right ROS type per selected type” — two simultaneously selected types on two topics, each with its own ROS type in discovery).

Status: done

Carrying the supported message priority map

Spec: REP-2009 §“Negotiation Algorithm”, step 2: “Receive the supported message priority maps from all connected subscriptions.” §“Defining Negotiating Publishers and Subscriptions”: the map travels over “a negotiation topic that will be used to negotiate the selected message types” — ordinary ROS pub/sub. Its shape is a sequence of nested structs carrying strings (ROS type name, string identifier, topic, priority).

Repo: the introspection CDR codec in rmw_c/rmw_zerodds.c (cdr_ser_msg/cdr_de_msg, sequence branch over size_function/ get_const_function/get_function/resize_function, nested message via type_id_ == 18) in both typesupport flavours — see “Introspection typesupport flavours (C and C++)”.

Tests: rmw_c/type_negotiation_test.cpp with diagnostic_msgs/msg/DiagnosticArray — the same shape as negotiated_interfaces/msg/SupportedTypes (DiagnosticStatus[] with three strings and a nested KeyValue[]): “priority map survives serialize/deserialize” (rmw_serialize/rmw_deserialize identity), “supported message priority map delivered” (over the wire) and “priority map round-trips field for field” (compared field by field). Via rmw_c/run_type_negotiation_test.sh.

Status: done

One regular publisher per selected message type

Spec: REP-2009 §“Negotiation Algorithm”, negotiating publisher: “The negotiating publisher then creates a regular publisher for each supported message type.” §“Negotiating Subscription”: “The negotiating subscription will then create exactly one subscription corresponding to the chosen type and topic name.” The endpoints therefore come into being at runtime, after the decision, each carrying a different ROS type.

Repo: rmw_c/rmw_zerodds.c (rmw_create_publisher/rmw_create_subscription at runtime on a live node; the type name comes per endpoint from zerodds_type_name and goes into discovery and onto the wire).

Tests: crates/rmw-zerodds-shim/rmw_c/type_negotiation_test.cpp: "4) both selected message types deliver on their topics" (std_msgs/msg/String and std_msgs/msg/Int32 at the same time — the case REP-2009 draws as N_1(x, y) to N_2(x) and N_3(y)), "4) discovery reports the right ROS type per selected type", "5) a regular publisher per selected message type", "5) the selected type appears in the graph with its ROS type"; driven by crates/rmw-zerodds-shim/rmw_c/run_type_negotiation_test.sh.

Status: done

Renegotiation — endpoints leave and are created again

Spec: REP-2009 §“Renegotiating Selected Message Types”: “If the selected messages must change, the negotiating publisher destroys its publishers. The negotiating publisher will then notify its subscriptions of the new selected message types on the user specified negotiation topic and create new publishers for the selected message types.” A destroyed endpoint therefore has to leave the graph at once, or the next round computes with a peer that no longer exists.

Repo: crates/zerodds-c-api/src/lib.rs (zerodds_writer_destroy / zerodds_reader_destroy now call DcpsRuntime::remove_user_writer/remove_user_reader) + crates/dcps/src/runtime.rs (both methods were pub(crate) and thus reachable only through the DCPS DataWriter, not through the runtime FFI path rmw uses; they take the endpoint out of the local registry, rebuild the intra-runtime routes, stop announcing it and send an SEDP dispose).

Previously a publisher destroyed through rmw stayed in the graph forever: rmw_count_publishers and rmw_get_publishers_info_by_topic kept reporting it, and a remote peer waited for the liveliness timeout instead of unmatching at once.

Tests: rmw_c/type_negotiation_test.cpp: “destroyed publishers leave the graph” (rmw_destroy_publisher on the rmw handle, then count_publishers == 0 and empty endpoint info), “discovery reports the renegotiated ROS type” (the same topic afterwards carries a different ROS type) and “the renegotiated selected type delivers”. Via rmw_c/run_type_negotiation_test.sh.

Status: done

The network change as a trigger — graph guard condition

Spec: REP-2009 §“Renegotiating Selected Message Types”: “In the case that the ROS 2 network is changing, the negotiating publisher may need to renegotiate its selected message types.” Together with step 1 (“Use discovery to find all the connected subscriptions”) that means the graph has to announce the change, not merely be pollable. rcl surfaces it as the node’s graph guard condition.

Repo: rmw_c/rmw_zerodds.c (zerodds_refresh_graph_guards, zerodds_fp_cb, zerodds_node_register/_unregister, graph_fp in zerodds_node_data_t): for every graph guard condition actually present in a wait set, rmw_wait folds all discovered publications and subscriptions of that node into a fingerprint and raises the flag when it changes. A node nobody watches costs nothing.

Alongside it, the guard condition evaluation itself: rcl hands rmw_wait the guard condition’s data pointer in rmw_guard_conditions_t.guard_conditions[i], not the rmw_guard_condition_t (rcl_wait_set_add_guard_condition: RMWStorage[n] = rmw_handle->data). The shim read the entry as a struct and compared its implementation_identifier — the comparison never matched, so no guard condition ever reported readiness: neither the graph guard condition, nor the executor’s interrupt guard condition, nor a user-created one. The entry is now read as what it is.

Also here: rmw_wait returned RMW_RET_TIMEOUT on an infinite timeout when a wake-up arrived with nothing ready. rcl’s graph listener treats that as a fatal error and aborts the process with “rcl_wait unexpectedly timed out”; a wait without a deadline cannot time out and now reports RMW_RET_OK.

Tests: crates/rmw-zerodds-shim/rmw_c/type_negotiation_test.cpp — "6) the graph change raised the node graph guard": the graph event is armed before the renegotiation, the wait path blocks on the guard condition, and the event must be set afterwards. Driven by crates/rmw-zerodds-shim/rmw_c/run_type_negotiation_test.sh.

Status: done


Topic-name mangling convention (de-facto, rmw_dds_common)

Prefix convention rt/rq/rr/rs

Spec: the design article design.ros2.org/articles/topic_and_service_names.html §“DDS Topic Names” + the de-facto convention from the ros2/rmw_dds_common implementation:

  • rt/<name> — topic pub-sub (ROS topic).
  • rq/<name>Request — service request.
  • rr/<name>Reply — service reply.
  • rs/<name> — service discovery (legacy, before the REP-2009 structures).

Repo: crates/ros2-rmw/src/topic_mangling.rs::{RosKind, mangle_topic_name, demangle_topic_name, is_ros_topic}.

Tests: topic_mangling::tests::mangle_topic_strips_leading_slash_and_prepends_rt, topic_mangling::tests::mangle_preserves_internal_slashes, topic_mangling::tests::mangle_each_kind_uses_correct_prefix, topic_mangling::tests::mangle_handles_already_unprefixed_name, topic_mangling::tests::mangle_rejects_empty_name, topic_mangling::tests::mangle_rejects_invalid_leading_character, topic_mangling::tests::mangle_accepts_underscore_leading, topic_mangling::tests::demangle_round_trips_all_kinds, topic_mangling::tests::demangle_rejects_unknown_prefix, topic_mangling::tests::is_ros_topic_recognizes_all_four_prefixes, topic_mangling::tests::mangle_demangle_round_trip.

Status: done — the suffixes (Request/Reply) remain a caller task (typically added automatically by the service codegen).


Standard RMW QoS profiles

rmw_qos_profile_* constants

Spec: rmw/qos_profiles.h (rmw 4.x) — seven default profiles: rmw_qos_profile_default (Reliable+Volatile+KeepLast(10)), rmw_qos_profile_sensor_data (BestEffort+Volatile+KeepLast(5)), rmw_qos_profile_parameters (Reliable+Volatile+KeepLast(1000)), rmw_qos_profile_services_default (Reliable+Volatile+KeepLast(10)), rmw_qos_profile_parameter_events (Reliable+Volatile+KeepLast(1000)), rmw_qos_profile_system_default (all fields as *_SYSTEM_DEFAULT sentinels; implementation-defined), rmw_qos_profile_unknown (all fields as *_UNKNOWN sentinels).

Repo: crates/ros2-rmw/src/qos_profiles.rs::profiles::* (DEFAULT, SENSOR_DATA, PARAMETERS, SERVICES_DEFAULT, PARAMETER_EVENTS, SYSTEM_DEFAULT, UNKNOWN, MAP) + an is_unknown(profile) predicate.

Tests: qos_profiles::tests::default_profile_is_reliable_volatile_keep_last_10, qos_profiles::tests::parameters_profile_uses_keep_last_1000, qos_profiles::tests::services_default_matches_rmw_defaults, qos_profiles::tests::parameter_events_uses_keep_last_1000, qos_profiles::tests::system_default_aliases_default, qos_profiles::tests::history_keep_last_distinct_from_keep_all, qos_profiles::tests::liveliness_and_deadline_default_to_infinite, qos_profiles::tests::unknown_profile_is_distinct_from_default, qos_profiles::tests::unknown_profile_uses_keep_last_zero_marker, qos_profiles::tests::is_unknown_recognizes_unknown_profile, qos_profiles::tests::is_unknown_rejects_real_profiles.

Status: done — all seven rmw default profiles mapped as constants: * Six 1:1 (DEFAULT, SENSOR_DATA, PARAMETERS, SERVICES_DEFAULT, PARAMETER_EVENTS, MAP). * SYSTEM_DEFAULT as an alias of DEFAULT (the spec allows implementation-defined; ZeroDDS chooses the same values as DEFAULT — a documented choice). * UNKNOWN as a KeepLast(0) marker with the is_unknown predicate.


RMW C-API (rmw/rmw.h)

rmw_ret_t return codes

Spec: rmw/types.h (rmw 4.x) — rmw_ret_t as an int32 with the values OK=0, ERROR=1, TIMEOUT=2, UNSUPPORTED=3, BAD_ALLOC=10, INVALID_ARGUMENT=11, INCORRECT_RMW_IMPLEMENTATION=12.

Repo: crates/ros2-rmw/src/ffi_api.rs::RmwRet (#[repr(i32)] enum with all seven values + a map_to_rmw_ret converter).

Tests: ffi_api::tests::error_codes_match_rmw_h, ffi_api::tests::ok_is_zero, ffi_api::tests::map_to_rmw_ret_ok, ffi_api::tests::map_to_rmw_ret_err.

Status: done

Implementation identifier (rmw_get_implementation_identifier)

Spec: rmw/rmw.h — rmw_get_implementation_identifier() returns a vendor string (convention rmw_<vendor>_cpp, e.g. "rmw_fastrtps_cpp"/"rmw_cyclonedds_cpp"/"rmw_connext_cpp"). The RMW_CHECK_*_FOR_NULL_* macros (in rmw/check_type_identifiers_match.h) check caller-passed strings against this identifier (RMW_RET_INCORRECT_RMW_IMPLEMENTATION on a mismatch).

Repo: crates/ros2-rmw/src/ffi_api.rs::check_rmw_identifier plus the constant RMW_IMPLEMENTATION_IDENTIFIER: &str = "rmw_zerodds_cpp" (follows the vendor-naming convention).

Tests: ffi_api::tests::implementation_identifier_matches_convention, ffi_api::tests::check_rmw_identifier_accepts_correct, ffi_api::tests::check_rmw_identifier_rejects_other_vendor.

Status: done

rmw_node_t construction

Spec: rmw/types.h — rmw_node_t struct with an implementation_identifier field + name/namespace/context.

Repo: crates/ros2-rmw/src/ffi_api.rs::RmwNode.

Tests: ffi_api::tests::rmw_node_construction.

Status: done


RMW-QoS mapping (rmw/qos_profiles.h)

rmw_qos_*_policy_t enums (History/Reliability/Durability)

Spec: rmw/qos_profiles.h — three C enums (rmw_qos_history_policy_t, rmw_qos_reliability_policy_t, rmw_qos_durability_policy_t) with SYSTEM_DEFAULT / KEEP_LAST / KEEP_ALL / RELIABLE / BEST_EFFORT / TRANSIENT_LOCAL / VOLATILE / UNKNOWN / BEST_AVAILABLE values.

Repo: crates/ros2-rmw/src/rmw_qos_mapping.rs::{RmwHistory, RmwReliability, RmwDurability} (#[repr(u32)] enums with all spec-conformant discriminants).

Tests: rmw_qos_mapping::tests::enum_repr_is_c_compatible.

Status: done

rmw_to_dds conversion + BEST_AVAILABLE handling

Spec: rmw/qos_profiles.h + design article — rmw_qos_profile_t is the ROS-2-side representation; it must be mapped bidirectionally onto the DDS QosProfile. BEST_AVAILABLE (Iron+) is resolved to BEST_EFFORT on the sender side.

Repo: crates/ros2-rmw/src/rmw_qos_mapping.rs::{rmw_to_dds, dds_to_rmw, RmwQosProfile}.

Tests: rmw_qos_mapping::tests::rmw_to_dds_round_trip_default, rmw_qos_mapping::tests::rmw_to_dds_keep_all_passes_through, crates/ros2-rmw/src/rmw_qos_mapping.rs::tests::rmw_system_default_passes_through_as_sentinel, crates/ros2-rmw/src/rmw_qos_mapping.rs::tests::rmw_unknown_passes_through_as_sentinel, rmw_qos_mapping::tests::rmw_best_available_maps_to_best_effort_on_sender, rmw_qos_mapping::tests::transient_local_round_trips, rmw_qos_mapping::tests::sensor_data_is_best_effort, rmw_qos_mapping::tests::services_default_uses_keep_last_10, rmw_qos_mapping::tests::parameters_uses_keep_last_1000, rmw_qos_mapping::tests::default_profile_matches_rmw_spec.

Status: done


ROS-IDL → DDS-XTypes wire mapping

Sub-namespace convention (msg/srv/action)

Spec: the ROS 2 design article design.ros2.org/articles/idl_interface_definition.html §“Naming” — top-level namespaces msg/, srv/, action/ with the convention onto the DDS wire <package>::<sub-namespace>::dds_::<TypeName>_.

Repo: crates/ros2-rmw/src/type_mapping.rs::RosNamespace with as_str()/from_str() converters; RosTypeRef::to_dds_type_name for the DDS wire form.

Tests: type_mapping::tests::namespace_str_repr, type_mapping::tests::dds_wire_form_uses_dds_dunder, type_mapping::tests::action_namespace_mapped_correctly, type_mapping::tests::srv_namespace_mapped_correctly.

Status: done

ROS-form → DDS-form round-trip

Spec: design article — conversion between the ROS wire form (std_msgs/msg/String) and the DDS wire form (see above). Round-trip mandatory.

Repo: crates/ros2-rmw/src/type_mapping.rs::{RosTypeRef::new, to_ros_form, from_ros_form}.

Tests: type_mapping::tests::ros_form_round_trip, type_mapping::tests::from_ros_form_rejects_unknown_namespace, type_mapping::tests::from_ros_form_rejects_wrong_segment_count.

Status: done

Builtin type tokens (int32, float64, string, …)

Spec: design article §“Field Types” — builtin type tokens (bool, byte, char, float32/float64, int8-uint64, string, wstring) map onto the DDS-IDL primitives.

Repo: crates/ros2-rmw/src/type_mapping.rs::RosBuiltinType with a from_ros_token() parser and a cdr_size helper.

Tests: type_mapping::tests::from_ros_token_round_trip, type_mapping::tests::from_ros_token_rejects_unknown, type_mapping::tests::cdr_size_matches_omg_cdr2, type_mapping::tests::builtin_idl_names_match_omg_idl.

Status: done — rosidl builtin → OMG IDL 4.2 / DDS-XTypes 1.3 primitive mapping verified.


RMW C-ABI plugin (crates/rmw-zerodds-shim)

The 14 items above cover the wire-mapping crate (crates/ros2-rmw). The actual rmw_* C-ABI plugin layer (rmw_c/rmw_zerodds.c bridges onto the Rust bridge src/lib.rs) is tracked separately here. Verification: rclpy interop pytest on ROS 2 Humble (crates/py/python/tests/ros2/), 4/4 green.

rmw_wait — event-driven

Spec: rmw/rmw.h rmw_wait (de facto) — blocks until an entity is ready or times out.

Repo: crates/rmw-zerodds-shim/rmw_c/rmw_zerodds.c (rmw_wait) + src/lib.rs (WaitNotify, subscription_on_data, rmw_zerodds_*_has_data, rmw_zerodds_context_wait_block) — reader data listener → per-subscription inbox → context condvar; no spin, no fixed-tick poll. For the raw delivery modes (RawSameHost/Iceoryx), which do not fire the RTPS listener, a doorbell thread (rmw_zerodds_subscription_start_doorbell → zerodds_reader_raw_wait) additionally wakes the same condvar event-driven (see “Loaned messages” below).

Tests: rmw-zerodds-shim::tests::event_driven_wait_roundtrip_inprocess, wait_notify_blocks_then_wakes_on_notify; rclpy test_rclpy_publish_subscribe_string_roundtrip (the executor drives rmw_wait).

Status: done

Services — request/reply

Spec: rmw/rmw.h rmw_create_client/service, rmw_send_request, rmw_take_request, rmw_send_response, rmw_take_response, rmw_service_server_is_available.

Repo: rmw_zerodds.c (service typesupport introspection, CDR [encap][body], rcl request ids) + src/services.rs (topics rq/…Request/ rr/…Reply, types …_Request_/_Response_, Cyclone form and related-identity form, waiting up to 100 ms for the reply reader).

Tests: crates/rmw-zerodds-shim/src/lib.rs::tests::{a_service_call_between_two_runtimes_correlates_by_the_related_identity, two_clients_in_one_runtime_each_get_their_own_reply}; rclpy test_rclpy_service_call_roundtrip (AddTwoInts 41+1=42); crates/rmw-zerodds-shim/rmw_c/run_ros2_services_test.sh (Linux test host, against rmw_cyclonedds_cpp and rmw_fastrtps_cpp in both directions).

Status: done

rmw_serialize / rmw_deserialize + serialized publish/take

Spec: rmw/rmw.h rmw_serialize, rmw_deserialize, rmw_publish_serialized_message, rmw_take_serialized_message[_with_info].

Repo: rmw_zerodds.c (introspection CDR [encap 4][body], cdr_ser_msg/cdr_de_msg; serialized pub/take over the bridge byte path).

Tests: the same introspection CDR as the verified pub/sub path (test_rclpy_publish_subscribe_string_roundtrip).

Status: done

Topic graph — rmw_get_topic_names_and_types + rmw_count_publishers/subscribers

Spec: rmw/rmw.h graph introspection.

Repo: crates/dcps/src/runtime.rs (discovered_publication_topics/discovered_subscription_topics — local user endpoints + SEDP-remote), c-api zerodds_runtime_for_each_publication/ _subscription, rmw_zerodds.c (demangle rt/<t>→/<t>, ::/__→/, dedup topic→types).

Tests: rclpy test_rclpy_topic_graph_introspection.

Status: done

Service graph and names by node

Spec: rmw/rmw.h rmw_get_service_names_and_types, rmw_count_services/clients, rmw_get_{publisher,subscriber,service,client}_names_and_types_by_node, no_demangle of rmw_get_topic_names_and_types, rmw_qos_profile_check_compatible.

Repo: rmw_zerodds.c (zerodds_demangle_service, zerodds_demangle_service_type, zerodds_names_and_types_by_node: each endpoint’s node from ros_discovery_info, services = readers on request topics, clients = writers on them), rmw_zerodds_qos.cpp (rmw_dds_common::qos_profile_check_compatible, as rmw_cyclonedds_cpp); src/services.rs announces service endpoints in ros_discovery_info. Before, all of these returned nothing or “compatible”.

Tests: crates/rmw-zerodds-shim/rmw_c/run_ros2_services_test.sh::graph (ros2 service list -t and ros2 node info across RMW boundaries, Linux test host)

Status: partial — graph checks rmw_get_service_names_and_types (ros2 service list -t) and the service servers per node (ros2 node info). Without a test: rmw_count_services, rmw_count_clients, publishers, subscribers and clients per node, no_demangle and rmw_qos_profile_check_compatible.

Guard conditions + event-driven wake

Spec: rmw/rmw.h rmw_create_guard_condition / rmw_trigger_guard_condition / rmw_wait. rcl stores a guard condition’s data pointer in rmw_guard_conditions_t.guard_conditions[i], not the rmw_guard_condition_t (rcl_wait_set_add_guard_condition: RMWStorage[n] = rmw_handle->data); after the wait rcl reads exactly that array to decide which guard condition fired.

Repo: rmw_zerodds.c (rmw_create/trigger_guard_condition → context_notify wakes the wait condvar; rmw_wait reads the array entries as unsigned int * flags, consumes a raised flag and nulls the rest).

Previously rmw_wait read the entry as an rmw_guard_condition_t * and checked its implementation_identifier. The comparison never matched, so no guard condition ever reported readiness — neither the executor’s interrupt guard condition, nor a node’s graph guard condition, nor a user-created one. Second defect on the same path: a wait with an infinite timeout returned RMW_RET_TIMEOUT on a wake-up with nothing ready; rcl’s graph listener treats that as fatal and aborts the process with “rcl_wait unexpectedly timed out”.

Tests: crates/rmw-zerodds-shim/rmw_c/type_negotiation_test.cpp — "6) the graph change raised the node graph guard": the event is set only when the guard condition really reports readiness; driven by crates/rmw-zerodds-shim/rmw_c/run_type_negotiation_test.sh. The rmw_wait path itself: crates/rmw-zerodds-shim/src/lib.rs::tests::wait_notify_blocks_then_wakes_on_notify, ::event_driven_wait_roundtrip_inprocess, ::wait_returns_invalid_for_null, ::has_data_and_context_block_null_safe.

Status: done

rmw_get_node_names + _with_enclaves

Spec: rmw/rmw.h rmw_get_node_names[_with_enclaves].

Repo: rmw_zerodds.c (rmw_get_node_names, accumulator) + src/lib.rs (NodeGraph, encode/decode_participant_info, discovery_on_data, rmw_zerodds_for_each_node) — each context publishes its ParticipantEntitiesInfo on ros_discovery_info (hand-encoded XCDR1) and aggregates local + remote nodes.

Tests: rclpy test_rclpy_node_names_graph.

Status: done — ros_discovery_info as in rmw_dds_common: NoKey, RELIABLE, TRANSIENT_LOCAL, KEEP_LAST(1) (src/lib.rs, discovery QoS); a process started later receives each participant’s latest state.

on_new_message/request/response callbacks (events executor)

Spec: rmw/rmw.h rmw_*_set_on_new_*_callback.

Repo: rmw_zerodds.c (3 setters → bridge) + src/lib.rs (SubInbox.event, inbox_set_event, invoked in subscription_on_data).

Tests: rmw-zerodds-shim::tests::event_callback_fires_on_arrival.

Status: done

rmw_get_serialized_message_size

Spec: rmw/rmw.h rmw_get_serialized_message_size.

Repo: crates/rmw-zerodds-shim/rmw_c/rmw_zerodds.c (rmw_get_serialized_message_size, zerodds_cdr_max_msg, zerodds_cdr_max_elem — introspection size walk, conservative upper bound; strings/sequences capped).

Tests: crates/rmw-zerodds-shim/tests/ros2_ext_surface.rs::the_serialized_size_walk_covers_the_header_and_every_member_kind — checks that the returned value adds the four-byte encapsulation header, that every member kind (primitive, string, nested, scalar, fixed array, sequence) has a branch in the walk, and that ZERODDS_STR_CAP/ ZERODDS_SEQ_CAP reserve room rather than nothing. No gate.

Status: partial — the walk is implemented and structurally checked. What is missing is the behavioural check “the returned value really is ≥ the length rmw_serialize writes”; that needs the rosidl introspection type supports and hence a ROS 2 host. Tracked as its own item below.

rmw_get_serialized_message_size — bound check on a ROS 2 host

Spec: rmw/rmw.h rmw_get_serialized_message_size — the value handed back must suffice for the pre-allocation.

Repo: —

Tests: —

Status: open — no test case holds rmw_get_serialized_message_size for a real message against the length rmw_serialize writes. That needs a ROS 2 host; the case belongs beside the existing rclcpp e2e tests in crates/rmw-zerodds-shim/rmw_c/.

Typed message loaning (rmw_borrow/publish/take_loaned_message)

Spec: rmw/rmw.h loaned-message API + can_loan_messages. Delivery shape: docs/specs/zerodds-delivery-modes-1.0.md (modes Portable/RawSameHost/Iceoryx).

Repo: rmw_c/rmw_zerodds.c (rmw_borrow/publish/take_loaned_message, rmw_return_loaned_message_from_publisher/subscription, can_loan_messages = fixed-POD from rosidl introspection) + the SHM bridge in crates/rmw-zerodds-shim/src/lib.rs (rmw_zerodds_publisher_enable_raw_loan/_loan/_commit/_discard, rmw_zerodds_subscription_enable_shm/_take_shm/_has_shm_data/_release_shm), feature flatdata-loan (default).

Tests: crates/rmw-zerodds-shim/src/lib.rs::tests::loaning_null_args_rejected, crates/rmw-zerodds-shim/src/lib.rs::tests::return_loan_null_args_rejected (the argument contract of the loan ABI, no gate, they run in the default run). Behaviour in both delivery modes: crates/rmw-zerodds-shim/rmw_c/loaned_message_test.cpp, started by crates/rmw-zerodds-shim/rmw_c/run_loaned_message_test.sh — an rclcpp e2e that runs only on a host with ROS 2 and is not part of the cargo default run.

Status: done — the loaned-message ABI is implemented and e2e verified, in two delivery modes (default via ZERODDS_DELIVERY_MODE):

  • Portable (default): rclcpp hands over a typed struct buffer, the user writes the struct, publish_loaned serializes struct→CDR and publishes over RTPS — interop-safe (cross-host/cross-vendor), real CDR on the wire. (can_loan=1 got=42 PASS.)
  • RawSameHost (real zero-copy/zero-serialize, same-host only): the writer is set to set_delivery_mode(RawSameHost) + enable_shm_loan; borrow returns a pointer into the POSIX-SHM slot (the user writes the struct directly into shared memory), commit finalizes in-place without serialization and without RTPS (c-api publishes_to_wire gate → no double-delivery). The reader maps the same segment via a deterministic topic-derived flink path (lazy attach) and reads the slot zero-copy (take_loaned) or with a struct memcpy for normal callbacks (rmw_take). (can_loan=1 got=42 PASS, proof of SHM: raw never goes on the wire.)
  • Iceoryx (same-host cross-stack, shim feature delivery-iceoryx): the same loan/commit/take path, but writer/reader are bound to an iceoryx2 service (topic-derived) — commit sends over iceoryx2, the reader receives from it. Without the feature, ZERODDS_DELIVERY_MODE=iceoryx degrades on both sides to Portable. (can_loan=1 got=42 PASS via ZERODDS_TEST_ICEORYX=1.)

Readiness (modes 1/2): event-driven. Per raw subscription a “doorbell” thread parks on zerodds_reader_raw_wait(reader, timeout_ms) (c-api), which blocks on the raw source — SHM change-generation futex (notify_generation/wait_for_change) or iceoryx2 listener — and wakes rmw_wait’s context condvar on a real arrival; the sender notifies the iceoryx2 event on commit. The doorbell is started lazily once the raw source is active and stopped + joined before the reader is destroyed (it holds the reader pointer). Since iceoryx2’s take is a destructive FIFO receive, readiness additionally prefetches exactly one sample into a non-consuming, idempotent pending buffer of the subscription; rmw_take/ take_loaned consume the held sample. So a blocking rclcpp spin() also wakes on raw-only data without the executor timeout.

Introspection typesupport flavours (C and C++)

Spec: rosidl ships two introspection typesupports with identical MessageMembers/ServiceMembers layout but different runtime types for the variable-length members: rosidl_typesupport_introspection_c (rosidl_runtime_c__String/__U16String, resolved by rclpy) and rosidl_typesupport_introspection_cpp (std::string/std::u16string, resolved by rclcpp). An rmw has to serve both.

Repo: rmw_c/rmw_zerodds.c (zerodds_introspect_ex, zerodds_service_introspect_ex report the flavour as a flag; the cpp field in crates/rmw-zerodds-shim/rmw_c/rmw_zerodds.c::cdr_t and crates/rmw-zerodds-shim/rmw_c/rmw_zerodds.c::rdr_t carries it through serialization and deserialization; publisher, subscription, client and service store it in members_cpp) + rmw_c/rmw_zerodds_cxx.cpp (zerodds_cxx_string_data/_assign, zerodds_cxx_u16string_data/_assign — the C translation unit cannot touch std::string) + rmw_c/build_librmw_zerodds_cpp.sh (compiles and links the C++ translation unit, -lstdc++).

Previously the C++ flavour was handled like the C one. A std::string member ended up in rosidl_runtime_c__String__assignn, which reallocs the std::string’s SSO buffer as if it were a heap pointer — realloc(): invalid pointer, process abort on the first std_msgs/msg/String over rclcpp. zerodds_service_introspect also queried only the C flavour, so rmw_create_client/rmw_create_service returned NULL for rclcpp services.

Tests: crates/rmw-zerodds-shim/rmw_c/type_adaptation_test.cpp — "2) adapted publish -> plain subscription", "3) plain publish -> adapted take", "5) adapted service request/response roundtrip" (std_msgs/msg/String through rclcpp publisher and subscription in both directions, example_interfaces/srv/AddTwoInts through rclcpp client and service), driven by crates/rmw-zerodds-shim/rmw_c/run_type_adaptation_test.sh. Regression of the C flavour: crates/py/python/tests/ros2/test_rmw_zerodds_interop.py::test_rclpy_publish_subscribe_string_roundtrip and ::test_rclpy_service_call_roundtrip via crates/rmw-zerodds-shim/rmw_c/run_ros2_pytest.sh.

Status: done


ROS 2 Jazzy Dynamic Type Support (rosidl_dynamic_typesupport)

Interface table + reflective mapping

Spec: rosidl_dynamic_typesupport/api/serialization_support_interface.h (ROS 2 Jazzy) — rosidl_dynamic_typesupport_serialization_support_interface_t, 192 function pointers: dynamic-type builder (every primitive/string kind × scalar/array/bounded/unbounded sequence, nested members through both dynamic_type_impl_t and dynamic_type_builder_impl_t), dynamic data (get/set/insert for every kind, sequence operations, nested values, loan, serialization). Implemented by rosidl_dynamic_typesupport_fastrtps for Fast DDS and by this repo for ZeroDDS — the header’s own description: “Downstream middlewares should populate this interface as appropriate.”

Repo: crates/rmw-zerodds-shim/src/dynamic.rs (dynamic_data_serialize, dynamic_data_init_from_dynamic_type, …) — every interface function maps onto zerodds_types::dynamic::{DynamicTypeBuilder, DynamicType, DynamicData}, the same reflective type family zerodds-dcps’s SerializedTypeSupport uses for runtime-described samples; serialization/deserialization goes through zerodds_wire_codec::{encode,decode}, the same codec path every other DynamicData consumer in the repo uses. crates/rmw-zerodds-shim/rmw_c/rmw_zerodds_dynamic.cpp populates the 192 function pointers; the ~90 extern "C" entry points behind them are declared by hand (cbindgen does not expand macro_rules!, see the file’s own comment), not taken from the generated include/rmw_zerodds.h — the same style rmw_c/rmw_zerodds.c already uses for its own extern declarations.

Since dynamic_type_builder_init cannot assume any shared model state across calls (each call gets a fresh ..._impl_t with no parent reference), every builder owns its own private zerodds_types::dynamic::DynamicTypeBuilder/Ast; a nested type is grafted — re-declared node by node, memoised — into the target Ast when composed (add_complex_member[_builder]), rather than sharing one model. Reasoning in crates/rmw-zerodds-shim/src/dynamic.rs’s module comment. The graft copies every facet a member or the struct type itself carries — @key, @optional, @must_understand, @external, @try_construct, the effective @id, @default, and @nested/@autoid(HASH) plus extensibility on the type — not only the type and the effective id: an earlier version silently dropped every one of these but the id, so a composed type lost its key, its @optional, its @default and more, with nothing reporting it. A @default value survives the graft through its text, not its Id<ConstExprNode> — that is not portable between two Ast instances — via zerodds_types::dynamic::default_expr_text, the inverse of the same parser that reads a @default text against the member’s type in the first place.

Tests: cargo test -p rmw-zerodds-shim --lib dynamic::tests:: (15 green): every primitive/string kind round-trips through get/set/insert (every_primitive_kind_round_trips), nested structs + sequences (nested_struct_and_sequence_round_trip), clone/equality on both type and data (clone_and_equals_agree), loan/return-loan writing back at the loaned origin (a_loan_writes_back_at_its_own_origin), a bounded string past its bound is refused rather than truncated (a_bound_violation_is_refused_not_truncated), the representation header octets match DDSI-RTPS 2.5 §10.5 (representation_of_matches_the_rtps_table) — and, as evidence for “byte-identical to the static typesupport’s CDR for the same message”: a_mixed_alignment_struct_matches_plain_cdr_byte_for_byte compares dynamic_data_serialize’s output for a bool+int32+float64 struct, byte for byte, against a by-hand plain-CDR encoding (alignment relative to the body start after the 4-byte encapsulation header, exactly as rmw_zerodds.c’s own cdr_align/rdr_align compute it — (len - 4) % a). For the graft fix specifically: a_grafted_members_facets_survive_and_match_idl_encoding builds a nested struct type with a @key, a @default(42) and an @optional member directly through DynamicTypeBuilder/NewMember (the C interface itself has no parameter for any of the three), composes it into an outer type through zerodds_dyn_builder_add_nested_member, reads the surviving @default back after dynamic_data_init_from_dynamic_type, and compares the serialized bytes octet for octet against the same declaration written in IDL and encoded directly with zerodds_wire_codec::encode — exactly where a dropped @key or @optional would show: §7.4.1.1’s presence flag ahead of an @optional member shifts every byte after it.

Status: done

RMW integration (rmw_serialization_support_init, rmw_take_dynamic_message[_with_info])

Spec: rmw/dynamic_message_type_support.h (ROS 2 Jazzy) — the three rmw entry points, previously answered with RMW_RET_UNSUPPORTED (as rmw_cyclonedds_cpp does), now really implemented.

Repo: crates/rmw-zerodds-shim/rmw_c/rmw_zerodds_dynamic.cpp — rmw_serialization_support_init checks serialization_lib_name against its own identifier "rmw_zerodds_cpp" (RMW_RET_INCORRECT_RMW_IMPLEMENTATION on a mismatch) and calls rosidl_dynamic_typesupport_serialization_support_init with the table built above; rmw_take_dynamic_message[_with_info] uses the same raw sample path the static introspection codec uses (rmw_zerodds_take/_with_origin, librmw_zerodds.so) and hands the CDR stream — encapsulation header included — to dynamic_data_deserialize, which reads the representation off the header itself rather than guessing it. crates/rmw-zerodds-shim/ament/CMakeLists.txt and rmw_c/build_librmw_zerodds_cpp.sh build the new file in; find_package(rosidl_dynamic_typesupport QUIET) (CMake) and <depend condition="$ROS_DISTRO != 'humble'"> (package.xml) keep Humble building unchanged.

A member’s default_value covers array/sequence shapes too, not only scalars: XTypes’ @default (§7.3.4.5.4) has no collection form, so a fixed array’s/bounded sequence’s/unbounded sequence’s default stays out of the model entirely and is parsed (crates/rmw-zerodds-shim/src/dynamic/collection_default.rs) straight into Vec<DynamicData> elements, kept next to the type (ZddsDynBuilder/ZddsDynType/ZddsDynData) and applied at dynamic_data_init_from_dynamic_type* and at dynamic_data_clear_value/clear_all_values/clear_nonkey_values. The text is rosidl_adapter’s repr(tuple(value)) inside an IDL string literal (@default(value="(1, 2, 3)")) — the module note gives the element grammar (bool True/False, a plain decimal integer for every integer kind including byte/char, a decimal or scientific float or inf/-inf/nan, a quoted backslash-escaped string). A text that does not parse, does not fit its element kind’s range, runs past a bounded string’s own bound, or makes the wrong number of elements is refused, never silently shortened, clamped or padded.

Tests: crates/rmw-zerodds-shim/rmw_c/dynamic_typesupport_test.cpp via run_dynamic_typesupport_test.sh against ROS 2 Jazzy: "1) dynamic take agrees with the static take", "2) every primitive add_*_member call succeeded" — std_msgs/msg/String and test_msgs/msg/BasicTypes, test_msgs/msg/Arrays and test_msgs/msg/BoundedSequences over a real publisher, compared between a statically typed rclcpp::Subscription and rmw_take_dynamic_message; crates/rmw-zerodds-shim/src/dynamic/collection_default.rs::tests::the_wrong_element_count_is_refused_for_a_fixed_array, crates/rmw-zerodds-shim/src/dynamic/collection_default.rs::tests::a_bounded_sequence_accepts_at_or_under_its_bound_and_no_more (18 tests of the parser), crates/rmw-zerodds-shim/src/dynamic.rs::tests::a_collection_members_default_is_applied_on_init_and_clear, crates/rmw-zerodds-shim/src/dynamic.rs::tests::a_collection_default_of_the_wrong_length_is_refused, crates/rmw-zerodds-shim/src/dynamic.rs::tests::a_collection_default_past_its_bound_is_refused.

Status: partial — the interface table and the RMW wiring are implemented. dynamic_data_serialize always writes @final, little-endian, XCDR1 (PLAIN_CDR1), like the static introspection codec in rmw_zerodds.c; the interface passes no writer or QoS context. long double on 32-bit ARM and PowerPC (double-double) is missing; own item.

RMW integration — long double on 32-bit ARM and PowerPC

Spec: rosidl_dynamic_typesupport/api/serialization_support_interface.h (ROS 2 Jazzy) — get/set and serialization of a long double member (float128) on every platform ROS 2 runs on.

Repo: —

Tests: —

Status: open — crates/rmw-zerodds-shim/src/dynamic.rs bridges long double only for the x86-64 80-bit extended and the aarch64 binary128 storage form (Float128::{to,from}_x87_padded_bytes, {to,from}_le_bytes); on 32-bit ARM and for PowerPC’s double-double long double it answers RCUTILS_RET_UNSUPPORTED.


Audit status

37 done / 3 partial / 2 open / 0 blocked / 1 n/a (informative).

Test run: cargo test -p zerodds-ros2-rmw (81 green) + cargo test -p rmw-zerodds-shim (45 green: 24 unit, 12 shim_cli_e2e, 5 CLI binary, 4 ros2_ext_surface) + rclpy interop crates/py/python/tests/ros2/ on ROS 2 Humble (6 green: init, pub/sub, service call, topic graph, node names, endpoint info) via run_ros2_pytest.sh + rclcpp loan e2e via run_loaned_message_test.sh (Portable + RawSameHost, each can_loan=1 got=42 PASS) + REP-2007 e2e via run_type_adaptation_test.sh (13 checks PASS, 0 failures) + REP-2008 e2e via run_accel_loan_memory_test.sh (Portable 15 checks PASS, RawSameHost 16 checks PASS, 0 failures each) + REP-2009 e2e via run_type_negotiation_test.sh (16 checks PASS, 0 failures).

All ROS 2 runs on the Linux test host (ROS 2 Humble via RoboStack/micromamba).

Open items: ros2-rmw.open.en.md.

ROS 2 RMW Bridge — Spec-Coverage

Quellen:

  • ROS Enhancement Proposals (REPs) — internal/standards/cache/ros2/rep-{2003,2004,2005,2007,2008,2009}.html.
  • RMW C-API Headers — rmw/rmw.h und rmw/qos_profiles.h aus rmw 4.x (ROS 2 Iron/Jazzy Distribution); leben im upstream ros2/rmw-GitHub-Repository, nicht in internal/standards/cache/, da eigenständige Distribution. De-facto-Spezifikation ohne eigene REP, normativ über die Header-Definitionen.
  • ROS 2 IDL Subset — Design-Article design.ros2.org/articles/ idl_interface_definition.html (nicht im Cache; live-Quelle) + Wire-Naming-Convention aus rosidl_typesupport_fastrtps_cpp (de-facto, in ros2/rosidl-GitHub-Repository).
  • Topic/Service-Naming-Convention — Design-Article design.ros2.org/articles/topic_and_service_names.html (nicht im Cache; live-Quelle). Implementiert in rmw_dds_common.

Folgt dem Format aus docs/spec-coverage/PROCESS.md.

Kontext: ROS 2 Robot-Middleware-Wrapper bauen auf DDS auf. Die RMW-API + RMW-QoS-Profile-Mapping + Topic-Mangling-Convention + ROS-IDL→DDS-XTypes-Wire-Convention sind die zentralen Wire-Mappings. Verteilt über:

Crate-Mapping:

Spec-Bereich Crate-File
REP-2003 Sensor/Map QoS crates/ros2-rmw/src/qos_profiles.rs (Profile-Konstanten)
REP-2004 Quality-Levels crates/ros2-rmw/src/quality.rs
Topic-Name-Mangling Convention crates/ros2-rmw/src/topic_mangling.rs
Standard RMW QoS Profiles crates/ros2-rmw/src/qos_profiles.rs::profiles::*
RMW C-API (rmw/rmw.h) crates/ros2-rmw/src/ffi_api.rs
RMW-QoS-Mapping (rmw/qos_profiles.h) crates/ros2-rmw/src/rmw_qos_mapping.rs
ROS-IDL → DDS-XTypes Wire-Mapping crates/ros2-rmw/src/type_mapping.rs
REP-2007 Type-Adaptation / REP-2008 Beschleuniger-Speicher crates/rmw-zerodds-shim/rmw_c/rmw_zerodds_ros2_ext.h

Implementation: crates/ros2-rmw/ (11 Module, 81 Tests grün via cargo test -p zerodds-ros2-rmw).


REP-2003 Sensor Data and Map QoS Settings

Map QoS

Spec: REP-2003 §“Map Quality of Service” — “Map providers […] are expected to provide all maps over a reliable transient-local topic. […] The depth of the transient-local storage depth is left to the designer, however a single map depth is a reasonable choice”.

Repo: crates/ros2-rmw/src/qos_profiles.rs::profiles::MAP — Reliable + TransientLocal + KeepLast(1).

Tests: qos_profiles::tests::map_profile_matches_rep_2003_specification.

Status: done

Sensor Data QoS (Consumer-Side)

Spec: REP-2003 §“Sensor Driver Quality of Service” — wörtlich: “Sensor data provided by a sensor driver from a camera, inertial measurement unit, laser scanner, GPS, depth, range finder, or other sensors are expected to be provided over a SystemDefaultsQoS quality of service as provided by the implemented ROS 2 version API. Consumers of sensor data are to use SensorDataQoS quality of service as provided by the implemented ROS 2 version API.”

Wichtig: REP-2003 verlangt für Driver-Side SystemDefaultsQoS (= Reliable+Volatile+KeepLast(10), siehe DEFAULT-Item unter “Standard RMW QoS Profiles” weiter unten). Die Consumer-Side verwendet SensorDataQoS (= BestEffort+Volatile+KeepLast(5)). Dieses Item beschreibt die Consumer-Side; die Driver-Side ist via profiles::DEFAULT abgedeckt.

Repo: crates/ros2-rmw/src/qos_profiles.rs::profiles::SENSOR_DATA — BestEffort + Volatile + KeepLast(5) (entspricht rmw_qos_profile_sensor_data aus rmw/qos_profiles.h).

Tests: qos_profiles::tests::sensor_data_profile_matches_rep_2003_specification prüft die Consumer-Side-Konstante (BestEffort+Volatile+KeepLast(5)).

Status: done — Consumer-Side abgedeckt; Driver-Side ist über DEFAULT-Profile (siehe unten) gleichermaßen abgedeckt.


REP-2004 Package Quality Categories

Quality Levels Q1-Q5

Spec: REP-2004 — fünf Levels mit folgenden Definitionen (zitiert aus REP-2004 §“Quality Level Categories”):

  • Quality Level 1 — “highest quality level; packages that are needed for production systems”.
  • Quality Level 2 — “high quality packages that are either: needed for production systems or commonly used”.
  • Quality Level 3 — “tooling quality packages”.
  • Quality Level 4 — “demos, tutorials, and experiments”.
  • Quality Level 5 — “default quality level” (Pakete ohne explizite Quality-Claims).

Numerische Repräsentation 1..5; Q1 ist die höchste, Q5 ist der Default für Pakete ohne explizit deklariertes Quality-Niveau.

Repo: crates/ros2-rmw/src/quality.rs::QualityLevel mit numeric()/from_numeric()-Konvertern.

Tests: quality::tests::quality_level_numeric_round_trip, quality::tests::quality_level_ordering_q1_is_highest, quality::tests::quality_level_from_numeric_rejects_out_of_range.

Status: done — Klassifikations-Modell exposed; tatsächliche Quality-Audit ist Caller-Aufgabe (z.B. package.xml-Tag).


REP-2005 ROS 2 Common Packages

Common-Package-List

Spec: REP-2005 — informational; Liste der ROS-2-Common-Packages.

Repo: —

Tests: —

Status: n/a (informative) — REP-2005 markiert sich selbst als informational; Common-Package-Liste ohne normative Anforderung an die RMW-Bridge.


REP-2007 Type Adaptation Feature

REP-2007 legt die Anwender-API (rclcpp::TypeAdapter) bewusst in rclcpp und benennt im selben Abschnitt, was der Middleware bleibt: „Perhaps we can support a form of this feature in other languages in rcl or rmw in the future. One challenge in doing this is that it may require custom type support, which may be middleware specific.” Die vier Items unten sind dieser Custom-Type-Support. Die Naht zu rclcpp ist die C++-Template-Syntax (TypeAdapter<Custom, Ros>, adapt_type::as, RCLCPP_USING_...) und die Ownership-Mechanik über unique_ptr/shared_ptr; alles darunter — Draht-Form, Typ-Identität, Kopierzahl — liegt hier.

Defining a Type Adapter — rmw-seitiges Custom-Type-Support

Spec: REP-2007 §“Defining a Type Adapter”: ein Adapter benennt custom_type, ros_message_type und die Konversionsfunktionen. §“Adding this feature in rclcpp”: „it may require custom type support, which may be middleware specific”.

Repo: crates/rmw-zerodds-shim/rmw_c/rmw_zerodds_ros2_ext.h (rmw_zerodds_type_adapter_t — ros_type_name, serialized_size, serialize, deserialize) + rmw_c/rmw_zerodds.c (rmw_zerodds_publisher_set_type_adapter, rmw_zerodds_subscription_set_type_adapter; Adapter-Feld in zerodds_pub_data_t/zerodds_sub_data_t). Der Endpoint selbst wird unverändert aus dem rosidl-Type-Support des ros_message_type gebaut, also bleiben DDS-Topic-Name, ROS-Typname und RIHS-Type-Hash die des unadaptierten Endpoints.

Tests: rmw_c/type_adaptation_test.cpp (rclcpp-e2e, Bindung an die rmw-Handles lebender rclcpp-Entities; Prüfung „endpoint topic type unchanged by adaptation” über get_publishers_info_by_topic vor und nach der Bindung) via rmw_c/run_type_adaptation_test.sh; crates/rmw-zerodds-shim/tests/ros2_ext_surface.rs::every_declared_extension_symbol_is_defined.

Status: done

Type Adaptation in Topics

Spec: REP-2007 §“Type Adaptation in Topics” — ein adaptierter Publisher bzw. eine adaptierte Subscription tauscht den custom_type aus, während auf dem Draht der ros_message_type steht.

Repo: rmw_c/rmw_zerodds.c (rmw_zerodds_publish_adapted, rmw_zerodds_take_adapted, zerodds_adapted_wire — 4-Byte-CDR_LE-Encapsulation identisch zum Introspection-Publish-Pfad). Als Teil davon füllen rmw_take_with_info, rmw_take_loaned_message_with_info und rmw_take_serialized_message_with_info jetzt rmw_message_info_t (zerodds_fill_message_info: received_timestamp, reception_sequence_number) statt einer Nullstruktur — rclcpp reicht diese Struktur an den adaptierten Callback weiter.

Tests: crates/rmw-zerodds-shim/rmw_c/type_adaptation_test.cpp: "2) adapted publish -> plain subscription" (adaptierter Publisher, gewöhnliche rclcpp-Subscription des ROS-Message-Typs), "3) plain publish -> adapted take" (Gegenrichtung), " adapted take fills rmw_message_info_t"; gefahren über crates/rmw-zerodds-shim/rmw_c/run_type_adaptation_test.sh.

Status: done

Type Adaptation in Services und Actions

Spec: REP-2007 §“Type Adaptation in Services”: Request- und Response-Hälfte werden unabhängig adaptiert („either the request or the response can be adapted”). §“Type Adaptation in Actions”: dieselbe Struktur für Goal, Feedback und Result.

Repo: rmw_c/rmw_zerodds.c (rmw_zerodds_client_set_type_adapters, rmw_zerodds_service_set_type_adapters, rmw_zerodds_send_request_adapted, rmw_zerodds_take_request_adapted, rmw_zerodds_send_response_adapted, rmw_zerodds_take_response_adapted; die adaptierte Nutzlast trägt denselben 24-Byte-Korrelations-Header wie der unadaptierte Pfad). Eine ROS-2-Action ist in rcl aus drei Services (Goal, Cancel, Result) und zwei Topics (Feedback, Status) zusammengesetzt — es gibt keine eigene Action-rmw-API —, also deckt die Service- plus Topic-Fläche die Action-Adaption ab.

Tests: crates/rmw-zerodds-shim/rmw_c/type_adaptation_test.cpp — "5) adapted service request/response roundtrip" über example_interfaces/srv/AddTwoInts mit std::pair<int64_t,int64_t> als Request- und int64_t als Response-Custom-Type (beides keine ROS-Nachrichten); gefahren über crates/rmw-zerodds-shim/rmw_c/run_type_adaptation_test.sh.

Status: done

(De)Serialize statt Convert — die vermiedene Kopie

Spec: REP-2007 §“Comparison to ROS 1’s Type Adaptation”: „This feature will support both convert and (de)serialize functions […] convert is superior for intra-process communication and the (de)serialization functions are better for inter-process communication.” Und: „An advantage of following the ROS 1 approach is that an extra copy can be avoided; although it is likely much more challenging to implement this feature the ROS 1 way because of the middleware.”

Repo: rmw_c/rmw_zerodds.c (zerodds_adapted_wire): der Draht-Puffer wird auf serialized_size(custom) alloziert und der Adapter schreibt den CDR-Rumpf direkt hinein. Es entsteht keine ros_message_type-Instanz, also findet die vom REP als vermeidbar beschriebene zweite Kopie nicht statt. Gegenrichtung analog in rmw_zerodds_take_adapted: der Rumpf wird direkt in den custom_type gelesen.

Tests: rmw_c/type_adaptation_test.cpp misst drei Größen: „adapted wire bytes == rmw_serialize bytes” (memcmp gegen die rmw_serialize-Ausgabe derselben Nachricht — die Draht-Identität ist gemessen, nicht per Konstruktion behauptet), „exactly one serialization per adapted publish” (Zähler in der Adapter-serialize) und „ros_message_type never materialized (no extra copy)” (der convert_to_ros_message-Zähler des Tests muss 0 bleiben). Via rmw_c/run_type_adaptation_test.sh. Draht-Identitäts-Regression: crates/rmw-zerodds-shim/tests/ros2_ext_surface.rs::adapted_publish_writes_the_same_encapsulation_header_as_plain_publish.

Status: done


REP-2008 Hardware Acceleration

REP-2008 ordnet die Schichten selbst zu: §“Kernel levels in ROS 2” stellt rmw und die rmw_adapters auf Level 2 und die Kommunikations-Middleware („including the communication middleware (e.g. DDS)“) auf Level 3, und §”Methodology” Schritt 3.2 verlangt, „inter-Node exchanges” zu beschleunigen und „the overhead of the ROS 2 message-passing system across all its abstraction layers” zu senken. Die Naht liegt bei den Kernel-Sprachen (CUDA, HLS, OpenCL) und der Firmware — beides erklärt der REP ausdrücklich zur Sache des Silizium-Herstellers und des Anwendungs-Architekten. Was hier liegt, ist die Speicherherkunft der Puffer, die zwischen Beschleuniger und Draht laufen.

Kernel-Level-Zuordnung

Spec: REP-2008 §“Kernel levels in ROS 2” — Level 2: „we consider rclcpp, rcl, rmw, and the corresponding rmw_adapters for each supported communication middleware”. Level 3: „Groups together all accelerators below the ROS 2 core layers […] including the communication middleware (e.g. DDS)“. Und: „Hardware acceleration solutions complying with this REP should aspire to support multiple kernel levels in ROS 2”.

Repo: crates/rmw-zerodds-shim/rmw_c/rmw_zerodds_ros2_ext.h (RMW_ZERODDS_ACCEL_LEVEL_1/2/3) + rmw_c/rmw_zerodds.c (rmw_zerodds_acceleration_kernel_levels → Level 2 | Level 3). Level 1 (ROS-2- Anwendungen und -Bibliotheken) bleibt beim Anwender.

Tests: rmw_c/accel_loan_memory_test.cpp („REP-2008 kernel levels 2 and 3 declared”) via rmw_c/run_accel_loan_memory_test.sh; crates/rmw-zerodds-shim/tests/ros2_ext_surface.rs::acceleration_kernel_levels_agree_across_header_impl_and_readme (Header, Implementierung und Vendor-Vorlage im README müssen dieselbe Zahl nennen).

Status: done

Pillar I — Erweiterungen des ament-Build-Systems

Spec: REP-2008 §“Pillar I - Extensions to ament” + §Specification: „Package maintainers are encouraged to integrate hardware acceleration in their ROS 2 packages by using the CMake hardware acceleration variables […] These CMake macros are meant to be used by maintainers to conditionally compile their ROS 2 packages”. Variablen: ROS_ACCELERATION (ament_hardware_acceleration), ROS_VITIS (ament_vitis).

Repo: crates/rmw-zerodds-shim/ament/CMakeLists.txt (find_package(ament_hardware_acceleration QUIET), find_package(ament_vitis QUIET), Auswertung von ROS_ACCELERATION/ROS_VITIS, RMW_ZERODDS_ACCELERATION_KERNEL_LEVELS, RMW_ZERODDS_LOAN_ALIGNMENT=page im Beschleuniger-Fall; Installation von rmw_zerodds_ros2_ext.h) + crates/rmw-zerodds-shim/ament/package.xml (bedingtes ament_hardware_acceleration-Build-Depend). Beide find_package sind QUIET: ein Workspace ohne Beschleuniger konfiguriert unverändert.

Tests: crates/rmw-zerodds-shim/rmw_c/accel_loan_memory_test.cpp prüft die Laufzeit-Seite derselben Entscheidung: "2) set_loan_alignment(page) accepted", "2) portable loan starts on a page boundary" (mit ZERODDS_LOAN_ALIGNMENT=page) und " non-power-of-two alignment rejected"; gefahren über crates/rmw-zerodds-shim/rmw_c/run_accel_loan_memory_test.sh.

Status: done

Pillar II und Template for Vendors — deklarierter Support-Umfang

Spec: REP-2008 §“Pillar II - firmware”: „each vendor should provide and maintain an acceleration_firmware_<solution> package specialization that delivers the corresponding artifacts” — Firmware-Artefakte je Silizium-Lösung. §“Template for Vendors”: „Silicon vendors and solution manufacturers can help set the expectations of the level of support their hardware acceleration technology provides in alignment with this REP by providing a template in the README.md files of their firmware and/or ament extensions.”

Repo: crates/rmw-zerodds-shim/README.md §“Template for Vendors (REP-2008 §”Template for Vendors”)” — die vom REP verlangte Tabelle: bediente Kernel-Level, Pillar I konsumiert, Pillar II nicht geliefert (Firmware ist das acceleration_firmware_<solution>-Paket des Silizium-Herstellers, ZeroDDS liefert keine Firmware-Artefakte), Technologie-Agnostik, die Beschleuniger-zugewandte API und die Umgebungsschalter.

Tests: crates/rmw-zerodds-shim/tests/ros2_ext_surface.rs::acceleration_kernel_levels_agree_across_header_impl_and_readme (die Vorlage darf nicht von Header und Implementierung abweichen).

Status: done

Methodology §3.2 — beschleuniger-registrierbarer Loan-Speicher

Spec: REP-2008 §“Methodology for developing ROS 2 packages that integrate hardware acceleration”, Schritt 3.2: „accelerate inter-Node exchanges and reduce the overhead of the ROS 2 message-passing system across all its abstraction layers. To do so, consider leveraging REP-2007 and REP-2009.”

CUDA, ROCm und XRT können nur aus Host-Speicher DMA-en, den sie vorher registriert (gepinnt) haben, und sie pinnen ganze Seiten. Ohne eine Aussage der Middleware, wo ein geliehener Nachrichtenpuffer liegt und welche seitenausgerichtete Ausdehnung ihn abdeckt, braucht ein beschleunigter Node einen eigenen gepinnten Staging-Puffer und kopiert bei jedem Publish — genau die Kopie, die Schritt 3.2 entfernen will.

Repo: crates/rmw-zerodds-shim/rmw_c/rmw_zerodds_ros2_ext.h (rmw_zerodds_loan_memory_t: kind, host_ptr, size, registration_base, registration_size, page_size, cross_process, segment_link) + rmw_c/rmw_zerodds.c (rmw_zerodds_publisher_get_loan_memory, rmw_zerodds_subscription_get_loan_memory, zerodds_fill_registration, rmw_zerodds_publisher_set_loan_alignment, zerodds_default_loan_alignment über ZERODDS_LOAN_ALIGNMENT, ausgerichtete Portable-Leihgabe in rmw_borrow_loaned_message). registration_base/registration_size sind die Argumente für cudaHostRegister, hipHostRegister oder xrt::bo.

Tests: crates/rmw-zerodds-shim/rmw_c/accel_loan_memory_test.cpp über crates/rmw-zerodds-shim/rmw_c/run_accel_loan_memory_test.sh, in beiden Delivery-Modi: "3) reported page size == sysconf(_SC_PAGESIZE)", "3) registration extent is page aligned on both ends", "3) registration extent covers the loaned buffer", "4) reported extent is pinnable (mlock)" — der Aufruf führt dieselbe Kernel-Operation aus, mit der eine Beschleuniger-Runtime Host-Speicher pinnt; die Pinnbarkeit ist damit gemessen und nicht gerechnet —, " the described buffer is the buffer that was delivered" und im Modus raw-same-host "5) forked child maps the segment and finds the payload" sowie "5) subscriber loan reports the same shared segment".

Status: done


REP-2009 Type Negotiation Feature

REP-2009 verortet die Negotiation-State-Machine selbst und sagt zugleich, was sie von den Schichten darunter erwartet — §“Implementation in the ROS 2 Core or in a Separate Project”: „While there are some advantages to having this project in the ROS 2 core, all of the needed APIs are available to make this a separate package. Thus, in the spirit of keeping the ROS 2 core smaller, this should be implemented in a separate project.”

Die rmw-Pflicht ist damit nicht, den Algorithmus zu fahren, sondern diesen Satz wahr zu machen. Die fünf Items unten sind die APIs, die §“Negotiation Algorithm”, §“Defining Supported Message Types” und §“Renegotiating Selected Message Types” namentlich brauchen; jedes ist gegen einen Lauf gemessen, der den REP-Algorithmus von Hand über rmw_zerodds fährt. Die Naht liegt bei der State-Machine selbst (Prioritätsrechnung, benutzerdefinierte Auswahlfunktionen, Deadlock-Timeout) — die legt der REP ausdrücklich in ein eigenes Paket über rmw.

Endpoint info by topic — rmw_get_publishers/subscriptions_info_by_topic

Spec: REP-2009 §“Negotiation Algorithm”, Negotiating Publisher, Schritt 1: „Use discovery to find all the connected subscriptions that are using the user specified negotiation topic.” Die rmw-Fläche dafür ist rmw/get_topic_endpoint_info.h: pro Topic die Endpoint-Liste mit Node-Name/-Namespace, Typ, Endpoint-Typ, 16-Byte-GUID und QoS (ros2 topic info -v), dazu rmw_count_publishers/rmw_count_subscribers.

Repo: rmw_c/rmw_zerodds.c (rmw_get_publishers_info_by_topic/ _subscriptions_info_by_topic → zerodds_get_endpoint_info_by_topic): enumeriert pro-Endpoint via rmw_zerodds_node_for_each_publication/subscription_endpoint, filtert auf das demanglede Topic, löst pro Endpoint die Node-Identität über rmw_zerodds_node_resolve_endpoint (Endpoint-GUID → Node) auf und füllt rmw_topic_endpoint_info_array_t. Datenpfad: crates/dcps/src/runtime.rs (discovered_publication/subscription_endpoints, lokal + SEDP-remote) → c-api (zerodds_runtime_for_each_*_endpoint + ZeroDdsEndpointInfo) → Shim. Voraussetzung Endpoint→Node: die ros_discovery_info-Participant-gid ist die echte DDS-Participant-GUID, und ParticipantEntitiesInfo trägt pro Node die reader/writer-gid-Sequenzen (Endpoint-GUID-Prefix matcht den Participant). QoS ist best-effort aus der Discovery (History/Depth liegen nicht auf dem Draht → UNKNOWN).

Tests: crates/py/python/tests/ros2/test_rmw_zerodds_interop.py::test_rclpy_endpoint_info_by_topic (rclpy get_publishers_info_by_topic: Typ + QoS + aus der Endpoint-GUID aufgelöster Node-Name/-Namespace) auf ROS 2 Humble; Shim-Roundtrip-Units crates/rmw-zerodds-shim/src/lib.rs::tests::participant_info_roundtrips_endpoint_gids, crates/rmw-zerodds-shim/src/lib.rs::tests::participant_info_roundtrips_without_endpoints; rmw_c/type_negotiation_test.cpp („negotiation subscription found through discovery”, „endpoint info names the negotiation message type”) via rmw_c/run_type_negotiation_test.sh.

Status: done

Supported-Message-Type-Identität — RIHS-Type-Hash

Spec: REP-2009 §“Defining Supported Message Types”: „The combination of the ROS message type and the string identifier must form a unique tuple, since multiple supported message types may be sent with the same ROS 2 message type.” Die rmw-Hälfte dieses Tupels ist der Typ auf dem Draht: Typname und RIHS-Type-Hash pro Endpoint (der String-Identifier lebt in der Negotiation- Nutzlast, siehe nächstes Item).

Repo: crates/rmw-zerodds-shim/src/lib.rs::rmw_zerodds_compute_type_hash (RIHS SHA-256) + der Endpoint-Info-Pfad oben.

Tests: type_hash_sha256_is_deterministic + 2 weitere Shim-Units; test_rclpy_endpoint_info_by_topic (Humble); rmw_c/type_negotiation_test.cpp („discovery reports the right ROS type per selected type” — zwei gleichzeitig ausgewählte Typen auf zwei Topics, jeder mit seinem eigenen ROS-Typ in der Discovery).

Status: done

Übertragung der Supported-Message-Priority-Map

Spec: REP-2009 §“Negotiation Algorithm”, Schritt 2: „Receive the supported message priority maps from all connected subscriptions.” §“Defining Negotiating Publishers and Subscriptions”: die Map wandert über „a negotiation topic that will be used to negotiate the selected message types” — also über gewöhnliches ROS-Pub/Sub. Ihre Form ist eine Sequenz verschachtelter Strukturen mit Strings (ROS-Typname, String-Identifier, Topic, Priorität).

Repo: der Introspection-CDR-Codec in rmw_c/rmw_zerodds.c (cdr_ser_msg/cdr_de_msg, Sequenz-Zweig über size_function/ get_const_function/get_function/resize_function, verschachtelte Nachricht über type_id_ == 18) in beiden Typesupport-Varianten — siehe „Introspection-Typesupport-Varianten (C und C++)“.

Tests: rmw_c/type_negotiation_test.cpp mit diagnostic_msgs/msg/DiagnosticArray — dieselbe Form wie negotiated_interfaces/msg/SupportedTypes (DiagnosticStatus[] mit drei Strings und einer verschachtelten KeyValue[]): „priority map survives serialize/deserialize” (rmw_serialize/rmw_deserialize-Identität), „supported message priority map delivered” (über den Draht) und „priority map round-trips field for field” (Feld für Feld verglichen). Via rmw_c/run_type_negotiation_test.sh.

Status: done

Ein regulärer Publisher je Selected Message Type

Spec: REP-2009 §“Negotiation Algorithm”, Negotiating Publisher: „The negotiating publisher then creates a regular publisher for each supported message type.” §“Negotiating Subscription”: „The negotiating subscription will then create exactly one subscription corresponding to the chosen type and topic name.” Die Endpoints entstehen also zur Laufzeit, nach der Entscheidung, und tragen je einen anderen ROS-Typ.

Repo: rmw_c/rmw_zerodds.c (rmw_create_publisher/rmw_create_subscription zur Laufzeit auf einem laufenden Node; der Typname kommt pro Endpoint aus zerodds_type_name und geht in Discovery und Draht).

Tests: crates/rmw-zerodds-shim/rmw_c/type_negotiation_test.cpp: "4) both selected message types deliver on their topics" (std_msgs/msg/String und std_msgs/msg/Int32 gleichzeitig — der Fall, den REP-2009 als N_1(x, y) an N_2(x) und N_3(y) zeichnet), "4) discovery reports the right ROS type per selected type", "5) a regular publisher per selected message type", "5) the selected type appears in the graph with its ROS type"; gefahren über crates/rmw-zerodds-shim/rmw_c/run_type_negotiation_test.sh.

Status: done

Renegotiation — Endpoints verschwinden und entstehen neu

Spec: REP-2009 §“Renegotiating Selected Message Types”: „If the selected messages must change, the negotiating publisher destroys its publishers. The negotiating publisher will then notify its subscriptions of the new selected message types on the user specified negotiation topic and create new publishers for the selected message types.” Ein zerstörter Endpoint muss also sofort aus dem Graph verschwinden, sonst rechnet die nächste Runde mit einem Peer, den es nicht mehr gibt.

Repo: crates/zerodds-c-api/src/lib.rs (zerodds_writer_destroy/ zerodds_reader_destroy rufen jetzt DcpsRuntime::remove_user_writer/remove_user_reader) + crates/dcps/src/runtime.rs (die beiden Methoden waren pub(crate) und damit nur über den DCPS-DataWriter erreichbar, nicht über den Runtime-FFI-Pfad, den rmw benutzt; sie nehmen den Endpoint aus der lokalen Registry, bauen die Intra-Runtime-Routen neu, stoppen die Ankündigung und senden ein SEDP-Dispose).

Vorher blieb ein über rmw zerstörter Publisher für immer im Graph: rmw_count_publishers und rmw_get_publishers_info_by_topic meldeten ihn weiter, und ein entfernter Peer wartete auf den Liveliness-Timeout statt sofort zu entmatchen.

Tests: rmw_c/type_negotiation_test.cpp: „destroyed publishers leave the graph” (rmw_destroy_publisher auf dem rmw-Handle, danach count_publishers == 0 und leere Endpoint-Info), „discovery reports the renegotiated ROS type” (dasselbe Topic trägt danach einen anderen ROS-Typ) und „the renegotiated selected type delivers”. Via rmw_c/run_type_negotiation_test.sh.

Status: done

Netzwerk-Änderung als Auslöser — Graph-Guard-Condition

Spec: REP-2009 §“Renegotiating Selected Message Types”: „In the case that the ROS 2 network is changing, the negotiating publisher may need to renegotiate its selected message types.” Zusammen mit Schritt 1 („Use discovery to find all the connected subscriptions”) heißt das: der Graph muss die Änderung melden, nicht nur abfragbar sein. rcl reicht das als Graph-Guard-Condition des Nodes durch.

Repo: rmw_c/rmw_zerodds.c (zerodds_refresh_graph_guards, zerodds_fp_cb, zerodds_node_register/_unregister, graph_fp im zerodds_node_data_t): rmw_wait bildet für jede Graph-Guard-Condition, die tatsächlich in einem Wait-Set liegt, einen Fingerabdruck über alle entdeckten Publications und Subscriptions des Nodes und hebt das Flag, sobald er sich ändert. Ein Node, den niemand beobachtet, kostet nichts.

Dazu die Guard-Condition-Auswertung selbst: rmw_wait bekommt von rcl in rmw_guard_conditions_t.guard_conditions[i] den data-Zeiger der Guard-Condition, nicht die rmw_guard_condition_t (rcl_wait_set_add_guard_condition: RMWStorage[n] = rmw_handle->data). Der Shim las den Eintrag als Struktur und verglich deren implementation_identifier — der Vergleich traf nie zu, also meldete keine Guard-Condition je Bereitschaft: weder die Graph-Guard-Condition noch die Interrupt-Guard-Condition des Executors noch eine benutzereigene. Der Eintrag wird jetzt als das gelesen, was er ist.

Ebenfalls hier: rmw_wait gab bei unendlichem Timeout RMW_RET_TIMEOUT zurück, wenn ein Weckruf ohne bereite Entität kam. rcls Graph-Listener behandelt das als fatalen Fehler und beendet den Prozess mit „rcl_wait unexpectedly timed out”; ein Wait ohne Deadline kann nicht ablaufen und meldet jetzt RMW_RET_OK.

Tests: crates/rmw-zerodds-shim/rmw_c/type_negotiation_test.cpp — "6) the graph change raised the node graph guard": das Graph-Event wird vor der Renegotiation scharf gestellt, der Wartepfad blockiert auf der Guard-Condition, und das Event muss danach gesetzt sein. Gefahren über crates/rmw-zerodds-shim/rmw_c/run_type_negotiation_test.sh.

Status: done


Topic-Name-Mangling Convention (de-facto, rmw_dds_common)

Prefix-Convention rt/rq/rr/rs

Spec: Design-Article design.ros2.org/articles/ topic_and_service_names.html §“DDS Topic Names” + de-facto-Konvention aus ros2/rmw_dds_common-Implementation:

  • rt/<name> — Topic-Pub-Sub (ROS-Topic).
  • rq/<name>Request — Service-Request.
  • rr/<name>Reply — Service-Reply.
  • rs/<name> — Service-Discovery (legacy, vor REP-2009-Strukturen).

Repo: crates/ros2-rmw/src/topic_mangling.rs::{RosKind, mangle_topic_name, demangle_topic_name, is_ros_topic}.

Tests: topic_mangling::tests::mangle_topic_strips_leading_slash_and_prepends_rt, topic_mangling::tests::mangle_preserves_internal_slashes, topic_mangling::tests::mangle_each_kind_uses_correct_prefix, topic_mangling::tests::mangle_handles_already_unprefixed_name, topic_mangling::tests::mangle_rejects_empty_name, topic_mangling::tests::mangle_rejects_invalid_leading_character, topic_mangling::tests::mangle_accepts_underscore_leading, topic_mangling::tests::demangle_round_trips_all_kinds, topic_mangling::tests::demangle_rejects_unknown_prefix, topic_mangling::tests::is_ros_topic_recognizes_all_four_prefixes, topic_mangling::tests::mangle_demangle_round_trip.

Status: done — Suffixe (Request/Reply) bleiben Caller-Aufgabe (typisch automatisch vom Service-Codegen ergänzt).


Standard RMW QoS Profiles

rmw_qos_profile_* Konstanten

Spec: rmw/qos_profiles.h (rmw 4.x) — sieben Default-Profiles: rmw_qos_profile_default (Reliable+Volatile+KeepLast(10)), rmw_qos_profile_sensor_data (BestEffort+Volatile+KeepLast(5)), rmw_qos_profile_parameters (Reliable+Volatile+KeepLast(1000)), rmw_qos_profile_services_default (Reliable+Volatile+KeepLast(10)), rmw_qos_profile_parameter_events (Reliable+Volatile+KeepLast(1000)), rmw_qos_profile_system_default (alle Felder als *_SYSTEM_DEFAULT-Sentinel; Implementation-defined), rmw_qos_profile_unknown (alle Felder als *_UNKNOWN-Sentinel).

Repo: crates/ros2-rmw/src/qos_profiles.rs::profiles::* (DEFAULT, SENSOR_DATA, PARAMETERS, SERVICES_DEFAULT, PARAMETER_EVENTS, SYSTEM_DEFAULT, UNKNOWN, MAP) + is_unknown/is_system_default-Predicates. Die Policy-Enums (Reliability/Durability/History) tragen echte SystemDefault- und Unknown-Sentinel-Varianten (spec-treu zu rmw_qos_*_policy_t); rmw_qos_mapping::rmw_to_dds reicht sie durch (Auflösung erst bei DDS-Entity-Erzeugung).

Tests: qos_profiles::tests::default_profile_is_reliable_volatile_keep_last_10, parameters_profile_uses_keep_last_1000, services_default_matches_rmw_defaults, parameter_events_uses_keep_last_1000, system_default_is_all_sentinels, unknown_profile_is_all_unknown, system_default_and_unknown_are_distinct, is_unknown_recognizes_unknown_profile, is_unknown_rejects_real_and_system_default_profiles; rmw_qos_mapping::tests::rmw_system_default_passes_through_as_sentinel, rmw_unknown_passes_through_as_sentinel.

Status: done — alle sieben rmw-Default-Profiles spec-treu: * Sechs konkret 1:1 (DEFAULT, SENSOR_DATA, PARAMETERS, SERVICES_DEFAULT, PARAMETER_EVENTS, MAP). * SYSTEM_DEFAULT = jedes Feld als *_SYSTEM_DEFAULT-Sentinel (nicht mehr Alias zu DEFAULT) → der DDS-Implementations-Default jedes Feldes greift. * UNKNOWN = jedes Feld als *_UNKNOWN-Sentinel + is_unknown-Predicate.


RMW C-API (rmw/rmw.h)

rmw_ret_t Return-Codes

Spec: rmw/types.h (rmw 4.x) — rmw_ret_t als int32 mit den Werten OK=0, ERROR=1, TIMEOUT=2, UNSUPPORTED=3, BAD_ALLOC=10, INVALID_ARGUMENT=11, INCORRECT_RMW_IMPLEMENTATION=12.

Repo: crates/ros2-rmw/src/ffi_api.rs::RmwRet (#[repr(i32)]- Enum mit allen sieben Werten + map_to_rmw_ret-Konverter).

Tests: ffi_api::tests::error_codes_match_rmw_h, ffi_api::tests::ok_is_zero, ffi_api::tests::map_to_rmw_ret_ok, ffi_api::tests::map_to_rmw_ret_err.

Status: done

Implementation-Identifier (rmw_get_implementation_identifier)

Spec: rmw/rmw.h — rmw_get_implementation_identifier() liefert Vendor-String (Konvention rmw_<vendor>_cpp, z.B. "rmw_fastrtps_cpp"/"rmw_cyclonedds_cpp"/"rmw_connext_cpp"). RMW_CHECK_*_FOR_NULL_*-Macros (in rmw/check_type_identifiers_match.h) prüfen Caller-übergebene Strings gegen diesen Identifier (RMW_RET_INCORRECT_RMW_IMPLEMENTATION bei Mismatch).

Repo: crates/ros2-rmw/src/ffi_api.rs::check_rmw_identifier plus Konstante RMW_IMPLEMENTATION_IDENTIFIER: &str = "rmw_zerodds_cpp" (folgt der Vendor-Naming-Convention).

Tests: ffi_api::tests::implementation_identifier_matches_convention, ffi_api::tests::check_rmw_identifier_accepts_correct, ffi_api::tests::check_rmw_identifier_rejects_other_vendor.

Status: done

rmw_node_t Construction

Spec: rmw/types.h — rmw_node_t-Struct mit implementation_identifier-Feld + name/namespace/context.

Repo: crates/ros2-rmw/src/ffi_api.rs::RmwNode.

Tests: ffi_api::tests::rmw_node_construction.

Status: done


RMW-QoS-Mapping (rmw/qos_profiles.h)

rmw_qos_*_policy_t-Enums (History/Reliability/Durability)

Spec: rmw/qos_profiles.h — drei C-Enums (rmw_qos_history_policy_t, rmw_qos_reliability_policy_t, rmw_qos_durability_policy_t) mit SYSTEM_DEFAULT / KEEP_LAST / KEEP_ALL / RELIABLE / BEST_EFFORT / TRANSIENT_LOCAL / VOLATILE / UNKNOWN / BEST_AVAILABLE-Werten.

Repo: crates/ros2-rmw/src/rmw_qos_mapping.rs::{RmwHistory, RmwReliability, RmwDurability} (#[repr(u32)]-Enums mit allen spec-konformen Diskriminanten).

Tests: rmw_qos_mapping::tests::enum_repr_is_c_compatible.

Status: done

rmw_to_dds-Konversion + BEST_AVAILABLE-Handling

Spec: rmw/qos_profiles.h + Design-Article — rmw_qos_profile_t ist die ROS-2-Side-Repräsentation; muss bidirektional auf DDS-QosProfile abgebildet werden. BEST_AVAILABLE (Iron+) wird auf Sender-Seite zu BEST_EFFORT resolved.

Repo: crates/ros2-rmw/src/rmw_qos_mapping.rs::{rmw_to_dds, dds_to_rmw, RmwQosProfile}.

Tests: rmw_qos_mapping::tests::rmw_to_dds_round_trip_default, rmw_qos_mapping::tests::rmw_to_dds_keep_all_passes_through, crates/ros2-rmw/src/rmw_qos_mapping.rs::tests::rmw_system_default_passes_through_as_sentinel, crates/ros2-rmw/src/rmw_qos_mapping.rs::tests::rmw_unknown_passes_through_as_sentinel, rmw_qos_mapping::tests::rmw_best_available_maps_to_best_effort_on_sender, rmw_qos_mapping::tests::transient_local_round_trips, rmw_qos_mapping::tests::sensor_data_is_best_effort, rmw_qos_mapping::tests::services_default_uses_keep_last_10, rmw_qos_mapping::tests::parameters_uses_keep_last_1000, rmw_qos_mapping::tests::default_profile_matches_rmw_spec.

Status: done


ROS-IDL → DDS-XTypes Wire-Mapping

Sub-Namespace-Convention (msg/srv/action)

Spec: ROS 2 Design-Article design.ros2.org/articles/idl_interface_definition.html §“Naming” — Top-Level-Namespaces msg/, srv/, action/ mit Convention auf DDS-Wire <package>::<sub-namespace>::dds_::<TypeName>_.

Repo: crates/ros2-rmw/src/type_mapping.rs::RosNamespace mit as_str()/from_str()-Konvertern; RosTypeRef::to_dds_type_name für die DDS-Wire-Form.

Tests: type_mapping::tests::namespace_str_repr, type_mapping::tests::dds_wire_form_uses_dds_dunder, type_mapping::tests::action_namespace_mapped_correctly, type_mapping::tests::srv_namespace_mapped_correctly.

Status: done

ROS-Form → DDS-Form Round-Trip

Spec: Design-Article — Konvertierung zwischen ROS-Wire-Form (std_msgs/msg/String) und DDS-Wire-Form (siehe oben). Roundtrip- Pflicht.

Repo: crates/ros2-rmw/src/type_mapping.rs::{RosTypeRef::new, to_ros_form, from_ros_form}.

Tests: type_mapping::tests::ros_form_round_trip, type_mapping::tests::from_ros_form_rejects_unknown_namespace, type_mapping::tests::from_ros_form_rejects_wrong_segment_count.

Status: done

Builtin-Type-Tokens (int32, float64, string, …)

Spec: Design-Article §“Field Types” — Builtin-Type-Tokens (bool, byte, char, float32/float64, int8-uint64, string, wstring) mappen auf DDS-IDL-Primitive.

Repo: crates/ros2-rmw/src/type_mapping.rs::RosBuiltinType mit from_ros_token()-Parser und cdr_size-Helper.

Tests: type_mapping::tests::from_ros_token_round_trip, type_mapping::tests::from_ros_token_rejects_unknown, type_mapping::tests::cdr_size_matches_omg_cdr2, type_mapping::tests::builtin_idl_names_match_omg_idl.

Status: done — rosidl-Builtin → OMG-IDL-4.2-/DDS-XTypes-1.3-Primitive- Mapping verifiziert.


RMW C-ABI Plugin (crates/rmw-zerodds-shim)

Die obigen 14 Items decken den Wire-Mapping-Crate (crates/ros2-rmw) ab. Die eigentliche rmw_*-C-ABI-Plugin-Schicht (rmw_c/rmw_zerodds.c brückt auf die Rust-Bridge src/lib.rs) ist hier separat geführt. Verifikation: rclpy- Interop-Pytest auf ROS 2 Humble (crates/py/python/tests/ros2/), 4/4 grün.

rmw_wait — event-driven

Spec: rmw/rmw.h rmw_wait (de-facto) — blockiert bis eine Entity ready ist oder Timeout.

Repo: crates/rmw-zerodds-shim/rmw_c/rmw_zerodds.c (rmw_wait) + src/lib.rs (WaitNotify, subscription_on_data, rmw_zerodds_*_has_data, rmw_zerodds_context_wait_block) — Reader-Daten-Listener → per-Subscription- Inbox → Context-Condvar; kein Spin, kein Fixed-Tick-Poll. Für die Raw-Delivery-Modi (RawSameHost/Iceoryx), die den RTPS-Listener nicht feuern, weckt zusätzlich ein Doorbell-Thread (rmw_zerodds_subscription_start_doorbell → zerodds_reader_raw_wait) dieselbe Condvar event-getrieben (siehe „Loaned Messages” unten).

Tests: rmw-zerodds-shim::tests::event_driven_wait_roundtrip_inprocess, wait_notify_blocks_then_wakes_on_notify; rclpy test_rclpy_publish_subscribe_string_roundtrip (Executor fährt durch rmw_wait).

Status: done

Services — Request/Reply

Spec: rmw/rmw.h rmw_create_client/service, rmw_send_request, rmw_take_request, rmw_send_response, rmw_take_response, rmw_service_server_is_available.

Repo: rmw_zerodds.c (Service-Typesupport-Introspection, CDR [encap][body], rcl-Request-IDs) + src/services.rs (Topics rq/…Request/ rr/…Reply, Typen …_Request_/_Response_, Cyclone-Form und Related-Identity-Form, Warten auf den Reply-Reader bis 100 ms).

Tests: crates/rmw-zerodds-shim/src/lib.rs::tests::{a_service_call_between_two_runtimes_correlates_by_the_related_identity, two_clients_in_one_runtime_each_get_their_own_reply}; rclpy test_rclpy_service_call_roundtrip (AddTwoInts 41+1=42); crates/rmw-zerodds-shim/rmw_c/run_ros2_services_test.sh (Linux-Testhost, gegen rmw_cyclonedds_cpp und rmw_fastrtps_cpp in beide Richtungen).

Status: done

rmw_serialize / rmw_deserialize + serialized publish/take

Spec: rmw/rmw.h rmw_serialize, rmw_deserialize, rmw_publish_serialized_message, rmw_take_serialized_message[_with_info].

Repo: rmw_zerodds.c (Introspection-CDR [encap 4][body], cdr_ser_msg/cdr_de_msg; serialized pub/take über den Bridge-Byte-Pfad).

Tests: dieselbe Introspection-CDR wie der verifizierte pub/sub-Pfad (test_rclpy_publish_subscribe_string_roundtrip).

Status: done

Topic-Graph — rmw_get_topic_names_and_types + rmw_count_publishers/subscribers

Spec: rmw/rmw.h Graph-Introspektion.

Repo: crates/dcps/src/runtime.rs (discovered_publication_topics/discovered_subscription_topics — lokale User-Endpoints + SEDP-remote), c-api zerodds_runtime_for_each_publication/ _subscription, rmw_zerodds.c (Demangle rt/<t>→/<t>, ::/__→/, dedup topic→types).

Tests: rclpy test_rclpy_topic_graph_introspection.

Status: done

Service-Graph und Namen pro Node

Spec: rmw/rmw.h rmw_get_service_names_and_types, rmw_count_services/clients, rmw_get_{publisher,subscriber,service,client}_names_and_types_by_node, no_demangle von rmw_get_topic_names_and_types, rmw_qos_profile_check_compatible.

Repo: rmw_zerodds.c (zerodds_demangle_service, zerodds_demangle_service_type, zerodds_names_and_types_by_node: Knoten jedes Endpoints aus ros_discovery_info, Services = Reader auf Request-Topics, Clients = Writer darauf), rmw_zerodds_qos.cpp (rmw_dds_common::qos_profile_check_compatible, wie rmw_cyclonedds_cpp); src/services.rs meldet Service-Endpoints in ros_discovery_info an. Vorher lieferten alle diese Funktionen nichts bzw. „kompatibel”.

Tests: crates/rmw-zerodds-shim/rmw_c/run_ros2_services_test.sh::graph (ros2 service list -t und ros2 node info über RMW-Grenzen, Linux-Testhost)

Status: partial — graph prüft rmw_get_service_names_and_types (ros2 service list -t) und die Service-Server je Node (ros2 node info). Ohne Test: rmw_count_services, rmw_count_clients, Publisher, Subscriber und Clients je Node, no_demangle und rmw_qos_profile_check_compatible.

Guard-Conditions + event-driven Wake

Spec: rmw/rmw.h rmw_create_guard_condition / rmw_trigger_guard_condition / rmw_wait. rcl legt in rmw_guard_conditions_t.guard_conditions[i] den data-Zeiger der Guard-Condition ab, nicht die rmw_guard_condition_t (rcl_wait_set_add_guard_condition: RMWStorage[n] = rmw_handle->data); nach dem Wait liest rcl genau dieses Array aus, um zu entscheiden, welche Guard-Condition ausgelöst hat.

Repo: rmw_zerodds.c (rmw_create/trigger_guard_condition → context_notify weckt die Wait-Condvar; rmw_wait liest die Array-Einträge als unsigned int *-Flag, konsumiert ein gesetztes Flag und nullt die übrigen Einträge).

Vorher las rmw_wait den Eintrag als rmw_guard_condition_t * und prüfte dessen implementation_identifier. Der Vergleich traf nie zu, also meldete keine Guard-Condition je Bereitschaft — weder die Interrupt-Guard-Condition des Executors noch die Graph-Guard-Condition eines Nodes noch eine benutzereigene. Zweiter Fehler auf demselben Pfad: ein Wait mit unendlichem Timeout gab bei einem Weckruf ohne bereite Entität RMW_RET_TIMEOUT zurück; rcls Graph-Listener behandelt das als fatalen Fehler und beendet den Prozess mit „rcl_wait unexpectedly timed out”.

Tests: crates/rmw-zerodds-shim/rmw_c/type_negotiation_test.cpp — "6) the graph change raised the node graph guard": das Event wird nur gesetzt, wenn die Guard-Condition wirklich Bereitschaft meldet; gefahren über crates/rmw-zerodds-shim/rmw_c/run_type_negotiation_test.sh. Der rmw_wait-Pfad selbst: crates/rmw-zerodds-shim/src/lib.rs::tests::wait_notify_blocks_then_wakes_on_notify, ::event_driven_wait_roundtrip_inprocess, ::wait_returns_invalid_for_null, ::has_data_and_context_block_null_safe.

Status: done

rmw_get_node_names + _with_enclaves

Spec: rmw/rmw.h rmw_get_node_names[_with_enclaves].

Repo: rmw_zerodds.c (rmw_get_node_names, Accumulator) + src/lib.rs (NodeGraph, encode/decode_participant_info, discovery_on_data, rmw_zerodds_for_each_node) — jeder Context publiziert seine ParticipantEntitiesInfo auf ros_discovery_info (hand-encodiertes XCDR1) und aggregiert lokale + remote Nodes.

Tests: rclpy test_rclpy_node_names_graph.

Status: done — ros_discovery_info wie in rmw_dds_common: NoKey, RELIABLE, TRANSIENT_LOCAL, KEEP_LAST(1) (src/lib.rs, Discovery-QoS); ein später gestarteter Prozess erhält den letzten Stand jedes Participants.

on_new_message/request/response-Callbacks (Events-Executor)

Spec: rmw/rmw.h rmw_*_set_on_new_*_callback.

Repo: rmw_zerodds.c (3 Setter → Bridge) + src/lib.rs (SubInbox.event, inbox_set_event, Invoke in subscription_on_data).

Tests: rmw-zerodds-shim::tests::event_callback_fires_on_arrival.

Status: done

rmw_get_serialized_message_size

Spec: rmw/rmw.h rmw_get_serialized_message_size.

Repo: crates/rmw-zerodds-shim/rmw_c/rmw_zerodds.c (rmw_get_serialized_message_size, zerodds_cdr_max_msg, zerodds_cdr_max_elem — Introspection-Size-Walk, konservative obere Schranke; Strings/Sequences gecappt).

Tests: crates/rmw-zerodds-shim/tests/ros2_ext_surface.rs::the_serialized_size_walk_covers_the_header_and_every_member_kind — prüft, dass der Rückgabewert den 4-Byte-Encapsulation-Header addiert, dass jede Member-Art (Primitive, String, Nested, Scalar, Fixed-Array, Sequence) einen Zweig im Walk hat und dass ZERODDS_STR_CAP/ ZERODDS_SEQ_CAP Platz reservieren statt null. Kein Gate.

Status: partial — der Walk ist implementiert und strukturell geprüft. Was fehlt, ist die Verhaltensprüfung „der zurückgegebene Wert ist tatsächlich ≥ der Länge, die rmw_serialize schreibt”; die braucht die rosidl-Introspection-Typesupports und damit einen ROS-2-Host. Rest als eigenes Item unten geführt.

rmw_get_serialized_message_size — Schranken-Nachweis auf einem ROS-2-Host

Spec: rmw/rmw.h rmw_get_serialized_message_size — der gelieferte Wert muss für die Vorab-Allokation reichen.

Repo: —

Tests: —

Status: open — es gibt keinen Testfall, der für eine reale Nachricht rmw_get_serialized_message_size gegen die von rmw_serialize geschriebene Länge hält. Voraussetzung ist ein ROS-2-Host; der Fall gehört neben die vorhandenen rclcpp-e2e-Tests in crates/rmw-zerodds-shim/rmw_c/.

Typed-Message-Loaning (rmw_borrow/publish/take_loaned_message)

Spec: rmw/rmw.h Loaned-Message-API + can_loan_messages. Delivery-Form: docs/specs/zerodds-delivery-modes-1.0.md (Modes Portable/RawSameHost/Iceoryx).

Repo: rmw_c/rmw_zerodds.c (rmw_borrow/publish/take_loaned_message, rmw_return_loaned_message_from_publisher/subscription, can_loan_messages = fixed-POD aus rosidl-Introspection) + die SHM-Bridge in crates/rmw-zerodds-shim/src/lib.rs (rmw_zerodds_publisher_enable_raw_loan/_loan/_commit/_discard, rmw_zerodds_subscription_enable_shm/_take_shm/_has_shm_data/_release_shm), Feature flatdata-loan (default).

Tests: crates/rmw-zerodds-shim/src/lib.rs::tests::loaning_null_args_rejected, crates/rmw-zerodds-shim/src/lib.rs::tests::return_loan_null_args_rejected (Argument-Kontrakt der Loan-ABI, kein Gate, laufen im Default-Lauf). Verhalten in beiden Delivery-Modi: crates/rmw-zerodds-shim/rmw_c/loaned_message_test.cpp, gestartet von crates/rmw-zerodds-shim/rmw_c/run_loaned_message_test.sh — rclcpp-e2e, läuft nur auf einem Host mit ROS 2 und ist im cargo-Default-Lauf nicht enthalten.

Status: done — die Loaned-Message-ABI ist implementiert und e2e verifiziert, in zwei Delivery-Modi (Default per ZERODDS_DELIVERY_MODE):

  • Portable (default): rclcpp übergibt einen getypten Struct-Buffer, der User schreibt die Struct, publish_loaned serialisiert Struct→CDR und publiziert über RTPS — interop-sicher (cross-host/cross-vendor), echtes CDR auf dem Draht. (can_loan=1 got=42 PASS.)
  • RawSameHost (echtes Zero-Copy/Zero-Serialize, same-host-only): der Writer ist auf set_delivery_mode(RawSameHost) + enable_shm_loan gestellt; borrow liefert einen Zeiger in den POSIX-SHM-Slot (der User schreibt die Struct direkt in Shared-Memory), commit finalisiert in-place ohne Serialisierung und ohne RTPS (c-api-publishes_to_wire-Gate → keine Double-Delivery). Der Reader mappt dasselbe Segment per deterministischem topic-abgeleitetem flink-Pfad (lazy attach) und liest den Slot zero-copy (take_loaned) bzw. mit einem Struct-memcpy für normale Callbacks (rmw_take). (can_loan=1 got=42 PASS, Beweis für SHM: Raw geht nie auf den Draht.)
  • Iceoryx (same-host cross-stack, Shim-Feature delivery-iceoryx): derselbe loan/commit/take-Pfad, aber Writer/Reader sind an einen iceoryx2- Service (topic-abgeleitet) gebunden — commit sendet über iceoryx2, der Reader empfängt davon. Ohne das Feature degradiert ZERODDS_DELIVERY_MODE=iceoryx beidseitig auf Portable. (can_loan=1 got=42 PASS via ZERODDS_TEST_ICEORYX=1.)

Readiness (Modi 1/2): event-getrieben. Pro Raw-Subscription parkt ein „Doorbell”-Thread auf zerodds_reader_raw_wait(reader, timeout_ms) (c-api), das auf der Raw-Quelle blockiert — SHM-Change-Generation-Futex (notify_generation/wait_for_change) bzw. iceoryx2-Listener — und bei einer echten Ankunft die Context-Condvar von rmw_wait weckt; der Sender notifiziert das iceoryx2-Event beim commit. Der Doorbell wird lazy gestartet, sobald die Raw-Quelle aktiv ist, und vor dem Reader-Destroy gestoppt + gejoint (er hält den Reader-Pointer). Da iceoryx2’s take ein destruktiver FIFO-Receive ist, prefetcht die Readiness zusätzlich genau ein Sample in einen Pending-Buffer der Subscription (nicht-konsumierend, idempotent); rmw_take/take_loaned konsumieren das gehaltene Sample. Damit weckt ein blockierendes rclcpp-spin() auch bei reinen Raw-Daten ohne Executor-Timeout.

Introspection-Typesupport-Varianten (C und C++)

Spec: rosidl liefert zwei Introspection-Typesupports mit identischem MessageMembers/ServiceMembers-Layout, aber unterschiedlichen Laufzeittypen für die variabel langen Member: rosidl_typesupport_introspection_c (rosidl_runtime_c__String/__U16String, von rclpy aufgelöst) und rosidl_typesupport_introspection_cpp (std::string/std::u16string, von rclcpp aufgelöst). Ein rmw muss beide bedienen.

Repo: rmw_c/rmw_zerodds.c (zerodds_introspect_ex, zerodds_service_introspect_ex liefern die Variante als Flag; das Feld cpp in crates/rmw-zerodds-shim/rmw_c/rmw_zerodds.c::cdr_t und crates/rmw-zerodds-shim/rmw_c/rmw_zerodds.c::rdr_t trägt sie durch Serialisierung und Deserialisierung; Publisher, Subscription, Client und Service speichern sie in members_cpp) + rmw_c/rmw_zerodds_cxx.cpp (zerodds_cxx_string_data/_assign, zerodds_cxx_u16string_data/_assign — der C-TU kann std::string nicht anfassen) + rmw_c/build_librmw_zerodds_cpp.sh (übersetzt und linkt die C++-Übersetzungseinheit, -lstdc++).

Vorher wurde die C++-Variante wie die C-Variante behandelt. Ein std::string-Member landete in rosidl_runtime_c__String__assignn, das den SSO-Puffer der std::string als Heap-Zeiger realloziert — realloc(): invalid pointer, Prozessabbruch beim ersten std_msgs/msg/String über rclcpp. zerodds_service_introspect fragte außerdem nur die C-Variante ab, sodass rmw_create_client/rmw_create_service für rclcpp-Services NULL lieferten.

Tests: crates/rmw-zerodds-shim/rmw_c/type_adaptation_test.cpp — "2) adapted publish -> plain subscription", "3) plain publish -> adapted take", "5) adapted service request/response roundtrip" (std_msgs/msg/String über rclcpp-Publisher und -Subscription in beiden Richtungen, example_interfaces/srv/AddTwoInts über rclcpp-Client und -Service), gefahren über crates/rmw-zerodds-shim/rmw_c/run_type_adaptation_test.sh. Regression der C-Variante: crates/py/python/tests/ros2/test_rmw_zerodds_interop.py::test_rclpy_publish_subscribe_string_roundtrip und ::test_rclpy_service_call_roundtrip über crates/rmw-zerodds-shim/rmw_c/run_ros2_pytest.sh.

Status: done


ROS 2 Jazzy Dynamic Type Support (rosidl_dynamic_typesupport)

Interface-Tabelle + reflektive Abbildung

Spec: rosidl_dynamic_typesupport/api/serialization_support_interface.h (ROS 2 Jazzy) — rosidl_dynamic_typesupport_serialization_support_interface_t, 192 Funktionszeiger: Dynamic-Type-Builder (jede Primitiv-/String-Art × skalar/Array/gebundene/ungebundene Sequenz, verschachtelte Member über dynamic_type_impl_t und über dynamic_type_builder_impl_t), Dynamic-Data (get/set/insert für jede Art, Sequenz-Operationen, verschachtelte Werte, Loan, Serialisierung). Von rosidl_dynamic_typesupport_fastrtps für Fast DDS und von diesem Repo für ZeroDDS implementiert — die Beschreibung “Downstream middlewares should populate this interface as appropriate” im Header selbst.

Repo: crates/rmw-zerodds-shim/src/dynamic.rs (dynamic_data_serialize, dynamic_data_init_from_dynamic_type, …) — jede Interface-Funktion bildet auf zerodds_types::dynamic::{DynamicTypeBuilder, DynamicType, DynamicData} ab, dieselbe reflektive Typ-Familie, die zerodds-dcpss SerializedTypeSupport für laufzeitbeschriebene Samples nutzt; Serialisierung/Deserialisierung geht durch zerodds_wire_codec:: {encode,decode}, denselben Codec-Pfad wie jeder andere DynamicData-Nutzer im Repo. crates/rmw-zerodds-shim/rmw_c/rmw_zerodds_dynamic.cpp füllt die 192 Funktionszeiger; die ~90 dahinterliegenden extern "C"-Einstiege sind händisch deklariert (cbindgen expandiert keine macro_rules!, siehe Datei-Kommentar), nicht aus dem generierten include/rmw_zerodds.h übernommen — derselbe Stil, den rmw_c/rmw_zerodds.c für seine eigenen extern-Deklarationen schon nutzt.

Da dynamic_type_builder_init keinen gemeinsamen Modell-Zustand über mehrere Aufrufe hinweg annehmen kann (jeder Aufruf bekommt ein frisches ..._impl_t, ohne Elternreferenz), baut jeder Builder sein eigenes, privates zerodds_types::dynamic::DynamicTypeBuilder/Ast; ein verschachtelter Typ wird beim Zusammensetzen (add_complex_member[_builder]) per Graft — Knoten für Knoten neu deklariert, memoisiert — in das Ziel-Ast kopiert, statt ein Modell zu teilen. Begründung im Modul-Kommentar von crates/rmw-zerodds-shim/src/dynamic.rs. Der Graft kopiert jedes Facet, das ein Member oder der Struct-Typ selbst trägt — @key, @optional, @must_understand, @external, @try_construct, die effektive @id, @default, sowie @nested/@autoid(HASH) und die Extensibility auf dem Typ —, nicht nur Typ und effektive Id: eine frühere Fassung ließ jedes dieser Facets außer der Id stillschweigend fallen, ein neu zusammengesetzter Typ verlor also seinen Key, sein @optional, seinen @default und mehr, ohne dass ein Fehler das meldete. Ein @default-Wert überlebt den Graft über seinen Text, nicht über seinen Id<ConstExprNode> — der ist zwischen zwei Ast-Instanzen nicht portabel — via zerodds_types::dynamic::default_expr_text, der Umkehrfunktion zu demselben Parser, der einen @default-Text erst gegen den Member-Typ liest.

Tests: cargo test -p rmw-zerodds-shim --lib dynamic::tests:: (15 grün): jede Primitiv-/String-Art rund-tripped über get/set/insert (every_primitive_kind_round_trips), verschachtelte Structs + Sequenzen (nested_struct_and_sequence_round_trip), Klon/Gleichheit auf Typ und Daten (clone_and_equals_agree), Loan/Return-Loan mit korrektem Zurückschreiben an die geliehene Stelle (a_loan_writes_back_at_its_own_origin), eine gebundene Zeichenkette wird bei Überlänge abgelehnt statt abgeschnitten (a_bound_violation_is_refused_not_truncated), die Repräsentations-Kopfbytes stimmen mit DDSI-RTPS 2.5 §10.5 überein (representation_of_matches_the_rtps_table) — und, als Beleg für “byte-identisch mit der statischen Typesupport-CDR derselben Nachricht”: a_mixed_alignment_struct_matches_plain_cdr_byte_for_byte vergleicht die von dynamic_data_serialize erzeugten Bytes eines Structs mit bool+int32+ float64 Byte für Byte gegen die von Hand berechnete Plain-CDR-Kodierung (Alignment relativ zum Body-Anfang nach dem 4-Byte-Encapsulation-Header, genau wie rmw_zerodds.cs eigener cdr_align/rdr_align — (len - 4) % a). Für den Graft-Fix speziell: a_grafted_members_facets_survive_and_match_idl_encoding baut einen verschachtelten Struct-Typ mit @key-, @default(42)- und @optional-Member direkt über DynamicTypeBuilder/NewMember (die C-Schnittstelle selbst hat für keines der drei einen Parameter), fügt ihn über zerodds_dyn_builder_add_nested_member in einen äußeren Typ ein, liest den überlebenden @default nach dynamic_data_init_from_dynamic_type zurück und vergleicht die serialisierten Bytes octet-für-octet mit derselben Deklaration, in IDL geschrieben und direkt mit zerodds_wire_codec::encode kodiert — genau dort, wo ein fallengelassenes @key oder @optional sich zeigen würde: §7.4.1.1s Presence-Flag vor einem @optional-Member verschiebt jedes Byte danach.

Status: done

RMW-Integration (rmw_serialization_support_init, rmw_take_dynamic_message[_with_info])

Spec: rmw/dynamic_message_type_support.h (ROS 2 Jazzy) — die drei rmw-Einstiegspunkte, vorher mit RMW_RET_UNSUPPORTED beantwortet (wie rmw_cyclonedds_cpp), jetzt echt implementiert.

Repo: crates/rmw-zerodds-shim/rmw_c/rmw_zerodds_dynamic.cpp — rmw_serialization_support_init prüft den serialization_lib_name gegen die eigene Kennung "rmw_zerodds_cpp" (RMW_RET_INCORRECT_RMW_IMPLEMENTATION bei Fehlpassung) und ruft rosidl_dynamic_typesupport_serialization_support_init mit der oben gefüllten Tabelle; rmw_take_dynamic_message[_with_info] nutzt denselben rohen Sample-Pfad wie der statische Introspection-Codec (rmw_zerodds_take/_with_origin, librmw_zerodds.so) und übergibt den CDR-Stream — Encapsulation-Header inklusive — an dynamic_data_deserialize, das die Repräsentation aus dem Header selbst liest statt sie zu raten. crates/rmw-zerodds-shim/ament/CMakeLists.txt und rmw_c/build_librmw_zerodds_cpp.sh bauen die neue Datei mit; find_package(rosidl_dynamic_typesupport QUIET) (CMake) und <depend condition="$ROS_DISTRO != 'humble'"> (package.xml) halten Humble unverändert baubar.

Der default_value eines Members deckt auch Array-/Sequence-Formen ab, nicht nur Skalare: XTypes’ @default (§7.3.4.5.4) kennt keine Collection-Form, also bleibt der Default eines fixen Arrays/einer Bounded-/Unbounded-Sequence komplett außerhalb des Modells und wird (crates/rmw-zerodds-shim/src/dynamic/collection_default.rs) direkt in Vec<DynamicData>-Elemente geparst, neben dem Typ gehalten (ZddsDynBuilder/ZddsDynType/ZddsDynData) und bei dynamic_data_init_from_dynamic_type* sowie bei dynamic_data_clear_value/clear_all_values/clear_nonkey_values angewendet. Der Text ist rosidl_adapters repr(tuple(value)) innerhalb eines IDL-String-Literals (@default(value="(1, 2, 3)")) — die Modul-Notiz gibt die Element-Grammatik (Bool True/False, eine einfache Dezimalzahl für jede Integer-Art inklusive byte/char, eine Dezimal- oder Exponential-Fließkommazahl oder inf/-inf/nan, ein gequoteter, backslash-escapter String). Ein Text, der nicht parst, nicht in den Wertebereich seiner Element-Art passt, über die eigene Bound eines beschränkten Strings hinausgeht oder die falsche Elementanzahl ergibt, wird abgelehnt, nie stillschweigend gekürzt, geklemmt oder aufgefüllt.

Tests: crates/rmw-zerodds-shim/rmw_c/dynamic_typesupport_test.cpp über run_dynamic_typesupport_test.sh gegen ROS 2 Jazzy: "1) dynamic take agrees with the static take", "2) every primitive add_*_member call succeeded" — std_msgs/msg/String und test_msgs/msg/BasicTypes, test_msgs/msg/Arrays und test_msgs/msg/BoundedSequences über einen echten Publisher, verglichen zwischen einer statisch typisierten rclcpp::Subscription und rmw_take_dynamic_message; crates/rmw-zerodds-shim/src/dynamic/collection_default.rs::tests::the_wrong_element_count_is_refused_for_a_fixed_array, crates/rmw-zerodds-shim/src/dynamic/collection_default.rs::tests::a_bounded_sequence_accepts_at_or_under_its_bound_and_no_more (18 Tests des Parsers), crates/rmw-zerodds-shim/src/dynamic.rs::tests::a_collection_members_default_is_applied_on_init_and_clear, crates/rmw-zerodds-shim/src/dynamic.rs::tests::a_collection_default_of_the_wrong_length_is_refused, crates/rmw-zerodds-shim/src/dynamic.rs::tests::a_collection_default_past_its_bound_is_refused.

Status: partial — Interface-Tabelle und RMW-Anbindung sind umgesetzt. dynamic_data_serialize schreibt immer @final, Little-Endian, XCDR1 (PLAIN_CDR1), wie der statische Introspection-Codec in rmw_zerodds.c; die Schnittstelle übergibt keinen Writer- oder QoS-Kontext. Es fehlt long double auf 32-Bit-ARM und PowerPC (Double-Double); eigenes Item.

RMW-Integration — long double auf 32-Bit-ARM und PowerPC

Spec: rosidl_dynamic_typesupport/api/serialization_support_interface.h (ROS 2 Jazzy) — Get/Set und Serialisierung eines long double-Members (float128) auf jeder Plattform, auf der ROS 2 läuft.

Repo: —

Tests: —

Status: open — crates/rmw-zerodds-shim/src/dynamic.rs brückt long double nur für die x86-64-80-Bit-Extended- und die aarch64-binary128-Speicherform (Float128::{to,from}_x87_padded_bytes, {to,from}_le_bytes); auf 32-Bit-ARM und für PowerPCs Double-Double-long double antwortet es mit RCUTILS_RET_UNSUPPORTED.


Audit-Status

37 done / 3 partial / 2 open / 0 blocked / 1 n/a (informative).

Test-Lauf: cargo test -p zerodds-ros2-rmw (81 grün) + cargo test -p rmw-zerodds-shim (45 grün: 24 Unit, 12 shim_cli_e2e, 5 CLI-Binary, 4 ros2_ext_surface) + rclpy-Interop crates/py/python/tests/ros2/ auf ROS 2 Humble (6 grün: init, pub/sub, service-call, topic-graph, node-names, endpoint-info) via run_ros2_pytest.sh + rclcpp-Loan-e2e via run_loaned_message_test.sh (Portable + RawSameHost, je can_loan=1 got=42 PASS) + REP-2007-e2e via run_type_adaptation_test.sh (13 Prüfungen PASS, 0 failure) + REP-2008-e2e via run_accel_loan_memory_test.sh (Portable 15 Prüfungen PASS, RawSameHost 16 Prüfungen PASS, je 0 failure) + REP-2009-e2e via run_type_negotiation_test.sh (16 Prüfungen PASS, 0 failure).

Alle ROS-2-Läufe auf dem Linux-Testhost (ROS 2 Humble via RoboStack/micromamba).

Offene Punkte: ros2-rmw.open.md.