prns-core 0.3.4

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

use crate::routing::announce::emit::AnnounceAppDataBytes;
use crate::routing::announce::DottedNameHash;
use crate::routing::upstream_app_destinations::{
    UpstreamAppDestinationKind, UpstreamAppDestinationTable,
};
use crate::storage::TablePushError;
use crate::wire::DestinationHash;

#[derive(Debug, Default)]
pub struct HeapUpstreamAppDestinationTable {
    destination: Vec<DestinationHash>,
    kind: Vec<UpstreamAppDestinationKind>,
    name_hash: Vec<DottedNameHash>,
    app_data: Vec<AnnounceAppDataBytes>,
}

impl UpstreamAppDestinationTable for HeapUpstreamAppDestinationTable {
    fn capacity(&self) -> usize {
        usize::MAX
    }
    fn len(&self) -> usize {
        self.destination.len()
    }

    fn destinations(&self) -> &[DestinationHash] {
        &self.destination
    }
    fn kinds(&self) -> &[UpstreamAppDestinationKind] {
        &self.kind
    }
    fn name_hashes(&self) -> &[DottedNameHash] {
        &self.name_hash
    }
    fn app_data_at(&self, index: usize) -> Option<&[u8]> {
        self.app_data.get(index).map(|data| data.as_slice())
    }

    fn kind_mut(&mut self, index: usize) -> &mut UpstreamAppDestinationKind {
        &mut self.kind[index]
    }

    fn upsert(
        &mut self,
        destination: DestinationHash,
        kind: UpstreamAppDestinationKind,
        name_hash: DottedNameHash,
        app_data: AnnounceAppDataBytes,
    ) -> Result<usize, TablePushError> {
        if let Some(i) = self
            .destination
            .iter()
            .position(|candidate| *candidate == destination)
        {
            self.kind[i] = kind;
            self.name_hash[i] = name_hash;
            self.app_data[i] = app_data;
            return Ok(i);
        }
        let i = self.destination.len();
        self.destination.push(destination);
        self.kind.push(kind);
        self.name_hash.push(name_hash);
        self.app_data.push(app_data);
        Ok(i)
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::crypto::ratchets::RatchetPolicy;
    use crate::identity::IdentityHash;
    use crate::routing::links::resources::ResourceStrategy;
    use crate::routing::upstream_app_destinations::LinkRequestPolicy;
    use crate::routing::upstream_app_destinations::ProofStrategy;
    use crate::wire::{DOTTED_NAME_HASH_BYTE_LEN, TRUNCATED_HASH_BYTE_LEN};

    #[test]
    fn grows_past_any_fixed_ceiling() {
        let mut table = HeapUpstreamAppDestinationTable::default();
        assert_eq!(table.capacity(), usize::MAX);

        for n in 0..100u8 {
            let upserted = table.upsert(
                DestinationHash::new([n; TRUNCATED_HASH_BYTE_LEN]),
                UpstreamAppDestinationKind::Single {
                    identity: IdentityHash::new([n; 16]),
                    proof_strategy: ProofStrategy::ProveNone,
                    link_request_policy: LinkRequestPolicy::AcceptAll,
                    resource_strategy: ResourceStrategy::AcceptNone,
                    maximum_request_bytes: crate::units::ByteLimit::Unlimited,
                    ratchet_policy: RatchetPolicy::NoRatchets,
                },
                DottedNameHash::new([n; DOTTED_NAME_HASH_BYTE_LEN]),
                AnnounceAppDataBytes::new(),
            );
            assert_eq!(upserted, Ok(n as usize));
        }
        assert_eq!(table.len(), 100);
        assert_eq!(table.destinations().len(), 100);
        assert_eq!(table.kinds().len(), 100);
        assert_eq!(table.name_hashes().len(), 100);
    }
}