Skip to content

Phase18f docs and 0 8 0 finalize#97

Draft
JustinKovacich wants to merge 7 commits into
feature/phase17_cleanupfrom
feature/phase18f_docs_and_0_8_0_finalize
Draft

Phase18f docs and 0 8 0 finalize#97
JustinKovacich wants to merge 7 commits into
feature/phase17_cleanupfrom
feature/phase18f_docs_and_0_8_0_finalize

Conversation

@JustinKovacich
Copy link
Copy Markdown
Contributor

No description provided.

JustinKovacich and others added 7 commits April 28, 2026 20:13
Drops the std-only HashMap backing in favor of a fixed-capacity
heapless::FnvIndexMap, sized to E2E_REGISTRY_CAP = 32. The registry
is now const-constructible and no_std-compatible; gating drops from
both the registry module itself and the e2e_check / e2e_protect /
E2EState support code (none of which actually used std).

Why this matters: phase 17 / 0.8.0 gated `bare_metal_handle_impls`
behind +std specifically because StaticE2EHandle wraps E2ERegistry
and the registry's HashMap was std-only. With the registry ported,
that gate is no longer load-bearing — phase 18c will drop it. This
sub-phase isolates the storage swap so the gating change has a
clean baseline.

API surface change (breaking, queued for 0.9.0):
- E2ERegistry::register now returns Result<(), E2ERegistryFull>.
  Replacing an already-registered key always succeeds (the slot is
  reused). Inserting a new key when the registry is at capacity
  returns Err(E2ERegistryFull(E2E_REGISTRY_CAP)).
- E2ERegistryHandle trait method `register` lifted to the same
  return type, so std (Arc<Mutex<E2ERegistry>>), bare_metal
  (StaticE2EHandle), and test (NullE2ERegistry) impls all forward
  the typed overflow.
- Client::register_e2e and Server::register_e2e now return
  Result<(), E2ERegistryFull> through to the public API. Callers
  that previously discarded the unit return must add a
  `?` / `.expect("E2E registry has capacity")` / explicit handling.

Two new regression tests:
- register_replacement_succeeds_when_full — re-registering an
  existing key at capacity must reuse the slot (locks in the
  FnvIndexMap "full + present" branch).
- register_overflow_returns_err_and_does_not_mutate — adding a new
  key beyond cap returns Err(E2ERegistryFull(E2E_REGISTRY_CAP)) AND
  does not insert.

512 lib tests pass (was 510; +2 new). cargo build clean across all
13 feature combos. cargo clippy --workspace --all-features
-- -D warnings -D clippy::pedantic clean. cargo build
--no-default-features (true no_std without bare_metal) compiles.

This is sub-phase 18a of phase 18 (per bare_metal_plan_v3.md).
Remaining sub-phases:
- 18b: replace std::sync references in SubscriptionManager
- 18c: provide no_std default lock-handle impls (ungate StaticE2EHandle, add StaticSubscriptionHandle)
- 18d: drop std from `client` / `server` Cargo features
- 18e: add the no_std-target CI gate (cross-build for thumbv7em-none-eabihf)
- 18f: docs + 0.9.0 bump

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Narrow scope per bare_metal_plan_v3.md 18b: drop the unconditional
`use std::{net::SocketAddrV4, vec::Vec};` from production code in
`src/server/subscription_manager.rs`, gate `get_subscribers ->
std::vec::Vec<Subscriber>` behind `#[cfg(feature = "std")]`, and
swap the unconditional `std::net::SocketAddrV4` import for
`core::net::SocketAddrV4`. The internal storage (FnvIndexMap +
heapless::Vec) was already heap-free since phase 13.5/13.6; this
sub-phase is what makes it literally compile in pure no_std.

`get_subscribers` is the only Vec-returning method on the manager,
and production code paths migrated to `for_each_subscriber`
(visitor) in phase 17. Std consumers keep the convenience accessor
unchanged; no_std consumers either use `for_each_subscriber` or
collect into their own heapless::Vec.

