Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
60 changes: 60 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,66 @@ jobs:
- uses: Swatinem/rust-cache@v2
- uses: obi1kenobi/cargo-semver-checks-action@v2

no_std_target:
# Cross-build for a true no_std target (cortex-m4f, no allocator,
# no std). This is the literal phase-18 gate from
# `bare_metal_plan_v3.md`: phases 4–17 shipped the trait surface
# and no-alloc primitives, but until this job is green the crate
# cannot actually be consumed on cortex-m. Each combination here
# is a separate `cargo build` so a failure surfaces the specific
# feature combo that regressed.
#
# `client + bare_metal` is verified alloc-free (no `__rust_alloc`
# symbols in the rlib); `server + bare_metal` and the combined
# build pull `extern crate alloc` for `Arc<EventPublisher>` /
# `Arc<F::Socket>` and so do reference allocator symbols — that's
# documented in `lib.rs` and tracked for a future refactor.
name: no_std target build (thumbv7em-none-eabihf)
needs: check
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: dtolnay/rust-toolchain@stable
with:
targets: thumbv7em-none-eabihf
- uses: Swatinem/rust-cache@v2
- name: bare_metal alone
run: cargo build --target thumbv7em-none-eabihf --no-default-features --features bare_metal
- name: server + bare_metal
run: cargo build --target thumbv7em-none-eabihf --no-default-features --features server,bare_metal
- name: client + server + bare_metal
run: cargo build --target thumbv7em-none-eabihf --no-default-features --features client,server,bare_metal
# `client + bare_metal` runs LAST so the rlib in
# target/thumbv7em-none-eabihf/debug/ comes from this exact
# feature set when the alloc-symbol audit reads it.
- name: client + bare_metal
run: |
# Wipe the bare_metal-only artifact from earlier in this
# job so the audit step doesn't accidentally read it; then
# build fresh under client+bare_metal.
rm -f target/thumbv7em-none-eabihf/debug/libsimple_someip*.rlib
cargo build --target thumbv7em-none-eabihf --no-default-features --features client,bare_metal
- name: alloc-symbol audit (client + bare_metal must be alloc-free)
# If `client + bare_metal` ever starts pulling `__rust_alloc`,
# something inside the client engine has regressed onto an
# allocator-bound primitive. Fail loudly so it gets caught in
# the PR rather than discovered downstream. (`server` and
# `client+server` builds DO reference alloc symbols via
# `Arc<EventPublisher>` — documented; not gated here.)
run: |
rlib=$(find target/thumbv7em-none-eabihf -name 'libsimple_someip*.rlib' | head -1)
if [ -z "$rlib" ]; then
echo "::error::no simple_someip rlib found under target/thumbv7em-none-eabihf"
exit 1
fi
alloc_refs=$(nm -A "$rlib" 2>/dev/null | grep -c -E '__rust_alloc|__rg_alloc' || true)
echo "client+bare_metal alloc-symbol references: $alloc_refs"
if [ "$alloc_refs" -ne 0 ]; then
echo "::error::client+bare_metal must be alloc-free; found $alloc_refs alloc references."
nm -A "$rlib" 2>/dev/null | grep -E '__rust_alloc|__rg_alloc' || true
Comment on lines +106 to +110
exit 1
fi

test:
name: Build, Test & Coverage
needs: check
Expand Down
22 changes: 20 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,12 @@
- **`transport::Spawner` trait** (re-exported as `simple_someip::Spawner`) — executor-agnostic task-spawn abstraction. `tokio_transport::TokioSpawner` is the default `std + tokio` impl.
- **`transport::LocalSpawner` trait** — single-threaded task-spawn abstraction for `!Send` futures. Enables use on runtimes like `tokio::LocalSet` or embassy's single-threaded executor.
- **`transport::TransportSocket` / `TransportFactory` / `Timer` traits** — executor-agnostic UDP transport abstraction. Default `tokio_transport::TokioTransport` / `TokioSocket` / `TokioTimer` impls available behind the `client-tokio` / `server-tokio` features.
- **`bare_metal` cargo feature** — activates embassy-sync as the channel backend and enables the `static_channels` module, `AtomicInterfaceHandle`, and `StaticE2EHandle` types. The heap-backed `EmbassySyncChannels` factory is separately gated by the `embassy_channels` feature (which implies `bare_metal`). See `examples/bare_metal_client/` and `examples/bare_metal_server/` for runnable integration examples. Validate with `cargo build -p bare_metal_client` / `cargo build -p bare_metal_server`, NOT `cargo build --workspace` (workspace builds may unify features and mask regressions).
- **`bare_metal` cargo feature** — activates embassy-sync as the channel backend and enables the `static_channels` module, `AtomicInterfaceHandle`, `StaticE2EHandle`, and `StaticSubscriptionHandle` types. All four are pure `no_std` (no allocator required). The heap-backed `EmbassySyncChannels` factory is separately gated by the `embassy_channels` feature (which implies `bare_metal`). See `examples/bare_metal_client/` and `examples/bare_metal_server/` for runnable integration examples. Validate with `cargo build -p bare_metal_client` / `cargo build -p bare_metal_server`, NOT `cargo build --workspace` (workspace builds may unify features and mask regressions).
- **`SubscriptionManager::subscribe` returning a `Result`** — see "Changed" below; the regression test list now exercises the major-version mismatch path explicitly.
- **`StaticSubscriptionHandle` + `StaticSubscriptionStorage`** — no-alloc `SubscriptionHandle` impl backed by `&'static BlockingMutex<CriticalSectionRawMutex, RefCell<SubscriptionManager>>`. The bare-metal counterpart to `Arc<RwLock<SubscriptionManager>>`. `SubscriptionManager::new()` is now `const`, so the storage can live in a plain `static` (no `Box::leak`). Gated on `feature = "bare_metal"`, re-exported from `server::*`.
- **`server::Error::InvalidUsage(&'static str)`** — new variant for `Server` API misuse paths. Currently emitted with the tags `"passive_server_announcement_loop"`, `"announcement_loop_already_started"`, and `"passive_server_run"`. Replaces the previous `Error::Io(std::io::Error::new(InvalidInput, ..))` paths so these errors are reachable on no_std builds.
- **`E2ERegistryFull`** — new typed error returned by `E2ERegistry::register` (and propagated through `E2ERegistryHandle::register` / `Client::register_e2e` / `Server::register_e2e`) when the fixed-capacity registry is at its `E2E_REGISTRY_CAP` limit. Replacing an already-registered key still always succeeds.
- **`PayloadWireFormat::for_each_offered_endpoint` / `for_each_service_instance`** — visitor-pattern methods replacing the previous `Vec`-returning `offered_endpoints` / `service_instances`. Lets the `Client` run loop iterate SD entries without per-message heap allocation, which was the last bare-metal blocker on the receive path. The `Vec`-returning forms are preserved as `cfg(feature = "std")` convenience wrappers that delegate to the visitors, so std consumers keep the original ergonomic shape.

