rust-embedded-community / embedded-nal

An Embedded Network Abstraction Layer
Apache License 2.0
177 stars 25 forks source link

[Suggestion] Ownership-based API for zero-copy #15

Open chrysn opened 4 years ago

chrysn commented 4 years ago

Considering expressing RIOT's network API (in particular GNRC; with their sockets it'd be different) using embedded-nal, their API could profit (read: avoid copies) from an ownership based model.

This suggestion is similar to #12, but while there things are about stream-based sockets, this is for datagram sockets. Like there, this might need an additional trait to implement (which could also be implemented by an alloc- or fixed-buffer backed wrapper for cases where a consumer needs it).

Rough API sketch:

trait OwnedReadable: UdpStack {
    // Anything more scatter-gather-y would make it easier to implement on
    // ring-buffer backed stacks, but harder to consume.
    type UdpMessage: AsRef<[u8]>;
    fn read_owned(&self, socket: &mut Self::UdpSocket) -> Result<Self::UdpMessage, nb::Error<Self::Error>>;
}

That would work well for the GNRC backend, which AFAICT guarantees contiguous allocation of inbound data. It'd also work well for the very small single-MTU stacks (which off my head I don't remember precisely; I think contiki worked like that). For backends like lwIP that don't guarantee contiguous allocation (because they put data across smaller slabs), it might be better to require something roughly like IntoIterator<Item=&[u8]>, which would then be expressed in its own trait anyway.

On the write side, I don't have that clear a suggestion, but I suppose combined usage would be a bit like

let received = block!(stack.receive(sock))?;
let extracted = parse(received);
drop(received);
let mut out = stack.write_owned_begin(sock, extracted.estimate_response_size())?; // might fail OOM
extracted.populate(&mut out); // might trim the response in the process
stack.write_owned(out);

Some applications might not be able to drop the received before starting the response; that's fully OK but uses more stack resources, so it'd be best practice to drop it ASAP to ensure that that works even on stacks that can have at most a single UDP message in flight.