use crate::security::verifier;
use bistun_core::{LmsError, RegistryStore, WormPayload};
use std::future::Future;
use std::pin::Pin;
pub type PayloadFuture<'a> =
Pin<Box<dyn Future<Output = Result<(String, String), LmsError>> + Send + 'a>>;
pub trait ISnapshotProvider: Send + Sync {
fn fetch_payload(&self) -> PayloadFuture<'_>;
}
#[cfg(feature = "simulation")]
pub struct SimulatedSnapshotProvider {
pub payload: String,
pub signature: String,
pub public_key: String,
}
#[cfg(feature = "simulation")]
impl Default for SimulatedSnapshotProvider {
fn default() -> Self {
Self::new()
}
}
#[cfg(feature = "simulation")]
impl SimulatedSnapshotProvider {
pub fn new() -> Self {
use base64::{Engine as _, engine::general_purpose::STANDARD as BASE64};
use bistun_core::SIMULATED_WORM_JSON;
use ed25519_dalek::{Signer, SigningKey};
use rand::rngs::OsRng;
let mut csprng = OsRng;
let signing_key = SigningKey::generate(&mut csprng);
let public_key = BASE64.encode(signing_key.verifying_key().as_bytes());
let signature = BASE64.encode(signing_key.sign(SIMULATED_WORM_JSON.as_bytes()).to_bytes());
Self { payload: SIMULATED_WORM_JSON.to_string(), signature, public_key }
}
}
#[cfg(feature = "simulation")]
impl ISnapshotProvider for SimulatedSnapshotProvider {
fn fetch_payload(&self) -> PayloadFuture<'_> {
let p = self.payload.clone();
let s = self.signature.clone();
Box::pin(async move { Ok((p, s)) })
}
}
pub async fn hydrate_snapshot(
provider: &impl ISnapshotProvider,
public_key_b64: &str, ) -> Result<RegistryStore, LmsError> {
let (json_payload, signature) = provider.fetch_payload().await?;
verifier::verify_snapshot(&json_payload, &signature, public_key_b64)?;
let payload: WormPayload =
serde_json::from_str(&json_payload).map_err(|e| LmsError::SecurityFault {
pipeline_step: "Phase 0: WORM Hydration".to_string(),
context: "Deserialization".to_string(),
reason: format!("Failed to parse WORM JSON: {}", e),
})?;
let mut store = RegistryStore::new();
store.set_metadata(payload.metadata);
for profile in payload.profiles {
store.insert_stub(profile);
}
for (alias, canonical) in payload.aliases {
store.insert_alias(alias, canonical);
}
Ok(store)
}
#[cfg(all(test, feature = "simulation"))]
mod tests {
use super::*;
use mockall::mock;
mock! {
pub SnapshotProvider {}
impl ISnapshotProvider for SnapshotProvider {
fn fetch_payload<'a>(&'a self) -> PayloadFuture<'_>;
}
}
#[tokio::test]
async fn test_hydrate_snapshot_succeeds() {
let real_provider = SimulatedSnapshotProvider::new();
let p_clone = real_provider.payload.clone();
let s_clone = real_provider.signature.clone();
let mut mock_provider = MockSnapshotProvider::new();
mock_provider.expect_fetch_payload().returning(move || {
let p = p_clone.clone();
let s = s_clone.clone();
Box::pin(async move { Ok((p, s)) })
});
let store = hydrate_snapshot(&mock_provider, &real_provider.public_key)
.await
.expect("Hydration failed");
assert!(store.get_profile("en-US").is_some(), "System Default must exist");
assert!(store.get_profile("ar-EG").is_some());
assert!(store.get_profile("th-TH").is_some());
assert!(store.get_profile("zh-Hant").is_some());
assert!(store.get_profile("invalid-locale").is_none());
assert_eq!(store.resolve_alias("in"), Some("id".to_string()));
assert_eq!(store.resolve_alias("zh-TW"), Some("zh-Hant".to_string()));
}
}