alpine_protocol_sdk/
typestate.rs1use 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}