embedded-nal icon indicating copy to clipboard operation
embedded-nal copied to clipboard

UDP stack compatible with embassy-net; socket splitting

Open ivmarkov opened this issue 6 months ago • 8 comments

Addresses #103

As per the subject, this PR contains two changes (I can split into two separate PRs as well, but given that both would be backwards incompatible, and are touching the same traits, perhaps it is better to discuss those in one go):

  • The PR introduces lifetimes in the UdpStack::Connected, UdpStack::UniquelyBound and UdpStack::MultiplyBound associated types, making those GATs
    • The change is similar and following the lead as to how the the TcpConnect::Connection type is modeled already
    • It is necessary, so that the e-nal-async UdpStack trait is implementable in embassy-net and possibly other non-allocating network stacks using an identical approach to the already implemented TcpConnect factory trait
  • The second change in this PR is that it puts an extra requirement on the UdpSocket factory trait. Namely, that the sockets it returns should be splittable into two separate halves - "receive" and "send" which can be used concurrently in app protocols that do require full-duplex UDP datagram sending and receiving. This is done by:
    • Splitting the ConnectedUdp and UnconnectedUdp existing traits into separate "send" and "receive" traits: ConnectedUdpReceive, ConnectedUdpSend, UnconnectedUdpReceive and UnconnectedUdpSend - similarly as to how the TcpConnect::Connection associated type is constrained by two separate traits for reading and writing from/to TCP sockets - namely - embedded_io_async::Read and embedded_io_async::Write
    • Introducing two new traits: ConnectedUdpSplit and UnconnectedUdpSplit - which model the notion of a splittable connected/unconnected UDP socket by using Send/Receive lifetimed associated types.

Background on these changes, and justification for introducing UDP socket splitting can be found in #103

Open topics / next steps:

  • As per #103 I think we should come to an agreement that socket splitting is important and necessary.

  • Regardless of the outcome of the above, I think the separation of ConnectedUdp and UnconnectedUdp into "receive" and "send" parts stands on its own. Reason being - if a concrete network stack does allow UDP socket splitting, and even if we decide (regrettably) not to require this in the UdpSocket factory, app code that does need split UDP sockets would still benefit from the existence of separate "send" and "receive" traits, for the very same reasons we have separate Read and Write traits for TCP / streaming cases - i.e. the app code can take an "already splitted" in a platform-specific way UDP socket in the form of - e.g. async fn foo(send: impl UnconnectedUdpSend, recv: impl UnconnectedUdpReceive) and then proceed with polling these separately

  • (UPDATED) The existing traits' names are mouthful. How about the following simplification:

    • Rename:
      • UnconnectedUdpReceive -> UdpReceive
      • UnconnectedUdpSend -> UdpSend
      • UnconnectedUdpSplit -> UdpSplit
      • Retire ALL of the Connected* traits and use the Unconnected* ones for the connected case as well. We are anyway already a bit in this situation, by re-using the Unconnected* traits for "single-bound" sockets, even though the API signatures of the unconnected traits are not a perfect fit there, as they take / return the local socket addr, which serves no useful purpose when the socket is already single-bound to a fixed local IP address.

ivmarkov avatar Feb 07 '24 10:02 ivmarkov