nwep-rs 0.1.8

Rust bindings for the NWEP (WEB/1) protocol library
Documentation
#![allow(unsafe_op_in_unsafe_fn)]

use crate::error::{Error, check};
use crate::ffi;
use crate::merkle::{MerkleEntry, MerkleLog};
use crate::types::NodeId;

/// `LogServerSettings` controls the behaviour of a [`LogServer`] instance.
///
/// All fields are optional.  The default value (via [`Default`]) leaves `authorize`
/// unset, which causes the server to reject every write with `ERR_PROTO_UNAUTHORIZED`.
pub struct LogServerSettings {
    /// Optional callback invoked for every write request before it is applied.
    ///
    /// Receives the requester's [`NodeId`] and the proposed [`MerkleEntry`].  Return
    /// `Ok(())` to allow the write or `Err(e)` to reject it with the given error code.
    /// When `None`, all writes are unconditionally rejected.
    pub authorize: Option<Box<dyn Fn(&NodeId, &MerkleEntry) -> Result<(), Error> + Send>>,
}

impl Default for LogServerSettings {
    fn default() -> Self {
        LogServerSettings { authorize: None }
    }
}

struct Callbacks {
    authorize: Option<Box<dyn Fn(&NodeId, &MerkleEntry) -> Result<(), Error> + Send>>,
}

unsafe extern "C" fn authorize_cb(
    user_data: *mut std::ffi::c_void,
    nodeid: *const ffi::nwep_nodeid,
    entry: *const ffi::nwep_merkle_entry,
) -> std::ffi::c_int {
    let cb = &*(user_data as *const Callbacks);
    if let Some(f) = &cb.authorize {
        let nid = NodeId((*nodeid).data);
        let e = MerkleEntry::from_ffi(&*entry);
        match f(&nid, &e) {
            Ok(()) => 0,
            Err(e) => e.code,
        }
    } else {
        crate::error::ERR_PROTO_UNAUTHORIZED
    }
}

/// `LogServer` wraps the NWEP C library's Merkle log server.
///
/// `LogServer` handles HTTP-like requests to the `/log` and `/log/*` endpoints,
/// enabling peers to submit new Merkle log entries and read existing ones.  The server
/// holds a mutable reference to the underlying [`MerkleLog`] for the duration of its
/// lifetime.
///
/// # Safety
///
/// The raw pointer `ptr` must remain valid for the lifetime of this value.  `LogServer`
/// is `Send` because access to the C object is mediated through `&mut self` methods.
pub struct LogServer {
    ptr: *mut ffi::nwep_log_server,
    _callbacks: Box<Callbacks>,
}

unsafe impl Send for LogServer {}

impl LogServer {
    /// `new` creates a new log server backed by the given [`MerkleLog`].
    ///
    /// The `settings` argument configures the optional write-authorization callback.
    /// Ownership of the callback closures is transferred into the returned `LogServer`
    /// and kept alive for as long as the server exists.
    ///
    /// # Errors
    ///
    /// Returns `Err` if the underlying C call fails (e.g. allocation failure or an
    /// invalid log pointer).
    ///
    /// # Examples
    ///
    /// ```no_run
    /// # use nwep::{logserver::{LogServer, LogServerSettings}, merkle::{MerkleLog, LogStorage}, error::Error};
    /// # struct MemLog(Vec<Vec<u8>>);
    /// # impl LogStorage for MemLog {
    /// #     fn append(&mut self, _: u64, e: &[u8]) -> Result<(), Error> { self.0.push(e.to_vec()); Ok(()) }
    /// #     fn get(&self, i: u64, buf: &mut [u8]) -> Result<usize, Error> { let d = &self.0[i as usize]; let n = d.len().min(buf.len()); buf[..n].copy_from_slice(&d[..n]); Ok(n) }
    /// #     fn size(&self) -> u64 { self.0.len() as u64 }
    /// # }
    /// let mut log = MerkleLog::new(MemLog(vec![])).unwrap();
    /// let settings = LogServerSettings {
    ///     authorize: Some(Box::new(|node_id, entry| {
    ///         // allow all writes from any peer
    ///         Ok(())
    ///     })),
    /// };
    /// let server = LogServer::new(&mut log, settings).unwrap();
    /// ```
    pub fn new(log: &mut MerkleLog, settings: LogServerSettings) -> Result<Self, Error> {
        let has_authorize = settings.authorize.is_some();
        let mut cb = Box::new(Callbacks {
            authorize: settings.authorize,
        });
        let cb_ptr = cb.as_mut() as *mut _ as *mut std::ffi::c_void;

        let ffi_settings = ffi::nwep_log_server_settings {
            authorize: if has_authorize {
                Some(authorize_cb)
            } else {
                None
            },
            user_data: cb_ptr,
        };

        let mut ptr: *mut ffi::nwep_log_server = std::ptr::null_mut();
        check(unsafe { ffi::nwep_log_server_new(&mut ptr, log.as_ptr(), &ffi_settings) })?;
        Ok(LogServer {
            ptr,
            _callbacks: cb,
        })
    }

    /// `handle_request` dispatches a single incoming NWEP request to the log server.
    ///
    /// Both `stream` and `req` are raw C pointers supplied by the NWEP server
    /// callback infrastructure.  In practice callers do not invoke this method
    /// directly — it is called automatically when a [`LogServer`] is registered with
    /// `ServerBuilder::log_server()`.
    ///
    /// # Errors
    ///
    /// Returns `Err` if the C library reports a protocol or I/O error while handling
    /// the request.
    pub fn handle_request(
        &mut self,
        stream: *mut ffi::nwep_stream,
        req: *const ffi::nwep_request,
    ) -> Result<(), Error> {
        check(unsafe { ffi::nwep_log_server_handle_request(self.ptr, stream, req) })
    }

    /// `as_ptr` returns the raw `nwep_log_server` pointer for use in FFI calls.
    ///
    /// The pointer is valid for the lifetime of this `LogServer`.  Callers must not
    /// free or outlive the returned pointer.
    pub fn as_ptr(&mut self) -> *mut ffi::nwep_log_server {
        self.ptr
    }
}

impl Drop for LogServer {
    fn drop(&mut self) {
        if !self.ptr.is_null() {
            unsafe { ffi::nwep_log_server_free(self.ptr) }
        }
    }
}