minerva 0.2.0

Causal ordering for distributed systems
extern crate alloc;

use alloc::vec::Vec;
use thiserror::Error;

use super::super::gate::{EpochAddress, InvalidEpochAddress};
use super::shared::{WIRE_ADDRESS_LEN, encode_address_into, read_address_parts, read_u64};

/// Version tag for the canonical adoption-report wire encoding.
const ADOPTION_REPORT_WIRE_V1: u8 = 0x01;
/// Version, winner address, and own-station counter.
const ADOPTION_REPORT_WIRE_LEN: usize = 1 + WIRE_ADDRESS_LEN + 8;

/// Decode failure for an [`AdoptionReportRecord`] wire frame.
#[non_exhaustive]
#[derive(Error, Debug, Clone, Copy, PartialEq, Eq)]
pub enum AdoptionReportDecodeError {
    /// The leading version byte is not recognized.
    #[error("unknown adoption-report wire version: {0:#04x}")]
    UnknownVersion(u8),
    /// The input is not exactly one fixed-width frame.
    #[error("unexpected adoption-report frame length: expected {expected}, found {found}")]
    UnexpectedLength {
        /// Required fixed frame length.
        expected: usize,
        /// Supplied input length.
        found: usize,
    },
    /// The winner address is invalid.
    #[error("adoption-report address: {0}")]
    Address(#[from] InvalidEpochAddress),
}

/// A member's adoption report in wire shape (S284; the PRD 0025 kind-7
/// payload).
///
/// The reporter is the authenticated envelope sender. The decoded record
/// feeds [`Epochs::adopt_report`](crate::metis::Epochs::adopt_report)
/// beside that identity.
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub struct AdoptionReportRecord {
    epoch: EpochAddress,
    own_counter: u64,
}

impl AdoptionReportRecord {
    /// Builds the report an adopting member emits.
    #[must_use]
    pub const fn new(epoch: EpochAddress, own_counter: u64) -> Self {
        Self { epoch, own_counter }
    }

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

    /// The reporter's own-station counter at adoption.
    #[must_use]
    pub const fn own_counter(&self) -> u64 {
        self.own_counter
    }

    /// Encodes this report as its canonical fixed-width frame.
    #[must_use]
    pub fn to_bytes(&self) -> Vec<u8> {
        let mut out = Vec::with_capacity(ADOPTION_REPORT_WIRE_LEN);
        out.push(ADOPTION_REPORT_WIRE_V1);
        encode_address_into(&mut out, self.epoch);
        out.extend_from_slice(&self.own_counter.to_be_bytes());
        out
    }

    /// The exact fixed frame length.
    #[must_use]
    pub const fn encoded_len(&self) -> usize {
        ADOPTION_REPORT_WIRE_LEN
    }

    /// Decodes one canonical fixed-width frame.
    ///
    /// # Errors
    ///
    /// Returns [`AdoptionReportDecodeError`] for an unknown version,
    /// invalid length, or impossible winner address.
    pub fn from_bytes(bytes: &[u8]) -> Result<Self, AdoptionReportDecodeError> {
        if bytes.len() != ADOPTION_REPORT_WIRE_LEN {
            return Err(AdoptionReportDecodeError::UnexpectedLength {
                expected: ADOPTION_REPORT_WIRE_LEN,
                found: bytes.len(),
            });
        }
        let version = bytes[0];
        if version != ADOPTION_REPORT_WIRE_V1 {
            return Err(AdoptionReportDecodeError::UnknownVersion(version));
        }
        let short = AdoptionReportDecodeError::UnexpectedLength {
            expected: ADOPTION_REPORT_WIRE_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 (own_counter, _) = read_u64(rest).ok_or(short)?;
        Ok(Self { epoch, own_counter })
    }
}