thenodes 0.4.0

TheNodes is a modular, plugin-driven P2P node framework for Rust, supporting node-embedded plugins (NEP) and core-as-a-library (CAL) modes with async-first APIs.
Documentation
use std::sync::Arc;
use std::time::Duration;

use thenodes::config::{Config, RelayConfig};
use thenodes::network::message::{
    decode_relay_opaque_payload_utf8, encode_relay_opaque_payload, Message, MessageType,
};
use thenodes::network::peer::Peer;
use thenodes::network::peer_manager::PeerManager;
use thenodes::network::peer_store::PeerStore;
use thenodes::network::transport::{connect_to_peer, ConnectToPeerParams};
use thenodes::plugin_host::manager::PluginManager;
use thenodes::realms::RealmInfo;

#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn relay_bind_and_forward_cross_a_real_two_node_connection() {
    let probe = std::net::TcpListener::bind("127.0.0.1:0").unwrap();
    let port = probe.local_addr().unwrap().port();
    drop(probe);

    let realm = RealmInfo::new("relay-smoke", "1.0");
    let mut relay_config = Config {
        port,
        realm: Some(realm.clone()),
        ..Default::default()
    };
    relay_config.network.as_mut().unwrap().relay = Some(RelayConfig {
        enabled: Some(true),
        store_forward: Some(true),
        queue_max_per_target: Some(16),
        queue_max_global: Some(32),
        selection: Some("none".to_string()),
    });

    let relay_manager = PeerManager::new();
    let relay_listener = {
        let relay_manager = relay_manager.clone();
        let relay_config = relay_config.clone();
        let realm = realm.clone();
        tokio::spawn(async move {
            if let Err(error) = thenodes::network::listener::start_listener(
                port,
                realm,
                relay_manager,
                Arc::new(PluginManager::new()),
                &relay_config,
                "relay-node".to_string(),
                PeerStore::new(),
                false,
            )
            .await
            {
                panic!("relay listener failed: {error}");
            }
        })
    };

    tokio::time::sleep(Duration::from_millis(100)).await;

    let client_manager = PeerManager::new();
    let peer = Peer::new("relay-node".to_string(), format!("127.0.0.1:{port}"));
    let client_connection = {
        let client_manager = client_manager.clone();
        let client_config = relay_config.clone();
        let realm = realm.clone();
        tokio::spawn(async move {
            connect_to_peer(ConnectToPeerParams {
                peer: &peer,
                our_realm: realm,
                our_port: 0,
                peer_manager: client_manager,
                plugin_manager: Arc::new(PluginManager::new()),
                allow_console: false,
                config: client_config,
                local_node_id: "client-node".to_string(),
                peer_store: Some(PeerStore::new()),
            })
            .await
        })
    };

    tokio::time::timeout(Duration::from_secs(5), async {
        while !client_manager.has_node_id("relay-node").await {
            tokio::time::sleep(Duration::from_millis(20)).await;
        }
    })
    .await
    .expect("client did not register relay node");

    let bind = Message::new(
        "client-node",
        "relay-node",
        MessageType::RelayBind {
            target: "offline-target".to_string(),
            want_store_forward: Some(true),
            qos: Some("reliable".to_string()),
            nonce: Some(7),
            expires_at: None,
        },
        None,
        Some(realm.clone()),
    );
    client_manager
        .send_to_node_id("relay-node", bind.as_json())
        .await
        .expect("send relay bind");

    tokio::time::timeout(Duration::from_secs(5), async {
        while !relay_manager
            .is_bound("client-node", "offline-target")
            .await
        {
            tokio::time::sleep(Duration::from_millis(20)).await;
        }
    })
    .await
    .expect("relay binding was not registered");

    let payload = "real two-node relay payload";
    let forward = Message::new(
        "client-node",
        "relay-node",
        MessageType::RelayForward {
            to: "offline-target".to_string(),
            from: "client-node".to_string(),
            sequence: Some(1),
            opaque_payload_b64: encode_relay_opaque_payload(payload),
        },
        None,
        Some(realm),
    );
    client_manager
        .send_to_node_id("relay-node", forward.as_json())
        .await
        .expect("send relay forward");

    tokio::time::timeout(Duration::from_secs(5), async {
        loop {
            let queue = relay_manager.test_get_queue_for("offline-target").await;
            if let Some((wire, ..)) = queue.first() {
                let queued = Message::from_json(wire).expect("queued relay frame");
                if let MessageType::RelayForward {
                    opaque_payload_b64, ..
                } = queued.msg_type
                {
                    assert_eq!(
                        decode_relay_opaque_payload_utf8(&opaque_payload_b64).as_deref(),
                        Some(payload)
                    );
                    assert!(queued.payload.is_none());
                    break;
                }
            }
            tokio::time::sleep(Duration::from_millis(20)).await;
        }
    })
    .await
    .expect("relay frame was not queued");

    client_connection.abort();
    relay_listener.abort();
}