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 fn synced_store_with_backend<T>(
348 self: &Arc<Self>,
349 store_id: &str,
350 backend: std::sync::Arc<dyn crate::synced_store::StoreBackend>,
351 ) -> Arc<crate::synced_store::SyncedStore<T>>
352 where
353 T: serde::Serialize
354 + serde::de::DeserializeOwned
355 + Clone
356 + Send
357 + Sync
358 + 'static,
359 {
360 crate::synced_store::SyncedStore::new_with_backend(self.clone(), store_id, backend)
361 }
362
363 pub async fn stop(&self) {
371 tracing::info!("node: stopping");
372 }
376
377 pub fn local_info(&self) -> NodeIdentity {
381 self.network.local_identity()
382 }
383
384 pub async fn peers(&self) -> Vec<Peer> {
391 self.session
392 .peers()
393 .await
394 .into_iter()
395 .map(Peer::from)
396 .collect()
397 }
398
399 pub fn on_peer_change(&self) -> broadcast::Receiver<PeerEvent> {
401 self.session.on_peer_change()
402 }
403
404 pub async fn resolve_peer_id(&self, peer_id: &str) -> Result<String, NodeError> {
410 let peers = self.session.peers().await;
411 peers
412 .iter()
413 .find(|p| p.id == peer_id || p.name == peer_id)
414 .map(|p| p.id.clone())
415 .ok_or_else(|| NodeError::PeerNotFound(peer_id.to_string()))
416 }
417
418 pub async fn ping(&self, peer_id: &str) -> Result<PingResult, NodeError> {
424 let peers = self.session.peers().await;
425 let peer = peers
426 .iter()
427 .find(|p| p.id == peer_id || p.name == peer_id)
428 .ok_or_else(|| NodeError::PeerNotFound(peer_id.to_string()))?;
429
430 let addr = peer.ip.to_string();
431 self.network
432 .ping(&addr)
433 .await
434 .map_err(NodeError::Network)
435 }
436
437 pub async fn health(&self) -> HealthInfo {
439 self.network.health().await
440 }
441
442 pub async fn send(
450 &self,
451 peer_id: &str,
452 namespace: &str,
453 data: &[u8],
454 ) -> Result<(), NodeError> {
455 let payload = std::str::from_utf8(data)
461 .ok()
462 .and_then(|s| serde_json::from_str::<serde_json::Value>(s).ok())
463 .unwrap_or_else(|| serde_json::Value::from(data.to_vec()));
464
465 let envelope = Envelope::new(
466 namespace,
467 "message",
468 payload,
469 )
470 .with_timestamp();
471
472 let encoded = self.codec.encode(&envelope)?;
473 self.session.send(peer_id, &encoded).await?;
474 Ok(())
475 }
476
477 pub async fn send_typed(
485 &self,
486 peer_id: &str,
487 namespace: &str,
488 msg_type: &str,
489 payload: &serde_json::Value,
490 ) -> Result<(), NodeError> {
491 let envelope = Envelope::new(namespace, msg_type, payload.clone()).with_timestamp();
492 let encoded = self.codec.encode(&envelope)?;
493 self.session.send(peer_id, &encoded).await?;
494 Ok(())
495 }
496
497 pub async fn broadcast_typed(
500 &self,
501 namespace: &str,
502 msg_type: &str,
503 payload: &serde_json::Value,
504 ) {
505 let envelope = Envelope::new(namespace, msg_type, payload.clone()).with_timestamp();
506 match self.codec.encode(&envelope) {
507 Ok(encoded) => {
508 self.session.broadcast(&encoded).await;
509 }
510 Err(e) => {
511 tracing::error!("node: failed to encode broadcast envelope: {e}");
512 }
513 }
514 }
515
516 pub async fn broadcast(&self, namespace: &str, data: &[u8]) {
521 let payload = std::str::from_utf8(data)
522 .ok()
523 .and_then(|s| serde_json::from_str::<serde_json::Value>(s).ok())
524 .unwrap_or_else(|| serde_json::Value::from(data.to_vec()));
525
526 let envelope = Envelope::new(
527 namespace,
528 "message",
529 payload,
530 )
531 .with_timestamp();
532
533 match self.codec.encode(&envelope) {
534 Ok(encoded) => {
535 self.session.broadcast(&encoded).await;
536 }
537 Err(e) => {
538 tracing::error!("node: failed to encode broadcast envelope: {e}");
539 }
540 }
541 }
542
543 pub fn subscribe(&self, namespace: &str) -> broadcast::Receiver<NamespacedMessage> {
549 {
551 let filters = self.namespace_filters.blocking_lock_read();
552 if let Some(tx) = filters.get(namespace) {
553 return tx.subscribe();
554 }
555 }
556
557 let mut filters = self.namespace_filters.blocking_lock_write();
559 if let Some(tx) = filters.get(namespace) {
561 return tx.subscribe();
562 }
563 let (tx, rx) = broadcast::channel(256);
564 filters.insert(namespace.to_string(), tx);
565 rx
566 }
567
568 pub async fn open_tcp(
576 &self,
577 peer_id: &str,
578 port: u16,
579 ) -> Result<TcpStream, NodeError> {
580 let peers = self.session.peers().await;
581 let peer = peers
582 .iter()
583 .find(|p| p.id == peer_id || p.name == peer_id)
584 .ok_or_else(|| NodeError::PeerNotFound(peer_id.to_string()))?;
585
586 let addr = peer.ip.to_string();
587 self.network
588 .dial_tcp(&addr, port)
589 .await
590 .map_err(|e| NodeError::ConnectionFailed(e.to_string()))
591 }
592
593 pub async fn listen_tcp(&self, port: u16) -> Result<RawListener, NodeError> {
598 use crate::transport::tcp::TcpTransport;
599 use crate::transport::RawTransport;
600
601 let tcp = TcpTransport::new(self.network.clone());
602 tcp.listen(port).await.map_err(NodeError::Transport)
603 }
604
605 pub async fn open_quic(&self, _peer_id: &str) -> Result<(), NodeError> {
609 Err(NodeError::NotImplemented(
610 "QUIC connections are not yet implemented".to_string(),
611 ))
612 }
613
614 pub async fn open_udp(&self, _peer_id: &str) -> Result<NetworkUdpSocket, NodeError> {
618 Err(NodeError::NotImplemented(
619 "UDP sockets are not yet implemented".to_string(),
620 ))
621 }
622}
623
624trait RwLockBlockingExt<T> {
632 fn blocking_lock_read(&self) -> tokio::sync::RwLockReadGuard<'_, T>;
633 fn blocking_lock_write(&self) -> tokio::sync::RwLockWriteGuard<'_, T>;
634}
635
636impl<T> RwLockBlockingExt<T> for RwLock<T> {
637 fn blocking_lock_read(&self) -> tokio::sync::RwLockReadGuard<'_, T> {
638 self.try_read().unwrap_or_else(|_| {
640 panic!("node: namespace_filters read lock contended in sync context")
643 })
644 }
645
646 fn blocking_lock_write(&self) -> tokio::sync::RwLockWriteGuard<'_, T> {
647 self.try_write().unwrap_or_else(|_| {
648 panic!("node: namespace_filters write lock contended in sync context")
649 })
650 }
651}
652
653#[derive(Debug, Clone)]
673pub struct NodeBuilder {
674 name: Option<String>,
675 sidecar_path: Option<PathBuf>,
676 state_dir: Option<String>,
677 auth_key: Option<String>,
678 ephemeral: bool,
679 ws_port: u16,
680}
681
682impl Default for NodeBuilder {
683 fn default() -> Self {
684 Self {
685 name: None,
686 sidecar_path: None,
687 state_dir: None,
688 auth_key: None,
689 ephemeral: false,
690 ws_port: 9417,
691 }
692 }
693}
694
695impl NodeBuilder {
696 pub fn name(mut self, name: &str) -> Self {
698 self.name = Some(name.to_string());
699 self
700 }
701
702 pub fn sidecar_path(mut self, path: impl Into<PathBuf>) -> Self {
704 self.sidecar_path = Some(path.into());
705 self
706 }
707
708 pub fn state_dir(mut self, dir: &str) -> Self {
710 self.state_dir = Some(dir.to_string());
711 self
712 }
713
714 pub fn auth_key(mut self, key: &str) -> Self {
716 self.auth_key = Some(key.to_string());
717 self
718 }
719
720 pub fn ephemeral(mut self, val: bool) -> Self {
722 self.ephemeral = val;
723 self
724 }
725
726 pub fn ws_port(mut self, port: u16) -> Self {
728 self.ws_port = port;
729 self
730 }
731
732 pub async fn build(self) -> Result<Node<TailscaleProvider>, NodeError> {
743 let binary_path = self
744 .sidecar_path
745 .ok_or_else(|| NodeError::BuildError("sidecar_path is required".into()))?;
746
747 let hostname = self
748 .name
749 .ok_or_else(|| NodeError::BuildError("name is required".into()))?;
750
751 let state_dir = self
752 .state_dir
753 .unwrap_or_else(|| format!("/tmp/truffle-{hostname}"));
754
755 let config = TailscaleConfig {
757 binary_path,
758 hostname,
759 state_dir,
760 auth_key: self.auth_key,
761 ephemeral: if self.ephemeral { Some(true) } else { None },
762 tags: None,
763 };
764
765 let mut provider = TailscaleProvider::new(config);
766 provider.start().await.map_err(NodeError::Network)?;
767
768 let network = Arc::new(provider);
769
770 let ws_config = WsConfig {
772 port: self.ws_port,
773 ..Default::default()
774 };
775 let ws_transport = Arc::new(WebSocketTransport::new(network.clone(), ws_config));
776
777 let session = Arc::new(PeerRegistry::new(network.clone(), ws_transport));
779 session.start().await;
780
781 let codec: Arc<dyn EnvelopeCodec> = Arc::new(JsonCodec);
783 let node = Node::from_parts(network, session, codec);
784
785 tracing::info!("node: started successfully");
786 Ok(node)
787 }
788
789 pub async fn build_with_auth_handler(
801 self,
802 on_auth: impl Fn(String) + Send + 'static,
803 ) -> Result<Node<TailscaleProvider>, NodeError> {
804 let binary_path = self
805 .sidecar_path
806 .ok_or_else(|| NodeError::BuildError("sidecar_path is required".into()))?;
807
808 let hostname = self
809 .name
810 .ok_or_else(|| NodeError::BuildError("name is required".into()))?;
811
812 let state_dir = self
813 .state_dir
814 .unwrap_or_else(|| format!("/tmp/truffle-{hostname}"));
815
816 let config = TailscaleConfig {
818 binary_path,
819 hostname,
820 state_dir,
821 auth_key: self.auth_key,
822 ephemeral: if self.ephemeral { Some(true) } else { None },
823 tags: None,
824 };
825
826 let mut provider = TailscaleProvider::new(config);
827
828 let mut auth_rx = provider.peer_events();
830
831 let auth_task = tokio::spawn(async move {
833 use crate::network::NetworkPeerEvent;
834 loop {
835 match auth_rx.recv().await {
836 Ok(NetworkPeerEvent::AuthRequired { url }) => {
837 on_auth(url);
838 }
839 Err(broadcast::error::RecvError::Closed) => break,
840 Err(broadcast::error::RecvError::Lagged(_)) => continue,
841 _ => {} }
843 }
844 });
845
846 let start_result = provider.start().await.map_err(NodeError::Network);
848
849 auth_task.abort();
851
852 start_result?;
853
854 let network = Arc::new(provider);
855
856 let ws_config = WsConfig {
858 port: self.ws_port,
859 ..Default::default()
860 };
861 let ws_transport = Arc::new(WebSocketTransport::new(network.clone(), ws_config));
862
863 let session = Arc::new(PeerRegistry::new(network.clone(), ws_transport));
865 session.start().await;
866
867 let codec: Arc<dyn EnvelopeCodec> = Arc::new(JsonCodec);
869 let node = Node::from_parts(network, session, codec);
870
871 tracing::info!("node: started successfully (with auth handler)");
872 Ok(node)
873 }
874}
875
876#[cfg(test)]
881mod tests {
882 use super::*;
883 use crate::network::{
884 HealthInfo, IncomingConnection, NetworkError, NetworkPeer, NetworkPeerEvent,
885 NetworkTcpListener, NetworkUdpSocket, PeerAddr,
886 };
887 use crate::transport::WsConfig;
888 use serde_json::json;
889 use std::time::Duration;
890 use tokio::sync::{broadcast, mpsc};
891
892 struct MockNetworkProvider {
895 identity: NodeIdentity,
896 local_addr: PeerAddr,
897 peer_event_tx: broadcast::Sender<NetworkPeerEvent>,
898 mock_peers: Arc<RwLock<Vec<NetworkPeer>>>,
900 }
901
902 impl MockNetworkProvider {
903 fn new(id: &str) -> Self {
904 let (peer_event_tx, _) = broadcast::channel(64);
905 Self {
906 identity: NodeIdentity {
907 id: id.to_string(),
908 hostname: format!("truffle-test-{id}"),
909 name: format!("Test Node {id}"),
910 dns_name: None,
911 ip: Some("127.0.0.1".parse().unwrap()),
912 },
913 local_addr: PeerAddr {
914 ip: Some("127.0.0.1".parse().unwrap()),
915 hostname: format!("truffle-test-{id}"),
916 dns_name: None,
917 },
918 peer_event_tx,
919 mock_peers: Arc::new(RwLock::new(Vec::new())),
920 }
921 }
922
923 fn event_sender(&self) -> broadcast::Sender<NetworkPeerEvent> {
924 self.peer_event_tx.clone()
925 }
926 }
927
928 impl NetworkProvider for MockNetworkProvider {
929 async fn start(&mut self) -> Result<(), NetworkError> {
930 Ok(())
931 }
932
933 async fn stop(&mut self) -> Result<(), NetworkError> {
934 Ok(())
935 }
936
937 fn local_identity(&self) -> NodeIdentity {
938 self.identity.clone()
939 }
940
941 fn local_addr(&self) -> PeerAddr {
942 self.local_addr.clone()
943 }
944
945 fn peer_events(&self) -> broadcast::Receiver<NetworkPeerEvent> {
946 self.peer_event_tx.subscribe()
947 }
948
949 async fn peers(&self) -> Vec<NetworkPeer> {
950 self.mock_peers.read().await.clone()
951 }
952
953 async fn dial_tcp(&self, addr: &str, port: u16) -> Result<TcpStream, NetworkError> {
954 let target = format!("{addr}:{port}");
955 TcpStream::connect(&target)
956 .await
957 .map_err(|e| NetworkError::DialFailed(format!("mock dial {target}: {e}")))
958 }
959
960 async fn listen_tcp(&self, port: u16) -> Result<NetworkTcpListener, NetworkError> {
961 let listener = tokio::net::TcpListener::bind(format!("127.0.0.1:{port}"))
962 .await
963 .map_err(|e| NetworkError::ListenFailed(format!("mock listen :{port}: {e}")))?;
964
965 let actual_port = listener.local_addr().unwrap().port();
966 let (tx, rx) = mpsc::channel::<IncomingConnection>(64);
967
968 tokio::spawn(async move {
969 loop {
970 match listener.accept().await {
971 Ok((stream, addr)) => {
972 let conn = IncomingConnection {
973 stream,
974 remote_addr: addr.to_string(),
975 remote_identity: String::new(),
976 port: actual_port,
977 };
978 if tx.send(conn).await.is_err() {
979 break;
980 }
981 }
982 Err(e) => {
983 tracing::debug!("mock listener error: {e}");
984 break;
985 }
986 }
987 }
988 });
989
990 Ok(NetworkTcpListener {
991 port: actual_port,
992 incoming: rx,
993 })
994 }
995
996 async fn unlisten_tcp(&self, _port: u16) -> Result<(), NetworkError> {
997 Ok(())
998 }
999
1000 async fn bind_udp(&self, _port: u16) -> Result<NetworkUdpSocket, NetworkError> {
1001 Err(NetworkError::Internal("mock: UDP not supported".into()))
1002 }
1003
1004 async fn ping(&self, _addr: &str) -> Result<PingResult, NetworkError> {
1005 Ok(PingResult {
1006 latency: Duration::from_millis(1),
1007 connection: "direct".to_string(),
1008 peer_addr: None,
1009 })
1010 }
1011
1012 async fn health(&self) -> HealthInfo {
1013 HealthInfo {
1014 state: "running".to_string(),
1015 healthy: true,
1016 ..Default::default()
1017 }
1018 }
1019 }
1020
1021 fn make_loopback_peer(id: &str) -> NetworkPeer {
1024 NetworkPeer {
1025 id: id.to_string(),
1026 hostname: format!("truffle-test-{id}"),
1027 ip: "127.0.0.1".parse().unwrap(),
1028 online: true,
1029 cur_addr: Some("127.0.0.1:41641".to_string()),
1030 relay: None,
1031 os: Some("linux".to_string()),
1032 last_seen: Some("2026-03-25T12:00:00Z".to_string()),
1033 key_expiry: None,
1034 dns_name: None,
1035 }
1036 }
1037
1038 fn ws_config(port: u16) -> WsConfig {
1039 WsConfig {
1040 port,
1041 ping_interval: Duration::from_secs(300),
1042 pong_timeout: Duration::from_secs(300),
1043 ..Default::default()
1044 }
1045 }
1046
1047 async fn random_port() -> u16 {
1048 let l = tokio::net::TcpListener::bind("127.0.0.1:0").await.unwrap();
1049 l.local_addr().unwrap().port()
1050 }
1051
1052 async fn make_test_node(
1054 id: &str,
1055 ws_port: u16,
1056 ) -> (
1057 Node<MockNetworkProvider>,
1058 broadcast::Sender<NetworkPeerEvent>,
1059 Arc<MockNetworkProvider>,
1060 ) {
1061 let provider = MockNetworkProvider::new(id);
1062 let event_tx = provider.event_sender();
1063 let network = Arc::new(provider);
1064 let ws_transport = Arc::new(WebSocketTransport::new(
1065 network.clone(),
1066 ws_config(ws_port),
1067 ));
1068 let session = Arc::new(PeerRegistry::new(network.clone(), ws_transport));
1069 session.start().await;
1070
1071 let codec: Arc<dyn EnvelopeCodec> = Arc::new(JsonCodec);
1072 let node = Node::from_parts(network.clone(), session, codec);
1073
1074 (node, event_tx, network)
1075 }
1076
1077 #[tokio::test]
1080 async fn test_node_builder_creates_node() {
1081 let ws_port = random_port().await;
1082 let (node, _event_tx, _network) = make_test_node("node-1", ws_port).await;
1083
1084 let identity = node.local_info();
1085 assert_eq!(identity.id, "node-1");
1086 assert!(identity.hostname.contains("node-1"));
1087 }
1088
1089 #[tokio::test]
1090 async fn test_node_peers_from_network() {
1091 let ws_port = random_port().await;
1092 let (node, event_tx, _network) = make_test_node("node-1", ws_port).await;
1093
1094 let peers = node.peers().await;
1096 assert!(peers.is_empty());
1097
1098 let peer = make_loopback_peer("peer-a");
1100 let _ = event_tx.send(NetworkPeerEvent::Joined(peer));
1101 tokio::time::sleep(Duration::from_millis(50)).await;
1102
1103 let peers = node.peers().await;
1104 assert_eq!(peers.len(), 1);
1105 assert_eq!(peers[0].id, "peer-a");
1106 assert!(peers[0].online);
1107 assert!(!peers[0].ws_connected);
1108 }
1109
1110 #[tokio::test]
1111 async fn test_node_send_to_unknown_peer_errors() {
1112 let ws_port = random_port().await;
1113 let (node, _event_tx, _network) = make_test_node("node-1", ws_port).await;
1114
1115 let result = node.send("nonexistent", "test", b"hello").await;
1116 assert!(result.is_err());
1117 let err_str = result.unwrap_err().to_string();
1118 assert!(
1119 err_str.contains("unknown peer") || err_str.contains("not found"),
1120 "expected unknown peer error, got: {err_str}"
1121 );
1122 }
1123
1124 #[tokio::test]
1125 async fn test_node_send_wraps_in_envelope() {
1126 let codec = JsonCodec;
1129 let data = b"hello world";
1130 let envelope = Envelope::new(
1131 "test-ns",
1132 "message",
1133 serde_json::Value::from(data.to_vec()),
1134 )
1135 .with_timestamp();
1136
1137 let encoded = codec.encode(&envelope).unwrap();
1138 let decoded = codec.decode(&encoded).unwrap();
1139
1140 assert_eq!(decoded.namespace, "test-ns");
1141 assert_eq!(decoded.msg_type, "message");
1142 assert!(decoded.timestamp.is_some());
1143 }
1144
1145 #[tokio::test]
1146 async fn test_node_subscribe_filters_by_namespace() {
1147 let ws_port = random_port().await;
1148 let (node, _event_tx, _network) = make_test_node("node-1", ws_port).await;
1149
1150 let _rx_chat = node.subscribe("chat");
1152 let _rx_ft = node.subscribe("ft");
1153
1154 let _rx_chat2 = node.subscribe("chat");
1156 }
1157
1158 #[tokio::test]
1159 async fn test_node_broadcast() {
1160 let ws_port = random_port().await;
1161 let (node, _event_tx, _network) = make_test_node("node-1", ws_port).await;
1162
1163 node.broadcast("test", b"hello everyone").await;
1165 }
1166
1167 #[tokio::test]
1168 async fn test_node_open_tcp_resolves_peer() {
1169 let ws_port = random_port().await;
1170 let (node, event_tx, _network) = make_test_node("node-1", ws_port).await;
1171
1172 let listener = tokio::net::TcpListener::bind("127.0.0.1:0").await.unwrap();
1174 let tcp_port = listener.local_addr().unwrap().port();
1175
1176 let peer = make_loopback_peer("peer-tcp");
1178 let _ = event_tx.send(NetworkPeerEvent::Joined(peer));
1179 tokio::time::sleep(Duration::from_millis(50)).await;
1180
1181 let accept_handle = tokio::spawn(async move {
1183 let (stream, _) = listener.accept().await.unwrap();
1184 stream
1185 });
1186
1187 let stream = node.open_tcp("peer-tcp", tcp_port).await;
1189 assert!(stream.is_ok(), "open_tcp failed: {:?}", stream.err());
1190
1191 let _ = accept_handle.await;
1192 }
1193
1194 #[tokio::test]
1195 async fn test_node_open_tcp_unknown_peer_errors() {
1196 let ws_port = random_port().await;
1197 let (node, _event_tx, _network) = make_test_node("node-1", ws_port).await;
1198
1199 let result = node.open_tcp("nonexistent", 8080).await;
1200 assert!(result.is_err());
1201 let err_str = result.unwrap_err().to_string();
1202 assert!(
1203 err_str.contains("not found"),
1204 "expected peer not found error, got: {err_str}"
1205 );
1206 }
1207
1208 #[tokio::test]
1209 async fn test_node_ping_resolves_peer() {
1210 let ws_port = random_port().await;
1211 let (node, event_tx, _network) = make_test_node("node-1", ws_port).await;
1212
1213 let result = node.ping("peer-ping").await;
1215 assert!(result.is_err());
1216
1217 let peer = make_loopback_peer("peer-ping");
1219 let _ = event_tx.send(NetworkPeerEvent::Joined(peer));
1220 tokio::time::sleep(Duration::from_millis(50)).await;
1221
1222 let result = node.ping("peer-ping").await;
1224 assert!(result.is_ok());
1225 assert_eq!(result.unwrap().latency, Duration::from_millis(1));
1226 }
1227
1228 #[tokio::test]
1229 async fn test_node_health() {
1230 let ws_port = random_port().await;
1231 let (node, _event_tx, _network) = make_test_node("node-1", ws_port).await;
1232
1233 let health = node.health().await;
1234 assert!(health.healthy);
1235 assert_eq!(health.state, "running");
1236 }
1237
1238 #[tokio::test]
1239 async fn test_node_open_quic_not_implemented() {
1240 let ws_port = random_port().await;
1241 let (node, _event_tx, _network) = make_test_node("node-1", ws_port).await;
1242
1243 let result = node.open_quic("peer").await;
1244 assert!(matches!(result, Err(NodeError::NotImplemented(_))));
1245 }
1246
1247 #[tokio::test]
1248 async fn test_node_open_udp_not_implemented() {
1249 let ws_port = random_port().await;
1250 let (node, _event_tx, _network) = make_test_node("node-1", ws_port).await;
1251
1252 let result = node.open_udp("peer").await;
1253 assert!(matches!(result, Err(NodeError::NotImplemented(_))));
1254 }
1255
1256 #[tokio::test]
1257 async fn test_node_listen_tcp() {
1258 let ws_port = random_port().await;
1259 let (node, _event_tx, _network) = make_test_node("node-1", ws_port).await;
1260
1261 let listener = node.listen_tcp(0).await;
1263 assert!(listener.is_ok(), "listen_tcp failed: {:?}", listener.err());
1264 }
1265
1266 #[tokio::test]
1267 async fn test_envelope_serialize_deserialize() {
1268 let envelope =
1269 Envelope::new("chat", "message", json!({"text": "hello"})).with_timestamp();
1270
1271 let bytes = envelope.serialize().unwrap();
1272 let decoded = Envelope::deserialize(&bytes).unwrap();
1273
1274 assert_eq!(decoded.namespace, "chat");
1275 assert_eq!(decoded.msg_type, "message");
1276 assert_eq!(decoded.payload["text"], "hello");
1277 assert!(decoded.timestamp.is_some());
1278 }
1279
1280 #[tokio::test]
1281 async fn test_envelope_codec_json() {
1282 let codec = JsonCodec;
1283 let envelope = Envelope::new("ft", "offer", json!({"file": "test.bin"}));
1284
1285 let encoded = codec.encode(&envelope).unwrap();
1286 let decoded = codec.decode(&encoded).unwrap();
1287
1288 assert_eq!(decoded.namespace, "ft");
1289 assert_eq!(decoded.payload["file"], "test.bin");
1290 }
1291
1292 #[tokio::test]
1293 async fn test_envelope_unknown_fields_ignored() {
1294 let json_bytes = br#"{
1295 "namespace": "v2",
1296 "msg_type": "new",
1297 "payload": {},
1298 "future_field": "ignored"
1299 }"#;
1300
1301 let codec = JsonCodec;
1302 let decoded = codec.decode(json_bytes).unwrap();
1303 assert_eq!(decoded.namespace, "v2");
1304 assert_eq!(decoded.msg_type, "new");
1305 }
1306
1307 #[tokio::test]
1308 async fn test_node_send_and_receive_roundtrip() {
1309 let port_a = random_port().await;
1311 let port_b = random_port().await;
1312
1313 let (node_a, event_tx_a, _net_a) = make_test_node("node-a", port_a).await;
1314 let (node_b, event_tx_b, _net_b) = make_test_node("node-b", port_b).await;
1315
1316 let peer_b = NetworkPeer {
1318 id: "node-b".to_string(),
1319 hostname: "truffle-test-node-b".to_string(),
1320 ip: "127.0.0.1".parse().unwrap(),
1321 online: true,
1322 cur_addr: Some("127.0.0.1:41641".to_string()),
1323 relay: None,
1324 os: None,
1325 last_seen: None,
1326 key_expiry: None,
1327 dns_name: None,
1328 };
1329 let peer_a = NetworkPeer {
1330 id: "node-a".to_string(),
1331 hostname: "truffle-test-node-a".to_string(),
1332 ip: "127.0.0.1".parse().unwrap(),
1333 online: true,
1334 cur_addr: Some("127.0.0.1:41641".to_string()),
1335 relay: None,
1336 os: None,
1337 last_seen: None,
1338 key_expiry: None,
1339 dns_name: None,
1340 };
1341
1342 let _ = event_tx_a.send(NetworkPeerEvent::Joined(peer_b));
1343 let _ = event_tx_b.send(NetworkPeerEvent::Joined(peer_a));
1344 tokio::time::sleep(Duration::from_millis(100)).await;
1345
1346 let mut rx = node_b.subscribe("test");
1348
1349 let send_result = node_a.send("node-b", "test", b"hello from a").await;
1352
1353 if send_result.is_ok() {
1361 let msg = tokio::time::timeout(Duration::from_secs(2), rx.recv()).await;
1363 if let Ok(Ok(msg)) = msg {
1364 assert_eq!(msg.namespace, "test");
1365 }
1366 }
1367 }
1370}