Skip to content

server: split run() into run_with_buffers + alloc shim#104

Draft
JustinKovacich wants to merge 1 commit into
feature/phase19h_embassy_net_client_examplefrom
feature/phase20a_server_recv_buf_split
Draft

server: split run() into run_with_buffers + alloc shim#104
JustinKovacich wants to merge 1 commit into
feature/phase19h_embassy_net_client_examplefrom
feature/phase20a_server_recv_buf_split

Conversation

@JustinKovacich
Copy link
Copy Markdown
Contributor

Introduces Server::run_with_buffers(&mut self, unicast_buf: &mut [u8], sd_buf: &mut [u8]) as the no-alloc-friendly entry point for the server event loop. The existing Server::run becomes a thin convenience shim that heap-allocates two 65535-byte buffers via alloc::vec! and delegates.

Why: bare-metal consumers (TC4D + future no-alloc targets) cannot call Server::run because it pulls in
alloc::vec![0u8; 65535] for the recv buffers. Splitting the buffer allocation out of the event loop body lets those consumers supply their own storage (typically static- declared [u8; 65535] arrays) while leaving std consumers' ergonomics unchanged.

Pre-existing 64 KiB sizing rationale carries over verbatim to run_with_buffers's docs: peer SD messages are bounded by link MTU, but the server is a sink for any peer datagram landing on its SD/unicast port — a smaller buffer would silently truncate larger-than-MTU peer messages instead of surfacing them. Caller picks an appropriate size for their target.

Reborrow nuance: inside the loop, recv_from(&mut *buf) rather than recv_from(&mut buf) because unicast_buf / sd_buf are now &mut [u8] parameters, not owned Vec<u8> locals. Direct &mut buf would produce &mut &mut [u8].

Clears 20-pre alloc audit's category-D recv-buffer item without breaking any std-side caller. Existing tests pass:

  • 11 client_server tests (serialized to avoid pre-existing port races)
  • 2 bare_metal_e2e tests
  • 3 simple-someip-embassy-net loopback tests

Doesn't yet eliminate the other 20-pre findings:

  • D / 19f H = Arc<F::Socket> default — handled by separate Server::new_with_handles work
  • E.1 Arc — handled by separate EventPublisherHandle work
  • E.2 Arc — handled by separate SdStateHandle work

What this leaves: the bare-metal consumer must still hold the 65535-byte buffers in static storage (or wherever the firmware can spare 128 KB total recv-buffer RAM). On TC4D specifically with a typical RAM budget the size may need to shrink to something like 1500 + a documented truncation caveat — that's a per-consumer decision now exposed via the new API surface.

Gates green:

  • cargo fmt --check
  • cargo clippy --tests (2 pre-existing warnings, unrelated)
  • cargo build --workspace --all-targets
  • cargo build --no-default-features --features client,server,bare_metal
  • cargo build -p simple-someip-embassy-net --target thumbv7em-none-eabihf
  • cargo test --features client-tokio,server-tokio --test client_server -- --test-threads=1
  • cargo test --features client,server,bare_metal --test bare_metal_e2e
  • cargo test -p simple-someip-embassy-net --test loopback

Introduces `Server::run_with_buffers(&mut self, unicast_buf:
&mut [u8], sd_buf: &mut [u8])` as the no-alloc-friendly entry
point for the server event loop. The existing `Server::run`
becomes a thin convenience shim that heap-allocates two
65535-byte buffers via `alloc::vec!` and delegates.

Why: bare-metal consumers (TC4D + future no-alloc targets)
cannot call `Server::run` because it pulls in
`alloc::vec![0u8; 65535]` for the recv buffers. Splitting
the buffer allocation out of the event loop body lets those
consumers supply their own storage (typically `static`-
declared `[u8; 65535]` arrays) while leaving std consumers'
ergonomics unchanged.

Pre-existing 64 KiB sizing rationale carries over verbatim
to `run_with_buffers`'s docs: peer SD messages are bounded
by link MTU, but the server is a sink for any peer datagram
landing on its SD/unicast port — a smaller buffer would
silently truncate larger-than-MTU peer messages instead of
surfacing them. Caller picks an appropriate size for their
target.

