Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
732cd89
cleanup: fix three correctness bugs in transport / E2E / server bind
JustinKovacich Apr 28, 2026
2d9238f
cleanup: honor close-semantic contracts on embassy + static-pool back…
JustinKovacich Apr 28, 2026
6a22fd2
cleanup: !Send Client construction via LocalSpawner + BindDispatch
JustinKovacich Apr 28, 2026
7c58649
cleanup: drop per-event allocations + Send bounds from server hot path
JustinKovacich Apr 28, 2026
76e4b21
cleanup: fix MockRecvFut busy-wake + MockTimer duration violations
JustinKovacich Apr 28, 2026
3f6e027
fix: phase 17 cleanup - docs, API alignment, alloc gating
JustinKovacich Apr 28, 2026
4c099ac
Fix tests so they run serially and don't flake.
JustinKovacich Apr 28, 2026
2d1c768
Fix waker being held during waker.wake when it didnt need to be
JustinKovacich Apr 28, 2026
850800c
Improve code coverage and remove dead code.
JustinKovacich Apr 28, 2026
1f2fd79
fix: address round-2 review comments on #95/#96
JustinKovacich Apr 28, 2026
8303b31
fix: address adversarial review for #95 (3 Crit + 12 High + 13 Med + …
JustinKovacich Apr 28, 2026
fe618cf
fix(examples): port workspace example mocks to GAT BindFuture/SleepFu…
JustinKovacich Apr 28, 2026
61c67f4
test: add regression tests for H3/H4/H5/H10/H12
JustinKovacich Apr 28, 2026
de31890
chore: remove accidentally-committed local config files
JustinKovacich Apr 28, 2026
dcb0f83
fix: address remaining Copilot inline comments on #95
JustinKovacich Apr 28, 2026
9aa2619
docs: correct assert_no_alloc semantics (abort, not panic)
JustinKovacich Apr 28, 2026
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
15 changes: 15 additions & 0 deletions .config/nextest.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,23 @@ leak-timeout = "1s"
filter = 'test(server::tests::) | binary(client_server)'
test-group = 'serial-sd-port'

# bare_metal_e2e tests share static channel pools declared via
# `define_static_channels!` — pool slots are not reclaimed until the
# process exits, so parallel tests exhaust the pools. Run serially.
[[profile.default.overrides]]
filter = 'binary(bare_metal_e2e)'
test-group = 'serial-static-pools'

# static_channels_alloc_witness tests share a counting global allocator
# and static channel pools. The internal MEASURE_LOCK serializes allocation
# measurement, but pool exhaustion still requires serial execution.
[[profile.default.overrides]]
filter = 'binary(static_channels_alloc_witness)'
test-group = 'serial-static-pools'

[test-groups]
serial-sd-port = { max-threads = 1 }
serial-static-pools = { max-threads = 1 }

[profile.default.junit] # Output the junit coverage for tools
path = "junit.xml"
14 changes: 14 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,20 @@ jobs:
with:
tool: cargo-llvm-cov, cargo-nextest
- run: cargo test --no-default-features
- name: Build matrix — partial feature subsets
run: |
cargo build --no-default-features --features bare_metal
cargo build --no-default-features --features embassy_channels
cargo build --no-default-features --features client
cargo build --no-default-features --features server
cargo build --no-default-features --features client,server
- name: Doc — partial feature subsets (catch unresolved intra-doc links)
env:
RUSTDOCFLAGS: -D warnings
run: |
cargo doc --no-deps --no-default-features --features client
cargo doc --no-deps --no-default-features --features server,bare_metal
cargo doc --no-deps --all-features
- name: No-alloc witness (explicit gate)
run: cargo test --features client,bare_metal --test no_alloc_witness
- run: cargo llvm-cov nextest --all-features --lcov --output-path ./target/lcov.info
Expand Down
23 changes: 15 additions & 8 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Changelog

