simple_someip/lib.rs
1//! # Simple SOME/IP
2//!
3//! [](https://github.com/luminartech/simple_someip/actions/workflows/ci.yml)
4//! [](https://app.codecov.io/gh/luminartech/simple_someip)
5//! [](https://crates.io/crates/simple-someip)
6//!
7//! A Rust implementation of the [SOME/IP](https://github.com/some-ip-com/open-someip-spec)
8//! automotive communication protocol — remote procedure calls, event notifications, service
9//! discovery, and wire-format serialization.
10//!
11//! The core protocol layer (`protocol`, `e2e`, and trait modules) is `no_std`-compatible with
12//! zero heap allocation, making it suitable for embedded targets. Optional `client` and `server`
13//! modules provide async tokio-based networking for `std` environments.
14//!
15//! ## Modules
16//!
17//! | Module | `no_std` | Description |
18//! |--------|----------|-------------|
19//! | [`protocol`] | Yes | Wire format: headers, messages, message types, return codes, and service discovery (SD) entries/options |
20//! | [`e2e`] | Yes | End-to-End protection — Profile 4 (CRC-32) and Profile 5 (CRC-16) |
21//! | [`Encode`] / [`PayloadWireFormat`] | Yes | Traits for serializing messages and defining custom payload types |
22//! | `client` | No | Async client trait surface — service discovery, subscriptions, request/response (feature `client`; add `client-tokio` for `Client::new`) |
23//! | `server` | No | Async server trait surface — service offering, event publishing, subscription management (feature `server`; add `server-tokio` for `Server::new`) |
24//!
25//! ## Feature Flags
26//!
27//! | Feature | Default | Description |
28//! |---------|---------|-------------|
29//! | `std` | yes | Enables std-dependent helpers (`RawPayload`, `VecSdHeader`) and the `Arc<Mutex<E2ERegistry>>` / `Arc<RwLock<…>>` default lock-handle impls used by the tokio backends. |
30//! | `client` | no | Trait-surface client. Pure `no_std`-clean (does not pull `extern crate alloc`). Caller supplies `Spawner` / `Timer` / `ChannelFactory` / `TransportFactory` / `E2ERegistryHandle` / `InterfaceHandle` impls. |
31//! | `client-tokio` | no | Adds the `Client::new` / `TokioSpawner` / `TokioTransport` convenience defaults; implies `client` + std + tokio + socket2. |
32//! | `server` | no | Trait-surface server. Alloc-free since PR #124: the no-alloc path is `Server::new_with_handles` + `run_with_buffers` with static handles. The `Arc`-backed conveniences (`new_with_deps`, `run`) are gated behind the internal `_alloc` feature (pulled in by `std` / `embassy_channels`). |
33//! | `server-tokio` | no | Adds the `Server::new` / `TokioTransport` / `TokioTimer` convenience defaults; implies `server` + std + tokio + socket2. |
34//! | `bare_metal` | no | Activates embassy-sync, the `static_channels` module (no-alloc `ChannelFactory`), `AtomicInterfaceHandle`, `StaticE2EHandle`, and `StaticSubscriptionHandle`. All five are pure `no_std` (no allocator required). See `examples/bare_metal_client/` and `examples/bare_metal_server/` for runnable bare-metal integration examples. |
35//! | `embassy_channels` | no | Heap-backed `EmbassySyncChannels` `ChannelFactory`. Implies `bare_metal` and pulls `extern crate alloc;` into the crate; **on `no_std`, downstream consumers must provide a `#[global_allocator]`**. Useful for tests / early prototypes before sizing static pools. |
36//!
37//! The default feature set is `["std"]`, which links `std` and enables
38//! the `RawPayload` / `VecSdHeader` helpers. For a minimal build with
39//! no allocator requirement — the `protocol`, trait, `transport`, and
40//! `e2e` modules only — pass `--no-default-features`. The
41//! trait-surface canary workspace members (`examples/bare_metal_client`,
42//! `examples/bare_metal_server`) depend on the crate with
43//! `default-features = false, features = ["bare_metal", "client"]` /
44//! `["bare_metal", "server"]` and validate that configuration when built
45//! in isolation (`cargo build -p bare_metal_client` /
46//! `cargo build -p bare_metal_server`), rather than as part of a workspace-wide
47//! build where features may be unified across members.
48//!
49//! ## Examples
50//!
51//! ### Encoding a SOME/IP-SD header (`no_std`)
52//!
53//! ```rust
54//! use simple_someip::Encode;
55//! use simple_someip::protocol::sd::{self, Entry, RebootFlag, ServiceEntry};
56//!
57//! // Build an SD header with a FindService entry
58//! let entries = [Entry::FindService(ServiceEntry::find(0x1234))];
59//! // A fresh process should set RebootFlag::RecentlyRebooted until its
60//! // session counter wraps past 0xFFFF for the first time.
61//! let sd_header =
62//! sd::Header::new(sd::Flags::new_sd(RebootFlag::RecentlyRebooted), &entries, &[]);
63//!
64//! // Encode to bytes
65//! let mut buf = [0u8; 64];
66//! let n = sd_header.encode(&mut buf.as_mut_slice()).unwrap();
67//!
68//! // Decode from bytes (zero-copy view)
69//! let view = sd::SdHeaderView::parse(&buf[..n]).unwrap();
70//! assert_eq!(view.entry_count(), 1);
71//! ```
72//!
73//! ### Async client (requires `feature = "client-tokio"`)
74//!
75//! ```rust,no_run
76//! # #[cfg(feature = "client-tokio")]
77//! # fn wrapper() {
78//! use simple_someip::{Client, ClientUpdate, RawPayload};
79//!
80//! #[tokio::main]
81//! async fn main() {
82//! // Client::new returns a Clone-able handle, an update stream, and
83//! // the run-loop future. Spawn the future on the tokio runtime;
84//! // the returned future depends on `tokio::select!` / `tokio::time`
85//! // / tokio sockets, so it is not executor-agnostic today.
86//! let (client, mut updates, run) = Client::<RawPayload, _, _, _>::new([192, 168, 1, 100].into());
87//! let _run_task = tokio::spawn(run);
88//! client.bind_discovery().await.unwrap();
89//!
90//! while let Some(update) = updates.recv().await {
91//! match update {
92//! ClientUpdate::DiscoveryUpdated(msg) => { /* SD message received */ }
93//! ClientUpdate::Unicast { message, e2e_status, source } => { /* unicast reply */ }
94//! ClientUpdate::SenderRebooted(addr) => { /* remote reboot */ }
95//! ClientUpdate::Error(err) => { /* error */ }
96//! }
97//! }
98//! }
99//! # }
100//! ```
101//!
102//! ## References
103//!
104//! - [Open SOME/IP Specification](https://github.com/some-ip-com/open-someip-spec)
105
106#![no_std]
107// embassy-executor's `nightly` feature expands `#[embassy_executor::task]`
108// to a `static TaskPool` whose `type Fut = impl Future` requires this. Only
109// enabled with `bare-metal-runtime` (which owns the executor + task); the
110// crate otherwise builds on stable.
111#![cfg_attr(feature = "bare-metal-runtime", feature(impl_trait_in_assoc_type))]
112#![warn(clippy::pedantic)]
113
114// `bare-metal-runtime` is a no-alloc feature (it uses the no-alloc server
115// handles, e.g. `started: &'static AtomicBool`) and pulls a nightly-only
116// crate feature. It is therefore mutually exclusive with the alloc features
117// (`std` / `_alloc` / `embassy_channels` / the `*-tokio` features that imply
118// `std`). Combining them — e.g. `--all-features` — otherwise fails deep in
119// the runtime with a cryptic type error; surface the real reason here. Build
120// the runtime on its own: `--no-default-features --features bare-metal-runtime`
121// (plus `client` / `server`). CI runs it in a dedicated nightly lane.
122#[cfg(all(feature = "bare-metal-runtime", feature = "_alloc"))]
123compile_error!(
124 "feature `bare-metal-runtime` is no-alloc and cannot be combined with the alloc \
125 features (`std`, `_alloc`, `embassy_channels`, or any `*-tokio`); build it with \
126 `--no-default-features --features bare-metal-runtime[,client|,server]`"
127);
128
129#[cfg(feature = "std")]
130extern crate std;
131
132// `alloc` is required by:
133// - `embassy_channels` — `EmbassySyncChannels` heap-allocates an
134// `Arc<Channel<...>>` per oneshot/bounded/unbounded.
135// - the allocator-backed server conveniences (`new_with_deps` /
136// `new_passive_with_deps`, `run`/`run_inner`'s owned buffers, the
137// `Arc` `StartedLatch`). The core `server` engine is alloc-free
138// since PR #124 (`new_with_handles` + `run_with_buffers`).
139//
140// The `static_channels` module (under `bare_metal` alone) does
141// NOT need alloc — users wanting `client` + `bare_metal` without
142// allocator get the no-alloc oneshot/mpsc primitives via the
143// macro. Pure `bare_metal` without `client` / `server` /
144// `embassy_channels` also stays alloc-free.
145// Pulls `alloc` into scope. Gated on the internal `_alloc` feature
146// (implied by `std` and `embassy_channels`). The
147// `Arc<T>: SharedHandle<T>` impl in `transport.rs` shares the same
148// gate so they move in lockstep.
149#[cfg(feature = "_alloc")]
150extern crate alloc;
151
152/// Maximum size, in bytes, of UDP payloads for `client` / `server` send
153/// paths that serialize into a fixed-size buffer of this size.
154///
155/// Paths currently capped by this constant:
156/// - `client::SocketManager::send` (unicast + SD outbound)
157/// - `server::EventPublisher::publish_event`
158/// - `server::EventPublisher::publish_raw_event`
159///
160/// When one of these paths is actually reached and serialization is
161/// attempted, messages larger than this cap fail with
162/// `client::Error::Capacity(crate::CapacityKind::UdpBuffer)` or
163/// `server::Error::Capacity(crate::CapacityKind::UdpBuffer)`, depending on the path.
164/// Paths that return early before
165/// attempting serialization (e.g. `publish_event` when there are no
166/// subscribers) are not affected. The remaining outbound SD paths
167/// (`OfferService` announcements, `SubscribeAck` / `SubscribeNack`)
168/// serialize into stack buffers of this same size — the phase-21
169/// per-event-allocation cleanup (`7c58649`) removed the former heap
170/// `Vec` buffers, so every outbound path is capped by this constant.
171///
172/// Note that this is an application-level UDP payload limit, not an
173/// Ethernet-MTU-safe size: a 1500-byte UDP payload exceeds a 1500-byte
174/// L2 MTU once IP/UDP headers are added (IPv4 leaves 1472 bytes of UDP
175/// payload, IPv6 leaves 1452), so sends at this size may fragment or
176/// fail depending on the network stack. Bare-metal ports targeting a
177/// smaller link MTU may want to lower this by forking.
178pub const UDP_BUFFER_SIZE: usize = 1500;
179
180/// Fixed-capacity pool of `&'static mut [u8]` receive/scratch buffers.
181/// Pure `no_std` (uses only `core::`). Exposed without a feature gate so
182/// both the bare-metal and std/tokio paths can reach [`buffer_pool::BufferPool`]
183/// and [`buffer_pool::BufferLease`].
184pub mod buffer_pool;
185/// Names the fixed-capacity internal structures reported by the
186/// `Capacity` variant of the client and server error enums.
187pub mod capacity;
188
189/// SOME/IP client for discovering services and exchanging messages.
190#[cfg(feature = "client")]
191pub mod client;
192/// End-to-end (E2E) protection utilities for SOME/IP payloads.
193pub mod e2e;
194/// no_std / no-alloc [`PayloadWireFormat`] mirroring the std-only
195/// `RawPayload` with `heapless::Vec`-backed storage. Available whenever
196/// the `bare_metal` feature is enabled.
197#[cfg(feature = "bare_metal")]
198pub mod heapless_payload;
199mod log;
200mod net_endpoint;
201/// SOME/IP protocol primitives: headers, messages, return codes, and service discovery.
202pub mod protocol;
203/// A general-purpose, heap-allocated [`PayloadWireFormat`] implementation.
204#[cfg(feature = "std")]
205mod raw_payload;
206/// SOME/IP server for offering services and handling incoming requests.
207///
208/// The engine is generic over [`transport::TransportFactory`] +
209/// [`transport::Timer`] + [`transport::E2ERegistryHandle`] +
210/// [`server::SubscriptionHandle`], so the bare `server` feature exposes the
211/// trait-surface server. The `server-tokio` feature additionally provides
212/// the tokio convenience constructors (`server::Server::new`,
213/// `server::Server::new_with_loopback`, `server::Server::new_passive`)
214/// that default the type parameters to
215/// `Arc<Mutex<E2ERegistry>>` / `Arc<RwLock<SubscriptionManager>>` /
216/// `TokioTransport` / `TokioTimer`.
217#[cfg(feature = "server")]
218pub mod server;
219/// Tokio + `socket2` implementation of the [`transport`] traits. Provided
220/// as the default `std` backend — available whenever `client-tokio` or
221/// `server-tokio` is enabled.
222#[cfg(any(feature = "client-tokio", feature = "server-tokio"))]
223pub mod tokio_transport;
224
225/// Reusable bare-metal SOME/IP runtime: callback-driven transport + RX
226/// mailbox + the embassy executor and single composed task, so a
227/// platform integrates by supplying only its catalog + I/O callbacks.
228#[cfg(feature = "bare_metal")]
229pub mod bare_metal_runtime;
230/// Spawnable, embassy-agnostic async futures (offer announce, subscribe
231/// announce, event RX+dispatch) plus a sync publish helper, so a
232/// bare-metal firmware only spawns futures and provides socket I/O.
233#[cfg(all(feature = "bare_metal", feature = "server"))]
234pub mod bare_metal_tasks;
235/// `embassy-sync`-backed implementation of [`transport::ChannelFactory`].
236/// Available whenever the `embassy_channels` feature is enabled. Uses
237/// heap allocation (`Arc<Channel<...>>`) — for no-alloc, use
238/// [`static_channels`] instead.
239#[cfg(feature = "embassy_channels")]
240pub mod embassy_channels;
241/// Pure, no-alloc SOME/IP + SD datagram codec: transport-agnostic
242/// builders/parsers used by the server receive loop, the firmware shim,
243/// and the spawnable futures in [`bare_metal_tasks`].
244#[cfg(any(feature = "bare_metal", feature = "server"))]
245pub mod sd_codec;
246/// Static-pool no-alloc primitives for [`transport::ChannelFactory`].
247/// Backs the consumer-declared static `OneshotPool` / `MpscPool`
248/// instances that the [`define_static_channels!`] macro
249/// generates per-`T` `*Pooled<MyChannels>` impls against.
250#[cfg(feature = "bare_metal")]
251pub mod static_channels;
252mod traits;
253/// Executor-agnostic UDP transport abstraction used by the client and
254/// server modules. `no_std`-compatible; a default `std + tokio` backend
255/// ships in `tokio_transport` (available under the `client-tokio` /
256/// `server-tokio` features) — the link is rendered as a code literal
257/// because the target module is feature-gated and would break
258/// default-feature rustdoc builds.
259pub mod transport;
260pub use automotive_wire_codec::{Decode, DecodeIter, DecodeIterator, Encode, EncodeToSliceError};
261#[cfg(feature = "bare_metal")]
262pub use heapless_payload::{HeaplessPayload, HeaplessSdHeader};
263pub use net_endpoint::{NetEndpoint, TransportProtocol};
264#[cfg(feature = "std")]
265pub use raw_payload::{RawPayload, VecSdHeader};
266pub use traits::{EncodeExt, OfferedEndpoint, PayloadWireFormat};
267
268#[cfg(feature = "client")]
269pub use client::{
270 Client, ClientDeps, ClientUpdate, ClientUpdates, DiscoveryMessage, PendingResponse,
271 ServiceEndpointKey,
272};
273// `ClientChannelTypes`, `ControlMessage`, `SendMessage`, `ReceivedMessage`
274// are intentionally NOT re-exported at crate root — they are
275// implementation-detail-with-a-public-name (reachable as
276// `simple_someip::client::ControlMessage` etc. for the
277// `define_static_channels!` macro) rather than first-class crate-API
278// types. Elevating them to crate root would lock their shape into
279// the public-API contract and tempt generic users into hitting the
280// `ClientChannelTypes` elaboration limit at the wrong call site.
281pub use capacity::CapacityKind;
282pub use e2e::{E2ECheckStatus, E2EKey, E2EProfile};
283#[cfg(feature = "server")]
284pub use server::{
285 NonSdRequestCallback, Server, ServerDeps, ServerHandles, ServerStorage, SubscriptionHandle,
286};
287#[cfg(any(feature = "client-tokio", feature = "server-tokio"))]
288pub use tokio_transport::{TokioChannels, TokioSocket, TokioSpawner, TokioTimer, TokioTransport};
289#[cfg(feature = "bare_metal")]
290pub use transport::AtomicInterfaceHandle;
291pub use transport::{
292 ChannelFactory, E2ERegistryHandle, InterfaceHandle, IoErrorKind, LocalSpawner, MpscRecv,
293 MpscSend, OneshotCancelled, OneshotRecv, OneshotSend, ReceivedDatagram, SocketOptions, Spawner,
294 Timer, TransportError, TransportFactory, TransportSocket, UnboundedRecv, UnboundedSend,
295};
296#[cfg(feature = "bare_metal")]
297pub use transport::{StaticE2EHandle, StaticE2EStorage};
298
299/// Parse a decimal `usize` from a compile-time optional env var string.
300///
301/// Used to size internal constants from `SIMPLE_SOMEIP_*` env vars
302/// (`SIMPLE_SOMEIP_MAX_OFFERS`, `SIMPLE_SOMEIP_*_CAP`, …)
303/// injected by the host build system (e.g. `CMake` via `.cargo/config.toml`).
304/// Returns `default` when the variable is absent or empty.
305/// Panics at compile time if the string contains a non-digit character.
306///
307/// Ungated: `e2e` is compiled unconditionally and sizes its registry caps
308/// through this, so there is no feature combination in which this is dead
309/// code. (It was previously gated on `server`/`client`, back when every
310/// caller lived in one of those modules.)
311pub(crate) const fn from_env_or(var: Option<&'static str>, default: usize) -> usize {
312 match var {
313 None => default,
314 Some(s) => {
315 let b = s.as_bytes();
316 if b.is_empty() {
317 return default;
318 }
319 let mut n = 0usize;
320 let mut i = 0;
321 while i < b.len() {
322 let byte = b[i];
323 assert!(
324 byte.is_ascii_digit(),
325 "SIMPLE_SOMEIP_* env var contains a non-digit character"
326 );
327 // `byte - b'0'` is in 0..=9; u8 -> usize is lossless. `as` is
328 // required here because `usize::from` is not a `const fn`.
329 n = n * 10 + (byte - b'0') as usize;
330 i += 1;
331 }
332 n
333 }
334 }
335}