Skip to main content

rightkit_daemon/contract/
handler.rs

1use super::caller::Caller;
2use crate::error::ServiceError;
3use crate::peer::PeerIdentity;
4use serde_json::Value;
5use std::sync::atomic::{AtomicBool, Ordering};
6use std::sync::Arc;
7
8/// Sends an `event` frame for the request being handled.
9pub type Emit = Arc<dyn Fn(Value) + Send + Sync>;
10
11/// What a handler knows about the request it is serving.
12#[derive(Clone)]
13pub struct CallContext {
14    conn: u64,
15    caller: Caller,
16    pub(crate) closed: Arc<AtomicBool>,
17    handoff: Arc<AtomicBool>,
18}
19
20pub(crate) fn new_context(
21    conn: u64,
22    caller: Caller,
23    closed: Arc<AtomicBool>,
24    handoff: Arc<AtomicBool>,
25) -> CallContext {
26    CallContext {
27        conn,
28        caller,
29        closed,
30        handoff,
31    }
32}
33
34impl CallContext {
35    pub fn conn_id(&self) -> u64 {
36        self.conn
37    }
38    /// Who is calling: a local app or a device.
39    pub fn origin(&self) -> &Caller {
40        &self.caller
41    }
42    /// The app label the client declared in `hello` (already allowlisted); for a device caller,
43    /// the device id.
44    ///
45    /// The label is a claim: any process of the current user can declare any allowlisted
46    /// label. Use it to choose what a caller may ask for, never as proof of who is asking.
47    /// [`peer`](Self::peer) is what the OS verified.
48    pub fn app(&self) -> &str {
49        self.caller.label()
50    }
51    /// Who the OS says is on the other end of this connection, from the peer check that ran
52    /// before any byte was read: the uid on Unix, the user SID on Windows, and the process id
53    /// where the platform reports it (Linux, macOS and Windows). `None` for a caller that is not
54    /// a local app (a device has no OS peer).
55    ///
56    /// This is the fact, as opposed to the [`app`](Self::app) label. The user is always the
57    /// current user, since any other peer was refused. A pid identifies the process only while
58    /// it runs: record it for an audit trail, do not authorise by it later.
59    pub fn peer(&self) -> Option<&PeerIdentity> {
60        self.caller.peer()
61    }
62    /// The client went away while this request was running.
63    pub fn peer_closed(&self) -> bool {
64        self.closed.load(Ordering::SeqCst)
65    }
66    /// A higher-ranked host asked for the service: return
67    /// [`ServiceError::handoff`] at the next safe boundary.
68    pub fn handoff_requested(&self) -> bool {
69        self.handoff.load(Ordering::SeqCst)
70    }
71}
72
73pub trait Handler: Send + Sync + 'static {
74    /// Serve one allowlisted request. Events go to `emit`; a returned
75    /// retryable error is sent to the client as-is.
76    fn call(
77        &self,
78        cx: &CallContext,
79        method: &str,
80        params: &Value,
81        emit: &Emit,
82    ) -> Result<Value, ServiceError>;
83    /// A takeover was accepted: stop starting new steps, finish at a boundary.
84    fn on_handoff(&self) {}
85    /// A connection closed (also fired mid-request when the peer drops).
86    fn connection_closed(&self, _conn: u64) {}
87}