## [Unreleased]
## [0.8.0]

### Added

Expand All @@ -9,10 +9,12 @@
- **`client::Error::Shutdown`** — new variant returned by every `Client` method when the control channel is closed (run-loop future was dropped, cancelled, or exited). Replaces the previous `.unwrap()`-on-closed-channel panic path.
- **`server::SubscribeError`** — new public enum (`SubscribersPerGroupFull`, `EventGroupsFull`) returned by `SubscriptionManager::subscribe` and `EventPublisher::register_subscriber` when a bounded capacity rejects a subscription. Re-exported from `server::mod`.
- **`Client::new_with_loopback(interface, multicast_loopback)`** — constructor that exposes the previously-internal `multicast_loopback` knob for same-host integration tests.
- **`Client::new_with_spawner_and_loopback(interface, multicast_loopback, spawner)`** — phase-9 executor-agnostic constructor that accepts a caller-supplied `Spawner` impl. Bare-metal callers swap `TokioSpawner` for their own task pool.
- **`Client::new_with_spawner_and_loopback(interface, multicast_loopback, spawner)`** — executor-agnostic constructor that accepts a caller-supplied `Spawner` impl. Bare-metal callers swap `TokioSpawner` for their own task pool.
- **`Client::new_with_deps_local`** — constructor for single-threaded / `!Send` executors. Accepts a `LocalSpawner` instead of `Spawner` and relaxes the `Send` bound on the transport socket.
- **`transport::Spawner` trait** (re-exported as `simple_someip::Spawner`) — executor-agnostic task-spawn abstraction. `tokio_transport::TokioSpawner` is the default `std + tokio` impl.
- **`transport::TransportSocket` / `TransportFactory` / `Timer` traits** — executor-agnostic UDP transport abstraction landed in phase 4 and finished out across phases 5–9. Default `tokio_transport::TokioTransport` / `TokioSocket` / `TokioTimer` impls available behind the `client` / `server` features.
- **`bare_metal` cargo feature** — pure marker, reserved for future no_std helpers. The real bare-metal canary is the `examples/bare_metal` workspace member, which depends on `simple-someip` with `default-features = false, features = ["bare_metal"]`. Validate with `cargo build -p bare_metal`, NOT `cargo build --workspace` (workspace builds may unify features and mask regressions).
- **`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).
- **`SubscriptionManager::subscribe` returning a `Result`** — see "Changed" below; the regression test list now exercises the major-version mismatch path explicitly.

### Changed
Expand All @@ -23,7 +25,12 @@
- **Breaking: `Client::reboot_flag(&self)` now returns `Result<protocol::sd::RebootFlag, Error>`** — previously returned the bare flag and could panic if the run-loop had exited. All other public `Client` methods migrated to the same `Err(Error::Shutdown)` policy in this release; `reboot_flag` is now consistent.
- **Breaking: `server::SubscriptionManager::subscribe` signature change** — now returns `Result<(), server::SubscribeError>` instead of `()`. Previously, capacity rejections were silently dropped with only a `warn!` log, which let the server emit a `SubscribeAck` for a subscription that had not been recorded. Callers must now handle the `Err` path (the server's own SD loop emits `SubscribeNack` on `Err`).
- **Breaking: `server::EventPublisher::register_subscriber` signature change** — now returns `Result<(), server::SubscribeError>` instead of `()`, surfacing the same capacity-rejection signal to externally managed subscription dispatchers.
- **Breaking: `Server::unicast_local_addr` return type changed** — previously returned `Result<std::net::SocketAddr, std::io::Error>`; now returns `Result<std::net::SocketAddr, server::Error>`. Callers that pattern-matched on `std::io::Error` must update to `server::Error::Transport(e)` and access the inner `TransportError` from there.
- **Breaking: default features changed `default = []` → `default = ["std"]`** — previously `embedded-io/std`, `thiserror/std`, and `tracing/std` were always-on; they are now gated behind the new `std` feature. Downstream consumers building with `default-features = false` who relied on the implicit `std` propagation must add `features = ["std"]` (or one of `client` / `server`, which both imply `std`).
- **Breaking: `Client::new` type signature now `Client::<M, R, I, C>::new`** — the `Client` struct gained three additional type parameters for the executor traits (`R: TransportFactory`, `I: InterfaceHandle`, `C: ChannelFactory`). The tokio-default convenience constructor is now gated behind the `client-tokio` feature (was `client`). Migration: add `features = ["client-tokio"]` to continue using `Client::new`; trait-surface consumers use `Client::new_with_deps`.
- **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.
- `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 @@ -32,18 +39,18 @@

