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". Becauseclient::Erroris 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 everyClientmethod 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 bySubscriptionManager::subscribeandEventPublisher::register_subscriberwhen a bounded capacity rejects a subscription. Re-exported fromserver::mod.Client::new_with_loopback(interface, multicast_loopback)— constructor that exposes the previously-internalmulticast_loopbackknob for same-host integration tests.Client::new_with_spawner_and_loopback(interface, multicast_loopback, spawner)— executor-agnostic constructor that accepts a caller-suppliedSpawnerimpl. Bare-metal callers swapTokioSpawnerfor their own task pool.Client::new_with_deps_local— constructor for single-threaded /!Sendexecutors. Accepts aLocalSpawnerinstead ofSpawnerand relaxes theSendbound on the transport socket.transport::Spawnertrait (re-exported assimple_someip::Spawner) — executor-agnostic task-spawn abstraction.tokio_transport::TokioSpawneris the defaultstd + tokioimpl.transport::LocalSpawnertrait — single-threaded task-spawn abstraction for!Sendfutures. Enables use on runtimes liketokio::LocalSetor embassy's single-threaded executor.transport::TransportSocket/TransportFactory/Timertraits — executor-agnostic UDP transport abstraction. Defaulttokio_transport::TokioTransport/TokioSocket/TokioTimerimpls available behind theclient-tokio/server-tokiofeatures.bare_metalcargo feature — activates embassy-sync as the channel backend and enables thestatic_channelsmodule,AtomicInterfaceHandle,StaticE2EHandle, andStaticSubscriptionHandletypes. All four are pureno_std(no allocator required). The heap-backedEmbassySyncChannelsfactory is separately gated by theembassy_channelsfeature (which impliesbare_metal). Seeexamples/bare_metal_client/andexamples/bare_metal_server/for runnable integration examples. Validate withcargo build -p bare_metal_client/cargo build -p bare_metal_server, NOTcargo build --workspace(workspace builds may unify features and mask regressions).SubscriptionManager::subscribereturning aResult— see "Changed" below; the regression test list now exercises the major-version mismatch path explicitly.StaticSubscriptionHandle+StaticSubscriptionStorage— no-allocSubscriptionHandleimpl backed by&'static BlockingMutex<CriticalSectionRawMutex, RefCell<SubscriptionManager>>. The bare-metal counterpart toArc<RwLock<SubscriptionManager>>.SubscriptionManager::new()is nowconst, so the storage can live in a plainstatic(noBox::leak). Gated onfeature = "bare_metal", re-exported fromserver::*.server::Error::InvalidUsage(&'static str)— new variant forServerAPI misuse paths. Currently emitted with the tags"passive_server_announcement_loop","announcement_loop_already_started", and"passive_server_run". Replaces the previousError::Io(std::io::Error::new(InvalidInput, ..))paths so these errors are reachable on no_std builds.E2ERegistryFull— new typed error returned byE2ERegistry::register(and propagated throughE2ERegistryHandle::register/Client::register_e2e/Server::register_e2e) when the fixed-capacity registry is at itsE2E_REGISTRY_CAPlimit. Replacing an already-registered key still always succeeds.PayloadWireFormat::for_each_offered_endpoint/for_each_service_instance— visitor-pattern methods replacing the previousVec-returningoffered_endpoints/service_instances. Lets theClientrun loop iterate SD entries without per-message heap allocation, which was the last bare-metal blocker on the receive path. TheVec-returning forms are preserved ascfg(feature = "std")convenience wrappers that delegate to the visitors, so std consumers keep the original ergonomic shape.
- 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 viatokio::spawn) for anyClientmethod to make progress. Migration: change destructuring to a 3-tuple and spawn or otherwise actively poll the future. - Breaking:
Server::start_announcingremoved →Server::announcement_loop— the new method returnsResult<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_announcementsrenamed toClient::sd_announcements_loop— same semantic shift asannouncement_loop: returns animpl Futureinstead of spawning internally, so the caller drives execution. - Breaking:
Client::reboot_flag(&self)now returnsResult<protocol::sd::RebootFlag, Error>— previously returned the bare flag and could panic if the run-loop had exited. All other publicClientmethods migrated to the sameErr(Error::Shutdown)policy in this release;reboot_flagis now consistent. - Breaking:
server::SubscriptionManager::subscribesignature change — now returnsResult<(), server::SubscribeError>instead of(). Previously, capacity rejections were silently dropped with only awarn!log, which let the server emit aSubscribeAckfor a subscription that had not been recorded. Callers must now handle theErrpath (the server's own SD loop emitsSubscribeNackonErr). - Breaking:
server::EventPublisher::register_subscribersignature change — now returnsResult<(), server::SubscribeError>instead of(), surfacing the same capacity-rejection signal to externally managed subscription dispatchers. - Breaking:
Server::unicast_local_addrreturn type changed — previously returnedResult<std::net::SocketAddr, std::io::Error>; now returnsResult<std::net::SocketAddr, server::Error>. Callers that pattern-matched onstd::io::Errormust update toserver::Error::Transport(e)and access the innerTransportErrorfrom there. - Breaking: default features changed
default = []→default = ["std"]— previouslyembedded-io/std,thiserror/std, andtracing/stdwere always-on; they are now gated behind the newstdfeature. Downstream consumers building withdefault-features = falsewho relied on the implicitstdpropagation must addfeatures = ["std"](or one ofclient/server, which both implystd). - Breaking:
Client::newtype signature nowClient::<M, R, I, C>::new— theClientstruct gained three additional type parameters for the executor traits (R: TransportFactory,I: InterfaceHandle,C: ChannelFactory). The tokio-default convenience constructor is now gated behind theclient-tokiofeature (wasclient). Migration: addfeatures = ["client-tokio"]to continue usingClient::new; trait-surface consumers useClient::new_with_deps. - Breaking:
Server::newtype signature nowServer::<R, S, F, Tm>::new— theServerstruct gained type parameters for the pluggable backends. The tokio-default convenience constructor is now gated behind theserver-tokiofeature (wasserver). Migration: addfeatures = ["server-tokio"]to continue usingServer::new; trait-surface consumers useServer::new_with_deps. - Breaking:
SubscriptionHandletrait redesigned — the previousget_subscribers(&self, …) -> impl Future<Output = Vec<Subscriber>>method has been replaced withfor_each_subscriber(&self, …, f: FnMut)visitor pattern. This allowsEventPublisher::publish_eventto copy subscriber addresses into a stack buffer (heapless::Vec<_, 16>) instead of allocating per-event. Implementors of customSubscriptionHandlemust migrate. - Breaking:
SubscriptionHandleRPITIT futures no longer+ Send— thesubscribe,unsubscribe, andfor_each_subscribermethods now returnimpl Future<…>without a+ Sendbound. This enables single-threaded lock-free implementations on bare-metal targets, but meansSubscriptionHandletrait objects cannot be held across.awaitpoints in multi-threaded executors. Direct usage with the defaultArc<RwLock<SubscriptionManager>>is unaffected. - Breaking:
clientandserverfeatures no longer implystd— previouslyclient = ["std", "dep:futures"]andserver = ["std", "dep:futures"]; nowclient = ["dep:futures-util"]andserver = ["dep:futures-util"]. Thestdfeature moved toclient-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.serverstill pullsextern crate allocbecauseServerholdsArc<EventPublisher>andEventPublisherholdsArc<F::Socket>— documented inlib.rs; refactor to&'staticborrows is tracked for a future phase. - Breaking: optional dep
futuresreplaced withfutures-util— direct dependency onfutures-utilwith features["async-await", "async-await-macro"]. Thefuturesumbrella crate'sselect!macro re-export is gated on itsstdfeature, which transitively pullsslab/memchr/futures-ioand breaks no_std cross-compiles.futures-utilprovidesselect_biased!,pin_mut!, andFutureExtunder justasync-await(-macro). - Breaking: internal
select!→select_biased!—Inner::run_future,socket_loop_future, andserver::runnow 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_instancesreplaced by visitor-pattern methods — seefor_each_offered_endpoint/for_each_service_instancein "Added" above. Implementors of customPayloadWireFormattypes must override the visitors instead of theVec-returning forms. TheVec-returning forms remain as default-implementedcfg(feature = "std")convenience wrappers, so std callers' code keeps compiling unchanged. - Breaking:
PayloadWireFormat::new_subscription_sd_headerparameter type —client_ipis nowcore::net::Ipv4Addr(wasstd::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_flagno longercfg(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:
OfferedEndpointno longercfg(feature = "std")— type is always available; itsaddrfield isOption<core::net::SocketAddrV4>(wasOption<std::net::SocketAddrV4>). Same underlying type; allows no_std consumers to receive offered-endpoint visits. - Breaking:
server::Error::Io(std::io::Error)nowcfg(feature = "std")— the variant is gated onfeature = "std"becausestd::io::Erroris itself std-only. No-std consumers receive transport failures viaError::Transport(TransportError)which carries the portableIoErrorKind. - Breaking: misuse paths on
Server::announcement_loop/Server::runreturnError::InvalidUsage(...)— previously these returnedError::Io(std::io::Error::new(InvalidInput, ..))with a formatted message. The new variant is no_std-friendly and carries a machine-readable&'static strtag ("passive_server_announcement_loop","announcement_loop_already_started","passive_server_run"); the diagnostic moves totracing::warn!. - Breaking:
server::SubscriptionManager::get_subscribersnowcfg(feature = "std")— convenience accessor returning a heapVec<Subscriber>. Production code paths usefor_each_subscriber(visitor) since 0.8.0; this accessor remains for std consumers' tests and ad-hoc tooling. No_std consumers must usefor_each_subscriber. - Breaking:
server::ServiceInfo/server::EventGroupInfonowcfg(feature = "std")— both types'pubfields holdVec<...>. Bare-metal consumers don't construct these types today; if the use case emerges, a future port will switch toheapless::Vec.Subscriberis unaffected and stays no_std. - Breaking:
E2ERegistryAPI change — backing storage migrated fromstd::collections::HashMaptoheapless::index_map::FnvIndexMap(cap =E2E_REGISTRY_CAP = 32, exposed).E2ERegistry::registernow returnsResult<(), E2ERegistryFull>; replacing an already-registered key always succeeds, adding a new key past the cap returnsErr.E2ERegistry::new()is nowconst. The module is no longercfg(feature = "std")—E2ERegistryworks in pure no_std. - Breaking:
E2ERegistryHandle::registertrait method now returnsResult<(), E2ERegistryFull>— propagates the new typed overflow fromE2ERegistry::registerthrough every handle impl. Callers (Client::register_e2e,Server::register_e2e) lift theResultthrough to their public surface. client::Error::Transportadopts#[error(transparent)]Display delegation (the previous wrapping with{:?}debug-formatted the innerTransportError); user-facing error strings are now stable.- Subscribe-NACK reason strings normalized to
snake_casefor 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 byTTL=0).
server::EventPublisher::publish_eventno longer silently sends UNPROTECTED payloads on E2E protect failure — counter exhaustion / key-lookup races etc. now surface asErr(Error::E2e(_))rather than logging and falling through (which had been emitting an unprotected message claiming an E2E-protected channel).- SD
Subscribewith mismatchedmajor_versionis now NACKed — previously an Ack would be returned and the subscription registered, leaving the application stack to silently mis-decode incompatible-version traffic. SocketManager::sendno longer panics on a dropped response oneshot — user-suppliedSpawnermade this path reachable; failures now returnErr(Error::SocketClosedUnexpectedly).client::Innerrequest-queue overflow no longer drops control messages silently — full queue now invokesreject_with_capacity("request_queue")on the rejected message, so callers see a typedErr(Error::Capacity("request_queue"))instead of aRecvErrormapped toError::Shutdown.- Per-socket recv-error hot loop bounded —
SocketManager's socket loop now closes afterMAX_CONSECUTIVE_RECV_ERRORS = 16consecutiverecv_fromfailures rather than spinning indefinitely on a permanently broken fd. Client::sendfails fast on oversize messages — pre-encode size check returnsErr(Error::Capacity("udp_buffer"))for messages whoserequired_size()exceedsUDP_BUFFER_SIZE. Mirrors the existingEventPublisher::publish_eventcapacity guard.
- Crate version bumped to 0.8.0 — reflects the breaking changes above. Downstream
Cargo.tomlsnippets inREADME.mdwere updated accordingly. - Bare-metal compile gate is now literal.
cargo build --target thumbv7em-none-eabihf --no-default-features --features client,server,bare_metalsucceeds;client + bare_metalis verified alloc-free (zero__rust_allocreferences 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. Seebare_metal_plan_v3.md. - Known limitation:
serverfeature pullsextern crate alloc.ServerholdsArc<EventPublisher>andEventPublisherholdsArc<F::Socket>; both require an allocator. Pure no_std-without-allocator consumers can use theclientfeature alone (alloc-free) but will need a global allocator for the server side. A refactor to&'staticborrows is on the v3 phase 21+ backlog.
tests/client_server.rsintegration tests share the SD multicast port (30490) viaSO_REUSEPORTand 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.tomlserializesclient_servervia theserial-sd-porttest-group, socargo 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 - 2026-04-20
- Bump to 0.6.0 and fix linting
- Default the reboot flag enum and have it to default to RecentlyRebooted(1) instead of Continuous(0)
- Add loopback support for simple someip.
0.5.3 - 2026-04-15
- Unify Service Discover across multiple server offers without conflict, HBs flow nicely
- Add a lot of robustness through unit testing and input validation.
0.5.2 - 2026-04-09
- Update src/client/mod.rs
- Drop the client sender to avoid hanging and delay our first sd message
- Respond to PR Feedback
- More Copilot comments
- Address PR comments - made the sender weak to avoid a hanging reference
- Add an example of how to submit SD messages while a client and server
- Respond to PR feedback and add unit tests.
- Undo server changes and add unit tests.
- Add an explicit command to the client to send SD announcements on a loop
- Allow users to add extra SD entries when sending offers.
- Fix issues sending someip commands on shared ports
0.5.1 - 2026-04-03
- Automatically create semver appropriate release PR
- Fix test.
- Respond to Copilot feedback
- Add a "Subscribe No Wait" to avoid blocking on subscriptions, + tests
- Formatted & remove duplicate sd payload.
- Tie SD session IDs to per service instances
- Pacify Clippy.
- Fix false reboot detection with interleaved SD session IDs
- Split
Clientinto handle + update stream —Client::new()now returns(Client, ClientUpdates)instead ofSelf. TheClienthandle isClone-able and all methods take&self, allowing concurrent use from multiple tasks withoutArc<Mutex<_>>.ClientUpdates::recv()replaces the oldclient.run()method. shut_down()is no longer async —Client::shut_down(self)drops the control channel synchronously. The inner event loop exits once allClientclones are dropped.add_endpointtakes alocal_portparameter — controls the source port used when sending to the endpoint. Pass0for an ephemeral OS-assigned port.
Client::request()— send a message and await the response in one call, without needing to driveClientUpdates::recv()concurrently.Client::send_to_service()— returns aPendingResponsehandle for manual request-response control.- Multiple concurrent requests — the inner event loop now tracks pending responses
in a
HashMapkeyed byrequest_id, supporting multiple in-flight request-response transactions. - Automatic E2E management —
Client::register_e2e()/unregister_e2e()andServer::register_e2e()/unregister_e2e()configure End-to-End protection per message key. Incoming messages are checked and outgoing messages are protected automatically. EventPublisher::publish_event()— type-safe event publishing usingMessage<P>instead of raw bytes.EventPublisher::subscriber_count()— query the number of subscribers for an event group.
- SD spec compliance —
SubscribeAckandSubscribeNackare now sent from the SD socket (port 30490) instead of the unicast socket, matching the SOME/IP-SD specification requirement that all SD messages originate from the SD port.
- Zero-copy parsing —
Header::read_from_bytes/Message::read_from_bytesreplaced byHeaderView::parseandMessageView::parse, which return borrowed views instead of owned structs. SD headers follow the same pattern withSdHeaderView::parse. - Simplified error types — flattened and consolidated error enums across the crate.
- Encapsulated protocol header —
Headerfields are no longer public; use constructors and accessors instead. - Removed
send_message/ binding API — the client now manages socket binding internally;Client::add_endpoint/Client::remove_endpointreplace the old approach. - Re-exported traits at crate root —
WireFormatandPayloadWireFormatare now available directly fromsimple_someip::*.
- Service registry —
Client::add_endpoint/Client::remove_endpointandClient::send_to_servicefor programmatic endpoint management. - Session handling — the client now tracks SD session IDs per sender and detects reboots
via
ClientUpdate::SenderRebooted. - Comprehensive API documentation — doc comments with
# Errorsand# Panicssections on every public function; crate-level rustdoc with usage examples.
- SD constants moved into the
protocol::sdmodule. - Standalone discovery example with proper feature-gated dependencies.
Initial public release.