prns-core 0.3.4

Pure Reticulum engine and wire contract for Personal Reticulum
mod ifac;
mod limits;

pub use ifac::{
    IfacContext, IfacSize, IfacSizeError, InterfaceIfac, DEFAULT_IFAC_SIZE, IFAC_MAX_SIZE,
};
pub use limits::{
    frame_cap_for, BROADCAST_WIRE_FRAME_LEN, EMBEDDED_MAX_LINK_MTU, EMBEDDED_MAX_WIRE_FRAME_LEN,
    MAX_WIRE_FRAME_LEN,
};

use crate::engine::InstantMillis;
use crate::interfaces::InterfaceId;

#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct RssiDbm(i16);

impl RssiDbm {
    #[must_use]
    pub const fn new(value: i16) -> Self {
        Self(value)
    }

    #[must_use]
    pub const fn get(self) -> i16 {
        self.0
    }
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct SnrQuarterDb(i16);

impl SnrQuarterDb {
    #[must_use]
    pub const fn new(quarters: i16) -> Self {
        Self(quarters)
    }

    #[must_use]
    pub const fn quarters(self) -> i16 {
        self.0
    }
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct SignalQualityTenthsPercent(u16);

impl SignalQualityTenthsPercent {
    pub const MAX: u16 = 1_000;

    #[must_use]
    pub const fn new(tenths_percent: u16) -> Option<Self> {
        if tenths_percent <= Self::MAX {
            Some(Self(tenths_percent))
        } else {
            None
        }
    }

    #[must_use]
    pub const fn tenths_percent(self) -> u16 {
        self.0
    }
}

#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
pub struct PacketPhyStats {
    pub rssi: Option<RssiDbm>,
    pub snr: Option<SnrQuarterDb>,
    pub quality: Option<SignalQualityTenthsPercent>,
}

impl PacketPhyStats {
    #[must_use]
    pub const fn is_empty(self) -> bool {
        self.rssi.is_none() && self.snr.is_none() && self.quality.is_none()
    }
}

#[derive(Debug, PartialEq, Eq)]
pub struct InboundPacket<'a> {
    pub arrived_at: InstantMillis,
    pub source_interface: InterfaceId,
    pub bytes: &'a mut [u8],
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct OutboundPacket<'a> {
    pub bytes: &'a [u8],
}

impl<'a> OutboundPacket<'a> {
    pub fn new(bytes: &'a [u8]) -> Self {
        Self { bytes }
    }
}

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

    #[test]
    fn signal_quality_accepts_the_closed_percentage_range() {
        assert_eq!(
            SignalQualityTenthsPercent::new(0).map(SignalQualityTenthsPercent::tenths_percent),
            Some(0)
        );
        assert_eq!(
            SignalQualityTenthsPercent::new(SignalQualityTenthsPercent::MAX)
                .map(SignalQualityTenthsPercent::tenths_percent),
            Some(SignalQualityTenthsPercent::MAX)
        );
        assert_eq!(
            SignalQualityTenthsPercent::new(SignalQualityTenthsPercent::MAX + 1),
            None
        );
    }

    #[test]
    fn packet_phy_is_empty_only_when_every_measurement_is_absent() {
        assert!(PacketPhyStats::default().is_empty());
        assert!(!PacketPhyStats {
            rssi: Some(RssiDbm::new(-90)),
            snr: None,
            quality: None,
        }
        .is_empty());
    }
}