# Changelog

All notable changes to EdgeFirst Perception Schemas will be documented in this file.

The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [Unreleased]

## [2.2.0] - 2026-03-22

### Added

- Type-coercing field reads via `PointFieldType::read_as_f64` — converts any stored
  numeric type to `f64` without precision loss; used by all coercion methods below
- `FieldDesc::read_as_f64` and `read_as_f32` — per-field coercion from any
  `PointFieldType` to a common float target (`f32` may lose precision for
  `Int32`/`Uint32` >24-bit or `Float64` narrowing)
- Public accessors `DynPoint::cloud()` and `DynPoint::data()` enabling zero-overhead
  hot-loop pattern with pre-resolved field descriptors
- Convenience methods on `DynPoint`: `read_as_f64` and `read_as_f32`
  (name-based coercing reads)
- Bulk coercing methods on `DynPointCloud`: `gather_as_f64` and `gather_as_f32`

## [2.1.0] - 2026-03-20

### Added

- Zero-copy `pointcloud` access layer over PointCloud2 data buffers (`sensor_msgs::pointcloud`)
  - `DynPointCloud`: Runtime field inspection with dynamic typed access to individual point fields
  - `DynPoint`: Zero-copy single-point view with by-name and pre-resolved descriptor access
  - `PointCloud<P>`: Compile-time typed access via the `Point` trait and `define_point!` macro
  - `PointFieldIter`: Non-allocating iterator over PointCloud2 field descriptors
  - `FieldDesc`, `PointFieldType`, and `PointCloudError` public types
- Convenience methods on `PointCloud2`: `as_dyn_cloud()` and `as_typed_cloud::<P>()`
  for direct construction of pointcloud views from a decoded message
- Complete `DynPoint` accessor coverage for all scalar types:
  `read_f32`, `read_f64`, `read_u8`, `read_u16`, `read_u32`,
  `read_i8`, `read_i16`, `read_i32` (plus `_at` variants)
- Bulk `gather_*` methods for all scalar types on `DynPointCloud`
- PointCloud access benchmarks for `DynPointCloud` and typed `PointCloud<P>`

### Changed

- Refactored `sensor_msgs` into a multi-file module (`sensor_msgs/mod.rs` +
  `sensor_msgs/pointcloud.rs`). Existing `use edgefirst_schemas::sensor_msgs::*`
  imports are unaffected.

## [2.0.1] - 2026-03-12

### Added

- C API functions for ModelInfo labels access: `ros_model_info_get_labels_len()`, `ros_model_info_get_label()`
- C API functions for ModelInfo shape access: `ros_model_info_get_input_shape()`, `ros_model_info_get_output_shape()`
- `CdrCursor::check_seq_count()` for sequence count validation against buffer bounds
- Golden CDR test data for multi-box Detect, ModelInfo with labels, and empty ModelInfo
- Comprehensive edge case tests for ModelInfo labels and DetectBox variable-length fields

### Changed

- Reduced ModelInfo offset table from 7 to 6 entries (removed unused slot)
- Reduced FoxgloveImageAnnotation offset table from 3 to 2 entries (removed unused slot)

## [2.0.0] - 2026-03-10

### Changed — BREAKING

**Replaced serde-based CDR with zero-copy CDR infrastructure.**

This release replaces the `serde` + `cdr` crate serialization with a hand-written
zero-copy CDR1-LE implementation. Wire format is unchanged — existing CDR
buffers from ROS 2 DDS remain fully compatible. The Rust and C APIs have changed
substantially.

**Removed:**
- `serde`, `cdr`, and `serde_derive` dependencies
- `src/serde_cdr.rs` module (replaced by `src/cdr.rs`)
- `Serialize` / `Deserialize` derives on all message types
- Owned-field struct constructors (replaced by buffer-backed generics)

**Added:**
- `cdr` module — `CdrCursor`, `CdrWriter`, `CdrSizer`, `CdrFixed` trait,
  `encode_fixed` / `decode_fixed` helpers, zero-copy `rd_*` / `wr_*` primitives
- Buffer-backed generic types `Type<B: AsRef<[u8]>>` for all variable-length
  messages — zero-copy `from_cdr(&[u8])` borrows the buffer with no allocation
- `CdrFixed` implementations for all fixed-size types (Time, Duration, Vector3,
  Quaternion, Pose, ColorRGBA, NavSatStatus, RegionOfInterest, etc.)
