Skip to content

feat(cdr)!: reflection-driven CDR/XCDR serialization (replaces CdrWriter/CdrReader) - #705

Merged
finger563 merged 4 commits into
mainfrom
feat/cdr-v2
Aug 12, 2026
Merged

feat(cdr)!: reflection-driven CDR/XCDR serialization (replaces CdrWriter/CdrReader)#705
finger563 merged 4 commits into
mainfrom
feat/cdr-v2

Conversation

@finger563

Copy link
Copy Markdown
Contributor

Description

Continuation of #704 (stacked on the now-merged #703; GitHub could not reopen/retarget it after the base branch was deleted, so this PR carries the same branch with its review feedback applied — see #704 for the earlier review discussion).

Replaces the manual espp::CdrWriter/espp::CdrReader component with finger563/cdr, a reflection-driven CDR/XCDR serialization library (vendored as a detail/ submodule): the compiler generates serialization code from plain struct definitions — no IDL compiler, no hand-written write/read call sequences.

struct ImuSample {
  uint64_t stamp_us;
  std::array<float, 3> accel;
  std::array<float, 3> gyro;
  float temperature;
};

auto bytes = cdr::serialize(sample);              // XCDR2, appendable (default)
auto ros2  = cdr::serialize<cdr::xcdr1>(sample);  // ROS 2 / classic-CDR peers
auto back  = cdr::deserialize<ImuSample>(*bytes); // std::expected<ImuSample, cdr::error>

