Skip to content
Closed
157 changes: 144 additions & 13 deletions CHANGELOG.md

Large diffs are not rendered by default.

12 changes: 10 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ tokio = { version = "1", default-features = false, features = [
"sync",
"time",
], optional = true }
tracing = { version = "0.1", default-features = false }
tracing = { version = "0.1", default-features = false, optional = true }

[dev-dependencies]
# `critical-section/std` provides a host-platform impl so integration
Expand All @@ -76,7 +76,15 @@ tracing-subscriber = "0.3"

[features]
default = ["std"]
std = ["embedded-io/std", "thiserror/std", "tracing/std", "_alloc"]
# `tracing` pulls in `tracing-core`, which (since 0.1.33) declares
# `extern crate alloc` unconditionally and therefore requires the
# `alloc` crate to be present in the target sysroot. Bare-metal
# targets that ship a `core`-only sysroot (e.g. the AURIX TriCore
# LLVM-IR proxy used by the halo build) cannot satisfy this. Gate
# tracing behind a feature so those builds can opt out; std users
# pick it up automatically through the `std` feature below.
tracing = ["dep:tracing"]
std = ["embedded-io/std", "thiserror/std", "tracing", "tracing/std", "_alloc"]
# Feature 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
Expand Down
20 changes: 9 additions & 11 deletions examples/bare_metal_server/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -249,10 +249,12 @@ async fn main() {
let subs = StaticSubscriptionHandle::new(&SUBS_STORAGE);

// service_id=0x1234, instance_id=1, bound to LOCALHOST:30490.
let config = ServerConfig::new(Ipv4Addr::LOCALHOST, 30490, 0x1234, 1);
let config = ServerConfig::new(0x1234, 1)
.with_interface(Ipv4Addr::LOCALHOST)
.with_local_port(30490);

let server =
Server::<StaticE2EHandle, StaticSubscriptionHandle, MockFactory, MockTimer>::new_with_deps(
let (_server, _handles, run) =
Server::<MockFactory, MockTimer, StaticE2EHandle, StaticSubscriptionHandle>::new_with_deps(
ServerDeps {
factory,
timer: MockTimer,
Expand All @@ -265,14 +267,10 @@ async fn main() {
.await
.expect("Server::new_with_deps failed");

// The announcement loop periodically multicasts SD OfferService
// entries so clients on the network can discover this service.
// It is Send + 'static and can be handed to any executor.
let announce_handle = tokio::spawn(
server
.announcement_loop()
.expect("non-passive server must have an announcement loop"),
);
// The combined run-future drives both receive and announce. It
// is `'static` and can be handed to any executor (here tokio for
// the canary harness).
let announce_handle = tokio::spawn(run);

// Yield twice: the announcement loop fires its first SD offer on the
// first poll before the inter-announcement timer starts.
Expand Down
23 changes: 11 additions & 12 deletions examples/client_server/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -119,25 +119,24 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
major_version: 1,
minor_version: 0,
ttl: 3,
..ServerConfig::new(
interface,
MY_SERVER_PORT,
MY_SERVER_SERVICE_ID,
MY_SERVER_INSTANCE_ID,
)
..ServerConfig::new(MY_SERVER_SERVICE_ID, MY_SERVER_INSTANCE_ID)
.with_interface(interface)
.with_local_port(MY_SERVER_PORT)
};

let mut server = Server::new(config).await?;
// Dispatcher topology — the client drives all SD traffic via
// its own `sd_announcements_loop`, so we suppress the server's
// own announcement arm with `with_announce(false)`. The single
// returned run-future drives only the receive loop.
let config = config.with_announce(false);
let (_server, handles, run) = Server::new(config).await?;
info!("Server bound on port {MY_SERVER_PORT}");

// NOTE: We intentionally do NOT spawn server.announcement_loop().
// The client's sd_announcements_loop handles all SD traffic.

let _publisher = server.publisher();
let _publisher = handles.publisher;

// Spawn the server event loop (handles incoming subscriptions).
let _server_handle = tokio::spawn(async move {
if let Err(e) = server.run().await {
if let Err(e) = run.await {
error!("Server error: {e}");
}
});
Expand Down
56 changes: 32 additions & 24 deletions examples/embassy_net_client/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -269,22 +269,26 @@ type SubKey = (u16, u16, u16, SocketAddrV4);
struct InMemorySubscriptions(Arc<Mutex<Vec<SubKey>>>);

impl SubscriptionHandle for InMemorySubscriptions {
type SubscribeFuture<'a> =
core::pin::Pin<Box<dyn Future<Output = Result<(), SubscribeError>> + 'a>>;
type UnsubscribeFuture<'a> = core::pin::Pin<Box<dyn Future<Output = ()> + 'a>>;

fn subscribe(
&self,
service_id: u16,
instance_id: u16,
event_group_id: u16,
subscriber_addr: SocketAddrV4,
) -> impl Future<Output = Result<(), SubscribeError>> + '_ {
) -> Self::SubscribeFuture<'_> {
let this = self.0.clone();
async move {
Box::pin(async move {
let mut g = this.lock().unwrap();
let k = (service_id, instance_id, event_group_id, subscriber_addr);
if !g.contains(&k) {
g.push(k);
}
Ok(())
}
})
}

fn unsubscribe(
Expand All @@ -293,12 +297,12 @@ impl SubscriptionHandle for InMemorySubscriptions {
instance_id: u16,
event_group_id: u16,
subscriber_addr: SocketAddrV4,
) -> impl Future<Output = ()> + '_ {
) -> Self::UnsubscribeFuture<'_> {
let this = self.0.clone();
async move {
Box::pin(async move {
let mut g = this.lock().unwrap();
g.retain(|e| *e != (service_id, instance_id, event_group_id, subscriber_addr));
}
})
}

fn for_each_subscriber<'a, F>(
Expand Down Expand Up @@ -363,7 +367,7 @@ async fn main() {
Box::leak(Box::new(SocketPool::new()));
let server_factory = EmbassyNetFactory::new(stack_a, server_pool);
let server_e2e: Arc<Mutex<E2ERegistry>> = Arc::new(Mutex::new(E2ERegistry::new()));
let server_config = ServerConfig::new(IP_A, 30500, SERVICE_ID, INSTANCE_ID);
let server_config = ServerConfig::new(SERVICE_ID, INSTANCE_ID).with_interface(IP_A).with_local_port(30500);

let server_deps = ServerDeps {
factory: server_factory,
Expand All @@ -372,24 +376,28 @@ async fn main() {
subscriptions: InMemorySubscriptions::default(),
};

// Phase 19f: default `H = Arc<F::Socket>`. Annotation
// is explicit because type inference can't chase H
// across the `ServerDeps` indirection.
let server: Server<_, _, _, _, Arc<EmbassyNetSocket>> =
Server::new_with_deps(server_deps, server_config, false)
.await
.expect("server construction over embassy-net");

// `_local` because `EmbassyNetSocket: !Sync` (it borrows
// from `Stack<LoopbackDriver>`'s `RefCell`-bearing
// internals); the Send-bounded `announcement_loop`
// doesn't typecheck for our `H`.
let announce_fut = server
.announcement_loop_local()
.expect("announcement_loop_local");
tokio::task::spawn_local(announce_fut);
// Default `H = Arc<F::Socket>`. Annotation is explicit
// because type inference can't chase H across the
// `ServerDeps` indirection. We use `run_with_buffers`
// instead of the alloc-backed `run` returned from the
// constructor because `EmbassyNetSocket: !Sync` makes
// the `run`-future `!Send`; ignoring it and re-building
// via `run_with_buffers` keeps us on the `spawn_local`
// path.
let (server, _handles, _run): (
Server<_, _, _, _, Arc<EmbassyNetSocket>>,
_,
_,
) = Server::new_with_deps(server_deps, server_config, false)
.await
.expect("server construction over embassy-net");

tokio::task::spawn_local(server.run_with_buffers(
Box::leak(Box::new([0u8; 65535])),
Box::leak(Box::new([0u8; 65535])),
));
println!(
"[server] announcement loop spawned, emitting OfferService(0x{SERVICE_ID:04X}) every 1s"
"[server] run loop spawned, emitting OfferService(0x{SERVICE_ID:04X}) every 1s"
);

// ── Client on stack B ────────────────────────────────
Expand Down
1 change: 0 additions & 1 deletion simple-someip-embassy-net/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ readme = "README.md"
# Sized for: bare-metal Rust embedded targets running embassy-net +
# embassy-executor (cortex-m, RISC-V). Does not require alloc.
#
# See `bare_metal_plan_v3.md` for the surrounding plan (phase 19).

[dependencies]
simple-someip = { path = "..", version = "0.8", default-features = false, features = [
Expand Down
10 changes: 4 additions & 6 deletions simple-someip-embassy-net/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,11 @@ add, without writing their own transport adapter.

## Status

Phase 19 of the [bare-metal roadmap][plan-v3]. As of phase 19a, this
crate is a scaffolded skeleton; the full `TransportFactory` /
`TransportSocket` impl lands incrementally in 19b–19c, with a host
loopback integration test in 19e and an in-tree example in 19f.
Reference adapter implementing the full `TransportFactory` /
`TransportSocket` surface, with a host loopback integration test and
an in-tree example.

## Quick sketch (target shape, post-19c)
## Quick sketch

```rust,ignore
use simple_someip::{Client, ClientDeps};
Expand Down Expand Up @@ -51,4 +50,3 @@ MIT OR Apache-2.0, matching `simple-someip`.
[embassy-net]: https://crates.io/crates/embassy-net
[embassy-executor]: https://crates.io/crates/embassy-executor
[`simple-someip`]: https://crates.io/crates/simple-someip
[plan-v3]: https://github.com/luminartech/simple_someip
23 changes: 8 additions & 15 deletions simple-someip-embassy-net/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,21 +9,14 @@
//!
//! # Why this crate exists
//!
//! Phase 18 of the bare-metal effort closed the literal compile gate:
//! `simple-someip` + `client,server,bare_metal` cross-compiles for
//! `thumbv7em-none-eabihf`. But "compiles" is not "works" — until a
//! real backend satisfies the trait surface against an actual `no_std`
//! network stack, the trait surface is unverified. This crate is the
//! verification: an end-to-end working backend that bare-metal Rust
//! consumers can either depend on directly or treat as the worked
//! example for their own (lwIP, smoltcp-direct, vendor-stack) adapters.
//!
//! # Status
//!
//! Phase 19 in progress (per `bare_metal_plan_v3.md`). 19a (this
//! commit) is the scaffold; 19b implements [`EmbassyNetFactory`],
//! 19c implements [`EmbassyNetSocket`], 19e wires up the loopback
//! integration test, 19f produces an in-tree example.
//! `simple-someip` with `client,server,bare_metal` cross-compiles for
//! `thumbv7em-none-eabihf` — the literal compile gate is closed. But
//! "compiles" is not "works": until a real backend satisfies the
//! trait surface against an actual `no_std` network stack, that trait
//! surface is unverified. This crate is the verification — an
//! end-to-end working backend that bare-metal Rust consumers can
//! either depend on directly or treat as the worked example for
//! their own (lwIP, smoltcp-direct, vendor-stack) adapters.
//!
//! # Pairing with `simple-someip`
//!
Expand Down
6 changes: 3 additions & 3 deletions simple-someip-embassy-net/src/socket.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
//! `TransportSocket` impl wrapping `embassy_net::udp::UdpSocket`.
//!
//! Phase 19c lands the real send/recv I/O — named future structs
//! drive `embassy_net`'s `poll_send_to` / `poll_recv_from` directly,
//! so each datagram costs zero heap allocations on the hot path.
//! Named future structs drive `embassy_net`'s `poll_send_to` /
//! `poll_recv_from` directly, so each datagram costs zero heap
//! allocations on the hot path.

use core::future::Future;
use core::net::{Ipv4Addr, SocketAddrV4};
Expand Down
Loading