- O(1) field accessors via pre-computed offset tables
- Zero-copy typed array views (`&[i16]`, `&[f32]`, `&[f64]`) on LE targets
- IMU FFI accessors: orientation, angular velocity, linear acceleration, and
  all three covariance matrices now accessible from C API
- 46 golden CDR test files generated by Python pycdr2 for cross-language
  validation (3-phase: decode, construct byte-identical, modify roundtrip)
- `CdrError` type with proper `Result`-based error handling throughout:
  `encode_fixed`, buffer-backed constructors, and setter methods all return
  `Result`. Zero panics in production library code — validated-buffer
  accessors use `expect()` with documented invariants.
- [`CAPI.md`](CAPI.md) — comprehensive C API reference with architecture
  overview, memory management rules, error handling, full API signatures
  for all types, and working examples
- Updated `examples/c/example.c` to demonstrate the new zero-copy C API

**Performance** (measured on Cortex-A53 @ 1.8 GHz, see [`BENCHMARKS.md`](BENCHMARKS.md)):
- Zero-copy borrow (`from_cdr`) in 38–166 ns regardless of payload size —
  applies to both Rust and C APIs; the C FFI `ros_*_from_cdr()` borrows the
  caller's buffer directly with no intermediate copy
- Rust encoding 270–530× faster than Python pycdr2 serialization
- Rust decoding effectively unbounded speedup for large payloads — 1.8 billion×
  faster for 1.5 MB radar cubes, growing proportionally with message size

### Migration Guide

**Rust API — CdrFixed types (Time, Vector3, Pose, etc.):**

```rust
// Before (1.x): serde-based
use edgefirst_schemas::serde_cdr;
let bytes = serde_cdr::to_cdr_bytes(&time).unwrap();
let time: Time = serde_cdr::from_cdr_bytes(&bytes).unwrap();

// After (2.0): CdrFixed
use edgefirst_schemas::cdr;
let bytes = cdr::encode_fixed(&time)?;
let time: Time = cdr::decode_fixed(&bytes)?;
```

**Rust API — Buffer-backed types (Image, PointCloud2, etc.):**

```rust
// Before (1.x): Owned structs with serde
let img = sensor_msgs::Image {
    header: Header { stamp, frame_id: "camera".into() },
    height: 480, width: 640, encoding: "rgb8".into(),
    is_bigendian: 0, step: 1920, data: pixel_data,
};
let bytes = serde_cdr::to_cdr_bytes(&img).unwrap();
let decoded: Image = serde_cdr::from_cdr_bytes(&bytes).unwrap();

// After (2.0): Buffer-backed constructors
let img = sensor_msgs::Image::new(
    stamp, "camera", 480, 640, "rgb8", 0, 1920, &pixel_data,
)?;
let bytes = img.to_cdr();                          // Get the CDR buffer
let decoded = sensor_msgs::Image::from_cdr(&bytes).unwrap(); // Zero-copy borrow
let height = decoded.height();                     // O(1) accessor
```

**Rust API — Zero-copy borrow (new capability):**

```rust
// Borrow existing bytes — no allocation, no copy
let view = sensor_msgs::Image::from_cdr(cdr_slice)?;
let pixels: &[u8] = view.data();     // Points into cdr_slice
let frame: &str = view.frame_id();   // Points into cdr_slice
```

**C API — Completely refactored for zero-copy CDR:**

The C API has been redesigned around the same zero-copy CDR patterns as the
Rust API. The old `_new()` + `_set_*()` + `_serialize()` mutable-handle
pattern is replaced by:

- **CdrFixed types** — `ros_<type>_encode(buf, cap, &written, ...fields)` /
  `ros_<type>_decode(data, len, ...out_fields)` directly into caller buffers.
  No opaque handles, no heap allocation.
- **Buffer-backed types** — `ros_<type>_encode(...)` for construction,
  `ros_<type>_from_cdr(data, len)` for decoding into immutable view handles,
  `ros_<type>_get_*()` for O(1) field access.
- **Borrowed pointers** — String and byte-array getters now return `const`
  pointers into the handle's internal buffer. Do **not** `free()` them
  (the old API returned allocated copies that required `free()`).
- **`ros_bytes_free()`** — Encode output must be freed with `ros_bytes_free()`
  instead of `free()`.

See [`CAPI.md`](CAPI.md) for the complete C API reference, memory management
rules, error handling conventions, and working examples.