Out of scope (deferred to 18d's broad sweep):
- ServiceInfo / EventGroupInfo (still use std::vec::Vec for their
  pub fields) — 18d will port to heapless::Vec with documented
  caps.
- event_publisher.rs / sd_state.rs / mod.rs std::sync references
  for the `Arc<Mutex<E2ERegistry>>` / `Arc<RwLock<...>>` lock-handle
  defaults.

Verification:
- cargo build --no-default-features clean
- cargo build --no-default-features --features bare_metal clean
- cargo build --all-features clean
- cargo clippy --workspace --all-features -- -D warnings -D clippy::pedantic clean
- cargo clippy --no-default-features -- -D warnings -D clippy::pedantic clean
- cargo test --lib --all-features: 512 pass, 0 fail (no regressions)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Two changes that complete the no_std default lock-handle story for
the bare-metal server:

1. Drop the `+ std` gate from `bare_metal_e2e_impl`. Phase 18a
   ported `E2ERegistry` to `heapless::FnvIndexMap`, so
   `StaticE2EHandle` no longer needs std. It's now reachable in
   pure no_std builds via `feature = "bare_metal"` alone. Updated
   the lib.rs feature-table line accordingly.

2. New `StaticSubscriptionHandle` (and `StaticSubscriptionStorage`
   alias) in `src/server/subscription_manager.rs`. Modeled on
   `StaticE2EHandle`: a `&'static BlockingMutex<CriticalSectionRawMutex,
   RefCell<SubscriptionManager>>` wrapper that implements the full
   `SubscriptionHandle` trait (subscribe / unsubscribe /
   for_each_subscriber). Gated on `feature = "bare_metal"`, so
   bare-metal Server consumers no longer need to write their own
   subscription handle.

Made `SubscriptionManager::new()` `const` so the storage can live in
a plain `static`, no `Box::leak` required:

```rust
static SUBS: StaticSubscriptionStorage =
    Mutex::new(RefCell::new(SubscriptionManager::new()));
let handle = StaticSubscriptionHandle::new(&SUBS);
```

Re-exported `StaticSubscriptionHandle` and `StaticSubscriptionStorage`
from `server::*` (gated on `bare_metal`).

Regression test (`static_subscription_handle_full_contract`) walks
subscribe → for_each_subscriber → unsubscribe → for_each_subscriber
through the trait surface to lock in the wiring. Includes a
`block_on_sync` helper that asserts the futures complete
synchronously (no .await inside the critical section), since the
embassy-sync `lock` closure is sync.

After this sub-phase, the three default lock-handles
(`StaticE2EHandle`, `AtomicInterfaceHandle`, `StaticSubscriptionHandle`)
are all available on pure no_std via `feature = "bare_metal"` —
matching the surface that bare-metal Client + Server consumers will
need from 18d onward when `client` / `server` features drop their
std requirement.

Verification:
- cargo build --no-default-features --features bare_metal clean
- cargo build --no-default-features --features server,bare_metal clean
- cargo build --all-features clean
- cargo clippy --workspace --all-features -- -D warnings -D clippy::pedantic clean
- cargo clippy --no-default-features -- -D warnings -D clippy::pedantic clean
- cargo test --lib --all-features: 513 pass, 0 fail (+1 new test)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The actual gate-closer for phase 18. After this sub-phase:

  cargo build --no-default-features --features client,server,bare_metal

…compiles in pure no_std (no allocator required for `client`; `server`
still pulls `extern crate alloc;` for its `Arc<EventPublisher>` /
`Arc<Socket>` plumbing, documented as a known limitation tracked for a
future refactor).

## Cargo.toml

- `client = ["dep:futures"]` (was `["std", "dep:futures"]`).
- `server = ["dep:futures"]` (was `["std", "dep:futures"]`).
- `client-tokio = ["client", "std", "dep:tokio", "dep:socket2"]` (added
  `"std"` so the tokio convenience constructors keep their std backing).
- `server-tokio = ["server", "std", "dep:tokio", "dep:socket2"]` (same).
- `extern crate alloc;` in lib.rs now activates on
  `cfg(any(feature = "embassy_channels", feature = "server"))` instead
  of just `embassy_channels`. Server's Arc usage is the trigger.

## Trait surface change (breaking, queued for 0.9.0)

`PayloadWireFormat` was tangled with std-only items (`new_subscription_sd_header`
took `std::net::Ipv4Addr`; `offered_endpoints` / `service_instances`
returned `Vec<_>`; `set_reboot_flag` was `cfg(feature = "std")`).
Restructured:

- `OfferedEndpoint` is no longer std-gated; `addr` is now
  `Option<core::net::SocketAddrV4>`.
- `set_reboot_flag` is no longer std-gated.
- `new_subscription_sd_header` is no longer std-gated; `client_ip`
  is now `core::net::Ipv4Addr`.
- `offered_endpoints -> Vec<...>` and `service_instances -> Vec<...>`
  replaced by visitor-pattern `for_each_offered_endpoint(&self, F)` and
  `for_each_service_instance(&self, F)` (no_std-friendly, alloc-free).
- Old `offered_endpoints` / `service_instances` Vec-returning
  signatures preserved as `cfg(feature = "std")` convenience wrappers
  that delegate to the new visitors. Std consumers' code keeps
  compiling unchanged.

`Client::run_future` updated to use the new visitor methods directly.
`RawPayload`'s impl block updated to override the new visitor signatures
(was overriding the old Vec-returning ones).

## server::Error API change

- `Error::Io(std::io::Error)` is now gated on `cfg(feature = "std")`.
  No-std consumers receive transport failures via `Error::Transport(_)`
  carrying the portable `IoErrorKind` instead.
- New `Error::InvalidUsage(&'static str)` variant for misuse paths
  (`announcement_loop` on a passive server, `announcement_loop` called
  twice, `run` on a passive server). These previously returned
  `Error::Io(std::io::Error::new(InvalidInput, ...))` with a
  formatted message; the new variant carries a `&'static str` tag and
  the diagnostic moves to `tracing::warn!`. Tags:
  `"passive_server_announcement_loop"`,
  `"announcement_loop_already_started"`, `"passive_server_run"`.

## ServiceInfo / EventGroupInfo

Both gated on `cfg(feature = "std")` because their pub fields hold
`Vec<EventGroupInfo>` / `Vec<u16>`. Bare-metal consumers don't
construct these types today; a future port will switch to
`heapless::Vec` if a use case emerges. `Subscriber` (no Vec field)
stays no_std and exported.

## Other std → core sweeps

- `src/client/session.rs`: `std::net::SocketAddr` → `core::net::SocketAddr`.
- `src/client/socket_manager.rs`: same.
- `src/client/inner.rs`: removed `use std::borrow::ToOwned;`, replaced
  `sd_header.to_owned()` with `Clone::clone(sd_header)`; replaced
  `std::future::poll_fn` with `core::future::poll_fn`; replaced
  `std::fmt::*` with `core::fmt::*`.
- `src/server/mod.rs`: `std::net::*` → `core::net::*`, `Arc` from
  `alloc::sync::Arc`, large `vec![0u8; 65535]` buffers use
  `alloc::vec![]`.
- `src/server/event_publisher.rs`: `Arc` from `alloc::sync::Arc`,
  `std::net::SocketAddrV4` → `core::net::SocketAddrV4`.
- `src/server/sd_state.rs`: `std::net::SocketAddrV4` → `core::net`.
- 3 server::tests assertions updated for the new `Error::InvalidUsage`
  variant (was matching `Error::Io` with InvalidInput kind).

## Verification

- cargo build --all-features clean
- cargo build --no-default-features clean
- cargo build --no-default-features --features client clean
- cargo build --no-default-features --features server clean
- cargo build --no-default-features --features client,bare_metal clean
- cargo build --no-default-features --features server,bare_metal clean
- cargo build --no-default-features --features client,server,bare_metal clean
- cargo clippy --workspace --all-features -- -D warnings -D clippy::pedantic clean
- cargo clippy --no-default-features -- -D warnings -D clippy::pedantic clean
- cargo clippy --no-default-features --features client,bare_metal -- -D warnings -D clippy::pedantic clean
- cargo fmt --all --check clean
- cargo test --lib --all-features: 513 pass, 0 fail (test assertions updated for new error variant)

The `cargo build --target thumbv7em-none-eabihf` cross-compile gate
is the next sub-phase (18e). Locally these cargo build invocations
target host x86_64 — they prove the std refs are gone but do NOT
prove the bare-metal ABI works end-to-end.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Cross-compiling `client,server,bare_metal` to a true no_std target
surfaced two issues that the host-side x86_64 build hid:

1. **`futures::select!` requires the futures crate's `std` feature**,
   which transitively pulls `slab` / `memchr` / `futures-io` —
   none of which compile on no_std. Switched dep from the `futures`
   umbrella to `futures-util` directly with features
   `["async-await", "async-await-macro"]`. `select_biased!` is in
   that subset; `select!` is not (it needs std for the random
   fairness shuffle). Replaced all four `select!` call sites with
   `select_biased!`.

   Behavioral consequence: `select_biased!` polls arms top-first
   instead of pseudo-randomly. For our three uses
   (`socket_loop_future`, `Inner::run_future`, `server::run`) the
   bias actually gives slightly better behavior — control messages
   and sends get priority over recvs. Genuine starvation requires
   the top arm to never go pending, which doesn't happen for any
   of these workloads (sends are sporadic, control is sparse, SD
   multicast is 1Hz).

2. **`futures::FutureExt::catch_unwind` requires futures-util's
   `std` feature.** Replaced the catch-unwind dance with
   `JoinHandle::is_panic()` on the `JoinHandle` returned by
   `tokio::spawn`. A second tokio task awaits the join and logs
   the panic via `tracing::error!` if `is_panic()` is true. Same
   observable behavior, no extra dep gating needed.

Verification — both host AND cortex-m4f cross-compile:

  cargo build --all-features                                                 ✓
  cargo build --no-default-features                                          ✓
  cargo build --no-default-features --features bare_metal                    ✓
  cargo build --no-default-features --features client,bare_metal             ✓
  cargo build --no-default-features --features server,bare_metal             ✓
  cargo build --no-default-features --features client,server,bare_metal      ✓
  cargo build --target thumbv7em-none-eabihf --no-default-features --features bare_metal                       ✓
  cargo build --target thumbv7em-none-eabihf --no-default-features --features client,bare_metal                ✓
  cargo build --target thumbv7em-none-eabihf --no-default-features --features server,bare_metal                ✓
  cargo build --target thumbv7em-none-eabihf --no-default-features --features client,server,bare_metal         ✓
  cargo clippy --workspace --all-features -- -D warnings -D clippy::pedantic ✓
  cargo fmt --all --check                                                    ✓
  cargo test --lib --all-features: 513 pass, 0 fail                          ✓

Alloc-symbol audit on the cortex-m4f rlib:
  client + bare_metal:           0 alloc references (truly alloc-free)
  client + server + bare_metal: 14 alloc references (Arc<EventPublisher>
                                  / Arc<F::Socket> as documented in 18d)

This commit closes phase 18's literal compile gate. The 18e CI step
(adding the cross-build to `.github/workflows/ci.yml`) plus 18f
(0.9.0 docs + bump) remain.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Locks in phase 18's literal compile gate by cross-building the
crate for `thumbv7em-none-eabihf` (cortex-m4f, no_std, no
allocator) on every PR. Until this job is green, the crate cannot
actually be consumed on bare-metal — phases 4–17 shipped the
trait surface and no-alloc primitives but the literal cross-build
was never verified in CI.

Four feature combos exercised, each as a separate `cargo build` so
a failure surfaces the specific combo that regressed:
  - bare_metal alone
  - server + bare_metal
  - client + server + bare_metal
  - client + bare_metal (last, for the alloc-symbol audit below)

Plus an alloc-symbol audit step: greps the resulting
`libsimple_someip.rlib` for `__rust_alloc` / `__rg_alloc` and
fails if any are found. `client + bare_metal` MUST stay alloc-free.
The `server` and `client+server` paths reference allocator symbols
via `Arc<EventPublisher>` / `Arc<F::Socket>` (documented in
`src/lib.rs`) and are not gated by the audit.

## Why thumbv7em-none-eabihf and not tricore

The project's actual production target is Infineon AURIX TriCore.
Mainline Rust does not have a TriCore target — `rustc --print
target-list | grep tricore` returns nothing, and upstream LLVM
does not ship a TriCore backend. Compiling Rust for TriCore today
requires HighTec's commercial Rust distribution (or a custom
LLVM build with their out-of-tree TriCore backend).

