1use std::collections::BTreeMap;
21use std::time::Duration;
22
23use serde::{Deserialize, Serialize};
24
25use crate::encryption::{EncryptionService, MasterKeyring, SealError};
26use crate::keys::UserKeypair;
27use crate::storage::cloud::ObjectSlot;
28use crate::sync::storage::{
29 ProtocolObjectContext, ProtocolObjectDomain, StorageError, SyncStorage,
30};
31use crate::sync::store::{
32 DeviceJoinAbandonment, DeviceJoinAction, DeviceJoinActivation, DeviceJoinCancellation,
33 DeviceJoinCleanupActivation, DeviceJoinCleanupReceipt, DeviceJoinError, DeviceJoinOffer,
34 DeviceJoinReadiness, DeviceJoinRole, DeviceJoinStatus, DeviceProviderAccessAdministrator,
35 DeviceProviderAccessRequest, DeviceProviderAdmissionApproval,
36 DeviceProviderAdmissionCompletion, DeviceRegistrationRequest, JoinerJoinTerminal,
37 ProviderAdminJoinTerminal, ProvisionalDeviceBootstrap, Store,
38};
39use crate::sync::store_commit::{DeviceJoinAttemptId, ObjectHash, STORE_PROTOCOL_VERSION};
40
41const TRANSPORT_ROOT: &str = "store-v1/device-join-transport";
43
44const SEAL_AAD_LABEL: &[u8] = b"coven.device-join-transport.v1";
47
48#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
51#[serde(rename_all = "kebab-case", deny_unknown_fields)]
52pub enum DeviceJoinTransportKind {
53 ProviderAccessRequest,
54 ProviderAdmissionApproval,
55 RegistrationRequest,
56 ProvisionalBootstrap,
57 ProviderReadyBootstrap,
58 Readiness,
59 ProviderAdmissionCompletion,
60 Activation,
61 Abandonment,
62 Cancellation,
63 ProviderAdminTerminal,
64 JoinerTerminal,
65 CleanupReceipt,
66 CleanupActivation,
67}
68
69impl DeviceJoinTransportKind {
70 pub const ALL: [Self; 14] = [
73 Self::ProviderAccessRequest,
74 Self::ProviderAdmissionApproval,
75 Self::RegistrationRequest,
76 Self::ProvisionalBootstrap,
77 Self::ProviderReadyBootstrap,
78 Self::Readiness,
79 Self::ProviderAdmissionCompletion,
80 Self::Activation,
81 Self::Abandonment,
82 Self::Cancellation,
83 Self::ProviderAdminTerminal,
84 Self::JoinerTerminal,
85 Self::CleanupReceipt,
86 Self::CleanupActivation,
87 ];
88
89 fn slug(self) -> &'static str {
91 match self {
92 Self::ProviderAccessRequest => "provider-access-request",
93 Self::ProviderAdmissionApproval => "provider-admission-approval",
94 Self::RegistrationRequest => "registration-request",
95 Self::ProvisionalBootstrap => "provisional-bootstrap",
96 Self::ProviderReadyBootstrap => "provider-ready-bootstrap",
97 Self::Readiness => "readiness",
98 Self::ProviderAdmissionCompletion => "provider-admission-completion",
99 Self::Activation => "activation",
100 Self::Abandonment => "abandonment",
101 Self::Cancellation => "cancellation",
102 Self::ProviderAdminTerminal => "provider-admin-terminal",
103 Self::JoinerTerminal => "joiner-terminal",
104 Self::CleanupReceipt => "cleanup-receipt",
105 Self::CleanupActivation => "cleanup-activation",
106 }
107 }
108
109 fn producer(self) -> DeviceJoinRole {
112 match self {
113 Self::ProviderAccessRequest
114 | Self::RegistrationRequest
115 | Self::Readiness
116 | Self::JoinerTerminal => DeviceJoinRole::Joiner,
117 Self::ProviderAdmissionApproval
118 | Self::ProviderReadyBootstrap
119 | Self::ProviderAdmissionCompletion
120 | Self::ProviderAdminTerminal => DeviceJoinRole::ProviderAdministrator,
121 Self::ProvisionalBootstrap
122 | Self::Activation
123 | Self::Abandonment
124 | Self::Cancellation
125 | Self::CleanupReceipt
126 | Self::CleanupActivation => DeviceJoinRole::Owner,
127 }
128 }
129
130 fn of(action: &DeviceJoinAction) -> Option<Self> {
135 match action {
136 DeviceJoinAction::TransferProviderAccessRequest(_) => Some(Self::ProviderAccessRequest),
137 DeviceJoinAction::TransferProviderAdmissionApproval(_) => {
138 Some(Self::ProviderAdmissionApproval)
139 }
140 DeviceJoinAction::TransferRegistrationRequest(_) => Some(Self::RegistrationRequest),
141 DeviceJoinAction::TransferProvisionalBootstrap(_) => Some(Self::ProvisionalBootstrap),
142 DeviceJoinAction::TransferProviderReadyBootstrap(_) => {
143 Some(Self::ProviderReadyBootstrap)
144 }
145 DeviceJoinAction::TransferReadiness(_) => Some(Self::Readiness),
146 DeviceJoinAction::TransferProviderAdmissionCompletion(_) => {
147 Some(Self::ProviderAdmissionCompletion)
148 }
149 DeviceJoinAction::TransferActivation(_) => Some(Self::Activation),
150 DeviceJoinAction::TransferAbandonment(_) => Some(Self::Abandonment),
151 DeviceJoinAction::TransferCancellation(_) => Some(Self::Cancellation),
152 DeviceJoinAction::TransferProviderAdminTerminal(_) => Some(Self::ProviderAdminTerminal),
153 DeviceJoinAction::TransferJoinerTerminal(_) => Some(Self::JoinerTerminal),
154 DeviceJoinAction::TransferCleanupReceipt(_) => Some(Self::CleanupReceipt),
155 DeviceJoinAction::TransferCleanupActivation(_) => Some(Self::CleanupActivation),
156 DeviceJoinAction::TransferOffer(_)
157 | DeviceJoinAction::CompleteJoin(_)
158 | DeviceJoinAction::CompleteCleanup(_)
159 | DeviceJoinAction::ResumeOperation { .. } => None,
160 }
161 }
162}
163
164pub trait DeviceJoinArtifact: Sized {
167 const KIND: DeviceJoinTransportKind;
168
169 fn from_action(action: DeviceJoinAction) -> Option<Self>;
170}
171
172macro_rules! device_join_artifact {
173 ($type:ty, $kind:ident, $variant:ident) => {
174 impl DeviceJoinArtifact for $type {
175 const KIND: DeviceJoinTransportKind = DeviceJoinTransportKind::$kind;
176
177 fn from_action(action: DeviceJoinAction) -> Option<Self> {
178 match action {
179 DeviceJoinAction::$variant(value) => Some(value),
180 _ => None,
181 }
182 }
183 }
184 };
185}
186
187device_join_artifact!(
188 DeviceProviderAccessRequest,
189 ProviderAccessRequest,
190 TransferProviderAccessRequest
191);
192device_join_artifact!(
193 DeviceProviderAdmissionApproval,
194 ProviderAdmissionApproval,
195 TransferProviderAdmissionApproval
196);
197device_join_artifact!(
198 DeviceRegistrationRequest,
199 RegistrationRequest,
200 TransferRegistrationRequest
201);
202device_join_artifact!(
203 ProvisionalDeviceBootstrap,
204 ProvisionalBootstrap,
205 TransferProvisionalBootstrap
206);
207device_join_artifact!(
208 crate::sync::store::ProviderReadyDeviceBootstrap,
209 ProviderReadyBootstrap,
210 TransferProviderReadyBootstrap
211);
212device_join_artifact!(DeviceJoinReadiness, Readiness, TransferReadiness);
213device_join_artifact!(
214 DeviceProviderAdmissionCompletion,
215 ProviderAdmissionCompletion,
216 TransferProviderAdmissionCompletion
217);
218device_join_artifact!(DeviceJoinActivation, Activation, TransferActivation);
219device_join_artifact!(DeviceJoinAbandonment, Abandonment, TransferAbandonment);
220device_join_artifact!(DeviceJoinCancellation, Cancellation, TransferCancellation);
221device_join_artifact!(
222 ProviderAdminJoinTerminal,
223 ProviderAdminTerminal,
224 TransferProviderAdminTerminal
225);
226device_join_artifact!(JoinerJoinTerminal, JoinerTerminal, TransferJoinerTerminal);
227device_join_artifact!(
228 DeviceJoinCleanupReceipt,
229 CleanupReceipt,
230 TransferCleanupReceipt
231);
232device_join_artifact!(
233 DeviceJoinCleanupActivation,
234 CleanupActivation,
235 TransferCleanupActivation
236);
237
238#[derive(Clone, Debug, Serialize, Deserialize)]
246#[serde(deny_unknown_fields)]
247pub struct DeviceJoinTransportParams {
248 pub version: u32,
249 pub attempt_namespace: String,
250 pub slots: BTreeMap<DeviceJoinTransportKind, ObjectSlot>,
251 #[serde(with = "seal_key")]
252 pub seal_key: MasterKeyring,
253}
254
255mod seal_key {
258 use super::MasterKeyring;
259 use serde::{Deserialize, Deserializer, Serializer};
260
261 pub(super) fn serialize<S: Serializer>(
262 keyring: &MasterKeyring,
263 serializer: S,
264 ) -> Result<S::Ok, S::Error> {
265 serializer.serialize_str(&keyring.to_serialized())
266 }
267
268 pub(super) fn deserialize<'de, D: Deserializer<'de>>(
269 deserializer: D,
270 ) -> Result<MasterKeyring, D::Error> {
271 let encoded = String::deserialize(deserializer)?;
272 MasterKeyring::from_serialized(&encoded).map_err(serde::de::Error::custom)
273 }
274}
275
276impl DeviceJoinTransportParams {
277 async fn allocate(
280 storage: &dyn SyncStorage,
281 offer: &DeviceJoinOffer,
282 ) -> Result<Self, DeviceJoinTransportError> {
283 let attempt_namespace = attempt_namespace(offer.attempt_id);
284 let context = slot_context(offer.store_root.store_root_hash);
285 let mut slots = BTreeMap::new();
286 for kind in DeviceJoinTransportKind::ALL {
287 let slot = storage
288 .allocate_protocol_slot(
289 &context,
290 &semantic_prefix(&attempt_namespace, kind),
291 ".json",
292 )
293 .await?;
294 slots.insert(kind, slot);
295 }
296 Ok(Self {
297 version: STORE_PROTOCOL_VERSION,
298 attempt_namespace,
299 slots,
300 seal_key: MasterKeyring::generate(),
301 })
302 }
303
304 fn slot(&self, kind: DeviceJoinTransportKind) -> Result<&ObjectSlot, DeviceJoinTransportError> {
305 self.slots
306 .get(&kind)
307 .ok_or(DeviceJoinTransportError::MissingSlot { kind })
308 }
309
310 fn validate_for(&self, offer: &DeviceJoinOffer) -> Result<(), DeviceJoinTransportError> {
311 if self.version != STORE_PROTOCOL_VERSION
312 || self.attempt_namespace != attempt_namespace(offer.attempt_id)
313 {
314 return Err(DeviceJoinTransportError::BundleMismatch);
315 }
316 let context = slot_context(offer.store_root.store_root_hash);
317 for kind in DeviceJoinTransportKind::ALL {
318 context.validate_slot(
319 self.slot(kind)?,
320 &semantic_prefix(&self.attempt_namespace, kind),
321 )?;
322 }
323 Ok(())
324 }
325}
326
327#[derive(Clone, Debug, Serialize, Deserialize)]
331#[serde(deny_unknown_fields)]
332pub struct DeviceJoinOfferBundle {
333 pub version: u32,
334 pub offer: DeviceJoinOffer,
335 pub transport: DeviceJoinTransportParams,
336}
337
338impl DeviceJoinOfferBundle {
339 pub async fn allocate(
342 storage: &dyn SyncStorage,
343 offer: DeviceJoinOffer,
344 ) -> Result<Self, DeviceJoinTransportError> {
345 let transport = DeviceJoinTransportParams::allocate(storage, &offer).await?;
346 Ok(Self {
347 version: STORE_PROTOCOL_VERSION,
348 offer,
349 transport,
350 })
351 }
352
353 pub fn to_bytes(&self) -> Vec<u8> {
354 serde_json::to_vec(self).expect("device join offer bundle serialization cannot fail")
355 }
356
357 pub fn from_bytes(bytes: &[u8]) -> Result<Self, DeviceJoinTransportError> {
358 let bundle: Self = serde_json::from_slice(bytes)?;
359 if bundle.version != STORE_PROTOCOL_VERSION {
360 return Err(DeviceJoinTransportError::BundleMismatch);
361 }
362 bundle.transport.validate_for(&bundle.offer)?;
363 Ok(bundle)
364 }
365}
366
367#[derive(Clone, Debug, PartialEq, Eq)]
370pub enum DeviceJoinStep<T> {
371 Continue(T),
372 Abandoned(DeviceJoinAbandonment),
373}
374
375#[derive(Clone, Debug, PartialEq, Eq)]
377pub enum DeviceJoinDriveOutcome {
378 Activated(DeviceJoinActivation),
379 Abandoned(DeviceJoinAbandonment),
380}
381
382#[derive(Clone, Copy, Debug, PartialEq, Eq)]
385pub struct DeviceJoinTransportTiming {
386 pub poll: Duration,
387 pub deadline: Duration,
388}
389
390#[derive(Debug, thiserror::Error)]
392pub enum DeviceJoinTransportError {
393 #[error("storage: {0}")]
394 Storage(#[from] StorageError),
395 #[error("device join: {0}")]
396 DeviceJoin(#[from] DeviceJoinError),
397 #[error("transport artifact is not valid JSON: {0}")]
398 Malformed(#[from] serde_json::Error),
399 #[error("transport artifact could not be unsealed: {0}")]
400 Unsealable(#[from] SealError),
401 #[error("the offer bundle does not describe this attempt's transport")]
402 BundleMismatch,
403 #[error("this attempt's transport has no {kind:?} slot")]
404 MissingSlot { kind: DeviceJoinTransportKind },
405 #[error("{0:?} carries nothing for the transport to deliver")]
409 NotTransferable(Box<DeviceJoinAction>),
410 #[error("a {kind:?} artifact is the {role:?}'s to publish, not this device's")]
412 WrongProducer {
413 kind: DeviceJoinTransportKind,
414 role: DeviceJoinRole,
415 },
416 #[error("the {kind:?} slot already holds a different artifact")]
420 ArtifactConflict { kind: DeviceJoinTransportKind },
421 #[error("the {kind:?} slot was written concurrently with different bytes")]
424 SlotConflict { kind: DeviceJoinTransportKind },
425 #[error("the {kind:?} slot holds an artifact of another kind")]
427 KindMismatch { kind: DeviceJoinTransportKind },
428 #[error("the {producer:?} never published its {kind:?} artifact")]
429 Timeout {
430 kind: DeviceJoinTransportKind,
431 producer: DeviceJoinRole,
432 },
433}
434
435#[derive(Clone, Copy, Debug, PartialEq, Eq)]
438pub struct DeviceJoinRoles {
439 pub owner: bool,
440 pub provider_administrator: bool,
441 pub joiner: bool,
442}
443
444impl DeviceJoinRoles {
445 pub fn joiner() -> Self {
446 Self {
447 owner: false,
448 provider_administrator: false,
449 joiner: true,
450 }
451 }
452
453 pub fn admitting(owner: bool, provider_administrator: bool) -> Self {
454 Self {
455 owner,
456 provider_administrator,
457 joiner: false,
458 }
459 }
460
461 fn holds(self, role: DeviceJoinRole) -> bool {
462 match role {
463 DeviceJoinRole::Owner => self.owner,
464 DeviceJoinRole::ProviderAdministrator => self.provider_administrator,
465 DeviceJoinRole::Joiner => self.joiner,
466 }
467 }
468
469 fn any(self) -> bool {
470 self.owner || self.provider_administrator || self.joiner
471 }
472}
473
474pub struct DeviceJoinTransport<'a> {
476 storage: &'a dyn SyncStorage,
477 params: &'a DeviceJoinTransportParams,
478 store_root_hash: ObjectHash,
479 seal: EncryptionService,
480 roles: DeviceJoinRoles,
481}
482
483impl<'a> DeviceJoinTransport<'a> {
484 pub fn open(
488 storage: &'a dyn SyncStorage,
489 bundle: &'a DeviceJoinOfferBundle,
490 roles: DeviceJoinRoles,
491 ) -> Result<Self, DeviceJoinTransportError> {
492 bundle.transport.validate_for(&bundle.offer)?;
493 Ok(Self {
494 storage,
495 params: &bundle.transport,
496 store_root_hash: bundle.offer.store_root.store_root_hash,
497 seal: EncryptionService::from(bundle.transport.seal_key.clone()),
498 roles,
499 })
500 }
501
502 pub async fn publish(&self, action: &DeviceJoinAction) -> Result<(), DeviceJoinTransportError> {
511 let kind = DeviceJoinTransportKind::of(action)
512 .ok_or_else(|| DeviceJoinTransportError::NotTransferable(Box::new(action.clone())))?;
513 let producer = kind.producer();
514 if !self.roles.holds(producer) {
515 return Err(DeviceJoinTransportError::WrongProducer {
516 kind,
517 role: producer,
518 });
519 }
520 if let Some(existing) = self.read(kind).await? {
521 return if existing == *action {
522 Ok(())
523 } else {
524 Err(DeviceJoinTransportError::ArtifactConflict { kind })
525 };
526 }
527 let sealed = self
528 .seal
529 .seal_app_data(&serde_json::to_vec(action)?, &self.seal_aad(kind));
530 let prepared = self.storage.prepare_protocol_object(
531 &slot_context(self.store_root_hash),
532 self.params.slot(kind)?.clone(),
533 &self.semantic_prefix(kind),
534 sealed,
535 )?;
536 self.storage
537 .create_protocol_object(&prepared)
538 .await
539 .map_err(|error| match error {
540 StorageError::SlotCollision(_) => DeviceJoinTransportError::SlotConflict { kind },
541 other => other.into(),
542 })
543 }
544
545 pub async fn read(
547 &self,
548 kind: DeviceJoinTransportKind,
549 ) -> Result<Option<DeviceJoinAction>, DeviceJoinTransportError> {
550 let sealed = match self
551 .storage
552 .read_protocol_slot(
553 &slot_context(self.store_root_hash),
554 self.params.slot(kind)?,
555 &self.semantic_prefix(kind),
556 )
557 .await
558 {
559 Ok((sealed, _)) => sealed,
560 Err(StorageError::NotFound(_)) => return Ok(None),
561 Err(error) => return Err(error.into()),
562 };
563 let opened = self.seal.open_app_data(&sealed, &self.seal_aad(kind))?;
564 let action: DeviceJoinAction = serde_json::from_slice(&opened)?;
565 if DeviceJoinTransportKind::of(&action) != Some(kind) {
566 return Err(DeviceJoinTransportError::KindMismatch { kind });
567 }
568 Ok(Some(action))
569 }
570
571 pub async fn await_artifact<T: DeviceJoinArtifact>(
575 &self,
576 timing: DeviceJoinTransportTiming,
577 ) -> Result<T, DeviceJoinTransportError> {
578 let kind = T::KIND;
579 let polled = tokio::time::timeout(timing.deadline, async {
580 loop {
581 if let Some(action) = self.read(kind).await? {
582 return T::from_action(action)
583 .ok_or(DeviceJoinTransportError::KindMismatch { kind });
584 }
585 tokio::time::sleep(timing.poll).await;
586 }
587 })
588 .await;
589 match polled {
590 Ok(artifact) => artifact,
591 Err(_) => Err(DeviceJoinTransportError::Timeout {
592 kind,
593 producer: kind.producer(),
594 }),
595 }
596 }
597
598 pub async fn await_step<T: DeviceJoinArtifact>(
606 &self,
607 timing: DeviceJoinTransportTiming,
608 ) -> Result<DeviceJoinStep<T>, DeviceJoinTransportError> {
609 let kind = T::KIND;
610 let polled = tokio::time::timeout(timing.deadline, async {
611 loop {
612 if let Some(action) = self.read(DeviceJoinTransportKind::Abandonment).await? {
613 return DeviceJoinAbandonment::from_action(action)
614 .map(DeviceJoinStep::Abandoned)
615 .ok_or(DeviceJoinTransportError::KindMismatch {
616 kind: DeviceJoinTransportKind::Abandonment,
617 });
618 }
619 if let Some(action) = self.read(kind).await? {
620 return T::from_action(action)
621 .map(DeviceJoinStep::Continue)
622 .ok_or(DeviceJoinTransportError::KindMismatch { kind });
623 }
624 tokio::time::sleep(timing.poll).await;
625 }
626 })
627 .await;
628 match polled {
629 Ok(step) => step,
630 Err(_) => Err(DeviceJoinTransportError::Timeout {
631 kind,
632 producer: kind.producer(),
633 }),
634 }
635 }
636
637 pub async fn delete_attempt_slots(&self) -> Result<(), DeviceJoinTransportError> {
647 for kind in DeviceJoinTransportKind::ALL {
648 let prepared = match self
649 .storage
650 .read_prepared_protocol_slot(
651 &slot_context(self.store_root_hash),
652 self.params.slot(kind)?,
653 &self.semantic_prefix(kind),
654 )
655 .await
656 {
657 Ok((_, prepared)) => prepared,
658 Err(StorageError::NotFound(_)) => continue,
659 Err(error) => return Err(error.into()),
660 };
661 self.storage
662 .delete_protocol_object(prepared.reference())
663 .await?;
664 }
665 Ok(())
666 }
667
668 fn semantic_prefix(&self, kind: DeviceJoinTransportKind) -> String {
669 semantic_prefix(&self.params.attempt_namespace, kind)
670 }
671
672 fn seal_aad(&self, kind: DeviceJoinTransportKind) -> Vec<u8> {
675 let prefix = self.semantic_prefix(kind);
676 let mut aad = SEAL_AAD_LABEL.to_vec();
677 aad.extend_from_slice(self.store_root_hash.as_bytes());
678 aad.extend_from_slice(&(prefix.len() as u64).to_le_bytes());
679 aad.extend_from_slice(prefix.as_bytes());
680 aad
681 }
682}
683
684fn attempt_namespace(attempt_id: DeviceJoinAttemptId) -> String {
685 format!("{TRANSPORT_ROOT}/{attempt_id}")
686}
687
688fn semantic_prefix(attempt_namespace: &str, kind: DeviceJoinTransportKind) -> String {
689 format!("{attempt_namespace}/{}", kind.slug())
690}
691
692fn slot_context(store_root_hash: ObjectHash) -> ProtocolObjectContext {
693 ProtocolObjectContext::recipient_sealed(
694 store_root_hash,
695 ProtocolObjectDomain::DeviceJoinTransport,
696 )
697}
698
699pub enum DeviceJoinApprovalPolicy<'a> {
701 AutoApproveSelfIssued,
706 Ask(&'a (dyn Fn(&DeviceProviderAccessRequest) -> DeviceJoinApproval + Send + Sync)),
708}
709
710#[derive(Clone, Copy, Debug, PartialEq, Eq)]
711pub enum DeviceJoinApproval {
712 Approve,
713 Refuse,
714}
715
716pub async fn drive_device_join(
728 store: &Store,
729 identity_signer: &UserKeypair,
730 bundle: &DeviceJoinOfferBundle,
731 policy: DeviceJoinApprovalPolicy<'_>,
732 access_administrator: Option<&dyn DeviceProviderAccessAdministrator>,
733 timing: DeviceJoinTransportTiming,
734) -> Result<DeviceJoinDriveOutcome, DeviceJoinTransportError> {
735 let roles = driver_roles(store, &bundle.offer).await?;
736 let transport = DeviceJoinTransport::open(&**store.storage(), bundle, roles)?;
737 let attempt_id = bundle.offer.attempt_id;
738
739 if let Some(DeviceJoinStatus::Abandoned { abandonment }) =
743 owner_status(store, attempt_id).await?
744 {
745 transport
746 .publish(&DeviceJoinAction::TransferAbandonment(abandonment.clone()))
747 .await?;
748 return Ok(DeviceJoinDriveOutcome::Abandoned(abandonment));
749 }
750
751 if roles.provider_administrator {
758 let approval = match admin_status(store, attempt_id).await? {
759 Some(DeviceJoinStatus::AwaitingRegistrationRequest { approval }) => Some(approval),
760 Some(
761 DeviceJoinStatus::AwaitingChallengePublication { .. }
762 | DeviceJoinStatus::AwaitingReadiness { .. }
763 | DeviceJoinStatus::AwaitingProviderCompletion { .. }
764 | DeviceJoinStatus::AwaitingActivation { .. },
765 ) => None,
766 _ => {
767 let request = transport
768 .await_artifact::<DeviceProviderAccessRequest>(timing)
769 .await?;
770 approve_access_request(store, roles, &bundle.offer, &request, &policy).await?;
771 Some(
772 store
773 .authorize_device_provider_access(
774 identity_signer,
775 request,
776 access_administrator,
777 )
778 .await?,
779 )
780 }
781 };
782 if let Some(approval) = approval {
783 transport
784 .publish(&DeviceJoinAction::TransferProviderAdmissionApproval(
785 approval,
786 ))
787 .await?;
788 }
789 }
790
791 if roles.owner {
792 let provisional = match owner_status(store, attempt_id).await? {
793 Some(DeviceJoinStatus::AwaitingChallengePublication { bootstrap }) => Some(bootstrap),
794 Some(
795 DeviceJoinStatus::AwaitingActivation { .. }
796 | DeviceJoinStatus::AwaitingCompletion { .. },
797 ) => None,
798 Some(DeviceJoinStatus::AwaitingBootstrap { request }) => Some(
799 store
800 .accept_device_registration_request(identity_signer, request)
801 .await?,
802 ),
803 _ => {
804 let request = transport
805 .await_artifact::<DeviceRegistrationRequest>(timing)
806 .await?;
807 Some(
808 store
809 .accept_device_registration_request(identity_signer, request)
810 .await?,
811 )
812 }
813 };
814 if let Some(provisional) = provisional {
815 transport
816 .publish(&DeviceJoinAction::TransferProvisionalBootstrap(provisional))
817 .await?;
818 }
819 }
820
821 if roles.provider_administrator {
822 let ready = match admin_status(store, attempt_id).await? {
823 Some(DeviceJoinStatus::AwaitingReadiness { bootstrap }) => Some(bootstrap),
824 Some(
825 DeviceJoinStatus::AwaitingProviderCompletion { .. }
826 | DeviceJoinStatus::AwaitingActivation { .. },
827 ) => None,
828 Some(DeviceJoinStatus::AwaitingChallengePublication { bootstrap }) => {
829 Some(store.publish_device_provider_challenge(bootstrap).await?)
830 }
831 _ => {
832 let provisional = transport
833 .await_artifact::<ProvisionalDeviceBootstrap>(timing)
834 .await?;
835 Some(store.publish_device_provider_challenge(provisional).await?)
836 }
837 };
838 if let Some(ready) = ready {
839 transport
840 .publish(&DeviceJoinAction::TransferProviderReadyBootstrap(ready))
841 .await?;
842 }
843
844 let completion = match admin_status(store, attempt_id).await? {
845 Some(DeviceJoinStatus::AwaitingActivation { completion }) => completion,
846 Some(DeviceJoinStatus::AwaitingProviderCompletion { readiness }) => {
847 store
848 .complete_device_provider_admission(identity_signer, readiness)
849 .await?
850 }
851 _ => {
852 let readiness = transport
853 .await_artifact::<DeviceJoinReadiness>(timing)
854 .await?;
855 store
856 .complete_device_provider_admission(identity_signer, readiness)
857 .await?
858 }
859 };
860 transport
861 .publish(&DeviceJoinAction::TransferProviderAdmissionCompletion(
862 completion,
863 ))
864 .await?;
865 }
866
867 if !roles.owner {
868 return Ok(DeviceJoinDriveOutcome::Activated(
869 transport
870 .await_artifact::<DeviceJoinActivation>(timing)
871 .await?,
872 ));
873 }
874
875 let activation = match owner_status(store, attempt_id).await? {
876 Some(DeviceJoinStatus::AwaitingCompletion { activation }) => activation,
877 Some(DeviceJoinStatus::AwaitingActivation { completion }) => {
878 store
879 .finalize_device_join(identity_signer, completion)
880 .await?
881 }
882 _ => {
883 let completion = transport
884 .await_artifact::<DeviceProviderAdmissionCompletion>(timing)
885 .await?;
886 store
887 .finalize_device_join(identity_signer, completion)
888 .await?
889 }
890 };
891 transport
892 .publish(&DeviceJoinAction::TransferActivation(activation.clone()))
893 .await?;
894 Ok(DeviceJoinDriveOutcome::Activated(activation))
895}
896
897pub async fn abandon_device_join_via_transport(
901 store: &Store,
902 identity_signer: &UserKeypair,
903 bundle: &DeviceJoinOfferBundle,
904) -> Result<DeviceJoinAbandonment, DeviceJoinTransportError> {
905 let roles = driver_roles(store, &bundle.offer).await?;
906 let transport = DeviceJoinTransport::open(&**store.storage(), bundle, roles)?;
907 let abandonment = store
908 .abandon_device_join(identity_signer, bundle.offer.clone())
909 .await?;
910 transport
911 .publish(&DeviceJoinAction::TransferAbandonment(abandonment.clone()))
912 .await?;
913 Ok(abandonment)
914}
915
916async fn admin_status(
917 store: &Store,
918 attempt_id: DeviceJoinAttemptId,
919) -> Result<Option<DeviceJoinStatus>, DeviceJoinTransportError> {
920 Ok(store
921 .database()
922 .device_join_status(attempt_id, DeviceJoinRole::ProviderAdministrator)
923 .await?)
924}
925
926async fn owner_status(
927 store: &Store,
928 attempt_id: DeviceJoinAttemptId,
929) -> Result<Option<DeviceJoinStatus>, DeviceJoinTransportError> {
930 Ok(store
931 .database()
932 .device_join_status(attempt_id, DeviceJoinRole::Owner)
933 .await?)
934}
935
936pub async fn cancel_device_join_via_transport(
949 store: &Store,
950 identity_signer: &UserKeypair,
951 bundle: &DeviceJoinOfferBundle,
952 attempt: crate::sync::store_commit::DeviceJoinAttemptRef,
953 timing: DeviceJoinTransportTiming,
954) -> Result<DeviceJoinCleanupActivation, DeviceJoinTransportError> {
955 let roles = driver_roles(store, &bundle.offer).await?;
956 let transport = DeviceJoinTransport::open(&**store.storage(), bundle, roles)?;
957 let attempt_id = bundle.offer.attempt_id;
958
959 let receipt = match owner_status(store, attempt_id).await? {
960 Some(DeviceJoinStatus::CleanupActivated { activation }) => {
962 transport
963 .publish(&DeviceJoinAction::TransferCleanupActivation(
964 activation.clone(),
965 ))
966 .await?;
967 store
968 .complete_owner_device_join_cleanup(activation.clone())
969 .await?;
970 return Ok(activation);
971 }
972 Some(DeviceJoinStatus::AwaitingCleanupActivation { receipt }) => receipt,
973 _ => {
974 let cancellation =
975 cancel_and_publish(store, identity_signer, &transport, attempt_id, attempt).await?;
976 let administrator_terminal = store
977 .close_device_provider_admission(identity_signer, cancellation.clone())
978 .await?;
979 transport
980 .publish(&DeviceJoinAction::TransferProviderAdminTerminal(
981 administrator_terminal.clone(),
982 ))
983 .await?;
984 let joiner_terminal = transport
985 .await_artifact::<JoinerJoinTerminal>(timing)
986 .await?;
987 store
988 .prepare_device_join_cleanup(
989 identity_signer,
990 cancellation,
991 administrator_terminal,
992 joiner_terminal,
993 )
994 .await?
995 }
996 };
997 transport
998 .publish(&DeviceJoinAction::TransferCleanupReceipt(receipt.clone()))
999 .await?;
1000
1001 let activation = store
1002 .activate_device_join_cleanup(identity_signer, receipt)
1003 .await?;
1004 transport
1005 .publish(&DeviceJoinAction::TransferCleanupActivation(
1006 activation.clone(),
1007 ))
1008 .await?;
1009 store
1010 .complete_owner_device_join_cleanup(activation.clone())
1011 .await?;
1012 Ok(activation)
1013}
1014
1015async fn cancel_and_publish(
1019 store: &Store,
1020 identity_signer: &UserKeypair,
1021 transport: &DeviceJoinTransport<'_>,
1022 attempt_id: DeviceJoinAttemptId,
1023 attempt: crate::sync::store_commit::DeviceJoinAttemptRef,
1024) -> Result<DeviceJoinCancellation, DeviceJoinTransportError> {
1025 let cancellation = match owner_status(store, attempt_id).await? {
1026 Some(
1027 DeviceJoinStatus::CleanupPending { cancellation, .. }
1028 | DeviceJoinStatus::CleanupReceiptCreatePending { cancellation, .. },
1029 ) => cancellation,
1030 _ => store.cancel_device_join(identity_signer, attempt).await?,
1031 };
1032 transport
1033 .publish(&DeviceJoinAction::TransferCancellation(
1034 cancellation.clone(),
1035 ))
1036 .await?;
1037 Ok(cancellation)
1038}
1039
1040async fn driver_roles(
1041 store: &Store,
1042 offer: &DeviceJoinOffer,
1043) -> Result<DeviceJoinRoles, DeviceJoinTransportError> {
1044 let local = store
1045 .database()
1046 .local_activated_registration_ref()
1047 .await
1048 .map_err(|error| DeviceJoinError::Store(error.to_string()))?
1049 .ok_or(DeviceJoinError::ActiveDeviceRequired)?;
1050 let roles = DeviceJoinRoles::admitting(
1051 local == offer.owner_registration,
1052 local == offer.provider_admin.administrator,
1053 );
1054 if !roles.any() {
1055 return Err(DeviceJoinError::ActiveDeviceRequired.into());
1056 }
1057 Ok(roles)
1058}
1059
1060async fn approve_access_request(
1061 store: &Store,
1062 roles: DeviceJoinRoles,
1063 offer: &DeviceJoinOffer,
1064 request: &DeviceProviderAccessRequest,
1065 policy: &DeviceJoinApprovalPolicy<'_>,
1066) -> Result<(), DeviceJoinTransportError> {
1067 let approval = match policy {
1068 DeviceJoinApprovalPolicy::AutoApproveSelfIssued => {
1069 if self_issued(store, roles, offer).await? && *request.offer == *offer {
1070 DeviceJoinApproval::Approve
1071 } else {
1072 DeviceJoinApproval::Refuse
1073 }
1074 }
1075 DeviceJoinApprovalPolicy::Ask(ask) => ask(request),
1076 };
1077 match approval {
1078 DeviceJoinApproval::Approve => Ok(()),
1079 DeviceJoinApproval::Refuse => Err(DeviceJoinError::OfferMismatch.into()),
1080 }
1081}
1082
1083async fn self_issued(
1092 store: &Store,
1093 roles: DeviceJoinRoles,
1094 offer: &DeviceJoinOffer,
1095) -> Result<bool, DeviceJoinTransportError> {
1096 if !roles.owner {
1097 return Ok(false);
1098 }
1099 Ok(store
1100 .database()
1101 .device_join_status(offer.attempt_id, DeviceJoinRole::Owner)
1102 .await?
1103 .is_some())
1104}