1use crate::disk_service::EntityDiskService;
22use crate::keystore::Keystore;
23use crate::message_sync::MessageSyncService;
24use crate::types::{DeviceType, UserProfile};
25use crate::webrtc::CommunitasWebRtcService;
26use blake3;
27use communitas_kanban::KanbanService;
28use fips204::traits::{SerDes, Signer, Verifier};
29use rand::rngs::OsRng;
30use saorsa_pqc::ml_dsa_87::{PrivateKey, PublicKey, try_keygen_with_rng};
31use std::collections::HashMap;
32use std::net::SocketAddr;
33use std::path::PathBuf;
34use std::sync::Arc;
35use tracing::{info, warn};
36
37pub struct CoreContext {
48 pub profile: UserProfile,
50
51 pub signing_key: PrivateKey,
53
54 pub public_key: PublicKey,
56
57 pub four_words: String,
59
60 pub display_name: String,
62
63 pub device_name: String,
65
66 pub crdt_manager: Arc<crate::CrdtManager>,
68
69 pub entity_service: Arc<crate::EntityService>,
71
72 pub message_service: Arc<crate::MessageService>,
74
75 pub message_sync: Arc<MessageSyncService>,
77
78 pub doc_replicator: Arc<crate::doc_replicator::DocReplicator>,
81
82 pub listen_address: Option<SocketAddr>,
84
85 pub connection_identity: Option<String>,
87
88 pub external_address: Option<SocketAddr>,
92
93 pub gossip: Option<Arc<crate::gossip::GossipContext>>,
96
97 pub group_keys: HashMap<String, GroupKeyPairPlaceholder>,
100
101 pub disk_service: Arc<EntityDiskService>,
103
104 pub webrtc: Option<Arc<CommunitasWebRtcService>>,
107
108 pub kanban_service: Arc<KanbanService>,
111
112 pub invite_service: Arc<crate::invite_service::InviteService>,
115}
116
117impl std::fmt::Debug for CoreContext {
118 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
119 f.debug_struct("CoreContext")
120 .field("profile", &self.profile)
121 .field("four_words", &self.four_words)
122 .field("display_name", &self.display_name)
123 .field("device_name", &self.device_name)
124 .field("crdt_manager", &"<active>")
125 .field("entity_service", &"<active>")
126 .field("message_service", &"<active>")
127 .field("listen_address", &self.listen_address)
128 .field("connection_identity", &self.connection_identity)
129 .field("external_address", &self.external_address)
130 .field("signing_key", &"<redacted>")
131 .field("public_key", &"<key_bytes>")
132 .field("doc_replicator", &"<active>")
133 .field("gossip", &self.gossip.as_ref().map(|_| "<active>"))
134 .field("group_keys", &self.group_keys)
135 .field("disk_service", &"<active>")
136 .field("webrtc", &self.webrtc.as_ref().map(|_| "<active>"))
137 .field("kanban_service", &"<active>")
138 .field("invite_service", &"<active>")
139 .finish()
140 }
141}
142
143#[derive(Debug, Clone)]
145pub struct GroupKeyPairPlaceholder {
146 pub group_id: String,
147 }
149
150impl CoreContext {
151 pub async fn initialize(
176 four_words: String,
177 display_name: String,
178 device_name: String,
179 device_type: DeviceType,
180 storage_dir: PathBuf,
181 ) -> Result<Self, String> {
182 let words: Vec<&str> = four_words.split('-').collect();
184 if words.len() != 4 {
185 return Err(format!(
186 "Invalid four-word format: expected 4 words, got {}",
187 words.len()
188 ));
189 }
190
191 let keystore = Keystore::new();
193
194 let id_hex = blake3::hash(four_words.as_bytes()).to_hex().to_string();
196
197 let (public_key, signing_key) = match keystore.load_mldsa_keys(&id_hex) {
199 Ok((pk_bytes, sk_bytes)) => {
200 info!(
201 "Loaded existing ML-DSA-87 keypair for identity '{}' from keystore",
202 four_words
203 );
204
205 let public_key = PublicKey::try_from_bytes(
207 pk_bytes
208 .as_slice()
209 .try_into()
210 .map_err(|_| "Invalid public key length in keystore".to_string())?,
211 )
212 .map_err(|e| format!("Failed to deserialize public key: {}", e))?;
213
214 let signing_key = PrivateKey::try_from_bytes(
215 sk_bytes
216 .as_slice()
217 .try_into()
218 .map_err(|_| "Invalid signing key length in keystore".to_string())?,
219 )
220 .map_err(|e| format!("Failed to deserialize signing key: {}", e))?;
221
222 (public_key, signing_key)
223 }
224 Err(_) => {
225 info!(
227 "Generating new ML-DSA-87 keypair for identity '{}' using CSPRNG",
228 four_words
229 );
230
231 let mut rng = OsRng;
232 let (public_key, signing_key) = try_keygen_with_rng(&mut rng)
233 .map_err(|e| format!("Failed to generate ML-DSA-87 keypair: {}", e))?;
234
235 let pk_bytes = public_key.clone().into_bytes();
237 let sk_bytes = signing_key.clone().into_bytes();
238
239 keystore
240 .save_mldsa_keys(&id_hex, &pk_bytes, &sk_bytes)
241 .map_err(|e| {
242 warn!(
243 "Failed to save keys to keystore: {}. Keys will not persist.",
244 e
245 );
246 e
247 })?;
248
249 info!("Saved ML-DSA-87 keypair to secure keystore (Level 5 PQC security)");
250
251 (public_key, signing_key)
252 }
253 };
254
255 let pubkey_bytes = public_key.clone().into_bytes();
257
258 if !storage_dir.exists() {
260 std::fs::create_dir_all(&storage_dir).map_err(|e| {
261 format!(
262 "Failed to create storage directory {:?}: {}",
263 storage_dir, e
264 )
265 })?;
266 }
267
268 let pubkey_array: [u8; 32] = pubkey_bytes[..32]
270 .try_into()
271 .map_err(|_| "Public key too short".to_string())?;
272
273 let profile = UserProfile::new(
275 four_words.clone(),
276 display_name.clone(),
277 pubkey_array,
278 device_type,
279 storage_dir.clone(),
280 );
281
282 let crdt_manager = Arc::new(
284 crate::CrdtManager::new(&storage_dir.join("crdt.db"))
285 .await
286 .map_err(|e| format!("Failed to initialize CrdtManager: {}", e))?,
287 );
288
289 let entity_service = Arc::new(crate::EntityService::new(crdt_manager.clone()));
291
292 let message_service = Arc::new(crate::MessageService::new(four_words.clone()));
294
295 let message_sync = Arc::new(MessageSyncService::new(four_words.clone()));
297
298 let doc_config = crate::doc_replicator::DocReplicatorConfig {
300 files_storage_enabled: true,
301 web_storage_enabled: true,
302 };
303 let doc_replicator = Arc::new(
304 crate::doc_replicator::DocReplicator::new(doc_config)
305 .await
306 .map_err(|e| format!("Failed to initialize DocReplicator: {}", e))?,
307 );
308
309 let disk_root = storage_dir.join("disks");
311 let disk_service = Arc::new(
312 EntityDiskService::new(&disk_root)
313 .await
314 .map_err(|e| format!("Failed to initialize EntityDiskService: {}", e))?,
315 );
316
317 let kanban_service = Arc::new(KanbanService::new(four_words.clone()));
319
320 let invite_service = Arc::new(crate::invite_service::InviteService::new(
322 crdt_manager.clone(),
323 entity_service.clone(),
324 ));
325
326 info!(
327 "CoreContext initialized for user '{}' ({}) with EntityService, MessageService, DocReplicator, DiskService, KanbanService, and InviteService",
328 display_name, four_words
329 );
330
331 Ok(Self {
332 profile,
333 signing_key,
334 public_key,
335 four_words,
336 display_name,
337 device_name,
338 crdt_manager,
339 entity_service,
340 message_service,
341 message_sync,
342 doc_replicator,
343 listen_address: None,
344 connection_identity: None,
345 external_address: None,
346 gossip: None,
347 group_keys: HashMap::new(),
348 disk_service,
349 webrtc: None, kanban_service,
351 invite_service,
352 })
353 }
354
355 pub async fn start_networking(
370 &mut self,
371 preferred_port: Option<u16>,
372 ) -> Result<String, String> {
373 info!("Starting gossip networking for {}", self.four_words);
374
375 let mut port_manager = if let Some(port) = preferred_port {
377 crate::gossip::PortManager::with_preferred_port(port)
378 } else {
379 crate::gossip::PortManager::new()
380 };
381
382 let listen_port = port_manager
383 .allocate_port()
384 .map_err(|e| format!("Failed to allocate port: {}", e))?;
385
386 info!("Allocated port {} for QUIC transport", listen_port);
387
388 let gossip_ctx = crate::gossip::GossipContext::initialize(
390 self.four_words.clone(),
391 self.display_name.clone(),
392 self.device_name.clone(),
393 Some(listen_port),
394 )
395 .await
396 .map_err(|e| format!("Failed to initialize gossip: {}", e))?;
397
398 info!("Executing gossip boot sequence (5 steps)");
401 let mut boot_sequence = crate::gossip::GossipBootSequence::new(gossip_ctx);
402 boot_sequence
403 .execute()
404 .await
405 .map_err(|e| format!("Failed to execute boot sequence: {}", e))?;
406
407 let gossip = boot_sequence.into_context();
409 info!("Gossip boot sequence completed successfully");
410
411 let local_ip =
413 local_ip_address::local_ip().map_err(|e| format!("Failed to get local IP: {}", e))?;
414
415 let listen_addr = std::net::SocketAddr::new(local_ip, listen_port);
416
417 let connection_identity = crate::conn_words(&listen_addr)
419 .map_err(|e| format!("Failed to encode connection address: {}", e))?;
420
421 info!(
422 "Gossip networking started on {} ({})",
423 listen_addr, connection_identity
424 );
425
426 self.listen_address = Some(listen_addr);
427 self.connection_identity = Some(connection_identity.clone());
428 let gossip_arc = Arc::new(gossip);
429 self.gossip = Some(gossip_arc.clone());
430
431 self.setup_entity_message_handler().await?;
433
434 match CommunitasWebRtcService::new(gossip_arc).await {
436 Ok(webrtc) => {
437 info!("WebRTC service initialized successfully");
438 self.webrtc = Some(Arc::new(webrtc));
439 }
440 Err(e) => {
441 warn!(
442 "Failed to initialize WebRTC service: {}. Voice/video calls will be unavailable.",
443 e
444 );
445 }
447 }
448
449 info!("Scheduling automatic external address detection...");
452
453 Ok(connection_identity)
454 }
455
456 async fn setup_entity_message_handler(&self) -> Result<(), String> {
464 let gossip = self.gossip.as_ref().ok_or("Gossip not initialized")?;
465
466 let message_service = self.message_service.clone();
468 let gossip_clone = gossip.clone();
469
470 let handler: crate::gossip::EntityMessageHandler =
472 Arc::new(move |entity_id, sender_peer_id, message_bytes| {
473 let gossip_msg: Result<crate::crdt::GossipMessageType, _> =
476 serde_json::from_slice(&message_bytes);
477
478 let message_service = message_service.clone();
479 let gossip_clone = gossip_clone.clone();
480 let entity_id_clone = entity_id.clone();
481
482 match gossip_msg {
483 Ok(crate::crdt::GossipMessageType::Chat(crdt_message)) => {
484 info!(
485 "Received chat message for entity {} from peer {:?}: {}",
486 entity_id, sender_peer_id, crdt_message.metadata.id
487 );
488 Self::handle_chat_message(message_service, entity_id_clone, crdt_message);
489 }
490 Ok(crate::crdt::GossipMessageType::SyncRequest(sync_request)) => {
491 info!(
492 "Received sync request for entity {} from peer {:?}",
493 entity_id, sender_peer_id
494 );
495 Self::handle_sync_request(
496 message_service,
497 gossip_clone,
498 entity_id_clone,
499 sender_peer_id,
500 sync_request,
501 );
502 }
503 Ok(crate::crdt::GossipMessageType::SyncResponse(sync_response)) => {
504 info!(
505 "Received sync response for entity {} from peer {:?} with {} messages",
506 entity_id,
507 sender_peer_id,
508 sync_response.messages.len()
509 );
510 Self::handle_sync_response(message_service, entity_id_clone, sync_response);
511 }
512 Err(_) => {
513 match serde_json::from_slice::<crate::crdt::CRDTMessage>(&message_bytes) {
515 Ok(crdt_message) => {
516 info!(
517 "Received legacy message for entity {} from peer {:?}: {}",
518 entity_id, sender_peer_id, crdt_message.metadata.id
519 );
520 Self::handle_chat_message(
521 message_service,
522 entity_id_clone,
523 crdt_message,
524 );
525 }
526 Err(e) => {
527 warn!(
528 "Failed to deserialize message for entity {}: {}",
529 entity_id, e
530 );
531 }
532 }
533 }
534 }
535 });
536
537 gossip.set_entity_message_handler(handler).await;
539
540 info!("Entity message handler registered for incoming gossip messages");
541 Ok(())
542 }
543
544 fn handle_chat_message(
546 message_service: Arc<crate::MessageService>,
547 entity_id: String,
548 crdt_message: crate::crdt::CRDTMessage,
549 ) {
550 tokio::spawn(async move {
551 match message_service.receive_message(crdt_message).await {
552 Ok(result) => {
553 if result.accepted {
554 info!("Stored incoming message for entity {}", entity_id);
555 } else if result.out_of_order {
556 warn!(
557 "Message for entity {} was out of order, queued for later",
558 entity_id
559 );
560 }
561 }
562 Err(e) => {
563 warn!(
564 "Failed to store incoming message for entity {}: {}",
565 entity_id, e
566 );
567 }
568 }
569 });
570 }
571
572 fn handle_sync_request(
577 message_service: Arc<crate::MessageService>,
578 gossip: Arc<crate::gossip::GossipContext>,
579 entity_id: String,
580 sender_peer_id: saorsa_gossip_types::PeerId,
581 _sync_request: crate::crdt::SyncRequest,
582 ) {
583 tokio::spawn(async move {
584 if let Err(e) = gossip
587 .add_peer_to_entity_topic(&entity_id, sender_peer_id)
588 .await
589 {
590 warn!(
591 "Failed to add peer {:?} to entity {} topic: {}",
592 sender_peer_id, entity_id, e
593 );
594 }
595
596 match message_service.get_entity_messages(entity_id.clone()).await {
598 Ok(sync_response) => {
599 info!(
600 "Sending sync response with {} messages for entity {}",
601 sync_response.messages.len(),
602 entity_id
603 );
604
605 let gossip_msg = crate::crdt::GossipMessageType::SyncResponse(sync_response);
607
608 match serde_json::to_vec(&gossip_msg) {
610 Ok(bytes) => {
611 if let Err(e) = gossip.publish_to_entity(&entity_id, bytes).await {
612 warn!("Failed to send sync response for {}: {}", entity_id, e);
613 }
614 }
615 Err(e) => {
616 warn!("Failed to serialize sync response: {}", e);
617 }
618 }
619 }
620 Err(e) => {
621 warn!("Failed to get messages for sync response: {}", e);
622 }
623 }
624 });
625 }
626
627 fn handle_sync_response(
629 message_service: Arc<crate::MessageService>,
630 entity_id: String,
631 sync_response: crate::crdt::SyncResponse,
632 ) {
633 tokio::spawn(async move {
634 let mut accepted = 0;
635 let mut rejected = 0;
636
637 for message in sync_response.messages {
638 match message_service.receive_message(message).await {
639 Ok(result) => {
640 if result.accepted {
641 accepted += 1;
642 } else {
643 rejected += 1;
644 }
645 }
646 Err(e) => {
647 warn!("Failed to process sync message for {}: {}", entity_id, e);
648 rejected += 1;
649 }
650 }
651 }
652
653 info!(
654 "Sync response processed for {}: {} accepted, {} rejected",
655 entity_id, accepted, rejected
656 );
657 });
658 }
659
660 pub async fn auto_request_external_address(&mut self) -> Result<(), String> {
668 for attempt in 1..=3 {
670 info!("Auto-detecting external address (attempt {}/3)...", attempt);
671
672 let delay_ms = if attempt == 1 { 2000 } else { 1000 };
674 tokio::time::sleep(tokio::time::Duration::from_millis(delay_ms)).await;
675
676 match self.request_external_address().await {
677 Ok(()) => {
678 if let Some(addr) = self.external_address {
679 info!("Auto-detected external address: {}", addr);
680 return Ok(());
681 }
682 }
683 Err(e) => {
684 if attempt < 3 {
685 warn!(
686 "External address detection attempt {} failed: {}. Retrying...",
687 attempt, e
688 );
689 } else {
690 warn!("External address detection failed after 3 attempts: {}", e);
691 }
692 }
693 }
694 }
695
696 Err("Failed to auto-detect external address after 3 attempts".to_string())
697 }
698
699 pub async fn request_external_address(&mut self) -> Result<(), String> {
711 let gossip = self
712 .gossip
713 .as_ref()
714 .ok_or("Networking not started. Call start_networking() first")?;
715
716 info!("Requesting external address via native QUIC address discovery");
717
718 if let Some(external_addr) = gossip.transport.get_external_address() {
721 info!(
722 "Got external address via QUIC OBSERVED_ADDRESS: {}",
723 external_addr
724 );
725 self.external_address = Some(external_addr);
726 return Ok(());
727 }
728
729 let cache = gossip.peer_cache.read().await;
731 let peers = cache.get_top_peers(5);
732 drop(cache);
733
734 if peers.is_empty() {
735 return Err("No connected peers - external address not yet available. \
736 The address will be discovered automatically when connections are established."
737 .to_string());
738 }
739
740 warn!(
745 "Connected to {} peers but no OBSERVED_ADDRESS received yet",
746 peers.len()
747 );
748 Err(
749 "External address not yet available - waiting for OBSERVED_ADDRESS frame from peers"
750 .to_string(),
751 )
752 }
753
754 pub async fn stop_networking(&mut self) -> Result<(), String> {
758 if let Some(_gossip) = self.gossip.take() {
759 info!("Stopping gossip networking for {}", self.four_words);
760 self.webrtc = None;
762 self.listen_address = None;
765 self.connection_identity = None;
766 self.external_address = None;
767 }
768 Ok(())
769 }
770
771 pub async fn connect_to_peer(&self, peer_four_words: &str) -> Result<(), String> {
782 let gossip = self
783 .gossip
784 .as_ref()
785 .ok_or("Networking not started. Call start_networking() first")?;
786
787 info!("Adding peer {} to favourites", peer_four_words);
788
789 gossip
791 .add_favourite_contact(peer_four_words.to_string())
792 .await
793 .map_err(|e| format!("Failed to add favourite contact: {}", e))?;
794
795 let maybe_addr: Option<std::net::SocketAddr> = peer_four_words
798 .parse::<std::net::SocketAddr>()
799 .ok()
800 .or_else(|| crate::identity::conn_from_words(peer_four_words).ok());
801
802 if let Some(addr) = maybe_addr {
803 info!("Dialing peer at {} ({})", addr, peer_four_words);
804 if let Err(e) = gossip.dial_address(addr).await {
805 warn!("Failed to dial peer {}: {}", addr, e);
806 } else {
807 info!("Successfully dialed peer {}", addr);
808 }
809 } else {
810 info!(
811 "Peer {} is not a direct address; relying on FOAF discovery",
812 peer_four_words
813 );
814 }
815
816 info!(
818 "Peer {} added. FOAF discovery will locate and connect automatically",
819 peer_four_words
820 );
821
822 Ok(())
823 }
824
825 pub async fn send_and_publish_channel_message(
839 &self,
840 channel_id: String,
841 content_text: String,
842 reply_to_id: Option<String>,
843 ) -> Result<String, String> {
844 use crate::crdt::{EntityType, MessageContent};
845
846 let content = MessageContent {
848 text: content_text,
849 author: self.four_words.clone(),
850 attachments: None,
851 };
852
853 let message = if let Some(reply_to) = reply_to_id {
855 self.message_service
856 .send_message(
857 channel_id.clone(),
858 EntityType::Channel,
859 content,
860 Some(reply_to),
861 )
862 .await
863 .map_err(|e| format!("Failed to send message: {}", e))?
864 } else {
865 self.message_service
866 .send_message(channel_id.clone(), EntityType::Channel, content, None)
867 .await
868 .map_err(|e| format!("Failed to send message: {}", e))?
869 };
870
871 let message_id = message.metadata.id.clone();
872
873 if let Some(gossip) = self.gossip.as_ref() {
875 if let Err(e) = gossip.join_entity(&channel_id, "channel").await {
877 warn!(
878 "Failed to join channel topic {} (may already be joined): {}",
879 channel_id, e
880 );
881 }
883
884 let message_bytes = serde_json::to_vec(&message)
886 .map_err(|e| format!("Failed to serialize message: {}", e))?;
887
888 if let Err(e) = gossip.publish_to_entity(&channel_id, message_bytes).await {
890 warn!("Failed to publish message to gossip: {}", e);
891 } else {
893 info!(
894 "Message {} published to gossip for channel {}",
895 message_id, channel_id
896 );
897 }
898 } else {
899 info!(
900 "Gossip not active - message {} stored locally only",
901 message_id
902 );
903 }
904
905 Ok(message_id)
906 }
907
908 pub fn add_group_key(&mut self, group_id: String, key: GroupKeyPairPlaceholder) {
914 self.group_keys.insert(group_id, key);
915 }
916
917 pub fn get_public_key(&self) -> &PublicKey {
919 &self.public_key
920 }
921
922 pub fn public_key_bytes(&self) -> [u8; 2592] {
925 self.public_key.clone().into_bytes()
926 }
927
928 pub fn sign(&self, message: &[u8]) -> Result<[u8; 4627], String> {
939 self.signing_key
940 .try_sign(message, &[]) .map_err(|e| format!("ML-DSA-87 signing failed: {}", e))
942 }
943
944 pub fn verify(&self, message: &[u8], signature: &[u8; 4627]) -> bool {
953 self.public_key.verify(message, signature, &[]) }
955
956 pub fn storage_dir(&self) -> &PathBuf {
958 &self.profile.storage_dir
959 }
960
961 pub fn is_networking_active(&self) -> bool {
963 self.gossip.is_some() && self.listen_address.is_some()
964 }
965
966 pub fn connection_identity(&self) -> Option<&str> {
968 self.connection_identity.as_deref()
969 }
970
971 pub fn set_display_name(&mut self, display_name: String) {
973 self.display_name = display_name.clone();
974 self.profile.display_name = display_name;
975 }
976
977 pub fn device_type(&self) -> DeviceType {
979 self.profile.device_type
980 }
981
982 pub fn has_passkey(&self) -> bool {
984 self.profile.has_passkey()
985 }
986}
987
988#[cfg(test)]
989mod tests {
990 use super::*;
991 use tempfile::TempDir;
992
993 #[tokio::test]
994 async fn test_core_context_initialization() {
995 let temp_dir = TempDir::new().unwrap();
996 let storage_dir = temp_dir.path().to_path_buf();
997
998 let context = CoreContext::initialize(
999 "ocean-forest-moon-star".to_string(),
1000 "Test User".to_string(),
1001 "Test Device".to_string(),
1002 DeviceType::Desktop,
1003 storage_dir.clone(),
1004 )
1005 .await;
1006
1007 assert!(context.is_ok());
1008 let ctx = context.unwrap();
1009
1010 assert_eq!(ctx.display_name, "Test User");
1011 assert_eq!(ctx.device_name, "Test Device");
1012 assert_eq!(ctx.profile.device_type, DeviceType::Desktop);
1013 assert_eq!(ctx.four_words, "ocean-forest-moon-star");
1014 assert!(!ctx.is_networking_active());
1015 assert!(storage_dir.exists());
1016 }
1017
1018 #[tokio::test]
1019 async fn test_invalid_four_word_format() {
1020 let temp_dir = TempDir::new().unwrap();
1021
1022 let context = CoreContext::initialize(
1023 "only-three-words".to_string(),
1024 "Test User".to_string(),
1025 "Test Device".to_string(),
1026 DeviceType::Desktop,
1027 temp_dir.path().to_path_buf(),
1028 )
1029 .await;
1030
1031 assert!(context.is_err());
1032 assert!(context.unwrap_err().contains("Invalid four-word format"));
1033 }
1034
1035 #[tokio::test]
1036 async fn test_display_name_update() {
1037 let temp_dir = TempDir::new().unwrap();
1038
1039 let mut context = CoreContext::initialize(
1040 "ocean-forest-moon-star".to_string(),
1041 "Old Name".to_string(),
1042 "Test Device".to_string(),
1043 DeviceType::Desktop,
1044 temp_dir.path().to_path_buf(),
1045 )
1046 .await
1047 .unwrap();
1048
1049 context.set_display_name("New Name".to_string());
1050
1051 assert_eq!(context.display_name, "New Name");
1052 assert_eq!(context.profile.display_name, "New Name");
1053 }
1054
1055 #[tokio::test]
1056 async fn test_signing() {
1057 let temp_dir = TempDir::new().unwrap();
1058
1059 let context = CoreContext::initialize(
1060 "ocean-forest-moon-star".to_string(),
1061 "Test User".to_string(),
1062 "Test Device".to_string(),
1063 DeviceType::Desktop,
1064 temp_dir.path().to_path_buf(),
1065 )
1066 .await
1067 .unwrap();
1068
1069 let message = b"test message";
1070 let signature = context.sign(message).unwrap();
1071
1072 assert!(context.verify(message, &signature));
1074
1075 let wrong_message = b"wrong message";
1077 assert!(!context.verify(wrong_message, &signature));
1078 }
1079
1080 #[tokio::test]
1081 async fn test_group_key_management() {
1082 let temp_dir = TempDir::new().unwrap();
1083
1084 let mut context = CoreContext::initialize(
1085 "ocean-forest-moon-star".to_string(),
1086 "Test User".to_string(),
1087 "Test Device".to_string(),
1088 DeviceType::Desktop,
1089 temp_dir.path().to_path_buf(),
1090 )
1091 .await
1092 .unwrap();
1093
1094 let group_id = "test-group".to_string();
1095 let key = GroupKeyPairPlaceholder {
1096 group_id: group_id.clone(),
1097 };
1098
1099 context.add_group_key(group_id.clone(), key);
1100
1101 assert!(context.group_keys.contains_key(&group_id));
1102 }
1103
1104 #[tokio::test]
1105 async fn test_networking_not_active_by_default() {
1106 let temp_dir = TempDir::new().unwrap();
1107
1108 let context = CoreContext::initialize(
1109 "ocean-forest-moon-star".to_string(),
1110 "Test User".to_string(),
1111 "Test Device".to_string(),
1112 DeviceType::Desktop,
1113 temp_dir.path().to_path_buf(),
1114 )
1115 .await
1116 .unwrap();
1117
1118 assert!(!context.is_networking_active());
1119 assert!(context.connection_identity().is_none());
1120 }
1121}