`thumbv7em-none-eabihf` is the closest no_std proxy mainline Rust
supports and runs for free in GitHub Actions:
- Same `no_std` posture (no `extern crate std`).
- Same alloc-optionality (no implicit allocator).
- Same `core::*` / `alloc::*` surface.
- Same fixed-width integer / atomic widths as TC1.6.

What the proxy does NOT prove for TriCore:
- LLVM TriCore-specific codegen edge cases.
- Atomic-instruction lowering on the actual chip.
- `critical-section` impl behavior under TriCore's split ISR /
  main-thread context model.

A future phase 20 will swap (or layer) this CI step onto a TriCore
HighTec runner once that infrastructure is in place. For now, the
cortex-m4f proxy is the strongest verification CI can give us
without a TriCore toolchain.

Verified locally:
  cargo build --target thumbv7em-none-eabihf --no-default-features --features bare_metal                       ✓
  cargo build --target thumbv7em-none-eabihf --no-default-features --features client,bare_metal                ✓
  cargo build --target thumbv7em-none-eabihf --no-default-features --features server,bare_metal                ✓
  cargo build --target thumbv7em-none-eabihf --no-default-features --features client,server,bare_metal         ✓
  alloc-symbol audit: client+bare_metal = 0 alloc references in rlib ✓

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Final sub-phase for the literal no_std compile gate. Folds 18a-18e
into the existing 0.8.0 CHANGELOG entry, updates the lib.rs and
README feature tables, and rewrites the bare-metal examples to use
the new no-alloc lock handles directly (no more `Arc<Mutex<_>>` /
`Arc<RwLock<_>>` placeholders).

