lum_ccc_rust/flutter_src/ccc/ratchet_state_manager.dart

155 lines
4.5 KiB
Dart
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

///
/// Ratchet State Manager Hive Persistence for Ratchet Chain State
///
/// Manages the lifecycle of [RatchetState] objects in a dedicated Hive box.
/// Each entry is keyed by ``'{channelUuid}_{senderIdentifier}'``.
///
/// Phase 2 of the Hydra Apocalypse Ratchet.
///
import 'package:hive_ce/hive.dart';
import 'package:letusmsg/data/ratchet_state_data.dart';
import 'package:letusmsg/utilz/Llog.dart';
/// Manages persistence of [RatchetState] objects in Hive.
///
/// Provides CRUD operations for ratchet states and channel-level cleanup.
/// The box is opened lazily on first access and remains open for the app
/// lifetime.
///
/// ## Key Format
/// ``'{channelUuid}_{senderIdentifier}'`` — deterministic from the
/// [RatchetState.hiveKey] property.
///
/// ## Usage
/// ```dart
/// final manager = RatchetStateManager();
/// await manager.open();
///
/// // Save after ratchet advance
/// await manager.save(state);
///
/// // Load for decrypt
/// final state = await manager.load('chan-uuid', 'sender-id');
/// ```
class RatchetStateManager {
static const String boxName = 'ratchet_state';
Box<RatchetState>? _box;
/// Whether the Hive box is currently open.
bool get isOpen => _box?.isOpen ?? false;
/// Open the ratchet state Hive box.
///
/// Safe to call multiple times — returns immediately if already open.
Future<void> open() async {
if (_box != null && _box!.isOpen) return;
try {
_box = await Hive.openBox<RatchetState>(boxName);
} catch (ex) {
Llog.log.e('[RatchetStateManager] Failed to open box: $ex');
rethrow;
}
}
/// Ensure the box is open, opening it if necessary.
Future<Box<RatchetState>> _ensureBox() async {
if (_box == null || !_box!.isOpen) {
await open();
}
return _box!;
}
/// Load ratchet state for a specific channel + sender.
///
/// Returns ``null`` if no state exists (channel not yet initialized with
/// ratchet, or pre-Phase 2 channel).
Future<RatchetState?> load(
String channelUuid,
String senderIdentifier,
) async {
final box = await _ensureBox();
final key = _buildKey(channelUuid, senderIdentifier);
return box.get(key);
}
/// Save ratchet state after advancing the chain.
///
/// Must be called after every send/receive operation to persist the
/// updated chain keys and counters.
Future<void> save(RatchetState state) async {
final box = await _ensureBox();
await box.put(state.hiveKey, state);
}
/// Delete ratchet state for a specific channel + sender.
Future<void> delete(
String channelUuid,
String senderIdentifier,
) async {
final box = await _ensureBox();
final key = _buildKey(channelUuid, senderIdentifier);
await box.delete(key);
}
/// Delete all ratchet states for a channel (all senders).
///
/// Called when a channel is deleted or left. Iterates all keys in the box
/// and removes those matching the channel prefix.
Future<void> deleteChannel(String channelUuid) async {
final box = await _ensureBox();
final prefix = '${channelUuid}_';
final keysToDelete = <dynamic>[];
for (final key in box.keys) {
if (key.toString().startsWith(prefix)) {
keysToDelete.add(key);
}
}
if (keysToDelete.isNotEmpty) {
await box.deleteAll(keysToDelete);
Llog.log.d('[RatchetStateManager] Deleted ${keysToDelete.length} '
'ratchet state(s) for channel $channelUuid');
}
}
/// List all ratchet states for a channel (all senders).
///
/// Useful for debugging and testing.
Future<List<RatchetState>> listForChannel(String channelUuid) async {
final box = await _ensureBox();
final prefix = '${channelUuid}_';
final results = <RatchetState>[];
for (final key in box.keys) {
if (key.toString().startsWith(prefix)) {
final state = box.get(key);
if (state != null) results.add(state);
}
}
return results;
}
/// Check if a ratchet state exists for a channel + sender.
Future<bool> exists(
String channelUuid,
String senderIdentifier,
) async {
final box = await _ensureBox();
return box.containsKey(_buildKey(channelUuid, senderIdentifier));
}
/// Close the Hive box.
///
/// Called during app shutdown or cleanup.
Future<void> close() async {
if (_box != null && _box!.isOpen) {
await _box!.close();
}
_box = null;
}
/// Build the composite Hive key from channel + sender identifiers.
static String _buildKey(String channelUuid, String senderIdentifier) {
return '${channelUuid}_$senderIdentifier';
}
}