Skip to main content

tuwunel_service/globals/
mod.rs

1//! Global server identity and sequence state.
2//!
3//! The service exposes the local server identity, shared monotonic counter, and process-wide
4//! security settings. Counter permits separate dispatched values from values whose writes are
5//! safe for readers to observe.
6
7mod data;
8
9use std::{ops::Range, sync::Arc};
10
11/// Persistent storage and retirement tracking for the global sequence counter.
12///
13/// Migration and lifecycle code use this storage directly to access the database version. Request
14/// paths normally use [`Service`] instead.
15pub use data::Data;
16use ruma::{OwnedUserId, RoomAliasId, ServerName, UserId};
17use tuwunel_core::{
18	Result, Server, err,
19	utils::{Secret, resolve_secret},
20};
21
22use crate::service;
23
24/// Provides process-wide server identity, secrets, and monotonic sequence numbers.
25///
26/// Sequence numbers are persisted when dispatched and become readable only after their permits
27/// retire. The service also centralizes locality checks against the configured server name.
28pub struct Service {
29	/// Persistent global counter and database version storage.
30	pub db: Data,
31	server: Arc<Server>,
32
33	/// Local user ID reserved for homeserver administration.
34	pub server_user: OwnedUserId,
35}
36
37impl crate::Service for Service {
38	fn build(args: &crate::Args<'_>) -> Result<Arc<Self>> {
39		let db = Data::new(args);
40
41		let server_user =
42			server_user(&args.server.config.server_user_localpart, &args.server.name)?;
43
44		Ok(Arc::new(Self {
45			db,
46			server: args.server.clone(),
47			server_user,
48		}))
49	}
50
51	fn name(&self) -> &str { service::make_name(std::module_path!()) }
52}
53
54/// Resolves the configured localpart to this server's administrative user.
55///
56/// Full user IDs are rejected so the configuration cannot select an identity
57/// belonging to another server. Invalid input names the configuration setting.
58fn server_user(localpart: &str, server_name: &ServerName) -> Result<OwnedUserId> {
59	UserId::parse_with_server_name(localpart, server_name)
60		.map_err(|e| err!("Invalid server_user_localpart configuration: {e}"))
61		.and_then(|user| {
62			user.localpart()
63				.eq(localpart)
64				.then_some(user)
65				.ok_or_else(|| {
66					err!("server_user_localpart must be a localpart, not a full user ID")
67				})
68		})
69}
70
71impl Service {
72	/// Waits until every sequence number dispatched at call time has retired.
73	///
74	/// The dispatched frontier is snapshotted before waiting. The returned value is the retirement
75	/// frontier that reached the sampled value.
76	#[tracing::instrument(
77		level = "trace",
78		skip_all,
79		ret,
80		fields(pending = ?self.pending_count()),
81	)]
82	pub async fn wait_pending(&self) -> Result<u64> { self.db.wait_pending().await }
83
84	/// Waits for the retirement frontier to reach a sequence number.
85	///
86	/// Completion means all writes through `count` are globally visible to readers. The returned
87	/// value may be greater when later writes retired while waiting.
88	#[tracing::instrument(
89		level = "trace",
90		skip_all,
91		ret,
92		fields(pending = ?self.pending_count()),
93	)]
94	pub async fn wait_count(&self, count: &u64) -> Result<u64> { self.db.wait_count(count).await }
95
96	/// Dispatches the next persistent sequence number.
97	///
98	/// The returned permit dereferences to the allocated number. Dropping it retires the associated
99	/// write and may advance the reader-visible frontier.
100	///
101	/// # Panics
102	///
103	/// Panics when the counter is exhausted or the dispatched value cannot be recorded.
104	#[tracing::instrument(
105		level = "debug",
106		skip_all,
107		fields(pending = ?self.pending_count()),
108	)]
109	#[must_use]
110	pub fn next_count(&self) -> data::Permit { self.db.next_count() }
111
112	/// Returns the highest sequence number whose writes have retired.
113	///
114	/// Readers can safely use this value as an upper bound for globally visible writes.
115	#[must_use]
116	pub fn current_count(&self) -> u64 { self.db.current_count() }
117
118	/// Returns a snapshot of the retired and dispatched counter frontiers.
119	///
120	/// The range start is the highest reader-visible number and the range end is the latest number
121	/// dispatched to a writer.
122	#[must_use]
123	pub fn pending_count(&self) -> Range<u64> { self.db.pending_count() }
124
125	/// Returns the configured local server name.
126	///
127	/// The returned name is borrowed from the server-wide configuration.
128	#[inline]
129	#[must_use]
130	pub fn server_name(&self) -> &ServerName { self.server.name.as_ref() }
131
132	/// Reports whether a user ID belongs to the local server.
133	///
134	/// Locality is determined solely by comparing the ID's server name with [`Self::server_name`].
135	#[inline]
136	#[must_use]
137	pub fn user_is_local(&self, user_id: &UserId) -> bool {
138		self.server_is_ours(user_id.server_name())
139	}
140
141	/// Reports whether a room alias belongs to the local server.
142	///
143	/// Locality is determined solely by comparing the alias server name with
144	/// [`Self::server_name`].
145	#[inline]
146	#[must_use]
147	pub fn alias_is_local(&self, alias: &RoomAliasId) -> bool {
148		self.server_is_ours(alias.server_name())
149	}
150
151	/// Reports whether a server name identifies this homeserver.
152	///
153	/// The comparison uses the configured local server name without resolving aliases or delegated
154	/// hosting.
155	#[inline]
156	#[must_use]
157	pub fn server_is_ours(&self, server_name: &ServerName) -> bool {
158		server_name == self.server_name()
159	}
160
161	/// Reports whether the database is open in read-only mode.
162	///
163	/// The value is delegated to the active database engine.
164	#[inline]
165	#[must_use]
166	pub fn is_read_only(&self) -> bool { self.db.db.is_read_only() }
167
168	/// Resolves the secret used to authenticate TURN credentials.
169	///
170	/// The configured secret file is read and trimmed on every call, allowing rotation without a
171	/// restart. A successfully read file, including an empty file, takes precedence; read failures
172	/// fall back to the inline secret.
173	#[must_use]
174	pub fn turn_secret(&self) -> Option<Secret> {
175		let config = &self.server.config;
176
177		resolve_secret(
178			config.turn_secret_file.as_deref(),
179			config.turn_secret.as_deref(),
180			"TURN secret",
181		)
182	}
183
184	/// Installs the default rustls cryptography provider when none exists.
185	///
186	/// Existing process-wide providers are preserved. A failure to install the AWS-LC provider is
187	/// returned to the caller.
188	pub fn init_rustls_provider(&self) -> Result {
189		if rustls::crypto::CryptoProvider::get_default().is_none() {
190			rustls::crypto::aws_lc_rs::default_provider()
191				.install_default()
192				.map_err(|_provider| {
193					err!(error!("Error initialising aws_lc_rs rustls crypto backend"))
194				})
195		} else {
196			Ok(())
197		}
198	}
199}
200
201#[cfg(test)]
202mod tests {
203	use ruma::server_name;
204
205	use super::server_user;
206
207	#[test]
208	fn configured_server_user_must_be_valid() {
209		let server_name = server_name!("example.org");
210
211		assert_eq!(server_user("conduit", server_name).unwrap(), "@conduit:example.org");
212		assert_eq!(server_user("_server", server_name).unwrap(), "@_server:example.org");
213		server_user("bad:localpart", server_name).unwrap_err();
214		server_user("@conduit:example.org", server_name).unwrap_err();
215		server_user("@conduit:elsewhere.org", server_name).unwrap_err();
216	}
217}