minerva 0.2.0

Causal ordering for distributed systems
extern crate alloc;

use alloc::vec::Vec;

use crate::metis::Cut;
use crate::metis::dot_set::HAVE_SET_WIRE_HEADER_LEN;
use thiserror::Error;

use super::super::gate::{EpochAddress, InvalidEpochAddress};
use super::error::WireCutError;
use super::shared::{
    WIRE_ADDRESS_LEN, cut_embedding_len, decode_cut, encode_address_into, encode_cut_into,
    read_address_parts,
};

/// Version tag for the canonical confirmation wire encoding.
const CONFIRMATION_WIRE_V1: u8 = 0x01;
/// Version and candidate address.
const CONFIRMATION_WIRE_PREFIX_LEN: usize = 1 + WIRE_ADDRESS_LEN;
/// The fixed prefix plus an empty cut embedding.
const CONFIRMATION_WIRE_MIN_LEN: usize = CONFIRMATION_WIRE_PREFIX_LEN + HAVE_SET_WIRE_HEADER_LEN;

/// Decode failure for a [`ConfirmationRecord`] wire frame.
#[non_exhaustive]
#[derive(Error, Debug, Clone, Copy, PartialEq, Eq)]
pub enum ConfirmationDecodeError {
    /// The leading version byte is not recognized.
    #[error("unknown confirmation wire version: {0:#04x}")]
    UnknownVersion(u8),
    /// The input is too short or carries trailing bytes.
    #[error("unexpected confirmation frame length: expected {expected}, found {found}")]
    UnexpectedLength {
        /// Required lower bound, or exact length for trailing input.
        expected: usize,
        /// Supplied input length.
        found: usize,
    },
    /// The candidate address is invalid.
    #[error("confirmation address: {0}")]
    Address(#[from] InvalidEpochAddress),
    /// The witnessed-cut embedding refused.
    #[error("confirmation cut: {0}")]
    Cut(#[from] WireCutError),
}

/// A member's confirmation report in wire shape (S284; the PRD 0025
/// kind-6 payload).
///
/// The reporter is the authenticated envelope sender. The decoded record
/// feeds [`Epochs::confirm`](crate::metis::Epochs::confirm) with that
/// identity; its delivered cut remains a peer claim.
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct ConfirmationRecord {
    epoch: EpochAddress,
    delivered: Cut,
}

impl ConfirmationRecord {
    /// Builds the report a confirming member emits.
    #[must_use]
    pub const fn new(epoch: EpochAddress, delivered: Cut) -> Self {
        Self { epoch, delivered }
    }

    /// The candidate address this report confirms.
    #[must_use]
    pub const fn epoch(&self) -> EpochAddress {
        self.epoch
    }

    /// The reporter's delivered cut at delivery.
    #[must_use]
    pub const fn delivered(&self) -> &Cut {
        &self.delivered
    }

    /// Consumes the record into the pair accepted by `Epochs::confirm`.
    #[must_use]
    pub fn into_parts(self) -> (EpochAddress, Cut) {
        (self.epoch, self.delivered)
    }

    /// Encodes this report as its canonical version-1 wire frame.
    #[must_use]
    pub fn to_bytes(&self) -> Vec<u8> {
        let mut out = Vec::with_capacity(self.encoded_len());
        out.push(CONFIRMATION_WIRE_V1);
        encode_address_into(&mut out, self.epoch);
        encode_cut_into(&mut out, self.delivered.as_vector());
        out
    }

    /// The exact frame length, computed allocation-free.
    #[must_use]
    pub fn encoded_len(&self) -> usize {
        CONFIRMATION_WIRE_PREFIX_LEN + cut_embedding_len(self.delivered.as_vector())
    }

    /// Decodes one canonical frame, rejecting trailing bytes.
    ///
    /// # Errors
    ///
    /// Returns [`ConfirmationDecodeError`] for an unknown version,
    /// invalid length, impossible address, or malformed cut.
    pub fn from_bytes(bytes: &[u8]) -> Result<Self, ConfirmationDecodeError> {
        if bytes.len() < CONFIRMATION_WIRE_MIN_LEN {
            return Err(ConfirmationDecodeError::UnexpectedLength {
                expected: CONFIRMATION_WIRE_MIN_LEN,
                found: bytes.len(),
            });
        }
        let version = bytes[0];
        if version != CONFIRMATION_WIRE_V1 {
            return Err(ConfirmationDecodeError::UnknownVersion(version));
        }
        let short = ConfirmationDecodeError::UnexpectedLength {
            expected: CONFIRMATION_WIRE_MIN_LEN,
            found: bytes.len(),
        };
        let ((generation, dot), rest) = read_address_parts(&bytes[1..]).ok_or(short)?;
        let epoch = EpochAddress::try_from_parts(generation, dot)?;
        let (delivered, tail) = decode_cut(rest)?;
        if !tail.is_empty() {
            return Err(ConfirmationDecodeError::UnexpectedLength {
                expected: bytes.len() - tail.len(),
                found: bytes.len(),
            });
        }
        Ok(Self {
            epoch,
            delivered: Cut::from_witnessed(delivered),
        })
    }
}