use super::{SyncError, SyncResult};
use serde::{Deserialize, Serialize};
use std::collections::BTreeSet;
pub const MAX_REVISION_CHAIN_V1: usize = 1_024;
#[derive(Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct ObjectRevisionV1 {
pub object_id: String,
pub revision_id: String,
pub base_revision_id: Option<String>,
pub event_id: String,
}
impl std::fmt::Debug for ObjectRevisionV1 {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("ObjectRevisionV1").finish_non_exhaustive()
}
}
impl ObjectRevisionV1 {
pub fn validate(&self) -> SyncResult<()> {
for id in [&self.object_id, &self.revision_id, &self.event_id] {
validate_id(id)?;
}
if let Some(base) = &self.base_revision_id {
validate_id(base)?;
if base == &self.revision_id {
return invalid();
}
}
Ok(())
}
}
#[derive(Clone)]
pub struct RevisionChainV1 {
revisions: Vec<ObjectRevisionV1>,
}
impl RevisionChainV1 {
pub fn new(revisions: Vec<ObjectRevisionV1>) -> SyncResult<Self> {
if revisions.is_empty() || revisions.len() > MAX_REVISION_CHAIN_V1 {
return invalid();
}
let mut ids = BTreeSet::new();
let mut events = BTreeSet::new();
for (index, revision) in revisions.iter().enumerate() {
revision.validate()?;
if revision.object_id != revisions[0].object_id
|| !ids.insert(&revision.revision_id)
|| !events.insert(&revision.event_id)
|| revisions[..index].iter().any(|previous| {
previous.base_revision_id.as_ref() == Some(&revision.revision_id)
})
{
return invalid();
}
if index > 0
&& revision.base_revision_id.as_ref() != Some(&revisions[index - 1].revision_id)
{
return invalid();
}
}
Ok(Self { revisions })
}
fn position(&self, revision: &ObjectRevisionV1) -> SyncResult<Option<usize>> {
let position = self.revisions.iter().position(|entry| {
entry.revision_id == revision.revision_id || entry.event_id == revision.event_id
});
if position.is_some_and(|index| self.revisions[index] != *revision) {
return invalid();
}
Ok(position)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
#[non_exhaustive]
pub enum RevisionDecisionV1 {
AlreadyApplied,
AncestorEcho,
Applicable,
ConcurrentBranch,
SnapshotRequired,
}
pub fn decide_object_revision_v1(
local: &ObjectRevisionV1,
remote: &ObjectRevisionV1,
chain: &RevisionChainV1,
) -> SyncResult<RevisionDecisionV1> {
local.validate()?;
remote.validate()?;
if local.object_id != remote.object_id || local.object_id != chain.revisions[0].object_id {
return invalid();
}
let left = chain.position(local)?;
let right = chain.position(remote)?;
if local.revision_id == remote.revision_id || local.event_id == remote.event_id {
return if local == remote {
Ok(RevisionDecisionV1::AlreadyApplied)
} else {
invalid()
};
}
if let (Some(left), Some(right)) = (left, right) {
return Ok(if right < left {
RevisionDecisionV1::AncestorEcho
} else {
RevisionDecisionV1::Applicable
});
}
if remote.base_revision_id.as_ref() == Some(&local.revision_id) {
return Ok(RevisionDecisionV1::Applicable);
}
if local.base_revision_id.is_some() && local.base_revision_id == remote.base_revision_id {
return Ok(RevisionDecisionV1::ConcurrentBranch);
}
Ok(RevisionDecisionV1::SnapshotRequired)
}
pub fn validate_checkpoint_head_v1(checkpoint: u64, observed_head: u64) -> SyncResult<()> {
if checkpoint > observed_head {
return Err(SyncError::InvalidSequence(checkpoint));
}
Ok(())
}
fn validate_id(id: &str) -> SyncResult<()> {
if id.is_empty() || id.len() > 128 || id.chars().any(char::is_control) {
return invalid();
}
Ok(())
}
fn invalid<T>() -> SyncResult<T> {
Err(SyncError::InvalidSyncMessage(
"invalid object revision evidence",
))
}