openrtc 2.8.2

OpenRTC: a Rust-first P2P runtime for device discovery, signaling, and iroh/QUIC networking.
Documentation
//! Bilateral protected proof for an Iroh carrier candidate.
//!
//! The serialized frame is always carried inside an application-crypto stream
//! opened on the pending Iroh connection. It proves that both admitted peers
//! can exchange protected product-shaped bytes over the exact candidate before
//! either side commits the physical replacement.

use crate::iroh_carrier_bootstrap::CarrierBootstrapKind;

pub const CARRIER_CANDIDATE_PROOF_TYPE: &str = "#openrtc-iroh-carrier-proof";
pub const CARRIER_CANDIDATE_PROOF_VERSION: u8 = 1;
pub(crate) const CARRIER_CANDIDATE_PROOF_DELIVERY_TIMEOUT: std::time::Duration =
    std::time::Duration::from_secs(20);

pub(crate) async fn read_candidate_proof_stream(
    recv: &mut crate::application_crypto_streams::PeerRecvStream,
) -> anyhow::Result<Vec<u8>> {
    const MAX_PROOF_BYTES: usize = 2_048;
    let mut payload = Vec::new();
    loop {
        let mut chunk = [0_u8; 512];
        let read = recv.read(&mut chunk).await?;
        if read == 0 {
            break;
        }
        anyhow::ensure!(
            payload.len().saturating_add(read) <= MAX_PROOF_BYTES,
            "carrier candidate proof exceeds {MAX_PROOF_BYTES} bytes"
        );
        payload.extend_from_slice(&chunk[..read]);
    }
    anyhow::ensure!(!payload.is_empty(), "carrier candidate proof is empty");
    Ok(payload)
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
#[serde(rename_all = "kebab-case")]
pub enum CarrierCandidateProofRole {
    Probe,
    Ack,
    Commit,
    Committed,
}

#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct CarrierCandidateProofFrame {
    #[serde(rename = "type")]
    pub frame_type: String,
    pub version: u8,
    pub role: CarrierCandidateProofRole,
    pub carrier: CarrierBootstrapKind,
    pub transport_id: u64,
    pub upgrade_id: String,
    pub challenge: String,
    pub base_transport_generation: u64,
    pub base_route_generation: u64,
}

impl CarrierCandidateProofFrame {
    pub fn probe(
        carrier: CarrierBootstrapKind,
        transport_id: u64,
        upgrade_id: impl Into<String>,
        base_transport_generation: u64,
        base_route_generation: u64,
    ) -> Result<Self, CarrierCandidateProofError> {
        let mut challenge = [0u8; 16];
        getrandom::getrandom(&mut challenge)
            .map_err(|_| CarrierCandidateProofError::EntropyUnavailable)?;
        let frame = Self {
            frame_type: CARRIER_CANDIDATE_PROOF_TYPE.to_string(),
            version: CARRIER_CANDIDATE_PROOF_VERSION,
            role: CarrierCandidateProofRole::Probe,
            carrier,
            transport_id,
            upgrade_id: upgrade_id.into(),
            challenge: hex::encode(challenge),
            base_transport_generation,
            base_route_generation,
        };
        frame.validate()?;
        Ok(frame)
    }

    pub fn ack_from(probe: &Self) -> Result<Self, CarrierCandidateProofError> {
        probe.validate()?;
        if probe.role != CarrierCandidateProofRole::Probe {
            return Err(CarrierCandidateProofError::InvalidTransition);
        }
        let mut ack = probe.clone();
        ack.role = CarrierCandidateProofRole::Ack;
        Ok(ack)
    }

    pub fn commit_from(probe: &Self) -> Result<Self, CarrierCandidateProofError> {
        probe.validate()?;
        if probe.role != CarrierCandidateProofRole::Probe {
            return Err(CarrierCandidateProofError::InvalidTransition);
        }
        let mut commit = probe.clone();
        commit.role = CarrierCandidateProofRole::Commit;
        Ok(commit)
    }

    pub fn committed_from(commit: &Self) -> Result<Self, CarrierCandidateProofError> {
        commit.validate()?;
        if commit.role != CarrierCandidateProofRole::Commit {
            return Err(CarrierCandidateProofError::InvalidTransition);
        }
        let mut committed = commit.clone();
        committed.role = CarrierCandidateProofRole::Committed;
        Ok(committed)
    }