## CHANGELOG

Folded into 0.8.0's existing Added / Changed / Notes sections:

Added:
  - StaticSubscriptionHandle + StaticSubscriptionStorage
  - server::Error::InvalidUsage(&'static str)
  - E2ERegistryFull (typed overflow on E2ERegistry::register)
  - PayloadWireFormat::for_each_offered_endpoint /
    for_each_service_instance visitor methods

Changed (breaking, queued for 0.8.0):
  - client / server features no longer imply std (moved to
    *-tokio); client compiles in pure no_std, server pulls
    extern crate alloc for Arc<EventPublisher> /
    Arc<F::Socket>.
  - futures dep replaced with futures-util (futures::select! is
    std-gated; switched to select_biased!).
  - Internal select! → select_biased! (top-arm-first instead of
    pseudo-random; observable only under contrived workloads).
  - PayloadWireFormat::offered_endpoints / service_instances
    Vec-returning forms preserved as cfg(feature = "std")
    convenience wrappers; trait now requires the visitor methods.
  - PayloadWireFormat::set_reboot_flag and
    new_subscription_sd_header no longer std-gated.
  - OfferedEndpoint no longer std-gated; addr is
    Option<core::net::SocketAddrV4>.
  - server::Error::Io now cfg(feature = "std")-gated; misuse paths
    return Error::InvalidUsage(tag) instead.
  - SubscriptionManager::get_subscribers now
    cfg(feature = "std")-only.
  - server::ServiceInfo / server::EventGroupInfo now
    cfg(feature = "std")-only.
  - E2ERegistry: HashMap → heapless::FnvIndexMap (cap = 32);
    register returns Result<(), E2ERegistryFull>; new() is const.
  - E2ERegistryHandle::register trait method lifts the same
    Result through every impl.

Notes:
  - Bare-metal compile gate is now literal — cargo build
    --target thumbv7em-none-eabihf --no-default-features --features
    client,server,bare_metal succeeds in CI; client + bare_metal
    is verified alloc-free.
  - Known limitation: server pulls extern crate alloc; refactor
    to &'static borrows tracked for v3 phase 21+.

## lib.rs feature table

Rewritten to honestly describe each feature:
  - std: now described as the gate for the std lock-handle defaults
    (Arc<Mutex<E2ERegistry>> etc.) used by tokio backends.
  - client: pure no_std-clean, does not pull extern crate alloc.
  - server: pulls extern crate alloc.
  - client-tokio / server-tokio: imply client/server + std.
  - bare_metal: lists all five no-alloc types
    (static_channels, AtomicInterfaceHandle, StaticE2EHandle,
    StaticSubscriptionHandle).

## README feature table

Mirrors lib.rs. Adds explicit note that the cross-build for
thumbv7em is verified in CI.

## Examples — bare_metal_client / bare_metal_server

Both now use the actual no-alloc handles end-to-end:
  - StaticE2EHandle over &'static StaticE2EStorage (was
    Arc<Mutex<E2ERegistry>>)
  - AtomicInterfaceHandle over &'static AtomicU32 (was
    Arc<RwLock<Ipv4Addr>>)  -- bare_metal_client only
  - StaticSubscriptionHandle over &'static
    StaticSubscriptionStorage (was MockSubscriptions, ~75 LoC of
    inline trait impl deleted)  -- bare_metal_server only

