Skip to content
Closed
27 changes: 27 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,33 @@

## [0.8.0]

### Breaking — bare-metal server buffer extraction (PR #125 / PR 3)

These changes are free before the 0.8.0 release — no published version
exposes the pre-refactor API.

- **`Server::run_with_buffers` takes two additional send-scratch buffers** —
the signature now requires `recv_send_buf: &mut [u8]` and
`announce_send_buf: &mut [u8]` after the existing `unicast_buf` / `sd_buf`
receive buffers. Callers that used the pre-refactor four-buffer form must
add two more `static [u8; N]` arguments (or equivalent heap slices). The
`_alloc` convenience `Server::run` is unchanged.

- **New `Server::announce_only_with_buffer`** — bare-metal supplementary
servers that need *only* the SD `OfferService` announcement loop (no recv)
now call this method instead of `announce_only_future`. It accepts a
caller-owned `&mut [u8]` scratch so the future does NOT park a
`[u8; UDP_BUFFER_SIZE]` (≈ 1500 B) in its own state.
`announce_only_future` (alloc-only) now delegates to this method internally
and carries a `#[cfg(feature = "_alloc")]` gate.

- **`EventPublisher::publish_event_with_buffers` /
`publish_raw_event_with_buffers` take caller scratch** — the two methods
now accept explicit `msg_buf: &mut [u8]` / `protected_buf: &mut [u8]`
slices. The future no longer parks two `[u8; UDP_BUFFER_SIZE]` arrays;
bare-metal callers supply `static` buffers. The `_alloc` wrappers
`publish_event` / `publish_raw_event` are unchanged.

### Client/Server API symmetry & ergonomics

The 0.8.0 ergonomics pass aligning the public Client and Server surfaces, removing the tokio-only `Server::new` cliff, and improving discoverability for bare-metal adopters. Six bundled changes: generic-parameter alignment, tokio-defaulted `Deps` builders, channel-types rustdoc, `ServerConfig` fluent builder, `Server::new` constructor reshape (the one breaking change in this set), and `SubscriptionHandle` GAT promotion. Migration shapes below are written against the previous published version (0.7.0); `cargo build` will surface every remaining call-site.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -289,17 +289,19 @@ use crate::static_channels::{BufferLease, BufferPool};
use crate::transport::BufferProvider;
use crate::UDP_BUFFER_SIZE;

