Skip to main content

nwep/
logserver.rs

1#![allow(unsafe_op_in_unsafe_fn)]
2
3use crate::error::{Error, check};
4use crate::ffi;
5use crate::merkle::{MerkleEntry, MerkleLog};
6use crate::types::NodeId;
7
8/// `LogServerSettings` controls the behaviour of a [`LogServer`] instance.
9///
10/// All fields are optional.  The default value (via [`Default`]) leaves `authorize`
11/// unset, which causes the server to reject every write with `ERR_PROTO_UNAUTHORIZED`.
12pub struct LogServerSettings {
13    /// Optional callback invoked for every write request before it is applied.
14    ///
15    /// Receives the requester's [`NodeId`] and the proposed [`MerkleEntry`].  Return
16    /// `Ok(())` to allow the write or `Err(e)` to reject it with the given error code.
17    /// When `None`, all writes are unconditionally rejected.
18    pub authorize: Option<Box<dyn Fn(&NodeId, &MerkleEntry) -> Result<(), Error> + Send>>,
19}
20
21impl Default for LogServerSettings {
22    fn default() -> Self {
23        LogServerSettings { authorize: None }
24    }
25}
26
27struct Callbacks {
28    authorize: Option<Box<dyn Fn(&NodeId, &MerkleEntry) -> Result<(), Error> + Send>>,
29}
30
31unsafe extern "C" fn authorize_cb(
32    user_data: *mut std::ffi::c_void,
33    nodeid: *const ffi::nwep_nodeid,
34    entry: *const ffi::nwep_merkle_entry,
35) -> std::ffi::c_int {
36    let cb = &*(user_data as *const Callbacks);
37    if let Some(f) = &cb.authorize {
38        let nid = NodeId((*nodeid).data);
39        let e = MerkleEntry::from_ffi(&*entry);
40        match f(&nid, &e) {
41            Ok(()) => 0,
42            Err(e) => e.code,
43        }
44    } else {
45        crate::error::ERR_PROTO_UNAUTHORIZED
46    }
47}
48
49/// `LogServer` wraps the NWEP C library's Merkle log server.
50///
51/// `LogServer` handles HTTP-like requests to the `/log` and `/log/*` endpoints,
52/// enabling peers to submit new Merkle log entries and read existing ones.  The server
53/// holds a mutable reference to the underlying [`MerkleLog`] for the duration of its
54/// lifetime.
55///
56/// # Safety
57///
58/// The raw pointer `ptr` must remain valid for the lifetime of this value.  `LogServer`
59/// is `Send` because access to the C object is mediated through `&mut self` methods.
60pub struct LogServer {
61    ptr: *mut ffi::nwep_log_server,
62    _callbacks: Box<Callbacks>,
63}
64
65unsafe impl Send for LogServer {}
66
67impl LogServer {
68    /// `new` creates a new log server backed by the given [`MerkleLog`].
69    ///
70    /// The `settings` argument configures the optional write-authorization callback.
71    /// Ownership of the callback closures is transferred into the returned `LogServer`
72    /// and kept alive for as long as the server exists.
73    ///
74    /// # Errors
75    ///
76    /// Returns `Err` if the underlying C call fails (e.g. allocation failure or an
77    /// invalid log pointer).
78    ///
79    /// # Examples
80    ///
81    /// ```no_run
82    /// # use nwep::{logserver::{LogServer, LogServerSettings}, merkle::{MerkleLog, LogStorage}, error::Error};
83    /// # struct MemLog(Vec<Vec<u8>>);
84    /// # impl LogStorage for MemLog {
85    /// #     fn append(&mut self, _: u64, e: &[u8]) -> Result<(), Error> { self.0.push(e.to_vec()); Ok(()) }
86    /// #     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) }
87    /// #     fn size(&self) -> u64 { self.0.len() as u64 }
88    /// # }
89    /// let mut log = MerkleLog::new(MemLog(vec![])).unwrap();
90    /// let settings = LogServerSettings {
91    ///     authorize: Some(Box::new(|node_id, entry| {
92    ///         // allow all writes from any peer
93    ///         Ok(())
94    ///     })),
95    /// };
96    /// let server = LogServer::new(&mut log, settings).unwrap();
97    /// ```
98    pub fn new(log: &mut MerkleLog, settings: LogServerSettings) -> Result<Self, Error> {
99        let has_authorize = settings.authorize.is_some();
100        let mut cb = Box::new(Callbacks {
101            authorize: settings.authorize,
102        });
103        let cb_ptr = cb.as_mut() as *mut _ as *mut std::ffi::c_void;
104
105        let ffi_settings = ffi::nwep_log_server_settings {
106            authorize: if has_authorize {
107                Some(authorize_cb)
108            } else {
109                None
110            },
111            user_data: cb_ptr,
112        };
113
114        let mut ptr: *mut ffi::nwep_log_server = std::ptr::null_mut();
115        check(unsafe { ffi::nwep_log_server_new(&mut ptr, log.as_ptr(), &ffi_settings) })?;
116        Ok(LogServer {
117            ptr,
118            _callbacks: cb,
119        })
120    }
121
122    /// `handle_request` dispatches a single incoming NWEP request to the log server.
123    ///
124    /// Both `stream` and `req` are raw C pointers supplied by the NWEP server
125    /// callback infrastructure.  In practice callers do not invoke this method
126    /// directly — it is called automatically when a [`LogServer`] is registered with
127    /// `ServerBuilder::log_server()`.
128    ///
129    /// # Errors
130    ///
131    /// Returns `Err` if the C library reports a protocol or I/O error while handling
132    /// the request.
133    pub fn handle_request(
134        &mut self,
135        stream: *mut ffi::nwep_stream,
136        req: *const ffi::nwep_request,
137    ) -> Result<(), Error> {
138        check(unsafe { ffi::nwep_log_server_handle_request(self.ptr, stream, req) })
139    }
140
141    /// `as_ptr` returns the raw `nwep_log_server` pointer for use in FFI calls.
142    ///
143    /// The pointer is valid for the lifetime of this `LogServer`.  Callers must not
144    /// free or outlive the returned pointer.
145    pub fn as_ptr(&mut self) -> *mut ffi::nwep_log_server {
146        self.ptr
147    }
148}
149
150impl Drop for LogServer {
151    fn drop(&mut self) {
152        if !self.ptr.is_null() {
153            unsafe { ffi::nwep_log_server_free(self.ptr) }
154        }
155    }
156}