I am writing a client library for a bespoke local RPC protocol. The service implementation is not under my control, so I can't change the protocol. Inconveniently, the protocol packet format embeds libc::sockaddr_* structs directly in each message; the message format looks something like this
#[repr(C)] union PacketAddr { sin: libc::sockaddr_in, sin6: libc::sockaddr_in6, ss: libc::sockaddr_storage, } #[repr(C)] struct Packet { packet_len: u32, proto_ver: u32, msg_type: u32, addr_len: u32, addr: PacketAddr, annotation: [u8], }; (The addr_len field tells you which field of the PacketAddr union is live but does not control where annotation starts; addr is always the full size of the union, zero-padded internally if necessary. annotation runs from that point to the end of the packet as defined by packet_len.)
Now, in order to talk this protocol over a local communication channel, one must somehow go from the above struct definition to a &[u8] that can be passed to libc::send. The problem is, the fields of sockaddr_* are not fully pinned down by any standard and in practice do vary between Unixes (N.B. this software is never going to support Windows), so I cannot copy addresses (along with the rest of the packet) field by field into a Cursor<Vec<u8>>, which is what I would usually do.
zerocopy's IntoBytes trait would be just the ticket, but I cannot use it because of the orphan rule (I'm not allowed to #[derive(IntoBytes)] for sockaddr_in because neither of those is defined by my crate).
Instead I came up with this hack:
/// Coerce an arbitrary type S (presumably some kind of struct sockaddr_XX) /// to a &[u8] slice covering its bytes. unsafe fn sockaddr_as_bytes<S>(sa: &S) -> &[u8] { // SAFETY: This fabricates an &[u8] slice that covers `sa` // exactly. Each byte of the slice is valid for read because `sa` // itself was valid for read. &[u8] cannot be misaligned and u8 // has no invalid states. Caller is responsible for ensuring S // has a fixed layout and `sa` is fully initialized including padding. unsafe { slice::from_raw_parts(sa as *const S as *const u8, mem::size_of::<S>()) } } ... which is basically what zerocopy's IntoBytes would do for me, but with fewer safety rails. It works, but I don't feel particularly good about it.
So the questions I have are:
- Is there a more "official" way to do the above? That is, did I miss something in
std::memorstd::ptrthat encapsulates at least some of the unsafety? - Is there a way to subvert the orphan rule so I can use zerocopy? Or is there some other third-party crate that would be helpful?
- Supposing I'm stuck with the unsafe hack, is my understanding of what needs to be true for the function to be OK to use (as encapsulated by the safety comment) accurate? Did I miss anything important?
2 posts - 2 participants