alpine-protocol-sdk 0.2.4

High-level SDK on top of the ALPINE protocol layer.
Documentation
use std::net::SocketAddr;

use alpine::crypto::identity::NodeCredentials;
use alpine::messages::DeviceIdentity;

use crate::discovery::{DeviceTrustState, DiscoveryOutcome, TrustedDiscoveryOutcome};
use crate::error::AlpineSdkError;
use crate::quarantine::quarantine_reason;
use crate::session::{AlpineClient, ControlCommand, ControlOptions, ControlResponse};
use crate::trust::TrustPolicy;

fn build_identity(outcome: &DiscoveryOutcome) -> DeviceIdentity {
    DeviceIdentity {
        device_id: outcome.reply.device_id.clone(),
        manufacturer_id: outcome.reply.manufacturer_id.clone(),
        model_id: outcome.reply.model_id.clone(),
        hardware_rev: outcome.reply.hardware_rev.clone(),
        firmware_rev: outcome.reply.firmware_rev.clone(),
    }
}

#[must_use]
#[derive(Debug, Clone)]
pub struct UntrustedClient {
    outcome: DiscoveryOutcome,
}

impl UntrustedClient {
    pub fn new(outcome: DiscoveryOutcome) -> Self {
        Self { outcome }
    }

    pub fn outcome(&self) -> &DiscoveryOutcome {
        &self.outcome
    }

    pub fn trust_state(&self) -> DeviceTrustState {
        self.outcome.trust_state()
    }

    pub fn verify_trust(self) -> Result<TrustedClient, AlpineSdkError> {
        let trusted = self.outcome.require_trusted()?;
        Ok(TrustedClient { outcome: trusted })
    }

    pub async fn connect_allow_untrusted(
        self,
        local_addr: SocketAddr,
        credentials: NodeCredentials,
    ) -> Result<ActiveSession, AlpineSdkError> {
        let identity = build_identity(&self.outcome);
        let capabilities = self.outcome.reply.capabilities.clone();
        let mut client = AlpineClient::connect(
            local_addr,
            self.outcome.peer,
            identity,
            capabilities,
            credentials,
        )
        .await?;
        client.set_run_id(self.outcome.run_id.clone());
        let quarantine_reason = quarantine_reason(client.remote_addr().ip());
        Ok(ActiveSession {
            client,
            trust_state: self.outcome.trust_state(),
            trust_policy: TrustPolicy::AllowUntrusted,
            quarantine_reason,
        })
    }
}

#[must_use]
#[derive(Debug, Clone)]
pub struct TrustedClient {
    outcome: TrustedDiscoveryOutcome,
}

impl TrustedClient {
    pub fn outcome(&self) -> &DiscoveryOutcome {
        &self.outcome.outcome
    }

    pub async fn connect(
        self,
        local_addr: SocketAddr,
        credentials: NodeCredentials,
    ) -> Result<ActiveSession, AlpineSdkError> {
        let identity = build_identity(&self.outcome.outcome);
        let capabilities = self.outcome.outcome.reply.capabilities.clone();
        let mut client = AlpineClient::connect(
            local_addr,
            self.outcome.outcome.peer,
            identity,
            capabilities,
            credentials,
        )
        .await?;
        client.set_run_id(self.outcome.outcome.run_id.clone());
        let quarantine_reason = quarantine_reason(client.remote_addr().ip());
        Ok(ActiveSession {
            client,
            trust_state: DeviceTrustState::Trusted,
            trust_policy: TrustPolicy::Strict,
            quarantine_reason,
        })
    }
}

#[must_use]
#[derive(Debug)]
pub struct ActiveSession {
    client: AlpineClient,
    trust_state: DeviceTrustState,
    trust_policy: TrustPolicy,
    quarantine_reason: Option<String>,
}

impl ActiveSession {
    pub fn client(&self) -> &AlpineClient {
        &self.client
    }

    pub fn client_mut(&mut self) -> &mut AlpineClient {
        &mut self.client
    }

    pub fn trust_state(&self) -> DeviceTrustState {
        self.trust_state.clone()
    }

    pub fn trust_policy(&self) -> TrustPolicy {
        self.trust_policy
    }

    pub fn set_trust_policy(&mut self, policy: TrustPolicy) {
        self.trust_policy = policy;
    }

    pub fn run_id(&self) -> Option<&str> {
        self.client.run_id()
    }

    pub fn quarantine_reason(&self) -> Option<&str> {
        self.quarantine_reason.as_deref()
    }

    pub async fn control(
        &self,
        command: ControlCommand,
    ) -> Result<ControlResponse, AlpineSdkError> {
        self.control_with_options(command, ControlOptions::default())
            .await
    }

    pub async fn control_with_options(
        &self,
        command: ControlCommand,
        options: ControlOptions,
    ) -> Result<ControlResponse, AlpineSdkError> {
        if let Some(reason) = self.quarantine_reason.as_ref() {
            if !command.is_observe_safe() {
                return Err(AlpineSdkError::Quarantined(reason.clone()));
            }
        }
        if command.requires_trust()
            && self.trust_policy == TrustPolicy::AllowUntrusted
            && self.trust_state != DeviceTrustState::Trusted
        {
            return Err(AlpineSdkError::SensitiveOperationRequiresTrust);
        }
        self.client.control_with_options(command, options).await
    }

    pub fn start_stream(
        &mut self,
        profile: alpine::profile::StreamProfile,
    ) -> Result<String, AlpineSdkError> {
        if let Some(reason) = self.quarantine_reason.as_ref() {
            return Err(AlpineSdkError::Quarantined(reason.clone()));
        }
        self.client.start_stream(profile)
    }

    pub async fn close(self) {
        self.client.close().await;
    }

    pub fn into_inner(self) -> AlpineClient {
        self.client
    }
}