mod probe;
mod receiver;
mod sender;
mod transmit;
pub use probe::{Probe, ProbeError, ProbeScheduler, ProbeTiming};
pub use receiver::Receiver;
pub use sender::Sender;
pub use transmit::{OpenRequest, Repair, StatusOutcome, TransmitError, Transmitter};
use std::collections::VecDeque;
use crate::{Digest, PipelineLimits, Prefix, Scope};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct ReceiveEpoch(u128);
impl ReceiveEpoch {
pub const fn new(value: u128) -> Result<Self, FlowError> {
if value == 0 {
Err(FlowError::Invalid)
} else {
Ok(Self(value))
}
}
pub const fn get(self) -> u128 {
self.0
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Channel {
pub scope: Scope,
pub epoch: ReceiveEpoch,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Operation {
pub prefix: Prefix,
pub previous_digest: Digest,
pub body_bytes: u64,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Report {
pub channel: Channel,
pub revision: u64,
pub base: Prefix,
pub received: Prefix,
pub received_bytes: u64,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, thiserror::Error)]
pub enum FlowError {
#[error("invalid replica flow metadata")]
Invalid,
#[error("replica flow channel mismatch")]
Channel,
#[error("replica flow capacity exhausted")]
Capacity,
#[error("replica flow history mismatch")]
History,
#[error("replica flow report is inconsistent")]
Report,
#[error("replica flow counter exhausted")]
Exhausted,
#[error("replica flow metadata allocation failed")]
Allocation,
}
fn capacities(limits: PipelineLimits) -> Result<(u64, u64), FlowError> {
if limits.max_operations == 0 || limits.max_body_bytes == 0 {
return Err(FlowError::Invalid);
}
Ok((
u64::try_from(limits.max_operations).map_err(|_| FlowError::Invalid)?,
u64::try_from(limits.max_body_bytes).map_err(|_| FlowError::Invalid)?,
))
}
fn ledger(limits: PipelineLimits) -> Result<VecDeque<Operation>, FlowError> {
capacities(limits)?;
let mut entries = VecDeque::new();
entries
.try_reserve_exact(limits.max_operations)
.map_err(|_| FlowError::Allocation)?;
Ok(entries)
}
fn prefix_valid(prefix: Prefix) -> bool {
(prefix.op.0 == 0) == (prefix.digest == Digest::ZERO)
}
fn batch(operations: &[Operation], previous: Prefix) -> Result<(Prefix, u64), FlowError> {
if operations.is_empty() {
return Err(FlowError::Invalid);
}
let mut end = previous;
let mut bytes = 0u64;
for operation in operations {
if operation.body_bytes == 0 || !prefix_valid(operation.prefix) {
return Err(FlowError::Invalid);
}
if operation.prefix.op.0 != end.op.0.checked_add(1).ok_or(FlowError::Exhausted)?
|| operation.previous_digest != end.digest
{
return Err(FlowError::History);
}
bytes = bytes
.checked_add(operation.body_bytes)
.ok_or(FlowError::Exhausted)?;
end = operation.prefix;
}
Ok((end, bytes))
}
impl Report {
pub fn validate(self, limits: PipelineLimits) -> Result<(), FlowError> {
capacities(limits)?;
self.validate_shape()
}
pub fn validate_shape(self) -> Result<(), FlowError> {
if self.channel.scope.group_id.as_bytes() == &[0; 16]
|| self.channel.scope.configuration_digest == Digest::ZERO
|| self.revision == 0
|| !prefix_valid(self.base)
|| !prefix_valid(self.received)
{
return Err(FlowError::Invalid);
}
let received = self
.received
.op
.0
.checked_sub(self.base.op.0)
.ok_or(FlowError::History)?;
if (received == 0 && self.received != self.base)
|| (received == 0) != (self.received_bytes == 0)
|| self.received_bytes < received
{
return Err(FlowError::Report);
}
Ok(())
}
}