reticulum-pyrs 0.1.0

A rust wrapper for the python rns and lxmf libraries
Documentation
use anyhow::{Result, anyhow};
use pyo3::prelude::*;
use pyo3::types::{IntoPyDict, PyList};

use crate::utils::{ShortHash, StdErrLog, StdOutLog};

pub struct PyRNSIdentity(pub(crate) PyObject);

pub struct RNSDestination(pub(crate) PyRNSDestination);

pub(crate) struct PyRNSDestination(pub(crate) PyObject);

pub(crate) struct PyRNSDestinationHash(pub(crate) ShortHash);

pub struct PyRNS {
    pub(crate) identity: PyRNSIdentity,
    rns_mod: Py<PyModule>,
}

#[derive(Debug)]
pub struct PrivateKey(Vec<u8>);

impl PrivateKey {
    pub fn bytes(&self) -> &[u8] {
        &self.0
    }

    pub fn from_bytes(bytes: Vec<u8>) -> Self {
        Self(bytes)
    }
}

impl PyRNS {
    // Initialize rns, using either a provided identity key or if not, a randomly generated one
    pub fn initialize(identity_key: Option<PrivateKey>) -> Result<Self> {
        pyo3::prepare_freethreaded_python();
        Python::with_gil(|py| {
            let sys = py.import("sys")?;
            sys.setattr("stdout", StdOutLog.into_pyobject(py)?)?;
            sys.setattr("stderr", StdErrLog.into_pyobject(py)?)?;
            let rns_mod = PyModule::import(py, "RNS")?;
            let _ = rns_mod.call_method0("Reticulum")?;
            let identity = if let Some(private_key) = identity_key {
                let kwargs = [("create_keys", false)].into_py_dict(py)?;
                let id = rns_mod.call_method("Identity", (), Some(&kwargs))?;
                let r: bool = id
                    .call_method1("load_private_key", (private_key.0,))?
                    .extract()?;
                if !r {
                    return Err(anyhow!("Failed to load private key"));
                }
                id
            } else {
                rns_mod.call_method0("Identity")?
            };
            let identity = PyRNSIdentity(identity.unbind());
            Ok(Self {
                identity,
                rns_mod: rns_mod.unbind(),
            })
        })
    }

    pub fn generate_private_key() -> Result<PrivateKey> {
        pyo3::prepare_freethreaded_python();
        Python::with_gil(|py| {
            let rns_mod = PyModule::import(py, "RNS")?;
            let identity = rns_mod.call_method0("Identity")?;
            let key: Vec<u8> = identity.call_method0("get_private_key")?.extract()?;
            Ok(PrivateKey(key))
        })
    }

    pub fn identity(&self) -> &PyRNSIdentity {
        &self.identity
    }

    pub fn get_private_key(&self) -> Result<PrivateKey> {
        Python::with_gil(|py| {
            let key = self.identity.0.bind(py).call_method0("get_private_key")?;
            let key: Vec<u8> = key.extract()?;
            Ok(PrivateKey(key))
        })
    }

    pub fn transport_has_path(&self, hash: &ShortHash) -> Result<bool> {
        Python::with_gil(|py| {
            let has_path: bool = self
                .rns_mod
                .getattr(py, "Transport")?
                .call_method1(py, "has_path", (hash.value(),))?
                .extract(py)?;
            Ok(has_path)
        })
    }

    pub fn transport_request_path(&self, hash: &ShortHash) -> Result<()> {
        Python::with_gil(|py| {
            self.rns_mod.getattr(py, "Transport")?.call_method1(
                py,
                "request_path",
                (hash.value(),),
            )?;
            Ok(())
        })
    }

    /// RNS will attempt to recall the Identity of the given Destination hash
    pub fn identity_recall(&self, hash: &ShortHash) -> Result<PyRNSIdentity> {
        Python::with_gil(|py| {
            let id = self.rns_mod.getattr(py, "Identity")?.call_method1(
                py,
                "recall",
                (hash.value(),),
            )?;
            Ok(PyRNSIdentity(id))
        })
    }

    /// RNS will attempt to recall the "app_data" of the given Destination hash
    pub fn identity_recall_app_data(&self, hash: &ShortHash) -> Result<Option<Vec<u8>>> {
        Python::with_gil(|py| {
            let app_data: Option<Vec<u8>> = self
                .rns_mod
                .getattr(py, "Identity")?
                .call_method1(py, "recall_app_data", (hash.value(),))?
                .extract(py)?;
            Ok(app_data)
        })
    }

    pub fn create_destination(&self, recipient_identity: &PyRNSIdentity) -> Result<RNSDestination> {
        Python::with_gil(|py| {
            let args = PyList::empty(py);
            args.append(&recipient_identity.0)?;
            args.append(DestinationDirections::OUT as isize)?;
            args.append(DestinationTypes::SINGLE as isize)?;
            args.append("lxmf")?;
            args.append("delivery")?;
            let destination = self
                .rns_mod
                .call_method(py, "Destination", args.to_tuple(), None)?;
            Ok(RNSDestination(PyRNSDestination(destination)))
        })
    }
}

pub enum DestinationTypes {
    SINGLE = 0x00,
    GROUP = 0x01,
    PLAIN = 0x02,
    LINK = 0x03,
}

pub enum DestinationDirections {
    IN = 0x11,
    OUT = 0x12,
}

#[cfg(test)]
mod tests {
    use crate::rns::{DestinationDirections, DestinationTypes};

    #[test]
    fn enum_discrimnants_work() {
        let single = DestinationTypes::SINGLE;
        assert_eq!(0, single as isize);
        let plain = DestinationTypes::PLAIN;
        assert_eq!(2, plain as isize);
        let out = DestinationDirections::OUT;
        assert_eq!(18, out as isize);
    }
}