### Changed

Expand All @@ -31,7 +35,19 @@
- **Breaking: `Server::new` type signature now `Server::<R, S, F, Tm>::new`** — the `Server` struct gained type parameters for the pluggable backends. The tokio-default convenience constructor is now gated behind the `server-tokio` feature (was `server`). Migration: add `features = ["server-tokio"]` to continue using `Server::new`; trait-surface consumers use `Server::new_with_deps`.
- **Breaking: `SubscriptionHandle` trait redesigned** — the previous `get_subscribers(&self, …) -> impl Future<Output = Vec<Subscriber>>` method has been replaced with `for_each_subscriber(&self, …, f: FnMut)` visitor pattern. This allows `EventPublisher::publish_event` to copy subscriber addresses into a stack buffer (`heapless::Vec<_, 16>`) instead of allocating per-event. Implementors of custom `SubscriptionHandle` must migrate.
- **Breaking: `SubscriptionHandle` RPITIT futures no longer `+ Send`** — the `subscribe`, `unsubscribe`, and `for_each_subscriber` methods now return `impl Future<…>` without a `+ Send` bound. This enables single-threaded lock-free implementations on bare-metal targets, but means `SubscriptionHandle` trait objects cannot be held across `.await` points in multi-threaded executors. Direct usage with the default `Arc<RwLock<SubscriptionManager>>` is unaffected.
- New optional dependency `dep:futures` (default-features-off) for `futures::select!` + `FusedFuture` plumbing — pulled in transitively by both `client` and `server` features.
- **Breaking: `client` and `server` features no longer imply `std`** — previously `client = ["std", "dep:futures"]` and `server = ["std", "dep:futures"]`; now `client = ["dep:futures-util"]` and `server = ["dep:futures-util"]`. The `std` feature moved to `client-tokio` / `server-tokio`, which is where it belongs (the tokio backends genuinely require std). Bare-metal trait-surface consumers (`features = ["client", "bare_metal"]`) compile in pure no_std now. `server` still pulls `extern crate alloc` because `Server` holds `Arc<EventPublisher>` and `EventPublisher` holds `Arc<F::Socket>` — documented in `lib.rs`; refactor to `&'static` borrows is tracked for a future phase.
- **Breaking: optional dep `futures` replaced with `futures-util`** — direct dependency on `futures-util` with features `["async-await", "async-await-macro"]`. The `futures` umbrella crate's `select!` macro re-export is gated on its `std` feature, which transitively pulls `slab` / `memchr` / `futures-io` and breaks no_std cross-compiles. `futures-util` provides `select_biased!`, `pin_mut!`, and `FutureExt` under just `async-await(-macro)`.
- **Breaking: internal `select!` → `select_biased!`** — `Inner::run_future`, `socket_loop_future`, and `server::run` now poll their select arms top-first instead of pseudo-randomly. For these workloads the bias gives slightly better behavior (control messages, sends, and unicast recvs get priority over their lower-priority siblings) and there is no genuine starvation path because the higher-priority arms are sporadic. The change is observable only under contrived workloads where every arm is permanently ready simultaneously.
- **Breaking: `PayloadWireFormat::offered_endpoints` / `service_instances` replaced by visitor-pattern methods** — see `for_each_offered_endpoint` / `for_each_service_instance` in "Added" above. Implementors of custom `PayloadWireFormat` types must override the visitors instead of the `Vec`-returning forms. The `Vec`-returning forms remain as default-implemented `cfg(feature = "std")` convenience wrappers, so std callers' code keeps compiling unchanged.
- **Breaking: `PayloadWireFormat::new_subscription_sd_header` parameter type** — `client_ip` is now `core::net::Ipv4Addr` (was `std::net::Ipv4Addr`). The two are the same underlying type; the change unblocks no_std builds. Dropping the `#[cfg(feature = "std")]` gate on the method itself makes it reachable in pure no_std.
- **Breaking: `PayloadWireFormat::set_reboot_flag` no longer `cfg(feature = "std")`** — the method is now always available on the trait. Its default impl is still a no-op; downstream payload types that participate in SD reboot tracking must override it.
- **Breaking: `OfferedEndpoint` no longer `cfg(feature = "std")`** — type is always available; its `addr` field is `Option<core::net::SocketAddrV4>` (was `Option<std::net::SocketAddrV4>`). Same underlying type; allows no_std consumers to receive offered-endpoint visits.
- **Breaking: `server::Error::Io(std::io::Error)` now `cfg(feature = "std")`** — the variant is gated on `feature = "std"` because `std::io::Error` is itself std-only. No-std consumers receive transport failures via `Error::Transport(TransportError)` which carries the portable `IoErrorKind`.
- **Breaking: misuse paths on `Server::announcement_loop` / `Server::run` return `Error::InvalidUsage(...)`** — previously these returned `Error::Io(std::io::Error::new(InvalidInput, ..))` with a formatted message. The new variant is no_std-friendly and carries a machine-readable `&'static str` tag (`"passive_server_announcement_loop"`, `"announcement_loop_already_started"`, `"passive_server_run"`); the diagnostic moves to `tracing::warn!`.
- **Breaking: `server::SubscriptionManager::get_subscribers` now `cfg(feature = "std")`** — convenience accessor returning a heap `Vec<Subscriber>`. Production code paths use `for_each_subscriber` (visitor) since 0.8.0; this accessor remains for std consumers' tests and ad-hoc tooling. No_std consumers must use `for_each_subscriber`.
- **Breaking: `server::ServiceInfo` / `server::EventGroupInfo` now `cfg(feature = "std")`** — both types' `pub` fields hold `Vec<...>`. Bare-metal consumers don't construct these types today; if the use case emerges, a future port will switch to `heapless::Vec`. `Subscriber` is unaffected and stays no_std.
- **Breaking: `E2ERegistry` API change** — backing storage migrated from `std::collections::HashMap` to `heapless::index_map::FnvIndexMap` (cap = `E2E_REGISTRY_CAP = 32`, exposed). `E2ERegistry::register` now returns `Result<(), E2ERegistryFull>`; replacing an already-registered key always succeeds, adding a new key past the cap returns `Err`. `E2ERegistry::new()` is now `const`. The module is no longer `cfg(feature = "std")` — `E2ERegistry` works in pure no_std.
- **Breaking: `E2ERegistryHandle::register` trait method now returns `Result<(), E2ERegistryFull>`** — propagates the new typed overflow from `E2ERegistry::register` through every handle impl. Callers (`Client::register_e2e`, `Server::register_e2e`) lift the `Result` through to their public surface.
- `client::Error::Transport` adopts `#[error(transparent)]` Display delegation (the previous wrapping with `{:?}` debug-formatted the inner `TransportError`); user-facing error strings are now stable.
- Subscribe-NACK reason strings normalized to `snake_case` for log consistency: `wrong_service_id`, `wrong_instance_id`, `wrong_major_version`, `no_endpoint_in_options`, `subscribers_per_group_full`, `event_groups_full`. Wire format is unchanged (NACK is signalled by `TTL=0`).

