prns-core 0.3.4

Pure Reticulum engine and wire contract for Personal Reticulum
use alloc::vec::Vec;

use crate::identity::destination_identity::{DestinationIdentityRecord, DestinationIdentityTable};
use crate::identity::IdentityPublicKeys;
use crate::lemire_index::HeapLemireIndex;
use crate::routing::announce::stored::AppDataHandle;
use crate::storage::TablePushError;
use crate::units::InstantMillis;
use crate::wire::DestinationHash;

#[derive(Debug, Default)]
pub struct HeapDestinationIdentityTable {
    destinations: Vec<DestinationHash>,
    public_keys: Vec<IdentityPublicKeys>,
    announced_at: Vec<InstantMillis>,
    retention: Vec<crate::identity::DestinationIdentityRetentionState>,
    app_data_handles: Vec<AppDataHandle>,
    index: HeapLemireIndex,
}

impl DestinationIdentityTable for HeapDestinationIdentityTable {
    fn capacity(&self) -> usize {
        usize::MAX
    }

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

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

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

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

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

    fn retention(&self) -> &[crate::identity::DestinationIdentityRetentionState] {
        &self.retention
    }

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

    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> {
        let index = self.destinations.len();
        self.destinations.push(destination);
        self.public_keys.push(record.public_keys);
        self.announced_at.push(record.announced_at);
        self.retention.push(record.retention);
        self.app_data_handles.push(record.app_data_handle);
        self.index.insert(index, &self.destinations);
        Ok(index)
    }

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

#[cfg(test)]
mod tests {
    use super::*;
    use crate::crypto::{Ed25519PublicKey, X25519PublicKey};
    use crate::identity::{
        DestinationIdentityRetentionState, IdentityEncryptionPublicKey, IdentitySigningPublicKey,
    };

    fn destination(n: u32) -> DestinationHash {
        let key = u64::from(n).wrapping_mul(0x9E37_79B9_7F4A_7C15);
        let mut bytes = [0u8; 16];
        bytes[..8].copy_from_slice(&key.to_be_bytes());
        bytes[8..12].copy_from_slice(&n.to_be_bytes());
        DestinationHash::new(bytes)
    }

    fn record(byte: u8) -> DestinationIdentityRecord {
        DestinationIdentityRecord {
            public_keys: IdentityPublicKeys {
                encryption: IdentityEncryptionPublicKey::new(X25519PublicKey([byte; 32])),
                signing: IdentitySigningPublicKey::new(Ed25519PublicKey([byte; 32])),
            },
            announced_at: InstantMillis(u64::from(byte)),
            retention: DestinationIdentityRetentionState::NeverUsed,
            app_data_handle: AppDataHandle::new(usize::from(byte)),
        }
    }

    #[test]
    fn grows_without_a_ceiling_and_indexes_every_destination() {
        let mut table = HeapDestinationIdentityTable::default();
        for n in 0..1_000u32 {
            assert_eq!(table.push(destination(n), record(n as u8)), Ok(n as usize));
        }
        assert_eq!(table.capacity(), usize::MAX);
        for n in 0..1_000u32 {
            assert_eq!(table.index_of(&destination(n)), Some(n as usize));
        }
        assert_eq!(table.index_of(&destination(2_000)), None);
    }

    #[test]
    fn the_index_tracks_rows_moved_by_removal() {
        let mut table = HeapDestinationIdentityTable::default();
        table.push(destination(1), record(1)).unwrap();
        table.push(destination(2), record(2)).unwrap();
        table.push(destination(3), record(3)).unwrap();

        table.swap_remove(0);

        assert_eq!(table.index_of(&destination(1)), None);
        assert_eq!(table.index_of(&destination(3)), Some(0));
        assert_eq!(table.index_of(&destination(2)), Some(1));
    }
}