1use core_types::{DaemonId, EventKind, SecurityLevel};
12use std::collections::HashMap;
13use std::path::{Path, PathBuf};
14use std::sync::Arc;
15use std::sync::atomic::{AtomicU64, Ordering};
16use std::time::Instant;
17use tokio::net::{UnixListener, UnixStream};
18use tokio::sync::{RwLock, mpsc};
19use uuid::Uuid;
20
21use crate::framing::{decode_frame, encode_frame};
22use crate::message::Message;
23use crate::registry::ClearanceRegistry;
24use crate::transport::{extract_ucred, local_credentials};
25
26#[derive(Debug, Clone)]
28pub struct SubscriptionFilter {
29 pub kind_prefix: Option<String>,
31 pub min_level: SecurityLevel,
33}
34
35#[allow(dead_code)] struct ConnectionState {
38 daemon_id: Option<DaemonId>,
39 verified_name: Option<String>,
42 tx: mpsc::Sender<Vec<u8>>,
43 peer: crate::transport::PeerCredentials,
44 security_clearance: SecurityLevel,
45 subscriptions: Vec<SubscriptionFilter>,
46 trust_vector: Option<core_types::TrustVector>,
48}
49
50struct ServerState {
52 connections: RwLock<HashMap<u64, ConnectionState>>,
53 pending_requests: RwLock<HashMap<Uuid, u64>>,
55 next_conn_id: AtomicU64,
56 epoch: Instant,
57 registry: RwLock<ClearanceRegistry>,
60 confirmations: RwLock<HashMap<Uuid, mpsc::Sender<Vec<u8>>>>,
64 name_to_conn: RwLock<HashMap<String, u64>>,
68}
69
70pub struct ConfirmationGuard {
76 correlation_id: Uuid,
77 state: Arc<ServerState>,
78}
79
80impl Drop for ConfirmationGuard {
81 fn drop(&mut self) {
82 if let Ok(mut confirmations) = self.state.confirmations.try_write() {
85 confirmations.remove(&self.correlation_id);
86 } else {
87 let id = self.correlation_id;
89 let state = Arc::clone(&self.state);
90 tokio::spawn(async move {
91 state.confirmations.write().await.remove(&id);
92 });
93 }
94 }
95}
96
97pub struct BusServer {
99 listener: Option<UnixListener>,
100 socket_path: Option<PathBuf>,
101 state: Arc<ServerState>,
102 keypair: Option<Arc<snow::Keypair>>,
105}
106
107impl BusServer {
108 #[must_use]
114 pub fn new() -> Self {
115 Self {
116 listener: None,
117 socket_path: None,
118 state: Arc::new(ServerState {
119 connections: RwLock::new(HashMap::new()),
120 pending_requests: RwLock::new(HashMap::new()),
121 next_conn_id: AtomicU64::new(1),
122 epoch: Instant::now(),
123 registry: RwLock::new(ClearanceRegistry::new()),
124 confirmations: RwLock::new(HashMap::new()),
125 name_to_conn: RwLock::new(HashMap::new()),
126 }),
127 keypair: None,
128 }
129 }
130
131 pub fn bind(
145 path: &Path,
146 keypair: snow::Keypair,
147 registry: ClearanceRegistry,
148 ) -> core_types::Result<Self> {
149 if let Some(parent) = path.parent() {
150 std::fs::create_dir_all(parent).map_err(|e| {
151 core_types::Error::Ipc(format!(
152 "failed to create socket directory {}: {e}",
153 parent.display()
154 ))
155 })?;
156 }
157
158 if path.exists() {
160 std::fs::remove_file(path).map_err(|e| {
161 core_types::Error::Ipc(format!(
162 "failed to remove stale socket {}: {e}",
163 path.display()
164 ))
165 })?;
166 }
167
168 let listener = UnixListener::bind(path).map_err(|e| {
169 core_types::Error::Ipc(format!("failed to bind socket {}: {e}", path.display()))
170 })?;
171
172 #[cfg(unix)]
176 {
177 use std::os::unix::fs::PermissionsExt;
178 if let Some(parent) = path.parent() {
179 std::fs::set_permissions(parent, std::fs::Permissions::from_mode(0o700)).map_err(
180 |e| {
181 core_types::Error::Ipc(format!(
182 "failed to set directory permissions on {}: {e}",
183 parent.display()
184 ))
185 },
186 )?;
187 }
188 std::fs::set_permissions(path, std::fs::Permissions::from_mode(0o700)).map_err(
189 |e| {
190 core_types::Error::Ipc(format!(
191 "failed to set socket permissions on {}: {e}",
192 path.display()
193 ))
194 },
195 )?;
196 }
197
198 tracing::info!(path = %path.display(), "IPC bus server bound");
199
200 Ok(Self {
201 listener: Some(listener),
202 socket_path: Some(path.to_owned()),
203 state: Arc::new(ServerState {
204 connections: RwLock::new(HashMap::new()),
205 pending_requests: RwLock::new(HashMap::new()),
206 next_conn_id: AtomicU64::new(1),
207 epoch: Instant::now(),
208 registry: RwLock::new(registry),
209 confirmations: RwLock::new(HashMap::new()),
210 name_to_conn: RwLock::new(HashMap::new()),
211 }),
212 keypair: Some(Arc::new(keypair)),
213 })
214 }
215
216 pub async fn run(&self) -> core_types::Result<()> {
230 let listener = self.listener.as_ref().ok_or_else(|| {
231 core_types::Error::Ipc("BusServer::run() called without bind()".into())
232 })?;
233
234 loop {
235 match listener.accept().await {
236 Ok((stream, _addr)) => {
237 let peer = match extract_ucred(&stream) {
238 Ok(creds) => creds,
239 Err(e) => {
240 tracing::error!(error = %e, "rejecting connection: UCred extraction failed");
241 continue;
242 }
243 };
244
245 let my_uid = local_credentials().uid;
247 if peer.uid != my_uid {
248 tracing::error!(
249 peer_uid = peer.uid,
250 my_uid,
251 "rejecting connection: UID mismatch"
252 );
253 continue;
254 }
255
256 let conn_id = self.state.next_conn_id.fetch_add(1, Ordering::Relaxed);
257
258 tracing::info!(conn_id, pid = peer.pid, uid = peer.uid, "client connected");
259
260 let (tx, rx) = mpsc::channel::<Vec<u8>>(256);
262
263 let state = Arc::clone(&self.state);
264 let keypair = self
265 .keypair
266 .clone()
267 .expect("BusServer::run() requires bind() — keypair is always set");
268 tokio::spawn(async move {
269 handle_connection(state, conn_id, stream, tx, rx, peer, keypair).await;
270 });
271 }
272 Err(e) => {
273 tracing::error!(error = %e, "accept failed");
274 tokio::time::sleep(std::time::Duration::from_millis(50)).await;
277 }
278 }
279 }
280 }
281
282 pub async fn register(
284 &self,
285 daemon_id: DaemonId,
286 peer: crate::transport::PeerCredentials,
287 security_clearance: SecurityLevel,
288 subscriptions: Vec<SubscriptionFilter>,
289 tx: mpsc::Sender<Vec<u8>>,
290 ) {
291 let conn_id = self.state.next_conn_id.fetch_add(1, Ordering::Relaxed);
292 let state = ConnectionState {
293 daemon_id: Some(daemon_id),
294 verified_name: None, tx,
296 security_clearance,
297 subscriptions,
298 peer,
299 trust_vector: None,
300 };
301 self.state.connections.write().await.insert(conn_id, state);
302 tracing::info!(%daemon_id, conn_id, "subscriber registered (in-process)");
303 }
304
305 pub async fn unregister(&self, daemon_id: &DaemonId) {
307 let mut conns = self.state.connections.write().await;
308 conns.retain(|_, state| state.daemon_id.as_ref() != Some(daemon_id));
309 tracing::info!(%daemon_id, "subscriber unregistered");
310 }
311
312 pub async fn publish(&self, frame: &[u8], security_level: SecurityLevel) {
314 let conns = self.state.connections.read().await;
315 let sender_id: Option<DaemonId> = decode_frame::<Message<EventKind>>(frame)
318 .ok()
319 .map(|msg| msg.sender);
320 for (id, state) in conns.iter() {
321 if let Some(sid) = sender_id
323 && state.daemon_id == Some(sid)
324 {
325 continue;
326 }
327 if state.security_clearance >= security_level
328 && state.tx.try_send(frame.to_vec()).is_err()
329 {
330 tracing::warn!(conn_id = id, "subscriber channel full, frame dropped");
331 }
332 }
333 }
334
335 #[must_use]
337 pub fn epoch(&self) -> Instant {
338 self.state.epoch
339 }
340
341 #[must_use]
343 pub fn socket_path(&self) -> Option<&Path> {
344 self.socket_path.as_deref()
345 }
346
347 pub async fn connection_count(&self) -> usize {
349 self.state.connections.read().await.len()
350 }
351
352 pub async fn send_to(&self, conn_id: u64, frame: &[u8]) -> bool {
357 let conns = self.state.connections.read().await;
358 if let Some(conn) = conns.get(&conn_id) {
359 conn.tx.try_send(frame.to_vec()).is_ok()
360 } else {
361 false
362 }
363 }
364
365 pub async fn registry_mut(&self) -> tokio::sync::RwLockWriteGuard<'_, ClearanceRegistry> {
367 self.state.registry.write().await
368 }
369
370 pub async fn take_pending_request(&self, correlation_id: &uuid::Uuid) -> Option<u64> {
377 self.state
378 .pending_requests
379 .write()
380 .await
381 .remove(correlation_id)
382 }
383
384 pub async fn register_confirmation(
390 &self,
391 correlation_id: Uuid,
392 confirm_tx: mpsc::Sender<Vec<u8>>,
393 ) -> ConfirmationGuard {
394 self.state
395 .confirmations
396 .write()
397 .await
398 .insert(correlation_id, confirm_tx);
399 ConfirmationGuard {
400 correlation_id,
401 state: Arc::clone(&self.state),
402 }
403 }
404
405 pub async fn send_to_named(&self, daemon_name: &str, frame: &[u8]) -> Result<(), String> {
416 let conn_id = {
417 let map = self.state.name_to_conn.read().await;
418 map.get(daemon_name).copied()
419 };
420 match conn_id {
421 Some(id) => {
422 if self.send_to(id, frame).await {
423 Ok(())
424 } else {
425 Err(format!(
426 "send_to_named({daemon_name}): connection gone or channel full"
427 ))
428 }
429 }
430 None => Err(format!(
431 "send_to_named({daemon_name}): daemon not connected"
432 )),
433 }
434 }
435}
436
437impl Default for BusServer {
438 fn default() -> Self {
439 Self::new()
440 }
441}
442
443impl Drop for BusServer {
444 fn drop(&mut self) {
445 if let Some(path) = &self.socket_path {
447 let _ = std::fs::remove_file(path);
448 }
449 }
450}
451
452fn hex_encode(bytes: &[u8; 32]) -> String {
454 use std::fmt::Write;
455 bytes.iter().fold(String::with_capacity(64), |mut s, b| {
456 let _ = write!(s, "{b:02x}");
457 s
458 })
459}
460
461#[allow(clippy::too_many_lines)]
468async fn handle_connection(
469 state: Arc<ServerState>,
470 conn_id: u64,
471 stream: UnixStream,
472 tx: mpsc::Sender<Vec<u8>>,
473 mut outbound_rx: mpsc::Receiver<Vec<u8>>,
474 peer_creds: crate::transport::PeerCredentials,
475 keypair: Arc<snow::Keypair>,
476) {
477 let (reader, writer) = stream.into_split();
478 let mut reader = tokio::io::BufReader::new(reader);
479 let mut writer = tokio::io::BufWriter::new(writer);
480
481 let local_creds = local_credentials();
483 let connected_at = Instant::now();
484
485 let mut transport = match crate::noise::server_handshake(
486 &mut reader,
487 &mut writer,
488 &keypair,
489 &local_creds,
490 &peer_creds,
491 )
492 .await
493 {
494 Ok(t) => t,
495 Err(e) => {
496 tracing::error!(
498 audit = "connection-lifecycle",
499 event_type = "handshake-failed",
500 conn_id,
501 peer_pid = peer_creds.pid,
502 peer_uid = peer_creds.uid,
503 error = %e,
504 "Noise handshake failed, dropping connection"
505 );
506 return;
507 }
508 };
509
510 let client_pubkey: [u8; 32] = transport
512 .remote_static()
513 .expect("Noise IK pattern guarantees remote static key after handshake")
514 .try_into()
515 .expect("Curve25519 public key is exactly 32 bytes");
516
517 let (security_clearance, verified_name) = {
519 let reg = state.registry.read().await;
520 if let Some(identity) = reg.lookup(&client_pubkey)
521 && let Some(name) = reg.lookup_name(&client_pubkey).map(str::to_owned)
522 {
523 tracing::info!(
524 audit = "connection-lifecycle",
525 event_type = "handshake-success",
526 conn_id,
527 daemon = %name,
528 clearance = ?identity.security_level,
529 pubkey = %hex_encode(&client_pubkey),
530 peer_pid = peer_creds.pid,
531 peer_uid = peer_creds.uid,
532 "daemon authenticated via registry"
533 );
534 (identity.security_level, Some(name))
535 } else {
536 tracing::info!(
537 audit = "connection-lifecycle",
538 event_type = "ephemeral-client-accepted",
539 conn_id,
540 clearance = ?core_types::SecurityLevel::SecretsOnly,
541 pubkey = %hex_encode(&client_pubkey),
542 peer_pid = peer_creds.pid,
543 peer_uid = peer_creds.uid,
544 "ephemeral client accepted via UCred same-UID validation"
545 );
546 (core_types::SecurityLevel::SecretsOnly, None)
547 }
548 };
549
550 let trust_vector = core_types::TrustVector {
554 authn_strength: core_types::TrustLevel::Low,
555 authz_freshness: std::time::Duration::ZERO,
556 delegation_depth: 0,
557 device_posture: 0.0,
558 network_exposure: core_types::NetworkTrust::Local,
559 agent_type: core_types::AgentType::Human,
560 };
561
562 let conn = ConnectionState {
563 daemon_id: None,
564 verified_name,
565 tx,
566 peer: peer_creds,
567 security_clearance,
568 subscriptions: vec![],
569 trust_vector: Some(trust_vector),
570 };
571 state.connections.write().await.insert(conn_id, conn);
572
573 loop {
581 tokio::select! {
582 result = transport.read_encrypted_frame(&mut reader) => {
583 match result {
584 Ok(mut payload) => {
585 route_frame(&state, conn_id, &payload).await;
586 zeroize::Zeroize::zeroize(&mut payload);
588 }
589 Err(e) => {
590 let session_ms = connected_at.elapsed().as_millis();
591 let daemon_name = state.connections.read().await
592 .get(&conn_id)
593 .and_then(|c| c.daemon_id)
594 .map(|id| id.to_string());
595 tracing::info!(
596 audit = "connection-lifecycle",
597 event_type = "disconnect",
598 conn_id,
599 daemon = daemon_name.as_deref().unwrap_or("unknown"),
600 session_duration_ms = %session_ms,
601 error = %e,
602 "client disconnected"
603 );
604 break;
605 }
606 }
607 }
608 Some(mut payload) = outbound_rx.recv() => {
609 let result = transport.write_encrypted_frame(&mut writer, &payload).await;
610 zeroize::Zeroize::zeroize(&mut payload);
612 if let Err(e) = result {
613 tracing::debug!(conn_id, error = %e, "encrypted write failed, closing");
614 break;
615 }
616 }
617 else => break,
618 }
619 }
620
621 state.connections.write().await.remove(&conn_id);
622 state
623 .pending_requests
624 .write()
625 .await
626 .retain(|_, cid| *cid != conn_id);
627 state
629 .name_to_conn
630 .write()
631 .await
632 .retain(|_, cid| *cid != conn_id);
633 let session_ms = connected_at.elapsed().as_millis();
634 tracing::debug!(
635 audit = "connection-lifecycle",
636 event_type = "cleanup",
637 conn_id,
638 session_duration_ms = %session_ms,
639 "connection cleaned up"
640 );
641}
642
643fn send_access_denied(
646 conn: &ConnectionState,
647 request_msg_id: Uuid,
648 epoch: Instant,
649 reason: String,
650) {
651 let ctx = crate::message::MessageContext::new(DaemonId::new());
652 let reply = Message::new(
653 &ctx,
654 EventKind::AccessDenied { reason },
655 SecurityLevel::Open,
656 epoch,
657 )
658 .with_correlation(request_msg_id);
659
660 if let Ok(reply_bytes) = encode_frame(&reply) {
661 let _ = conn.tx.try_send(reply_bytes);
662 }
663}
664
665#[allow(clippy::too_many_lines)]
672async fn route_frame(state: &ServerState, sender_conn_id: u64, payload: &[u8]) {
673 let mut msg: Message<EventKind> = match decode_frame(payload) {
675 Ok(m) => m,
676 Err(e) => {
677 tracing::warn!(conn_id = sender_conn_id, error = %e, "malformed frame, dropping");
678 return;
679 }
680 };
681
682 let verified_name: Option<String>;
685 let conn_trust_vector: Option<core_types::TrustVector>;
686 {
687 let mut conns = state.connections.write().await;
688 if let Some(conn) = conns.get_mut(&sender_conn_id) {
689 if let Some(known_id) = conn.daemon_id {
693 if known_id != msg.sender {
694 tracing::warn!(
695 audit = "security",
696 event_type = "sender-identity-mismatch",
697 conn_id = sender_conn_id,
698 expected_sender = %known_id,
699 claimed_sender = %msg.sender,
700 verified_name = conn.verified_name.as_deref().unwrap_or("unregistered"),
701 "sender DaemonId changed mid-session, dropping frame"
702 );
703 send_access_denied(
704 conn,
705 msg.msg_id,
706 state.epoch,
707 "sender identity changed mid-session".into(),
708 );
709 return;
710 }
711 } else {
712 conn.daemon_id = Some(msg.sender);
713
714 if let Some(ref name) = conn.verified_name {
716 tracing::info!(
717 audit = "identity-binding",
718 conn_id = sender_conn_id,
719 daemon_name = %name,
720 daemon_id = %msg.sender,
721 "daemon identity bound: registry name <-> self-declared DaemonId"
722 );
723 }
724 }
725
726 if conn.security_clearance < msg.security_level {
730 tracing::warn!(
731 conn_id = sender_conn_id,
732 sender_clearance = ?conn.security_clearance,
733 msg_level = ?msg.security_level,
734 "sender clearance below message security level, rejecting"
735 );
736 send_access_denied(
737 conn,
738 msg.msg_id,
739 state.epoch,
740 format!(
741 "sender clearance {:?} below message security level {:?}",
742 conn.security_clearance, msg.security_level,
743 ),
744 );
745 return;
746 }
747
748 verified_name = conn.verified_name.clone();
750 conn_trust_vector = conn.trust_vector.clone();
751 } else {
752 verified_name = None;
753 conn_trust_vector = None;
754 }
755 }
756
757 if let EventKind::DaemonStarted { .. } = &msg.payload
763 && let Some(ref name) = verified_name
764 {
765 let existing = state.name_to_conn.read().await.get(name).copied();
766 if let Some(existing_conn_id) = existing
767 && existing_conn_id != sender_conn_id
768 {
769 state.connections.write().await.remove(&existing_conn_id);
772 tracing::info!(
773 audit = "connection-lifecycle",
774 event_type = "key-rotation-reconnect",
775 daemon_name = %name,
776 old_conn_id = existing_conn_id,
777 new_conn_id = sender_conn_id,
778 "evicted stale connection, accepting reconnect"
779 );
780 }
781 state
782 .name_to_conn
783 .write()
784 .await
785 .insert(name.clone(), sender_conn_id);
786 tracing::info!(
787 daemon_name = %name,
788 conn_id = sender_conn_id,
789 "name_to_conn mapping registered"
790 );
791 }
792
793 msg.verified_sender_name = verified_name;
799 if msg.trust_snapshot.is_none() {
800 msg.trust_snapshot = conn_trust_vector;
801 }
802 let stamped_payload = match encode_frame(&msg) {
803 Ok(p) => p,
804 Err(e) => {
805 tracing::error!(conn_id = sender_conn_id, error = %e, "failed to re-encode stamped frame");
806 return;
807 }
808 };
809
810 if let Some(corr_id) = msg.correlation_id {
811 let confirm_tx = state.confirmations.read().await.get(&corr_id).cloned();
815 if let Some(ref tx) = confirm_tx {
816 let _ = tx.try_send(stamped_payload.clone()).inspect_err(|_| {
817 tracing::warn!(
818 correlation_id = %corr_id,
819 "confirmed RPC response dropped: confirmation channel full"
820 );
821 });
822 }
823
824 let target_conn = state.pending_requests.write().await.remove(&corr_id);
826 if let Some(target_id) = target_conn {
827 let conns = state.connections.read().await;
828 if let Some(target) = conns.get(&target_id)
829 && target.tx.try_send(stamped_payload.clone()).is_err()
830 {
831 tracing::warn!(
832 conn_id = target_id,
833 correlation_id = %corr_id,
834 "response dropped: channel full"
835 );
836 }
837 } else if confirm_tx.is_none() {
838 tracing::debug!(
839 correlation_id = %corr_id,
840 "response for unknown request, dropping"
841 );
842 }
843 } else {
844 state
846 .pending_requests
847 .write()
848 .await
849 .insert(msg.msg_id, sender_conn_id);
850
851 let conns = state.connections.read().await;
852 for (&cid, conn) in conns.iter() {
853 if cid == sender_conn_id {
854 continue; }
856 if conn.security_clearance < msg.security_level {
857 continue; }
859 if conn.tx.try_send(stamped_payload.clone()).is_err() {
860 tracing::warn!(conn_id = cid, "subscriber channel full, frame dropped");
861 }
862 }
863 }
864}