**Python API — No changes.** Python bindings continue to use pycdr2 for
CDR serialization. No migration required.

## [1.5.5] - 2026-02-17

### Fixed
- Release workflow race condition: build jobs now upload workflow artifacts
  and a final job publishes the release in one shot
- Changelog extraction in release workflow not matching version due to `v` prefix

## [1.5.4] - 2026-02-17

### Added
- pkg-config configuration file (`edgefirst-schemas.pc`) included in C API release packages
- SOVERSION symlinks in C API release packages (GNU/Linux `libedgefirst_schemas.so.1` convention)

### Fixed
- C API test builds failing due to missing SONAME symlink for runtime linker

## [1.5.3] - 2026-02-12

### Fixed
- **C API: Replaced bulk array getters with indexed accessors for opaque types**
  - `ros_point_cloud2_get_fields()` → `ros_point_cloud2_get_num_fields()` + `ros_point_cloud2_get_field_at()`
  - `edgefirst_detect_get_boxes()` → `edgefirst_detect_get_num_boxes()` + `edgefirst_detect_get_box_at()`
  - Returning `Vec::as_ptr()` for opaque Rust-layout structs (containing `String`) was unusable from C
    since forward-declared types have unknown `sizeof`, making `fields[i]` impossible to compile
- C API: Fixed out-of-bounds handling in `edgefirst_detect_get_box_at()` and
  `ros_point_cloud2_get_field_at()` — replaced `assert!` (which panics across FFI, causing UB)
  with safe `match .get(index)` returning `NULL`
- C API: Added 5 missing declarations to `schemas.h`:
  - `foxglove_point_annotations_get_num_outline_colors()`
  - `foxglove_point_annotations_get_outline_color_at()`
  - `foxglove_point_annotations_set_outline_colors()`
  - `foxglove_point_annotations_clear_outline_colors()`
  - `edgefirst_detect_clear_boxes()`
- Fixed `EdgeFirstDmaBuf` → `EdgeFirstDmaBuffer` type name in C example (`examples/c/example.c`)

## [1.5.2] - 2026-01-27

### Fixed
- C API errno handling now uses portable `errno` crate for Windows and macOS support
- Replaced platform-specific set_errno implementations with cross-platform solution

## [1.5.1] - 2026-01-26

### Fixed
- Fixed PyPI trusted publisher configuration for automated releases
- Removed redundant ROS workflow trigger on tag pushes
- Added cross-workflow Debian package publishing to GitHub Releases

## [1.5.0] - 2026-01-26

### Added

**C API Expansion:**
- Comprehensive C FFI bindings for all message types via `libedgefirst_schemas.so`
- New C header `include/edgefirst/schemas.h` with full Doxygen documentation
- Support for 60+ message types across all namespaces:
  - `builtin_interfaces`: Time, Duration
  - `std_msgs`: Header, ColorRGBA
  - `geometry_msgs`: Vector3, Point, Point32, Quaternion, Pose, Pose2D, PoseStamped, Transform, TransformStamped, Twist, TwistStamped, Accel, AccelStamped, Wrench, WrenchStamped, Inertia, InertiaStamped, Polygon, PolygonStamped, PoseArray, PoseWithCovariance, PoseWithCovarianceStamped, TwistWithCovariance, TwistWithCovarianceStamped, AccelWithCovariance, AccelWithCovarianceStamped
  - `sensor_msgs`: Image, PointCloud2, PointField, CompressedImage, CameraInfo, RegionOfInterest, Imu, NavSatFix, NavSatStatus, Range, Temperature, RelativeHumidity, Illuminance, FluidPressure, BatteryState, JointState, Joy, JoyFeedback, JoyFeedbackArray, TimeReference, LaserScan, MultiEchoLaserScan, LaserEcho, MagneticField
  - `nav_msgs`: Odometry, Path, OccupancyGrid, MapMetaData, GridCells
  - `rosgraph_msgs`: Log, Clock
  - `foxglove_msgs`: CompressedVideo
  - `edgefirst_msgs`: DmaBuf, RadarCube, RadarInfo, Mask, Model, ModelInfo, Detect, Box, Track
- Service message types: SetBool, Trigger, Empty
- Complete unit tests for C API using Criterion test framework

