wacore_ng/store/traits.rs
1//! Storage traits for the WhatsApp client.
2//!
3//! This module defines 4 domain-grouped traits that together form the `Backend` trait:
4//!
5//! - [`SignalStore`]: Signal protocol cryptographic operations (identity, sessions, keys)
6//! - [`AppSyncStore`]: WhatsApp app state synchronization
7//! - [`ProtocolStore`]: WhatsApp Web protocol alignment (SKDM, LID mapping, device registry)
8//! - [`DeviceStore`]: Device persistence operations
9
10use crate::appstate::hash::HashState;
11use crate::store::error::Result;
12use async_trait::async_trait;
13use serde::{Deserialize, Serialize};
14use wacore_appstate_ng::processor::AppStateMutationMAC;
15use wacore_binary_ng::jid::Jid;
16
17/// App state synchronization key for WhatsApp's app state protocol.
18#[derive(Debug, Clone, Default, Serialize, Deserialize)]
19pub struct AppStateSyncKey {
20 pub key_data: Vec<u8>,
21 pub fingerprint: Vec<u8>,
22 pub timestamp: i64,
23}
24
25/// Entry representing a LID to Phone Number mapping.
26#[derive(Debug, Clone, Serialize, Deserialize)]
27pub struct LidPnMappingEntry {
28 /// The LID user part (e.g., "100000012345678")
29 pub lid: String,
30 /// The phone number user part (e.g., "559980000001")
31 pub phone_number: String,
32 /// Unix timestamp when the mapping was first learned
33 pub created_at: i64,
34 /// Unix timestamp when the mapping was last updated
35 pub updated_at: i64,
36 /// The source from which this mapping was learned (e.g., "usync", "peer_pn_message")
37 pub learning_source: String,
38}
39
40/// Trusted contact privacy token entry.
41///
42/// Matches WhatsApp Web's Chat.tcToken / tcTokenTimestamp / tcTokenSenderTimestamp.
43#[derive(Debug, Clone, Serialize, Deserialize)]
44pub struct TcTokenEntry {
45 /// Raw token bytes received from the server.
46 pub token: Vec<u8>,
47 /// Unix timestamp (seconds) when the token was received.
48 pub token_timestamp: i64,
49 /// Unix timestamp (seconds) when we last issued our token to this contact.
50 pub sender_timestamp: Option<i64>,
51}
52
53/// Device information for registry tracking.
54#[derive(Debug, Clone, Serialize, Deserialize)]
55pub struct DeviceInfo {
56 /// The device ID (0 = primary device, 1+ = companion devices)
57 pub device_id: u32,
58 /// The key index, if known
59 pub key_index: Option<u32>,
60}
61
62/// Device list record matching WhatsApp Web's DeviceListRecord structure.
63#[derive(Debug, Clone, Serialize, Deserialize)]
64pub struct DeviceListRecord {
65 /// The user part of the JID (phone number or LID)
66 pub user: String,
67 /// List of known devices for this user
68 pub devices: Vec<DeviceInfo>,
69 /// Timestamp when this record was last updated
70 pub timestamp: i64,
71 /// Participant hash from usync, if available
72 pub phash: Option<String>,
73}
74
75/// Signal protocol cryptographic storage operations.
76///
77/// Handles identity keys, sessions, pre-keys, signed pre-keys, and sender keys
78/// for end-to-end encryption.
79#[async_trait]
80pub trait SignalStore: Send + Sync {
81 // --- Identity Operations ---
82
83 /// Store an identity key for a remote address.
84 async fn put_identity(&self, address: &str, key: [u8; 32]) -> Result<()>;
85
86 /// Load an identity key for a remote address.
87 async fn load_identity(&self, address: &str) -> Result<Option<Vec<u8>>>;
88
89 /// Delete an identity key.
90 async fn delete_identity(&self, address: &str) -> Result<()>;
91
92 // --- Session Operations ---
93
94 /// Get an encrypted session for an address.
95 async fn get_session(&self, address: &str) -> Result<Option<Vec<u8>>>;
96
97 /// Store an encrypted session.
98 async fn put_session(&self, address: &str, session: &[u8]) -> Result<()>;
99
100 /// Delete a session.
101 async fn delete_session(&self, address: &str) -> Result<()>;
102
103 /// Check if a session exists. Default implementation uses `get_session`.
104 async fn has_session(&self, address: &str) -> Result<bool> {
105 Ok(self.get_session(address).await?.is_some())
106 }
107
108 // --- PreKey Operations ---
109
110 /// Store a pre-key.
111 async fn store_prekey(&self, id: u32, record: &[u8], uploaded: bool) -> Result<()>;
112
113 /// Store multiple pre-keys in a single batch operation.
114 /// Default implementation falls back to individual `store_prekey` calls.
115 async fn store_prekeys_batch(&self, keys: &[(u32, Vec<u8>)], uploaded: bool) -> Result<()> {
116 for (id, record) in keys {
117 self.store_prekey(*id, record, uploaded).await?;
118 }
119 Ok(())
120 }
121
122 /// Load a pre-key by ID.
123 async fn load_prekey(&self, id: u32) -> Result<Option<Vec<u8>>>;
124
125 /// Remove a pre-key.
126 async fn remove_prekey(&self, id: u32) -> Result<()>;
127
128 /// Get the maximum pre-key ID currently stored, or 0 if none exist.
129 /// Used for migration when `next_pre_key_id` counter is not yet initialized.
130 async fn get_max_prekey_id(&self) -> Result<u32>;
131
132 // --- Signed PreKey Operations ---
133
134 /// Store a signed pre-key.
135 async fn store_signed_prekey(&self, id: u32, record: &[u8]) -> Result<()>;
136
137 /// Load a signed pre-key by ID.
138 async fn load_signed_prekey(&self, id: u32) -> Result<Option<Vec<u8>>>;
139
140 /// Load all signed pre-keys. Returns (id, record) pairs.
141 async fn load_all_signed_prekeys(&self) -> Result<Vec<(u32, Vec<u8>)>>;
142
143 /// Remove a signed pre-key.
144 async fn remove_signed_prekey(&self, id: u32) -> Result<()>;
145
146 // --- Sender Key Operations ---
147
148 /// Store a sender key for group messaging.
149 async fn put_sender_key(&self, address: &str, record: &[u8]) -> Result<()>;
150
151 /// Get a sender key.
152 async fn get_sender_key(&self, address: &str) -> Result<Option<Vec<u8>>>;
153
154 /// Delete a sender key.
155 async fn delete_sender_key(&self, address: &str) -> Result<()>;
156}
157
158/// WhatsApp app state synchronization storage.
159///
160/// Handles sync keys, version tracking, and mutation MACs for the app state protocol.
161#[async_trait]
162pub trait AppSyncStore: Send + Sync {
163 /// Get an app state sync key by ID.
164 async fn get_sync_key(&self, key_id: &[u8]) -> Result<Option<AppStateSyncKey>>;
165
166 /// Set an app state sync key.
167 async fn set_sync_key(&self, key_id: &[u8], key: AppStateSyncKey) -> Result<()>;
168
169 /// Get the app state version for a collection.
170 async fn get_version(&self, name: &str) -> Result<HashState>;
171
172 /// Set the app state version for a collection.
173 async fn set_version(&self, name: &str, state: HashState) -> Result<()>;
174
175 /// Store mutation MACs for a version.
176 async fn put_mutation_macs(
177 &self,
178 name: &str,
179 version: u64,
180 mutations: &[AppStateMutationMAC],
181 ) -> Result<()>;
182
183 /// Get a mutation MAC by index.
184 async fn get_mutation_mac(&self, name: &str, index_mac: &[u8]) -> Result<Option<Vec<u8>>>;
185
186 /// Delete mutation MACs by their index MACs.
187 async fn delete_mutation_macs(&self, name: &str, index_macs: &[Vec<u8>]) -> Result<()>;
188
189 /// Get the most recently stored app state sync key ID.
190 async fn get_latest_sync_key_id(&self) -> Result<Option<Vec<u8>>>;
191}
192
193/// WhatsApp Web protocol alignment storage.
194///
195/// Handles SKDM tracking, LID-PN mapping, base key collision detection,
196/// device registry, and sender key status.
197#[async_trait]
198pub trait ProtocolStore: Send + Sync {
199 // --- SKDM Tracking ---
200
201 /// Get device JIDs that have received SKDM for a group.
202 async fn get_skdm_recipients(&self, group_jid: &str) -> Result<Vec<Jid>>;
203
204 /// Record devices that have received SKDM for a group.
205 async fn add_skdm_recipients(&self, group_jid: &str, device_jids: &[Jid]) -> Result<()>;
206
207 /// Clear SKDM recipients for a group (call when sender key is rotated).
208 async fn clear_skdm_recipients(&self, group_jid: &str) -> Result<()>;
209
210 // --- LID-PN Mapping ---
211
212 /// Get a mapping by LID.
213 async fn get_lid_mapping(&self, lid: &str) -> Result<Option<LidPnMappingEntry>>;
214
215 /// Get a mapping by phone number (returns the most recent LID for that phone).
216 async fn get_pn_mapping(&self, phone: &str) -> Result<Option<LidPnMappingEntry>>;
217
218 /// Store or update a LID-PN mapping.
219 async fn put_lid_mapping(&self, entry: &LidPnMappingEntry) -> Result<()>;
220
221 /// Get all LID-PN mappings (for cache warm-up).
222 async fn get_all_lid_mappings(&self) -> Result<Vec<LidPnMappingEntry>>;
223
224 // --- Base Key Collision Detection ---
225
226 /// Save the base key for a session address during retry collision detection.
227 async fn save_base_key(&self, address: &str, message_id: &str, base_key: &[u8]) -> Result<()>;
228
229 /// Check if the current session has the same base key as the saved one.
230 async fn has_same_base_key(
231 &self,
232 address: &str,
233 message_id: &str,
234 current_base_key: &[u8],
235 ) -> Result<bool>;
236
237 /// Delete a base key entry.
238 async fn delete_base_key(&self, address: &str, message_id: &str) -> Result<()>;
239
240 // --- Device Registry ---
241
242 /// Update the device list for a user (called after usync responses).
243 async fn update_device_list(&self, record: DeviceListRecord) -> Result<()>;
244
245 /// Get all known devices for a user.
246 async fn get_devices(&self, user: &str) -> Result<Option<DeviceListRecord>>;
247
248 // --- Sender Key Status (Lazy Deletion) ---
249
250 /// Mark a participant's sender key as needing regeneration for a group.
251 async fn mark_forget_sender_key(&self, group_jid: &str, participant: &str) -> Result<()>;
252
253 /// Get participants that need fresh SKDM (marked for forget).
254 /// Consumes the marks (deletes them after reading).
255 async fn consume_forget_marks(&self, group_jid: &str) -> Result<Vec<String>>;
256
257 // --- TcToken Storage ---
258
259 /// Get a trusted contact token for a JID (stored under LID).
260 async fn get_tc_token(&self, jid: &str) -> Result<Option<TcTokenEntry>>;
261
262 /// Store or update a trusted contact token for a JID.
263 async fn put_tc_token(&self, jid: &str, entry: &TcTokenEntry) -> Result<()>;
264
265 /// Delete a trusted contact token for a JID.
266 async fn delete_tc_token(&self, jid: &str) -> Result<()>;
267
268 /// Get all JIDs that have stored tc tokens.
269 async fn get_all_tc_token_jids(&self) -> Result<Vec<String>>;
270
271 /// Delete tc tokens with token_timestamp older than cutoff. Returns count deleted.
272 async fn delete_expired_tc_tokens(&self, cutoff_timestamp: i64) -> Result<u32>;
273}
274
275/// Device data persistence operations.
276#[async_trait]
277pub trait DeviceStore: Send + Sync {
278 /// Save device data.
279 async fn save(&self, device: &crate::store::Device) -> Result<()>;
280
281 /// Load device data.
282 async fn load(&self) -> Result<Option<crate::store::Device>>;
283
284 /// Check if a device exists.
285 async fn exists(&self) -> Result<bool>;
286
287 /// Create a new device row and return its generated device_id.
288 async fn create(&self) -> Result<i32>;
289
290 /// Create a snapshot of the database state.
291 /// The argument `name` can be used to label the snapshot file.
292 /// `extra_content` can be used to save a related binary blob (e.g. the message that caused the failure).
293 async fn snapshot_db(&self, _name: &str, _extra_content: Option<&[u8]>) -> Result<()> {
294 Ok(())
295 }
296}
297
298/// Combined storage backend trait.
299///
300/// Any type implementing all four domain traits automatically implements `Backend`.
301pub trait Backend: SignalStore + AppSyncStore + ProtocolStore + DeviceStore + Send + Sync {}
302
303impl<T> Backend for T where T: SignalStore + AppSyncStore + ProtocolStore + DeviceStore + Send + Sync
304{}