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
}
}