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 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 connected: s.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 async fn stop(&self) {
328 tracing::info!("node: stopping");
329 }
333
334 pub fn local_info(&self) -> NodeIdentity {
338 self.network.local_identity()
339 }
340
341 pub async fn peers(&self) -> Vec<Peer> {
348 self.session
349 .peers()
350 .await
351 .into_iter()
352 .map(Peer::from)
353 .collect()
354 }
355
356 pub fn on_peer_change(&self) -> broadcast::Receiver<PeerEvent> {
358 self.session.on_peer_change()
359 }
360
361 pub async fn resolve_peer_id(&self, peer_id: &str) -> Result<String, NodeError> {
367 let peers = self.session.peers().await;
368 peers
369 .iter()
370 .find(|p| p.id == peer_id || p.name == peer_id)
371 .map(|p| p.id.clone())
372 .ok_or_else(|| NodeError::PeerNotFound(peer_id.to_string()))
373 }
374
375 pub async fn ping(&self, peer_id: &str) -> Result<PingResult, NodeError> {
381 let peers = self.session.peers().await;
382 let peer = peers
383 .iter()
384 .find(|p| p.id == peer_id || p.name == peer_id)
385 .ok_or_else(|| NodeError::PeerNotFound(peer_id.to_string()))?;
386
387 let addr = peer.ip.to_string();
388 self.network
389 .ping(&addr)
390 .await
391 .map_err(NodeError::Network)
392 }
393
394 pub async fn health(&self) -> HealthInfo {
396 self.network.health().await
397 }
398
399 pub async fn send(
407 &self,
408 peer_id: &str,
409 namespace: &str,
410 data: &[u8],
411 ) -> Result<(), NodeError> {
412 let payload = std::str::from_utf8(data)
418 .ok()
419 .and_then(|s| serde_json::from_str::<serde_json::Value>(s).ok())
420 .unwrap_or_else(|| serde_json::Value::from(data.to_vec()));
421
422 let envelope = Envelope::new(
423 namespace,
424 "message",
425 payload,
426 )
427 .with_timestamp();
428
429 let encoded = self.codec.encode(&envelope)?;
430 self.session.send(peer_id, &encoded).await?;
431 Ok(())
432 }
433
434 pub async fn broadcast(&self, namespace: &str, data: &[u8]) {
439 let payload = std::str::from_utf8(data)
440 .ok()
441 .and_then(|s| serde_json::from_str::<serde_json::Value>(s).ok())
442 .unwrap_or_else(|| serde_json::Value::from(data.to_vec()));
443
444 let envelope = Envelope::new(
445 namespace,
446 "message",
447 payload,
448 )
449 .with_timestamp();
450
451 match self.codec.encode(&envelope) {
452 Ok(encoded) => {
453 self.session.broadcast(&encoded).await;
454 }
455 Err(e) => {
456 tracing::error!("node: failed to encode broadcast envelope: {e}");
457 }
458 }
459 }
460
461 pub fn subscribe(&self, namespace: &str) -> broadcast::Receiver<NamespacedMessage> {
467 {
469 let filters = self.namespace_filters.blocking_lock_read();
470 if let Some(tx) = filters.get(namespace) {
471 return tx.subscribe();
472 }
473 }
474
475 let mut filters = self.namespace_filters.blocking_lock_write();
477 if let Some(tx) = filters.get(namespace) {
479 return tx.subscribe();
480 }
481 let (tx, rx) = broadcast::channel(256);
482 filters.insert(namespace.to_string(), tx);
483 rx
484 }
485
486 pub async fn open_tcp(
494 &self,
495 peer_id: &str,
496 port: u16,
497 ) -> Result<TcpStream, NodeError> {
498 let peers = self.session.peers().await;
499 let peer = peers
500 .iter()
501 .find(|p| p.id == peer_id || p.name == peer_id)
502 .ok_or_else(|| NodeError::PeerNotFound(peer_id.to_string()))?;
503
504 let addr = peer.ip.to_string();
505 self.network
506 .dial_tcp(&addr, port)
507 .await
508 .map_err(|e| NodeError::ConnectionFailed(e.to_string()))
509 }
510
511 pub async fn listen_tcp(&self, port: u16) -> Result<RawListener, NodeError> {
516 use crate::transport::tcp::TcpTransport;
517 use crate::transport::RawTransport;
518
519 let tcp = TcpTransport::new(self.network.clone());
520 tcp.listen(port).await.map_err(NodeError::Transport)
521 }
522
523 pub async fn open_quic(&self, _peer_id: &str) -> Result<(), NodeError> {
527 Err(NodeError::NotImplemented(
528 "QUIC connections are not yet implemented".to_string(),
529 ))
530 }
531
532 pub async fn open_udp(&self, _peer_id: &str) -> Result<NetworkUdpSocket, NodeError> {
536 Err(NodeError::NotImplemented(
537 "UDP sockets are not yet implemented".to_string(),
538 ))
539 }
540}
541
542trait RwLockBlockingExt<T> {
550 fn blocking_lock_read(&self) -> tokio::sync::RwLockReadGuard<'_, T>;
551 fn blocking_lock_write(&self) -> tokio::sync::RwLockWriteGuard<'_, T>;
552}
553
554impl<T> RwLockBlockingExt<T> for RwLock<T> {
555 fn blocking_lock_read(&self) -> tokio::sync::RwLockReadGuard<'_, T> {
556 self.try_read().unwrap_or_else(|_| {
558 panic!("node: namespace_filters read lock contended in sync context")
561 })
562 }
563
564 fn blocking_lock_write(&self) -> tokio::sync::RwLockWriteGuard<'_, T> {
565 self.try_write().unwrap_or_else(|_| {
566 panic!("node: namespace_filters write lock contended in sync context")
567 })
568 }
569}
570
571#[derive(Debug, Clone)]
591pub struct NodeBuilder {
592 name: Option<String>,
593 sidecar_path: Option<PathBuf>,
594 state_dir: Option<String>,
595 auth_key: Option<String>,
596 ephemeral: bool,
597 ws_port: u16,
598}
599
600impl Default for NodeBuilder {
601 fn default() -> Self {
602 Self {
603 name: None,
604 sidecar_path: None,
605 state_dir: None,
606 auth_key: None,
607 ephemeral: false,
608 ws_port: 9417,
609 }
610 }
611}
612
613impl NodeBuilder {
614 pub fn name(mut self, name: &str) -> Self {
616 self.name = Some(name.to_string());
617 self
618 }
619
620 pub fn sidecar_path(mut self, path: impl Into<PathBuf>) -> Self {
622 self.sidecar_path = Some(path.into());
623 self
624 }
625
626 pub fn state_dir(mut self, dir: &str) -> Self {
628 self.state_dir = Some(dir.to_string());
629 self
630 }
631
632 pub fn auth_key(mut self, key: &str) -> Self {
634 self.auth_key = Some(key.to_string());
635 self
636 }
637
638 pub fn ephemeral(mut self, val: bool) -> Self {
640 self.ephemeral = val;
641 self
642 }
643
644 pub fn ws_port(mut self, port: u16) -> Self {
646 self.ws_port = port;
647 self
648 }
649
650 pub async fn build(self) -> Result<Node<TailscaleProvider>, NodeError> {
661 let binary_path = self
662 .sidecar_path
663 .ok_or_else(|| NodeError::BuildError("sidecar_path is required".into()))?;
664
665 let hostname = self
666 .name
667 .ok_or_else(|| NodeError::BuildError("name is required".into()))?;
668
669 let state_dir = self
670 .state_dir
671 .unwrap_or_else(|| format!("/tmp/truffle-{hostname}"));
672
673 let config = TailscaleConfig {
675 binary_path,
676 hostname,
677 state_dir,
678 auth_key: self.auth_key,
679 ephemeral: if self.ephemeral { Some(true) } else { None },
680 tags: None,
681 };
682
683 let mut provider = TailscaleProvider::new(config);
684 provider.start().await.map_err(NodeError::Network)?;
685
686 let network = Arc::new(provider);
687
688 let ws_config = WsConfig {
690 port: self.ws_port,
691 ..Default::default()
692 };
693 let ws_transport = Arc::new(WebSocketTransport::new(network.clone(), ws_config));
694
695 let session = Arc::new(PeerRegistry::new(network.clone(), ws_transport));
697 session.start().await;
698
699 let codec: Arc<dyn EnvelopeCodec> = Arc::new(JsonCodec);
701 let node = Node::from_parts(network, session, codec);
702
703 tracing::info!("node: started successfully");
704 Ok(node)
705 }
706
707 pub async fn build_with_auth_handler(
719 self,
720 on_auth: impl Fn(String) + Send + 'static,
721 ) -> Result<Node<TailscaleProvider>, NodeError> {
722 let binary_path = self
723 .sidecar_path
724 .ok_or_else(|| NodeError::BuildError("sidecar_path is required".into()))?;
725
726 let hostname = self
727 .name
728 .ok_or_else(|| NodeError::BuildError("name is required".into()))?;
729
730 let state_dir = self
731 .state_dir
732 .unwrap_or_else(|| format!("/tmp/truffle-{hostname}"));
733
734 let config = TailscaleConfig {
736 binary_path,
737 hostname,
738 state_dir,
739 auth_key: self.auth_key,
740 ephemeral: if self.ephemeral { Some(true) } else { None },
741 tags: None,
742 };
743
744 let mut provider = TailscaleProvider::new(config);
745
746 let mut auth_rx = provider.peer_events();
748
749 let auth_task = tokio::spawn(async move {
751 use crate::network::NetworkPeerEvent;
752 loop {
753 match auth_rx.recv().await {
754 Ok(NetworkPeerEvent::AuthRequired { url }) => {
755 on_auth(url);
756 }
757 Err(broadcast::error::RecvError::Closed) => break,
758 Err(broadcast::error::RecvError::Lagged(_)) => continue,
759 _ => {} }
761 }
762 });
763
764 let start_result = provider.start().await.map_err(NodeError::Network);
766
767 auth_task.abort();
769
770 start_result?;
771
772 let network = Arc::new(provider);
773
774 let ws_config = WsConfig {
776 port: self.ws_port,
777 ..Default::default()
778 };
779 let ws_transport = Arc::new(WebSocketTransport::new(network.clone(), ws_config));
780
781 let session = Arc::new(PeerRegistry::new(network.clone(), ws_transport));
783 session.start().await;
784
785 let codec: Arc<dyn EnvelopeCodec> = Arc::new(JsonCodec);
787 let node = Node::from_parts(network, session, codec);
788
789 tracing::info!("node: started successfully (with auth handler)");
790 Ok(node)
791 }
792}
793
794#[cfg(test)]
799mod tests {
800 use super::*;
801 use crate::network::{
802 HealthInfo, IncomingConnection, NetworkError, NetworkPeer, NetworkPeerEvent,
803 NetworkTcpListener, NetworkUdpSocket, PeerAddr,
804 };
805 use crate::transport::WsConfig;
806 use serde_json::json;
807 use std::time::Duration;
808 use tokio::sync::{broadcast, mpsc};
809
810 struct MockNetworkProvider {
813 identity: NodeIdentity,
814 local_addr: PeerAddr,
815 peer_event_tx: broadcast::Sender<NetworkPeerEvent>,
816 mock_peers: Arc<RwLock<Vec<NetworkPeer>>>,
818 }
819
820 impl MockNetworkProvider {
821 fn new(id: &str) -> Self {
822 let (peer_event_tx, _) = broadcast::channel(64);
823 Self {
824 identity: NodeIdentity {
825 id: id.to_string(),
826 hostname: format!("truffle-test-{id}"),
827 name: format!("Test Node {id}"),
828 dns_name: None,
829 ip: Some("127.0.0.1".parse().unwrap()),
830 },
831 local_addr: PeerAddr {
832 ip: Some("127.0.0.1".parse().unwrap()),
833 hostname: format!("truffle-test-{id}"),
834 dns_name: None,
835 },
836 peer_event_tx,
837 mock_peers: Arc::new(RwLock::new(Vec::new())),
838 }
839 }
840
841 fn event_sender(&self) -> broadcast::Sender<NetworkPeerEvent> {
842 self.peer_event_tx.clone()
843 }
844 }
845
846 impl NetworkProvider for MockNetworkProvider {
847 async fn start(&mut self) -> Result<(), NetworkError> {
848 Ok(())
849 }
850
851 async fn stop(&mut self) -> Result<(), NetworkError> {
852 Ok(())
853 }
854
855 fn local_identity(&self) -> NodeIdentity {
856 self.identity.clone()
857 }
858
859 fn local_addr(&self) -> PeerAddr {
860 self.local_addr.clone()
861 }
862
863 fn peer_events(&self) -> broadcast::Receiver<NetworkPeerEvent> {
864 self.peer_event_tx.subscribe()
865 }
866
867 async fn peers(&self) -> Vec<NetworkPeer> {
868 self.mock_peers.read().await.clone()
869 }
870
871 async fn dial_tcp(&self, addr: &str, port: u16) -> Result<TcpStream, NetworkError> {
872 let target = format!("{addr}:{port}");
873 TcpStream::connect(&target)
874 .await
875 .map_err(|e| NetworkError::DialFailed(format!("mock dial {target}: {e}")))
876 }
877
878 async fn listen_tcp(&self, port: u16) -> Result<NetworkTcpListener, NetworkError> {
879 let listener = tokio::net::TcpListener::bind(format!("127.0.0.1:{port}"))
880 .await
881 .map_err(|e| NetworkError::ListenFailed(format!("mock listen :{port}: {e}")))?;
882
883 let actual_port = listener.local_addr().unwrap().port();
884 let (tx, rx) = mpsc::channel::<IncomingConnection>(64);
885
886 tokio::spawn(async move {
887 loop {
888 match listener.accept().await {
889 Ok((stream, addr)) => {
890 let conn = IncomingConnection {
891 stream,
892 remote_addr: addr.to_string(),
893 remote_identity: String::new(),
894 port: actual_port,
895 };
896 if tx.send(conn).await.is_err() {
897 break;
898 }
899 }
900 Err(e) => {
901 tracing::debug!("mock listener error: {e}");
902 break;
903 }
904 }
905 }
906 });
907
908 Ok(NetworkTcpListener {
909 port: actual_port,
910 incoming: rx,
911 })
912 }
913
914 async fn unlisten_tcp(&self, _port: u16) -> Result<(), NetworkError> {
915 Ok(())
916 }
917
918 async fn bind_udp(&self, _port: u16) -> Result<NetworkUdpSocket, NetworkError> {
919 Err(NetworkError::Internal("mock: UDP not supported".into()))
920 }
921
922 async fn ping(&self, _addr: &str) -> Result<PingResult, NetworkError> {
923 Ok(PingResult {
924 latency: Duration::from_millis(1),
925 connection: "direct".to_string(),
926 peer_addr: None,
927 })
928 }
929
930 async fn health(&self) -> HealthInfo {
931 HealthInfo {
932 state: "running".to_string(),
933 healthy: true,
934 ..Default::default()
935 }
936 }
937 }
938
939 fn make_loopback_peer(id: &str) -> NetworkPeer {
942 NetworkPeer {
943 id: id.to_string(),
944 hostname: format!("truffle-test-{id}"),
945 ip: "127.0.0.1".parse().unwrap(),
946 online: true,
947 cur_addr: Some("127.0.0.1:41641".to_string()),
948 relay: None,
949 os: Some("linux".to_string()),
950 last_seen: Some("2026-03-25T12:00:00Z".to_string()),
951 key_expiry: None,
952 dns_name: None,
953 }
954 }
955
956 fn ws_config(port: u16) -> WsConfig {
957 WsConfig {
958 port,
959 ping_interval: Duration::from_secs(300),
960 pong_timeout: Duration::from_secs(300),
961 ..Default::default()
962 }
963 }
964
965 async fn random_port() -> u16 {
966 let l = tokio::net::TcpListener::bind("127.0.0.1:0").await.unwrap();
967 l.local_addr().unwrap().port()
968 }
969
970 async fn make_test_node(
972 id: &str,
973 ws_port: u16,
974 ) -> (
975 Node<MockNetworkProvider>,
976 broadcast::Sender<NetworkPeerEvent>,
977 Arc<MockNetworkProvider>,
978 ) {
979 let provider = MockNetworkProvider::new(id);
980 let event_tx = provider.event_sender();
981 let network = Arc::new(provider);
982 let ws_transport = Arc::new(WebSocketTransport::new(
983 network.clone(),
984 ws_config(ws_port),
985 ));
986 let session = Arc::new(PeerRegistry::new(network.clone(), ws_transport));
987 session.start().await;
988
989 let codec: Arc<dyn EnvelopeCodec> = Arc::new(JsonCodec);
990 let node = Node::from_parts(network.clone(), session, codec);
991
992 (node, event_tx, network)
993 }
994
995 #[tokio::test]
998 async fn test_node_builder_creates_node() {
999 let ws_port = random_port().await;
1000 let (node, _event_tx, _network) = make_test_node("node-1", ws_port).await;
1001
1002 let identity = node.local_info();
1003 assert_eq!(identity.id, "node-1");
1004 assert!(identity.hostname.contains("node-1"));
1005 }
1006
1007 #[tokio::test]
1008 async fn test_node_peers_from_network() {
1009 let ws_port = random_port().await;
1010 let (node, event_tx, _network) = make_test_node("node-1", ws_port).await;
1011
1012 let peers = node.peers().await;
1014 assert!(peers.is_empty());
1015
1016 let peer = make_loopback_peer("peer-a");
1018 let _ = event_tx.send(NetworkPeerEvent::Joined(peer));
1019 tokio::time::sleep(Duration::from_millis(50)).await;
1020
1021 let peers = node.peers().await;
1022 assert_eq!(peers.len(), 1);
1023 assert_eq!(peers[0].id, "peer-a");
1024 assert!(peers[0].online);
1025 assert!(!peers[0].connected);
1026 }
1027
1028 #[tokio::test]
1029 async fn test_node_send_to_unknown_peer_errors() {
1030 let ws_port = random_port().await;
1031 let (node, _event_tx, _network) = make_test_node("node-1", ws_port).await;
1032
1033 let result = node.send("nonexistent", "test", b"hello").await;
1034 assert!(result.is_err());
1035 let err_str = result.unwrap_err().to_string();
1036 assert!(
1037 err_str.contains("unknown peer") || err_str.contains("not found"),
1038 "expected unknown peer error, got: {err_str}"
1039 );
1040 }
1041
1042 #[tokio::test]
1043 async fn test_node_send_wraps_in_envelope() {
1044 let codec = JsonCodec;
1047 let data = b"hello world";
1048 let envelope = Envelope::new(
1049 "test-ns",
1050 "message",
1051 serde_json::Value::from(data.to_vec()),
1052 )
1053 .with_timestamp();
1054
1055 let encoded = codec.encode(&envelope).unwrap();
1056 let decoded = codec.decode(&encoded).unwrap();
1057
1058 assert_eq!(decoded.namespace, "test-ns");
1059 assert_eq!(decoded.msg_type, "message");
1060 assert!(decoded.timestamp.is_some());
1061 }
1062
1063 #[tokio::test]
1064 async fn test_node_subscribe_filters_by_namespace() {
1065 let ws_port = random_port().await;
1066 let (node, _event_tx, _network) = make_test_node("node-1", ws_port).await;
1067
1068 let _rx_chat = node.subscribe("chat");
1070 let _rx_ft = node.subscribe("ft");
1071
1072 let _rx_chat2 = node.subscribe("chat");
1074 }
1075
1076 #[tokio::test]
1077 async fn test_node_broadcast() {
1078 let ws_port = random_port().await;
1079 let (node, _event_tx, _network) = make_test_node("node-1", ws_port).await;
1080
1081 node.broadcast("test", b"hello everyone").await;
1083 }
1084
1085 #[tokio::test]
1086 async fn test_node_open_tcp_resolves_peer() {
1087 let ws_port = random_port().await;
1088 let (node, event_tx, _network) = make_test_node("node-1", ws_port).await;
1089
1090 let listener = tokio::net::TcpListener::bind("127.0.0.1:0").await.unwrap();
1092 let tcp_port = listener.local_addr().unwrap().port();
1093
1094 let peer = make_loopback_peer("peer-tcp");
1096 let _ = event_tx.send(NetworkPeerEvent::Joined(peer));
1097 tokio::time::sleep(Duration::from_millis(50)).await;
1098
1099 let accept_handle = tokio::spawn(async move {
1101 let (stream, _) = listener.accept().await.unwrap();
1102 stream
1103 });
1104
1105 let stream = node.open_tcp("peer-tcp", tcp_port).await;
1107 assert!(stream.is_ok(), "open_tcp failed: {:?}", stream.err());
1108
1109 let _ = accept_handle.await;
1110 }
1111
1112 #[tokio::test]
1113 async fn test_node_open_tcp_unknown_peer_errors() {
1114 let ws_port = random_port().await;
1115 let (node, _event_tx, _network) = make_test_node("node-1", ws_port).await;
1116
1117 let result = node.open_tcp("nonexistent", 8080).await;
1118 assert!(result.is_err());
1119 let err_str = result.unwrap_err().to_string();
1120 assert!(
1121 err_str.contains("not found"),
1122 "expected peer not found error, got: {err_str}"
1123 );
1124 }
1125
1126 #[tokio::test]
1127 async fn test_node_ping_resolves_peer() {
1128 let ws_port = random_port().await;
1129 let (node, event_tx, _network) = make_test_node("node-1", ws_port).await;
1130
1131 let result = node.ping("peer-ping").await;
1133 assert!(result.is_err());
1134
1135 let peer = make_loopback_peer("peer-ping");
1137 let _ = event_tx.send(NetworkPeerEvent::Joined(peer));
1138 tokio::time::sleep(Duration::from_millis(50)).await;
1139
1140 let result = node.ping("peer-ping").await;
1142 assert!(result.is_ok());
1143 assert_eq!(result.unwrap().latency, Duration::from_millis(1));
1144 }
1145
1146 #[tokio::test]
1147 async fn test_node_health() {
1148 let ws_port = random_port().await;
1149 let (node, _event_tx, _network) = make_test_node("node-1", ws_port).await;
1150
1151 let health = node.health().await;
1152 assert!(health.healthy);
1153 assert_eq!(health.state, "running");
1154 }
1155
1156 #[tokio::test]
1157 async fn test_node_open_quic_not_implemented() {
1158 let ws_port = random_port().await;
1159 let (node, _event_tx, _network) = make_test_node("node-1", ws_port).await;
1160
1161 let result = node.open_quic("peer").await;
1162 assert!(matches!(result, Err(NodeError::NotImplemented(_))));
1163 }
1164
1165 #[tokio::test]
1166 async fn test_node_open_udp_not_implemented() {
1167 let ws_port = random_port().await;
1168 let (node, _event_tx, _network) = make_test_node("node-1", ws_port).await;
1169
1170 let result = node.open_udp("peer").await;
1171 assert!(matches!(result, Err(NodeError::NotImplemented(_))));
1172 }
1173
1174 #[tokio::test]
1175 async fn test_node_listen_tcp() {
1176 let ws_port = random_port().await;
1177 let (node, _event_tx, _network) = make_test_node("node-1", ws_port).await;
1178
1179 let listener = node.listen_tcp(0).await;
1181 assert!(listener.is_ok(), "listen_tcp failed: {:?}", listener.err());
1182 }
1183
1184 #[tokio::test]
1185 async fn test_envelope_serialize_deserialize() {
1186 let envelope =
1187 Envelope::new("chat", "message", json!({"text": "hello"})).with_timestamp();
1188
1189 let bytes = envelope.serialize().unwrap();
1190 let decoded = Envelope::deserialize(&bytes).unwrap();
1191
1192 assert_eq!(decoded.namespace, "chat");
1193 assert_eq!(decoded.msg_type, "message");
1194 assert_eq!(decoded.payload["text"], "hello");
1195 assert!(decoded.timestamp.is_some());
1196 }
1197
1198 #[tokio::test]
1199 async fn test_envelope_codec_json() {
1200 let codec = JsonCodec;
1201 let envelope = Envelope::new("ft", "offer", json!({"file": "test.bin"}));
1202
1203 let encoded = codec.encode(&envelope).unwrap();
1204 let decoded = codec.decode(&encoded).unwrap();
1205
1206 assert_eq!(decoded.namespace, "ft");
1207 assert_eq!(decoded.payload["file"], "test.bin");
1208 }
1209
1210 #[tokio::test]
1211 async fn test_envelope_unknown_fields_ignored() {
1212 let json_bytes = br#"{
1213 "namespace": "v2",
1214 "msg_type": "new",
1215 "payload": {},
1216 "future_field": "ignored"
1217 }"#;
1218
1219 let codec = JsonCodec;
1220 let decoded = codec.decode(json_bytes).unwrap();
1221 assert_eq!(decoded.namespace, "v2");
1222 assert_eq!(decoded.msg_type, "new");
1223 }
1224
1225 #[tokio::test]
1226 async fn test_node_send_and_receive_roundtrip() {
1227 let port_a = random_port().await;
1229 let port_b = random_port().await;
1230
1231 let (node_a, event_tx_a, _net_a) = make_test_node("node-a", port_a).await;
1232 let (node_b, event_tx_b, _net_b) = make_test_node("node-b", port_b).await;
1233
1234 let peer_b = NetworkPeer {
1236 id: "node-b".to_string(),
1237 hostname: "truffle-test-node-b".to_string(),
1238 ip: "127.0.0.1".parse().unwrap(),
1239 online: true,
1240 cur_addr: Some("127.0.0.1:41641".to_string()),
1241 relay: None,
1242 os: None,
1243 last_seen: None,
1244 key_expiry: None,
1245 dns_name: None,
1246 };
1247 let peer_a = NetworkPeer {
1248 id: "node-a".to_string(),
1249 hostname: "truffle-test-node-a".to_string(),
1250 ip: "127.0.0.1".parse().unwrap(),
1251 online: true,
1252 cur_addr: Some("127.0.0.1:41641".to_string()),
1253 relay: None,
1254 os: None,
1255 last_seen: None,
1256 key_expiry: None,
1257 dns_name: None,
1258 };
1259
1260 let _ = event_tx_a.send(NetworkPeerEvent::Joined(peer_b));
1261 let _ = event_tx_b.send(NetworkPeerEvent::Joined(peer_a));
1262 tokio::time::sleep(Duration::from_millis(100)).await;
1263
1264 let mut rx = node_b.subscribe("test");
1266
1267 let send_result = node_a.send("node-b", "test", b"hello from a").await;
1270
1271 if send_result.is_ok() {
1279 let msg = tokio::time::timeout(Duration::from_secs(2), rx.recv()).await;
1281 if let Ok(Ok(msg)) = msg {
1282 assert_eq!(msg.namespace, "test");
1283 }
1284 }
1285 }
1288}