/// Tokio-path buffer provider: a single leaked `BufferPool` sized at
/// `UNICAST_SOCKETS_CAP + 1` × `UDP_BUFFER_SIZE` (one per possible socket
/// plus discovery). Leaking is fine — a client process holds one for its
/// lifetime; the API hides it entirely from callers.
#[derive(Clone, Copy, Debug)]
pub struct TokioBufferProvider(&'static BufferPool<9, UDP_BUFFER_SIZE>);
/// Tokio-path buffer provider: a single `Arc`-backed `BufferPool` sized at
/// `UNICAST_SOCKETS_CAP + 1 (discovery) + 1 (release-lag slack)` ×
/// `UDP_BUFFER_SIZE`. `Arc`-backed, NOT leaked — the pool is freed when the
/// last provider/lease drops; the API hides it entirely from callers.
/// (Rev: the original sketch used `Box::leak`; the merged implementation is
/// `Arc`-backed so dynamically-created clients don't leak a pool each.)
#[derive(Clone, Debug)]
pub struct TokioBufferProvider(alloc::sync::Arc<BufferPool<10, UDP_BUFFER_SIZE>>);

impl TokioBufferProvider {
#[must_use]
pub fn new() -> Self {
Self(Box::leak(Box::new(BufferPool::new())))
Self(alloc::sync::Arc::new(BufferPool::new()))
}
}

Expand Down

Large diffs are not rendered by default.

11 changes: 7 additions & 4 deletions examples/bare_metal_client/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -299,11 +299,14 @@ async fn main() {
timer: MockTimer,
e2e_registry: e2e,
interface: iface,
// Caller-declared static buffer pool (#125): one slot per
// possible socket. On real firmware this is a `static`; here it
// is a function-local `static` for the example.
// Caller-declared static buffer pool (#125): UNICAST_SOCKETS_CAP
// (8) + 1 discovery + 1 release-lag slack = 10 slots. An evicted
// socket's lease frees asynchronously, so size one above the max
// live socket count to avoid a transient Capacity("udp_buffer")
// on evict-then-rebind. On real firmware this is a `static`; here
// it is a function-local `static` for the example.
buffer_provider: {
static POOL: BufferPool<9, UDP_BUFFER_SIZE> = BufferPool::new();
static POOL: BufferPool<10, UDP_BUFFER_SIZE> = BufferPool::new();
StaticBufferProvider(&POOL)
},
},
Expand Down
2 changes: 2 additions & 0 deletions examples/embassy_net_client/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -399,6 +399,8 @@ async fn main() {
tokio::task::spawn_local(server.run_with_buffers(
Box::leak(vec![0u8; 65535].into_boxed_slice()),
Box::leak(vec![0u8; 65535].into_boxed_slice()),
Box::leak(vec![0u8; simple_someip::UDP_BUFFER_SIZE].into_boxed_slice()),
Box::leak(vec![0u8; simple_someip::UDP_BUFFER_SIZE].into_boxed_slice()),
));
println!(
"[server] run loop spawned, emitting OfferService(0x{SERVICE_ID:04X}) every 1s"
Expand Down
2 changes: 2 additions & 0 deletions simple-someip-embassy-net/tests/loopback.rs
Original file line number Diff line number Diff line change
Expand Up @@ -633,6 +633,8 @@ async fn client_receives_server_sd_announcement() {
tokio::task::spawn_local(server.run_with_buffers(
Box::leak(Box::new([0u8; 65535])),
Box::leak(Box::new([0u8; 65535])),
Box::leak(Box::new([0u8; simple_someip::UDP_BUFFER_SIZE])),
Box::leak(Box::new([0u8; simple_someip::UDP_BUFFER_SIZE])),
));

// ── Client on stack B ────────────────────────────────
Expand Down
15 changes: 9 additions & 6 deletions src/client/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -341,10 +341,11 @@ impl
/// Build a `ClientDeps` with the tokio defaults.
///
/// `buffer_provider` is a single `TokioBufferProvider::new()`
/// constructed here exactly once. `TokioBufferProvider::new()` does
/// a `Box::leak`, so it MUST be one-per-client and never called on a
/// per-bind / hot path — this constructor is the canonical single
/// call site for the tokio path.
/// constructed here exactly once. It is `Arc`-backed (the pool is freed
/// when the last provider/lease drops — not leaked); keeping it
/// one-per-client shares that pool and avoids a fresh heap allocation on
/// every bind, so it should not be reconstructed on a per-bind / hot
/// path — this constructor is the canonical single call site.
#[must_use]
pub fn tokio(interface: Ipv4Addr) -> Self {
Self {
Expand Down Expand Up @@ -691,8 +692,10 @@ where
interface: Arc::new(RwLock::new(interface)),
spawner,
// One `TokioBufferProvider::new()` per client construction.
// It `Box::leak`s internally, so it must not be moved to a
// per-bind path; this single call covers every `bind_*`.
// It is `Arc`-backed (freed when the last provider/lease
// drops); keeping it one-per-client shares the pool and
// avoids a per-bind heap allocation. This single call
// covers every `bind_*`.
buffer_provider: crate::tokio_transport::TokioBufferProvider::new(),
},
multicast_loopback,
Expand Down
6 changes: 3 additions & 3 deletions src/client/socket_manager.rs
Original file line number Diff line number Diff line change
Expand Up @@ -879,9 +879,9 @@ mod tests {
}

/// Claim a single socket-loop buffer for a direct `bind_with_transport`
/// call in these unit tests. Each call leaks one small `BufferPool`
/// (acceptable in a test); production paths claim from one shared
/// provider per client.
/// call in these unit tests. Each call builds a fresh `Arc`-backed
/// `TokioBufferProvider` (the pool is freed when the lease drops — no
/// leak); production paths claim from one shared provider per client.
fn test_buf() -> crate::buffer_pool::BufferLease {
use crate::tokio_transport::TokioBufferProvider;
use crate::transport::BufferProvider;
Expand Down
Loading