Skip to main content

de_mls/
defaults.rs

1//! Reference implementations of the library's plug-in traits.
2//!
3//! Gathered in one place so the trait surface (`core`, `mls_crypto`,
4//! `app`) is honestly the library's protocol contract and these are the
5//! batteries that ship alongside it. Production integrators normally
6//! swap one or more for their own implementations — durable storage,
7//! a different identity / consensus signer, custom scoring — and pull
8//! in only the defaults they actually want.
9//!
10//! Contents:
11//! - [`crate::defaults::MemoryDeMlsStorage`] — in-memory MLS keystore.
12//! - [`crate::defaults::InMemoryPeerScoreStorage`] — `HashMap`-backed
13//!   peer-score storage.
14//! - [`crate::defaults::DefaultConsensusPlugin`] — in-memory consensus
15//!   backend over `hashgraph_like_consensus` types.
16//! - [`crate::defaults::DefaultMlsService`],
17//!   [`crate::defaults::DefaultPeerScoring`],
18//!   [`crate::defaults::DefaultStewardList`] — type aliases for the
19//!   default-bundle per-conversation plug-ins.
20//! - [`crate::defaults::DefaultConversationPluginsFactory`] —
21//!   `ConversationPluginsFactory` wired to the above (covers the
22//!   identity-bound `generate_key_package` entry as well).
23
24use std::collections::{HashMap, HashSet};
25use std::sync::{Arc, RwLock};
26
27use hashgraph_like_consensus::{
28    events::BroadcastEventBus, signing::EthereumConsensusSigner, storage::InMemoryConsensusStorage,
29};
30use openmls_rust_crypto::MemoryStorage;
31
32use crate::core::{
33    ConsensusPlugin, ConversationPluginsFactory, DeterministicStewardList, PeerScoreStorage,
34    PeerScoringService, ScoringConfig, StewardListConfig, default_score_deltas,
35};
36use crate::mls_crypto::{DeMlsStorage, KeyPackageBytes, MlsCredentials, MlsError, OpenMlsService};
37
38// ═══════════════════════════════════════════════════════════════════
39// In-memory storage backends
40// ═══════════════════════════════════════════════════════════════════
41
42/// In-memory MLS keystore for development and testing.
43///
44/// All data is lost on restart. Production callers supply a persistent
45/// [`DeMlsStorage`] (e.g. SQLite-backed) instead.
46#[derive(Default)]
47pub struct MemoryDeMlsStorage {
48    key_package_refs: RwLock<HashSet<Vec<u8>>>,
49    mls: MemoryStorage,
50}
51
52impl MemoryDeMlsStorage {
53    pub fn new() -> Self {
54        Self::default()
55    }
56}
57
58impl DeMlsStorage for MemoryDeMlsStorage {
59    type MlsStorage = MemoryStorage;
60    type StorageError = openmls_rust_crypto::MemoryStorageError;
61
62    fn store_key_package_ref(&self, hash_ref: &[u8]) -> Result<(), MlsError> {
63        self.key_package_refs.write()?.insert(hash_ref.to_vec());
64        Ok(())
65    }
66
67    fn is_our_key_package(&self, hash_ref: &[u8]) -> Result<bool, MlsError> {
68        Ok(self.key_package_refs.read()?.contains(hash_ref))
69    }
70
71    fn remove_key_package_ref(&self, hash_ref: &[u8]) -> Result<(), MlsError> {
72        self.key_package_refs.write()?.remove(hash_ref);
73        Ok(())
74    }
75
76    fn mls_storage(&self) -> &Self::MlsStorage {
77        &self.mls
78    }
79}
80
81/// `HashMap`-backed [`PeerScoreStorage`] for tests and simple deployments.
82/// Production integrators supply a durable backend.
83#[derive(Debug, Clone, Default)]
84pub struct InMemoryPeerScoreStorage {
85    scores: HashMap<Vec<u8>, i64>,
86}
87
88impl InMemoryPeerScoreStorage {
89    pub fn new() -> Self {
90        Self::default()
91    }
92}
93
94impl PeerScoreStorage for InMemoryPeerScoreStorage {
95    fn get(&self, member_id: &[u8]) -> Option<i64> {
96        self.scores.get(member_id).copied()
97    }
98
99    fn set(&mut self, member_id: &[u8], score: i64) {
100        self.scores.insert(member_id.to_vec(), score);
101    }
102
103    fn remove(&mut self, member_id: &[u8]) {
104        self.scores.remove(member_id);
105    }
106
107    fn all_scores(&self) -> Vec<(Vec<u8>, i64)> {
108        self.scores.iter().map(|(k, v)| (k.clone(), *v)).collect()
109    }
110}
111
112// ═══════════════════════════════════════════════════════════════════
113// Default consensus plug-in
114// ═══════════════════════════════════════════════════════════════════
115
116/// In-memory consensus plug-in suitable for tests and simple deployments.
117/// The [`ConsensusPlugin`] trait itself is defined in [`crate::core`].
118pub struct DefaultConsensusPlugin;
119
120impl ConsensusPlugin for DefaultConsensusPlugin {
121    type Scope = String;
122    type ConsensusStorage = InMemoryConsensusStorage<String>;
123    type EventBus = BroadcastEventBus<String>;
124    type Signer = EthereumConsensusSigner;
125
126    fn new_storage() -> Self::ConsensusStorage {
127        InMemoryConsensusStorage::new()
128    }
129
130    fn new_event_bus() -> Self::EventBus {
131        BroadcastEventBus::default()
132    }
133}
134
135// ═══════════════════════════════════════════════════════════════════
136// Default per-conversation plug-in bundle
137// ═══════════════════════════════════════════════════════════════════
138
139/// MLS service type for the default-bundle `User`. Uses in-memory storage
140/// shared across every per-conversation service via `Arc` (the
141/// `Arc<S>: DeMlsStorage` blanket impl makes this work).
142pub type DefaultMlsService = OpenMlsService<Arc<MemoryDeMlsStorage>>;
143
144/// Peer-scoring plug-in type for the default-bundle `User`: the reference
145/// [`PeerScoringService`] over in-memory storage. The per-event score
146/// deltas are supplied at construction (see [`default_score_deltas`]).
147pub type DefaultPeerScoring = PeerScoringService<InMemoryPeerScoreStorage>;
148
149/// Steward-list plug-in type for the default-bundle `User`: the reference
150/// [`DeterministicStewardList`].
151pub type DefaultStewardList = DeterministicStewardList;
152
153/// Default per-conversation plug-in bundle: in-memory MLS storage shared
154/// across per-conversation services; reference scoring + steward
155/// implementations.
156pub struct DefaultConversationPluginsFactory {
157    pub(crate) storage: Arc<MemoryDeMlsStorage>,
158    pub(crate) credentials: Arc<MlsCredentials>,
159}
160
161impl DefaultConversationPluginsFactory {
162    /// Build the default factory from an MLS storage handle and the
163    /// User-level [`MlsCredentials`]. Both are cloned into every
164    /// per-conversation MLS service this factory creates.
165    pub fn new(storage: Arc<MemoryDeMlsStorage>, credentials: Arc<MlsCredentials>) -> Self {
166        Self {
167            storage,
168            credentials,
169        }
170    }
171}
172
173impl ConversationPluginsFactory for DefaultConversationPluginsFactory {
174    type Mls = DefaultMlsService;
175    type Scoring = DefaultPeerScoring;
176    type StewardList = DefaultStewardList;
177
178    fn create_mls(&self, conversation_id: String) -> Result<Self::Mls, MlsError> {
179        OpenMlsService::new_as_creator(
180            conversation_id,
181            Arc::clone(&self.storage),
182            Arc::clone(&self.credentials),
183        )
184    }
185
186    fn welcome_mls(&self, welcome_bytes: &[u8]) -> Result<Option<Self::Mls>, MlsError> {
187        OpenMlsService::new_from_welcome(
188            welcome_bytes,
189            Arc::clone(&self.storage),
190            Arc::clone(&self.credentials),
191        )
192    }
193
194    fn make_scoring(&self, config: &ScoringConfig) -> Self::Scoring {
195        PeerScoringService::new(
196            InMemoryPeerScoreStorage::new(),
197            default_score_deltas(),
198            config.clone(),
199        )
200    }
201
202    fn make_steward_list(
203        &self,
204        conversation_id: &[u8],
205        config: StewardListConfig,
206    ) -> Self::StewardList {
207        DeterministicStewardList::empty(conversation_id.to_vec(), config)
208    }
209
210    fn generate_key_package(&self) -> Result<KeyPackageBytes, MlsError> {
211        OpenMlsService::<Arc<MemoryDeMlsStorage>>::generate_key_package(
212            &self.storage,
213            &self.credentials,
214        )
215    }
216}
217
218#[cfg(test)]
219mod tests {
220    use super::*;
221
222    #[test]
223    fn in_memory_storage_round_trip() {
224        let mut storage = InMemoryPeerScoreStorage::new();
225        assert_eq!(storage.get(b"alice"), None);
226        storage.set(b"alice", 42);
227        assert_eq!(storage.get(b"alice"), Some(42));
228        storage.set(b"bob", -3);
229        let all = storage.all_scores();
230        assert_eq!(all.len(), 2);
231        storage.remove(b"alice");
232        assert_eq!(storage.get(b"alice"), None);
233        assert_eq!(storage.all_scores().len(), 1);
234    }
235}