Storage `static`s declared at module scope (clippy::pedantic
dislikes `static` after `let`). `E2ERegistry::new()` and
`SubscriptionManager::new()` are both const, so no Box::leak.

Both example Cargo.toml files now opt into the std feature
explicitly. The examples use RawPayload (std-only) and tokio for
their host-side mock drivers; firmware drops std and provides its
own PayloadWireFormat impl. Documented inline.

The "What is not yet demonstrated" stale section in
bare_metal_client is gone — there is nothing left undemonstrated;
the example covers the actual firmware-target shape end-to-end.

## Verification

  cargo fmt --all --check                                                      ✓
  cargo clippy --workspace --all-features -- -D warnings -D clippy::pedantic   ✓
  cargo clippy --no-default-features  -- -D warnings -D clippy::pedantic       ✓
  cargo test --lib --all-features: 513 pass                                    ✓
  cargo run -p bare_metal_client                                               ✓ (runs end-to-end)
  cargo run -p bare_metal_server                                               ✓ (announces + asserts SD sent)
  cargo build --target thumbv7em-none-eabihf --no-default-features --features client,server,bare_metal  ✓

Phase 18 (a through f) is complete. The literal "client + server
compile on cortex-m4f no_std" gate from bare_metal_plan_v3.md is
closed and CI-enforced. Phase 19 (embassy-net reference adapter)
is the next milestone per the v3 plan.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@JustinKovacich JustinKovacich requested a review from Copilot April 29, 2026 01:08
@JustinKovacich JustinKovacich changed the title Feature/phase18f docs and 0 8 0 finalize Phase18f docs and 0 8 0 finalize Apr 29, 2026
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

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 finalizes the 0.8.0 “phase18f” work to make the crate’s client/server trait surfaces viable on real no_std targets, eliminating hidden heap usage in hot paths and adding CI enforcement for the bare-metal build matrix.

