diff --git a/Cargo.toml b/Cargo.toml index cf519b5..44cd972 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -50,7 +50,24 @@ tracing-subscriber = "0.3" [features] default = ["std"] std = ["embedded-io/std", "thiserror/std", "tracing/std"] -client = ["std", "dep:tokio", "dep:socket2", "dep:futures"] +# Phase 13 split: `client` exposes the protocol/trait-surface client +# (no tokio, no socket2); `client-tokio` layers the tokio + socket2 +# convenience defaults on top. Consumers of the bare-metal trait surface +# enable `client` only (and supply their own `Spawner` / `Timer` / +# `ChannelFactory` / `TransportFactory` impls). Consumers who want the +# `Client::new` shortcut (defaulting to `TokioSpawner` / `TokioTimer` / +# `TokioChannels` / `TokioTransport`) enable `client-tokio`. +# +# `server` is **not** split in phase 13 — `src/server/sd_state.rs`, +# `src/server/subscription_manager.rs`, and `src/server/mod.rs` still +# reference `tokio::net::UdpSocket`, `tokio::sync::RwLock`, and +# `socket2::Socket` directly in production code. Phase 14 (server +# parallel) is the phase that retargets the server to the trait +# surface; once that lands, `server` will gain the same split into +# `server` + `server-tokio`. Until then, enabling `server` continues +# to pull tokio + socket2. +client = ["std", "dep:futures"] +client-tokio = ["client", "dep:tokio", "dep:socket2"] server = ["std", "dep:tokio", "dep:socket2", "dep:futures"] # Marks a build as intended for bare-metal / no_std consumption. # Currently a pure marker — enables no crate code on its own. Reserved @@ -74,4 +91,4 @@ bare_metal = ["dep:embassy-sync"] [[test]] name = "client_server" -required-features = ["client", "server"] +required-features = ["client-tokio", "server"] diff --git a/examples/bare_metal/src/main.rs b/examples/bare_metal/src/main.rs index ff88e97..e5d9026 100644 --- a/examples/bare_metal/src/main.rs +++ b/examples/bare_metal/src/main.rs @@ -58,14 +58,20 @@ //! - Phase 12: `TransportSocket` GATs — `SendFuture` / `RecvFuture` //! express `Send` bounds without RTN; `Socket = TokioSocket` pin //! removed from `bind_*` functions +//! - Phase 13 (partial): client-side feature-flag split. `client` no +//! longer pulls tokio + socket2; the tokio convenience defaults +//! (`Client::new`, `TokioSpawner`, etc.) live behind a new +//! `client-tokio` feature. //! //! **Remaining gaps:** -//! 1. **Feature-flag split** (Phase 13): `client` / `server` still -//! pull in tokio + socket2. A future split (`client` vs -//! `client-tokio`) will make the core types `no_std`-compatible. -//! -//! Until those are closed, `feature = "client"` / `feature = "server"` -//! pull in `std + tokio + socket2`. +//! 1. **Server-side feature-flag split** (Phase 13 server half, +//! deferred to Phase 14): `feature = "server"` still pulls in +//! tokio + socket2 because `server::sd_state` and +//! `server::subscription_manager` reference `tokio::net::UdpSocket` +//! / `tokio::sync::RwLock` / `socket2::Socket` directly. Phase 14 +//! (server parallel) is the phase that retargets the server to the +//! trait surface; once that lands, `server` will gain the same +//! `server` + `server-tokio` split. //! //! # Recommendation for `no_alloc` consumers today //! @@ -407,7 +413,8 @@ fn main() { println!( "note: trait layer (TransportSocket + TransportFactory + Timer + \ Spawner + ChannelFactory) exercised end-to-end. Phases 9-12 \ - complete. Remaining gap: client/server feature flags still pull \ - in tokio + socket2 (Phase 13). See top-of-file docblock." + complete; phase 13 (client half) complete. Remaining: phase 14 \ + server-trait retargeting + server-side `server-tokio` split. \ + See top-of-file docblock." ); } diff --git a/examples/client_server/Cargo.toml b/examples/client_server/Cargo.toml index 9d4495f..b9bf2c2 100644 --- a/examples/client_server/Cargo.toml +++ b/examples/client_server/Cargo.toml @@ -5,7 +5,7 @@ edition = "2024" publish = false [dependencies] -simple-someip = { path = "../..", features = ["client", "server"] } +simple-someip = { path = "../..", features = ["client-tokio", "server"] } tokio = { version = "1", features = ["macros", "rt-multi-thread", "time"] } tracing = "0.1" tracing-subscriber = "0.3" diff --git a/examples/client_server/src/main.rs b/examples/client_server/src/main.rs index d715d3c..516e064 100644 --- a/examples/client_server/src/main.rs +++ b/examples/client_server/src/main.rs @@ -106,7 +106,8 @@ async fn main() -> Result<(), Box> { // ── Create the client (handles discovery, subscriptions, SD socket) ── - let (client, mut updates, run_fut) = simple_someip::Client::::new(interface); + let (client, mut updates, run_fut) = + simple_someip::Client::::new(interface); let _run_handle = tokio::spawn(run_fut); client.bind_discovery().await?; info!("Client discovery bound"); diff --git a/examples/discovery_client/Cargo.toml b/examples/discovery_client/Cargo.toml index 7ccb1e4..51a9cd3 100644 --- a/examples/discovery_client/Cargo.toml +++ b/examples/discovery_client/Cargo.toml @@ -6,7 +6,7 @@ publish = false [dependencies] embedded-io = "0.7" -simple-someip = { path = "../..", features = ["client"] } +simple-someip = { path = "../..", features = ["client-tokio"] } tokio = { version = "1", features = ["macros", "rt-multi-thread"] } tracing = "0.1" tracing-subscriber = "0.3" diff --git a/examples/discovery_client/src/main.rs b/examples/discovery_client/src/main.rs index 3f17152..e4efd3b 100644 --- a/examples/discovery_client/src/main.rs +++ b/examples/discovery_client/src/main.rs @@ -287,7 +287,8 @@ async fn main() -> Result<(), Error> { info!("Starting discovery client on interface {interface}"); - let (client, mut updates, run_fut) = simple_someip::Client::::new(interface); + let (client, mut updates, run_fut) = + simple_someip::Client::::new(interface); let _run_handle = tokio::spawn(run_fut); client.bind_discovery().await.unwrap(); diff --git a/src/client/inner.rs b/src/client/inner.rs index d6e5cb6..94d0178 100644 --- a/src/client/inner.rs +++ b/src/client/inner.rs @@ -19,13 +19,14 @@ use crate::{ }, e2e::E2ERegistry, protocol::{self, Message}, - tokio_transport::{TokioChannels, TokioSpawner, TokioTimer, TokioTransport}, traits::PayloadWireFormat, transport::{ ChannelFactory, E2ERegistryHandle, MpscRecv, OneshotSend, Spawner, UnboundedSend, }, }; +#[cfg(feature = "client-tokio")] +use crate::tokio_transport::{TokioChannels, TokioSpawner, TokioTimer, TokioTransport}; use super::error::Error; @@ -42,7 +43,7 @@ const PENDING_RESPONSES_CAP: usize = 64; /// two. const UNICAST_SOCKETS_CAP: usize = 8; -pub(super) enum ControlMessage { +pub(super) enum ControlMessage { SetInterface(Ipv4Addr, C::OneshotSender>), BindDiscovery(C::OneshotSender>), UnbindDiscovery(C::OneshotSender>), @@ -308,9 +309,9 @@ pub(super) struct Inner< /// Target interface for sockets interface: Ipv4Addr, /// Socket manager for service discovery if bound - discovery_socket: Option>, + discovery_socket: Option>, /// Socket managers for unicast messages, keyed by local port - unicast_sockets: FnvIndexMap, UNICAST_SOCKETS_CAP>, + unicast_sockets: FnvIndexMap, UNICAST_SOCKETS_CAP>, /// Per-sender SD session state for reboot detection session_tracker: SessionTracker, /// Registry of known service endpoints (auto-populated from SD + manual) @@ -528,7 +529,7 @@ where } async fn receive_discovery( - socket_manager: &mut Option>, + socket_manager: &mut Option>, ) -> Result< ( SocketAddr, @@ -563,7 +564,7 @@ where async fn receive_any_unicast( unicast_sockets: &mut FnvIndexMap< u16, - SocketManager, + SocketManager, UNICAST_SOCKETS_CAP, >, ) -> Result, Error> { @@ -1135,7 +1136,7 @@ mod tests { use tokio::sync::{mpsc, oneshot}; use tokio::sync::mpsc::Sender; - type TestControl = ControlMessage; + type TestControl = ControlMessage; #[test] fn test_control_message_constructors() { diff --git a/src/client/mod.rs b/src/client/mod.rs index e84825a..ed8a1d1 100644 --- a/src/client/mod.rs +++ b/src/client/mod.rs @@ -29,30 +29,42 @@ //! (either a `static` or a heap allocator); the capacity constants plus //! [`crate::UDP_BUFFER_SIZE`] are the knobs for trimming this footprint. mod error; +#[cfg(feature = "client-tokio")] mod inner; +#[cfg(feature = "client-tokio")] mod service_registry; +#[cfg(feature = "client-tokio")] mod session; +#[cfg(feature = "client-tokio")] mod socket_manager; pub use error::Error; +#[cfg(feature = "client-tokio")] use crate::Timer; -use crate::e2e::{E2ECheckStatus, E2EKey, E2EProfile, E2ERegistry}; +use crate::e2e::E2ECheckStatus; +#[cfg(feature = "client-tokio")] +use crate::e2e::{E2EKey, E2EProfile, E2ERegistry}; +#[cfg(feature = "client-tokio")] use crate::tokio_transport::{TokioChannels, TokioSpawner, TokioTimer}; -use crate::transport::{ - ChannelFactory, E2ERegistryHandle, InterfaceHandle, MpscSend, OneshotRecv, Spawner, - UnboundedRecv, -}; +use crate::transport::{ChannelFactory, OneshotRecv, UnboundedRecv}; +#[cfg(feature = "client-tokio")] +use crate::transport::{E2ERegistryHandle, InterfaceHandle, MpscSend, Spawner}; use crate::{protocol, protocol::Message, traits::PayloadWireFormat}; +#[cfg(feature = "client-tokio")] use inner::{ControlMessage, Inner}; -use std::net::{Ipv4Addr, SocketAddr, SocketAddrV4}; +use std::net::SocketAddr; +#[cfg(feature = "client-tokio")] +use std::net::{Ipv4Addr, SocketAddrV4}; +#[cfg(feature = "client-tokio")] use std::sync::{Arc, Mutex, RwLock}; +#[cfg(feature = "client-tokio")] use tracing::info; /// Handle to a pending SOME/IP request-response transaction. /// Resolves when the inner loop receives a matching unicast reply. /// Does not borrow `Client`. -pub struct PendingResponse { +pub struct PendingResponse { receiver: C::OneshotReceiver>, } @@ -144,7 +156,7 @@ impl std::fmt::Debug for ClientUpdate

