alpine-protocol-sdk 0.2.2

High-level SDK on top of the ALPINE protocol layer.
Documentation
use std::sync::atomic::{AtomicUsize, Ordering};

use crate::error::AlpineSdkError;
use tracing::info;

/// Represents the current progression through discovery/handshake phases.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(usize)]
pub(crate) enum Phase {
    Idle = 0,
    Discovery = 1,
    Handshake = 2,
}

impl Phase {
    fn from_usize(value: usize) -> Self {
        match value {
            1 => Phase::Discovery,
            2 => Phase::Handshake,
            _ => Phase::Idle,
        }
    }

    pub(crate) fn label(&self) -> &'static str {
        match self {
            Phase::Idle => "Idle",
            Phase::Discovery => "Discovery",
            Phase::Handshake => "Handshake",
        }
    }
}

static CURRENT_PHASE: AtomicUsize = AtomicUsize::new(Phase::Idle as usize);

/// Guard that holds the SDK in a specific phase until dropped.
pub struct PhaseGuard {
    previous: Phase,
    active: Phase,
}

impl Drop for PhaseGuard {
    fn drop(&mut self) {
        CURRENT_PHASE.store(self.previous as usize, Ordering::SeqCst);
        info!(
            "[ALPINE][PHASE] exiting phase: {} -> {}",
            self.active.label(),
            self.previous.label()
        );
    }
}

pub(crate) fn current_phase() -> Phase {
    Phase::from_usize(CURRENT_PHASE.load(Ordering::SeqCst))
}

fn enter_phase(desired: Phase) -> Result<PhaseGuard, AlpineSdkError> {
    info!(
        "[ALPINE][PHASE] attempting to enter phase: {}",
        desired.label()
    );
    match CURRENT_PHASE.compare_exchange(
        Phase::Idle as usize,
        desired as usize,
        Ordering::SeqCst,
        Ordering::SeqCst,
    ) {
        Ok(_) => {
            info!("[ALPINE][PHASE] entered phase: {}", desired.label());
            Ok(PhaseGuard {
                previous: Phase::Idle,
                active: desired,
            })
        }
        Err(current) => match Phase::from_usize(current) {
            Phase::Discovery => {
                if desired == Phase::Handshake {
                    Err(AlpineSdkError::InvalidPhaseTransition(
                        "discovery already running".into(),
                    ))
                } else {
                    Err(AlpineSdkError::InvalidPhaseTransition(
                        "discovery already running".into(),
                    ))
                }
            }
            Phase::Handshake => {
                if desired == Phase::Discovery {
                    Err(AlpineSdkError::DiscoveryAfterHandshake)
                } else {
                    Err(AlpineSdkError::HandshakeAlreadyInProgress)
                }
            }
            Phase::Idle => enter_phase(desired),
        },
    }
}

/// Claims the discovery phase, preventing handshake/streaming work while held.
pub fn claim_discovery() -> Result<PhaseGuard, AlpineSdkError> {
    enter_phase(Phase::Discovery)
}

/// Claims the handshake phase, preventing discovery while held.
pub fn claim_handshake() -> Result<PhaseGuard, AlpineSdkError> {
    enter_phase(Phase::Handshake)
}