**Benchmark Suite:**
- Comprehensive CDR serialization benchmarks for all heavy message types
- On-target benchmark execution on NXP i.MX 8M Plus (Cortex-A53)
- SmartMicro DRVEGRD radar configurations:
  - DRVEGRD-169 (Corner Radar): Ultra-Short, Short, Medium, Long modes
  - DRVEGRD-171 (Front Radar): Short, Medium, Long modes
- Image benchmarks with RGB8, YUYV, NV12 encodings at VGA/HD/FHD
- Mask benchmarks at 320/640/1280 with 8 and 32 classes
- CompressedMask benchmarks with zstd compression
- DmaBuf zero-copy reference benchmarks (nanosecond scale)
- PointCloud2 benchmarks from sparse (1K) to very dense (131K points)

**Schema Registry:**
- Runtime schema name registry (`schema_registry.rs`, `registry.py`) for type lookup by ROS 2 schema name

**MCAP Validation:**
- Rust MCAP integration tests for real-world CDR validation against hardware recordings
- Python MCAP tests (`test_mcap.py`) and schema registry tests (`test_schema_registry.py`)

**Release Packaging:**
- Pre-built C API binary packages for Linux x86_64 and aarch64 attached to GitHub Releases
- Archives include shared library (.so), static library (.a), C header, README, and LICENSE

**CI/CD Improvements:**
- QuickChart integration for grouped bar charts in GitHub Actions Summary
- Criterion JSON parsing for reliable benchmark data extraction
- EnricoMi/publish-unit-test-result-action for C API test reporting
- SonarCloud integration with Rust and C coverage reporting via llvm-cov
- On-target ARM64 benchmark execution via self-hosted runner

### Changed
- Benchmark charts now auto-scale Y-axis to actual data
- DmaBuf benchmarks display in nanoseconds for precision
- All chart values show 2 decimal places for readability
- Updated `sonar-project.properties` to include C API sources and tests

### Fixed
- Criterion `slope: null` handling for benchmarks without linear fit
- UTF-8 encoding issues in Mermaid charts (switched to QuickChart)
- Stale benchmark cache causing legacy names to appear

## [1.4.1] - 2025-11-18

### Changed
- **All packages now licensed under Apache-2.0**: Updated Rust crate, Python package, and ROS2 package metadata to reflect Apache-2.0 license across all distributions
- SBOM generation is now 3x faster (17s → 5-6s), reducing build times for downstream users
- Updated dependency scanning for improved security and compliance

### Fixed
- Improved reliability of license policy enforcement

## [1.4.0] - 2025-11-17

### Added

**Version Management:**
- Implemented cargo-release for automated version management across Rust, Python, and ROS2
- Added `release.toml` with pre-release-replacements for version synchronization
- Created `.github/scripts/check_version_sync.sh` to verify version consistency
- Added dedicated `version-check.yml` GitHub Actions workflow to block PRs with version mismatches

**CI/CD Improvements:**
- Created automated GitHub Release workflow (`release.yml`)
- Fixed GitHub Actions workflow syntax errors (branches array format)
- Removed version manipulation from rust.yml and pypi.yml (now managed by cargo-release)
- Added automated CHANGELOG extraction for GitHub Releases

**Documentation:**
- Updated CONTRIBUTING.md with comprehensive cargo-release workflow documentation
- Added visual workflow diagram showing developer actions vs automated steps
- Clarified that GitHub Release is auto-created by tag push, not manually
- Added troubleshooting section for failed releases

**Package Updates:**
- Updated ROS2 package.xml to use real version instead of 0.0.0 placeholder
- Updated debian/rules to extract version from package.xml instead of git tags

### Changed
- Python version now sourced from `__init__.py` __version__ (industry standard)
- All three version sources (Cargo.toml, __init__.py, package.xml) now synchronized via cargo-release
- Release process now uses standard cargo-release steps: version, replace, commit, tag, push

### Fixed
- Version synchronization issues between Rust, Python, and ROS2 packages
- GitHub Actions workflow schema validation errors

## [1.3.1] - 2025-05-14

### Changed
- Optimized PointCloud2 decode to decode entire point in one call instead of one call per field
- Use named tuple for even faster decode
- Added decoding fields where count > 1

## [1.3.0] - 2025-05-12

### Added
- PointCloud2 parsing utilities moved from samples to schemas for better reuse
- Boxes field to mask in edgefirst_msgs.py

### Changed
- Migrated to pyproject.toml for Python packaging
- Removed Git hash from version for Python
- More explicit version string parsing for Python __init__.py