- **`server::EventPublisher::publish_event` no longer silently sends UNPROTECTED payloads on E2E protect failure** — counter exhaustion / key-lookup races etc. now surface as `Err(Error::E2e(_))` rather than logging and falling through (which had been emitting an unprotected message claiming an E2E-protected channel).
- **SD `Subscribe` with mismatched `major_version` is now NACKed** — previously an Ack would be returned and the subscription registered, leaving the application stack to silently mis-decode incompatible-version traffic.
- **`SocketManager::send` no longer panics on a dropped response oneshot** — phase-9 user-supplied `Spawner` made this path reachable; failures now return `Err(Error::SocketClosedUnexpectedly)`.
- **`SocketManager::send` no longer panics on a dropped response oneshot** — user-supplied `Spawner` made this path reachable; failures now return `Err(Error::SocketClosedUnexpectedly)`.
- **`client::Inner` request-queue overflow no longer drops control messages silently** — full queue now invokes `reject_with_capacity("request_queue")` on the rejected message, so callers see a typed `Err(Error::Capacity("request_queue"))` instead of a `RecvError` mapped to `Error::Shutdown`.
- **Per-socket recv-error hot loop bounded** — `SocketManager`'s socket loop now closes after `MAX_CONSECUTIVE_RECV_ERRORS = 16` consecutive `recv_from` failures rather than spinning indefinitely on a permanently broken fd.
- **`Client::send` fails fast on oversize messages** — pre-encode size check returns `Err(Error::Capacity("udp_buffer"))` for messages whose `required_size()` exceeds `UDP_BUFFER_SIZE`. Mirrors the existing `EventPublisher::publish_event` capacity guard.

### Notes

- **Crate version bumped to 0.7.0** — reflects the breaking changes above. Downstream `Cargo.toml` snippets in `README.md` were updated accordingly.
- **Crate version bumped to 0.8.0** — reflects the breaking changes above. Downstream `Cargo.toml` snippets in `README.md` were updated accordingly.

### Known issues
### Test runner

- `tests/client_server.rs` integration tests share the SD multicast port (30490) via `SO_REUSEPORT` and rely on Linux's reuseport hashing for traffic delivery. Under cargo's default parallel test runner this produces cross-test Subscribe deliveries that flake ~half the tests. Run with `cargo test --test client_server -- --test-threads=1` until each test can be given its own SD port. The `cargo test --lib` unit-test suite is unaffected. (Pre-existing, called out here so consumers do not assume `cargo test --workspace` is green.)
- `tests/client_server.rs` integration tests share the SD multicast port (30490) via `SO_REUSEPORT` and rely on Linux's reuseport hashing for traffic delivery. Under cargo's default parallel test runner cross-test Subscribe deliveries flake. The crate's `.config/nextest.toml` serializes `client_server` via the `serial-sd-port` test-group, so `cargo nextest run` (used by CI) gives stable results. For the legacy harness, pass `--test-threads=1`: `cargo test --test client_server -- --test-threads=1`.


## [0.6.0](https://github.com/luminartech/simple_someip/compare/v0.5.3...v0.6.0) - 2026-04-20
Expand Down
2 changes: 1 addition & 1 deletion Cargo.lock

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

