Repository: rust-lang/socket2
Pull request #161 (2020-12-21 14:23:13)

Change Socket::recv to accept [MaybeUninit<u8>]
Allow uninitialised buffers to be used. Also in the following functions:

* Socket::recv_out_of_band
* Socket::recv_with_flags
* Socket::peek
* Socket::recv_from
* Socket::recv_from_with_flags
* Socket::peek_from


Patch:
diff --git a/README.md b/README.md
index 3569286..a4a707b 100644
--- a/README.md
+++ b/README.md
@@ -1,9 +1,62 @@
 # Socket2
 
+Socket2 is a crate that provides utilities for creating and using sockets.
+
+The goal of this crate is to create and use a socket using advanced
+configuration options (those that are not available in the types in the standard
+library) without using any unsafe code.
+
+This crate provides as direct as possible access to the system's functionality
+for sockets, this means little effort to provide cross-platform utilities. It is
+up to the user to know how to use sockets when using this crate. *If you don't
+know how to create a socket using libc/system calls then this crate is not for
+you*. Most, if not all, functions directly relate to the equivalent system call
+with no error handling applied, so no handling errors such as `EINTR`. As a
+result using this crate can be a little wordy, but it should give you maximal
+flexibility over configuration of sockets.
+
 See the [API documentation] for more.
 
 [API documentation]: https://docs.rs/socket2
 
+# OS support
+
+Socket2 attempts to support the same OS/architectures as Rust does, see
+https://doc.rust-lang.org/nightly/rustc/platform-support.html. However this is
+not always possible, below is current list of support OSs.
+
+*If your favorite OS is not on the list consider contributing it! See [issue
+#78].*
+
+[issue #78]: https://github.com/rust-lang/socket2/issues/78
+
+### Tier 1
+
+These OSs are tested with each commit in the CI and must always pass the tests.
+All functions/types/etc., excluding ones behind the `all` feature, must work on
+these OSs.
+
+* Linux
+* macOS
+* Windows
+
+### Tier 2
+
+These OSs are currently build in the CI, but not tested. Not all
+functions/types/etc. may work on these OSs, even ones **not** behind the `all`
+feature flag.
+
+* Redox
+
+### Tier 3
+
+Theses OSs are supported, but are not checked in the CI.
+
+* Android
+* FreeBSD
+* iOS
+* NetBSD
+
 # License
 
 This project is licensed under either of
diff --git a/src/lib.rs b/src/lib.rs
index 1c9f725..1783bfe 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -20,14 +20,14 @@
 //! standard library) without using any unsafe code.
 //!
 //! This crate provides as direct as possible access to the system's
-//! functionality for sockets, this means **no** effort to provide
-//! cross-platform utilities, no extra goodies, no creature comforts. It is up
-//! to the user to know how to use sockets when using this crate. *If you don't
-//! know how to create a socket using libc/system calls then this crate is not
-//! for you*. Most, if not all, functions directly relate to the equivalent
-//! system call with no error handling applied, so no handling errors such as
-//! [`EINTR`]. As a result using this crate can be a little wordy, but it should
-//! give you maximal flexibility over configuration of sockets.
+//! functionality for sockets, this means little effort to provide
+//! cross-platform utilities. It is up to the user to know how to use sockets
+//! when using this crate. *If you don't know how to create a socket using
+//! libc/system calls then this crate is not for you*. Most, if not all,
+//! functions directly relate to the equivalent system call with no error
+//! handling applied, so no handling errors such as [`EINTR`]. As a result using
+//! this crate can be a little wordy, but it should give you maximal flexibility
+//! over configuration of sockets.
 //!
 //! [`EINTR`]: std::io::ErrorKind::Interrupted
 //!
diff --git a/src/socket.rs b/src/socket.rs
index c025cfa..fdb5c37 100644
--- a/src/socket.rs
+++ b/src/socket.rs
@@ -10,6 +10,7 @@ use std::fmt;
 use std::io::{self, Read, Write};
 #[cfg(not(target_os = "redox"))]
 use std::io::{IoSlice, IoSliceMut};
+use std::mem::MaybeUninit;
 use std::net::{self, Ipv4Addr, Ipv6Addr, Shutdown};
 #[cfg(unix)]
 use std::os::unix::io::{FromRawFd, IntoRawFd};
@@ -188,6 +189,7 @@ impl Socket {
             target_os = "android",
             target_os = "dragonfly",
             target_os = "freebsd",
+            target_os = "illumos",
             target_os = "linux",
             target_os = "netbsd",
             target_os = "openbsd",
@@ -199,6 +201,7 @@ impl Socket {
             target_os = "android",
             target_os = "dragonfly",
             target_os = "freebsd",
+            target_os = "illumos",
             target_os = "linux",
             target_os = "netbsd",
             target_os = "openbsd",
@@ -284,7 +287,20 @@ impl Socket {
     /// This method might fail if the socket is not connected.
     ///
     /// [`connect`]: Socket::connect
-    pub fn recv(&self, buf: &mut [u8]) -> io::Result<usize> {
+    ///
+    /// # Safety
+    ///
+    /// Normally casting a `&mut [u8]` to `&mut [MaybeUninit<u8>]` would be
+    /// unsound, as that allows us to write uninitialised bytes to the buffer.
+    /// However this implementation promises to not write uninitialised bytes to
+    /// the `buf`fer and passes it directly to `recv(2)` system call. This
+    /// promise ensures that this function can be called using a `buf`fer of
+    /// type `&mut [u8]`.
+    ///
+    /// Note that the [`io::Read::read`] implementation calls this function with
+    /// a `buf`fer of type `&mut [u8]`, allowing initialised buffers to be used
+    /// without using `unsafe`.
+    pub fn recv(&self, buf: &mut [MaybeUninit<u8>]) -> io::Result<usize> {
         self.recv_with_flags(buf, 0)
     }
 
@@ -295,7 +311,7 @@ impl Socket {
     ///
     /// [`recv`]: Socket::recv
     /// [`out_of_band_inline`]: Socket::out_of_band_inline
-    pub fn recv_out_of_band(&self, buf: &mut [u8]) -> io::Result<usize> {
+    pub fn recv_out_of_band(&self, buf: &mut [MaybeUninit<u8>]) -> io::Result<usize> {
         self.recv_with_flags(buf, sys::MSG_OOB)
     }
 
@@ -303,7 +319,11 @@ impl Socket {
     /// the underlying `recv` call.
     ///
     /// [`recv`]: Socket::recv
-    pub fn recv_with_flags(&self, buf: &mut [u8], flags: sys::c_int) -> io::Result<usize> {
+    pub fn recv_with_flags(
+        &self,
+        buf: &mut [MaybeUninit<u8>],
+        flags: sys::c_int,
+    ) -> io::Result<usize> {
         sys::recv(self.inner, buf, flags)
     }
 
@@ -343,13 +363,27 @@ impl Socket {
     ///
     /// Successive calls return the same data. This is accomplished by passing
     /// `MSG_PEEK` as a flag to the underlying `recv` system call.
-    pub fn peek(&self, buf: &mut [u8]) -> io::Result<usize> {
+    ///
+    /// # Safety
+    ///
+    /// `peek` makes the same safety guarantees regarding the `buf`fer as
+    /// [`recv`].
+    ///
+    /// [`recv`]: Socket::recv
+    pub fn peek(&self, buf: &mut [MaybeUninit<u8>]) -> io::Result<usize> {
         self.recv_with_flags(buf, sys::MSG_PEEK)
     }
 
     /// Receives data from the socket. On success, returns the number of bytes
     /// read and the address from whence the data came.
-    pub fn recv_from(&self, buf: &mut [u8]) -> io::Result<(usize, SockAddr)> {
+    ///
+    /// # Safety
+    ///
+    /// `recv_from` makes the same safety guarantees regarding the `buf`fer as
+    /// [`recv`].
+    ///
+    /// [`recv`]: Socket::recv
+    pub fn recv_from(&self, buf: &mut [MaybeUninit<u8>]) -> io::Result<(usize, SockAddr)> {
         self.recv_from_with_flags(buf, 0)
     }
 
@@ -359,7 +393,7 @@ impl Socket {
     /// [`recv_from`]: Socket::recv_from
     pub fn recv_from_with_flags(
         &self,
-        buf: &mut [u8],
+        buf: &mut [MaybeUninit<u8>],
         flags: i32,
     ) -> io::Result<(usize, SockAddr)> {
         sys::recv_from(self.inner, buf, flags)
@@ -398,7 +432,14 @@ impl Socket {
     ///
     /// On success, returns the number of bytes peeked and the address from
     /// whence the data came.
-    pub fn peek_from(&self, buf: &mut [u8]) -> io::Result<(usize, SockAddr)> {
+    ///
+    /// # Safety
+    ///
+    /// `peek_from` makes the same safety guarantees regarding the `buf`fer as
+    /// [`recv`].
+    ///
+    /// [`recv`]: Socket::recv
+    pub fn peek_from(&self, buf: &mut [MaybeUninit<u8>]) -> io::Result<(usize, SockAddr)> {
         self.recv_from_with_flags(buf, sys::MSG_PEEK)
     }
 
@@ -493,6 +534,7 @@ fn set_common_type(ty: Type) -> Type {
         target_os = "android",
         target_os = "dragonfly",
         target_os = "freebsd",
+        target_os = "illumos",
         target_os = "linux",
         target_os = "netbsd",
         target_os = "openbsd",
@@ -516,6 +558,7 @@ fn set_common_flags(socket: Socket) -> io::Result<Socket> {
             target_os = "android",
             target_os = "dragonfly",
             target_os = "freebsd",
+            target_os = "illumos",
             target_os = "linux",
             target_os = "netbsd",
             target_os = "openbsd",
@@ -1241,6 +1284,9 @@ impl Socket {
 
 impl Read for Socket {
     fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
+        // Safety: the `recv` implementation promises not to write uninitialised
+        // bytes to the `buf`fer, so this casting is safe.
+        let buf = unsafe { &mut *(buf as *mut [u8] as *mut [MaybeUninit<u8>]) };
         self.recv(buf)
     }
 
@@ -1252,6 +1298,8 @@ impl Read for Socket {
 
 impl<'a> Read for &'a Socket {
     fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
+        // Safety: see other `Read::read` impl.
+        let buf = unsafe { &mut *(buf as *mut [u8] as *mut [MaybeUninit<u8>]) };
         self.recv(buf)
     }
 
diff --git a/src/sys/unix.rs b/src/sys/unix.rs
index 84699b7..32780ca 100644
--- a/src/sys/unix.rs
+++ b/src/sys/unix.rs
@@ -187,6 +187,7 @@ impl Type {
             target_os = "android",
             target_os = "dragonfly",
             target_os = "freebsd",
+            target_os = "illumos",
             target_os = "linux",
             target_os = "netbsd",
             target_os = "openbsd"
@@ -208,6 +209,7 @@ impl Type {
             target_os = "android",
             target_os = "dragonfly",
             target_os = "freebsd",
+            target_os = "illumos",
             target_os = "linux",
             target_os = "netbsd",
             target_os = "openbsd"
@@ -221,6 +223,7 @@ impl Type {
         target_os = "android",
         target_os = "dragonfly",
         target_os = "freebsd",
+        target_os = "illumos",
         target_os = "linux",
         target_os = "netbsd",
         target_os = "openbsd"
@@ -416,7 +419,7 @@ pub(crate) fn shutdown(fd: Socket, how: Shutdown) -> io::Result<()> {
     syscall!(shutdown(fd, how)).map(|_| ())
 }
 
-pub(crate) fn recv(fd: Socket, buf: &mut [u8], flags: c_int) -> io::Result<usize> {
+pub(crate) fn recv(fd: Socket, buf: &mut [MaybeUninit<u8>], flags: c_int) -> io::Result<usize> {
     syscall!(recv(
         fd,
         buf.as_mut_ptr().cast(),
@@ -426,7 +429,11 @@ pub(crate) fn recv(fd: Socket, buf: &mut [u8], flags: c_int) -> io::Result<usize
     .map(|n| n as usize)
 }
 
-pub(crate) fn recv_from(fd: Socket, buf: &mut [u8], flags: c_int) -> io::Result<(usize, SockAddr)> {
+pub(crate) fn recv_from(
+    fd: Socket,
+    buf: &mut [MaybeUninit<u8>],
+    flags: c_int,
+) -> io::Result<(usize, SockAddr)> {
     // Safety: `recvfrom` initialises the `SockAddr` for us.
     unsafe {
         SockAddr::init(|addr, addrlen| {
diff --git a/src/sys/windows.rs b/src/sys/windows.rs
index 3720eef..e595b58 100644
--- a/src/sys/windows.rs
+++ b/src/sys/windows.rs
@@ -273,7 +273,7 @@ pub(crate) fn shutdown(socket: Socket, how: Shutdown) -> io::Result<()> {
     syscall!(shutdown(socket, how), PartialEq::eq, sock::SOCKET_ERROR).map(|_| ())
 }
 
-pub(crate) fn recv(socket: Socket, buf: &mut [u8], flags: c_int) -> io::Result<usize> {
+pub(crate) fn recv(socket: Socket, buf: &mut [MaybeUninit<u8>], flags: c_int) -> io::Result<usize> {
     let res = syscall!(
         recv(
             socket,
@@ -325,7 +325,7 @@ pub(crate) fn recv_vectored(
 
 pub(crate) fn recv_from(
     socket: Socket,
-    buf: &mut [u8],
+    buf: &mut [MaybeUninit<u8>],
     flags: c_int,
 ) -> io::Result<(usize, SockAddr)> {
     // Safety: `recvfrom` initialises the `SockAddr` for us.


Test patch:
diff --git a/tests/socket.rs b/tests/socket.rs
index 5200fc4..2b44b60 100644
--- a/tests/socket.rs
+++ b/tests/socket.rs
@@ -7,6 +7,7 @@ use std::io::Read;
 use std::io::Write;
 #[cfg(not(target_os = "redox"))]
 use std::io::{IoSlice, IoSliceMut};
+use std::mem::MaybeUninit;
 use std::net::{Ipv4Addr, Ipv6Addr, SocketAddr, SocketAddrV4, SocketAddrV6};
 #[cfg(unix)]
 use std::os::unix::io::AsRawFd;
@@ -359,14 +360,14 @@ fn out_of_band() {
     // this from happening we'll sleep to ensure the data is present.
     thread::sleep(Duration::from_millis(10));
 
-    let mut buf = [1; DATA.len() + 1];
+    let mut buf = [MaybeUninit::new(1); DATA.len() + 1];
     let n = receiver.recv_out_of_band(&mut buf).unwrap();
     assert_eq!(n, FIRST.len());
-    assert_eq!(&buf[..n], FIRST);
+    assert_eq!(unsafe { assume_init(&buf[..n]) }, FIRST);
 
     let n = receiver.recv(&mut buf).unwrap();
     assert_eq!(n, DATA.len());
-    assert_eq!(&buf[..n], DATA);
+    assert_eq!(unsafe { assume_init(&buf[..n]) }, DATA);
 }
 
 #[test]
@@ -643,6 +644,12 @@ fn any_ipv4() -> SockAddr {
     SocketAddrV4::new(Ipv4Addr::LOCALHOST, 0).into()
 }
 
+/// Assume the `buf`fer to be initialised.
+// TODO: replace with `MaybeUninit::slice_assume_init_ref` once stable.
+unsafe fn assume_init(buf: &[MaybeUninit<u8>]) -> &[u8] {
+    &*(buf as *const [MaybeUninit<u8>] as *const [u8])
+}
+
 /// Macro to create a simple test to set and get a socket option.
 macro_rules! test {
     // Test using the `arg`ument as expected return value.