Skip to content
Closed
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
35 changes: 34 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,46 @@

### Added

- **`client::Error::Capacity(&'static str)`** — new variant returned when a fixed-capacity internal structure is full (e.g. `"unicast_sockets"`, `"udp_buffer"`). Because `client::Error` is not `#[non_exhaustive]`, this is a breaking change for downstream crates that match the enum exhaustively.
- **`client::Error::Capacity(&'static str)`** — new variant returned when a fixed-capacity internal structure is full. Current tags: `"unicast_sockets"`, `"udp_buffer"`, `"pending_responses"`, `"request_queue"`. Because `client::Error` is not `#[non_exhaustive]`, this is a breaking change for downstream crates that match the enum exhaustively.
- **`client::Error::Transport(crate::transport::TransportError)`** — new variant surfacing failures from the pluggable transport backend (`#[from]`-converted, displays transparently). Same exhaustive-match caveat as above.
- **`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.
- **`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).
- **`SubscriptionManager::subscribe` returning a `Result`** — see "Changed" below; the regression test list now exercises the major-version mismatch path explicitly.

### Changed

- **Breaking: `Client::new(interface)` return shape** — previously returned `(Client, ClientUpdates)`; now returns `(Client, ClientUpdates, impl Future<Output = ()> + Send + 'static)`. The third element is the run-loop future, which the caller MUST drive (typically via `tokio::spawn`) for any `Client` method to make progress. Migration: change destructuring to a 3-tuple and spawn or otherwise actively poll the future.
- **Breaking: `Server::start_announcing` removed → `Server::announcement_loop`** — the new method returns `Result<impl Future<Output = ()> + Send + 'static, Error>` (annotated `#[must_use]`). Spawn the returned future to fire announcements; calling and dropping the future is a silent no-op.
- **Breaking: `Client::start_sd_announcements` renamed to `Client::sd_announcements_loop`** — same semantic shift as `announcement_loop`: returns an `impl Future` instead of spawning internally, so the caller drives execution.
- **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: 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`).
- 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`).

### Fixed

- **`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)`.
- **`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.

### Known issues

- `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.)


## [0.6.0](https://github.com/luminartech/simple_someip/compare/v0.5.3...v0.6.0) - 2026-04-20
Expand Down
23 changes: 14 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,17 +10,20 @@ The library supports both `std` and `no_std` environments, making it suitable fo

## Features

- **`no_std` compatible** — the `protocol`, `traits`, and `e2e` modules work without the standard library
- **`no_std` compatible** — `protocol`, `traits`, `transport`, and `e2e` modules work without the standard library
- **Service Discovery** — SD entry/option encoding and decoding via fixed-capacity `heapless` collections (no heap allocation)
- **End-to-End protection** — Profile 4 (CRC-32) and Profile 5 (CRC-16) with zero-allocation APIs
- **Executor-agnostic transport traits** — `TransportSocket`, `TransportFactory`, `Timer`, `Spawner` (default `tokio` impls behind feature gates)
- **Async client and server** — tokio-based, gated behind optional feature flags
- **`embedded-io`** traits for serialization — abstracts over `std::io::Read`/`Write`

## Modules

- `protocol` — Wire format layer: SOME/IP header, `MessageId`, `MessageType`, `ReturnCode`, SD entries/options
- `traits` — `WireFormat` and `PayloadWireFormat` traits for custom message types
- `transport` — Executor-agnostic UDP socket / factory / timer / spawner traits (no_std-compatible)
- `e2e` — End-to-End protection profiles (always available, no heap allocation)
- `tokio_transport` — Default `std + tokio` impls of the transport traits (requires `feature = "client"` or `feature = "server"`)
- `client` — High-level async tokio client (requires `feature = "client"`)
- `server` — Async tokio server with SD announcements and event publishing (requires `feature = "server"`)

Expand All @@ -31,19 +34,19 @@ Add to your `Cargo.toml`:
```toml
[dependencies]
# Default — includes std, thiserror, and tracing
simple-someip = "0.5"
simple-someip = "0.7"

# no_std only (protocol/E2E/traits, no heap allocation)
simple-someip = { version = "0.5", default-features = false }
# no_std only (protocol/transport/E2E/traits, no heap allocation)
simple-someip = { version = "0.7", default-features = false }

# Client only
simple-someip = { version = "0.5", features = ["client"] }
simple-someip = { version = "0.7", features = ["client"] }

# Server only
simple-someip = { version = "0.5", features = ["server"] }
simple-someip = { version = "0.7", features = ["server"] }

# Both client and server
simple-someip = { version = "0.5", features = ["client", "server"] }
simple-someip = { version = "0.7", features = ["client", "server"] }
```

### Feature flags
Expand All @@ -53,8 +56,9 @@ simple-someip = { version = "0.5", features = ["client", "server"] }
| `std` | **yes** | Enables `thiserror`, `tracing`, and `embedded-io/std` |
| `client` | no | Async tokio client; implies `std` + tokio + socket2 |
| `server` | no | Async tokio server; implies `std` + tokio + socket2 |
| `bare_metal` | no | Pure marker — reserved for future no_std helpers. The real bare-metal canary is the `examples/bare_metal` workspace member; verify it with `cargo build -p bare_metal` (NOT `cargo build --workspace`, which can unify features). |

By default the crate enables `std`. To use in a `no_std` environment (e.g., embedded targets), disable default features with `default-features = false`. In that mode only the `protocol`, `traits`, and `e2e` modules are available, and the crate compiles in `no_std` mode. Most applications only need one of `client` or `server`.
By default the crate enables `std`. To use in a `no_std` environment (e.g., embedded targets), disable default features with `default-features = false`. In that mode the `protocol`, `traits`, `transport`, and `e2e` modules are available; `client` / `server` (and their `tokio_transport` backend) are not. Most applications only need one of `client` or `server`.

## Quick Start

Expand Down Expand Up @@ -108,7 +112,8 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
let publisher = server.publisher();
let run_handle = tokio::spawn(async move { server.run().await });

// Publish events to subscribers...
// Publish events to subscribers, e.g.:
// publisher.publish_event(0x1234, 1, 0x01, &message).await?;

tokio::select! {
res = announce_handle => eprintln!("announcement loop exited unexpectedly: {res:?}"),
Expand Down
Loading