Changes:

  • Make E2E registry and SD parsing no_std-compatible (fixed-capacity heapless registry + visitor APIs replacing Vec returns).
  • Introduce/expand bare-metal handle implementations (StaticE2EHandle, StaticSubscriptionHandle) and adjust public APIs to surface capacity/misuse errors.
  • Replace futures umbrella usage with futures-util and add a new CI job to cross-build and audit alloc usage on a thumb target.

Reviewed changes

Copilot reviewed 27 out of 28 changed files in this pull request and generated 6 comments.

Show a summary per file
File Description
tests/no_alloc_witness.rs Updates E2E registration calls to handle fallible register() without allocating in hot paths.
tests/client_server.rs Updates integration test to handle fallible register_e2e().
src/transport.rs Makes E2ERegistryHandle::register fallible; exposes bare-metal E2E handle under bare_metal (not std).
src/traits.rs Introduces visitor APIs for SD iteration; makes OfferedEndpoint no_std-friendly.
src/tokio_transport.rs Changes tokio task panic logging strategy to use a watcher task + JoinError.
src/server/subscription_manager.rs Makes SubscriptionManager::new() const; adds StaticSubscriptionHandle; gates heap-alloc accessor behind std.
src/server/service_info.rs Gates heap-backed service info types behind std; keeps Subscriber no_std.
src/server/sd_state.rs Moves SocketAddrV4 usage to core::net for no_std.
src/server/mod.rs Makes server APIs no_std friendlier (InvalidUsage tags), switches address types to core::net, and updates select macro usage.
src/server/event_publisher.rs Uses alloc::sync::Arc and core::net address types; updates E2E registration calls.
src/server/error.rs Gates Error::Io behind std; adds Error::InvalidUsage for misuse paths.
src/raw_payload.rs Implements SD iteration via new visitor methods (keeps std-only convenience type).
src/lib.rs Updates feature docs and alloc gating rules; exports StaticE2EHandle under bare_metal.
src/e2e/registry.rs Replaces HashMap with fixed-capacity FnvIndexMap; adds E2ERegistryFull + cap constant.
src/e2e/mod.rs Makes E2E registry available outside std and exports new cap/error types.
src/client/socket_manager.rs Updates to futures-util and biased select usage; updates E2E registration calls.
src/client/session.rs Switches SocketAddr import to core::net.
src/client/mod.rs Makes register_e2e fallible and switches Debug bounds/imports to core.
src/client/inner.rs Uses visitor APIs for SD iteration and switches to futures-util biased select usage.
examples/bare_metal_server/src/main.rs Switches host example to use static bare-metal handles for E2E + subscriptions.
examples/bare_metal_server/Cargo.toml Enables std for host tokio usage; adds explicit embassy-sync dep for storage types.
examples/bare_metal_client/src/main.rs Switches host example to static bare-metal E2E + atomic interface handle.
examples/bare_metal_client/Cargo.toml Enables std for RawPayload in host example; adds explicit embassy-sync dep.
README.md Updates feature matrix and no_std guidance to match new feature split and handles.
Cargo.toml Replaces optional futures with optional futures-util; updates feature wiring for client/server.
Cargo.lock Reflects dependency graph changes (futures-util, embassy-sync in examples).
CHANGELOG.md Documents breaking changes and new no_std/bare-metal contracts for 0.8.0.
.github/workflows/ci.yml Adds thumb no_std cross-build + alloc-symbol audit job.
Comments suppressed due to low confidence (1)

