1use std::collections::{HashMap, HashSet};
2use std::sync::{Arc, RwLock};
3
4use iroh::PublicKey;
5use iroh_docs::NamespaceId;
6
7use crate::storage::node_db::NodeDatabase;
8use crate::{Result, SyncwebError};
9
10use super::network::{Network, NetworkId, NetworkOptions, NetworkTicket};
11use super::network_log::{NetworkEventType, NetworkLogger};
12
13#[derive(Clone, Debug)]
15pub struct NetworkManager {
16 db: NodeDatabase,
17 local_node: PublicKey,
18 networks: HashMap<NetworkId, Network>,
19 logger: Option<NetworkLogger>,
20 member_keys: Arc<RwLock<HashSet<iroh::PublicKey>>>,
21}
22
23impl NetworkManager {
24 pub fn new(
30 db: NodeDatabase,
31 local_node: PublicKey,
32 member_keys: Arc<RwLock<HashSet<iroh::PublicKey>>>,
33 ) -> Result<Self> {
34 let networks = db
35 .list_networks()?
36 .into_iter()
37 .map(|network| (network.id, network))
38 .collect();
39 let mgr = Self {
40 db,
41 local_node,
42 networks,
43 logger: None,
44 member_keys,
45 };
46 mgr.sync_member_keys();
47 Ok(mgr)
48 }
49
50 pub fn with_logger(
56 db: NodeDatabase,
57 local_node: PublicKey,
58 logger: NetworkLogger,
59 member_keys: Arc<RwLock<HashSet<iroh::PublicKey>>>,
60 ) -> Result<Self> {
61 let mut mgr = Self::new(db, local_node, member_keys)?;
62 mgr.logger = Some(logger);
63 Ok(mgr)
64 }
65
66 #[must_use]
68 pub const fn logger(&self) -> &Option<NetworkLogger> {
69 &self.logger
70 }
71
72 pub fn set_logger(&mut self, logger: NetworkLogger) {
74 self.logger = Some(logger);
75 }
76
77 #[must_use]
79 pub const fn database(&self) -> &NodeDatabase {
80 &self.db
81 }
82
83 #[must_use]
85 pub const fn local_node(&self) -> &iroh::PublicKey {
86 &self.local_node
87 }
88
89 pub fn create(&mut self, name: &str, options: NetworkOptions) -> Result<NetworkId> {
95 self.create_with_doc_ticket(name, options, None)
96 }
97
98 pub fn create_with_doc_ticket(
104 &mut self,
105 name: &str,
106 options: NetworkOptions,
107 doc_ticket: Option<String>,
108 ) -> Result<NetworkId> {
109 let normalized = name.trim();
110 if normalized.is_empty() {
111 return Err(SyncwebError::InvalidConfig("network name cannot be empty".to_owned()));
112 }
113 let id = NetworkId::from_name(normalized);
114 if self.networks.contains_key(&id) {
115 return Err(SyncwebError::InvalidConfig(format!(
116 "network {normalized:?} already exists"
117 )));
118 }
119 let mut network = Network::new(normalized, self.local_node, options);
120 network.doc_ticket = doc_ticket;
121 self.db.create_network(&network)?;
122 self.networks.insert(id, network.clone());
123 self.log_event(
124 &id,
125 NetworkEventType::MemberAdded,
126 Some(&self.local_node),
127 Some("created"),
128 );
129 if network.is_invite_only() {
130 self.log_event(
131 &id,
132 NetworkEventType::TicketCreated,
133 None,
134 Some("invite-only network created"),
135 );
136 }
137 Ok(id)
138 }
139
140 pub fn join(&mut self, ticket: NetworkTicket) -> Result<NetworkId> {
146 if ticket.name.trim().is_empty() || NetworkId::from_name(ticket.name.trim()) != ticket.network_id {
147 return Err(SyncwebError::InvalidTicket(
148 "network ticket ID does not match its network name".to_owned(),
149 ));
150 }
151 if ticket.invited_node.is_some_and(|invited| invited != self.local_node) {
152 return Err(SyncwebError::InvalidTicket(
153 "network ticket was issued for another device".to_owned(),
154 ));
155 }
156 if self.networks.contains_key(&ticket.network_id) {
157 return Err(SyncwebError::InvalidConfig(format!(
158 "already joined network {}",
159 ticket.network_id,
160 )));
161 }
162 let mut members = ticket.members;
163 members.insert(self.local_node);
164 let network = Network {
165 id: ticket.network_id,
166 name: ticket.name,
167 label: ticket.label,
168 owner: ticket.owner,
169 members,
170 folders: ticket.folders,
171 shared_secret: ticket.shared_secret,
172 doc_ticket: ticket.doc_ticket,
173 };
174 let id = network.id;
175 self.db.create_network(&network)?;
176 self.networks.insert(id, network);
177 self.log_event(
178 &id,
179 NetworkEventType::MemberAdded,
180 Some(&self.local_node),
181 Some("joined via ticket"),
182 );
183 self.sync_member_keys();
184 self.log_event(&id, NetworkEventType::TicketAccepted, None, Some("network joined"));
185 Ok(id)
186 }
187
188 pub fn leave(&mut self, id: NetworkId) -> Result<()> {
194 self.log_event(
195 &id,
196 NetworkEventType::MemberRemoved,
197 Some(&self.local_node),
198 Some("left network"),
199 );
200 self.networks
201 .remove(&id)
202 .ok_or_else(|| SyncwebError::FolderNotFound(format!("network {id}")))?;
203 self.sync_member_keys();
204 self.db.delete_network(id)
205 }
206
207 pub fn invite(&mut self, id: NetworkId, device: PublicKey) -> Result<NetworkTicket> {
214 let network = self.network_mut_as_owner(id)?;
215 network.members.insert(device);
216 let ticket = NetworkTicket {
217 network_id: network.id,
218 name: network.name.clone(),
219 label: network.label.clone(),
220 owner: network.owner,
221 invited_node: Some(device),
222 members: network.members.clone(),
223 folders: network.folders.clone(),
224 shared_secret: network.shared_secret,
225 doc_ticket: network.doc_ticket.clone(),
226 };
227 self.db.add_member(id, device)?;
228 self.sync_member_keys();
229 self.log_event(&id, NetworkEventType::MemberAdded, Some(&device), Some("invited"));
230 self.log_event(
231 &id,
232 NetworkEventType::TicketCreated,
233 None,
234 Some("device-bound ticket generated"),
235 );
236 Ok(ticket)
237 }
238
239 pub fn invite_any(&self, id: NetworkId) -> Result<NetworkTicket> {
245 let network = self.network_as_owner(id)?;
246 self.log_event(
247 &id,
248 NetworkEventType::TicketCreated,
249 None,
250 Some("open ticket generated"),
251 );
252 Ok(NetworkTicket {
253 network_id: network.id,
254 name: network.name.clone(),
255 label: network.label.clone(),
256 owner: network.owner,
257 invited_node: None,
258 members: network.members.clone(),
259 folders: network.folders.clone(),
260 shared_secret: network.shared_secret,
261 doc_ticket: network.doc_ticket.clone(),
262 })
263 }
264
265 pub fn kick(&mut self, id: NetworkId, device: &PublicKey) -> Result<()> {
272 let network = self.network_mut_as_owner(id)?;
273 if network.owner == *device {
274 return Err(SyncwebError::InvalidConfig(
275 "the network owner cannot be kicked".to_owned(),
276 ));
277 }
278 if !network.members.remove(device) {
279 return Err(SyncwebError::InvalidConfig("device is not a network member".to_owned()));
280 }
281 self.db.remove_member(id, device)?;
282 self.sync_member_keys();
283 self.log_event(&id, NetworkEventType::MemberRemoved, Some(device), Some("kicked"));
284 Ok(())
285 }
286
287 pub fn set_doc_ticket(&mut self, id: NetworkId, doc_ticket: &Option<String>) -> Result<()> {
293 let network = self
294 .networks
295 .get_mut(&id)
296 .ok_or_else(|| SyncwebError::FolderNotFound(format!("network {id}")))?;
297 network.doc_ticket.clone_from(doc_ticket);
298 self.db.set_network_doc_ticket(id, doc_ticket)
299 }
300
301 pub fn add_folder(&mut self, id: NetworkId, folder: NamespaceId) -> Result<()> {
307 self.network_mut(id)?.folders.insert(folder);
308 self.log_event(
309 &id,
310 NetworkEventType::FolderAdded,
311 None,
312 Some(&format!("folder {folder}")),
313 );
314 self.db.add_folder_to_network(id, folder)
315 }
316
317 pub fn remove_folder(&mut self, id: NetworkId, folder: NamespaceId) -> Result<()> {
323 self.network_mut(id)?.folders.remove(&folder);
324 self.log_event(
325 &id,
326 NetworkEventType::FolderRemoved,
327 None,
328 Some(&format!("folder {folder}")),
329 );
330 self.db.remove_folder_from_network(id, folder)
331 }
332
333 #[must_use]
334 pub fn list(&self) -> Vec<&Network> {
335 let mut networks = self.networks.values().collect::<Vec<_>>();
336 networks.sort_by(|left, right| left.name.cmp(&right.name));
337 networks
338 }
339
340 #[must_use]
341 pub fn get(&self, id: &NetworkId) -> Option<&Network> {
342 self.networks.get(id)
343 }
344
345 #[must_use]
346 pub fn get_by_name(&self, name: &str) -> Option<&Network> {
347 self.networks.values().find(|network| network.name == name)
348 }
349
350 pub fn networks_for_folder(&self, namespace_id: &NamespaceId) -> Result<Vec<String>> {
356 self.db.networks_for_folder(&namespace_id.to_string())
357 }
358
359 pub fn network_for_folder(&self, namespace_id: &NamespaceId) -> Result<Option<String>> {
365 Ok(self
366 .db
367 .networks_for_folder(&namespace_id.to_string())?
368 .into_iter()
369 .next())
370 }
371
372 pub fn folders_for_network(&self, network_id: &NetworkId) -> Result<Vec<NamespaceId>> {
378 let folders = self.db.folders_for_network(&network_id.to_string())?;
379 folders
380 .into_iter()
381 .map(|f| {
382 f.parse()
383 .map_err(|error| SyncwebError::operation("invalid namespace in database", error))
384 })
385 .collect()
386 }
387
388 pub fn members_of_network(&self, network_id: &NetworkId) -> Result<Vec<iroh::PublicKey>> {
394 let members = self.db.members_of_network(&network_id.to_string())?;
395 members
396 .into_iter()
397 .map(|m| {
398 m.parse()
399 .map_err(|error| SyncwebError::operation("invalid member key in database", error))
400 })
401 .collect()
402 }
403
404 fn sync_member_keys(&self) {
406 let keys: HashSet<iroh::PublicKey> = self.networks.values().flat_map(|n| n.members.iter().copied()).collect();
407 if let Ok(mut guard) = self.member_keys.write() {
408 *guard = keys;
409 }
410 }
411
412 fn log_event(
413 &self,
414 network_id: &NetworkId,
415 event: NetworkEventType,
416 peer: Option<&iroh::PublicKey>,
417 details: Option<&str>,
418 ) {
419 if let Some(ref logger) = self.logger {
420 let peer_str = peer.map(std::string::ToString::to_string);
421 let _ = logger.record_event(&network_id.to_string(), event, peer_str.as_deref(), details);
422 }
423 }
424
425 fn network_mut(&mut self, id: NetworkId) -> Result<&mut Network> {
426 self.networks
427 .get_mut(&id)
428 .ok_or_else(|| SyncwebError::FolderNotFound(format!("network {id}")))
429 }
430
431 fn network_as_owner(&self, id: NetworkId) -> Result<&Network> {
432 let network = self
433 .networks
434 .get(&id)
435 .ok_or_else(|| SyncwebError::FolderNotFound(format!("network {id}")))?;
436 if network.owner != self.local_node {
437 return Err(SyncwebError::InvalidConfig(
438 "only the network owner can manage invitations".to_owned(),
439 ));
440 }
441 Ok(network)
442 }
443
444 fn network_mut_as_owner(&mut self, id: NetworkId) -> Result<&mut Network> {
445 self.network_as_owner(id)?;
446 self.network_mut(id)
447 }
448}