prns-core 0.3.4

Pure Reticulum engine and wire contract for Personal Reticulum
//! An id is `kind ++ hash(channel_tag)`: this byte names *what kind of wire* the interface is, the channel tag names *which* one. The kind namespaces the hash (two kinds never collide even if their channel-tag hashes did) and makes an id self-describing. Supervisors and the fleet members they stand up are distinct kinds. The discriminant is written into every id (and, once routes persist, onto disk), so it is a stable wire-like contract: never renumber a variant, only append; renumbering would silently repoint every persisted route of that kind.

#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[repr(u8)]
pub enum InterfaceKind {
    Loopback = 0,
    TcpClient = 1,
    TcpServer = 2,
    Udp = 3,
    Serial = 4,
    UsbAutoHost = 5,
    UsbAutoDevice = 6,
    AutoWifi = 7,
    WifiPeer = 8,
    LocalServer = 9,
    LocalClient = 10,
    TcpServerPeer = 11,
    BluetoothAuto = 12,
    BluetoothPeer = 13,
    LoRa = 14,
    Kiss = 15,
    Ax25Kiss = 16,
    Pipe = 17,
    Rnode = 18,
    BackboneServer = 19,
    BackboneServerPeer = 20,
    BackboneClient = 21,
    EspNow = 22,
    WebSocketClient = 23,
    WebSocketServer = 24,
    WebSocketServerPeer = 25,
    WifiDirect = 26,
    WifiDirectPeer = 27,
    WifiAware = 28,
    WifiAwarePeer = 29,
    I2p = 30,
    I2pPeer = 31,
    Weave = 32,
    WeavePeer = 33,
}

impl InterfaceKind {
    pub const ALL: [Self; 34] = [
        Self::Loopback,
        Self::TcpClient,
        Self::TcpServer,
        Self::Udp,
        Self::Serial,
        Self::UsbAutoHost,
        Self::UsbAutoDevice,
        Self::AutoWifi,
        Self::WifiPeer,
        Self::LocalServer,
        Self::LocalClient,
        Self::TcpServerPeer,
        Self::BluetoothAuto,
        Self::BluetoothPeer,
        Self::LoRa,
        Self::Kiss,
        Self::Ax25Kiss,
        Self::Pipe,
        Self::Rnode,
        Self::BackboneServer,
        Self::BackboneServerPeer,
        Self::BackboneClient,
        Self::EspNow,
        Self::WebSocketClient,
        Self::WebSocketServer,
        Self::WebSocketServerPeer,
        Self::WifiDirect,
        Self::WifiDirectPeer,
        Self::WifiAware,
        Self::WifiAwarePeer,
        Self::I2p,
        Self::I2pPeer,
        Self::Weave,
        Self::WeavePeer,
    ];

    #[must_use]
    pub const fn from_u8(byte: u8) -> Option<Self> {
        match byte {
            0 => Some(Self::Loopback),
            1 => Some(Self::TcpClient),
            2 => Some(Self::TcpServer),
            3 => Some(Self::Udp),
            4 => Some(Self::Serial),
            5 => Some(Self::UsbAutoHost),
            6 => Some(Self::UsbAutoDevice),
            7 => Some(Self::AutoWifi),
            8 => Some(Self::WifiPeer),
            9 => Some(Self::LocalServer),
            10 => Some(Self::LocalClient),
            11 => Some(Self::TcpServerPeer),
            12 => Some(Self::BluetoothAuto),
            13 => Some(Self::BluetoothPeer),
            14 => Some(Self::LoRa),
            15 => Some(Self::Kiss),
            16 => Some(Self::Ax25Kiss),
            17 => Some(Self::Pipe),
            18 => Some(Self::Rnode),
            19 => Some(Self::BackboneServer),
            20 => Some(Self::BackboneServerPeer),
            21 => Some(Self::BackboneClient),
            22 => Some(Self::EspNow),
            23 => Some(Self::WebSocketClient),
            24 => Some(Self::WebSocketServer),
            25 => Some(Self::WebSocketServerPeer),
            26 => Some(Self::WifiDirect),
            27 => Some(Self::WifiDirectPeer),
            28 => Some(Self::WifiAware),
            29 => Some(Self::WifiAwarePeer),
            30 => Some(Self::I2p),
            31 => Some(Self::I2pPeer),
            32 => Some(Self::Weave),
            33 => Some(Self::WeavePeer),
            _ => None,
        }
    }