Expand All @@ -47,6 +63,8 @@
### Notes

- **Crate version bumped to 0.8.0** — reflects the breaking changes above. Downstream `Cargo.toml` snippets in `README.md` were updated accordingly.
- **Bare-metal compile gate is now literal.** `cargo build --target thumbv7em-none-eabihf --no-default-features --features client,server,bare_metal` succeeds; `client + bare_metal` is verified alloc-free (zero `__rust_alloc` references in the resulting rlib). CI runs this matrix on every PR. The cortex-m4f target is the closest no_std proxy mainline Rust supports — the project's actual production target (Infineon AURIX TriCore) requires HighTec's commercial Rust distribution because mainline Rust + LLVM don't have a TriCore backend; a future phase will swap or layer in a TriCore CI runner once that infrastructure is in place. See `bare_metal_plan_v3.md`.
- **Known limitation: `server` feature pulls `extern crate alloc`.** `Server` holds `Arc<EventPublisher>` and `EventPublisher` holds `Arc<F::Socket>`; both require an allocator. Pure no_std-without-allocator consumers can use the `client` feature alone (alloc-free) but will need a global allocator for the server side. A refactor to `&'static` borrows is on the v3 phase 21+ backlog.

### Test runner

Expand Down
51 changes: 3 additions & 48 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading