tor_rtcompat/impls/
streamops.rs

1//! Helpers for implementing [`StreamOps`].
2
3use std::io;
4
5#[cfg(target_os = "linux")]
6use {
7    std::mem,
8    std::os::fd::{AsRawFd, RawFd},
9};
10
11use crate::StreamOps;
12#[cfg(not(target_os = "linux"))]
13use crate::UnsupportedStreamOp;
14
15/// A wrapper over the file descriptor of a TCP socket.
16///
17/// Returned from various [`StreamOps::new_handle`] impls.
18#[derive(Clone, Copy)]
19#[cfg(target_os = "linux")]
20pub(crate) struct TcpSockFd(RawFd);
21
22#[cfg(target_os = "linux")]
23impl AsRawFd for TcpSockFd {
24    fn as_raw_fd(&self) -> RawFd {
25        self.0
26    }
27}
28
29#[cfg(target_os = "linux")]
30impl TcpSockFd {
31    /// Create a `TcpSockFd` out of a raw file descriptor.
32    pub(crate) fn from_fd(fd: &impl AsRawFd) -> Self {
33        Self(fd.as_raw_fd())
34    }
35}
36
37#[cfg(target_os = "linux")]
38impl StreamOps for TcpSockFd {
39    fn set_tcp_notsent_lowat(&self, notsent_lowat: u32) -> io::Result<()> {
40        set_tcp_notsent_lowat(self, notsent_lowat)
41    }
42
43    fn new_handle(&self) -> Box<dyn StreamOps + Send + Unpin> {
44        Box::new(*self)
45    }
46}
47
48/// Helper for implementing [`set_tcp_notsent_lowat`](crate::StreamOps::set_tcp_notsent_lowat).
49///
50/// Only implemented on Linux. Returns an error on all other platforms.
51#[cfg(target_os = "linux")]
52pub(crate) fn set_tcp_notsent_lowat<S: AsRawFd>(sock: &S, notsent_lowat: u32) -> io::Result<()> {
53    let fd = sock.as_raw_fd();
54    let res = unsafe {
55        libc::setsockopt(
56            fd,
57            libc::SOL_TCP,
58            libc::TCP_NOTSENT_LOWAT,
59            &notsent_lowat as *const _ as *const libc::c_void,
60            mem::size_of_val(&notsent_lowat) as libc::socklen_t,
61        )
62    };
63
64    if res != 0 {
65        return Err(io::Error::last_os_error());
66    }
67
68    Ok(())
69}
70
71/// Helper for implementing [`set_tcp_notsent_lowat`](crate::StreamOps::set_tcp_notsent_lowat).
72///
73/// Only implemented on Linux. Returns an error on all other platforms.
74#[cfg(not(target_os = "linux"))]
75pub(crate) fn set_tcp_notsent_lowat<S>(_sock: &S, _notsent_lowat: u32) -> io::Result<()> {
76    Err(UnsupportedStreamOp::new(
77        "set_tcp_notsent_lowat",
78        "unsupported on non-linux platforms",
79    )
80    .into())
81}
82
83#[cfg(test)]
84mod tests {
85    // @@ begin test lint list maintained by maint/add_warning @@
86    #![allow(clippy::bool_assert_comparison)]
87    #![allow(clippy::clone_on_copy)]
88    #![allow(clippy::dbg_macro)]
89    #![allow(clippy::mixed_attributes_style)]
90    #![allow(clippy::print_stderr)]
91    #![allow(clippy::print_stdout)]
92    #![allow(clippy::single_char_pattern)]
93    #![allow(clippy::unwrap_used)]
94    #![allow(clippy::unchecked_duration_subtraction)]
95    #![allow(clippy::useless_vec)]
96    #![allow(clippy::needless_pass_by_value)]
97    //! <!-- @@ end test lint list maintained by maint/add_warning @@ -->
98    use super::*;
99    use std::net::TcpListener;
100
101    #[cfg(target_os = "linux")]
102    pub(crate) fn get_tcp_notsent_lowat<S: AsRawFd>(sock: &S) -> io::Result<u32> {
103        let fd = sock.as_raw_fd();
104        let mut notsent_lowat = 0;
105        let mut socklen: u32 = mem::size_of_val(&notsent_lowat) as libc::socklen_t;
106        let res = unsafe {
107            libc::getsockopt(
108                fd,
109                libc::SOL_TCP,
110                libc::TCP_NOTSENT_LOWAT,
111                &mut notsent_lowat as *mut _ as *mut libc::c_void,
112                &mut socklen as *mut _,
113            )
114        };
115
116        if res != 0 {
117            return Err(io::Error::last_os_error());
118        }
119
120        Ok(notsent_lowat)
121    }
122
123    #[test]
124    #[cfg(target_os = "linux")]
125    #[cfg_attr(miri, ignore)] // sockets are unsupported https://github.com/rust-lang/miri/issues/3449
126    fn tcp_notsent_lowat() {
127        let sock = TcpListener::bind("127.0.0.1:0").unwrap();
128        set_tcp_notsent_lowat(&sock, 1337).unwrap();
129        let notsent_lowat = get_tcp_notsent_lowat(&sock).unwrap();
130        assert_eq!(1337, notsent_lowat);
131    }
132
133    #[test]
134    #[cfg(not(target_os = "linux"))]
135    #[cfg_attr(miri, ignore)] // sockets are unsupported https://github.com/rust-lang/miri/issues/3449
136    fn tcp_notsent_lowat() {
137        let sock = TcpListener::bind("127.0.0.1:0").unwrap();
138        // Currently not supported on non-linux platforms
139        assert!(set_tcp_notsent_lowat(&sock, 1337).is_err());
140    }
141}