prns-core 0.3.4

Pure Reticulum engine and wire contract for Personal Reticulum
use alloc::format;
use alloc::string::String;
use alloc::vec::Vec;
use core::time::Duration;

use crate::identity::IdentityHash;
use crate::interfaces::IfacSize;
use crate::interfaces::{
    ConnectionState, InterfaceId, InterfaceMode, InterfaceSnapshot, TransferRates,
};

use super::message_pack::MessagePackEncoder;
use super::wire_names::{interface, transport};
use super::{interface_name, RnsManagementEncodeError};

#[derive(Debug, Clone, PartialEq, Eq)]
pub struct RnsInterfaceAccessCode {
    signature: [u8; 64],
    size: IfacSize,
    network_name: Option<String>,
}

impl RnsInterfaceAccessCode {
    pub fn new(signature: [u8; 64], size: IfacSize, network_name: Option<String>) -> Self {
        Self {
            signature,
            size,
            network_name,
        }
    }
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub struct RnsInterfaceStatsEntry {
    name: Option<String>,
    snapshot: InterfaceSnapshot,
    access_code: Option<RnsInterfaceAccessCode>,
}

impl RnsInterfaceStatsEntry {
    pub fn new(
        name: Option<String>,
        snapshot: InterfaceSnapshot,
        access_code: Option<RnsInterfaceAccessCode>,
    ) -> Self {
        Self {
            name,
            snapshot,
            access_code,
        }
    }
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct RnsTransportStatus {
    transport_identity: IdentityHash,
    network_identity: Option<IdentityHash>,
    uptime: Duration,
}

impl RnsTransportStatus {
    pub const fn new(
        transport_identity: IdentityHash,
        network_identity: Option<IdentityHash>,
        uptime: Duration,
    ) -> Self {
        Self {
            transport_identity,
            network_identity,
            uptime,
        }
    }
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub struct RnsInterfaceStats {
    entries: Vec<RnsInterfaceStatsEntry>,
    transport: Option<RnsTransportStatus>,
}

impl RnsInterfaceStats {
    pub fn new(entries: Vec<RnsInterfaceStatsEntry>) -> Self {
        Self {
            entries,
            transport: None,
        }
    }

    pub fn with_transport(mut self, transport: RnsTransportStatus) -> Self {
        self.transport = Some(transport);
        self
    }

    pub fn encode_message_pack(&self) -> Result<Vec<u8>, RnsManagementEncodeError> {
        let mut encoder = MessagePackEncoder::new();
        self.encode_into(&mut encoder)?;
        Ok(encoder.finish())
    }

    pub fn encode_remote_response(
        &self,
        link_count: Option<u32>,
    ) -> Result<Vec<u8>, RnsManagementEncodeError> {
        let mut encoder = MessagePackEncoder::new();
        encoder.array(if link_count.is_some() { 2 } else { 1 })?;
        self.encode_into(&mut encoder)?;
        if let Some(link_count) = link_count {
            encoder.unsigned(u64::from(link_count));
        }
        Ok(encoder.finish())
    }

