use std::{
future::Future,
sync::atomic::{AtomicU64, Ordering},
};
use saddle_runtime::compiled_route::ClassifiedCompiledRouteAdapter;
use saddle_service::internal::{
CompiledDbPermit, CompiledExecutionCapability, EntryFraming, ExecutionError,
RegisteredRouteExecutionProof, RegistryError,
};
static NEXT_TOKEN: AtomicU64 = AtomicU64::new(1);
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum GeneratedBootstrapError {
EmptyRoutes,
RouteCountMismatch,
ForeignToken,
DuplicateRoute,
InvalidRoute,
InvalidProfile,
}
#[derive(Clone, Copy)]
pub struct GeneratedRoute {
token: u64,
route: &'static [u8],
declared_length: usize,
}
impl GeneratedRoute {
pub(crate) const fn route(self) -> &'static [u8] {
self.route
}
pub(crate) const fn declared_length(self) -> usize {
self.declared_length
}
}
const HTTP1_POST_FIXED_FRAMING_IDENTITY: [u8; 32] = [0x48; 32];
pub const fn generated_http1_framing_identity() -> [u8; 32] {
HTTP1_POST_FIXED_FRAMING_IDENTITY
}
#[derive(Clone, Copy)]
pub struct GeneratedHttp1StaticFacts {
pub route_count: usize,
pub route_set_identity: [u8; 32],
pub framing_identity: [u8; 32],
pub static_layout_identity: [u8; 32],
pub db_finalization_proof: saddle_db::internal::QueryOptionalFinalizationProof,
}
#[derive(Clone, Copy)]
pub struct ApprovedGeneratedHttp1Facts {
token: u64,
facts: GeneratedHttp1StaticFacts,
}
pub trait GeneratedContextFactory<C>: Send + Sync + 'static {
fn make_context(
&self,
memory: &saddle_admission::RequestMemory,
database: Option<&saddle_db::Database>,
) -> C;
}
pub fn generated_bootstrap_type_identity<A, C, const ROUTES: usize>() -> [u8; 32]
where
A: GeneratedCompiledAdapter,
C: GeneratedContextFactory<A::Context>,
{
let mut lanes = [
0xcbf2_9ce4_8422_2325_u64,
0x9e37_79b1_85eb_ca87,
0xc2b2_ae3d_27d4_eb4f,
0x1656_67b1_9e37_79f9,
];
for (index, byte) in core::any::type_name::<(A, C)>()
.bytes()
.chain(ROUTES.to_le_bytes())
.enumerate()
{
for (lane, prime) in lanes.iter_mut().zip([
0x0000_0100_0000_01b3,
0x9e37_79b1_85eb_ca87,
0xc2b2_ae3d_27d4_eb4f,
0x1656_67b1_9e37_79f9,
]) {
*lane ^= u64::from(byte).wrapping_add(index as u64);
*lane = lane.wrapping_mul(prime);
*lane ^= *lane >> 29;
}
}
let mut identity = [0; 32];
for (index, lane) in lanes.into_iter().enumerate() {
identity[index * 8..(index + 1) * 8].copy_from_slice(&lane.to_le_bytes());
}
identity
}
pub trait GeneratedCompiledAdapter:
ClassifiedCompiledRouteAdapter<Proof = RegisteredRouteExecutionProof>
{
fn lookup_generated(
&self,
route: &[u8],
declared_length: usize,
) -> Result<RegisteredRouteExecutionProof, RegistryError>;
}
impl<E, C, F, const BODY: usize, const OUTPUT: usize> GeneratedCompiledAdapter
for CompiledExecutionCapability<E, C, F, BODY, OUTPUT>
where
E: Fn(
usize,
C,
saddle_admission::ManagedBytes,
CompiledDbPermit,
saddle_admission::ManagedResponseBuilder,
) -> F
+ Send
+ Sync
+ 'static,
C: Send + Unpin + 'static,
F: Future<Output = Result<saddle_admission::ManagedResponse, ExecutionError>> + Send + 'static,
{
fn lookup_generated(
&self,
route: &[u8],
declared_length: usize,
) -> Result<RegisteredRouteExecutionProof, RegistryError> {
self.lookup(
route,
EntryFraming::post_managed_content_length(),
Some(declared_length as u64),
)
}
}
impl<E, C, F, const BODY: usize, const OUTPUT: usize> GeneratedCompiledAdapter
for saddle_service::internal::CompiledExecutionWithCapacityLeaf<E, C, F, BODY, OUTPUT>
where
E: Fn(
usize,
C,
saddle_admission::ManagedBytes,
CompiledDbPermit,
saddle_admission::ManagedResponseBuilder,
) -> F
+ Send
+ Sync
+ 'static,
C: Send + Unpin + 'static,
F: Future<Output = Result<saddle_admission::ManagedResponse, ExecutionError>> + Send + 'static,
{
fn lookup_generated(
&self,
route: &[u8],
declared_length: usize,
) -> Result<RegisteredRouteExecutionProof, RegistryError> {
self.lookup(
route,
EntryFraming::post_managed_content_length(),
Some(declared_length as u64),
)
}
}
pub(crate) trait ProductionServiceBundle: GeneratedCompiledAdapter + Sized {
fn common_identities(&self) -> [[u8; 32]; 3];
fn owner_generation(&self) -> u64;
fn route_identities(&self) -> Box<[u64]>;
fn termination_topology(&self) -> [u64; 4];
fn production_fact_input(
&self,
) -> Result<
saddle_service::internal::ServiceProductionFactInput,
saddle_service::internal::ServiceProductionFactError,
>;
fn compose_generated<T>(
self,
runtime: saddle_runtime::capacity_leaf::VerifiedRuntimeBuildCapacityLeaf,
calibration: saddle_runtime::capacity_leaf::VerifiedRuntimeBuildCalibrationOwner,
termination: T,
) -> Result<
(Self, saddle_admission::ComposedGeneratedStartupFactsOwner),
saddle_admission::GeneratedStartupComposerError,
>
where
T: saddle_admission::GeneratedTerminationTopologyWorkOwner;
}
impl<E, C, F, const BODY: usize, const OUTPUT: usize> ProductionServiceBundle
for saddle_service::internal::CompiledExecutionWithCapacityLeaf<E, C, F, BODY, OUTPUT>
where
E: Fn(
usize,
C,
saddle_admission::ManagedBytes,
CompiledDbPermit,
saddle_admission::ManagedResponseBuilder,
) -> F
+ Send
+ Sync
+ 'static,
C: Send + Unpin + 'static,
F: Future<Output = Result<saddle_admission::ManagedResponse, ExecutionError>> + Send + 'static,
{
fn common_identities(&self) -> [[u8; 32]; 3] {
saddle_admission::ServiceCapacitySourceLeaf::common_identities(&self.capacity_source())
}
fn owner_generation(&self) -> u64 {
saddle_admission::ServiceCapacitySourceLeaf::owner_generation(&self.capacity_source())
}
fn route_identities(&self) -> Box<[u64]> {
let source = self.capacity_source();
(0..saddle_admission::ServiceCapacitySourceLeaf::route_count(&source))
.filter_map(|index| {
saddle_admission::ServiceCapacitySourceLeaf::route_identity(&source, index)
})
.collect()
}
fn termination_topology(&self) -> [u64; 4] {
let source = self.capacity_source();
let routes = saddle_admission::ServiceCapacitySourceLeaf::route_count(&source);
let db_routes = (0..routes)
.filter(|index| {
saddle_admission::ServiceCapacitySourceLeaf::db_connections(&source, *index)
.unwrap_or(0)
> 0
})
.count();
[db_routes as u64, 1, 1, 3]
}
fn production_fact_input(
&self,
) -> Result<
saddle_service::internal::ServiceProductionFactInput,
saddle_service::internal::ServiceProductionFactError,
> {
self.production_fact_input()
}
fn compose_generated<T>(
self,
runtime: saddle_runtime::capacity_leaf::VerifiedRuntimeBuildCapacityLeaf,
calibration: saddle_runtime::capacity_leaf::VerifiedRuntimeBuildCalibrationOwner,
termination: T,
) -> Result<
(Self, saddle_admission::ComposedGeneratedStartupFactsOwner),
saddle_admission::GeneratedStartupComposerError,
>
where
T: saddle_admission::GeneratedTerminationTopologyWorkOwner,
{
#[cfg(test)]
{
let generated = saddle_admission::compose_generated_startup_facts_test_fixture(
self.capacity_source(),
runtime,
calibration,
termination,
)?;
Ok((self, generated))
}
#[cfg(not(test))]
{
let _ = (runtime, calibration, termination);
Err(saddle_admission::GeneratedStartupComposerError::InvalidTopology)
}
}
}
pub struct GeneratedRuntimeAdapter<A, C> {
adapter: A,
context_factory: C,
database: Option<saddle_db::Database>,
}
impl<A, C> GeneratedCompiledAdapter for GeneratedRuntimeAdapter<A, C>
where
A: GeneratedCompiledAdapter,
C: GeneratedContextFactory<A::Context>,
{
fn lookup_generated(
&self,
route: &[u8],
declared_length: usize,
) -> Result<RegisteredRouteExecutionProof, RegistryError> {
self.adapter.lookup_generated(route, declared_length)
}
}
impl<A, C> GeneratedRuntimeAdapter<A, C> {
pub(crate) fn production_fact_input(
&self,
) -> Result<
saddle_service::internal::ServiceProductionFactInput,
saddle_service::internal::ServiceProductionFactError,
>
where
A: ProductionServiceBundle,
{
self.adapter.production_fact_input()
}
pub(crate) fn attach_database(mut self, database: Option<saddle_db::Database>) -> Self {
self.database = database;
self
}
pub(crate) fn compose_generated<T>(
self,
runtime: saddle_runtime::capacity_leaf::VerifiedRuntimeBuildCapacityLeaf,
calibration: saddle_runtime::capacity_leaf::VerifiedRuntimeBuildCalibrationOwner,
termination: T,
) -> Result<
(Self, saddle_admission::ComposedGeneratedStartupFactsOwner),
saddle_admission::GeneratedStartupComposerError,
>
where
A: ProductionServiceBundle,
T: saddle_admission::GeneratedTerminationTopologyWorkOwner,
{
let (adapter, generated) =
self.adapter
.compose_generated(runtime, calibration, termination)?;
Ok((Self { adapter, ..self }, generated))
}
}
impl<A, C> ClassifiedCompiledRouteAdapter for GeneratedRuntimeAdapter<A, C>
where
A: GeneratedCompiledAdapter,
C: GeneratedContextFactory<A::Context>,
{
type Proof = A::Proof;
type Context = ();
type Error = A::Error;
type Future = A::Future;
fn managed_commitment(&self, proof: Self::Proof) -> Result<usize, Self::Error> {
self.adapter.managed_commitment(proof)
}
fn response_capacity(&self, proof: Self::Proof) -> Result<usize, Self::Error> {
self.adapter.response_capacity(proof)
}
fn db_resources<'a>(
&self,
proof: Self::Proof,
domain: Option<&'a saddle_admission::DbPermitDomain>,
) -> Result<saddle_admission::DbRouteResources<'a>, Self::Error> {
self.adapter.db_resources(proof, domain)
}
fn execute(
&self,
proof: Self::Proof,
(): Self::Context,
body: saddle_admission::ManagedBytes,
permit: Option<saddle_admission::DbRequestPermit>,
memory: &saddle_admission::RequestMemory,
) -> Self::Future {
let context = self
.context_factory
.make_context(memory, self.database.as_ref());
self.adapter.execute(proof, context, body, permit, memory)
}
}
pub struct GeneratedBootstrapToken {
identity: u64,
}
impl GeneratedBootstrapToken {
pub(crate) fn issue() -> Self {
let identity = NEXT_TOKEN
.fetch_update(Ordering::AcqRel, Ordering::Acquire, |value| {
value.checked_add(1)
})
.unwrap_or_else(|_| std::process::abort());
Self { identity }
}
pub fn bind_route(
&self,
route: &'static [u8],
declared_length: usize,
) -> Result<GeneratedRoute, GeneratedBootstrapError> {
if route.len() < 2
|| route[0] != b'/'
|| declared_length == 0
|| route
.iter()
.any(|byte| !matches!(byte, b'/' | b'a'..=b'z' | b'0'..=b'9' | b'-' | b'_'))
{
return Err(GeneratedBootstrapError::InvalidRoute);
}
Ok(GeneratedRoute {
token: self.identity,
route,
declared_length,
})
}
pub fn approve_http1_facts(
&self,
facts: GeneratedHttp1StaticFacts,
) -> Result<ApprovedGeneratedHttp1Facts, GeneratedBootstrapError> {
if facts.route_count == 0
|| facts.route_set_identity == [0; 32]
|| facts.framing_identity != HTTP1_POST_FIXED_FRAMING_IDENTITY
|| facts.static_layout_identity == [0; 32]
|| facts.db_finalization_proof.cancel_external_io_awaits() != 0
|| facts.db_finalization_proof.shutdown_external_io_awaits() != 0
|| facts.db_finalization_proof.panic_external_io_awaits() != 0
|| !facts
.db_finalization_proof
.releases_pool_size_before_permit()
|| !facts
.db_finalization_proof
.normal_return_requires_termination_bound()
{
return Err(GeneratedBootstrapError::InvalidProfile);
}
Ok(ApprovedGeneratedHttp1Facts {
token: self.identity,
facts,
})
}
pub fn consume<A, C, const ROUTES: usize>(
self,
adapter: A,
context_factory: C,
routes: [GeneratedRoute; ROUTES],
profile: ApprovedGeneratedHttp1Facts,
) -> Result<GeneratedApplicationBootstrap<A, C, ROUTES>, GeneratedBootstrapError>
where
A: GeneratedCompiledAdapter,
C: GeneratedContextFactory<A::Context>,
{
if ROUTES == 0 {
return Err(GeneratedBootstrapError::EmptyRoutes);
}
if profile.token != self.identity {
return Err(GeneratedBootstrapError::ForeignToken);
}
if profile.facts.route_count != ROUTES {
return Err(GeneratedBootstrapError::RouteCountMismatch);
}
let mut proofs = [None; ROUTES];
for (index, route) in routes.iter().enumerate() {
if route.token != self.identity {
return Err(GeneratedBootstrapError::ForeignToken);
}
if routes[..index]
.iter()
.any(|existing| existing.route == route.route)
{
return Err(GeneratedBootstrapError::DuplicateRoute);
}
proofs[index] = Some(
adapter
.lookup_generated(route.route, route.declared_length)
.map_err(|_| GeneratedBootstrapError::InvalidRoute)?,
);
}
let proofs = proofs.map(|proof| {
proof.unwrap_or_else(|| {
std::process::abort()
})
});
Ok(GeneratedApplicationBootstrap {
adapter,
context_factory,
routes,
proofs,
static_facts: profile.facts,
})
}
}
pub struct GeneratedApplicationBootstrap<A, C, const ROUTES: usize>
where
A: GeneratedCompiledAdapter,
C: GeneratedContextFactory<A::Context>,
{
adapter: A,
context_factory: C,
routes: [GeneratedRoute; ROUTES],
proofs: [RegisteredRouteExecutionProof; ROUTES],
static_facts: GeneratedHttp1StaticFacts,
}
pub(crate) struct GeneratedBootstrapParts<A, C, const ROUTES: usize>
where
A: GeneratedCompiledAdapter,
C: GeneratedContextFactory<A::Context>,
{
pub adapter: GeneratedRuntimeAdapter<A, C>,
pub routes: [GeneratedRoute; ROUTES],
pub proofs: [RegisteredRouteExecutionProof; ROUTES],
pub static_facts: GeneratedHttp1StaticFacts,
}
pub(crate) struct ProductionBundleFacts {
pub common: [[u8; 32]; 3],
pub generation: u64,
pub routes: Box<[u64]>,
pub termination_topology: [u64; 4],
}
impl<A, C, const ROUTES: usize> GeneratedApplicationBootstrap<A, C, ROUTES>
where
A: GeneratedCompiledAdapter,
C: GeneratedContextFactory<A::Context>,
{
pub(crate) fn production_bundle_facts(&self) -> ProductionBundleFacts
where
A: ProductionServiceBundle,
{
ProductionBundleFacts {
common: self.adapter.common_identities(),
generation: self.adapter.owner_generation(),
routes: self.adapter.route_identities(),
termination_topology: self.adapter.termination_topology(),
}
}
pub(crate) fn into_parts(
self,
database: Option<saddle_db::Database>,
) -> GeneratedBootstrapParts<A, C, ROUTES> {
GeneratedBootstrapParts {
adapter: GeneratedRuntimeAdapter {
adapter: self.adapter,
context_factory: self.context_factory,
database,
},
routes: self.routes,
proofs: self.proofs,
static_facts: self.static_facts,
}
}
}
#[cfg(test)]
mod tests {
use std::future::{Ready, ready};
use saddle_admission::{ManagedBytes, ManagedResponse, ManagedResponseBuilder};
use saddle_service::{
Service, ServiceDescriptor,
internal::{
CompiledExecutionBuilder, CompiledRouteExecutionProof, CompiledServiceRegistryBuilder,
EntryCapabilityLimits, ExecutionLayoutProof, FixedManagedCodec, ManagedU64,
},
};
use super::*;
const BODY: usize = 8;
const OUTPUT: usize = 8;
struct Route;
impl Service for Route {
type Request = ManagedU64;
type Response = ManagedU64;
}
fn layout(slot: usize) -> Option<CompiledRouteExecutionProof> {
if slot != 0 {
return None;
}
ExecutionLayoutProof::bind::<
Route,
BODY,
OUTPUT,
ManagedU64,
ManagedU64,
FixedManagedCodec<ManagedU64, ManagedU64>,
Ready<Result<ManagedU64, ExecutionError>>,
>()
.ok()
.map(ExecutionLayoutProof::without_db)
}
fn dispatch(
slot: usize,
_context: (),
body: ManagedBytes,
db: CompiledDbPermit,
mut output: ManagedResponseBuilder,
) -> Ready<Result<ManagedResponse, ExecutionError>> {
let result = (|| {
if slot != 0 || !db.is_none() || body.len() != BODY {
return Err(ExecutionError::InvalidInput);
}
output
.try_extend_from_slice(body.as_slice())
.map_err(|_| ExecutionError::OutputTooLarge)?;
drop(body);
output
.finish()
.map_err(|_| ExecutionError::ResponseReservationFailed)
})();
ready(result)
}
fn capability() -> impl GeneratedCompiledAdapter<Context = ()> {
let mut registry = CompiledServiceRegistryBuilder::new(1).unwrap();
registry
.register::<Route>(ServiceDescriptor::new("route", "fixture", "route"))
.unwrap();
let mut builder = CompiledExecutionBuilder::<_, _, _, _, BODY, OUTPUT>::new(
registry.freeze().unwrap(),
dispatch,
layout,
EntryCapabilityLimits::new(1, 16, 32, 1, 1),
)
.unwrap();
builder
.expose::<Route>(b"/route", EntryFraming::post_managed_content_length())
.unwrap();
builder.freeze().unwrap()
}
struct ContextFactory;
impl GeneratedContextFactory<()> for ContextFactory {
fn make_context(
&self,
_memory: &saddle_admission::RequestMemory,
_database: Option<&saddle_db::Database>,
) {
}
}
fn profile(route_count: usize) -> GeneratedHttp1StaticFacts {
GeneratedHttp1StaticFacts {
route_count,
route_set_identity: [1; 32],
framing_identity: generated_http1_framing_identity(),
static_layout_identity: [2; 32],
db_finalization_proof: saddle_db::internal::query_optional_finalization_proof(),
}
}
fn error<T>(result: Result<T, GeneratedBootstrapError>) -> GeneratedBootstrapError {
match result {
Ok(_) => panic!("expected generated bootstrap rejection"),
Err(error) => error,
}
}
#[test]
fn token_derives_same_adapter_proofs_and_publishes_one_owner() {
let token = GeneratedBootstrapToken::issue();
let route = token.bind_route(b"/route", BODY).unwrap();
let profile = token.approve_http1_facts(profile(1)).unwrap();
let owner = token
.consume(capability(), ContextFactory, [route], profile)
.unwrap();
fn assert_send<T: Send>(_: &T) {}
assert_send(&owner);
let parts = owner.into_parts(None);
assert_eq!(parts.routes[0].route, b"/route");
assert_eq!(parts.static_facts.route_count, 1);
assert_eq!(
parts
.adapter
.lookup_generated(b"/route", BODY)
.unwrap()
.response_capacity(),
parts.proofs[0].response_capacity()
);
}
#[test]
fn rejects_foreign_token_duplicate_missing_and_unknown_route() {
let first = GeneratedBootstrapToken::issue();
let foreign = first.bind_route(b"/route", BODY).unwrap();
let second = GeneratedBootstrapToken::issue();
let approved = second.approve_http1_facts(profile(1)).unwrap();
assert_eq!(
error(second.consume(capability(), ContextFactory, [foreign], approved)),
GeneratedBootstrapError::ForeignToken
);
let token = GeneratedBootstrapToken::issue();
let route = token.bind_route(b"/route", BODY).unwrap();
let approved = token.approve_http1_facts(profile(2)).unwrap();
assert_eq!(
error(token.consume(capability(), ContextFactory, [route, route], approved)),
GeneratedBootstrapError::DuplicateRoute
);
let token = GeneratedBootstrapToken::issue();
let approved = token.approve_http1_facts(profile(1)).unwrap();
assert_eq!(
error(token.consume(capability(), ContextFactory, [], approved)),
GeneratedBootstrapError::EmptyRoutes
);
let token = GeneratedBootstrapToken::issue();
assert_eq!(
error(token.approve_http1_facts(profile(0))),
GeneratedBootstrapError::InvalidProfile
);
let token = GeneratedBootstrapToken::issue();
let mut missing_layout = profile(1);
missing_layout.static_layout_identity = [0; 32];
assert_eq!(
error(token.approve_http1_facts(missing_layout)),
GeneratedBootstrapError::InvalidProfile
);
let token = GeneratedBootstrapToken::issue();
let unknown = token.bind_route(b"/unknown", BODY).unwrap();
let approved = token.approve_http1_facts(profile(1)).unwrap();
assert_eq!(
error(token.consume(capability(), ContextFactory, [unknown], approved)),
GeneratedBootstrapError::InvalidRoute
);
}
}