Skip to main content

alpine_protocol_sdk/
typestate.rs

1use std::net::SocketAddr;
2
3use alpine::crypto::identity::NodeCredentials;
4use alpine::messages::DeviceIdentity;
5
6use crate::discovery::{DeviceTrustState, DiscoveryOutcome, TrustedDiscoveryOutcome};
7use crate::error::AlpineSdkError;
8use crate::quarantine::quarantine_reason;
9use crate::session::{AlpineClient, ControlCommand, ControlOptions, ControlResponse};
10use crate::trust::TrustPolicy;
11
12fn build_identity(outcome: &DiscoveryOutcome) -> DeviceIdentity {
13    DeviceIdentity {
14        device_id: outcome.reply.device_id.clone(),
15        manufacturer_id: outcome.reply.manufacturer_id.clone(),
16        model_id: outcome.reply.model_id.clone(),
17        hardware_rev: outcome.reply.hardware_rev.clone(),
18        firmware_rev: outcome.reply.firmware_rev.clone(),
19    }
20}
21
22#[must_use]
23#[derive(Debug, Clone)]
24pub struct UntrustedClient {
25    outcome: DiscoveryOutcome,
26}
27
28impl UntrustedClient {
29    pub fn new(outcome: DiscoveryOutcome) -> Self {
30        Self { outcome }
31    }
32
33    pub fn outcome(&self) -> &DiscoveryOutcome {
34        &self.outcome
35    }
36
37    pub fn trust_state(&self) -> DeviceTrustState {
38        self.outcome.trust_state()
39    }
40
41    pub fn verify_trust(self) -> Result<TrustedClient, AlpineSdkError> {
42        let trusted = self.outcome.require_trusted()?;
43        Ok(TrustedClient { outcome: trusted })
44    }
45
46    pub async fn connect_allow_untrusted(
47        self,
48        local_addr: SocketAddr,
49        credentials: NodeCredentials,
50    ) -> Result<ActiveSession, AlpineSdkError> {
51        let identity = build_identity(&self.outcome);
52        let capabilities = self.outcome.reply.capabilities.clone();
53        let mut client = AlpineClient::connect(
54            local_addr,
55            self.outcome.peer,
56            identity,
57            capabilities,
58            credentials,
59        )
60        .await?;
61        client.set_run_id(self.outcome.run_id.clone());
62        let quarantine_reason = quarantine_reason(client.remote_addr().ip());
63        Ok(ActiveSession {
64            client,
65            trust_state: self.outcome.trust_state(),
66            trust_policy: TrustPolicy::AllowUntrusted,
67            quarantine_reason,
68        })
69    }
70}
71
72#[must_use]
73#[derive(Debug, Clone)]
74pub struct TrustedClient {
75    outcome: TrustedDiscoveryOutcome,
76}
77
78impl TrustedClient {
79    pub fn outcome(&self) -> &DiscoveryOutcome {
80        &self.outcome.outcome
81    }
82
83    pub async fn connect(
84        self,
85        local_addr: SocketAddr,
86        credentials: NodeCredentials,
87    ) -> Result<ActiveSession, AlpineSdkError> {
88        let identity = build_identity(&self.outcome.outcome);
89        let capabilities = self.outcome.outcome.reply.capabilities.clone();
90        let mut client = AlpineClient::connect(
91            local_addr,
92            self.outcome.outcome.peer,
93            identity,
94            capabilities,
95            credentials,
96        )
97        .await?;
98        client.set_run_id(self.outcome.outcome.run_id.clone());
99        let quarantine_reason = quarantine_reason(client.remote_addr().ip());
100        Ok(ActiveSession {
101            client,
102            trust_state: DeviceTrustState::Trusted,
103            trust_policy: TrustPolicy::Strict,
104            quarantine_reason,
105        })
106    }
107}
108
109#[must_use]
110#[derive(Debug)]
111pub struct ActiveSession {
112    client: AlpineClient,
113    trust_state: DeviceTrustState,
114    trust_policy: TrustPolicy,
115    quarantine_reason: Option<String>,
116}
117
118impl ActiveSession {
119    pub fn client(&self) -> &AlpineClient {
120        &self.client
121    }
122
123    pub fn client_mut(&mut self) -> &mut AlpineClient {
124        &mut self.client
125    }
126
127    pub fn trust_state(&self) -> DeviceTrustState {
128        self.trust_state.clone()
129    }
130
131    pub fn trust_policy(&self) -> TrustPolicy {
132        self.trust_policy
133    }
134
135    pub fn set_trust_policy(&mut self, policy: TrustPolicy) {
136        self.trust_policy = policy;
137    }
138
139    pub fn run_id(&self) -> Option<&str> {
140        self.client.run_id()
141    }
142
143    pub fn quarantine_reason(&self) -> Option<&str> {
144        self.quarantine_reason.as_deref()
145    }
146
147    pub async fn control(
148        &self,
149        command: ControlCommand,
150    ) -> Result<ControlResponse, AlpineSdkError> {
151        self.control_with_options(command, ControlOptions::default())
152            .await
153    }
154
155    pub async fn control_with_options(
156        &self,
157        command: ControlCommand,
158        options: ControlOptions,
159    ) -> Result<ControlResponse, AlpineSdkError> {
160        if let Some(reason) = self.quarantine_reason.as_ref() {
161            if !command.is_observe_safe() {
162                return Err(AlpineSdkError::Quarantined(reason.clone()));
163            }
164        }
165        if command.requires_trust()
166            && self.trust_policy == TrustPolicy::AllowUntrusted
167            && self.trust_state != DeviceTrustState::Trusted
168        {
169            return Err(AlpineSdkError::SensitiveOperationRequiresTrust);
170        }
171        self.client.control_with_options(command, options).await
172    }
173
174    pub fn start_stream(
175        &mut self,
176        profile: alpine::profile::StreamProfile,
177    ) -> Result<String, AlpineSdkError> {
178        if let Some(reason) = self.quarantine_reason.as_ref() {
179            return Err(AlpineSdkError::Quarantined(reason.clone()));
180        }
181        self.client.start_stream(profile)
182    }
183
184    pub async fn close(self) {
185        self.client.close().await;
186    }
187
188    pub fn into_inner(self) -> AlpineClient {
189        self.client
190    }
191}