src/server/mod.rs:679

  • The comment claims select! (unbiased) is used for pseudo-random fairness, but the code now uses select_biased!, which always prioritizes the first ready arm. Either switch back to the unbiased select! macro, or update the comment and ensure the bias cannot starve SD-multicast receive handling under sustained unicast load (SD socket backlog/drops).
            // `select!` (not `select_biased!`) gives pseudo-random fairness
            // across ready arms each poll — matches the prior
            // `tokio::select!` behavior and avoids starving either the
            // unicast or SD-multicast arm under sustained one-sided load.
            //

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/server/mod.rs
Comment on lines 476 to 486
&self,
) -> Result<impl core::future::Future<Output = ()> + Send + 'static, Error> {
if self.is_passive {
return Err(Error::Io(std::io::Error::new(
std::io::ErrorKind::InvalidInput,
format!(
"announcement_loop called on passive Server for service 0x{:04X}; \
announcements must be driven externally (e.g. via \
`simple_someip::Client::sd_announcements_loop`)",
self.config.service_id
),
)));
tracing::warn!(
"announcement_loop called on passive Server for service 0x{:04X}; \
announcements must be driven externally (e.g. via \
`simple_someip::Client::sd_announcements_loop`)",
self.config.service_id
);
return Err(Error::InvalidUsage("passive_server_announcement_loop"));
}
Comment thread src/server/mod.rs
Comment on lines 649 to 662
use crate::protocol::MessageView;