50 changes: 30 additions & 20 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ members = [

[package]
name = "simple-someip"
version = "0.7.0"
version = "0.8.0"
edition = "2024"
license = "MIT OR Apache-2.0"
description = "A lightweight SOME/IP serialization and communication library"
Expand Down Expand Up @@ -56,7 +56,7 @@ tracing-subscriber = "0.3"
[features]
default = ["std"]
std = ["embedded-io/std", "thiserror/std", "tracing/std"]
# Phase 13a split: `client` exposes the protocol/trait-surface client
# Feature split: `client` exposes the protocol/trait-surface client
# (no tokio, no socket2); `client-tokio` layers the tokio + socket2
# convenience defaults on top. Consumers of the bare-metal trait surface
# enable `client` only (and supply their own `Spawner` / `Timer` /
Expand All @@ -65,34 +65,36 @@ std = ["embedded-io/std", "thiserror/std", "tracing/std"]
# `TokioChannels` / `TokioTransport`) enable `client-tokio`.
client = ["std", "dep:futures"]
client-tokio = ["client", "dep:tokio", "dep:socket2"]
# Phase 14b split (matches phase 13a on 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>>`
# 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"]
# Marks a build as intended for bare-metal / no_std consumption.
# Currently a pure marker — enables no crate code on its own. Reserved
# for future phases to gate no_std-specific helper types.
# Activates embassy-sync as the channel backend, the `static_channels`
# module, `AtomicInterfaceHandle`, and `StaticE2EHandle`.
#
# **To demonstrate the bare-metal trait surface, use the
# `examples/bare_metal` workspace member directly:** `cargo run -p
# bare_metal`. That workspace member depends on `simple-someip` with
# `default-features = false, features = ["bare_metal"]`, so it
# exercises the actual bare-metal configuration.
# `examples/bare_metal_client` / `examples/bare_metal_server` workspace
# members directly:** `cargo build -p bare_metal_client`. Those workspace
# members depend on `simple-someip` with `default-features = false,
# features = ["bare_metal", "client"]` / `["bare_metal", "server"]`,
# so they exercise the actual bare-metal configuration.
#
# Enabling `bare_metal` on its own does NOT make the crate
# bare-metal-complete: the `client` and `server` feature paths still
# spawn per-socket I/O loops on `tokio::spawn`, and a fully tokio-free
# build additionally needs a user-provided `Spawner` impl (phase 9).
# `bare_metal` activates embassy-sync as the channel backend. The feature
# is a prerequisite for the Phase 11 channel-handle abstraction: with
# `bare_metal` enabled, `EmbassySyncChannels` is available as the
# `ChannelFactory` impl that does not depend on tokio.
# require a user-provided `Spawner` impl and `TransportFactory` impl.
# With `bare_metal` enabled, `static_channels` and `define_static_channels!`
# are available as the no-alloc `ChannelFactory` impl.
bare_metal = ["dep:embassy-sync"]
# Heap-backed embassy-sync channel backend (`EmbassySyncChannels`).
# Implies `bare_metal` and pulls in `alloc` for `Arc<Channel<...>>`.
# Useful for tests or early prototypes before sizing static pools.
embassy_channels = ["bare_metal"]

[[test]]
name = "client_server"
Expand All @@ -102,6 +104,10 @@ required-features = ["client-tokio", "server-tokio"]
name = "bare_metal_client"
required-features = ["client", "bare_metal"]

[[test]]
name = "bare_metal_client_local"
required-features = ["client", "bare_metal"]

[[test]]
name = "static_channels_alloc_witness"
required-features = ["client", "bare_metal"]
Expand All @@ -114,3 +120,7 @@ harness = false
[[test]]
name = "bare_metal_server"
required-features = ["server", "bare_metal"]

[[test]]
name = "bare_metal_e2e"
required-features = ["client", "server", "bare_metal"]
Loading
Loading