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