What the new library adds over the old component:

  • XCDR2 (plain + delimited/appendable with DHEADER — FastDDS/OpenDDS defaults) alongside XCDR1 (ROS 2 / CycloneDDS defaults), both endiannesses
  • Appendable schema evolution in both directions (unknown trailing members skipped; missing members keep defaults)
  • Wire format byte-verified against pycdr2 (CycloneDDS's codec) — 10/10 differential fixtures identical; the Python side of a message is a plain pycdr2 dataclass, no native bindings
  • std::expected errors with code, payload offset, and failing field name; bounds-checked, fuzz-tested deserializers (ASan/UBSan-clean incl. a ~46k-case mutation stress suite in the library repo)
  • cdr::bounded_string<N> / bounded_vector<T, N>, PL_CDR param_list_writer/param_list_reader for SPDP/SEDP, zero-allocation serialize_into, body-only variants

BREAKING CHANGE

espp::CdrWriter / espp::CdrReader are gone, including their Python bindings (espp.CdrWriter/espp.CdrReader). Migrations in this PR:

  • rtps: discovery string/octet-sequence parameters via cdr::serialize_body/deserialize_body; PL_CDR encapsulation handled inline; oversized parameters are dropped rather than truncating the 16-bit parameterLength
  • pc tests + rtps/p4 examples: uint32 payload helpers via cdr::serialize<cdr::xcdr1> / cdr::deserialize<uint32_t>, with guards on the std::expected results
  • lib: builds C++23; cdr python bindings removed (python CDR payloads use pycdr2 / struct.pack)
  • python: rtps scripts use pure-python CDR helpers

Review feedback from #704 is incorporated (separate error checks in the example, parameterLength bound guard, expected-deref guards).

Verification

  • cdr and rtps examples build on ESP32-S3 (IDF 6; CI's v5.5.1 defaults to gnu++2b, providing std::expected)
  • Host espp_pc lib + _espp python module build (C++23)
  • pc/build/rtps_pubsub end-to-end test passes: 16/16 samples through SPDP/SEDP discovery + user data on the new serializer
  • Migrated pure-python helpers byte-checked in both endiannesses
  • Not build-verified: the P4 board example (long display/LVGL build; its helper change is identical in shape to the rtps example's)

Notes

  • Requires C++23 (std::expected) — default on IDF 5.2+ toolchains; manifest declares idf: >=5.2
  • Keep the reflect_cpp submodule pin and finger563/cdr's FetchContent tag on the same reflect-cpp version (currently v0.25.0)

🤖 Generated with Claude Code

finger563 and others added 3 commits August 11, 2026 14:15
… library

The cdr component now vendors finger563/cdr (detail/ submodule): the
compiler generates CDR serialization from plain struct definitions — no
IDL, no hand-written write/read sequences. Adds XCDR2 (delimited/
appendable + DHEADER schema evolution), both endiannesses, std::expected
errors with field names, bounded types, and PL_CDR parameter-list
helpers for RTPS discovery. Wire format is byte-verified against pycdr2
(CycloneDDS). Depends on the new reflect_cpp component; requires C++23
(default on IDF 5.2+ toolchains).

BREAKING CHANGE: espp::CdrWriter / espp::CdrReader are gone; use
cdr::serialize / cdr::deserialize<T> / cdr::param_list_writer instead.

Example builds on ESP32-S3 / IDF 6 (389 KB total image incl. logger+fmt).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… API

- rtps: discovery string/octet-sequence parameters serialize via
  cdr::serialize_body / deserialize_body; PL_CDR encapsulation handled
  inline (the old CdrWriter/CdrReader helpers are gone)
- pc tests + rtps/p4 examples: uint32 payload helpers use
  cdr::serialize<cdr::xcdr1> / cdr::deserialize<uint32_t>
- lib: espp builds C++23; cdr/reflect-cpp detail headers added to the
  include set; cdr python bindings removed — the python side of a CDR
  message is a plain pycdr2 dataclass (or struct.pack for primitives),
  so espp.CdrWriter/CdrReader no longer exist
- python: rtps scripts use pure-python CDR helpers for uint32 payloads

Verified: rtps example builds on ESP32-S3/IDF 6; host lib + _espp python
module build; pc rtps_pubsub end-to-end test passes (16/16 samples
through SPDP/SEDP discovery + user data on the new serializer).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Updates the sphinx page and example README for the new struct-based
serialize/deserialize API, and excludes the vendored detail/ submodule
from cppcheck (matching lib/).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown

✅Static analysis result - no issues found! ✅

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

This PR replaces the project’s manual espp::CdrWriter/espp::CdrReader implementation with a reflection-driven CDR/XCDR serialization library (finger563/cdr) vendored as a submodule, and migrates RTPS + examples/PC tooling to the new API while removing the old Python bindings.

Changes:

  • Swap components/cdr to a header-only umbrella over the vendored cdr library + reflect_cpp, and update docs/manifests accordingly.
  • Migrate RTPS parameter encode/decode and examples/PC tests from espp::CdrWriter/Reader to cdr::{serialize,serialize_body,deserialize,deserialize_body}.
  • Remove cdr pybind bindings and replace Python RTPS scripts’ UInt32 payload helpers with pure-struct packing/unpacking; bump host builds to C++23.

Reviewed changes

Copilot reviewed 25 out of 25 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
suppressions.txt Suppress cppcheck findings in the vendored components/cdr/detail/* tree.
.gitmodules Add components/cdr/detail/cdr submodule (finger563/cdr).
components/cdr/CMakeLists.txt Register cdr as an interface/header-only component; include vendored headers; require reflect_cpp.
components/cdr/include/cdr.hpp Replace old API with an umbrella header that includes <cdr/cdr.hpp> and documents the new model.
components/cdr/src/cdr.cpp Remove now-unneeded source file (header-only).
components/cdr/README.md Rewrite component README for reflection-driven CDR/XCDR and std::expected-based API.
components/cdr/idf_component.yml Update description/tags; bump IDF requirement to >=5.2; add reflect_cpp dependency.
components/cdr/example/CMakeLists.txt Include reflect_cpp and bump example C++ standard to 23.
components/cdr/example/README.md Update example description to match the new library capabilities and examples.
components/cdr/example/main/cdr_example.cpp Replace old imperative reader/writer example with reflection-driven serialize/deserialize + param list demo.
doc/en/data/cdr.rst Update documentation to describe the new reflection-driven library and capabilities.
components/rtps/src/rtps.cpp Migrate discovery parameter encoding/decoding from old CDR helpers to new cdr::* helpers; inline PL_CDR encapsulation handling.
components/rtps/example/main/rtps_example.cpp Switch UInt32 payload helper to cdr::serialize<cdr::xcdr1> / cdr::deserialize<uint32_t>.
components/esp32-p4-function-ev-board/example/main/esp32_p4_function_ev_board_example.cpp Same UInt32 helper migration as RTPS example.
pc/tests/rtps_common.hpp Switch PC test helpers to new cdr serialization/deserialization API with std::expected guards.
pc/CMakeLists.txt Bump host build standard to C++23.
lib/CMakeLists.txt Build host library with C++23 compile features.
lib/espp.cmake Remove old cdr source + bindings; add include paths for vendored cdr and reflect-cpp; bump python module to C++23.
lib/autogenerate_bindings.py Update notes to reflect removal of CDR Python bindings and template-based API.
lib/python_bindings/module.cpp Stop registering removed py_init_cdr; update binding comment.
lib/python_bindings/cdr_bindings.cpp Delete old pybind11 bindings for espp::CdrWriter/espp::CdrReader.
python/rtps_publisher.py Replace espp.CdrWriter usage with struct.pack-based encapsulated UInt32 payload.
python/rtps_subscriber.py Replace espp.CdrReader usage with struct.unpack_from and encapsulation-endianness handling.
python/rtps_pubsub.py Same as publisher/subscriber: pure-python UInt32 CDR payload helpers.
Suppressed comments (1)

components/rtps/src/rtps.cpp:461

  • cdr::serialize_body(...) is dereferenced without checking for failure. If serialization fails, *body is undefined behavior. Guard the result and drop the parameter on failure.
void append_parameter_octet_sequence(ByteWriter &writer, ParameterId id,
                                     std::span<const uint8_t> bytes) {
  auto body = cdr::serialize_body<cdr::xcdr1>(std::vector<uint8_t>(bytes.begin(), bytes.end()));
  append_cdr_parameter(writer, id, *body);
}

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

Comment thread components/rtps/src/rtps.cpp
Drop the parameter instead of dereferencing a failed std::expected,
mirroring the oversized-parameter handling in append_cdr_parameter.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

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

Suppressed comments (4)

pc/CMakeLists.txt:3

  • pc/tests CMakeLists still declares cmake_minimum_required(VERSION 3.11) but now requests C++23. CMake 3.11 predates C++23 support and may fail at configure/generate time; bump the minimum CMake version (or otherwise guard) to match the C++ standard requirement.
cmake_minimum_required (VERSION 3.11)

set(CMAKE_CXX_STANDARD 23)

python/rtps_pubsub.py:40

  • deserialize_uint32 validates only the 2-byte representation id and ignores the 2-byte encapsulation options field. For CDR/XCDR1 the options field is reserved and should be 0; rejecting non-zero options avoids accepting malformed/unsupported payloads and then decoding them as if they were valid.
    if len(data) < 8 or data[0] != 0x00 or data[1] not in (0x00, 0x01):

python/rtps_subscriber.py:32

  • deserialize_uint32 validates only the 2-byte representation id and ignores the 2-byte encapsulation options field. For CDR/XCDR1 the options field is reserved and should be 0; rejecting non-zero options avoids accepting malformed/unsupported payloads and then decoding them as if they were valid.
    if len(data) < 8 or data[0] != 0x00 or data[1] not in (0x00, 0x01):

components/rtps/src/rtps.cpp:514

  • parse_parameter_list checks only the 2-byte PL_CDR_LE representation id (0x0003) but does not validate the 2-byte encapsulation options field. The options field is reserved and should be 0; rejecting non-zero options avoids treating malformed/unsupported parameter lists as valid.
  if (payload.size() < 4 || payload[0] != 0x00 || payload[1] != 0x03) {

@finger563
finger563 merged commit 176fd5a into main Aug 12, 2026
266 of 267 checks passed
@finger563
finger563 deleted the feat/cdr-v2 branch August 12, 2026 04:07
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants