1use std::collections::HashMap;
33use std::net::IpAddr;
34use std::path::PathBuf;
35use std::sync::Arc;
36
37use tokio::net::TcpStream;
38use tokio::sync::{broadcast, RwLock};
39
40use crate::envelope::codec::{EnvelopeCodec, JsonCodec};
41use crate::envelope::{Envelope, EnvelopeError};
42use crate::file_transfer::{self, FileTransferState};
43use crate::network::tailscale::{TailscaleConfig, TailscaleProvider};
44use crate::network::{
45 HealthInfo, NetworkProvider, NetworkUdpSocket, NodeIdentity, PingResult,
46};
47use crate::session::{PeerEvent, PeerRegistry, PeerState};
48use crate::transport::websocket::WebSocketTransport;
49use crate::transport::{RawListener, WsConfig};
50
51#[derive(Debug, Clone)]
60pub struct NamespacedMessage {
61 pub from: String,
63 pub namespace: String,
65 pub msg_type: String,
67 pub payload: serde_json::Value,
69 pub timestamp: Option<u64>,
71}
72
73#[derive(Debug, Clone)]
83pub struct Peer {
84 pub id: String,
86 pub name: String,
88 pub ip: IpAddr,
90 pub online: bool,
92 pub ws_connected: bool,
94 pub connection_type: String,
96 pub os: Option<String>,
98 pub last_seen: Option<String>,
100}
101
102impl From<PeerState> for Peer {
103 fn from(s: PeerState) -> Self {
104 Self {
105 id: s.id,
106 name: s.name,
107 ip: s.ip,
108 online: s.online,
109 ws_connected: s.ws_connected,
110 connection_type: s.connection_type,
111 os: s.os,
112 last_seen: s.last_seen,
113 }
114 }
115}
116
117#[derive(Debug, thiserror::Error)]
123pub enum NodeError {
124 #[error("peer not found: {0}")]
126 PeerNotFound(String),
127
128 #[error("connection failed: {0}")]
130 ConnectionFailed(String),
131
132 #[error("send failed: {0}")]
134 SendFailed(String),
135
136 #[error("envelope error: {0}")]
138 Envelope(#[from] EnvelopeError),
139
140 #[error("session error: {0}")]
142 Session(#[from] crate::session::SessionError),
143
144 #[error("network error: {0}")]
146 Network(#[from] crate::network::NetworkError),
147
148 #[error("transport error: {0}")]
150 Transport(#[from] crate::transport::TransportError),
151
152 #[error("not implemented: {0}")]
154 NotImplemented(String),
155
156 #[error("node stopped")]
158 Stopped,
159
160 #[error("build error: {0}")]
162 BuildError(String),
163}
164
165pub struct Node<N: NetworkProvider + 'static> {
181 network: Arc<N>,
183 session: Arc<PeerRegistry<N>>,
185 codec: Arc<dyn EnvelopeCodec>,
187 #[allow(dead_code)]
190 incoming_tx: broadcast::Sender<NamespacedMessage>,
191 namespace_filters: Arc<RwLock<HashMap<String, broadcast::Sender<NamespacedMessage>>>>,
193 pub(crate) file_transfer_state: FileTransferState,
195}
196
197impl<N: NetworkProvider + 'static> Node<N> {
198 pub(crate) fn from_parts(
204 network: Arc<N>,
205 session: Arc<PeerRegistry<N>>,
206 codec: Arc<dyn EnvelopeCodec>,
207 ) -> Self {
208 let (incoming_tx, _) = broadcast::channel(1024);
209 let namespace_filters: Arc<RwLock<HashMap<String, broadcast::Sender<NamespacedMessage>>>> =
210 Arc::new(RwLock::new(HashMap::new()));
211
212 let node = Self {
213 network,
214 session: session.clone(),
215 codec: codec.clone(),
216 incoming_tx: incoming_tx.clone(),
217 namespace_filters: namespace_filters.clone(),
218 file_transfer_state: FileTransferState::new(),
219 };
220
221 node.spawn_envelope_router(session, codec, incoming_tx, namespace_filters);
223
224 node
225 }
226
227 fn spawn_envelope_router(
231 &self,
232 session: Arc<PeerRegistry<N>>,
233 codec: Arc<dyn EnvelopeCodec>,
234 incoming_tx: broadcast::Sender<NamespacedMessage>,
235 namespace_filters: Arc<RwLock<HashMap<String, broadcast::Sender<NamespacedMessage>>>>,
236 ) {
237 let mut rx = session.subscribe();
238
239 tokio::spawn(async move {
240 loop {
241 match rx.recv().await {
242 Ok(msg) => {
243 if let Ok(envelope) = codec.decode(&msg.data) {
244 let namespaced = NamespacedMessage {
245 from: msg.from,
246 namespace: envelope.namespace.clone(),
247 msg_type: envelope.msg_type,
248 payload: envelope.payload,
249 timestamp: envelope.timestamp,
250 };
251
252 tracing::debug!(
253 from = %namespaced.from,
254 namespace = %namespaced.namespace,
255 msg_type = %namespaced.msg_type,
256 "envelope router: dispatching message"
257 );
258
259 let _ = incoming_tx.send(namespaced.clone());
261
262 let filters = namespace_filters.read().await;
264 let _has_subscriber = filters.contains_key(&namespaced.namespace);
265 if let Some(tx) = filters.get(&namespaced.namespace) {
266 let send_result = tx.send(namespaced);
267 tracing::debug!(
268 namespace = %envelope.namespace,
269 subscriber_count = tx.receiver_count(),
270 sent = send_result.is_ok(),
271 "envelope router: sent to namespace subscriber"
272 );
273 } else {
274 tracing::debug!(
275 namespace = %envelope.namespace,
276 "envelope router: no subscriber for namespace"
277 );
278 }
279 } else {
280 tracing::warn!(
281 from = %msg.from,
282 data_len = msg.data.len(),
283 "node: failed to decode envelope from incoming message"
284 );
285 }
286 }
287 Err(broadcast::error::RecvError::Lagged(n)) => {
288 tracing::warn!(
289 missed = n,
290 "node: envelope router lagged, missed {n} messages"
291 );
292 continue;
293 }
294 Err(broadcast::error::RecvError::Closed) => {
295 tracing::debug!("node: session incoming channel closed, router exiting");
296 break;
297 }
298 }
299 }
300 });
301 }
302
303 pub fn builder() -> NodeBuilder {
307 NodeBuilder::default()
308 }
309
310 pub fn file_transfer(&self) -> file_transfer::FileTransfer<'_, N> {
317 file_transfer::FileTransfer::new(self)
318 }
319
320 pub fn synced_store<T>(
329 self: &Arc<Self>,
330 store_id: &str,
331 ) -> Arc<crate::synced_store::SyncedStore<T>>
332 where
333 T: serde::Serialize
334 + serde::de::DeserializeOwned
335 + Clone
336 + Send
337 + Sync
338 + 'static,
339 {
340 crate::synced_store::SyncedStore::new(self.clone(), store_id)
341 }
342
343 pub async fn stop(&self) {
351 tracing::info!("node: stopping");
352 }
356
357 pub fn local_info(&self) -> NodeIdentity {
361 self.network.local_identity()
362 }
363
364 pub async fn peers(&self) -> Vec<Peer> {
371 self.session
372 .peers()
373 .await
374 .into_iter()
375 .map(Peer::from)
376 .collect()
377 }
378
379 pub fn on_peer_change(&self) -> broadcast::Receiver<PeerEvent> {
381 self.session.on_peer_change()
382 }
383
384 pub async fn resolve_peer_id(&self, peer_id: &str) -> Result<String, NodeError> {
390 let peers = self.session.peers().await;
391 peers
392 .iter()
393 .find(|p| p.id == peer_id || p.name == peer_id)
394 .map(|p| p.id.clone())
395 .ok_or_else(|| NodeError::PeerNotFound(peer_id.to_string()))
396 }
397
398 pub async fn ping(&self, peer_id: &str) -> Result<PingResult, NodeError> {
404 let peers = self.session.peers().await;
405 let peer = peers
406 .iter()
407 .find(|p| p.id == peer_id || p.name == peer_id)
408 .ok_or_else(|| NodeError::PeerNotFound(peer_id.to_string()))?;
409
410 let addr = peer.ip.to_string();
411 self.network
412 .ping(&addr)
413 .await
414 .map_err(NodeError::Network)
415 }
416
417 pub async fn health(&self) -> HealthInfo {
419 self.network.health().await
420 }
421
422 pub async fn send(
430 &self,
431 peer_id: &str,
432 namespace: &str,
433 data: &[u8],
434 ) -> Result<(), NodeError> {
435 let payload = std::str::from_utf8(data)
441 .ok()
442 .and_then(|s| serde_json::from_str::<serde_json::Value>(s).ok())
443 .unwrap_or_else(|| serde_json::Value::from(data.to_vec()));
444
445 let envelope = Envelope::new(
446 namespace,
447 "message",
448 payload,
449 )
450 .with_timestamp();
451
452 let encoded = self.codec.encode(&envelope)?;
453 self.session.send(peer_id, &encoded).await?;
454 Ok(())
455 }
456
457 pub async fn send_typed(
465 &self,
466 peer_id: &str,
467 namespace: &str,
468 msg_type: &str,
469 payload: &serde_json::Value,
470 ) -> Result<(), NodeError> {
471 let envelope = Envelope::new(namespace, msg_type, payload.clone()).with_timestamp();
472 let encoded = self.codec.encode(&envelope)?;
473 self.session.send(peer_id, &encoded).await?;
474 Ok(())
475 }
476
477 pub async fn broadcast_typed(
480 &self,
481 namespace: &str,
482 msg_type: &str,
483 payload: &serde_json::Value,
484 ) {
485 let envelope = Envelope::new(namespace, msg_type, payload.clone()).with_timestamp();
486 match self.codec.encode(&envelope) {
487 Ok(encoded) => {
488 self.session.broadcast(&encoded).await;
489 }
490 Err(e) => {
491 tracing::error!("node: failed to encode broadcast envelope: {e}");
492 }
493 }
494 }
495
496 pub async fn broadcast(&self, namespace: &str, data: &[u8]) {
501 let payload = std::str::from_utf8(data)
502 .ok()
503 .and_then(|s| serde_json::from_str::<serde_json::Value>(s).ok())
504 .unwrap_or_else(|| serde_json::Value::from(data.to_vec()));
505
506 let envelope = Envelope::new(
507 namespace,
508 "message",
509 payload,
510 )
511 .with_timestamp();
512
513 match self.codec.encode(&envelope) {
514 Ok(encoded) => {
515 self.session.broadcast(&encoded).await;
516 }
517 Err(e) => {
518 tracing::error!("node: failed to encode broadcast envelope: {e}");
519 }
520 }
521 }
522
523 pub fn subscribe(&self, namespace: &str) -> broadcast::Receiver<NamespacedMessage> {
529 {
531 let filters = self.namespace_filters.blocking_lock_read();
532 if let Some(tx) = filters.get(namespace) {
533 return tx.subscribe();
534 }
535 }
536
537 let mut filters = self.namespace_filters.blocking_lock_write();
539 if let Some(tx) = filters.get(namespace) {
541 return tx.subscribe();
542 }
543 let (tx, rx) = broadcast::channel(256);
544 filters.insert(namespace.to_string(), tx);
545 rx
546 }
547
548 pub async fn open_tcp(
556 &self,
557 peer_id: &str,
558 port: u16,
559 ) -> Result<TcpStream, NodeError> {
560 let peers = self.session.peers().await;
561 let peer = peers
562 .iter()
563 .find(|p| p.id == peer_id || p.name == peer_id)
564 .ok_or_else(|| NodeError::PeerNotFound(peer_id.to_string()))?;
565
566 let addr = peer.ip.to_string();
567 self.network
568 .dial_tcp(&addr, port)
569 .await
570 .map_err(|e| NodeError::ConnectionFailed(e.to_string()))
571 }
572
573 pub async fn listen_tcp(&self, port: u16) -> Result<RawListener, NodeError> {
578 use crate::transport::tcp::TcpTransport;
579 use crate::transport::RawTransport;
580
581 let tcp = TcpTransport::new(self.network.clone());
582 tcp.listen(port).await.map_err(NodeError::Transport)
583 }
584
585 pub async fn open_quic(&self, _peer_id: &str) -> Result<(), NodeError> {
589 Err(NodeError::NotImplemented(
590 "QUIC connections are not yet implemented".to_string(),
591 ))
592 }
593
594 pub async fn open_udp(&self, _peer_id: &str) -> Result<NetworkUdpSocket, NodeError> {
598 Err(NodeError::NotImplemented(
599 "UDP sockets are not yet implemented".to_string(),
600 ))
601 }
602}
603
604trait RwLockBlockingExt<T> {
612 fn blocking_lock_read(&self) -> tokio::sync::RwLockReadGuard<'_, T>;
613 fn blocking_lock_write(&self) -> tokio::sync::RwLockWriteGuard<'_, T>;
614}
615
616impl<T> RwLockBlockingExt<T> for RwLock<T> {
617 fn blocking_lock_read(&self) -> tokio::sync::RwLockReadGuard<'_, T> {
618 self.try_read().unwrap_or_else(|_| {
620 panic!("node: namespace_filters read lock contended in sync context")
623 })
624 }
625
626 fn blocking_lock_write(&self) -> tokio::sync::RwLockWriteGuard<'_, T> {
627 self.try_write().unwrap_or_else(|_| {
628 panic!("node: namespace_filters write lock contended in sync context")
629 })
630 }
631}
632
633#[derive(Debug, Clone)]
653pub struct NodeBuilder {
654 name: Option<String>,
655 sidecar_path: Option<PathBuf>,
656 state_dir: Option<String>,
657 auth_key: Option<String>,
658 ephemeral: bool,
659 ws_port: u16,
660}
661
662impl Default for NodeBuilder {
663 fn default() -> Self {
664 Self {
665 name: None,
666 sidecar_path: None,
667 state_dir: None,
668 auth_key: None,
669 ephemeral: false,
670 ws_port: 9417,
671 }
672 }
673}
674
675impl NodeBuilder {
676 pub fn name(mut self, name: &str) -> Self {
678 self.name = Some(name.to_string());
679 self
680 }
681
682 pub fn sidecar_path(mut self, path: impl Into<PathBuf>) -> Self {
684 self.sidecar_path = Some(path.into());
685 self
686 }
687
688 pub fn state_dir(mut self, dir: &str) -> Self {
690 self.state_dir = Some(dir.to_string());
691 self
692 }
693
694 pub fn auth_key(mut self, key: &str) -> Self {
696 self.auth_key = Some(key.to_string());
697 self
698 }
699
700 pub fn ephemeral(mut self, val: bool) -> Self {
702 self.ephemeral = val;
703 self
704 }
705
706 pub fn ws_port(mut self, port: u16) -> Self {
708 self.ws_port = port;
709 self
710 }
711
712 pub async fn build(self) -> Result<Node<TailscaleProvider>, NodeError> {
723 let binary_path = self
724 .sidecar_path
725 .ok_or_else(|| NodeError::BuildError("sidecar_path is required".into()))?;
726
727 let hostname = self
728 .name
729 .ok_or_else(|| NodeError::BuildError("name is required".into()))?;
730
731 let state_dir = self
732 .state_dir
733 .unwrap_or_else(|| format!("/tmp/truffle-{hostname}"));
734
735 let config = TailscaleConfig {
737 binary_path,
738 hostname,
739 state_dir,
740 auth_key: self.auth_key,
741 ephemeral: if self.ephemeral { Some(true) } else { None },
742 tags: None,
743 };
744
745 let mut provider = TailscaleProvider::new(config);
746 provider.start().await.map_err(NodeError::Network)?;
747
748 let network = Arc::new(provider);
749
750 let ws_config = WsConfig {
752 port: self.ws_port,
753 ..Default::default()
754 };
755 let ws_transport = Arc::new(WebSocketTransport::new(network.clone(), ws_config));
756
757 let session = Arc::new(PeerRegistry::new(network.clone(), ws_transport));
759 session.start().await;
760
761 let codec: Arc<dyn EnvelopeCodec> = Arc::new(JsonCodec);
763 let node = Node::from_parts(network, session, codec);
764
765 tracing::info!("node: started successfully");
766 Ok(node)
767 }
768
769 pub async fn build_with_auth_handler(
781 self,
782 on_auth: impl Fn(String) + Send + 'static,
783 ) -> Result<Node<TailscaleProvider>, NodeError> {
784 let binary_path = self
785 .sidecar_path
786 .ok_or_else(|| NodeError::BuildError("sidecar_path is required".into()))?;
787
788 let hostname = self
789 .name
790 .ok_or_else(|| NodeError::BuildError("name is required".into()))?;
791
792 let state_dir = self
793 .state_dir
794 .unwrap_or_else(|| format!("/tmp/truffle-{hostname}"));
795
796 let config = TailscaleConfig {
798 binary_path,
799 hostname,
800 state_dir,
801 auth_key: self.auth_key,
802 ephemeral: if self.ephemeral { Some(true) } else { None },
803 tags: None,
804 };
805
806 let mut provider = TailscaleProvider::new(config);
807
808 let mut auth_rx = provider.peer_events();
810
811 let auth_task = tokio::spawn(async move {
813 use crate::network::NetworkPeerEvent;
814 loop {
815 match auth_rx.recv().await {
816 Ok(NetworkPeerEvent::AuthRequired { url }) => {
817 on_auth(url);
818 }
819 Err(broadcast::error::RecvError::Closed) => break,
820 Err(broadcast::error::RecvError::Lagged(_)) => continue,
821 _ => {} }
823 }
824 });
825
826 let start_result = provider.start().await.map_err(NodeError::Network);
828
829 auth_task.abort();
831
832 start_result?;
833
834 let network = Arc::new(provider);
835
836 let ws_config = WsConfig {
838 port: self.ws_port,
839 ..Default::default()
840 };
841 let ws_transport = Arc::new(WebSocketTransport::new(network.clone(), ws_config));
842
843 let session = Arc::new(PeerRegistry::new(network.clone(), ws_transport));
845 session.start().await;
846
847 let codec: Arc<dyn EnvelopeCodec> = Arc::new(JsonCodec);
849 let node = Node::from_parts(network, session, codec);
850
851 tracing::info!("node: started successfully (with auth handler)");
852 Ok(node)
853 }
854}
855
856#[cfg(test)]
861mod tests {
862 use super::*;
863 use crate::network::{
864 HealthInfo, IncomingConnection, NetworkError, NetworkPeer, NetworkPeerEvent,
865 NetworkTcpListener, NetworkUdpSocket, PeerAddr,
866 };
867 use crate::transport::WsConfig;
868 use serde_json::json;
869 use std::time::Duration;
870 use tokio::sync::{broadcast, mpsc};
871
872 struct MockNetworkProvider {
875 identity: NodeIdentity,
876 local_addr: PeerAddr,
877 peer_event_tx: broadcast::Sender<NetworkPeerEvent>,
878 mock_peers: Arc<RwLock<Vec<NetworkPeer>>>,
880 }
881
882 impl MockNetworkProvider {
883 fn new(id: &str) -> Self {
884 let (peer_event_tx, _) = broadcast::channel(64);
885 Self {
886 identity: NodeIdentity {
887 id: id.to_string(),
888 hostname: format!("truffle-test-{id}"),
889 name: format!("Test Node {id}"),
890 dns_name: None,
891 ip: Some("127.0.0.1".parse().unwrap()),
892 },
893 local_addr: PeerAddr {
894 ip: Some("127.0.0.1".parse().unwrap()),
895 hostname: format!("truffle-test-{id}"),
896 dns_name: None,
897 },
898 peer_event_tx,
899 mock_peers: Arc::new(RwLock::new(Vec::new())),
900 }
901 }
902
903 fn event_sender(&self) -> broadcast::Sender<NetworkPeerEvent> {
904 self.peer_event_tx.clone()
905 }
906 }
907
908 impl NetworkProvider for MockNetworkProvider {
909 async fn start(&mut self) -> Result<(), NetworkError> {
910 Ok(())
911 }
912
913 async fn stop(&mut self) -> Result<(), NetworkError> {
914 Ok(())
915 }
916
917 fn local_identity(&self) -> NodeIdentity {
918 self.identity.clone()
919 }
920
921 fn local_addr(&self) -> PeerAddr {
922 self.local_addr.clone()
923 }
924
925 fn peer_events(&self) -> broadcast::Receiver<NetworkPeerEvent> {
926 self.peer_event_tx.subscribe()
927 }
928
929 async fn peers(&self) -> Vec<NetworkPeer> {
930 self.mock_peers.read().await.clone()
931 }
932
933 async fn dial_tcp(&self, addr: &str, port: u16) -> Result<TcpStream, NetworkError> {
934 let target = format!("{addr}:{port}");
935 TcpStream::connect(&target)
936 .await
937 .map_err(|e| NetworkError::DialFailed(format!("mock dial {target}: {e}")))
938 }
939
940 async fn listen_tcp(&self, port: u16) -> Result<NetworkTcpListener, NetworkError> {
941 let listener = tokio::net::TcpListener::bind(format!("127.0.0.1:{port}"))
942 .await
943 .map_err(|e| NetworkError::ListenFailed(format!("mock listen :{port}: {e}")))?;
944
945 let actual_port = listener.local_addr().unwrap().port();
946 let (tx, rx) = mpsc::channel::<IncomingConnection>(64);
947
948 tokio::spawn(async move {
949 loop {
950 match listener.accept().await {
951 Ok((stream, addr)) => {
952 let conn = IncomingConnection {
953 stream,
954 remote_addr: addr.to_string(),
955 remote_identity: String::new(),
956 port: actual_port,
957 };
958 if tx.send(conn).await.is_err() {
959 break;
960 }
961 }
962 Err(e) => {
963 tracing::debug!("mock listener error: {e}");
964 break;
965 }
966 }
967 }
968 });
969
970 Ok(NetworkTcpListener {
971 port: actual_port,
972 incoming: rx,
973 })
974 }
975
976 async fn unlisten_tcp(&self, _port: u16) -> Result<(), NetworkError> {
977 Ok(())
978 }
979
980 async fn bind_udp(&self, _port: u16) -> Result<NetworkUdpSocket, NetworkError> {
981 Err(NetworkError::Internal("mock: UDP not supported".into()))
982 }
983
984 async fn ping(&self, _addr: &str) -> Result<PingResult, NetworkError> {
985 Ok(PingResult {
986 latency: Duration::from_millis(1),
987 connection: "direct".to_string(),
988 peer_addr: None,
989 })
990 }
991
992 async fn health(&self) -> HealthInfo {
993 HealthInfo {
994 state: "running".to_string(),
995 healthy: true,
996 ..Default::default()
997 }
998 }
999 }
1000
1001 fn make_loopback_peer(id: &str) -> NetworkPeer {
1004 NetworkPeer {
1005 id: id.to_string(),
1006 hostname: format!("truffle-test-{id}"),
1007 ip: "127.0.0.1".parse().unwrap(),
1008 online: true,
1009 cur_addr: Some("127.0.0.1:41641".to_string()),
1010 relay: None,
1011 os: Some("linux".to_string()),
1012 last_seen: Some("2026-03-25T12:00:00Z".to_string()),
1013 key_expiry: None,
1014 dns_name: None,
1015 }
1016 }
1017
1018 fn ws_config(port: u16) -> WsConfig {
1019 WsConfig {
1020 port,
1021 ping_interval: Duration::from_secs(300),
1022 pong_timeout: Duration::from_secs(300),
1023 ..Default::default()
1024 }
1025 }
1026
1027 async fn random_port() -> u16 {
1028 let l = tokio::net::TcpListener::bind("127.0.0.1:0").await.unwrap();
1029 l.local_addr().unwrap().port()
1030 }
1031
1032 async fn make_test_node(
1034 id: &str,
1035 ws_port: u16,
1036 ) -> (
1037 Node<MockNetworkProvider>,
1038 broadcast::Sender<NetworkPeerEvent>,
1039 Arc<MockNetworkProvider>,
1040 ) {
1041 let provider = MockNetworkProvider::new(id);
1042 let event_tx = provider.event_sender();
1043 let network = Arc::new(provider);
1044 let ws_transport = Arc::new(WebSocketTransport::new(
1045 network.clone(),
1046 ws_config(ws_port),
1047 ));
1048 let session = Arc::new(PeerRegistry::new(network.clone(), ws_transport));
1049 session.start().await;
1050
1051 let codec: Arc<dyn EnvelopeCodec> = Arc::new(JsonCodec);
1052 let node = Node::from_parts(network.clone(), session, codec);
1053
1054 (node, event_tx, network)
1055 }
1056
1057 #[tokio::test]
1060 async fn test_node_builder_creates_node() {
1061 let ws_port = random_port().await;
1062 let (node, _event_tx, _network) = make_test_node("node-1", ws_port).await;
1063
1064 let identity = node.local_info();
1065 assert_eq!(identity.id, "node-1");
1066 assert!(identity.hostname.contains("node-1"));
1067 }
1068
1069 #[tokio::test]
1070 async fn test_node_peers_from_network() {
1071 let ws_port = random_port().await;
1072 let (node, event_tx, _network) = make_test_node("node-1", ws_port).await;
1073
1074 let peers = node.peers().await;
1076 assert!(peers.is_empty());
1077
1078 let peer = make_loopback_peer("peer-a");
1080 let _ = event_tx.send(NetworkPeerEvent::Joined(peer));
1081 tokio::time::sleep(Duration::from_millis(50)).await;
1082
1083 let peers = node.peers().await;
1084 assert_eq!(peers.len(), 1);
1085 assert_eq!(peers[0].id, "peer-a");
1086 assert!(peers[0].online);
1087 assert!(!peers[0].ws_connected);
1088 }
1089
1090 #[tokio::test]
1091 async fn test_node_send_to_unknown_peer_errors() {
1092 let ws_port = random_port().await;
1093 let (node, _event_tx, _network) = make_test_node("node-1", ws_port).await;
1094
1095 let result = node.send("nonexistent", "test", b"hello").await;
1096 assert!(result.is_err());
1097 let err_str = result.unwrap_err().to_string();
1098 assert!(
1099 err_str.contains("unknown peer") || err_str.contains("not found"),
1100 "expected unknown peer error, got: {err_str}"
1101 );
1102 }
1103
1104 #[tokio::test]
1105 async fn test_node_send_wraps_in_envelope() {
1106 let codec = JsonCodec;
1109 let data = b"hello world";
1110 let envelope = Envelope::new(
1111 "test-ns",
1112 "message",
1113 serde_json::Value::from(data.to_vec()),
1114 )
1115 .with_timestamp();
1116
1117 let encoded = codec.encode(&envelope).unwrap();
1118 let decoded = codec.decode(&encoded).unwrap();
1119
1120 assert_eq!(decoded.namespace, "test-ns");
1121 assert_eq!(decoded.msg_type, "message");
1122 assert!(decoded.timestamp.is_some());
1123 }
1124
1125 #[tokio::test]
1126 async fn test_node_subscribe_filters_by_namespace() {
1127 let ws_port = random_port().await;
1128 let (node, _event_tx, _network) = make_test_node("node-1", ws_port).await;
1129
1130 let _rx_chat = node.subscribe("chat");
1132 let _rx_ft = node.subscribe("ft");
1133
1134 let _rx_chat2 = node.subscribe("chat");
1136 }
1137
1138 #[tokio::test]
1139 async fn test_node_broadcast() {
1140 let ws_port = random_port().await;
1141 let (node, _event_tx, _network) = make_test_node("node-1", ws_port).await;
1142
1143 node.broadcast("test", b"hello everyone").await;
1145 }
1146
1147 #[tokio::test]
1148 async fn test_node_open_tcp_resolves_peer() {
1149 let ws_port = random_port().await;
1150 let (node, event_tx, _network) = make_test_node("node-1", ws_port).await;
1151
1152 let listener = tokio::net::TcpListener::bind("127.0.0.1:0").await.unwrap();
1154 let tcp_port = listener.local_addr().unwrap().port();
1155
1156 let peer = make_loopback_peer("peer-tcp");
1158 let _ = event_tx.send(NetworkPeerEvent::Joined(peer));
1159 tokio::time::sleep(Duration::from_millis(50)).await;
1160
1161 let accept_handle = tokio::spawn(async move {
1163 let (stream, _) = listener.accept().await.unwrap();
1164 stream
1165 });
1166
1167 let stream = node.open_tcp("peer-tcp", tcp_port).await;
1169 assert!(stream.is_ok(), "open_tcp failed: {:?}", stream.err());
1170
1171 let _ = accept_handle.await;
1172 }
1173
1174 #[tokio::test]
1175 async fn test_node_open_tcp_unknown_peer_errors() {
1176 let ws_port = random_port().await;
1177 let (node, _event_tx, _network) = make_test_node("node-1", ws_port).await;
1178
1179 let result = node.open_tcp("nonexistent", 8080).await;
1180 assert!(result.is_err());
1181 let err_str = result.unwrap_err().to_string();
1182 assert!(
1183 err_str.contains("not found"),
1184 "expected peer not found error, got: {err_str}"
1185 );
1186 }
1187
1188 #[tokio::test]
1189 async fn test_node_ping_resolves_peer() {
1190 let ws_port = random_port().await;
1191 let (node, event_tx, _network) = make_test_node("node-1", ws_port).await;
1192
1193 let result = node.ping("peer-ping").await;
1195 assert!(result.is_err());
1196
1197 let peer = make_loopback_peer("peer-ping");
1199 let _ = event_tx.send(NetworkPeerEvent::Joined(peer));
1200 tokio::time::sleep(Duration::from_millis(50)).await;
1201
1202 let result = node.ping("peer-ping").await;
1204 assert!(result.is_ok());
1205 assert_eq!(result.unwrap().latency, Duration::from_millis(1));
1206 }
1207
1208 #[tokio::test]
1209 async fn test_node_health() {
1210 let ws_port = random_port().await;
1211 let (node, _event_tx, _network) = make_test_node("node-1", ws_port).await;
1212
1213 let health = node.health().await;
1214 assert!(health.healthy);
1215 assert_eq!(health.state, "running");
1216 }
1217
1218 #[tokio::test]
1219 async fn test_node_open_quic_not_implemented() {
1220 let ws_port = random_port().await;
1221 let (node, _event_tx, _network) = make_test_node("node-1", ws_port).await;
1222
1223 let result = node.open_quic("peer").await;
1224 assert!(matches!(result, Err(NodeError::NotImplemented(_))));
1225 }
1226
1227 #[tokio::test]
1228 async fn test_node_open_udp_not_implemented() {
1229 let ws_port = random_port().await;
1230 let (node, _event_tx, _network) = make_test_node("node-1", ws_port).await;
1231
1232 let result = node.open_udp("peer").await;
1233 assert!(matches!(result, Err(NodeError::NotImplemented(_))));
1234 }
1235
1236 #[tokio::test]
1237 async fn test_node_listen_tcp() {
1238 let ws_port = random_port().await;
1239 let (node, _event_tx, _network) = make_test_node("node-1", ws_port).await;
1240
1241 let listener = node.listen_tcp(0).await;
1243 assert!(listener.is_ok(), "listen_tcp failed: {:?}", listener.err());
1244 }
1245
1246 #[tokio::test]
1247 async fn test_envelope_serialize_deserialize() {
1248 let envelope =
1249 Envelope::new("chat", "message", json!({"text": "hello"})).with_timestamp();
1250
1251 let bytes = envelope.serialize().unwrap();
1252 let decoded = Envelope::deserialize(&bytes).unwrap();
1253
1254 assert_eq!(decoded.namespace, "chat");
1255 assert_eq!(decoded.msg_type, "message");
1256 assert_eq!(decoded.payload["text"], "hello");
1257 assert!(decoded.timestamp.is_some());
1258 }
1259
1260 #[tokio::test]
1261 async fn test_envelope_codec_json() {
1262 let codec = JsonCodec;
1263 let envelope = Envelope::new("ft", "offer", json!({"file": "test.bin"}));
1264
1265 let encoded = codec.encode(&envelope).unwrap();
1266 let decoded = codec.decode(&encoded).unwrap();
1267
1268 assert_eq!(decoded.namespace, "ft");
1269 assert_eq!(decoded.payload["file"], "test.bin");
1270 }
1271
1272 #[tokio::test]
1273 async fn test_envelope_unknown_fields_ignored() {
1274 let json_bytes = br#"{
1275 "namespace": "v2",
1276 "msg_type": "new",
1277 "payload": {},
1278 "future_field": "ignored"
1279 }"#;
1280
1281 let codec = JsonCodec;
1282 let decoded = codec.decode(json_bytes).unwrap();
1283 assert_eq!(decoded.namespace, "v2");
1284 assert_eq!(decoded.msg_type, "new");
1285 }
1286
1287 #[tokio::test]
1288 async fn test_node_send_and_receive_roundtrip() {
1289 let port_a = random_port().await;
1291 let port_b = random_port().await;
1292
1293 let (node_a, event_tx_a, _net_a) = make_test_node("node-a", port_a).await;
1294 let (node_b, event_tx_b, _net_b) = make_test_node("node-b", port_b).await;
1295
1296 let peer_b = NetworkPeer {
1298 id: "node-b".to_string(),
1299 hostname: "truffle-test-node-b".to_string(),
1300 ip: "127.0.0.1".parse().unwrap(),
1301 online: true,
1302 cur_addr: Some("127.0.0.1:41641".to_string()),
1303 relay: None,
1304 os: None,
1305 last_seen: None,
1306 key_expiry: None,
1307 dns_name: None,
1308 };
1309 let peer_a = NetworkPeer {
1310 id: "node-a".to_string(),
1311 hostname: "truffle-test-node-a".to_string(),
1312 ip: "127.0.0.1".parse().unwrap(),
1313 online: true,
1314 cur_addr: Some("127.0.0.1:41641".to_string()),
1315 relay: None,
1316 os: None,
1317 last_seen: None,
1318 key_expiry: None,
1319 dns_name: None,
1320 };
1321
1322 let _ = event_tx_a.send(NetworkPeerEvent::Joined(peer_b));
1323 let _ = event_tx_b.send(NetworkPeerEvent::Joined(peer_a));
1324 tokio::time::sleep(Duration::from_millis(100)).await;
1325
1326 let mut rx = node_b.subscribe("test");
1328
1329 let send_result = node_a.send("node-b", "test", b"hello from a").await;
1332
1333 if send_result.is_ok() {
1341 let msg = tokio::time::timeout(Duration::from_secs(2), rx.recv()).await;
1343 if let Ok(Ok(msg)) = msg {
1344 assert_eq!(msg.namespace, "test");
1345 }
1346 }
1347 }
1350}