### Fixed
- Rust formatting fixes
- Python build module installation
- ModelInfo added to Python schemas
- LocalTime import issues
- Import error: cannot import name 'default_field'
- Added missing PCD fields to p.fields dictionary

## [1.2.11] - 2025-05-06

### Fixed
- Explicitly added edgefirst.schemas module to Python package

## [1.2.10] - 2025-05-04

### Changed
- Removed edgefirst/__init__.py to avoid issues with multiple packages using the edgefirst namespace

## [1.2.9] - 2025-05-03

### Fixed
- RadarInfo Duration initialization

## [1.2.8] - 2025-05-03

### Fixed
- RadarInfo and Model messages not using default_field for Header initialization

## [1.2.7] - 2025-04-30

### Added
- Boxed boolean to mask for instanced segmentation

### Fixed
- Comment about unused encoding
- Duration not using ROS Duration type

## [1.2.6] - 2025-03-27

### Changed
- Formatting and documentation updates

## [1.2.5] - 2025-03-27

### Added
- DmaBuffer message schema and Python API
- Python variants of all provided messages
- Float16 datatype support
- Additional datatypes for model_info
- ModelInfo to Rust schemas
- Custom Detect schema for Python
- CompressedImage schema for JPEG handling
- CompressedVideo schema
- CameraInfo and ImageAnnotation schema for Python parser
- Time conversion traits to u64

### Changed
- Renamed DetectBoxes2D to Detect
- Changed label type from String to u32, then back to String
- Moved Box and Track to separate files
- Corrected Detect to be a sequence of boxes
- Updated comments in detect schema
- Removed is_tracked field from Detect
- Re-organized Rust library to bring all name_msgs into base module

### Fixed
- Fixed edgefirst typo
- Moved detect.py to correct directory
- Updated setup.py to include all Python messages
- Python package long description using README.md
- Python build issues with wheel generation
- Removed auxiliary files from ROS2 schemas not required for this project

[Unreleased]: https://github.com/EdgeFirstAI/schemas/compare/v2.2.0...HEAD
[2.2.0]: https://github.com/EdgeFirstAI/schemas/compare/v2.1.0...v2.2.0
[2.1.0]: https://github.com/EdgeFirstAI/schemas/compare/v2.0.1...v2.1.0
[2.0.1]: https://github.com/EdgeFirstAI/schemas/compare/v2.0.0...v2.0.1
[2.0.0]: https://github.com/EdgeFirstAI/schemas/compare/v1.5.5...v2.0.0
[1.5.5]: https://github.com/EdgeFirstAI/schemas/compare/v1.5.4...v1.5.5
[1.5.4]: https://github.com/EdgeFirstAI/schemas/compare/v1.5.3...v1.5.4
[1.5.3]: https://github.com/EdgeFirstAI/schemas/compare/v1.5.2...v1.5.3
[1.5.2]: https://github.com/EdgeFirstAI/schemas/compare/v1.5.1...v1.5.2
[1.5.1]: https://github.com/EdgeFirstAI/schemas/compare/v1.5.0...v1.5.1
[1.5.0]: https://github.com/EdgeFirstAI/schemas/compare/v1.4.1...v1.5.0
[1.4.1]: https://github.com/EdgeFirstAI/schemas/compare/v1.4.0...v1.4.1
[1.4.0]: https://github.com/EdgeFirstAI/schemas/compare/v1.3.1...v1.4.0
[1.3.1]: https://github.com/EdgeFirstAI/schemas/compare/v1.3.0...v1.3.1
[1.3.0]: https://github.com/EdgeFirstAI/schemas/compare/v1.2.11...v1.3.0
[1.2.11]: https://github.com/EdgeFirstAI/schemas/compare/v1.2.10...v1.2.11
[1.2.10]: https://github.com/EdgeFirstAI/schemas/compare/v1.2.9...v1.2.10
[1.2.9]: https://github.com/EdgeFirstAI/schemas/compare/v1.2.8...v1.2.9
[1.2.8]: https://github.com/EdgeFirstAI/schemas/compare/v1.2.7...v1.2.8
[1.2.7]: https://github.com/EdgeFirstAI/schemas/compare/v1.2.6...v1.2.7
[1.2.6]: https://github.com/EdgeFirstAI/schemas/compare/v1.2.5...v1.2.6
[1.2.5]: https://github.com/EdgeFirstAI/schemas/releases/tag/v1.2.5
