prns-core 0.3.4

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

use crate::identity::IdentityHash;
use crate::routing::{BlackholeExpiry, BlackholedIdentity};
use crate::units::InstantMillis;

use super::message_pack::MessagePackEncoder;
use super::wire_names::{blackhole, common};
use super::RnsManagementEncodeError;

mod decoder;

pub use decoder::RnsBlackholeDecodeError;

#[derive(Debug, Clone, PartialEq, Eq)]
struct RnsBlackholeEntry {
    identity: IdentityHash,
    source: IdentityHash,
    expiry: BlackholeExpiry,
    reason: Option<String>,
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub struct RnsBlackholeTable {
    entries: Vec<RnsBlackholeEntry>,
}

impl RnsBlackholeTable {
    pub fn from_entries<Reason: AsRef<str>>(
        entries: impl IntoIterator<Item = BlackholedIdentity<Reason>>,
    ) -> Self {
        Self {
            entries: entries.into_iter().map(RnsBlackholeEntry::from).collect(),
        }
    }

    pub fn from_source_entries<Reason: AsRef<str>>(
        source: IdentityHash,
        entries: impl IntoIterator<Item = BlackholedIdentity<Reason>>,
    ) -> Self {
        Self::from_entries(entries.into_iter().filter(|entry| entry.source == source))
    }

    pub fn decode_source_file(
        bytes: &[u8],
        source: IdentityHash,
        now: InstantMillis,
    ) -> Result<Self, RnsBlackholeDecodeError> {
        decoder::decode_source_file(bytes, source, now).map(|entries| Self { entries })
    }

    pub fn decode_published_table(
        bytes: &[u8],
        now: InstantMillis,
    ) -> Result<Self, RnsBlackholeDecodeError> {
        decoder::decode_published_table(bytes, now).map(|entries| Self { entries })
    }

    pub fn into_entries(self) -> Vec<BlackholedIdentity<String>> {
        self.entries
            .into_iter()
            .map(BlackholedIdentity::from)
            .collect()
    }

    pub fn empty() -> Self {
        Self {
            entries: Vec::new(),
        }
    }

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

    pub(crate) fn encode_into(
        &self,
        encoder: &mut MessagePackEncoder,
    ) -> Result<(), RnsManagementEncodeError> {
        encoder.map(self.entries.len())?;
        for entry in &self.entries {
            encoder.binary(entry.identity.as_bytes())?;
            encoder.map(3)?;
            encoder.field(blackhole::SOURCE)?;
            encoder.binary(entry.source.as_bytes())?;
            encoder.field(common::UNTIL)?;
            match entry.expiry {
                BlackholeExpiry::Indefinite => encoder.nil(),
                BlackholeExpiry::At(at) => encoder.float(blackhole_timestamp(at)),
            }
            encoder.field(common::REASON)?;
            match entry.reason.as_deref() {
                Some(reason) => encoder.string(reason)?,
                None => encoder.nil(),
            }
        }
        Ok(())
    }
}

impl<Reason: AsRef<str>> From<BlackholedIdentity<Reason>> for RnsBlackholeEntry {
    fn from(entry: BlackholedIdentity<Reason>) -> Self {
        Self {
            identity: entry.identity,
            source: entry.source,
            expiry: entry.expiry,
            reason: entry.reason.map(|reason| String::from(reason.as_ref())),
        }
    }
}

impl From<RnsBlackholeEntry> for BlackholedIdentity<String> {
    fn from(entry: RnsBlackholeEntry) -> Self {
        Self {
            identity: entry.identity,
            source: entry.source,
            expiry: entry.expiry,
            reason: entry.reason,
        }
    }
}

fn blackhole_timestamp(timestamp: InstantMillis) -> f64 {
    timestamp.0 as f64 / 1_000.0
}

#[cfg(test)]
mod tests;