personal-rns 0.3.4

Personal Reticulum: the pure engine, the high-level runtime, and every interface family behind one crate and one prelude
#![expect(clippy::expect_used, clippy::panic)]

use core::time::Duration;

use personal_rns::prelude::*;

const CHANGE_TIMEOUT: Duration = Duration::from_secs(5);

#[tokio::main]
async fn main() {
    let node = PrnsNode::new(PrnsNodeRecipe {
        transport_identity: None,
        pre_configured_destinations: [example_destination()],
        app_state: (),
        storage: GrowableHeap,
        request_endpoints: request_endpoints![],
        on_event: |_event, _state| {},
        interfaces: ManuallyAttached,
        persistence: NoPersistence,
    });
    let handle = node.handle();
    let server = TcpServer::bind("127.0.0.1:0")
        .await
        .expect("could not bind a localhost TCP server");
    let attached_interface = handle.supervise(server);
    let interface_id = attached_interface.id();

    let changes = async {
        loop {
            if handle
                .interfaces()
                .iter()
                .any(|interface| interface.id == interface_id)
            {
                break;
            }
            tokio::task::yield_now().await;
        }
        println!("Attached {interface_id:?}");
        attached_interface.teardown();
        loop {
            if handle
                .interfaces()
                .iter()
                .all(|interface| interface.id != interface_id)
            {
                break;
            }
            tokio::task::yield_now().await;
        }
        println!("Detached {interface_id:?}");
    };

    tokio::select! {
        result = tokio::time::timeout(CHANGE_TIMEOUT, changes) => {
            result.expect("The interface change did not complete within 5 seconds");
        }
        result = node.run() => {
            result.expect("The node failed");
            panic!("The node stopped before interface teardown");
        }
    }
}

fn example_destination() -> PreConfiguredDestination<'static> {
    PreConfiguredDestination::Single {
        resource_strategy: ResourceStrategy::AcceptNone,
        maximum_request_bytes: Default::default(),
        app_name: "prns-example",
        aspects: &["example", "dynamic-interface"],
        identity: try_generate_identity_secret().expect("identity generation failed"),
        announce_app_data: b"",
        proof: ProofStrategy::ProveAll,
        link_requests: LinkRequestPolicy::AcceptAll,
        ratchet: RatchetPolicy::NoRatchets,
        request_endpoints: ServeMyRequestEndpoints::No,
    }
}