breez_sdk_liquid/plugin/
storage.rs

1use aes::cipher::generic_array::GenericArray;
2use aes_gcm::{
3    aead::{Aead, OsRng},
4    AeadCore as _, Aes256Gcm, KeyInit as _, Nonce,
5};
6use anyhow::{bail, Result};
7
8use std::sync::{Arc, Weak};
9
10use crate::persist::Persister;
11
12#[derive(Clone)]
13pub struct PluginStorage {
14    plugin_id: String,
15    persister: Weak<Persister>,
16    cipher: Aes256Gcm,
17}
18
19#[derive(Debug, thiserror::Error)]
20pub enum PluginStorageError {
21    #[error("Could not write to storage: value has changed since last read.")]
22    DataTooOld,
23
24    #[error("Could not encrypt storage data: {err}")]
25    Encryption { err: String },
26
27    #[error("Plugin storage operation failed: {err}")]
28    Generic { err: String },
29}
30
31impl From<aes_gcm::Error> for PluginStorageError {
32    fn from(value: aes_gcm::Error) -> Self {
33        Self::Encryption {
34            err: value.to_string(),
35        }
36    }
37}
38
39impl From<rusqlite::Error> for PluginStorageError {
40    fn from(value: rusqlite::Error) -> Self {
41        Self::Generic {
42            err: value.to_string(),
43        }
44    }
45}
46
47impl From<anyhow::Error> for PluginStorageError {
48    fn from(value: anyhow::Error) -> Self {
49        Self::Generic {
50            err: value.to_string(),
51        }
52    }
53}
54
55impl PluginStorage {
56    pub(crate) fn new(
57        persister: Weak<Persister>,
58        passphrase: &[u8],
59        plugin_id: String,
60    ) -> Result<Self> {
61        if plugin_id.is_empty() {
62            log::error!("Plugin ID cannot be an empty string!");
63            bail!("Plugin ID cannot be an empty string!");
64        }
65        let passphrase = GenericArray::clone_from_slice(passphrase);
66        let cipher = Aes256Gcm::new(&passphrase);
67
68        Ok(Self {
69            cipher,
70            persister,
71            plugin_id,
72        })
73    }
74
75    fn get_persister(&self) -> Result<Arc<Persister>, PluginStorageError> {
76        self.persister.upgrade().ok_or(PluginStorageError::Generic {
77            err: "SDK is not running.".to_string(),
78        })
79    }
80
81    fn encrypt(&self, data: String) -> Result<String, PluginStorageError> {
82        let nonce = Aes256Gcm::generate_nonce(&mut OsRng);
83        let encrypted = self.cipher.encrypt(&nonce, data.as_bytes())?;
84        let mut payload = nonce.to_vec();
85        payload.extend_from_slice(&encrypted);
86        Ok(hex::encode(payload))
87    }
88
89    fn decrypt(&self, data: String) -> Result<String, PluginStorageError> {
90        let decoded = hex::decode(data).map_err(|err| PluginStorageError::Encryption {
91            err: err.to_string(),
92        })?;
93        let (nonce, data) = decoded.split_at(12);
94        let nonce = Nonce::from_slice(nonce);
95        let decrypted = self.cipher.decrypt(nonce, data)?;
96        let result =
97            String::from_utf8(decrypted).map_err(|err| PluginStorageError::Encryption {
98                err: err.to_string(),
99            })?;
100        Ok(result)
101    }
102
103    pub(crate) fn scoped_key(&self, key: &str) -> String {
104        format!("{}-{}", self.plugin_id, key)
105    }
106
107    /// Writes/updates a value in the database
108    ///
109    /// # Arguments
110    ///   - key: The name of the database key to write into
111    ///   - value: The value to write
112    ///   - old_value (optional): The previous value of that field (if any). When provided, it
113    ///     will ensure that the value that's being written has not been modified, throwing a
114    ///     [PluginStorageError::DataTooOld] error otherwise
115    pub fn set_item(
116        &self,
117        key: &str,
118        value: String,
119        old_value: Option<String>,
120    ) -> Result<(), PluginStorageError> {
121        let scoped_key = self.scoped_key(key);
122        let persister = self.get_persister()?;
123        let mut con = persister.get_connection()?;
124        let tx = con.transaction_with_behavior(rusqlite::TransactionBehavior::Immediate)?;
125        if let Some(old_value) = old_value {
126            if let Some(current_value) = Persister::get_cached_item_inner(&tx, &scoped_key)? {
127                let current_value = self.decrypt(current_value)?;
128                if old_value != current_value {
129                    return Err(PluginStorageError::DataTooOld);
130                }
131            }
132        }
133        Persister::update_cached_item_inner(&tx, &scoped_key, self.encrypt(value)?)?;
134        tx.commit()?;
135        Ok(())
136    }
137
138    pub fn get_item(&self, key: &str) -> Result<Option<String>, PluginStorageError> {
139        let scoped_key = self.scoped_key(key);
140        let value = self
141            .get_persister()?
142            .get_cached_item(&scoped_key)
143            .map_err(Into::<PluginStorageError>::into)?;
144        if let Some(value) = value {
145            return Ok(Some(self.decrypt(value)?));
146        }
147        Ok(None)
148    }
149
150    pub fn remove_item(&self, key: &str) -> Result<(), PluginStorageError> {
151        let scoped_key = self.scoped_key(key);
152        self.get_persister()?
153            .delete_cached_item(&scoped_key)
154            .map_err(Into::into)
155    }
156}