mirror of
https://github.com/logos-messaging/libchat.git
synced 2026-02-10 08:53:08 +00:00
feat: delegate DR storage oprations to dr crate
This commit is contained in:
parent
d27b439c2d
commit
fa5e48e8de
@ -6,6 +6,8 @@
|
||||
use std::collections::HashMap;
|
||||
use std::rc::Rc;
|
||||
|
||||
use double_ratchets::storage::RatchetStorage;
|
||||
|
||||
use crate::{
|
||||
common::{Chat, HasChatId, InboundMessageHandler},
|
||||
dm::privatev1::PrivateV1Convo,
|
||||
@ -67,7 +69,10 @@ pub struct ChatManager {
|
||||
/// In-memory cache of active chats. Chats are loaded from storage on demand.
|
||||
chats: HashMap<String, PrivateV1Convo>,
|
||||
inbox: Inbox,
|
||||
/// Storage for chat metadata (identity, inbox keys, chat records).
|
||||
storage: ChatStorage,
|
||||
/// Storage for ratchet state (delegated to double-ratchets crate).
|
||||
ratchet_storage: RatchetStorage,
|
||||
}
|
||||
|
||||
impl ChatManager {
|
||||
@ -78,7 +83,10 @@ impl ChatManager {
|
||||
///
|
||||
/// Inbox ephemeral keys are loaded lazily when handling incoming handshakes.
|
||||
pub fn open(config: StorageConfig) -> Result<Self, ChatManagerError> {
|
||||
let mut storage = ChatStorage::new(config)?;
|
||||
let mut storage = ChatStorage::new(config.clone())?;
|
||||
|
||||
// Initialize ratchet storage (delegated to double-ratchets crate)
|
||||
let ratchet_storage = RatchetStorage::with_config(config)?;
|
||||
|
||||
// Load or create identity
|
||||
let identity = if let Some(identity) = storage.load_identity()? {
|
||||
@ -100,6 +108,7 @@ impl ChatManager {
|
||||
chats: HashMap::new(),
|
||||
inbox,
|
||||
storage,
|
||||
ratchet_storage,
|
||||
})
|
||||
}
|
||||
|
||||
@ -160,10 +169,8 @@ impl ChatManager {
|
||||
);
|
||||
self.storage.save_chat(&chat_record)?;
|
||||
|
||||
// Persist ratchet state
|
||||
let (state, skipped_keys) = convo.to_storage();
|
||||
self.storage
|
||||
.save_ratchet_state(&chat_id, &state, &skipped_keys)?;
|
||||
// Persist ratchet state (delegated to double-ratchets storage)
|
||||
self.ratchet_storage.save(&chat_id, convo.ratchet_state())?;
|
||||
|
||||
// Store in memory cache
|
||||
self.chats.insert(chat_id.clone(), convo);
|
||||
@ -189,10 +196,8 @@ impl ChatManager {
|
||||
|
||||
let payloads = chat.send_message(content)?;
|
||||
|
||||
// Persist updated ratchet state
|
||||
let (state, skipped_keys) = chat.to_storage();
|
||||
self.storage
|
||||
.save_ratchet_state(chat_id, &state, &skipped_keys)?;
|
||||
// Persist updated ratchet state (delegated to double-ratchets storage)
|
||||
self.ratchet_storage.save(chat_id, chat.ratchet_state())?;
|
||||
|
||||
let remote_id = chat.remote_id();
|
||||
Ok(payloads
|
||||
@ -207,9 +212,10 @@ impl ChatManager {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
// Try to load from storage
|
||||
if let Some((state, skipped_keys)) = self.storage.load_ratchet_state(chat_id)? {
|
||||
let convo = PrivateV1Convo::from_storage(chat_id.to_string(), state, skipped_keys);
|
||||
// Try to load ratchet state from double-ratchets storage
|
||||
if self.ratchet_storage.exists(chat_id)? {
|
||||
let dr_state = self.ratchet_storage.load(chat_id)?;
|
||||
let convo = PrivateV1Convo::from_state(chat_id.to_string(), dr_state);
|
||||
self.chats.insert(chat_id.to_string(), convo);
|
||||
Ok(())
|
||||
} else if self.storage.chat_exists(chat_id)? {
|
||||
@ -301,6 +307,8 @@ impl ChatManager {
|
||||
pub fn delete_chat(&mut self, chat_id: &str) -> Result<(), ChatManagerError> {
|
||||
self.chats.remove(chat_id);
|
||||
self.storage.delete_chat(chat_id)?;
|
||||
// Also delete ratchet state from double-ratchets storage
|
||||
let _ = self.ratchet_storage.delete(chat_id);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@ -419,10 +427,9 @@ mod tests {
|
||||
|
||||
// Scope 1: Create chat and send messages
|
||||
{
|
||||
let mut alice = ChatManager::open(StorageConfig::File(
|
||||
db_path.to_str().unwrap().to_string(),
|
||||
))
|
||||
.unwrap();
|
||||
let mut alice =
|
||||
ChatManager::open(StorageConfig::File(db_path.to_str().unwrap().to_string()))
|
||||
.unwrap();
|
||||
|
||||
let result = alice.start_private_chat(&bob_intro, "Message 1").unwrap();
|
||||
chat_id = result.0;
|
||||
@ -438,10 +445,9 @@ mod tests {
|
||||
|
||||
// Scope 2: Reopen and verify chat is restored
|
||||
{
|
||||
let mut alice2 = ChatManager::open(StorageConfig::File(
|
||||
db_path.to_str().unwrap().to_string(),
|
||||
))
|
||||
.unwrap();
|
||||
let mut alice2 =
|
||||
ChatManager::open(StorageConfig::File(db_path.to_str().unwrap().to_string()))
|
||||
.unwrap();
|
||||
|
||||
// Chat is in storage but not loaded yet
|
||||
assert!(alice2.list_stored_chats().unwrap().contains(&chat_id));
|
||||
|
||||
@ -12,7 +12,6 @@ use crate::{
|
||||
common::{Chat, ChatId, HasChatId},
|
||||
errors::{ChatError, EncryptionError},
|
||||
proto,
|
||||
storage::types::{RatchetStateRecord, SkippedKeyRecord},
|
||||
types::AddressedEncryptedPayload,
|
||||
utils::timestamp_millis,
|
||||
};
|
||||
@ -34,7 +33,11 @@ impl PrivateV1Convo {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_responder(chat_id: String, seed_key: SecretKey, dh_self: InstallationKeyPair) -> Self {
|
||||
pub fn new_responder(
|
||||
chat_id: String,
|
||||
seed_key: SecretKey,
|
||||
dh_self: InstallationKeyPair,
|
||||
) -> Self {
|
||||
Self {
|
||||
chat_id,
|
||||
// TODO: Danger - Fix double-ratchets types to Accept SecretKey
|
||||
@ -42,62 +45,14 @@ impl PrivateV1Convo {
|
||||
}
|
||||
}
|
||||
|
||||
/// Restore a conversation from stored ratchet state.
|
||||
pub fn from_storage(
|
||||
chat_id: String,
|
||||
state: RatchetStateRecord,
|
||||
skipped_keys: Vec<SkippedKeyRecord>,
|
||||
) -> Self {
|
||||
use std::collections::HashMap;
|
||||
|
||||
let dh_self = InstallationKeyPair::from_secret_bytes(state.dh_self_secret);
|
||||
let dh_remote = state.dh_remote.map(PublicKey::from);
|
||||
|
||||
let skipped: HashMap<(PublicKey, u32), [u8; 32]> = skipped_keys
|
||||
.into_iter()
|
||||
.map(|sk| ((PublicKey::from(sk.public_key), sk.msg_num), sk.message_key))
|
||||
.collect();
|
||||
|
||||
let dr_state = RatchetState::from_parts(
|
||||
state.root_key,
|
||||
state.sending_chain,
|
||||
state.receiving_chain,
|
||||
dh_self,
|
||||
dh_remote,
|
||||
state.msg_send,
|
||||
state.msg_recv,
|
||||
state.prev_chain_len,
|
||||
skipped,
|
||||
);
|
||||
|
||||
/// Restore a conversation from a loaded RatchetState.
|
||||
pub fn from_state(chat_id: String, dr_state: RatchetState) -> Self {
|
||||
Self { chat_id, dr_state }
|
||||
}
|
||||
|
||||
/// Get the current ratchet state for storage.
|
||||
pub fn to_storage(&self) -> (RatchetStateRecord, Vec<SkippedKeyRecord>) {
|
||||
let state = RatchetStateRecord {
|
||||
root_key: self.dr_state.root_key,
|
||||
sending_chain: self.dr_state.sending_chain,
|
||||
receiving_chain: self.dr_state.receiving_chain,
|
||||
dh_self_secret: *self.dr_state.dh_self.secret_bytes(),
|
||||
dh_remote: self.dr_state.dh_remote.map(|pk| pk.to_bytes()),
|
||||
msg_send: self.dr_state.msg_send,
|
||||
msg_recv: self.dr_state.msg_recv,
|
||||
prev_chain_len: self.dr_state.prev_chain_len,
|
||||
};
|
||||
|
||||
let skipped_keys: Vec<SkippedKeyRecord> = self
|
||||
.dr_state
|
||||
.skipped_keys
|
||||
.iter()
|
||||
.map(|((pk, msg_num), key)| SkippedKeyRecord {
|
||||
public_key: pk.to_bytes(),
|
||||
msg_num: *msg_num,
|
||||
message_key: *key,
|
||||
})
|
||||
.collect();
|
||||
|
||||
(state, skipped_keys)
|
||||
/// Get a reference to the ratchet state for storage.
|
||||
pub fn ratchet_state(&self) -> &RatchetState {
|
||||
&self.dr_state
|
||||
}
|
||||
|
||||
fn encrypt(&mut self, frame: PrivateV1Frame) -> EncryptedPayload {
|
||||
|
||||
@ -5,10 +5,11 @@ use std::collections::HashMap;
|
||||
use storage::{RusqliteError, SqliteDb, StorageConfig, StorageError, params};
|
||||
use x25519_dalek::StaticSecret;
|
||||
|
||||
use super::types::{ChatRecord, IdentityRecord, RatchetStateRecord, SkippedKeyRecord};
|
||||
use super::types::{ChatRecord, IdentityRecord};
|
||||
use crate::identity::Identity;
|
||||
|
||||
/// Schema for chat storage tables.
|
||||
/// Note: Ratchet state is stored by double_ratchets::RatchetStorage separately.
|
||||
const CHAT_SCHEMA: &str = "
|
||||
-- Identity table (single row)
|
||||
CREATE TABLE IF NOT EXISTS identity (
|
||||
@ -33,39 +34,14 @@ const CHAT_SCHEMA: &str = "
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_chats_type ON chats(chat_type);
|
||||
|
||||
-- Ratchet state for each conversation
|
||||
CREATE TABLE IF NOT EXISTS ratchet_state (
|
||||
conversation_id TEXT PRIMARY KEY,
|
||||
root_key BLOB NOT NULL,
|
||||
sending_chain BLOB,
|
||||
receiving_chain BLOB,
|
||||
dh_self_secret BLOB NOT NULL,
|
||||
dh_remote BLOB,
|
||||
msg_send INTEGER NOT NULL,
|
||||
msg_recv INTEGER NOT NULL,
|
||||
prev_chain_len INTEGER NOT NULL
|
||||
);
|
||||
|
||||
-- Skipped message keys (for out-of-order messages)
|
||||
CREATE TABLE IF NOT EXISTS skipped_keys (
|
||||
conversation_id TEXT NOT NULL,
|
||||
public_key BLOB NOT NULL,
|
||||
msg_num INTEGER NOT NULL,
|
||||
message_key BLOB NOT NULL,
|
||||
created_at INTEGER NOT NULL DEFAULT (strftime('%s', 'now')),
|
||||
PRIMARY KEY (conversation_id, public_key, msg_num),
|
||||
FOREIGN KEY (conversation_id) REFERENCES ratchet_state(conversation_id) ON DELETE CASCADE
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_skipped_keys_conversation
|
||||
ON skipped_keys(conversation_id);
|
||||
";
|
||||
|
||||
/// Chat-specific storage operations.
|
||||
///
|
||||
/// This struct wraps a `SqliteDb` and provides domain-specific
|
||||
/// storage operations for chat state.
|
||||
/// This struct wraps a SqliteDb and provides domain-specific
|
||||
/// storage operations for chat state (identity, inbox keys, chat metadata).
|
||||
///
|
||||
/// Note: Ratchet state persistence is delegated to double_ratchets::RatchetStorage.
|
||||
pub struct ChatStorage {
|
||||
db: SqliteDb,
|
||||
}
|
||||
@ -125,35 +101,6 @@ impl ChatStorage {
|
||||
}
|
||||
}
|
||||
|
||||
/// Saves a chat record.
|
||||
pub fn save_chat(&mut self, chat: &ChatRecord) -> Result<(), StorageError> {
|
||||
self.db.connection().execute(
|
||||
"INSERT OR REPLACE INTO chats (chat_id, chat_type, remote_public_key, remote_address, created_at)
|
||||
VALUES (?1, ?2, ?3, ?4, ?5)",
|
||||
params![
|
||||
chat.chat_id,
|
||||
chat.chat_type,
|
||||
chat.remote_public_key.as_ref().map(|k| k.as_slice()),
|
||||
chat.remote_address,
|
||||
chat.created_at,
|
||||
],
|
||||
)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Lists all chat IDs.
|
||||
pub fn list_chat_ids(&self) -> Result<Vec<String>, StorageError> {
|
||||
let mut stmt = self.db.connection().prepare("SELECT chat_id FROM chats")?;
|
||||
let rows = stmt.query_map([], |row| row.get(0))?;
|
||||
|
||||
let mut ids = Vec::new();
|
||||
for row in rows {
|
||||
ids.push(row?);
|
||||
}
|
||||
|
||||
Ok(ids)
|
||||
}
|
||||
|
||||
// ==================== Inbox Key Operations ====================
|
||||
|
||||
/// Saves an inbox ephemeral key.
|
||||
@ -230,7 +177,23 @@ impl ChatStorage {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// ==================== Chat Operations ====================
|
||||
// ==================== Chat Metadata Operations ====================
|
||||
|
||||
/// Saves a chat record.
|
||||
pub fn save_chat(&mut self, chat: &ChatRecord) -> Result<(), StorageError> {
|
||||
self.db.connection().execute(
|
||||
"INSERT OR REPLACE INTO chats (chat_id, chat_type, remote_public_key, remote_address, created_at)
|
||||
VALUES (?1, ?2, ?3, ?4, ?5)",
|
||||
params![
|
||||
chat.chat_id,
|
||||
chat.chat_type,
|
||||
chat.remote_public_key.as_ref().map(|k| k.as_slice()),
|
||||
chat.remote_address,
|
||||
chat.created_at,
|
||||
],
|
||||
)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Loads a chat record by ID.
|
||||
pub fn load_chat(&self, chat_id: &str) -> Result<Option<ChatRecord>, StorageError> {
|
||||
@ -273,6 +236,19 @@ impl ChatStorage {
|
||||
}
|
||||
}
|
||||
|
||||
/// Lists all chat IDs.
|
||||
pub fn list_chat_ids(&self) -> Result<Vec<String>, StorageError> {
|
||||
let mut stmt = self.db.connection().prepare("SELECT chat_id FROM chats")?;
|
||||
let rows = stmt.query_map([], |row| row.get(0))?;
|
||||
|
||||
let mut ids = Vec::new();
|
||||
for row in rows {
|
||||
ids.push(row?);
|
||||
}
|
||||
|
||||
Ok(ids)
|
||||
}
|
||||
|
||||
/// Checks if a chat exists in storage.
|
||||
pub fn chat_exists(&self, chat_id: &str) -> Result<bool, StorageError> {
|
||||
let mut stmt = self
|
||||
@ -284,181 +260,14 @@ impl ChatStorage {
|
||||
Ok(exists)
|
||||
}
|
||||
|
||||
/// Deletes a chat record and its ratchet state.
|
||||
/// Deletes a chat record.
|
||||
/// Note: Ratchet state must be deleted separately via RatchetStorage.
|
||||
pub fn delete_chat(&mut self, chat_id: &str) -> Result<(), StorageError> {
|
||||
let tx = self.db.transaction()?;
|
||||
// Delete skipped keys first (foreign key constraint)
|
||||
tx.execute(
|
||||
"DELETE FROM skipped_keys WHERE conversation_id = ?1",
|
||||
params![chat_id],
|
||||
)?;
|
||||
tx.execute(
|
||||
"DELETE FROM ratchet_state WHERE conversation_id = ?1",
|
||||
params![chat_id],
|
||||
)?;
|
||||
tx.execute("DELETE FROM chats WHERE chat_id = ?1", params![chat_id])?;
|
||||
tx.commit()?;
|
||||
self.db
|
||||
.connection()
|
||||
.execute("DELETE FROM chats WHERE chat_id = ?1", params![chat_id])?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// ==================== Ratchet State Operations ====================
|
||||
|
||||
/// Saves the ratchet state for a conversation.
|
||||
pub fn save_ratchet_state(
|
||||
&mut self,
|
||||
conversation_id: &str,
|
||||
state: &RatchetStateRecord,
|
||||
skipped_keys: &[SkippedKeyRecord],
|
||||
) -> Result<(), StorageError> {
|
||||
let tx = self.db.transaction()?;
|
||||
|
||||
// Upsert main state
|
||||
tx.execute(
|
||||
"
|
||||
INSERT INTO ratchet_state (
|
||||
conversation_id, root_key, sending_chain, receiving_chain,
|
||||
dh_self_secret, dh_remote, msg_send, msg_recv, prev_chain_len
|
||||
) VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9)
|
||||
ON CONFLICT(conversation_id) DO UPDATE SET
|
||||
root_key = excluded.root_key,
|
||||
sending_chain = excluded.sending_chain,
|
||||
receiving_chain = excluded.receiving_chain,
|
||||
dh_self_secret = excluded.dh_self_secret,
|
||||
dh_remote = excluded.dh_remote,
|
||||
msg_send = excluded.msg_send,
|
||||
msg_recv = excluded.msg_recv,
|
||||
prev_chain_len = excluded.prev_chain_len
|
||||
",
|
||||
params![
|
||||
conversation_id,
|
||||
state.root_key.as_slice(),
|
||||
state.sending_chain.as_ref().map(|c| c.as_slice()),
|
||||
state.receiving_chain.as_ref().map(|c| c.as_slice()),
|
||||
state.dh_self_secret.as_slice(),
|
||||
state.dh_remote.as_ref().map(|c| c.as_slice()),
|
||||
state.msg_send,
|
||||
state.msg_recv,
|
||||
state.prev_chain_len,
|
||||
],
|
||||
)?;
|
||||
|
||||
// Sync skipped keys: delete old ones and insert new
|
||||
tx.execute(
|
||||
"DELETE FROM skipped_keys WHERE conversation_id = ?1",
|
||||
params![conversation_id],
|
||||
)?;
|
||||
|
||||
for sk in skipped_keys {
|
||||
tx.execute(
|
||||
"INSERT INTO skipped_keys (conversation_id, public_key, msg_num, message_key)
|
||||
VALUES (?1, ?2, ?3, ?4)",
|
||||
params![
|
||||
conversation_id,
|
||||
sk.public_key.as_slice(),
|
||||
sk.msg_num,
|
||||
sk.message_key.as_slice(),
|
||||
],
|
||||
)?;
|
||||
}
|
||||
|
||||
tx.commit()?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Loads the ratchet state for a conversation.
|
||||
pub fn load_ratchet_state(
|
||||
&self,
|
||||
conversation_id: &str,
|
||||
) -> Result<Option<(RatchetStateRecord, Vec<SkippedKeyRecord>)>, StorageError> {
|
||||
// Load main state
|
||||
let state = self.load_ratchet_state_data(conversation_id)?;
|
||||
let state = match state {
|
||||
Some(s) => s,
|
||||
None => return Ok(None),
|
||||
};
|
||||
|
||||
// Load skipped keys
|
||||
let skipped_keys = self.load_skipped_keys(conversation_id)?;
|
||||
|
||||
Ok(Some((state, skipped_keys)))
|
||||
}
|
||||
|
||||
fn load_ratchet_state_data(
|
||||
&self,
|
||||
conversation_id: &str,
|
||||
) -> Result<Option<RatchetStateRecord>, StorageError> {
|
||||
let conn = self.db.connection();
|
||||
let mut stmt = conn.prepare(
|
||||
"
|
||||
SELECT root_key, sending_chain, receiving_chain, dh_self_secret,
|
||||
dh_remote, msg_send, msg_recv, prev_chain_len
|
||||
FROM ratchet_state
|
||||
WHERE conversation_id = ?1
|
||||
",
|
||||
)?;
|
||||
|
||||
let result = stmt.query_row(params![conversation_id], |row| {
|
||||
Ok(RatchetStateRecord {
|
||||
root_key: blob_to_array(row.get::<_, Vec<u8>>(0)?),
|
||||
sending_chain: row.get::<_, Option<Vec<u8>>>(1)?.map(blob_to_array),
|
||||
receiving_chain: row.get::<_, Option<Vec<u8>>>(2)?.map(blob_to_array),
|
||||
dh_self_secret: blob_to_array(row.get::<_, Vec<u8>>(3)?),
|
||||
dh_remote: row.get::<_, Option<Vec<u8>>>(4)?.map(blob_to_array),
|
||||
msg_send: row.get(5)?,
|
||||
msg_recv: row.get(6)?,
|
||||
prev_chain_len: row.get(7)?,
|
||||
})
|
||||
});
|
||||
|
||||
match result {
|
||||
Ok(record) => Ok(Some(record)),
|
||||
Err(RusqliteError::QueryReturnedNoRows) => Ok(None),
|
||||
Err(e) => Err(StorageError::Database(e.to_string())),
|
||||
}
|
||||
}
|
||||
|
||||
fn load_skipped_keys(
|
||||
&self,
|
||||
conversation_id: &str,
|
||||
) -> Result<Vec<SkippedKeyRecord>, StorageError> {
|
||||
let conn = self.db.connection();
|
||||
let mut stmt = conn.prepare(
|
||||
"
|
||||
SELECT public_key, msg_num, message_key
|
||||
FROM skipped_keys
|
||||
WHERE conversation_id = ?1
|
||||
",
|
||||
)?;
|
||||
|
||||
let rows = stmt.query_map(params![conversation_id], |row| {
|
||||
Ok(SkippedKeyRecord {
|
||||
public_key: blob_to_array(row.get::<_, Vec<u8>>(0)?),
|
||||
msg_num: row.get(1)?,
|
||||
message_key: blob_to_array(row.get::<_, Vec<u8>>(2)?),
|
||||
})
|
||||
})?;
|
||||
|
||||
rows.collect::<Result<Vec<_>, _>>()
|
||||
.map_err(|e| StorageError::Database(e.to_string()))
|
||||
}
|
||||
|
||||
/// Checks if a ratchet state exists for a conversation.
|
||||
pub fn ratchet_state_exists(&self, conversation_id: &str) -> Result<bool, StorageError> {
|
||||
let conn = self.db.connection();
|
||||
let count: i64 = conn.query_row(
|
||||
"SELECT COUNT(*) FROM ratchet_state WHERE conversation_id = ?1",
|
||||
params![conversation_id],
|
||||
|row| row.get(0),
|
||||
)?;
|
||||
Ok(count > 0)
|
||||
}
|
||||
}
|
||||
|
||||
/// Helper to convert a Vec<u8> to a fixed-size array.
|
||||
fn blob_to_array(blob: Vec<u8>) -> [u8; 32] {
|
||||
let mut arr = [0u8; 32];
|
||||
arr.copy_from_slice(&blob);
|
||||
arr
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
||||
@ -1,4 +1,7 @@
|
||||
//! Storage record types for serialization/deserialization.
|
||||
//!
|
||||
//! Note: Ratchet state types (RatchetStateRecord, SkippedKeyRecord) are in
|
||||
//! double_ratchets::storage module and handled by RatchetStorage.
|
||||
|
||||
use x25519_dalek::{PublicKey, StaticSecret};
|
||||
|
||||
@ -27,7 +30,7 @@ impl From<IdentityRecord> for Identity {
|
||||
}
|
||||
|
||||
/// Record for storing chat metadata.
|
||||
/// Note: The actual double ratchet state is stored separately by the DR storage.
|
||||
/// Note: The actual double ratchet state is stored separately by RatchetStorage.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ChatRecord {
|
||||
/// Unique chat identifier.
|
||||
@ -57,24 +60,3 @@ impl ChatRecord {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Raw ratchet state data for SQLite storage.
|
||||
#[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,
|
||||
}
|
||||
|
||||
/// Skipped key record for out-of-order message handling.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct SkippedKeyRecord {
|
||||
pub public_key: [u8; 32],
|
||||
pub msg_num: u32,
|
||||
pub message_key: [u8; 32],
|
||||
}
|
||||
|
||||
@ -47,6 +47,12 @@ pub struct RatchetStorage {
|
||||
}
|
||||
|
||||
impl RatchetStorage {
|
||||
/// Creates a new RatchetStorage with the given configuration.
|
||||
pub fn with_config(config: storage::StorageConfig) -> Result<Self, StorageError> {
|
||||
let db = SqliteDb::new(config)?;
|
||||
Self::run_migration(db)
|
||||
}
|
||||
|
||||
/// Opens an existing encrypted database file.
|
||||
pub fn new(path: &str, key: &str) -> Result<Self, StorageError> {
|
||||
let db = SqliteDb::sqlcipher(path.to_string(), key.to_string())?;
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user