Skip to main content

coven_database/store/store_session/
device_join_challenges.rs

1use crate::*;
2use coven_protocol::provider::{
3    DeviceJoinChallengePublicationProgress, DeviceJoinChallengePublicationRecord,
4};
5
6use super::*;
7
8impl StoreSession<'_> {
9    fn prepare_device_join_challenge_publication(
10        &self,
11        key: &str,
12        value: &str,
13        prepared: &coven_protocol::provider::DeviceJoinChallengePublicationRecord,
14    ) -> Result<coven_protocol::provider::DeviceJoinChallengePublicationRecord, DbError> {
15        let actual = crate::store::store_session::StoreRecords::new(self.conn, self.store_dir)
16            .begin_protocol_state(key, value)?;
17        let actual: coven_protocol::provider::DeviceJoinChallengePublicationRecord =
18            serde_json::from_str(&actual).map_err(|error| {
19                DbError::context("parse device join challenge publication", error)
20            })?;
21        if actual.challenge != prepared.challenge {
22            return Err(DbError::Message(
23                "device join challenge probe id was reused with different bytes".to_string(),
24            ));
25        }
26        Ok(actual)
27    }
28
29    fn publish_device_join_challenge(
30        &self,
31        key: &str,
32        authorization: coven_protocol::provider::DeviceJoinChallengePublicationAuthorization,
33        challenge: coven_protocol::provider::CrossPrincipalProbeChallenge,
34    ) -> Result<(), DbError> {
35        use coven_protocol::provider::{
36            DeviceJoinChallengePublicationProgress, DeviceJoinChallengePublicationRecord,
37        };
38
39        let previous_json =
40            crate::store::store_session::StoreRecords::new(self.conn, self.store_dir)
41                .required_protocol_state(key)?;
42        let previous: DeviceJoinChallengePublicationRecord = serde_json::from_str(&previous_json)
43            .map_err(|error| {
44            DbError::context("parse device join challenge publication", error)
45        })?;
46        if previous.challenge != challenge {
47            return Err(DbError::Message(
48                "device join challenge publication differs from prepared bytes".to_string(),
49            ));
50        }
51        match &previous.progress {
52            DeviceJoinChallengePublicationProgress::Prepared => {
53                let next = DeviceJoinChallengePublicationRecord {
54                    challenge,
55                    progress: DeviceJoinChallengePublicationProgress::Published { authorization },
56                };
57                let next_json = serde_json::to_string(&next).map_err(|error| {
58                    DbError::context("serialize device join challenge publication", error)
59                })?;
60                if !crate::store::store_session::StoreRecords::new(self.conn, self.store_dir)
61                    .compare_exchange_protocol_state(key, &previous_json, &next_json)?
62                {
63                    return Err(DbError::Message(
64                        "device join challenge publication lost its exact predecessor".to_string(),
65                    ));
66                }
67            }
68            DeviceJoinChallengePublicationProgress::Published {
69                authorization: existing,
70            } if existing == &authorization => {}
71            DeviceJoinChallengePublicationProgress::Published { .. } => {
72                return Err(DbError::Message(
73                    "device join challenge publication authorization changed".to_string(),
74                ));
75            }
76        }
77        Ok(())
78    }
79}
80
81impl StoreDatabase {
82    pub async fn prepare_device_join_challenge_publication(
83        &self,
84        challenge: coven_protocol::provider::CrossPrincipalProbeChallenge,
85    ) -> Result<coven_protocol::provider::DeviceJoinChallengePublicationRecord, DbError> {
86        let key = format!(
87            "device_join_challenge_publication/{}",
88            hex::encode(challenge.probe_id.as_bytes())
89        );
90        let prepared = DeviceJoinChallengePublicationRecord {
91            challenge,
92            progress: DeviceJoinChallengePublicationProgress::Prepared,
93        };
94        let value = serde_json::to_string(&prepared).map_err(|error| {
95            DbError::context("serialize device join challenge publication", error)
96        })?;
97        self.call_store(move |session| {
98            session.prepare_device_join_challenge_publication(&key, &value, &prepared)
99        })
100        .await
101    }
102
103    pub async fn publish_device_join_challenge(
104        &self,
105        authorization: coven_protocol::provider::DeviceJoinChallengePublicationAuthorization,
106        challenge: coven_protocol::provider::CrossPrincipalProbeChallenge,
107    ) -> Result<(), DbError> {
108        let key = format!(
109            "device_join_challenge_publication/{}",
110            hex::encode(challenge.probe_id.as_bytes())
111        );
112        self.call_store(move |session| {
113            session.publish_device_join_challenge(&key, authorization, challenge)
114        })
115        .await
116    }
117}
118
119#[async_trait::async_trait]
120impl coven_protocol::provider::DeviceJoinChallengePublicationJournal for StoreDatabase {
121    async fn prepare(
122        &self,
123        challenge: &coven_protocol::provider::CrossPrincipalProbeChallenge,
124    ) -> Result<
125        coven_protocol::provider::DeviceJoinChallengePublicationRecord,
126        coven_protocol::objects::StorageError,
127    > {
128        self.prepare_device_join_challenge_publication(challenge.clone())
129            .await
130            .map_err(|error| {
131                coven_protocol::objects::StorageError::backend(
132                    coven_protocol::objects::StorageBackendFailure::Internal,
133                    "prepare device join challenge publication",
134                    error,
135                )
136            })
137    }
138
139    async fn claim_published(
140        &self,
141        authorization: &coven_protocol::provider::DeviceJoinChallengePublicationAuthorization,
142        challenge: &coven_protocol::provider::CrossPrincipalProbeChallenge,
143    ) -> Result<(), coven_protocol::objects::StorageError> {
144        self.publish_device_join_challenge(authorization.clone(), challenge.clone())
145            .await
146            .map_err(|error| {
147                coven_protocol::objects::StorageError::backend(
148                    coven_protocol::objects::StorageBackendFailure::Internal,
149                    "publish device join challenge",
150                    error,
151                )
152            })
153    }
154}