core_crypto/proteus/session.rs
1use core_crypto_keystore::{Transaction, entities::ProteusSession, traits::FetchFromDatabase};
2use proteus_wasm::{keys::PreKeyBundle, message::Envelope, session::Session};
3
4use super::{ProteusCentral, ProteusConversationSession};
5use crate::{KeystoreError, LeafError, ProteusError, Result};
6
7impl ProteusCentral {
8 /// Creates a new session from a prekey
9 pub async fn session_from_prekey(
10 &mut self,
11 session_id: &str,
12 key: &[u8],
13 ) -> 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).await?;
86 }
87 Ok(())
88 }
89
90 pub(crate) async fn session_save_by_ref(
91 transaction: &Transaction,
92 session: &ProteusConversationSession,
93 ) -> Result<()> {
94 let db_session = ProteusSession {
95 id: session.identifier().to_string(),
96 session: session
97 .session
98 .serialise()
99 .map_err(ProteusError::wrap("serializing session"))?,
100 };
101 transaction
102 .save(db_session)
103 .await
104 .map_err(KeystoreError::wrap("saving proteus session"))?;
105 Ok(())
106 }
107
108 /// Deletes a session in the store
109 pub(crate) async fn session_delete(&mut self, transaction: &Transaction, session_id: &str) -> Result<()> {
110 if transaction.remove_borrowed::<ProteusSession>(session_id).await.is_ok() {
111 let _ = self.proteus_sessions.remove(session_id);
112 }
113 Ok(())
114 }
115
116 /// Session accessor
117 pub(crate) async fn session(
118 &mut self,
119 session_id: &str,
120 keystore: &impl FetchFromDatabase,
121 ) -> Result<Option<&mut ProteusConversationSession>> {
122 self.proteus_sessions.get_or_fetch(session_id, keystore).await
123 }
124
125 /// Session exists
126 pub(crate) async fn session_exists(&mut self, session_id: &str, keystore: &impl FetchFromDatabase) -> bool {
127 self.session(session_id, keystore).await.ok().flatten().is_some()
128 }
129
130 /// Proteus Session local hex-encoded fingerprint
131 ///
132 /// # Errors
133 /// When the session is not found
134 pub(crate) async fn fingerprint_local(
135 &mut self,
136 session_id: &str,
137 keystore: &impl FetchFromDatabase,
138 ) -> Result<String> {
139 let session = self
140 .session(session_id, keystore)
141 .await?
142 .ok_or(LeafError::ConversationNotFound(session_id.as_bytes().into()))
143 .map_err(ProteusError::wrap("getting session"))?;
144 Ok(session.fingerprint_local())
145 }
146
147 /// Proteus Session remote hex-encoded fingerprint
148 ///
149 /// # Errors
150 /// When the session is not found
151 pub(crate) async fn fingerprint_remote(
152 &mut self,
153 session_id: &str,
154 keystore: &impl FetchFromDatabase,
155 ) -> Result<String> {
156 let session = self
157 .session(session_id, keystore)
158 .await?
159 .ok_or(LeafError::ConversationNotFound(session_id.as_bytes().into()))
160 .map_err(ProteusError::wrap("getting session"))?;
161 Ok(session.fingerprint_remote())
162 }
163}