prns-core 0.3.4

Pure Reticulum engine and wire contract for Personal Reticulum
use core::convert::TryFrom;

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Capabilities {
    pub receives: bool,
    pub transmits: bool,
    pub forwards: bool,
    pub repeats: bool,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct InterfaceCapabilities {
    pub ingress: IngressCapability,
    pub egress: EgressCapability,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum IngressCapability {
    Disabled,
    Enabled,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum EgressCapability {
    Disabled,
    Enabled(TransportCapability),
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum TransportCapability {
    NoTransport,
    CrossInterfaceOnly,
    SameInterfaceRepeat,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum InterfaceCapabilitiesError {
    TransportRequiresTransmit,
    SameInterfaceRepeatRequiresTransport,
}

impl InterfaceCapabilities {
    pub const fn allows_transmit(self) -> bool {
        !matches!(self.egress, EgressCapability::Disabled)
    }

    pub const fn allows_transport(self) -> bool {
        matches!(
            self.egress,
            EgressCapability::Enabled(
                TransportCapability::CrossInterfaceOnly | TransportCapability::SameInterfaceRepeat
            )
        )
    }

    pub const fn allows_same_interface_repeat(self) -> bool {
        matches!(
            self.egress,
            EgressCapability::Enabled(TransportCapability::SameInterfaceRepeat)
        )
    }
}

impl TryFrom<Capabilities> for InterfaceCapabilities {
    type Error = InterfaceCapabilitiesError;

    fn try_from(capabilities: Capabilities) -> Result<Self, Self::Error> {
        let ingress = if capabilities.receives {
            IngressCapability::Enabled
        } else {
            IngressCapability::Disabled
        };

        let egress = match (
            capabilities.transmits,
            capabilities.forwards,
            capabilities.repeats,
        ) {
            (false, false, false) => EgressCapability::Disabled,
            (true, false, false) => EgressCapability::Enabled(TransportCapability::NoTransport),
            (true, true, false) => {
                EgressCapability::Enabled(TransportCapability::CrossInterfaceOnly)
            }
            (true, true, true) => {
                EgressCapability::Enabled(TransportCapability::SameInterfaceRepeat)
            }
            (false, true, _) => return Err(InterfaceCapabilitiesError::TransportRequiresTransmit),
            (false, false, true) => {
                return Err(InterfaceCapabilitiesError::SameInterfaceRepeatRequiresTransport);
            }
            (true, false, true) => {
                return Err(InterfaceCapabilitiesError::SameInterfaceRepeatRequiresTransport);
            }
        };

        Ok(Self { ingress, egress })
    }
}

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

    #[test]
    fn normalizes_non_transport_transmit_shape() {
        let normalized = InterfaceCapabilities::try_from(Capabilities {
            receives: true,
            transmits: true,
            forwards: false,
            repeats: false,
        })
        .unwrap();

        assert_eq!(normalized.ingress, IngressCapability::Enabled);
        assert_eq!(
            normalized.egress,
            EgressCapability::Enabled(TransportCapability::NoTransport)
        );
    }

    #[test]
    fn normalizes_cross_interface_transport_shape() {
        let normalized = InterfaceCapabilities::try_from(Capabilities {
            receives: true,
            transmits: true,
            forwards: true,
            repeats: false,
        })
        .unwrap();

        assert_eq!(
            normalized.egress,
            EgressCapability::Enabled(TransportCapability::CrossInterfaceOnly)
        );
    }

    #[test]
    fn normalizes_same_interface_repeat_shape() {
        let normalized = InterfaceCapabilities::try_from(Capabilities {
            receives: true,
            transmits: true,
            forwards: true,
            repeats: true,
        })
        .unwrap();

        assert_eq!(
            normalized.egress,
            EgressCapability::Enabled(TransportCapability::SameInterfaceRepeat)
        );
    }

    #[test]
    fn predicates_reflect_the_normalized_egress_shape() {
        let disabled = InterfaceCapabilities::try_from(Capabilities {
            receives: false,
            transmits: false,
            forwards: false,
            repeats: false,
        })
        .unwrap();
        assert!(!disabled.allows_transmit());
        assert!(!disabled.allows_transport());
        assert!(!disabled.allows_same_interface_repeat());

        let transmit_only = InterfaceCapabilities::try_from(Capabilities {
            receives: true,
            transmits: true,
            forwards: false,
            repeats: false,
        })
        .unwrap();
        assert!(transmit_only.allows_transmit());
        assert!(!transmit_only.allows_transport());
        assert!(!transmit_only.allows_same_interface_repeat());

        let cross_interface = InterfaceCapabilities::try_from(Capabilities {
            receives: true,
            transmits: true,
            forwards: true,
            repeats: false,
        })
        .unwrap();
        assert!(cross_interface.allows_transport());
        assert!(!cross_interface.allows_same_interface_repeat());

        let same_interface = InterfaceCapabilities::try_from(Capabilities {
            receives: true,
            transmits: true,
            forwards: true,
            repeats: true,
        })
        .unwrap();
        assert!(same_interface.allows_transport());
        assert!(same_interface.allows_same_interface_repeat());
    }

    #[test]
    fn rejects_transport_without_transmit() {
        assert_eq!(
            InterfaceCapabilities::try_from(Capabilities {
                receives: true,
                transmits: false,
                forwards: true,
                repeats: false,
            }),
            Err(InterfaceCapabilitiesError::TransportRequiresTransmit)
        );
    }

    #[test]
    fn rejects_same_interface_repeat_without_transport() {
        assert_eq!(
            InterfaceCapabilities::try_from(Capabilities {
                receives: true,
                transmits: true,
                forwards: false,
                repeats: true,
            }),
            Err(InterfaceCapabilitiesError::SameInterfaceRepeatRequiresTransport)
        );
    }
}