prns-core 0.3.4

Pure Reticulum engine and wire contract for Personal Reticulum
#[cfg(feature = "runtime-metrics")]
use crate::engine::AnnounceOrigin;
use crate::engine::{Directive, EngineReaction, FanTarget};
use crate::interfaces::{AttachedInterfaces, InterfaceKind, InterfaceMode};

pub(in crate::engine) fn fan_frame(
    interfaces: AttachedInterfaces<'_>,
    fanout: FanTarget,
    bytes: &[u8],
    sink: &mut impl FnMut(EngineReaction<'_>),
) {
    fan(interfaces, fanout, bytes, EmissionKind::Frame, sink);
}

pub(in crate::engine) fn fan_announce(
    interfaces: AttachedInterfaces<'_>,
    fanout: FanTarget,
    bytes: &[u8],
    sink: &mut impl FnMut(EngineReaction<'_>),
) {
    fan(interfaces, fanout, bytes, EmissionKind::Announce, sink);
}

#[derive(Clone, Copy)]
enum EmissionKind {
    Frame,
    Announce,
}

fn fan(
    interfaces: AttachedInterfaces<'_>,
    fanout: FanTarget,
    bytes: &[u8],
    emission: EmissionKind,
    sink: &mut impl FnMut(EngineReaction<'_>),
) {
    let mut fleets_emitted: u128 = 0;
    for descriptor in interfaces {
        if !descriptor.capabilities.allows_transmit() {
            continue;
        }
        let targeted = match fanout {
            FanTarget::All => true,
            FanTarget::Only(id) => descriptor.id == id,
            FanTarget::AllExcept(id) => descriptor.id != id,
        };
        if !targeted {
            continue;
        }
        match descriptor
            .id
            .kind()
            .and_then(InterfaceKind::supervisor_kind)
        {
            Some(supervisor) => {
                debug_assert!(
                    (supervisor as u8) < 128,
                    "InterfaceKind discriminants must stay below 128 to index the fleet seen-bitmask",
                );
                let bit = 1u128 << (supervisor as u8);
                if fleets_emitted & bit == 0 {
                    fleets_emitted |= bit;
                    match emission {
                        EmissionKind::Frame => {
                            sink(EngineReaction::Directive(Directive::SendToFleet {
                                supervisor,
                                fan: fanout,
                                bytes,
                            }));
                        }
                        EmissionKind::Announce => {
                            sink(EngineReaction::Directive(Directive::SendAnnounceToFleet {
                                supervisor,
                                fan: fanout,
                                bytes,
                                hops: 0,
                                #[cfg(feature = "runtime-metrics")]
                                origin: AnnounceOrigin::Local,
                            }))
                        }
                    }
                }
            }
            None => {
                if matches!(emission, EmissionKind::Announce)
                    && descriptor.mode == InterfaceMode::AccessPoint
                {
                    continue;
                }
                match emission {
                    EmissionKind::Frame => sink(EngineReaction::Directive(Directive::Send {
                        target: descriptor.id,
                        bytes,
                    })),
                    EmissionKind::Announce => {
                        sink(EngineReaction::Directive(Directive::SendAnnounce {
                            target: descriptor.id,
                            bytes,
                            hops: 0,
                            #[cfg(feature = "runtime-metrics")]
                            origin: AnnounceOrigin::Local,
                        }))
                    }
                }
            }
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::engine::test_support::routable_descriptor;
    use crate::interfaces::{InterfaceDescriptor, InterfaceId};

    fn iface(byte: u8) -> InterfaceId {
        InterfaceId::new([byte; 8])
    }

    #[test]
    fn local_announces_use_every_mode_except_access_point() {
        let interfaces = [
            InterfaceDescriptor {
                mode: InterfaceMode::Full,
                ..routable_descriptor(iface(0xE1))
            },
            InterfaceDescriptor {
                mode: InterfaceMode::PointToPoint,
                ..routable_descriptor(iface(0xE2))
            },
            InterfaceDescriptor {
                mode: InterfaceMode::AccessPoint,
                ..routable_descriptor(iface(0xE3))
            },
            InterfaceDescriptor {
                mode: InterfaceMode::Roaming,
                ..routable_descriptor(iface(0xE4))
            },
            InterfaceDescriptor {
                mode: InterfaceMode::Boundary,
                ..routable_descriptor(iface(0xE5))
            },
            InterfaceDescriptor {
                mode: InterfaceMode::Gateway,
                ..routable_descriptor(iface(0xE6))
            },
            InterfaceDescriptor {
                mode: InterfaceMode::Internal,
                ..routable_descriptor(iface(0xE7))
            },
        ];

        let mut targets = std::vec::Vec::new();
        fan_announce(
            AttachedInterfaces::new(&interfaces),
            FanTarget::All,
            &[0xAB],
            &mut |reaction| {
                if let EngineReaction::Directive(Directive::SendAnnounce {
                    target, hops: 0, ..
                }) = reaction
                {
                    targets.push(target);
                }
            },
        );

        assert_eq!(
            targets,
            std::vec![
                iface(0xE1),
                iface(0xE2),
                iface(0xE4),
                iface(0xE5),
                iface(0xE6),
                iface(0xE7),
            ],
        );
    }

    #[test]
    fn local_announces_use_one_announce_directive_for_a_supervised_fleet() {
        let first = InterfaceId::new([InterfaceKind::BluetoothPeer as u8, 0x41, 0, 0, 0, 0, 0, 0]);
        let second = InterfaceId::new([InterfaceKind::BluetoothPeer as u8, 0x42, 0, 0, 0, 0, 0, 0]);
        let interfaces = [routable_descriptor(first), routable_descriptor(second)];
        let mut fleets = std::vec::Vec::new();

        fan_announce(
            AttachedInterfaces::new(&interfaces),
            FanTarget::All,
            &[0xAB],
            &mut |reaction| {
                if let EngineReaction::Directive(Directive::SendAnnounceToFleet {
                    supervisor,
                    fan,
                    hops: 0,
                    ..
                }) = reaction
                {
                    fleets.push((supervisor, fan));
                }
            },
        );

        assert_eq!(
            fleets,
            std::vec![(InterfaceKind::BluetoothAuto, FanTarget::All)]
        );
    }

    #[test]
    fn frames_emit_once_for_a_supervised_interface_fleet() {
        let first = InterfaceId::new([InterfaceKind::BluetoothPeer as u8, 0x41, 0, 0, 0, 0, 0, 0]);
        let second = InterfaceId::new([InterfaceKind::BluetoothPeer as u8, 0x42, 0, 0, 0, 0, 0, 0]);
        let interfaces = [routable_descriptor(first), routable_descriptor(second)];
        let mut fleets = std::vec::Vec::new();

        fan_frame(
            AttachedInterfaces::new(&interfaces),
            FanTarget::All,
            &[0xAB],
            &mut |reaction| {
                if let EngineReaction::Directive(Directive::SendToFleet { supervisor, .. }) =
                    reaction
                {
                    fleets.push(supervisor);
                }
            },
        );

        assert_eq!(fleets, std::vec![InterfaceKind::BluetoothAuto]);
    }
}