prns-runtime 0.3.4

Runtime-neutral Personal Reticulum node contracts and manifold kernel
Documentation
use core::num::NonZeroUsize;

use heapless::Vec as HeaplessVec;
use prns_core::lemire_index::LemireIndex;

use crate::interfaces::{RssiDbm, SignalQualityTenthsPercent, SnrQuarterDb};
use crate::routing::dedup::{dedup_index_buckets, PacketHash};

use super::{PacketMetricStorage, PacketPhyRetention};

pub struct FixedPacketMetricStorage<Metric, const CAPACITY: usize, const BUCKETS: usize> {
    packet_hashes: HeaplessVec<PacketHash, CAPACITY>,
    metrics: HeaplessVec<Metric, CAPACITY>,
    index: LemireIndex<BUCKETS>,
}

impl<Metric, const CAPACITY: usize, const BUCKETS: usize>
    FixedPacketMetricStorage<Metric, CAPACITY, BUCKETS>
{
    pub const fn new() -> Self {
        const {
            assert!(
                CAPACITY > 0,
                "fixed packet PHY retention capacity must be non-zero"
            );
            assert!(
                CAPACITY < u16::MAX as usize,
                "fixed packet PHY retention exceeds its index slot range"
            );
            assert!(
                BUCKETS >= dedup_index_buckets(CAPACITY),
                "fixed packet PHY index needs two-thirds-load headroom"
            );
        }
        Self {
            packet_hashes: HeaplessVec::new(),
            metrics: HeaplessVec::new(),
            index: LemireIndex::new(),
        }
    }
}

impl<Metric, const CAPACITY: usize, const BUCKETS: usize> Default
    for FixedPacketMetricStorage<Metric, CAPACITY, BUCKETS>
{
    fn default() -> Self {
        Self::new()
    }
}

impl<Metric: Copy, const CAPACITY: usize, const BUCKETS: usize> PacketMetricStorage
    for FixedPacketMetricStorage<Metric, CAPACITY, BUCKETS>
{
    type Metric = Metric;

    fn capacity(&self) -> NonZeroUsize {
        match NonZeroUsize::new(CAPACITY) {
            Some(capacity) => capacity,
            None => unreachable!("fixed packet PHY retention capacity is non-zero"),
        }
    }

    fn len(&self) -> usize {
        self.packet_hashes.len()
    }

    fn append(&mut self, packet_hash: PacketHash, metric: Metric) {
        let slot = self.packet_hashes.len();
        assert!(
            self.packet_hashes.push(packet_hash).is_ok(),
            "shared packet PHY retention exceeded fixed hash capacity"
        );
        assert!(
            self.metrics.push(metric).is_ok(),
            "shared packet PHY retention exceeded fixed metric capacity"
        );
        self.index.insert(slot, &self.packet_hashes);
    }

    fn replace(&mut self, slot: usize, packet_hash: PacketHash, metric: Metric) {
        self.index.remove_slot(slot, &self.packet_hashes);
        self.packet_hashes[slot] = packet_hash;
        self.metrics[slot] = metric;
        self.index.insert(slot, &self.packet_hashes);
    }

    fn get(&self, packet_hash: PacketHash) -> Option<Metric> {
        self.index
            .get(&packet_hash, &self.packet_hashes)
            .map(|slot| self.metrics[slot])
    }
}

pub type FixedPacketPhyRetention<const CAPACITY: usize, const BUCKETS: usize> = PacketPhyRetention<
    FixedPacketMetricStorage<RssiDbm, CAPACITY, BUCKETS>,
    FixedPacketMetricStorage<SnrQuarterDb, CAPACITY, BUCKETS>,
    FixedPacketMetricStorage<SignalQualityTenthsPercent, CAPACITY, BUCKETS>,
>;

pub const fn fixed_packet_phy_retention<const CAPACITY: usize, const BUCKETS: usize>(
) -> FixedPacketPhyRetention<CAPACITY, BUCKETS> {
    PacketPhyRetention::from_storages(
        FixedPacketMetricStorage::new(),
        FixedPacketMetricStorage::new(),
        FixedPacketMetricStorage::new(),
    )
}