prns-runtime 0.3.4

Runtime-neutral Personal Reticulum node contracts and manifold kernel
Documentation
#[cfg(feature = "runtime-metrics")]
use crate::engine::AnnounceOrigin;
use crate::engine::{Directive, EngineReaction, FanTarget, Journaled};
use crate::interfaces::{InterfaceId, InterfaceKind};

pub struct AnnounceDirective<'a> {
    bytes: &'a [u8],
    hops: u8,
    #[cfg(feature = "runtime-metrics")]
    origin: AnnounceOrigin,
}

impl<'a> AnnounceDirective<'a> {
    #[must_use]
    pub fn bytes(&self) -> &'a [u8] {
        self.bytes
    }

    #[must_use]
    pub fn hops(&self) -> u8 {
        self.hops
    }

    #[cfg(feature = "runtime-metrics")]
    #[must_use]
    pub fn origin(&self) -> AnnounceOrigin {
        self.origin
    }
}

pub trait DirectiveEgress {
    fn send(&mut self, target: InterfaceId, bytes: &[u8]);

    fn send_if_online(&mut self, target: InterfaceId, bytes: &[u8], on_send: &mut dyn FnMut()) {
        on_send();
        self.send(target, bytes);
    }

    fn send_announce(&mut self, target: InterfaceId, announce: AnnounceDirective<'_>);

    fn send_to_fleet(&mut self, supervisor: InterfaceKind, fan: FanTarget, bytes: &[u8]);

    fn send_announce_to_fleet(
        &mut self,
        supervisor: InterfaceKind,
        fan: FanTarget,
        announce: AnnounceDirective<'_>,
    );

    fn emit_frame(
        &mut self,
        target: InterfaceId,
        size_hint: usize,
        fill: &mut dyn FnMut(&mut [u8]) -> Option<usize>,
    );

    fn send_measured_local_announce(&mut self, target: InterfaceId, bytes: &[u8]) {
        self.send(target, bytes);
    }

    fn send_measured_local_announce_to_fleet(
        &mut self,
        supervisor: InterfaceKind,
        fan: FanTarget,
        bytes: &[u8],
    ) {
        self.send_to_fleet(supervisor, fan, bytes);
    }
}

pub fn route_reaction(
    reaction: EngineReaction<'_>,
    egress: &mut impl DirectiveEgress,
    app: &mut impl FnMut(Journaled<'_>),
) {
    match reaction {
        EngineReaction::Directive(Directive::Send { target, bytes }) => {
            egress.send(target, bytes);
        }
        EngineReaction::Directive(Directive::SendIfOnline {
            target,
            bytes,
            on_send,
        }) => {
            egress.send_if_online(target, bytes, on_send);
        }
        EngineReaction::Directive(Directive::SendAnnounce {
            target,
            bytes,
            hops,
            #[cfg(feature = "runtime-metrics")]
            origin,
        }) => {
            egress.send_announce(
                target,
                AnnounceDirective {
                    bytes,
                    hops,
                    #[cfg(feature = "runtime-metrics")]
                    origin,
                },
            );
        }
        EngineReaction::Directive(Directive::SendToFleet {
            supervisor,
            fan,
            bytes,
        }) => {
            egress.send_to_fleet(supervisor, fan, bytes);
        }
        EngineReaction::Directive(Directive::SendAnnounceToFleet {
            supervisor,
            fan,
            bytes,
            hops,
            #[cfg(feature = "runtime-metrics")]
            origin,
        }) => {
            egress.send_announce_to_fleet(
                supervisor,
                fan,
                AnnounceDirective {
                    bytes,
                    hops,
                    #[cfg(feature = "runtime-metrics")]
                    origin,
                },
            );
        }
        EngineReaction::Directive(Directive::EmitFrame {
            target,
            size_hint,
            fill,
        }) => {
            egress.emit_frame(target, size_hint, fill);
        }
        #[cfg(feature = "runtime-metrics")]
        EngineReaction::Directive(Directive::SendMeasuredLocalAnnounce { target, bytes }) => {
            egress.send_measured_local_announce(target, bytes);
        }
        #[cfg(feature = "runtime-metrics")]
        EngineReaction::Directive(Directive::SendMeasuredLocalAnnounceToFleet {
            supervisor,
            fan,
            bytes,
        }) => {
            egress.send_measured_local_announce_to_fleet(supervisor, fan, bytes);
        }
        EngineReaction::Journaled(journaled) => app(journaled),
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    struct CountingEgress {
        sends: usize,
    }

    impl DirectiveEgress for CountingEgress {
        fn send(&mut self, _target: InterfaceId, _bytes: &[u8]) {
            self.sends += 1;
        }

        fn send_announce(&mut self, _target: InterfaceId, _announce: AnnounceDirective<'_>) {}

        fn send_to_fleet(&mut self, _supervisor: InterfaceKind, _fan: FanTarget, _bytes: &[u8]) {}

        fn send_announce_to_fleet(
            &mut self,
            _supervisor: InterfaceKind,
            _fan: FanTarget,
            _announce: AnnounceDirective<'_>,
        ) {
        }

        fn emit_frame(
            &mut self,
            _target: InterfaceId,
            _size_hint: usize,
            _fill: &mut dyn FnMut(&mut [u8]) -> Option<usize>,
        ) {
        }
    }

    #[test]
    fn an_online_only_send_records_after_egress_accepts_it() {
        let target = InterfaceId::new([0x6c; 8]);
        let mut records = 0;
        let mut on_send = || records += 1;
        let mut egress = CountingEgress { sends: 0 };

        route_reaction(
            EngineReaction::Directive(Directive::SendIfOnline {
                target,
                bytes: b"path request",
                on_send: &mut on_send,
            }),
            &mut egress,
            &mut |_| {},
        );

        assert_eq!((records, egress.sends), (1, 1));
    }
}