    #[must_use]
    pub const fn name(self) -> &'static str {
        match self {
            Self::Loopback => "loopback",
            Self::TcpClient => "tcp-client",
            Self::TcpServer => "tcp-server",
            Self::Udp => "udp",
            Self::Serial => "serial",
            Self::UsbAutoHost => "usb-auto-host",
            Self::UsbAutoDevice => "usb-auto-device",
            Self::AutoWifi => "auto-wifi",
            Self::WifiPeer => "wifi-peer",
            Self::LocalServer => "local-server",
            Self::LocalClient => "local-client",
            Self::TcpServerPeer => "tcp-server-peer",
            Self::BluetoothAuto => "bluetooth-auto",
            Self::BluetoothPeer => "bluetooth-peer",
            Self::LoRa => "lora",
            Self::Kiss => "kiss",
            Self::Ax25Kiss => "ax25-kiss",
            Self::Pipe => "pipe",
            Self::Rnode => "rnode",
            Self::BackboneServer => "backbone-server",
            Self::BackboneServerPeer => "backbone-server-peer",
            Self::BackboneClient => "backbone-client",
            Self::EspNow => "esp-now",
            Self::WebSocketClient => "websocket-client",
            Self::WebSocketServer => "websocket-server",
            Self::WebSocketServerPeer => "websocket-server-peer",
            Self::WifiDirect => "wifi-direct",
            Self::WifiDirectPeer => "wifi-direct-peer",
            Self::WifiAware => "wifi-aware",
            Self::WifiAwarePeer => "wifi-aware-peer",
            Self::I2p => "i2p",
            Self::I2pPeer => "i2p-peer",
            Self::Weave => "weave",
            Self::WeavePeer => "weave-peer",
        }
    }

    #[must_use]
    pub const fn member_kind(self) -> Option<Self> {
        match self {
            Self::AutoWifi => Some(Self::WifiPeer),
            Self::LocalServer => Some(Self::LocalClient),
            Self::TcpServer => Some(Self::TcpServerPeer),
            Self::BackboneServer => Some(Self::BackboneServerPeer),
            Self::BluetoothAuto => Some(Self::BluetoothPeer),
            Self::WebSocketServer => Some(Self::WebSocketServerPeer),
            Self::WifiDirect => Some(Self::WifiDirectPeer),
            Self::WifiAware => Some(Self::WifiAwarePeer),
            Self::I2p => Some(Self::I2pPeer),
            Self::Weave => Some(Self::WeavePeer),
            _ => None,
        }
    }

    #[must_use]
    pub const fn supervisor_kind(self) -> Option<Self> {
        match self {
            Self::WifiPeer => Some(Self::AutoWifi),
            Self::LocalClient => Some(Self::LocalServer),
            Self::TcpServerPeer => Some(Self::TcpServer),
            Self::BackboneServerPeer => Some(Self::BackboneServer),
            Self::BluetoothPeer => Some(Self::BluetoothAuto),
            Self::WebSocketServerPeer => Some(Self::WebSocketServer),
            Self::WifiDirectPeer => Some(Self::WifiDirect),
            Self::WifiAwarePeer => Some(Self::WifiAware),
            Self::I2pPeer => Some(Self::I2p),
            Self::WeavePeer => Some(Self::Weave),
            _ => None,
        }
    }
}

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

    #[test]
    fn all_covers_every_stable_discriminant_in_order() {
        for (index, kind) in InterfaceKind::ALL.into_iter().enumerate() {
            assert_eq!(kind as usize, index);
            assert_eq!(InterfaceKind::from_u8(index as u8), Some(kind));
        }
        assert_eq!(InterfaceKind::from_u8(InterfaceKind::ALL.len() as u8), None);
    }

    #[test]
    fn the_local_kinds_round_trip_their_discriminants() {
        for kind in [InterfaceKind::LocalServer, InterfaceKind::LocalClient] {
            assert_eq!(InterfaceKind::from_u8(kind as u8), Some(kind));
        }
    }

    #[test]
    fn a_local_server_supervises_local_clients() {
        assert_eq!(
            InterfaceKind::LocalServer.member_kind(),
            Some(InterfaceKind::LocalClient)
        );
        assert_eq!(InterfaceKind::LocalClient.member_kind(), None);
    }

    #[test]
    fn a_backbone_server_supervises_backbone_peers() {
        assert_eq!(
            InterfaceKind::from_u8(19),
            Some(InterfaceKind::BackboneServer)
        );
        assert_eq!(
            InterfaceKind::from_u8(20),
            Some(InterfaceKind::BackboneServerPeer)
        );
        assert_eq!(
            InterfaceKind::from_u8(21),
            Some(InterfaceKind::BackboneClient)
        );
        assert_eq!(
            InterfaceKind::BackboneServer.member_kind(),
            Some(InterfaceKind::BackboneServerPeer)
        );
        assert_eq!(
            InterfaceKind::BackboneServerPeer.supervisor_kind(),
            Some(InterfaceKind::BackboneServer)
        );
        assert_eq!(InterfaceKind::BackboneClient.member_kind(), None);
        assert_eq!(InterfaceKind::BackboneClient.supervisor_kind(), None);
    }

    #[test]
    fn bluetooth_auto_supervises_bluetooth_peers() {
        assert_eq!(
            InterfaceKind::from_u8(12),
            Some(InterfaceKind::BluetoothAuto)
        );
        assert_eq!(
            InterfaceKind::from_u8(13),
            Some(InterfaceKind::BluetoothPeer)
        );
        assert_eq!(
            InterfaceKind::BluetoothAuto.member_kind(),
            Some(InterfaceKind::BluetoothPeer)
        );
        assert_eq!(
            InterfaceKind::BluetoothPeer.supervisor_kind(),
            Some(InterfaceKind::BluetoothAuto)
        );
    }

    #[test]
    fn websocket_server_supervises_websocket_peers() {
        assert_eq!(
            InterfaceKind::from_u8(23),
            Some(InterfaceKind::WebSocketClient)
        );
        assert_eq!(
            InterfaceKind::from_u8(24),
            Some(InterfaceKind::WebSocketServer)
        );
        assert_eq!(
            InterfaceKind::from_u8(25),
            Some(InterfaceKind::WebSocketServerPeer)
        );
        assert_eq!(
            InterfaceKind::WebSocketServer.member_kind(),
            Some(InterfaceKind::WebSocketServerPeer)
        );
        assert_eq!(
            InterfaceKind::WebSocketServerPeer.supervisor_kind(),
            Some(InterfaceKind::WebSocketServer)
        );
        assert_eq!(InterfaceKind::WebSocketClient.member_kind(), None);
        assert_eq!(InterfaceKind::WebSocketClient.supervisor_kind(), None);
    }

    #[test]
    fn every_fleet_supervisor_discriminant_fits_the_fan_mask() {
        for byte in 0..=u8::MAX {
            let Some(kind) = InterfaceKind::from_u8(byte) else {
                continue;
            };
            if let Some(supervisor) = kind.supervisor_kind() {
                assert!(
                    (supervisor as u8) < 128,
                    "the announce-fan mask is u128; a supervisor discriminant past 127 overflows its shift",
                );
            }
        }
    }

    #[test]
    fn wifi_direct_supervises_wifi_direct_peers() {
        assert_eq!(InterfaceKind::from_u8(26), Some(InterfaceKind::WifiDirect));
        assert_eq!(
            InterfaceKind::from_u8(27),
            Some(InterfaceKind::WifiDirectPeer)
        );
        assert_eq!(
            InterfaceKind::WifiDirect.member_kind(),
            Some(InterfaceKind::WifiDirectPeer)
        );
        assert_eq!(
            InterfaceKind::WifiDirectPeer.supervisor_kind(),
            Some(InterfaceKind::WifiDirect)
        );
    }

    #[test]
    fn wifi_aware_supervises_wifi_aware_peers() {
        assert_eq!(InterfaceKind::from_u8(28), Some(InterfaceKind::WifiAware));
        assert_eq!(
            InterfaceKind::from_u8(29),
            Some(InterfaceKind::WifiAwarePeer)
        );
        assert_eq!(
            InterfaceKind::WifiAware.member_kind(),
            Some(InterfaceKind::WifiAwarePeer)
        );
        assert_eq!(
            InterfaceKind::WifiAwarePeer.supervisor_kind(),
            Some(InterfaceKind::WifiAware)
        );
    }

    #[test]
    fn i2p_supervises_i2p_peers() {
        assert_eq!(InterfaceKind::from_u8(30), Some(InterfaceKind::I2p));
        assert_eq!(InterfaceKind::from_u8(31), Some(InterfaceKind::I2pPeer));
        assert_eq!(
            InterfaceKind::I2p.member_kind(),
            Some(InterfaceKind::I2pPeer)
        );
        assert_eq!(
            InterfaceKind::I2pPeer.supervisor_kind(),
            Some(InterfaceKind::I2p)
        );
    }

    #[test]
    fn weave_supervises_weave_peers() {
        assert_eq!(InterfaceKind::from_u8(32), Some(InterfaceKind::Weave));
        assert_eq!(InterfaceKind::from_u8(33), Some(InterfaceKind::WeavePeer));
        assert_eq!(
            InterfaceKind::Weave.member_kind(),
            Some(InterfaceKind::WeavePeer)
        );
        assert_eq!(
            InterfaceKind::WeavePeer.supervisor_kind(),
            Some(InterfaceKind::Weave)
        );
    }
}

#[cfg_attr(mutants, mutants::skip)]
#[cfg(kani)]
mod kani_proofs {
    use super::*;

    #[kani::proof]
    fn fleet_member_and_supervisor_kinds_are_inverses() {
        let byte: u8 = kani::any();
        if let Some(kind) = InterfaceKind::from_u8(byte) {
            if let Some(member) = kind.member_kind() {
                assert_eq!(member.supervisor_kind(), Some(kind));
            }
            if let Some(supervisor) = kind.supervisor_kind() {
                assert_eq!(supervisor.member_kind(), Some(kind));
            }
        }
    }

    #[kani::proof]
    fn fleet_supervisor_discriminants_fit_the_fan_mask() {
        let byte: u8 = kani::any();
        if let Some(kind) = InterfaceKind::from_u8(byte) {
            if let Some(supervisor) = kind.supervisor_kind() {
                assert!((supervisor as u8) < 128);
            }
        }
    }
}