coven/sync/
device_join_transport.rs1use tokio::sync::watch;
12
13use crate::config::Config;
14use crate::sync::join::{BootstrapError, DeviceJoinClient};
15use crate::sync::store::{
16 DeviceJoinAbandonment, DeviceJoinAction, DeviceJoinActivation, DeviceJoinCancellation,
17 DeviceJoinCleanupActivation, DeviceJoinOfferBundle, DeviceJoinRoles, DeviceJoinStatus,
18 DeviceJoinStep, DeviceJoinTransport, DeviceJoinTransportTiming,
19 DeviceProviderAdmissionApproval, ProviderReadyDeviceBootstrap,
20};
21
22#[derive(Clone, Debug)]
24pub enum DeviceJoinTransportOutcome {
25 Joined(Config),
27 Abandoned(DeviceJoinAbandonment),
29}
30
31impl DeviceJoinClient {
32 pub async fn join_via_transport(
40 &self,
41 bundle: &DeviceJoinOfferBundle,
42 timing: DeviceJoinTransportTiming,
43 on_status: impl Fn(&str),
44 cancel: &watch::Receiver<bool>,
45 ) -> Result<DeviceJoinTransportOutcome, BootstrapError> {
46 let storage = self.transport_storage().await?;
47 let transport = DeviceJoinTransport::open(&storage, bundle, DeviceJoinRoles::joiner())?;
48 let attempt_id = bundle.offer.attempt_id;
49
50 loop {
56 match self.device_join_status(attempt_id)? {
57 None | Some(DeviceJoinStatus::AwaitingAccessRequest { .. }) => {
58 let request = self
59 .prepare_provider_access_request(bundle.offer.clone())
60 .await?;
61 transport
62 .publish(&DeviceJoinAction::TransferProviderAccessRequest(request))
63 .await?;
64 }
65 Some(DeviceJoinStatus::Abandoned { abandonment }) => {
66 return self.accept_abandonment(&transport, abandonment).await;
67 }
68 Some(DeviceJoinStatus::AwaitingProviderAdmission { request }) => {
69 transport
70 .publish(&DeviceJoinAction::TransferProviderAccessRequest(request))
71 .await?;
72 let approval = match transport
76 .await_step::<DeviceProviderAdmissionApproval>(timing)
77 .await?
78 {
79 DeviceJoinStep::Continue(approval) => approval,
80 DeviceJoinStep::Abandoned(abandonment) => {
81 return self.accept_abandonment(&transport, abandonment).await;
82 }
83 };
84 let registration_request = self.prepare_registration_request(approval).await?;
85 transport
86 .publish(&DeviceJoinAction::TransferRegistrationRequest(
87 registration_request,
88 ))
89 .await?;
90 }
91 Some(DeviceJoinStatus::AwaitingRegistrationRequest { approval }) => {
92 let registration_request = self.prepare_registration_request(approval).await?;
93 transport
94 .publish(&DeviceJoinAction::TransferRegistrationRequest(
95 registration_request,
96 ))
97 .await?;
98 }
99 Some(DeviceJoinStatus::AwaitingBootstrap { request }) => {
100 transport
101 .publish(&DeviceJoinAction::TransferRegistrationRequest(request))
102 .await?;
103 let provider_ready = transport
104 .await_artifact::<ProviderReadyDeviceBootstrap>(timing)
105 .await?;
106 let readiness =
107 Box::pin(self.bootstrap_pending_device(provider_ready, &on_status, cancel))
108 .await?;
109 transport
110 .publish(&DeviceJoinAction::TransferReadiness(readiness))
111 .await?;
112 }
113 Some(DeviceJoinStatus::AwaitingReadiness { bootstrap }) => {
114 let readiness =
115 Box::pin(self.bootstrap_pending_device(bootstrap, &on_status, cancel))
116 .await?;
117 transport
118 .publish(&DeviceJoinAction::TransferReadiness(readiness))
119 .await?;
120 }
121 Some(DeviceJoinStatus::AwaitingProviderCompletion { readiness }) => {
122 transport
123 .publish(&DeviceJoinAction::TransferReadiness(readiness))
124 .await?;
125 let activation = transport
126 .await_artifact::<DeviceJoinActivation>(timing)
127 .await?;
128 return self.finish(&transport, activation, &on_status).await;
129 }
130 Some(DeviceJoinStatus::AwaitingCompletion { activation }) => {
131 return self.finish(&transport, activation, &on_status).await;
132 }
133 Some(DeviceJoinStatus::Activated { store }) => {
134 return self.finish(&transport, store.activation, &on_status).await;
135 }
136 Some(_) => return Err(crate::DeviceJoinError::JournalConflict.into()),
137 }
138 }
139 }
140
141 async fn finish(
145 &self,
146 transport: &DeviceJoinTransport<'_>,
147 activation: DeviceJoinActivation,
148 on_status: &impl Fn(&str),
149 ) -> Result<DeviceJoinTransportOutcome, BootstrapError> {
150 let config = self.complete_device_join(activation, on_status).await?;
151 transport.delete_attempt_slots().await?;
152 Ok(DeviceJoinTransportOutcome::Joined(config))
153 }
154
155 async fn accept_abandonment(
159 &self,
160 transport: &DeviceJoinTransport<'_>,
161 abandonment: DeviceJoinAbandonment,
162 ) -> Result<DeviceJoinTransportOutcome, BootstrapError> {
163 let accepted = self.accept_device_join_abandonment(abandonment).await?;
164 transport.delete_attempt_slots().await?;
165 Ok(DeviceJoinTransportOutcome::Abandoned(accepted))
166 }
167
168 pub async fn close_device_join_via_transport(
175 &self,
176 bundle: &DeviceJoinOfferBundle,
177 timing: DeviceJoinTransportTiming,
178 ) -> Result<(), BootstrapError> {
179 let storage = self.transport_storage().await?;
180 let transport = DeviceJoinTransport::open(&storage, bundle, DeviceJoinRoles::joiner())?;
181 let attempt_id = bundle.offer.attempt_id;
182
183 match self.device_join_status(attempt_id)? {
184 Some(DeviceJoinStatus::CleanupActivated { activation }) => {
187 if self
188 .resume_device_joins()?
189 .contains(&DeviceJoinAction::CompleteCleanup(activation.clone()))
190 {
191 self.complete_cancelled_device_join(activation).await?;
192 }
193 transport.delete_attempt_slots().await?;
194 return Ok(());
195 }
196 Some(DeviceJoinStatus::JoinerClosed { terminal }) => {
197 transport
198 .publish(&DeviceJoinAction::TransferJoinerTerminal(terminal))
199 .await?;
200 }
201 _ => {
202 let cancellation = transport
203 .await_artifact::<DeviceJoinCancellation>(timing)
204 .await?;
205 let terminal = self.close_pending_device_join(cancellation).await?;
206 transport
207 .publish(&DeviceJoinAction::TransferJoinerTerminal(terminal))
208 .await?;
209 }
210 }
211
212 let activation = transport
213 .await_artifact::<DeviceJoinCleanupActivation>(timing)
214 .await?;
215 self.complete_cancelled_device_join(activation).await?;
216 transport.delete_attempt_slots().await?;
217 Ok(())
218 }
219}