ctru/services/
soc.rs

1//! Network Socket service.
2//!
3//! By using this service the program enables the use of network sockets and utilities such as those found in `std::net`, which are completely inaccessible by default.
4//! As such, remember to hold a handle to this service handle while using any network functionality, or else the `std::net` methods will return generic OS errors.
5#![doc(alias = "socket")]
6#![doc(alias = "network")]
7
8use libc::memalign;
9use std::net::Ipv4Addr;
10use std::sync::Mutex;
11
12use crate::Error;
13use crate::error::ResultCode;
14use crate::services::ServiceReference;
15
16/// Handle to the Network Socket service.
17pub struct Soc {
18    _service_handler: ServiceReference,
19    sock_3dslink: libc::c_int,
20}
21
22static SOC_ACTIVE: Mutex<()> = Mutex::new(());
23
24impl Soc {
25    /// Initialize a new service handle using a socket buffer size of `0x100000` bytes.
26    ///
27    /// # Errors
28    ///
29    /// This function will return an error if the [`Soc`] service is already being used.
30    ///
31    /// # Example
32    ///
33    /// ```
34    /// # let _runner = test_runner::GdbRunner::default();
35    /// # use std::error::Error;
36    /// # fn main() -> Result<(), Box<dyn Error>> {
37    /// #
38    /// use ctru::services::soc::Soc;
39    ///
40    /// let soc = Soc::new()?;
41    /// #
42    /// # Ok(())
43    /// # }
44    /// ```
45    #[doc(alias = "socInit")]
46    pub fn new() -> crate::Result<Self> {
47        Self::init_with_buffer_size(0x100000)
48    }
49
50    /// Initialize a new service handle using a custom socket buffer size.
51    ///
52    /// The size should be `0x100000` bytes or greater.
53    ///
54    /// # Errors
55    ///
56    /// This function will return an error if the [`Soc`] service is already being used.
57    ///
58    /// # Example
59    ///
60    /// ```
61    /// # let _runner = test_runner::GdbRunner::default();
62    /// # use std::error::Error;
63    /// # fn main() -> Result<(), Box<dyn Error>> {
64    /// #
65    /// use ctru::services::soc::Soc;
66    ///
67    /// let soc = Soc::init_with_buffer_size(0x100000)?;
68    /// #
69    /// # Ok(())
70    /// # }
71    /// ```
72    #[doc(alias = "socInit")]
73    pub fn init_with_buffer_size(num_bytes: usize) -> crate::Result<Self> {
74        let _service_handler = ServiceReference::new(
75            &SOC_ACTIVE,
76            || {
77                let soc_mem = unsafe { memalign(0x1000, num_bytes) } as *mut u32;
78                ResultCode(unsafe { ctru_sys::socInit(soc_mem, num_bytes as u32) })?;
79
80                Ok(())
81            },
82            // `socExit` returns an error code. There is no documentantion of when errors could happen,
83            // but we wouldn't be able to handle them in the `Drop` implementation anyways.
84            // Surely nothing bad will happens :D
85            || unsafe {
86                // The socket buffer is freed automatically by `socExit`
87                let _ = ctru_sys::socExit();
88            },
89        )?;
90
91        Ok(Self {
92            _service_handler,
93            sock_3dslink: -1,
94        })
95    }
96
97    /// Returns the local IP Address of the Nintendo 3DS system.
98    ///
99    /// # Example
100    ///
101    /// ```
102    /// # let _runner = test_runner::GdbRunner::default();
103    /// # use std::error::Error;
104    /// # fn main() -> Result<(), Box<dyn Error>> {
105    /// #
106    /// use ctru::services::soc::Soc;
107    /// let soc = Soc::new()?;
108    ///
109    /// let address = soc.host_address();
110    /// #
111    /// # Ok(())
112    /// # }
113    /// ```
114    #[doc(alias = "gethostid")]
115    pub fn host_address(&self) -> Ipv4Addr {
116        let raw_id = unsafe { libc::gethostid() };
117        Ipv4Addr::from(raw_id.to_ne_bytes())
118    }
119
120    /// Redirect output streams (i.e. `stdout` and `stderr`) to the `3dslink` server.
121    ///
122    /// With this redirection it is possible to send (and view in real time) the output of `stdout` operations,
123    /// such as `println!` or `dbg!`.
124    ///
125    /// Requires `3dslink` >= 0.6.1 and `new-hbmenu` >= 2.3.0 and the use of the `--server` flag.
126    /// The `--server` flag is also availble to use via `cargo-3ds` if the requirements are met.
127    ///
128    /// # Errors
129    ///
130    /// Returns an error if a connection cannot be established to the server,
131    /// or if the output was already being redirected.
132    ///
133    /// # Example
134    ///
135    /// ```
136    /// # let _runner = test_runner::GdbRunner::default();
137    /// # use std::error::Error;
138    /// # fn main() -> Result<(), Box<dyn Error>> {
139    /// #
140    /// use ctru::services::soc::Soc;
141    /// let mut soc = Soc::new()?;
142    ///
143    /// // Redirect to the `3dslink` server that sent this program.
144    /// let address = soc.redirect_to_3dslink(true, true)?;
145    ///
146    /// println!("I'm visible from a PC!");
147    /// #
148    /// # Ok(())
149    /// # }
150    /// ```
151    #[doc(alias = "link3dsConnectToHost")]
152    pub fn redirect_to_3dslink(&mut self, stdout: bool, stderr: bool) -> crate::Result<()> {
153        if self.sock_3dslink >= 0 {
154            return Err(Error::OutputAlreadyRedirected);
155        }
156
157        if !stdout && !stderr {
158            return Ok(());
159        }
160
161        self.sock_3dslink = unsafe { ctru_sys::link3dsConnectToHost(stdout, stderr) };
162        if self.sock_3dslink < 0 {
163            Err(Error::from_errno())
164        } else {
165            Ok(())
166        }
167    }
168}
169
170impl Drop for Soc {
171    #[doc(alias = "socExit")]
172    fn drop(&mut self) {
173        if self.sock_3dslink >= 0 {
174            unsafe {
175                libc::close(self.sock_3dslink);
176            }
177        }
178    }
179}
180
181#[cfg(test)]
182mod tests {
183    use super::*;
184
185    #[test]
186    fn soc_duplicate() {
187        let _soc = Soc::new().unwrap();
188
189        assert!(matches!(Soc::new(), Err(Error::ServiceAlreadyActive)))
190    }
191}