prns-core 0.3.4

Pure Reticulum engine and wire contract for Personal Reticulum
use crate::engine::test_support::{routable_descriptor, TestStorageLayout};
use crate::engine::{CommandId, EngineState, IngestIo};
use crate::interfaces::{AttachedInterfaces, InboundPacket, InterfaceId};
use crate::routing::links::maintenance::{write_keepalive, KEEPALIVE_ECHO};
use crate::routing::links::table::{InitiatedLink, LinkActivation, LinkPhase};
use crate::routing::links::{LinkId, LinkKey};
use crate::units::{InstantMillis, RttMillis};
use crate::wire::{DestinationHash, BROADCAST_MTU};

fn engine_with_active_link() -> (EngineState<TestStorageLayout>, LinkId, InterfaceId) {
    use crate::crypto::{
        x25519_diffie_hellman, Ed25519PublicKey, Ed25519SecretKey, X25519PublicKey, X25519SecretKey,
    };
    let link_id = LinkId::new([0x0F; 16]);
    let lane = InterfaceId::new([0xEE; 8]);
    let shared = x25519_diffie_hellman(
        &X25519SecretKey::new([0x33; 32]),
        &X25519PublicKey([0x55; 32]),
    );
    let mut engine = EngineState::<TestStorageLayout>::default();
    engine
        .links
        .track_initiated(InitiatedLink {
            link_id,
            destination: DestinationHash::new([0x77; 16]),
            expected_hops: 1,
            mode: crate::routing::links::LinkMode::Aes256Cbc,
            initiator_secret: X25519SecretKey::new([0x33; 32]),
            link_signing: Ed25519SecretKey::new([0x33; 32]),
            requested_at: InstantMillis(500),
            timeout_at: InstantMillis(5_000),
            command_id: CommandId(1),
        })
        .unwrap();
    engine
        .links
        .activate_initiated(
            &link_id,
            LinkKey::derive(&link_id, &shared),
            &LinkActivation {
                received_hops: 1,
                rtt: RttMillis::new(250),
                mtu: BROADCAST_MTU,
                attached_interface: lane,
                peer_signing: Ed25519PublicKey([0x99; 32]),
            },
            InstantMillis(1_000),
        )
        .unwrap();
    (engine, link_id, lane)
}

#[test]
fn a_keepalive_echo_records_inbound_without_postponing_the_silent_outbound_arm() {
    let (mut engine, link_id, lane) = engine_with_active_link();
    let before = engine.link_deadlines_wake();

    let mut frame = [0u8; BROADCAST_MTU];
    let written = write_keepalive(&link_id, KEEPALIVE_ECHO, &mut frame).unwrap();
    let wake = engine.ingest_packet_into(
        InboundPacket {
            arrived_at: InstantMillis(2_000),
            source_interface: lane,
            bytes: &mut frame[..written],
        },
        IngestIo {
            interfaces: AttachedInterfaces::new(&[routable_descriptor(lane)]),
            now: InstantMillis(2_000),
            fill_entropy: &mut |bytes: &mut [u8]| bytes.fill(0xC7),
            should_prove: &mut |_| false,
            should_accept_resource: &mut |_: &crate::routing::links::resources::ResourceOffer| {
                false
            },
            sink: &mut |_| {},
        },
    );

    let truth = engine.link_deadlines_wake();
    assert_eq!(
        before, truth,
        "fresh inbound cannot mask the already-earlier outbound-silence wake",
    );
    let Some(LinkPhase::Active {
        last_inbound,
        last_outbound,
        ..
    }) = engine.links.phase_for(&link_id)
    else {
        panic!("the keepalive echo must leave the link active");
    };
    assert_eq!(*last_inbound, InstantMillis(2_000));
    assert_eq!(*last_outbound, InstantMillis(1_000));
    assert_eq!(
        wake.link_deadlines, truth,
        "the ingest delta must still report the complete recomputed schedule",
    );
}