{ /// /// Returned by [`Client::new`]. Call [`recv`](Self::recv) to receive /// discovery, unicast, and error updates. -pub struct ClientUpdates { +pub struct ClientUpdates { update_receiver: C::UnboundedReceiver>, } @@ -178,18 +190,20 @@ impl ClientU /// (`Arc>` and `Arc>`) are used by the /// standard constructors [`Self::new`] / [`Self::new_with_loopback`] / /// [`Self::new_with_spawner_and_loopback`]. +#[cfg(feature = "client-tokio")] #[derive(Clone)] pub struct Client< MessageDefinitions: PayloadWireFormat + Send + 'static, - R: E2ERegistryHandle = Arc>, - I: InterfaceHandle = Arc>, - C: ChannelFactory = TokioChannels, + R: E2ERegistryHandle, + I: InterfaceHandle, + C: ChannelFactory, > { interface: I, control_sender: C::BoundedSender>, e2e_registry: R, } +#[cfg(feature = "client-tokio")] impl std::fmt::Debug for Client where MessageDefinitions: PayloadWireFormat + Send + 'static, @@ -204,7 +218,13 @@ where } } -/// Constructors that create the default `Arc`-backed handles for `std + tokio`. +/// Convenience constructors that default to `Arc>` / `Arc>` +/// handles, the `TokioChannels` channel factory, and the `TokioSpawner` task +/// submitter. Available under the `client-tokio` feature, which pulls in +/// `tokio` + `socket2`. Bare-metal callers use +/// [`Self::new_with_spawner_and_loopback`] (always available under `client`) +/// and supply their own channel factory + spawner. +#[cfg(feature = "client-tokio")] impl Client>, Arc>, TokioChannels> where @@ -228,7 +248,7 @@ where /// # use simple_someip::{Client, RawPayload}; /// # use std::net::Ipv4Addr; /// # async fn demo() { - /// let (client, mut updates, run) = Client::::new(Ipv4Addr::LOCALHOST); + /// let (client, mut updates, run) = Client::::new(Ipv4Addr::LOCALHOST); /// let _run_task = tokio::spawn(run); /// // ...interact with `client` and `updates`... /// # let _ = (client, updates); @@ -239,7 +259,7 @@ where interface: Ipv4Addr, ) -> ( Self, - ClientUpdates, + ClientUpdates, impl core::future::Future + Send + 'static, ) { Self::new_with_loopback(interface, false) @@ -274,7 +294,7 @@ where multicast_loopback: bool, ) -> ( Self, - ClientUpdates, + ClientUpdates, impl core::future::Future + Send + 'static, ) { Self::new_with_spawner_and_loopback(interface, multicast_loopback, TokioSpawner) @@ -293,7 +313,7 @@ where /// # fn spawn(&self, _: impl core::future::Future + Send + 'static) {} /// # } /// let (client, mut updates, run) = - /// Client::::new_with_spawner_and_loopback( + /// Client::::new_with_spawner_and_loopback( /// Ipv4Addr::LOCALHOST, /// false, /// MySpawner, @@ -345,6 +365,7 @@ where } /// Methods available on all `Client` regardless of handle types. +#[cfg(feature = "client-tokio")] impl Client where MessageDefinitions: PayloadWireFormat + Clone + std::fmt::Debug + 'static, @@ -737,6 +758,7 @@ where /// because it requires `tokio::sync::mpsc::Sender::downgrade()` for the /// weak-sender shutdown pattern. A bare-metal alternative would need a /// different lifecycle mechanism (phase-future). +#[cfg(feature = "client-tokio")] impl Client where MessageDefinitions: PayloadWireFormat + Clone + std::fmt::Debug + 'static, @@ -769,9 +791,18 @@ where /// (via `shut_down()` or going out of scope). /// /// ```no_run - /// # use simple_someip::{Client, RawPayload, VecSdHeader}; + /// # use simple_someip::{Client, RawPayload, TokioChannels, VecSdHeader}; /// # use simple_someip::protocol::sd::{self, RebootFlag, Flags}; - /// # async fn demo(client: Client) { + /// # use std::sync::{Arc, Mutex, RwLock}; + /// # use std::net::Ipv4Addr; + /// # async fn demo( + /// # client: Client< + /// # RawPayload, + /// # Arc>, + /// # Arc>, + /// # TokioChannels, + /// # >, + /// # ) { /// let header = VecSdHeader { /// flags: Flags::new_sd(RebootFlag::RecentlyRebooted), /// entries: vec![], @@ -877,14 +908,15 @@ where } } -#[cfg(test)] +#[cfg(all(test, feature = "client-tokio"))] mod tests { use super::*; use crate::protocol::sd::test_support::{TestPayload, empty_sd_header}; use crate::traits::WireFormat; use std::format; - type TestClient = Client>, Arc>>; + type TestClient = + Client>, Arc>, TokioChannels>; #[tokio::test] async fn test_client_new_and_interface() { diff --git a/src/client/socket_manager.rs b/src/client/socket_manager.rs index f79c2b6..5d0021e 100644 --- a/src/client/socket_manager.rs +++ b/src/client/socket_manager.rs @@ -32,22 +32,26 @@ //! removed; `SendFuture` / `RecvFuture` associated types express //! `Send` bounds for spawnable socket loops. //! +//! **Phase 13 (client half) complete:** the `client` feature no longer +//! pulls tokio or socket2. The full `Client` / `Inner` / `SocketManager` +//! types — including the `bind` / `bind_discovery_seeded` convenience +//! constructors that default to `TokioTransport` + `TokioSpawner` — are +//! gated behind the new `client-tokio` feature, which layers tokio + +//! socket2 on top of `client`. +//! //! **Remaining gaps:** -//! - **Feature-flag split** (Phase 13): `client` / `server` still -//! pull in tokio + socket2 dependencies. +//! - **Server-side split** (deferred to Phase 14): `feature = "server"` +//! still pulls tokio + socket2 because `server::sd_state` / +//! `server::subscription_manager` reference tokio types directly. //! -//! Until Phase 13 completes, enabling `feature = "client"` pulls -//! in `std + tokio + socket2`. The `bare_metal` feature flag activates -//! `EmbassySyncChannels` but does not make this module `no_alloc` on -//! its own. For `no_alloc` SOME/IP usage today, consume `protocol`, -//! `e2e`, and the `transport` trait layer directly — the `bare_metal` -//! example workspace member demonstrates that surface. +//! For `no_alloc` SOME/IP usage today, consume `protocol`, `e2e`, and +//! the `transport` trait layer directly — the `bare_metal` example +//! workspace member demonstrates that surface. use crate::{ UDP_BUFFER_SIZE, e2e::{E2ECheckStatus, E2EKey}, protocol::{Message, MessageView, sd}, - tokio_transport::TokioChannels, traits::{PayloadWireFormat, WireFormat}, transport::{ ChannelFactory, E2ERegistryHandle, MpscRecv, MpscSend, OneshotRecv, OneshotSend, @@ -79,7 +83,7 @@ pub struct ReceivedMessage

{ } /// Structure representing a request to send a message -pub struct SendMessage { +pub struct SendMessage { pub target_addr: SocketAddrV4, pub message: Message, response: C::OneshotSender>, @@ -98,7 +102,7 @@ impl std::fmt::Debug f /// block returns this scalar so the pinned per-iteration `send_fut` / /// `recv_fut` futures drop before the processing body — releasing their /// `&mut buf` / `&mut socket` borrows. -enum Outcome { +enum Outcome { Send(Option>), Recv(Result), } @@ -122,7 +126,7 @@ impl } } -pub struct SocketManager { +pub struct SocketManager { receiver: C::BoundedReceiver, Error>>, sender: C::BoundedSender>, local_port: u16, @@ -164,7 +168,9 @@ where /// /// Currently `#[cfg(test)]`-gated: production callers reach the /// socket through the `_with_transport` variant so the `Spawner` - /// trait can be exercised end-to-end. + /// trait can be exercised end-to-end. The enclosing `socket_manager` + /// module is itself gated to `feature = "client-tokio"`, so this + /// method is implicitly client-tokio-only. #[cfg(test)] pub async fn bind_discovery_seeded( interface: Ipv4Addr, @@ -652,12 +658,12 @@ where } } -#[cfg(test)] +#[cfg(all(test, feature = "client-tokio"))] mod tests { use super::*; use crate::e2e::E2ERegistry; use crate::protocol::sd::test_support::{TestPayload, empty_sd_header}; - use crate::tokio_transport::TokioSpawner; + use crate::tokio_transport::{TokioChannels, TokioSpawner}; use std::format; use std::sync::{Arc, Mutex}; use std::vec; @@ -666,7 +672,7 @@ mod tests { // abstraction via `TokioTransport`. use tokio::net::UdpSocket; - type TestSocketManager = SocketManager; + type TestSocketManager = SocketManager; fn test_registry() -> Arc> { Arc::new(Mutex::new(E2ERegistry::new())) @@ -688,7 +694,7 @@ mod tests { use crate::transport::OneshotRecv; let target = SocketAddrV4::new(Ipv4Addr::LOCALHOST, 1234); let msg = Message::new_sd(1, &empty_sd_header()); - let (rx, send_msg) = SendMessage::::new(target, msg); + let (rx, send_msg) = SendMessage::::new(target, msg); assert_eq!(send_msg.target_addr, target); // Verify the oneshot channel works send_msg.response.send(Ok(())).unwrap(); @@ -798,7 +804,7 @@ mod tests { async fn test_send_message_debug() { let target = SocketAddrV4::new(Ipv4Addr::LOCALHOST, 1234); let msg = Message::::new_sd(1, &empty_sd_header()); - let (_rx, send_msg) = SendMessage::::new(target, msg); + let (_rx, send_msg) = SendMessage::::new(target, msg); let s = format!("{send_msg:?}"); assert!(s.contains("SendMessage")); } @@ -924,7 +930,7 @@ mod tests { reg.register(key, E2EProfile::Profile4(Profile4Config::new(0, 15))); let e2e_registry = Arc::new(Mutex::new(reg)); - let mut sm = SocketManager::::bind(0, e2e_registry) + let mut sm = SocketManager::::bind(0, e2e_registry) .await .unwrap(); @@ -1031,7 +1037,7 @@ mod tests { } } - let mut sm = SocketManager::::bind_with_transport( + let mut sm = SocketManager::::bind_with_transport( &ForceReuseFactory, &TokioSpawner, 0, @@ -1146,7 +1152,7 @@ mod tests { // Compile-time witness: this `let` binding only typechecks if // `bind_with_transport` accepts `F::Socket = WrappedSocket` — // i.e. the previous `F::Socket = TokioSocket` pin is gone. - let mut sm = SocketManager::::bind_with_transport( + let mut sm = SocketManager::::bind_with_transport( &WrappingFactory, &TokioSpawner, 0, diff --git a/src/lib.rs b/src/lib.rs index 199c10d..dbe7cc9 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -27,8 +27,9 @@ //! | Feature | Default | Description | //! |---------|---------|-------------| //! | `std` | yes | Enables std-dependent helpers (`RawPayload`, `VecSdHeader`, `OfferedEndpoint`) | -//! | `client` | no | Async tokio client; implies `std` + tokio + socket2 + futures | -//! | `server` | no | Async tokio server; implies `std` + tokio + socket2 + futures | +//! | `client` | no | Trait-surface client; implies `std` + futures (no tokio) | +//! | `client-tokio` | no | Adds the `Client::new` / `TokioSpawner` / `TokioTransport` convenience defaults; implies `client` + tokio + socket2 | +//! | `server` | no | Async tokio server; implies `std` + tokio + socket2 + futures (server-tokio split deferred to phase 14) | //! | `bare_metal` | no | Pure marker — does not enable any crate code. See `examples/bare_metal/` (the trait-surface canary) for the full bare-metal-readiness story. | //! //! The default feature set is `["std"]`, which links `std` and enables @@ -66,10 +67,10 @@ //! assert_eq!(view.entry_count(), 1); //! ``` //! -//! ### Async client (requires `feature = "client"`) +//! ### Async client (requires `feature = "client-tokio"`) //! //! ```rust,no_run -//! # #[cfg(feature = "client")] +//! # #[cfg(feature = "client-tokio")] //! # fn wrapper() { //! use simple_someip::{Client, ClientUpdate, RawPayload}; //! @@ -79,7 +80,7 @@ //! // the run-loop future. Spawn the future on the tokio runtime; //! // the returned future depends on `tokio::select!` / `tokio::time` //! // / tokio sockets, so it is not executor-agnostic today. -//! let (client, mut updates, run) = Client::::new([192, 168, 1, 100].into()); +//! let (client, mut updates, run) = Client::::new([192, 168, 1, 100].into()); //! let _run_task = tokio::spawn(run); //! client.bind_discovery().await.unwrap(); //! @@ -146,17 +147,19 @@ mod raw_payload; #[cfg(feature = "server")] pub mod server; /// Tokio + `socket2` implementation of the [`transport`] traits. Provided -/// as the default `std` backend — available whenever `client` or `server` -/// is enabled. -#[cfg(any(feature = "client", feature = "server"))] +/// as the default `std` backend — available whenever `client-tokio` or +/// `server` is enabled. (Phase 13: `client` is now no-tokio; the tokio +/// backend lives behind `client-tokio`. `server` still pulls tokio +/// transitively until phase 14 retargets it to the trait surface.) +#[cfg(any(feature = "client-tokio", feature = "server"))] pub mod tokio_transport; mod traits; /// Executor-agnostic UDP transport abstraction used by the client and /// server modules. `no_std`-compatible; a default `std + tokio` backend -/// ships in `tokio_transport` (available under the `client` / `server` -/// features) — the link is rendered as a code literal because the target -/// module is feature-gated and would break default-feature rustdoc -/// builds. +/// ships in `tokio_transport` (available under the `client-tokio` / +/// `server` features) — the link is rendered as a code literal because +/// the target module is feature-gated and would break default-feature +/// rustdoc builds. pub mod transport; #[cfg(feature = "std")] pub use raw_payload::{RawPayload, VecSdHeader}; @@ -165,11 +168,13 @@ pub use traits::OfferedEndpoint; pub use traits::{PayloadWireFormat, WireFormat}; #[cfg(feature = "client")] -pub use client::{Client, ClientUpdate, ClientUpdates, DiscoveryMessage, PendingResponse}; +pub use client::{ClientUpdate, ClientUpdates, DiscoveryMessage, PendingResponse}; +#[cfg(feature = "client-tokio")] +pub use client::Client; pub use e2e::{E2ECheckStatus, E2EKey, E2EProfile}; #[cfg(feature = "server")] pub use server::Server; -#[cfg(any(feature = "client", feature = "server"))] +#[cfg(any(feature = "client-tokio", feature = "server"))] pub use tokio_transport::{TokioChannels, TokioSocket, TokioSpawner, TokioTimer, TokioTransport}; pub use transport::{ ChannelFactory, E2ERegistryHandle, InterfaceHandle, IoErrorKind, MpscRecv, MpscSend, diff --git a/src/tokio_transport.rs b/src/tokio_transport.rs index f2523e1..298b889 100644 --- a/src/tokio_transport.rs +++ b/src/tokio_transport.rs @@ -38,17 +38,13 @@ use core::pin::Pin; use core::task::{Context, Poll}; use core::time::Duration; use std::net::{IpAddr, SocketAddr}; -use std::sync::{Arc, Mutex, RwLock}; use tokio::io::ReadBuf; use tokio::net::UdpSocket; -use crate::e2e::{E2ECheckStatus, E2EKey, E2EProfile}; -use crate::e2e::Error as E2EError; -use crate::e2e::E2ERegistry; use crate::transport::{ - ChannelFactory, E2ERegistryHandle, InterfaceHandle, IoErrorKind, MpscRecv, MpscSend, - OneshotCancelled, OneshotRecv, OneshotSend, ReceivedDatagram, SocketOptions, Timer, - TransportError, TransportFactory, TransportSocket, UnboundedRecv, UnboundedSend, + ChannelFactory, IoErrorKind, MpscRecv, MpscSend, OneshotCancelled, OneshotRecv, OneshotSend, + ReceivedDatagram, SocketOptions, Timer, TransportError, TransportFactory, TransportSocket, + UnboundedRecv, UnboundedSend, }; /// Factory that binds [`TokioSocket`]s configured via `socket2`. @@ -247,53 +243,6 @@ impl crate::transport::Spawner for TokioSpawner { } } -impl E2ERegistryHandle for Arc> { - fn register(&self, key: E2EKey, profile: E2EProfile) { - self.lock().expect("e2e registry lock poisoned").register(key, profile); - } - - fn unregister(&self, key: &E2EKey) { - self.lock().expect("e2e registry lock poisoned").unregister(key); - } - - fn contains_key(&self, key: &E2EKey) -> bool { - self.lock().expect("e2e registry lock poisoned").contains_key(key) - } - - fn protect( - &self, - key: E2EKey, - payload: &[u8], - upper_header: [u8; 8], - output: &mut [u8], - ) -> Option> { - self.lock() - .expect("e2e registry lock poisoned") - .protect(key, payload, upper_header, output) - } - - fn check<'a>( - &self, - key: E2EKey, - payload: &'a [u8], - upper_header: [u8; 8], - ) -> Option<(E2ECheckStatus, &'a [u8])> { - self.lock() - .expect("e2e registry lock poisoned") - .check(key, payload, upper_header) - } -} - -impl InterfaceHandle for Arc> { - fn get(&self) -> Ipv4Addr { - *self.read().expect("interface lock poisoned") - } - - fn set(&self, addr: Ipv4Addr) { - *self.write().expect("interface lock poisoned") = addr; - } -} - /// Synchronously create and configure a UDP socket via `socket2`, then /// hand it to tokio. Mirrors the existing bind paths in /// `crate::client::socket_manager` and `crate::server` (rendered as diff --git a/src/transport.rs b/src/transport.rs index bcc6b4e..e3e1872 100644 --- a/src/transport.rs +++ b/src/transport.rs @@ -99,7 +99,7 @@ //! # Minimal adapter sketch //! //! ``` -//! # #[cfg(feature = "client")] +//! # #[cfg(feature = "client-tokio")] //! # fn wrapper() { //! use core::future::Future; //! use core::net::{Ipv4Addr, SocketAddrV4}; @@ -693,13 +693,73 @@ pub trait InterfaceHandle: Clone + Send + Sync + 'static { fn set(&self, addr: Ipv4Addr); } -// ── Channel-handle abstraction (Phase 11) ───────────────────────────────── +/// Default `std`-flavoured impls of [`E2ERegistryHandle`] and +/// [`InterfaceHandle`] backed by `std::sync::{Arc, Mutex, RwLock}`. Pure +/// std — no tokio dependency — so they live in the executor-agnostic +/// transport module rather than the tokio backend. +#[cfg(feature = "std")] +mod std_handle_impls { + use super::{E2ERegistryHandle, InterfaceHandle}; + use crate::e2e::{E2ECheckStatus, E2EKey, E2EProfile, E2ERegistry}; + use crate::e2e::Error as E2EError; + use core::net::Ipv4Addr; + use std::sync::{Arc, Mutex, RwLock}; + + impl E2ERegistryHandle for Arc> { + fn register(&self, key: E2EKey, profile: E2EProfile) { + self.lock().expect("e2e registry lock poisoned").register(key, profile); + } + + fn unregister(&self, key: &E2EKey) { + self.lock().expect("e2e registry lock poisoned").unregister(key); + } + + fn contains_key(&self, key: &E2EKey) -> bool { + self.lock().expect("e2e registry lock poisoned").contains_key(key) + } + + fn protect( + &self, + key: E2EKey, + payload: &[u8], + upper_header: [u8; 8], + output: &mut [u8], + ) -> Option> { + self.lock() + .expect("e2e registry lock poisoned") + .protect(key, payload, upper_header, output) + } + + fn check<'a>( + &self, + key: E2EKey, + payload: &'a [u8], + upper_header: [u8; 8], + ) -> Option<(E2ECheckStatus, &'a [u8])> { + self.lock() + .expect("e2e registry lock poisoned") + .check(key, payload, upper_header) + } + } + + impl InterfaceHandle for Arc> { + fn get(&self) -> Ipv4Addr { + *self.read().expect("interface lock poisoned") + } + + fn set(&self, addr: Ipv4Addr) { + *self.write().expect("interface lock poisoned") = addr; + } + } +} + +// ── Channel-handle abstraction ──────────────────────────────────────────── // -// `ChannelFactory` and its associated sender / receiver traits replace direct -// use of `tokio::sync::mpsc` and `tokio::sync::oneshot` in the client. -// `TokioChannels` (in `tokio_transport`) is the default for `std + tokio` -// builds; `EmbassySyncChannels` (in `tokio_transport`, gated behind -// `bare_metal`) is the alternative for no-tokio / no_std builds. +// `ChannelFactory` and its associated sender / receiver traits abstract over +// the channel primitive used by the client. `TokioChannels` (in +// `tokio_transport`) is the default for `std + tokio` builds; +// `EmbassySyncChannels` (in `tokio_transport`, gated behind `bare_metal`) +// is the alternative for no-tokio / no_std builds. /// Returned by [`OneshotRecv::recv`] when the sender was dropped before /// sending a value. diff --git a/tests/client_server.rs b/tests/client_server.rs index 15f6a12..8b7a359 100644 --- a/tests/client_server.rs +++ b/tests/client_server.rs @@ -29,7 +29,9 @@ use simple_someip::e2e::{E2ECheckStatus, E2EKey, E2EProfile, Profile4Config}; use simple_someip::protocol::{Header, Message, MessageId, sd}; use simple_someip::server::ServerConfig; -use simple_someip::{Client, ClientUpdate, PayloadWireFormat, RawPayload, Server, VecSdHeader}; +use simple_someip::{ + Client, ClientUpdate, PayloadWireFormat, RawPayload, Server, TokioChannels, VecSdHeader, +}; use std::net::{Ipv4Addr, SocketAddrV4}; use std::sync::atomic::{AtomicU16, Ordering}; @@ -50,7 +52,12 @@ fn empty_sd_header() -> VecSdHeader { } } -type TestClient = Client; +type TestClient = Client< + RawPayload, + std::sync::Arc>, + std::sync::Arc>, + TokioChannels, +>; /// Create a server on an ephemeral unicast port, returning (Server, actual_port). async fn create_server(service_id: u16, instance_id: u16) -> (Server, u16) { @@ -265,7 +272,7 @@ async fn test_add_endpoint_and_send_to_service() { "expected ServiceNotFound after remove, got {result:?}" ); // Verify that PendingResponse is importable from the crate root - let _: fn() -> Option> = || None; + let _: fn() -> Option> = || None; client.shut_down(); server_handle.abort();