use crate::fixed::SignedFixed;
use crate::observatory::ObservatoryFlagSet;
#[inline(always)]
pub(crate) fn mix64(a: u64, b: u64) -> u64 {
let mut x = a ^ b.wrapping_mul(0x9E37_79B9_7F4A_7C15);
x ^= x >> 30;
x = x.wrapping_mul(0xBF58_476D_1CE4_E5B9);
x ^= x >> 27;
x = x.wrapping_mul(0x94D0_49BB_1331_11EB);
x ^= x >> 31;
x
}
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub enum ProposalRefusal {
ProposalDigestMismatch,
RoundIdentityMismatch,
CurrentModeDigestMismatch,
UnsupportedDelta,
TelemetryStandingBlocked,
}
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub struct ModeProposal {
proposed_control_delta: SignedFixed,
observation_digest: u64,
current_mode_digest: u64,
round_identity: u64,
flags: ObservatoryFlagSet,
proposal_digest: u64,
}
impl ModeProposal {
#[inline(always)]
pub(crate) fn propose(
proposed_control_delta: SignedFixed,
observation_digest: u64,
current_mode_digest: u64,
round_identity: u64,
flags: ObservatoryFlagSet,
) -> Self {
let proposal_digest = Self::seal(
proposed_control_delta,
observation_digest,
current_mode_digest,
round_identity,
flags,
);
Self {
proposed_control_delta,
observation_digest,
current_mode_digest,
round_identity,
flags,
proposal_digest,
}
}
#[cfg(test)]
pub fn test_fixture(
proposed_control_delta: SignedFixed,
observation_digest: u64,
current_mode_digest: u64,
round_identity: u64,
flags: ObservatoryFlagSet,
) -> Self {
Self::propose(
proposed_control_delta,
observation_digest,
current_mode_digest,
round_identity,
flags,
)
}
#[inline(always)]
fn seal(
proposed_control_delta: SignedFixed,
observation_digest: u64,
current_mode_digest: u64,
round_identity: u64,
flags: ObservatoryFlagSet,
) -> u64 {
let mut d = mix64(
proposed_control_delta.value_bits() as u64,
observation_digest,
);
d = mix64(d, current_mode_digest);
d = mix64(d, round_identity);
d = mix64(d, flags.bits() as u64);
d
}
#[inline(always)]
pub fn proposed_control_delta(&self) -> SignedFixed {
self.proposed_control_delta
}
#[inline(always)]
pub fn observation_digest(&self) -> u64 {
self.observation_digest
}
#[inline(always)]
pub fn current_mode_digest(&self) -> u64 {
self.current_mode_digest
}
#[inline(always)]
pub fn round_identity(&self) -> u64 {
self.round_identity
}
#[inline(always)]
pub fn flags(&self) -> ObservatoryFlagSet {
self.flags
}
#[inline(always)]
pub fn proposal_digest(&self) -> u64 {
self.proposal_digest
}
}
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub struct AdmittedProposal {
pub(crate) proposal: ModeProposal,
}
impl AdmittedProposal {
#[inline(always)]
pub fn proposal(&self) -> &ModeProposal {
&self.proposal
}
}
pub fn admit_proposal(
proposal: ModeProposal,
expected_round_identity: u64,
expected_current_mode_digest: u64,
max_supported_delta: SignedFixed,
) -> Result<AdmittedProposal, ProposalRefusal> {
let recomputed = ModeProposal::seal(
proposal.proposed_control_delta,
proposal.observation_digest,
proposal.current_mode_digest,
proposal.round_identity,
proposal.flags,
);
if recomputed != proposal.proposal_digest {
return Err(ProposalRefusal::ProposalDigestMismatch);
}
if proposal.round_identity != expected_round_identity {
return Err(ProposalRefusal::RoundIdentityMismatch);
}
if proposal.current_mode_digest != expected_current_mode_digest {
return Err(ProposalRefusal::CurrentModeDigestMismatch);
}
if proposal.proposed_control_delta.value_bits().unsigned_abs()
> max_supported_delta.value_bits().unsigned_abs()
{
return Err(ProposalRefusal::UnsupportedDelta);
}
if !proposal.flags.telemetry_admissible() {
return Err(ProposalRefusal::TelemetryStandingBlocked);
}
Ok(AdmittedProposal { proposal })
}
#[cfg(test)]
mod tests {
use super::*;
use crate::observatory::ObservatoryFlag;
fn clean_flags() -> ObservatoryFlagSet {
ObservatoryFlagSet::EMPTY.insert(ObservatoryFlag::RecertificationCandidate)
}
fn base_proposal() -> ModeProposal {
ModeProposal::test_fixture(SignedFixed::from_bits(10), 1, 2, 3, clean_flags())
}
#[test]
fn admits_when_every_binding_matches() {
let p = base_proposal();
let admitted = admit_proposal(p, 3, 2, SignedFixed::from_bits(100));
assert!(admitted.is_ok());
}
#[test]
fn refuses_on_digest_tamper() {
let mut p = base_proposal();
p.proposal_digest ^= 1; assert_eq!(
admit_proposal(p, 3, 2, SignedFixed::from_bits(100)),
Err(ProposalRefusal::ProposalDigestMismatch)
);
}
#[test]
fn refuses_on_round_mismatch() {
let p = base_proposal();
assert_eq!(
admit_proposal(p, 999, 2, SignedFixed::from_bits(100)),
Err(ProposalRefusal::RoundIdentityMismatch)
);
}
#[test]
fn refuses_on_current_mode_digest_mismatch() {
let p = base_proposal();
assert_eq!(
admit_proposal(p, 3, 999, SignedFixed::from_bits(100)),
Err(ProposalRefusal::CurrentModeDigestMismatch)
);
}
#[test]
fn refuses_on_unsupported_delta() {
let p = base_proposal();
assert_eq!(
admit_proposal(p, 3, 2, SignedFixed::from_bits(1)),
Err(ProposalRefusal::UnsupportedDelta)
);
}
#[test]
fn refuses_on_blocked_telemetry_standing() {
let bad_flags = ObservatoryFlagSet::EMPTY.insert(ObservatoryFlag::Drifting);
let p = ModeProposal::test_fixture(SignedFixed::from_bits(10), 1, 2, 3, bad_flags);
assert_eq!(
admit_proposal(p, 3, 2, SignedFixed::from_bits(100)),
Err(ProposalRefusal::TelemetryStandingBlocked)
);
}
#[test]
fn cannot_construct_admitted_proposal_except_via_admit_proposal() {
let p = base_proposal();
let admitted = admit_proposal(p, 3, 2, SignedFixed::from_bits(100)).unwrap();
assert_eq!(admitted.proposal().round_identity(), 3);
}
}