libchat/core/storage/src/store.rs
kaichao c44c52b127
feat: storage implementation and trait abstraction (#79)
* feat: storage for conversations

* fix: db types conversion

* feat: run migrations from sql files

* feat: persist identity

* fix: revert double ratchet storage refactor

* fix: clean

* refactor: use result wrapper for ffi

* refactor: uniform storage error into chat error

* fix: zeroize identity record

* fix: zeroize for secret keys in db operations

* fix: transactional sql migration

* fix: remove destroy_string

* feat: db storage for inbox ephermeral keys

* chore: remove in memory hashmap for ephemeral keys

* feat: persist conversation store

* feat: wire with the double ratchet storage

* feat: remove conversation store

* chore: fix conversation type not used

* feat: mock chat store implementation

* chore: sqlite module

* feat: sqlite crate

* chore: sqlite rename

* chore: more refactor

* extract ratchet store trait

* chore: clear error conversion

* chore: remove customized db conn

* chore: fix clippy

* chore: refactor to use generics and enum

* chore: further clean for review comments
2026-04-03 08:25:26 +08:00

127 lines
3.8 KiB
Rust

use crypto::{Identity, PrivateKey};
use crate::StorageError;
/// Persistence operations for installation identity data.
pub trait IdentityStore {
/// Loads the stored identity if one exists.
fn load_identity(&self) -> Result<Option<Identity>, StorageError>;
/// Persists the installation identity.
fn save_identity(&mut self, identity: &Identity) -> Result<(), StorageError>;
}
pub trait EphemeralKeyStore {
fn save_ephemeral_key(
&mut self,
public_key_hex: &str,
private_key: &PrivateKey,
) -> Result<(), StorageError>;
fn load_ephemeral_key(&self, public_key_hex: &str) -> Result<Option<PrivateKey>, StorageError>;
fn remove_ephemeral_key(&mut self, public_key_hex: &str) -> Result<(), StorageError>;
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum ConversationKind {
PrivateV1,
Unknown(String),
}
impl ConversationKind {
pub fn as_str(&self) -> &str {
match self {
Self::PrivateV1 => "private_v1",
Self::Unknown(value) => value.as_str(),
}
}
}
impl From<&str> for ConversationKind {
fn from(value: &str) -> Self {
match value {
"private_v1" => Self::PrivateV1,
other => Self::Unknown(other.to_string()),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ConversationMeta {
pub local_convo_id: String,
pub remote_convo_id: String,
pub kind: ConversationKind,
}
pub trait ConversationStore {
fn save_conversation(&mut self, meta: &ConversationMeta) -> Result<(), StorageError>;
fn load_conversation(
&self,
local_convo_id: &str,
) -> Result<Option<ConversationMeta>, StorageError>;
fn remove_conversation(&mut self, local_convo_id: &str) -> Result<(), StorageError>;
fn load_conversations(&self) -> Result<Vec<ConversationMeta>, StorageError>;
fn has_conversation(&self, local_convo_id: &str) -> Result<bool, StorageError>;
}
/// Raw state data for ratchet storage (without generic parameter).
#[derive(Debug, Clone)]
pub struct RatchetStateRecord {
pub root_key: [u8; 32],
pub sending_chain: Option<[u8; 32]>,
pub receiving_chain: Option<[u8; 32]>,
pub dh_self_secret: [u8; 32],
pub dh_remote: Option<[u8; 32]>,
pub msg_send: u32,
pub msg_recv: u32,
pub prev_chain_len: u32,
}
/// A skipped message key stored alongside ratchet state.
#[derive(Debug, Clone)]
pub struct SkippedKeyRecord {
pub public_key: [u8; 32],
pub msg_num: u32,
pub message_key: [u8; 32],
}
/// Persistence operations for double-ratchet state.
pub trait RatchetStore {
/// Saves ratchet state and skipped keys for a conversation.
fn save_ratchet_state(
&mut self,
conversation_id: &str,
state: &RatchetStateRecord,
skipped_keys: &[SkippedKeyRecord],
) -> Result<(), StorageError>;
/// Loads ratchet state for a conversation.
fn load_ratchet_state(&self, conversation_id: &str)
-> Result<RatchetStateRecord, StorageError>;
/// Loads skipped keys for a conversation.
fn load_skipped_keys(
&self,
conversation_id: &str,
) -> Result<Vec<SkippedKeyRecord>, StorageError>;
/// Checks if a ratchet state exists for a conversation.
fn has_ratchet_state(&self, conversation_id: &str) -> Result<bool, StorageError>;
/// Deletes ratchet state and skipped keys for a conversation.
fn delete_ratchet_state(&mut self, conversation_id: &str) -> Result<(), StorageError>;
/// Cleans up old skipped keys older than the given age in seconds.
fn cleanup_old_skipped_keys(&mut self, max_age_secs: i64) -> Result<usize, StorageError>;
}
pub trait ChatStore: IdentityStore + EphemeralKeyStore + ConversationStore + RatchetStore {}
impl<T> ChatStore for T where T: IdentityStore + EphemeralKeyStore + ConversationStore + RatchetStore
{}