use std::fmt;
#[non_exhaustive]
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum ProtocolStateError {
NotFound(String),
Corrupted(String),
StoreFailed(String),
LoadFailed(String),
DeleteFailed(String),
}
impl fmt::Display for ProtocolStateError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::NotFound(detail) => write!(f, "Protocol state entry not found: {}", detail),
Self::Corrupted(detail) => write!(f, "Protocol state entry corrupted: {}", detail),
Self::StoreFailed(detail) => write!(f, "Failed to store protocol state: {}", detail),
Self::LoadFailed(detail) => write!(f, "Failed to load protocol state: {}", detail),
Self::DeleteFailed(detail) => write!(f, "Failed to delete protocol state: {}", detail),
}
}
}
impl std::error::Error for ProtocolStateError {}
pub type ProtocolStateResult<T> = Result<T, ProtocolStateError>;
pub const MAX_PROTOCOL_STATE_RECORD_TRANSFER_BYTES: usize = 8 * 1024 * 1024;
pub trait ProtocolStateStorage: Send + Sync {
fn store(&self, key_type: &str, key_id: &str, data: &[u8]) -> ProtocolStateResult<()>;
fn load(&self, key_type: &str, key_id: &str) -> ProtocolStateResult<Option<Vec<u8>>>;
fn delete(&self, key_type: &str, key_id: &str) -> ProtocolStateResult<()>;
fn list_keys(&self, key_type: &str) -> ProtocolStateResult<Vec<String>>;
}
#[cfg(test)]
mod tests {
use super::MAX_PROTOCOL_STATE_RECORD_TRANSFER_BYTES;
use crate::protocol::state_crypto::SEALED_RECORD_OVERHEAD;
use crate::protocol::MAX_PROTOCOL_STATE_RECORD_BYTES;
#[test]
fn bounded_load_ceiling_is_a_superset_of_the_record_cap() {
assert!(
MAX_PROTOCOL_STATE_RECORD_BYTES + SEALED_RECORD_OVERHEAD
< MAX_PROTOCOL_STATE_RECORD_TRANSFER_BYTES,
"provider read ceiling ({}) must exceed the largest sealed record \
core can write ({} + {})",
MAX_PROTOCOL_STATE_RECORD_TRANSFER_BYTES,
MAX_PROTOCOL_STATE_RECORD_BYTES,
SEALED_RECORD_OVERHEAD,
);
}
#[test]
fn built_in_providers_mirror_the_transfer_ceiling() {
let mib = MAX_PROTOCOL_STATE_RECORD_TRANSFER_BYTES / (1024 * 1024);
assert_eq!(
mib * 1024 * 1024,
MAX_PROTOCOL_STATE_RECORD_TRANSFER_BYTES,
"all three providers spell this ceiling `N * 1024 * 1024`, so a \
value that is not a whole number of MiB cannot be matched here — \
respell the provider literals and this test together rather than \
letting the guard silently stop covering them"
);
let repo = std::path::Path::new(env!("CARGO_MANIFEST_DIR")).join("../..");
let sources = [
(
repo.join("bindings/react-native/ios/ProtocolStateStorage.swift"),
format!("static let maxValueBytes = {mib} * 1024 * 1024"),
),
(
repo.join(
"bindings/react-native/android/src/main/java/com/offlineprotocol/\
ProtocolStateStorage.kt",
),
format!("const val MAX_VALUE_BYTES = {mib} * 1024 * 1024"),
),
(
repo.join("bindings/python/offline_protocol_sdk/state_storage.py"),
format!("MAX_VALUE_BYTES = {mib} * 1024 * 1024"),
),
];
for (path, expected) in &sources {
let Ok(source) = std::fs::read_to_string(path) else {
eprintln!(
"bindings tree not present, skipping ceiling drift check for {}",
path.display()
);
continue;
};
assert!(
source.contains(expected.as_str()),
"built-in provider record ceiling drifted from \
MAX_PROTOCOL_STATE_RECORD_TRANSFER_BYTES: expected `{expected}` in {}",
path.display()
);
}
}
}