    pub(crate) fn encode_into(
        &self,
        encoder: &mut MessagePackEncoder,
    ) -> Result<(), RnsManagementEncodeError> {
        encoder.map(if self.transport.is_some() { 10 } else { 6 })?;
        encoder.field(interface::INTERFACES)?;
        encoder.array(self.entries.len())?;

        let mut total_receive_bytes = 0u64;
        let mut total_transmit_bytes = 0u64;
        let mut total_receive_speed = 0u64;
        let mut total_transmit_speed = 0u64;
        for entry in &self.entries {
            encode_interface_entry(encoder, entry)?;
            let rates = entry.snapshot.transfer_rates.unwrap_or(TransferRates {
                rx_bps: 0,
                tx_bps: 0,
            });
            total_receive_bytes = total_receive_bytes.saturating_add(entry.snapshot.rx_bytes);
            total_transmit_bytes = total_transmit_bytes.saturating_add(entry.snapshot.tx_bytes);
            total_receive_speed = total_receive_speed.saturating_add(u64::from(rates.rx_bps));
            total_transmit_speed = total_transmit_speed.saturating_add(u64::from(rates.tx_bps));
        }

        encoder.unsigned_field(interface::RECEIVE_BYTES, total_receive_bytes)?;
        encoder.unsigned_field(interface::TRANSMIT_BYTES, total_transmit_bytes)?;
        encoder.unsigned_field(interface::RECEIVE_SPEED, total_receive_speed)?;
        encoder.unsigned_field(interface::TRANSMIT_SPEED, total_transmit_speed)?;
        encoder.field(interface::RESIDENT_SET_SIZE)?;
        encoder.nil();

        if let Some(status) = self.transport {
            encoder.field(transport::IDENTITY)?;
            encoder.binary(status.transport_identity.as_bytes())?;
            encoder.field(transport::NETWORK_IDENTITY)?;
            match status.network_identity {
                Some(identity) => encoder.binary(identity.as_bytes())?,
                None => encoder.nil(),
            }
            encoder.field(transport::UPTIME)?;
            encoder.float(status.uptime.as_secs_f64());
            encoder.field(transport::PROBE_RESPONDER)?;
            encoder.nil();
        }
        Ok(())
    }
}

fn encode_interface_entry(
    encoder: &mut MessagePackEncoder,
    entry: &RnsInterfaceStatsEntry,
) -> Result<(), RnsManagementEncodeError> {
    let name = entry
        .name
        .clone()
        .unwrap_or_else(|| interface_name(entry.snapshot.id));
    let rates = entry.snapshot.transfer_rates.unwrap_or(TransferRates {
        rx_bps: 0,
        tx_bps: 0,
    });
    encoder.map(14)?;
    encoder.string_field(interface::NAME, &name)?;
    encoder.string_field(interface::SHORT_NAME, &name)?;
    encoder.string_field(interface::TYPE, &interface_type(entry.snapshot.id))?;
    encoder.field(interface::STATUS)?;
    encoder.boolean(is_online(entry.snapshot.connection));
    encoder.field(interface::MODE)?;
    encoder.signed(interface_mode(entry.snapshot.mode));
    encoder.field(interface::GRAVITY)?;
    encoder.signed(entry.snapshot.gravity.get());
    encoder.field(interface::CLIENTS)?;
    encoder.nil();
    encoder.unsigned_field(interface::RECEIVE_BYTES, entry.snapshot.rx_bytes)?;
    encoder.unsigned_field(interface::TRANSMIT_BYTES, entry.snapshot.tx_bytes)?;
    encoder.unsigned_field(interface::RECEIVE_SPEED, u64::from(rates.rx_bps))?;
    encoder.unsigned_field(interface::TRANSMIT_SPEED, u64::from(rates.tx_bps))?;
    encoder.field(interface::IFAC_SIGNATURE)?;
    match &entry.access_code {
        Some(access_code) => encoder.binary(&access_code.signature)?,
        None => encoder.nil(),
    }
    encoder.field(interface::IFAC_SIZE)?;
    match &entry.access_code {
        Some(access_code) => {
            encoder.unsigned(u64::try_from(access_code.size.bytes()).unwrap_or(u64::MAX))
        }
        None => encoder.nil(),
    }
    encoder.field(interface::IFAC_NETWORK_NAME)?;
    match entry
        .access_code
        .as_ref()
        .and_then(|access_code| access_code.network_name.as_deref())
    {
        Some(network_name) => encoder.string(network_name)?,
        None => encoder.nil(),
    }
    Ok(())
}

fn interface_mode(mode: InterfaceMode) -> i64 {
    match mode {
        InterfaceMode::Full => 0x01,
        InterfaceMode::PointToPoint => 0x02,
        InterfaceMode::AccessPoint => 0x03,
        InterfaceMode::Roaming => 0x04,
        InterfaceMode::Boundary => 0x05,
        InterfaceMode::Gateway => 0x06,
        InterfaceMode::Internal => 0x07,
    }
}

fn interface_type(id: InterfaceId) -> String {
    match id.kind() {
        Some(kind) => format!("{kind:?}"),
        None => String::from("Interface"),
    }
}

fn is_online(connection: ConnectionState) -> bool {
    matches!(
        connection,
        ConnectionState::Connected | ConnectionState::Degraded
    )
}