m4a_agent/backend/mod.rs
1//! The backend trait: what the client needs from any tier. Tiers are cargo features, so a tier
2//! that is not wanted is not compiled.
3//!
4//! A backend gets a session (enroll once, then log in by signature) and, with the `engine`
5//! feature, executes the engine's requests and waits for news.
6
7use serde::{Deserialize, Serialize};
8use zeroize::Zeroizing;
9
10use crate::error::Result;
11use crate::identity::{IdentityStore, SessionIdentity};
12
13pub mod wire;
14#[cfg(feature = "engine")]
15pub mod live;
16#[cfg(feature = "engine")]
17pub mod attached;
18#[cfg(all(feature = "engine", feature = "tier-matrix"))]
19pub mod sliding;
20#[cfg(feature = "tier-server")]
21pub mod server;
22#[cfg(feature = "tier-matrix")]
23pub mod matrix;
24
25/// Which tier an identity belongs to. Tier 1 (the local mail node) has no server and no login.
26#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
27#[serde(rename_all = "lowercase")]
28pub enum BackendKind {
29 /// Tier 2: our server through its own product API.
30 Server,
31 /// Tier 3: the Matrix client API.
32 Matrix,
33}
34
35#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
36pub struct Capabilities {
37 /// Rooms and spaces (Matrix semantics).
38 pub rooms: bool,
39 /// Talks to other servers through federation.
40 pub federation: bool,
41}
42
43/// A live session. The token is a secret: it is zeroized on drop and hidden from `Debug`.
44pub struct Session {
45 pub token: Zeroizing<String>,
46 pub nick: String,
47 /// Stable credential reference of the identity (its key id).
48 pub cred_ref: String,
49 /// Matrix tier only.
50 pub user_id: Option<String>,
51 pub device_id: Option<String>,
52}
53
54impl std::fmt::Debug for Session {
55 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
56 f.debug_struct("Session").field("nick", &self.nick).field("cred_ref", &self.cred_ref).field("user_id", &self.user_id).field("device_id", &self.device_id).field("token", &"<hidden>").finish()
57 }
58}
59
60pub trait Backend: Send + Sync {
61 fn kind(&self) -> BackendKind;
62 fn capabilities(&self) -> Capabilities;
63 /// The normalized server base URL this backend talks to (the identity is bound to it).
64 fn server_ref(&self) -> &str;
65 /// A live session for `id`. First time: redeems the operator's `invite` (enroll the public key,
66 /// proving possession of the private one); afterwards: logs in by signing a fresh challenge.
67 /// Reuses the stored token while the server still accepts it. Persists what it learns.
68 fn ensure_session(&self, ids: &IdentityStore, id: &mut SessionIdentity, invite: Option<&str>) -> Result<Session>;
69
70 /// Performs one request of the engine as this session (bearer added, token refreshed once by
71 /// signature if the server forgot it). Callable from any thread. Needs `ensure_session` first.
72 #[cfg(feature = "engine")]
73 fn execute(&self, request: &mail4agent_messenger::OutgoingRequest) -> Result<mail4agent_messenger::HttpResponseDescriptor>;
74
75 /// A copy of this backend whose routing choices are frozen now, for a request that will run
76 /// later on a worker (a long poll started for a local exchange must stay local). `None`: the
77 /// backend has no such choices.
78 #[cfg(feature = "engine")]
79 fn frozen(&self) -> Option<std::sync::Arc<dyn Backend>> {
80 None
81 }
82
83 /// `(user_id, device_id)` of the session (what the engine needs to open a core).
84 #[cfg(feature = "engine")]
85 fn whoami(&self) -> Result<(String, String)>;
86
87 /// Whether this server serves the Matrix paths under `/_matrix` (found out, not assumed).
88 #[cfg(feature = "engine")]
89 fn keep_prefix(&self) -> bool;
90
91 /// Waits up to `timeout` for news for this session: room events pushed on the server's push
92 /// socket, or `Idle` (timeout, or the server has no push channel and the sync long poll is the
93 /// wait).
94 #[cfg(feature = "engine")]
95 fn wait_for_news(&self, timeout: std::time::Duration) -> Result<crate::engine::News>;
96}