Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions crates/engine/src/content/provider.rs
Original file line number Diff line number Diff line change
Expand Up @@ -314,6 +314,24 @@ pub enum ProviderError {
AddressMismatch,
}

impl ProviderError {
/// The stable check name a host branches on. Never carries the endpoint or
/// the credential.
pub fn check(&self) -> &'static str {
match self {
ProviderError::InvalidEndpoint => "byo-endpoint-invalid",
ProviderError::InsecureTransport => "byo-endpoint-insecure",
ProviderError::BlockedAddress => "byo-endpoint-blocked",
ProviderError::InvalidCredential => "byo-credential-invalid",
ProviderError::Unreachable => "byo-unreachable",
ProviderError::NoVerdict => "byo-no-verdict",
ProviderError::Rejected { .. } => "byo-rejected",
ProviderError::MalformedBlockAddress => "byo-block-address-malformed",
ProviderError::AddressMismatch => "byo-address-mismatch",
}
}
}

/// Test that a member's BYO provider is reachable and authenticated, engine-side
/// over the Http seam. Issues the provider's standard health/auth probe and
/// treats any 2xx as success.
Expand Down
15 changes: 15 additions & 0 deletions crates/engine/src/entropy.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
//! injected — engine logic never calls an RNG directly — so tests substitute
//! the test kit's seeded source and every seed and nonce becomes reproducible.

use core::cell::RefCell;
use core::fmt;

use cipherbox_core::suite::aead::NONCE_LEN;
Expand Down Expand Up @@ -66,6 +67,20 @@ impl<E: Entropy + ?Sized> Entropy for Box<E> {
}
}

/// A shared [`Entropy`] cell as an [`Entropy`] source that re-borrows per draw.
///
/// The engine holds one boxed source behind a [`RefCell`] shared with every
/// spawned loop, and an async port that takes `&mut dyn Entropy` would
/// otherwise hold the `RefMut` across each `.await` — a panic the moment a
/// loop drew from the same cell.
pub(crate) struct SharedEntropy<'a>(pub &'a RefCell<Box<dyn Entropy>>);

impl Entropy for SharedEntropy<'_> {
fn fill(&mut self, dest: &mut [u8]) -> Result<(), EntropyError> {
self.0.borrow_mut().fill(dest)
}
}

/// A fresh 32-byte HPKE ephemeral scalar, or a closed failure.
///
/// Reuse across two seals under one recipient key is a confidentiality break, so
Expand Down
285 changes: 265 additions & 20 deletions crates/engine/src/facade.rs

Large diffs are not rendered by default.

12 changes: 8 additions & 4 deletions crates/engine/src/seams/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -91,14 +91,18 @@ pub type SeamResult<T> = Result<T, SeamError>;
pub trait SeamTypes {
/// Durable floor storage ([`FloorStore`]).
type FloorStore: FloorStore;
/// Dumb `/routing/v1` byte mover ([`RecordTransport`]).
type RecordTransport: RecordTransport;
/// Dumb `/routing/v1` byte mover ([`RecordTransport`]). `Clone + 'static`
/// because the publish port hands a handle to the background re-PUT it
/// spawns on the scheduler.
type RecordTransport: RecordTransport + Clone + 'static;
/// Plain HTTP ([`Http`]).
type Http: Http;
/// Sealed-blob mailbox transport ([`Mailbox`]).
type Mailbox: Mailbox;
/// Timers, background tasks, wall clock ([`Scheduler`]).
type Scheduler: Scheduler;
/// Timers, background tasks, wall clock ([`Scheduler`]). `Clone + 'static`
/// for the same reason as [`RecordTransport`](Self::RecordTransport): the
/// spawned task owns a handle of its own.
type Scheduler: Scheduler + Clone + 'static;
/// Durable op queue and staged bytes ([`StagingStore`]).
type StagingStore: StagingStore;
/// Durable last-known-good cache ([`SnapshotCache`]).
Expand Down
20 changes: 17 additions & 3 deletions crates/engine/src/settings.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,8 @@ use crate::gate::floor;
use crate::net::eol::is_expired;
use crate::net::fanout_get_verify;
use crate::net::fetch_head_block;
use crate::net::publish::{PublishOutcome, PublishReceipt};
use crate::net::liveness::HeldRecord;
use crate::net::publish::PublishOutcome;
use crate::net::record_publish::{
PreflightError, RecordPublishError, RecordPublishRequest, preflight_settings, publish_record,
};
Expand Down Expand Up @@ -462,6 +463,12 @@ async fn next_revision<F: FloorStore>(
/// Seal `settings` and publish them at [`settings_name`] through the shared
/// publish port, so the record inherits register-first, seq-CAS, and confirm
/// like every other record.
///
/// Returns the confirmed record as a [`HeldRecord`], so the caller can enrol it
/// in the session's renewal set: the settings record carries a client-signed
/// 90-day EOL and the API republisher is keyless, so a name nobody renews
/// lapses on its own and every device without a cached copy then refuses the
/// placement decision fail-closed.
#[allow(clippy::too_many_arguments)]
pub async fn publish_settings<T, H, C, F, Sn, Sch>(
transport: &T,
Expand All @@ -474,7 +481,7 @@ pub async fn publish_settings<T, H, C, F, Sn, Sch>(
orphans: &OrphanHeads,
login_secret: &[u8],
settings: &VaultSettings,
) -> Result<PublishReceipt, SettingsPublishError>
) -> Result<HeldRecord, SettingsPublishError>
where
T: RecordTransport + Clone + 'static,
H: Http,
Expand Down Expand Up @@ -547,7 +554,14 @@ where
floor::advance_sequence_on_unseal(floors, &revision_adopted_key(&name), revision)
.await
.map_err(SettingsPublishError::Floor)?;
Ok(receipt)
Ok(HeldRecord {
routing_key: name.as_str().to_owned(),
record_bytes: receipt.record_bytes,
signer,
head_cid: head.cid().to_owned(),
// The settings record anchors its sealed body and nothing else.
content_cids: Vec::new(),
})
}

/// Resolve the vault settings record, bounded by
Expand Down
Loading