Reborrow nuance: inside the loop, `recv_from(&mut *buf)`
rather than `recv_from(&mut buf)` because `unicast_buf` /
`sd_buf` are now `&mut [u8]` parameters, not owned `Vec<u8>`
locals. Direct `&mut buf` would produce `&mut &mut [u8]`.

Clears 20-pre alloc audit's category-D recv-buffer item
without breaking any std-side caller. Existing tests pass:
- 11 client_server tests (serialized to avoid pre-existing port races)
- 2 bare_metal_e2e tests
- 3 simple-someip-embassy-net loopback tests

Doesn't yet eliminate the other 20-pre findings:
- D / 19f H = Arc<F::Socket> default — handled by separate
  `Server::new_with_handles` work
- E.1 Arc<EventPublisher> — handled by separate
  `EventPublisherHandle` work
- E.2 Arc<SdStateManager> — handled by separate
  `SdStateHandle` work

What this leaves: the bare-metal consumer must still hold
the 65535-byte buffers in static storage (or wherever the
firmware can spare 128 KB total recv-buffer RAM). On TC4D
specifically with a typical RAM budget the size may need to
shrink to something like 1500 + a documented truncation
caveat — that's a per-consumer decision now exposed via the
new API surface.

Gates green:
- cargo fmt --check
- cargo clippy --tests (2 pre-existing warnings, unrelated)
- cargo build --workspace --all-targets
- cargo build --no-default-features --features client,server,bare_metal
- cargo build -p simple-someip-embassy-net --target thumbv7em-none-eabihf
- cargo test --features client-tokio,server-tokio --test client_server -- --test-threads=1
- cargo test --features client,server,bare_metal --test bare_metal_e2e
- cargo test -p simple-someip-embassy-net --test loopback

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR introduces a no-alloc-friendly server event loop entry point by splitting the existing Server::run() into a new Server::run_with_buffers(&mut self, unicast_buf: &mut [u8], sd_buf: &mut [u8]) that uses caller-provided receive buffers, plus an alloc-based run() convenience shim that heap-allocates the (existing) 64 KiB buffers and delegates.

Changes:

  • Added Server::run_with_buffers(...) as the primary event-loop implementation with externally supplied recv buffers.
  • Refactored Server::run() into a thin alloc-backed wrapper that allocates two 65535-byte buffers and calls run_with_buffers.
  • Updated rustdocs to explain buffer sizing and bare-metal usage patterns.
Comments suppressed due to low confidence (1)

src/server/mod.rs:770

  • The # Errors docs here say this returns Error::Io with std::io::ErrorKind::InvalidInput when called on a passive server, but the implementation returns Error::InvalidUsage("passive_server_run") (and this API is also usable in no_std contexts). Update the docs to match the actual error variant/message so callers know what to handle.
    /// # Errors
    ///
    /// Returns [`Error::Io`] with [`std::io::ErrorKind::InvalidInput`] if
    /// called on a server constructed via `Server::new_passive` — passive
    /// servers have no real SD socket to read from, so the run loop would
    /// block forever on the ephemeral placeholder socket.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/server/mod.rs
Comment on lines +747 to +750
/// unicast port. Backends that surface truncation
/// (`ReceivedDatagram::truncated`) emit a `tracing::warn!` when
/// the caller's buffer was too small; backends that don't
/// (TokioSocket today) silently truncate at the OS level.
Comment thread src/server/mod.rs
Comment on lines +813 to +822
// Reborrow `&mut *foo` rather than `&mut foo` because
// `unicast_buf` / `sd_buf` are `&mut [u8]` parameters
// here (caller-owned), not owned `Vec<u8>` locals
// — direct `&mut foo` would produce `&mut &mut [u8]`.
let unicast_fut = self
.unicast_socket
.socket()
.recv_from(&mut unicast_buf)
.recv_from(&mut *unicast_buf)
.fuse();
let sd_fut = self.sd_socket.socket().recv_from(&mut sd_buf).fuse();
let sd_fut = self.sd_socket.socket().recv_from(&mut *sd_buf).fuse();
Comment thread src/server/mod.rs
let sd_fut = self.sd_socket.socket().recv_from(&mut sd_buf).fuse();
let sd_fut = self.sd_socket.socket().recv_from(&mut *sd_buf).fuse();
pin_mut!(unicast_fut, sd_fut);
select_biased! {
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants