Skip to main content

river_core/room_state/
privacy.rs

1use serde::{Deserialize, Serialize};
2use std::fmt;
3
4/// Version identifier for room secrets
5pub type SecretVersion = u32;
6
7/// Privacy mode for a chat room
8#[derive(Serialize, Deserialize, Clone, Debug, Default, PartialEq, Eq)]
9pub enum PrivacyMode {
10    /// Room content is visible to all network participants
11    #[default]
12    Public,
13    /// Room content is encrypted and only visible to members
14    Private,
15}
16
17/// Cipher specification for encrypted room content
18#[derive(Serialize, Deserialize, Clone, PartialEq, Debug)]
19pub enum RoomCipherSpec {
20    /// AES-256-GCM with 12-byte nonce
21    Aes256Gcm,
22}
23
24/// A value that may be public or encrypted
25#[derive(Serialize, Deserialize, Clone, PartialEq, Debug)]
26pub enum SealedBytes {
27    /// Plaintext value (only for public rooms)
28    Public { value: Vec<u8> },
29    /// Encrypted value with metadata
30    Private {
31        ciphertext: Vec<u8>,
32        nonce: [u8; 12],
33        secret_version: SecretVersion,
34        declared_len_bytes: u32,
35    },
36}
37
38impl SealedBytes {
39    /// Create a new public sealed bytes value
40    pub fn public(value: Vec<u8>) -> Self {
41        Self::Public { value }
42    }
43
44    /// Create a new private sealed bytes value
45    pub fn private(
46        ciphertext: Vec<u8>,
47        nonce: [u8; 12],
48        secret_version: SecretVersion,
49        declared_len_bytes: u32,
50    ) -> Self {
51        Self::Private {
52            ciphertext,
53            nonce,
54            secret_version,
55            declared_len_bytes,
56        }
57    }
58
59    /// Check if this is a public value
60    pub fn is_public(&self) -> bool {
61        matches!(self, Self::Public { .. })
62    }
63
64    /// Check if this is a private value
65    pub fn is_private(&self) -> bool {
66        matches!(self, Self::Private { .. })
67    }
68
69    /// Get the declared length in bytes for validation
70    pub fn declared_len(&self) -> usize {
71        match self {
72            Self::Public { value } => value.len(),
73            Self::Private {
74                declared_len_bytes, ..
75            } => *declared_len_bytes as usize,
76        }
77    }
78
79    /// Get the secret version (if private)
80    pub fn secret_version(&self) -> Option<SecretVersion> {
81        match self {
82            Self::Public { .. } => None,
83            Self::Private { secret_version, .. } => Some(*secret_version),
84        }
85    }
86
87    /// Get the value if public, otherwise return a placeholder
88    /// This is a temporary helper for UI integration during development
89    pub fn to_string_lossy(&self) -> String {
90        match self {
91            Self::Public { value } => String::from_utf8_lossy(value).to_string(),
92            Self::Private {
93                declared_len_bytes,
94                secret_version,
95                ..
96            } => {
97                format!(
98                    "[Encrypted: {} bytes, v{}]",
99                    declared_len_bytes, secret_version
100                )
101            }
102        }
103    }
104
105    /// Try to get the public value as bytes, returns None if private
106    pub fn as_public_bytes(&self) -> Option<&[u8]> {
107        match self {
108            Self::Public { value } => Some(value),
109            Self::Private { .. } => None,
110        }
111    }
112}
113
114impl fmt::Display for SealedBytes {
115    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
116        write!(f, "{}", self.to_string_lossy())
117    }
118}
119
120/// Display metadata for a room (name and optional description)
121#[derive(Serialize, Deserialize, Clone, PartialEq, Debug)]
122pub struct RoomDisplayMetadata {
123    pub name: SealedBytes,
124    pub description: Option<SealedBytes>,
125}
126
127impl RoomDisplayMetadata {
128    /// Create public display metadata
129    pub fn public(name: String, description: Option<String>) -> Self {
130        Self {
131            name: SealedBytes::public(name.into_bytes()),
132            description: description.map(|d| SealedBytes::public(d.into_bytes())),
133        }
134    }
135
136    /// Create private display metadata
137    pub fn private(
138        name_ciphertext: Vec<u8>,
139        name_nonce: [u8; 12],
140        name_declared_len: u32,
141        description: Option<(Vec<u8>, [u8; 12], u32)>,
142        secret_version: SecretVersion,
143    ) -> Self {
144        Self {
145            name: SealedBytes::private(
146                name_ciphertext,
147                name_nonce,
148                secret_version,
149                name_declared_len,
150            ),
151            description: description.map(|(ciphertext, nonce, declared_len)| {
152                SealedBytes::private(ciphertext, nonce, secret_version, declared_len)
153            }),
154        }
155    }
156
157    /// Check if both name and description are public
158    pub fn is_public(&self) -> bool {
159        self.name.is_public() && self.description.as_ref().is_none_or(|d| d.is_public())
160    }
161
162    /// Check if name is private
163    pub fn is_private(&self) -> bool {
164        self.name.is_private()
165    }
166}
167
168impl Default for RoomDisplayMetadata {
169    fn default() -> Self {
170        Self::public("Default Room Name".to_string(), None)
171    }
172}
173
174#[cfg(test)]
175mod tests {
176    use super::*;
177
178    #[test]
179    fn test_privacy_mode_default() {
180        assert_eq!(PrivacyMode::default(), PrivacyMode::Public);
181    }
182
183    #[test]
184    fn test_sealed_bytes_public() {
185        let data = b"test data".to_vec();
186        let sealed = SealedBytes::public(data.clone());
187
188        assert!(sealed.is_public());
189        assert!(!sealed.is_private());
190        assert_eq!(sealed.declared_len(), data.len());
191        assert_eq!(sealed.secret_version(), None);
192    }
193
194    #[test]
195    fn test_sealed_bytes_private() {
196        let ciphertext = vec![1, 2, 3, 4];
197        let nonce = [0u8; 12];
198        let secret_version = 1;
199        let declared_len = 10;
200
201        let sealed = SealedBytes::private(ciphertext.clone(), nonce, secret_version, declared_len);
202
203        assert!(!sealed.is_public());
204        assert!(sealed.is_private());
205        assert_eq!(sealed.declared_len(), declared_len as usize);
206        assert_eq!(sealed.secret_version(), Some(secret_version));
207    }
208}