    pub fn validate(&self) -> Result<(), CarrierCandidateProofError> {
        if self.frame_type != CARRIER_CANDIDATE_PROOF_TYPE {
            return Err(CarrierCandidateProofError::WrongFrameType);
        }
        if self.version != CARRIER_CANDIDATE_PROOF_VERSION {
            return Err(CarrierCandidateProofError::UnsupportedVersion);
        }
        if self.transport_id == 0 {
            return Err(CarrierCandidateProofError::InvalidTransportId);
        }
        if !(8..=96).contains(&self.upgrade_id.len())
            || !self
                .upgrade_id
                .bytes()
                .all(|byte| byte.is_ascii_alphanumeric() || byte == b'-')
        {
            return Err(CarrierCandidateProofError::InvalidUpgradeId);
        }
        if self.challenge.len() != 32
            || !self
                .challenge
                .bytes()
                .all(|byte| byte.is_ascii_digit() || (b'a'..=b'f').contains(&byte))
        {
            return Err(CarrierCandidateProofError::InvalidChallenge);
        }
        Ok(())
    }

    pub fn matches_probe(&self, probe: &Self) -> bool {
        self.validate().is_ok()
            && probe.validate().is_ok()
            && self.role == CarrierCandidateProofRole::Ack
            && probe.role == CarrierCandidateProofRole::Probe
            && self.carrier == probe.carrier
            && self.transport_id == probe.transport_id
            && self.upgrade_id == probe.upgrade_id
            && self.challenge == probe.challenge
            && self.base_transport_generation == probe.base_transport_generation
            && self.base_route_generation == probe.base_route_generation
    }

    pub fn matches_commit_for_probe(&self, probe: &Self) -> bool {
        self.validate().is_ok()
            && probe.validate().is_ok()
            && self.role == CarrierCandidateProofRole::Commit
            && probe.role == CarrierCandidateProofRole::Probe
            && self.carrier == probe.carrier
            && self.transport_id == probe.transport_id
            && self.upgrade_id == probe.upgrade_id
            && self.challenge == probe.challenge
            && self.base_transport_generation == probe.base_transport_generation
            && self.base_route_generation == probe.base_route_generation
    }

    pub fn matches_committed_for_commit(&self, commit: &Self) -> bool {
        self.validate().is_ok()
            && commit.validate().is_ok()
            && self.role == CarrierCandidateProofRole::Committed
            && commit.role == CarrierCandidateProofRole::Commit
            && self.carrier == commit.carrier
            && self.transport_id == commit.transport_id
            && self.upgrade_id == commit.upgrade_id
            && self.challenge == commit.challenge
            && self.base_transport_generation == commit.base_transport_generation
            && self.base_route_generation == commit.base_route_generation
    }
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum CarrierCandidateProofError {
    EntropyUnavailable,
    WrongFrameType,
    UnsupportedVersion,
    InvalidTransportId,
    InvalidUpgradeId,
    InvalidChallenge,
    InvalidTransition,
}

impl std::fmt::Display for CarrierCandidateProofError {
    fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(formatter, "{self:?}")
    }
}

impl std::error::Error for CarrierCandidateProofError {}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn ack_echoes_complete_probe_fence() {
        let probe = CarrierCandidateProofFrame::probe(
            CarrierBootstrapKind::Ble,
            0x424c45,
            "upgrade-1234",
            7,
            2,
        )
        .unwrap();
        let ack = CarrierCandidateProofFrame::ack_from(&probe).unwrap();
        assert!(ack.matches_probe(&probe));
    }

    #[test]
    fn stale_or_cross_transport_ack_is_rejected() {
        let probe = CarrierCandidateProofFrame::probe(
            CarrierBootstrapKind::WebRtc,
            41,
            "upgrade-1234",
            7,
            2,
        )
        .unwrap();
        let mut ack = CarrierCandidateProofFrame::ack_from(&probe).unwrap();
        ack.transport_id = 42;
        assert!(!ack.matches_probe(&probe));
    }

    #[test]
    fn commit_confirmation_echoes_the_proven_candidate() {
        let probe = CarrierCandidateProofFrame::probe(
            CarrierBootstrapKind::WebRtc,
            41,
            "upgrade-1234",
            7,
            2,
        )
        .unwrap();
        let commit = CarrierCandidateProofFrame::commit_from(&probe).unwrap();
        assert!(commit.matches_commit_for_probe(&probe));
        let committed = CarrierCandidateProofFrame::committed_from(&commit).unwrap();
        assert!(committed.matches_committed_for_commit(&commit));
    }
}