Skip to main content

core_crypto/proteus/
session.rs

1use core_crypto_keystore::{
2    Transaction,
3    entities::ProteusSession,
4    traits::{EntityDatabaseMutation as _, EntityDeleteBorrowed, FetchFromDatabase},
5};
6use proteus_wasm::{keys::PreKeyBundle, message::Envelope, session::Session};
7
8use super::{ProteusCentral, ProteusConversationSession};
9use crate::{KeystoreError, ProteusError, ProteusErrorKind, Result};
10
11impl ProteusCentral {
12    /// Creates a new session from a prekey
13    pub async fn session_from_prekey(
14        &mut self,
15        session_id: &str,
16        key: &[u8],
17    ) -> Result<&mut ProteusConversationSession> {
18        let prekey = PreKeyBundle::deserialise(key).map_err(ProteusError::wrap("deserializing prekey bundle"))?;
19        // Note on the `::<>` turbofish below:
20        //
21        // `init_from_prekey` returns an error type which is parametric over some wrapped `E`,
22        // because one variant (not relevant to this particular operation) wraps an error type based
23        // on a parameter of a different function entirely.
24        //
25        // Rust complains here, because it can't figure out what type that `E` should be. After all, it's
26        // not inferrable from this function call! It is also entirely irrelevant in this case.
27        //
28        // We can derive two general rules about error-handling in Rust from this example:
29        //
30        // 1. It's better to make smaller error types where possible, encapsulating fallible operations with their own
31        //    error variants, and then wrapping those errors where required, as opposed to creating giant catch-all
32        //    errors. Doing so also has knock-on benefits with regard to tracing the precise origin of the error.
33        // 2. One should never make an error wrapper parametric. If you need to wrap an unknown error, it's always
34        //    better to wrap a `Box<dyn std::error::Error>` than to make your error type parametric. The allocation cost
35        //    of creating the `Box` is utterly trivial in an error-handling path, and it avoids parametric virality.
36        //    (`init_from_prekey` is itself only generic because it returns this error type with a type-parametric
37        //    variant, which the function never returns.)
38        //
39        // In this case, we have the out of band knowledge that `ProteusErrorKind` has a `#[from]` implementation
40        // for `proteus_wasm::session::Error<core_crypto_keystore::CryptoKeystoreError>` and for no other kinds
41        // of session error. So we can safely say that the type of error we are meant to catch here, and
42        // therefore pass in that otherwise-irrelevant type, to ensure that error handling works properly.
43        //
44        // Some people say that if it's stupid but it works, it's not stupid. I disagree. If it's stupid but
45        // it works, that's our cue to seek out even better, non-stupid ways to get things done. I reiterate:
46        // the actual type referred to in this turbofish is nothing but a magic incantation to make error
47        // handling work; it has no bearing on the error retured from this function. How much better would it
48        // have been if `session::Error` were not parametric and we could have avoided the turbofish entirely?
49        let proteus_session = Session::init_from_prekey::<core_crypto_keystore::CryptoKeystoreError>(
50            self.proteus_identity.clone(),
51            prekey,
52        )
53        .map_err(ProteusError::wrap("initializing session from prekey"))?;
54
55        let conversation = ProteusConversationSession {
56            identifier: session_id.into(),
57            session: proteus_session,
58        };
59
60        Ok(self.proteus_sessions.insert(conversation))
61    }
62
63    /// Creates a new proteus Session from a received message
64    pub(crate) async fn session_from_message(
65        &mut self,
66        transaction: &Transaction,
67        session_id: &str,
68        envelope: &[u8],
69    ) -> Result<(&mut ProteusConversationSession, Vec<u8>)> {
70        let message = Envelope::deserialise(envelope).map_err(ProteusError::wrap("deserialising envelope"))?;
71        let (session, payload) = Session::init_from_message(self.proteus_identity.clone(), transaction, &message)
72            .await
73            .map_err(ProteusError::wrap("initializing session from message"))?;
74
75        let conversation = ProteusConversationSession {
76            identifier: session_id.into(),
77            session,
78        };
79
80        Ok((self.proteus_sessions.insert(conversation), payload))
81    }
82
83    /// Persists a session in store
84    ///
85    /// **Note**: This isn't usually needed as persisting sessions happens automatically when decrypting/encrypting
86    /// messages and initializing Sessions
87    pub(crate) async fn session_save(&mut self, transaction: &Transaction, session_id: &str) -> Result<()> {
88        if let Some(session) = self.proteus_sessions.get_or_fetch(session_id, transaction).await? {
89            Self::session_save_by_ref(transaction, session).await?;
90        }
91        Ok(())
92    }
93
94    pub(crate) async fn session_save_by_ref(
95        transaction: &Transaction,
96        session: &ProteusConversationSession,
97    ) -> Result<()> {
98        ProteusSession {
99            id: session.identifier().to_string(),
100            session: session
101                .session
102                .serialise()
103                .map_err(ProteusError::wrap("serializing session"))?,
104        }
105        .save(transaction)
106        .map_err(KeystoreError::wrap("saving proteus session"))?;
107        Ok(())
108    }
109
110    /// Deletes a session in the store
111    pub(crate) async fn session_delete(&mut self, transaction: &Transaction, session_id: &str) -> Result<()> {
112        if ProteusSession::delete_borrowed(transaction, session_id).is_ok() {
113            let _ = self.proteus_sessions.remove(session_id);
114        }
115        Ok(())
116    }
117
118    /// Session accessor
119    pub(crate) async fn session(
120        &mut self,
121        session_id: &str,
122        keystore: &impl FetchFromDatabase,
123    ) -> Result<Option<&mut ProteusConversationSession>> {
124        self.proteus_sessions.get_or_fetch(session_id, keystore).await
125    }
126
127    /// Session exists
128    pub(crate) async fn session_exists(&mut self, session_id: &str, keystore: &impl FetchFromDatabase) -> Result<bool> {
129        self.session(session_id, keystore)
130            .await
131            .map(|maybe_session| maybe_session.is_some())
132    }
133
134    /// Proteus Session local hex-encoded fingerprint
135    ///
136    /// # Errors
137    /// When the session is not found
138    pub(crate) async fn fingerprint_local(
139        &mut self,
140        session_id: &str,
141        keystore: &impl FetchFromDatabase,
142    ) -> Result<String> {
143        let session = self
144            .session(session_id, keystore)
145            .await?
146            .ok_or_else(|| ProteusErrorKind::SessionNotFound(session_id.to_owned()))
147            .map_err(ProteusError::wrap("getting session"))?;
148        Ok(session.fingerprint_local())
149    }
150
151    /// Proteus Session remote hex-encoded fingerprint
152    ///
153    /// # Errors
154    /// When the session is not found
155    pub(crate) async fn fingerprint_remote(
156        &mut self,
157        session_id: &str,
158        keystore: &impl FetchFromDatabase,
159    ) -> Result<String> {
160        let session = self
161            .session(session_id, keystore)
162            .await?
163            .ok_or_else(|| ProteusErrorKind::SessionNotFound(session_id.to_owned()))
164            .map_err(ProteusError::wrap("getting session"))?;
165        Ok(session.fingerprint_remote())
166    }
167}