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//! | [`WireFormat`] / [`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::WireFormat;
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("udp_buffer")` or
163/// `server::Error::Capacity("udp_buffer")`, 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
186/// SOME/IP client for discovering services and exchanging messages.
187#[cfg(feature = "client")]
188pub mod client;
189/// End-to-end (E2E) protection utilities for SOME/IP payloads.
190pub mod e2e;
191/// no_std / no-alloc [`PayloadWireFormat`] mirroring the std-only
192/// `RawPayload` with `heapless::Vec`-backed storage. Available whenever
193/// the `bare_metal` feature is enabled.
194#[cfg(feature = "bare_metal")]
195pub mod heapless_payload;
196mod log;
197/// SOME/IP protocol primitives: headers, messages, return codes, and service discovery.
198pub mod protocol;
199/// A general-purpose, heap-allocated [`PayloadWireFormat`] implementation.
200#[cfg(feature = "std")]
201mod raw_payload;
202/// SOME/IP server for offering services and handling incoming requests.
203///
204/// The engine is generic over [`transport::TransportFactory`] +
205/// [`transport::Timer`] + [`transport::E2ERegistryHandle`] +
206/// [`server::SubscriptionHandle`], so the bare `server` feature exposes the
207/// trait-surface server. The `server-tokio` feature additionally provides
208/// the tokio convenience constructors (`server::Server::new`,
209/// `server::Server::new_with_loopback`, `server::Server::new_passive`)
210/// that default the type parameters to
211/// `Arc<Mutex<E2ERegistry>>` / `Arc<RwLock<SubscriptionManager>>` /
212/// `TokioTransport` / `TokioTimer`.
213#[cfg(feature = "server")]
214pub mod server;
215/// Tokio + `socket2` implementation of the [`transport`] traits. Provided
216/// as the default `std` backend — available whenever `client-tokio` or
217/// `server-tokio` is enabled.
218#[cfg(any(feature = "client-tokio", feature = "server-tokio"))]
219pub mod tokio_transport;
220
221/// Reusable bare-metal SOME/IP runtime: callback-driven transport + RX
222/// mailbox + the embassy executor and single composed task, so a
223/// platform integrates by supplying only its catalog + I/O callbacks.
224#[cfg(feature = "bare_metal")]
225pub mod bare_metal_runtime;
226/// Spawnable, embassy-agnostic async futures (offer announce, subscribe
227/// announce, event RX+dispatch) plus a sync publish helper, so a
228/// bare-metal firmware only spawns futures and provides socket I/O.
229#[cfg(all(feature = "bare_metal", feature = "server"))]
230pub mod bare_metal_tasks;
231/// `embassy-sync`-backed implementation of [`transport::ChannelFactory`].
232/// Available whenever the `embassy_channels` feature is enabled. Uses
233/// heap allocation (`Arc<Channel<...>>`) — for no-alloc, use
234/// [`static_channels`] instead.
235#[cfg(feature = "embassy_channels")]
236pub mod embassy_channels;
237/// Pure, no-alloc SOME/IP + SD datagram codec: transport-agnostic
238/// builders/parsers used by the server receive loop, the firmware shim,
239/// and the spawnable futures in [`bare_metal_tasks`].
240#[cfg(any(feature = "bare_metal", feature = "server"))]
241pub mod sd_codec;
242/// Static-pool no-alloc primitives for [`transport::ChannelFactory`].
243/// Backs the consumer-declared static `OneshotPool` / `MpscPool`
244/// instances that the [`define_static_channels!`] macro
245/// generates per-`T` `*Pooled<MyChannels>` impls against.
246#[cfg(feature = "bare_metal")]
247pub mod static_channels;
248mod traits;
249/// Executor-agnostic UDP transport abstraction used by the client and
250/// server modules. `no_std`-compatible; a default `std + tokio` backend
251/// ships in `tokio_transport` (available under the `client-tokio` /
252/// `server-tokio` features) — the link is rendered as a code literal
253/// because the target module is feature-gated and would break
254/// default-feature rustdoc builds.
255pub mod transport;
256#[cfg(feature = "bare_metal")]
257pub use heapless_payload::{HeaplessPayload, HeaplessSdHeader};
258#[cfg(feature = "std")]
259pub use raw_payload::{RawPayload, VecSdHeader};
260pub use traits::{OfferedEndpoint, PayloadWireFormat, WireFormat};
261
262#[cfg(feature = "client")]
263pub use client::{
264 Client, ClientDeps, ClientUpdate, ClientUpdates, DiscoveryMessage, PendingResponse,
265};
266// `ClientChannelTypes`, `ControlMessage`, `SendMessage`, `ReceivedMessage`
267// are intentionally NOT re-exported at crate root — they are
268// implementation-detail-with-a-public-name (reachable as
269// `simple_someip::client::ControlMessage` etc. for the
270// `define_static_channels!` macro) rather than first-class crate-API
271// types. Elevating them to crate root would lock their shape into
272// the public-API contract and tempt generic users into hitting the
273// `ClientChannelTypes` elaboration limit at the wrong call site.
274pub use e2e::{E2ECheckStatus, E2EKey, E2EProfile};
275#[cfg(feature = "server")]
276pub use server::{
277 NonSdRequestCallback, Server, ServerDeps, ServerHandles, ServerStorage, SubscriptionHandle,
278};
279#[cfg(any(feature = "client-tokio", feature = "server-tokio"))]
280pub use tokio_transport::{TokioChannels, TokioSocket, TokioSpawner, TokioTimer, TokioTransport};
281#[cfg(feature = "bare_metal")]
282pub use transport::AtomicInterfaceHandle;
283pub use transport::{
284 ChannelFactory, E2ERegistryHandle, InterfaceHandle, IoErrorKind, LocalSpawner, MpscRecv,
285 MpscSend, OneshotCancelled, OneshotRecv, OneshotSend, ReceivedDatagram, SocketOptions, Spawner,
286 Timer, TransportError, TransportFactory, TransportSocket, UnboundedRecv, UnboundedSend,
287};
288#[cfg(feature = "bare_metal")]
289pub use transport::{StaticE2EHandle, StaticE2EStorage};
290
291/// Parse a decimal `usize` from a compile-time optional env var string.
292///
293/// Used to size internal constants from `SIMPLE_SOMEIP_MAX_*` env vars
294/// injected by the host build system (e.g. `CMake` via `.cargo/config.toml`).
295/// Returns `default` when the variable is absent or empty.
296/// Panics at compile time if the string contains a non-digit character.
297///
298/// Gated on `server`: every caller lives in the server module or in the
299/// `bare-metal-runtime` runtime (which itself implies `server`), so a
300/// `client`-only / `bare_metal`-only build would otherwise see it as dead code.
301#[cfg(feature = "server")]
302pub(crate) const fn from_env_or(var: Option<&'static str>, default: usize) -> usize {
303 match var {
304 None => default,
305 Some(s) => {
306 let b = s.as_bytes();
307 if b.is_empty() {
308 return default;
309 }
310 let mut n = 0usize;
311 let mut i = 0;
312 while i < b.len() {
313 let byte = b[i];
314 assert!(
315 byte.is_ascii_digit(),
316 "SIMPLE_SOMEIP_MAX_* env var contains a non-digit character"
317 );
318 // `byte - b'0'` is in 0..=9; u8 -> usize is lossless. `as` is
319 // required here because `usize::from` is not a `const fn`.
320 n = n * 10 + (byte - b'0') as usize;
321 i += 1;
322 }
323 n
324 }
325 }
326}