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, LeafError, ProteusError, 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(LeafError::ConversationNotFound(session_id.as_bytes().into()))
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(LeafError::ConversationNotFound(session_id.as_bytes().into()))
164 .map_err(ProteusError::wrap("getting session"))?;
165 Ok(session.fingerprint_remote())
166 }
167}