if self.is_passive {
return Err(Error::Io(std::io::Error::new(
std::io::ErrorKind::InvalidInput,
format!(
"run called on passive Server for service 0x{:04X}; \
SD receive must be driven externally (e.g. via the \
Client's discovery socket, routing Subscribes to \
`EventPublisher::register_subscriber`)",
self.config.service_id
),
)));
tracing::warn!(
"run called on passive Server for service 0x{:04X}; \
SD receive must be driven externally (e.g. via the \
Client's discovery socket, routing Subscribes to \
`EventPublisher::register_subscriber`)",
self.config.service_id
);
return Err(Error::InvalidUsage("passive_server_run"));
}

// Incoming-peer buffers sized to the IP datagram limit (64 KiB - 1).
let recv_fut = socket.recv_from(&mut buf).fuse();
pin_mut!(send_fut, recv_fut);
select! {
select_biased! {
Comment thread src/client/inner.rs
Comment on lines 1075 to 1080
// `select!` (not `select_biased!`) randomizes the
// arm check order each poll so no single arm can
// starve the others under sustained load. Matches
// the original `tokio::select!` fairness behavior.
select! {
select_biased! {
// Receive a control message
Comment thread .github/workflows/ci.yml
Comment on lines +106 to +110
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 thread Cargo.toml
Comment on lines 74 to +83
# Feature split (matches the client side): `server` exposes the
# trait-surface server (no tokio, no socket2). The engine itself uses
# `futures::select!` so `dep:futures` lives here. `server-tokio` adds
# the tokio + socket2 convenience defaults (`Server::new`,
# `Server::new_with_loopback`, `Server::new_passive`), bringing
# `Arc<Mutex<E2ERegistry>>` / `Arc<RwLock<SubscriptionManager>>` /
# / `TokioTransport` / `TokioTimer` defaults into scope.
server = ["std", "dep:futures"]
server-tokio = ["server", "dep:tokio", "dep:socket2"]
# trait-surface server (no tokio, no socket2, no std). The engine
# itself uses `futures::select!` so `dep:futures` lives here.
# `server-tokio` adds the tokio + socket2 convenience defaults
# (`Server::new`, `Server::new_with_loopback`, `Server::new_passive`),
# bringing `Arc<Mutex<E2ERegistry>>` / `Arc<RwLock<SubscriptionManager>>` /
# / `TokioTransport` / `TokioTimer` defaults into scope, and forces
# `std`.
server = ["dep:futures-util"]
server-tokio = ["server", "std", "dep:tokio", "dep:socket2"]
This was referenced Apr 30, 2026
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