Skip to main content

_native/
lib.rs

1use std::sync::Arc;
2
3use pyo3::exceptions::PyRuntimeError;
4use pyo3::prelude::*;
5use pyo3::types::{PyAny, PyBytes};
6
7#[pyclass(name = "Session", frozen)]
8struct PySession {
9    inner: Arc<onionlink_core::Session>,
10}
11
12fn map_err(err: onionlink_core::Error) -> PyErr {
13    PyRuntimeError::new_err(err.to_string())
14}
15
16fn map_join_err(err: tokio::task::JoinError) -> PyErr {
17    PyRuntimeError::new_err(format!("async runtime task failed: {err}"))
18}
19
20#[pymethods]
21impl PySession {
22    #[new]
23    #[pyo3(signature = (bootstrap = "128.31.0.39:9131", consensus_file = "", timeout_ms = 30000, verbose = false))]
24    fn new(
25        py: Python<'_>,
26        bootstrap: &str,
27        consensus_file: &str,
28        timeout_ms: i32,
29        verbose: bool,
30    ) -> PyResult<Self> {
31        let inner = py
32            .detach(|| onionlink_core::Session::new(bootstrap, consensus_file, timeout_ms, verbose))
33            .map_err(map_err)?;
34        Ok(Self {
35            inner: Arc::new(inner),
36        })
37    }
38
39    #[staticmethod]
40    #[pyo3(signature = (bootstrap = "128.31.0.39:9131", consensus_file = "", timeout_ms = 30000, verbose = false))]
41    fn create_async<'py>(
42        py: Python<'py>,
43        bootstrap: &str,
44        consensus_file: &str,
45        timeout_ms: i32,
46        verbose: bool,
47    ) -> PyResult<Bound<'py, PyAny>> {
48        let bootstrap = bootstrap.to_owned();
49        let consensus_file = consensus_file.to_owned();
50        pyo3_async_runtimes::tokio::future_into_py(py, async move {
51            let inner = tokio::task::spawn_blocking(move || {
52                onionlink_core::Session::new(&bootstrap, &consensus_file, timeout_ms, verbose)
53            })
54            .await
55            .map_err(map_join_err)?
56            .map_err(map_err)?;
57
58            Python::attach(|py| {
59                Py::new(
60                    py,
61                    PySession {
62                        inner: Arc::new(inner),
63                    },
64                )
65            })
66        })
67    }
68
69    #[pyo3(signature = (onion, port, payload = Vec::<u8>::new(), response_limit = 4 * 1024 * 1024))]
70    fn request<'py>(
71        &self,
72        py: Python<'py>,
73        onion: &str,
74        port: u16,
75        payload: Vec<u8>,
76        response_limit: usize,
77    ) -> PyResult<Bound<'py, PyBytes>> {
78        let inner = Arc::clone(&self.inner);
79        let onion = onion.to_owned();
80        let inbound = py
81            .detach(move || inner.request(&onion, port, &payload, response_limit))
82            .map_err(map_err)?;
83        Ok(PyBytes::new(py, &inbound))
84    }
85
86    #[pyo3(signature = (onion, port, payload = Vec::<u8>::new(), response_limit = 4 * 1024 * 1024))]
87    fn request_async<'py>(
88        &self,
89        py: Python<'py>,
90        onion: &str,
91        port: u16,
92        payload: Vec<u8>,
93        response_limit: usize,
94    ) -> PyResult<Bound<'py, PyAny>> {
95        let inner = Arc::clone(&self.inner);
96        let onion = onion.to_owned();
97        pyo3_async_runtimes::tokio::future_into_py(py, async move {
98            let inbound = tokio::task::spawn_blocking(move || {
99                inner.request(&onion, port, &payload, response_limit)
100            })
101            .await
102            .map_err(map_join_err)?
103            .map_err(map_err)?;
104
105            Python::attach(|py| Ok(PyBytes::new(py, &inbound).unbind()))
106        })
107    }
108
109    #[pyo3(signature = (onion, port = 80, path = "/", response_limit = 4 * 1024 * 1024))]
110    fn http_get<'py>(
111        &self,
112        py: Python<'py>,
113        onion: &str,
114        port: u16,
115        path: &str,
116        response_limit: usize,
117    ) -> PyResult<Bound<'py, PyBytes>> {
118        let inner = Arc::clone(&self.inner);
119        let onion = onion.to_owned();
120        let path = path.to_owned();
121        let inbound = py
122            .detach(move || inner.http_get(&onion, port, &path, response_limit))
123            .map_err(map_err)?;
124        Ok(PyBytes::new(py, &inbound))
125    }
126
127    #[pyo3(signature = (onion, port = 80, path = "/", response_limit = 4 * 1024 * 1024))]
128    fn http_get_async<'py>(
129        &self,
130        py: Python<'py>,
131        onion: &str,
132        port: u16,
133        path: &str,
134        response_limit: usize,
135    ) -> PyResult<Bound<'py, PyAny>> {
136        let inner = Arc::clone(&self.inner);
137        let onion = onion.to_owned();
138        let path = path.to_owned();
139        pyo3_async_runtimes::tokio::future_into_py(py, async move {
140            let inbound = tokio::task::spawn_blocking(move || {
141                inner.http_get(&onion, port, &path, response_limit)
142            })
143            .await
144            .map_err(map_join_err)?
145            .map_err(map_err)?;
146
147            Python::attach(|py| Ok(PyBytes::new(py, &inbound).unbind()))
148        })
149    }
150}
151
152#[pymodule(gil_used = false)]
153fn _native(m: &Bound<'_, PyModule>) -> PyResult<()> {
154    m.add("__doc__", "Native Rust bindings for onionlink")?;
155    m.add_class::<PySession>()?;
156    Ok(())
157}