Skip to main content

o_sfu_core/engine/room/
definition.rs

1//! Room identity and immutable runtime policy.
2//!
3//! A room has two identities:
4//!
5//! ```text
6//! Odoo-facing identity        Runtime-local placement
7//! issuer, uuid, key           instance, router, media worker
8//! ```
9//!
10//! The first identity is visible at the HTTP and websocket edge. The second
11//! identity is process-local and drives transport ownership, diagnostics and
12//! room topology. Mutable committed routing directories live in `RoomState`
13//! rather than in this immutable definition.
14
15use secrecy::{ExposeSecret, SecretSlice};
16use subtle::ConstantTimeEq;
17use uuid::Uuid;
18
19use super::{RoomConfig, RoomRuntimeContext, RoomRuntimePolicy};
20use crate::{
21    RoomWorkerPolicy, RuntimeFeatureFlags,
22    engine::{AvailableFeatures, RoomInstanceId},
23};
24
25/// Central gate for exposing recording as a production room capability.
26///
27/// This must become true only when accepted recording controls can produce the
28/// promised persistent artifact or handoff.
29const fn persistent_recording_backend_available() -> bool {
30    false
31}
32
33#[derive(Debug, Clone)]
34struct RoomIdentity {
35    uuid: String,
36    issuer: String,
37    key: SecretSlice<u8>,
38}
39
40impl RoomIdentity {
41    fn new(issuer: String, key: SecretSlice<u8>) -> Self {
42        Self {
43            uuid: Uuid::new_v4().to_string(),
44            issuer,
45            key,
46        }
47    }
48}
49
50#[derive(Debug, Clone)]
51pub(crate) struct RoomDefinition {
52    /// Live room instance id used by runtime diagnostics and transport keys.
53    ///
54    /// Recreating a room for the same issuer allocates a fresh instance id so
55    /// stale transport work cannot be confused with the new room lifetime.
56    instance_id: RoomInstanceId,
57    room_worker_policy: RoomWorkerPolicy,
58    identity: RoomIdentity,
59    config: RoomConfig,
60    feature_flags: RuntimeFeatureFlags,
61}
62
63impl RoomDefinition {
64    #[must_use]
65    pub(crate) fn new(
66        runtime_context: &RoomRuntimeContext,
67        runtime_policy: &RoomRuntimePolicy,
68        issuer: String,
69        key: SecretSlice<u8>,
70        config: RoomConfig,
71    ) -> Self {
72        Self {
73            instance_id: runtime_context.instance(),
74            room_worker_policy: runtime_policy.room_worker_policy,
75            identity: RoomIdentity::new(issuer, key),
76            config,
77            feature_flags: runtime_policy.feature_flags,
78        }
79    }
80
81    #[must_use]
82    pub(crate) fn matches_reservation(&self, key: &SecretSlice<u8>, config: &RoomConfig) -> bool {
83        // Reservation comparison must not stop at the first differing secret byte.
84        bool::from(self.identity.key.expose_secret().ct_eq(key.expose_secret()))
85            && self.config == *config
86    }
87
88    #[must_use]
89    pub(crate) fn uuid(&self) -> &str {
90        &self.identity.uuid
91    }
92
93    #[must_use]
94    pub(crate) fn issuer(&self) -> &str {
95        &self.identity.issuer
96    }
97
98    #[must_use]
99    pub(crate) fn key(&self) -> &SecretSlice<u8> {
100        &self.identity.key
101    }
102
103    #[must_use]
104    pub(crate) fn available_features(&self) -> AvailableFeatures {
105        let recording_available = self.recording_available();
106        AvailableFeatures {
107            rtc: self.config.web_rtc_enabled,
108            transcription: recording_available && self.feature_flags.transcription,
109            audio_recording: recording_available && self.feature_flags.audio_recording,
110            video_recording: recording_available && self.feature_flags.video_recording,
111        }
112    }
113
114    #[must_use]
115    pub(crate) const fn web_rtc_enabled(&self) -> bool {
116        self.config.web_rtc_enabled
117    }
118
119    #[must_use]
120    pub(crate) const fn recording_available(&self) -> bool {
121        if self.config.recording_address.is_none() {
122            return false;
123        }
124        persistent_recording_backend_available()
125    }
126
127    #[cfg(any(test, feature = "testing-transport"))]
128    #[must_use]
129    pub fn recording_address(&self) -> Option<&str> {
130        self.config.recording_address.as_deref()
131    }
132
133    #[must_use]
134    pub(crate) fn room_worker_policy(&self) -> RoomWorkerPolicy {
135        self.room_worker_policy
136    }
137
138    #[must_use]
139    pub(crate) const fn instance_id(&self) -> RoomInstanceId {
140        self.instance_id
141    }
142}