prns-core 0.3.4

Pure Reticulum engine and wire contract for Personal Reticulum
use allocator_api2::alloc::{Allocator, Global};
use allocator_api2::boxed::Box;
use allocator_api2::vec::Vec;

use crate::crypto::{Ed25519PublicKey, X25519PublicKey};
use crate::identity::destination_identity::impls::fixed::destination_identity_index_buckets;
use crate::identity::destination_identity::{DestinationIdentityRecord, DestinationIdentityTable};
use crate::identity::{
    DestinationIdentityRetentionState, IdentityEncryptionPublicKey, IdentityPublicKeys,
    IdentitySigningPublicKey,
};
use crate::lemire_index::LemireIndex;
use crate::routing::announce::stored::AppDataHandle;
use crate::storage::TablePushError;
use crate::units::InstantMillis;
use crate::wire::DestinationHash;

fn filled<T: Clone, A: Allocator>(value: T, len: usize, allocator: A) -> Box<[T], A> {
    let mut column = Vec::with_capacity_in(len, allocator);
    column.resize(len, value);
    column.into_boxed_slice()
}

pub struct FixedHeapDestinationIdentityTable<
    const CAPACITY: usize,
    const INDEX_BUCKETS: usize,
    A: Allocator = Global,
> {
    len: usize,
    destinations: Box<[DestinationHash], A>,
    public_keys: Box<[IdentityPublicKeys], A>,
    announced_at: Box<[InstantMillis], A>,
    retention: Box<[DestinationIdentityRetentionState], A>,
    app_data_handles: Box<[AppDataHandle], A>,
    index: LemireIndex<INDEX_BUCKETS>,
}

impl<const CAPACITY: usize, const INDEX_BUCKETS: usize, A: Allocator + Default> Default
    for FixedHeapDestinationIdentityTable<CAPACITY, INDEX_BUCKETS, A>
{
    fn default() -> Self {
        const {
            assert!(
                INDEX_BUCKETS >= destination_identity_index_buckets(CAPACITY),
                "INDEX_BUCKETS must preserve two-thirds-load headroom over CAPACITY",
            );
            assert!(
                CAPACITY < u16::MAX as usize,
                "FixedHeapDestinationIdentityTable indexes slots as u16",
            );
        }
        Self {
            len: 0,
            destinations: filled(DestinationHash::new([0; 16]), CAPACITY, A::default()),
            public_keys: filled(
                IdentityPublicKeys {
                    encryption: IdentityEncryptionPublicKey::new(X25519PublicKey([0; 32])),
                    signing: IdentitySigningPublicKey::new(Ed25519PublicKey([0; 32])),
                },
                CAPACITY,
                A::default(),
            ),
            announced_at: filled(InstantMillis(0), CAPACITY, A::default()),
            retention: filled(
                DestinationIdentityRetentionState::NeverUsed,
                CAPACITY,
                A::default(),
            ),
            app_data_handles: filled(AppDataHandle::new(0), CAPACITY, A::default()),
            index: LemireIndex::default(),
        }
    }
}

impl<const CAPACITY: usize, const INDEX_BUCKETS: usize, A: Allocator> DestinationIdentityTable
    for FixedHeapDestinationIdentityTable<CAPACITY, INDEX_BUCKETS, A>
{
    fn capacity(&self) -> usize {
        CAPACITY
    }

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

    fn index_of(&self, destination: &DestinationHash) -> Option<usize> {
        self.index.get(destination, &self.destinations)
    }

    fn destinations(&self) -> &[DestinationHash] {
        &self.destinations[..self.len]
    }

    fn public_keys(&self) -> &[IdentityPublicKeys] {
        &self.public_keys[..self.len]
    }

    fn announced_at(&self) -> &[InstantMillis] {
        &self.announced_at[..self.len]
    }

    fn retention(&self) -> &[DestinationIdentityRetentionState] {
        &self.retention[..self.len]
    }

    fn app_data_handles(&self) -> &[AppDataHandle] {
        &self.app_data_handles[..self.len]
    }

    fn set_row(&mut self, index: usize, record: DestinationIdentityRecord) {
        self.public_keys[index] = record.public_keys;
        self.announced_at[index] = record.announced_at;
        self.retention[index] = record.retention;
        self.app_data_handles[index] = record.app_data_handle;
    }

    fn push(
        &mut self,
        destination: DestinationHash,
        record: DestinationIdentityRecord,
    ) -> Result<usize, TablePushError> {
        if self.len >= CAPACITY {
            return Err(TablePushError::TableFull);
        }
        let index = self.len;
        self.destinations[index] = destination;
        self.set_row(index, record);
        self.len += 1;
        self.index.insert(index, &self.destinations);
        Ok(index)
    }

    fn swap_remove(&mut self, index: usize) {
        let last = self.len - 1;
        self.index.remove_slot(index, &self.destinations);
        if index != last {
            let moved = self.destinations[last];
            self.index.repoint(&moved, index, &self.destinations);
            self.destinations[index] = self.destinations[last];
            self.public_keys[index] = self.public_keys[last];
            self.announced_at[index] = self.announced_at[last];
            self.retention[index] = self.retention[last];
            self.app_data_handles[index] = self.app_data_handles[last];
        }
        self.len = last;
    }
}