Skip to main content

simple_someip/
lib.rs

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