use std::marker::PhantomData;
use saddle_admission::{
PendingStartupPlan, StartupCreatorError, StartupInputAdapter,
VerifiedDbTerminationServiceProofOwner, VerifiedFilesystemTerminationServiceProofOwner,
VerifiedGeneratedStartupFactsOwner, VerifiedSchedulerTerminationServiceProofOwner,
VerifiedStartupEnvelopeOwner, VerifiedSupervisorTerminationServiceProofOwner,
create_pending_startup_plan,
};
const CPU_UNITS_PER_CORE: usize = 100;
#[doc(hidden)]
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum FiniteStartupPolicy {
ExecutionProtected,
Balanced,
BurstProtected,
}
#[doc(hidden)]
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum ResourceEnvelopeVerificationError {
InvalidCpu,
InvalidMemory,
InvalidCredits,
InvalidTime,
InvalidIdentity,
SizeOverflow,
AdmissionRejected,
}
#[doc(hidden)]
pub trait DeploymentResourceAttestation: Sized {
fn cpu_quota_us(&self) -> u64;
fn cpu_period_us(&self) -> u64;
fn effective_cpuset(&self) -> &[usize];
fn memory_max_bytes(&self) -> usize;
fn memory_high_bytes(&self) -> usize;
fn saddle_logical_memory_bytes(&self) -> usize;
fn saddle_requested_memory_bytes(&self) -> usize;
fn registration_credits(&self) -> usize;
fn event_credits(&self) -> usize;
fn db_connection_credits(&self) -> usize;
fn db_operation_credits(&self) -> usize;
fn public_time_policy_ms(&self) -> [u64; 2];
fn resource_attestation(&self) -> [u8; 32];
fn public_time_policy_attestation(&self) -> [u8; 32];
fn environment_identity(&self) -> [u8; 32];
fn supervisor_attestation(&self) -> [u8; 32];
fn build_identity(&self) -> [u8; 32];
fn gate_identity(&self) -> [u8; 32];
}
#[doc(hidden)]
#[derive(Debug)]
pub struct VerifiedResourceEnvelope {
available: [usize; 7],
public_time_policy: [u64; 2],
pub(crate) resource_attestation: [u8; 32],
public_time_policy_attestation: [u8; 32],
pub(crate) environment_identity: [u8; 32],
pub(crate) supervisor_attestation: [u8; 32],
pub(crate) build_identity: [u8; 32],
pub(crate) gate_identity: [u8; 32],
}
struct RuntimeStartupAdapter<G, D, F, S, U> {
policy: FiniteStartupPolicy,
provenance: [u8; 32],
_owners: PhantomData<RuntimeStartupOwnerTypes<G, D, F, S, U>>,
}
type RuntimeStartupOwnerTypes<G, D, F, S, U> = fn() -> (G, D, F, S, U);
type RuntimeTerminationProofOwners<D, F, S, U> = (D, F, S, U);
impl<G, D, F, S, U> StartupInputAdapter for RuntimeStartupAdapter<G, D, F, S, U>
where
G: VerifiedGeneratedStartupFactsOwner,
D: VerifiedDbTerminationServiceProofOwner,
F: VerifiedFilesystemTerminationServiceProofOwner,
S: VerifiedSchedulerTerminationServiceProofOwner,
U: VerifiedSupervisorTerminationServiceProofOwner,
{
type VerifiedEnvelopeOwner = VerifiedResourceEnvelope;
type GeneratedFactsOwner = G;
type DbServiceProofOwner = D;
type FilesystemServiceProofOwner = F;
type SchedulerServiceProofOwner = S;
type SupervisorServiceProofOwner = U;
fn adapter_provenance(&self) -> [u8; 32] {
self.provenance
}
fn finite_policy_version(&self) -> u8 {
match self.policy {
FiniteStartupPolicy::ExecutionProtected => 1,
FiniteStartupPolicy::Balanced => 2,
FiniteStartupPolicy::BurstProtected => 3,
}
}
fn db_return_issuer_identity(&self) -> [u8; 32] {
crate::db_return_budget::DB_RETURN_BUDGET_ISSUER_IDENTITY
}
}
impl VerifiedStartupEnvelopeOwner for VerifiedResourceEnvelope {
fn resource_attestation(&self) -> [u8; 32] {
self.resource_attestation
}
fn available_resources(&self) -> [usize; 7] {
self.available
}
fn public_time_policy_ms(&self) -> [u64; 2] {
self.public_time_policy
}
fn public_time_policy_attestation(&self) -> [u8; 32] {
self.public_time_policy_attestation
}
fn supervisor_attestation(&self) -> [u8; 32] {
self.supervisor_attestation
}
fn build_identity(&self) -> [u8; 32] {
self.build_identity
}
fn gate_identity(&self) -> [u8; 32] {
self.gate_identity
}
}
#[doc(hidden)]
pub fn verify_resource_envelope<A: DeploymentResourceAttestation>(
attestation: A,
) -> Result<VerifiedResourceEnvelope, ResourceEnvelopeVerificationError> {
let cpuset = attestation.effective_cpuset();
if attestation.cpu_quota_us() == 0
|| attestation.cpu_period_us() == 0
|| cpuset.is_empty()
|| cpuset.windows(2).any(|pair| pair[0] >= pair[1])
{
return Err(ResourceEnvelopeVerificationError::InvalidCpu);
}
let quota_units = usize::try_from(attestation.cpu_quota_us())
.ok()
.and_then(|quota| quota.checked_mul(CPU_UNITS_PER_CORE))
.ok_or(ResourceEnvelopeVerificationError::SizeOverflow)?
/ usize::try_from(attestation.cpu_period_us())
.map_err(|_| ResourceEnvelopeVerificationError::SizeOverflow)?;
let cpuset_units = cpuset
.len()
.checked_mul(CPU_UNITS_PER_CORE)
.ok_or(ResourceEnvelopeVerificationError::SizeOverflow)?;
let cpu_units = quota_units.min(cpuset_units);
if cpu_units < CPU_UNITS_PER_CORE {
return Err(ResourceEnvelopeVerificationError::InvalidCpu);
}
let memory_max = attestation.memory_max_bytes();
let memory_high = attestation.memory_high_bytes();
let logical = attestation.saddle_logical_memory_bytes();
let requested = attestation.saddle_requested_memory_bytes();
if memory_max == 0
|| memory_high == 0
|| logical == 0
|| requested == 0
|| memory_high > memory_max
|| logical > memory_high
|| requested > memory_high
{
return Err(ResourceEnvelopeVerificationError::InvalidMemory);
}
let registration = attestation.registration_credits();
let events = attestation.event_credits();
let db_connections = attestation.db_connection_credits();
let db_operations = attestation.db_operation_credits();
if registration == 0 || events == 0 || ((db_connections == 0) != (db_operations == 0)) {
return Err(ResourceEnvelopeVerificationError::InvalidCredits);
}
let public_time_policy = attestation.public_time_policy_ms();
if public_time_policy.contains(&0) {
return Err(ResourceEnvelopeVerificationError::InvalidTime);
}
let environment = attestation.environment_identity();
let resource_attestation = attestation.resource_attestation();
let public_time_policy_attestation = attestation.public_time_policy_attestation();
let supervisor_attestation = attestation.supervisor_attestation();
let build_identity = attestation.build_identity();
let gate_identity = attestation.gate_identity();
if [
environment,
resource_attestation,
public_time_policy_attestation,
supervisor_attestation,
build_identity,
gate_identity,
]
.contains(&[0; 32])
{
return Err(ResourceEnvelopeVerificationError::InvalidIdentity);
}
Ok(VerifiedResourceEnvelope {
available: [
cpu_units,
logical,
requested,
registration,
events,
db_connections,
db_operations,
],
public_time_policy,
resource_attestation,
public_time_policy_attestation,
environment_identity: environment,
supervisor_attestation,
build_identity,
gate_identity,
})
}
#[doc(hidden)]
pub fn create_runtime_startup_plan<A, G, D, F, S, U>(
policy: FiniteStartupPolicy,
adapter_provenance: [u8; 32],
attestation: A,
generated: G,
termination_proofs: RuntimeTerminationProofOwners<D, F, S, U>,
) -> Result<PendingStartupPlan, ResourceEnvelopeVerificationError>
where
A: DeploymentResourceAttestation,
G: VerifiedGeneratedStartupFactsOwner,
D: VerifiedDbTerminationServiceProofOwner,
F: VerifiedFilesystemTerminationServiceProofOwner,
S: VerifiedSchedulerTerminationServiceProofOwner,
U: VerifiedSupervisorTerminationServiceProofOwner,
{
let (db_service, filesystem_service, scheduler_service, supervisor_service) =
termination_proofs;
if adapter_provenance == [0; 32] {
return Err(ResourceEnvelopeVerificationError::InvalidIdentity);
}
let envelope = verify_resource_envelope(attestation)?;
create_runtime_startup_plan_from_verified(
policy,
adapter_provenance,
envelope,
generated,
(
db_service,
filesystem_service,
scheduler_service,
supervisor_service,
),
)
}
#[doc(hidden)]
pub fn create_runtime_startup_plan_from_verified<G, D, F, S, U>(
policy: FiniteStartupPolicy,
adapter_provenance: [u8; 32],
envelope: VerifiedResourceEnvelope,
generated: G,
termination_proofs: RuntimeTerminationProofOwners<D, F, S, U>,
) -> Result<PendingStartupPlan, ResourceEnvelopeVerificationError>
where
G: VerifiedGeneratedStartupFactsOwner,
D: VerifiedDbTerminationServiceProofOwner,
F: VerifiedFilesystemTerminationServiceProofOwner,
S: VerifiedSchedulerTerminationServiceProofOwner,
U: VerifiedSupervisorTerminationServiceProofOwner,
{
let (db_service, filesystem_service, scheduler_service, supervisor_service) =
termination_proofs;
if adapter_provenance == [0; 32] {
return Err(ResourceEnvelopeVerificationError::InvalidIdentity);
}
create_pending_startup_plan(
RuntimeStartupAdapter::<G, D, F, S, U> {
policy,
provenance: adapter_provenance,
_owners: PhantomData,
},
envelope,
generated,
db_service,
filesystem_service,
scheduler_service,
supervisor_service,
)
.map_err(|error| match error {
StartupCreatorError::InvalidAdapterFacts => {
ResourceEnvelopeVerificationError::InvalidIdentity
}
StartupCreatorError::SolverRejected => ResourceEnvelopeVerificationError::AdmissionRejected,
})
}
#[cfg(test)]
pub(crate) mod tests {
use saddle_admission::{RouteCapacityFact, StartupActualFacts, StartupClaimKind};
use super::*;
struct Attestation {
cpu_quota: u64,
cpuset: Vec<usize>,
memory_high: usize,
db: usize,
public_time_policy: [u64; 2],
identity: [u8; 32],
}
impl DeploymentResourceAttestation for Attestation {
fn cpu_quota_us(&self) -> u64 {
self.cpu_quota
}
fn cpu_period_us(&self) -> u64 {
100_000
}
fn effective_cpuset(&self) -> &[usize] {
&self.cpuset
}
fn memory_max_bytes(&self) -> usize {
16_000_000
}
fn memory_high_bytes(&self) -> usize {
self.memory_high
}
fn saddle_logical_memory_bytes(&self) -> usize {
self.memory_high / 2
}
fn saddle_requested_memory_bytes(&self) -> usize {
self.memory_high / 2
}
fn registration_credits(&self) -> usize {
260
}
fn event_credits(&self) -> usize {
260
}
fn db_connection_credits(&self) -> usize {
self.db
}
fn db_operation_credits(&self) -> usize {
self.db
}
fn public_time_policy_ms(&self) -> [u64; 2] {
self.public_time_policy
}
fn resource_attestation(&self) -> [u8; 32] {
self.identity
}
fn public_time_policy_attestation(&self) -> [u8; 32] {
[2; 32]
}
fn environment_identity(&self) -> [u8; 32] {
self.identity
}
fn supervisor_attestation(&self) -> [u8; 32] {
[3; 32]
}
fn build_identity(&self) -> [u8; 32] {
[4; 32]
}
fn gate_identity(&self) -> [u8; 32] {
[5; 32]
}
}
fn attestation() -> Attestation {
Attestation {
cpu_quota: 800_000,
cpuset: (0..8).collect(),
memory_high: 8_000_000,
db: 64,
public_time_policy: [2_000, 8_000],
identity: [1; 32],
}
}
struct Generated;
const ROUTES: [RouteCapacityFact; 1] = [RouteCapacityFact {
route_id: 1,
managed_commitment_bytes: 1_024,
managed_objects_peak: 2,
framework_bytes: 512,
task_storage_bytes: 4_096,
response_carrier_bytes: 512,
db_connections: 1,
db_operations: 1,
}];
impl VerifiedGeneratedStartupFactsOwner for Generated {
fn costs_attestation(&self) -> [u8; 32] {
[6; 32]
}
fn cost_vectors(&self) -> [[usize; 7]; 5] {
[
[0, 16_000, 16_000, 3, 3, 0, 0],
[100, 100, 100, 0, 0, 0, 0],
[0, 20_000, 20_000, 1, 1, 0, 0],
[0, 1_000, 1_000, 1, 1, 0, 0],
[0, 1_000, 1_000, 0, 0, 1, 1],
]
}
fn allocator_costs(&self) -> [usize; 2] {
[4_000, 4_000]
}
fn support_attestation(&self) -> [u8; 32] {
[7; 32]
}
fn support_limits(&self) -> [usize; 4] {
[8, 128, 128, 64]
}
fn route_set_attestation(&self) -> [u8; 32] {
[8; 32]
}
fn routes(&self) -> &'static [RouteCapacityFact] {
&ROUTES
}
fn closure_constants(&self) -> [usize; 6] {
[1, 2, 3, 64, 1_024, 512]
}
fn termination_topology(&self) -> [u64; 4] {
[1, 1, 1, 2]
}
fn termination_topology_identity(&self) -> [u8; 32] {
[9; 32]
}
fn db_return_work_identity(&self) -> [u8; 32] {
[10; 32]
}
fn writer_work_identity(&self) -> [u8; 32] {
[11; 32]
}
fn runtime_work_identity(&self) -> [u8; 32] {
[12; 32]
}
}
struct DbService;
struct FilesystemService;
struct SchedulerService;
struct SupervisorService;
impl VerifiedDbTerminationServiceProofOwner for DbService {
fn work_identity(&self) -> [u8; 32] {
[10; 32]
}
fn service_attestation(&self) -> [u8; 32] {
[13; 32]
}
fn max_service_nanos(&self) -> u64 {
2_000_000
}
}
impl VerifiedFilesystemTerminationServiceProofOwner for FilesystemService {
fn work_identity(&self) -> [u8; 32] {
[11; 32]
}
fn service_attestation(&self) -> [u8; 32] {
[14; 32]
}
fn max_service_nanos(&self) -> u64 {
1_000_000
}
}
impl VerifiedSchedulerTerminationServiceProofOwner for SchedulerService {
fn runtime_work_identity(&self) -> [u8; 32] {
[12; 32]
}
fn service_attestation(&self) -> [u8; 32] {
[15; 32]
}
fn max_delivery_nanos(&self) -> u64 {
1_000_000
}
}
impl VerifiedSupervisorTerminationServiceProofOwner for SupervisorService {
fn service_attestation(&self) -> [u8; 32] {
[16; 32]
}
fn shutdown_delivery_nanos(&self) -> u64 {
1_000_000
}
fn delivery_headroom_nanos(&self) -> u64 {
1_000_000
}
}
pub(crate) fn pending_plan() -> PendingStartupPlan {
create_runtime_startup_plan(
FiniteStartupPolicy::Balanced,
[9; 32],
attestation(),
Generated,
(
DbService,
FilesystemService,
SchedulerService,
SupervisorService,
),
)
.unwrap()
}
#[test]
fn verified_owner_enters_the_only_controlled_creator() {
let mut plan = pending_plan();
assert_ne!(
plan.claim(StartupClaimKind::Runtime).unwrap().expected(),
StartupActualFacts::Runtime {
worker_threads: 0,
event_credits: 0,
}
);
}
#[test]
fn invalid_resource_and_identity_inputs_fail_before_creator() {
let mut invalid_cpu = attestation();
invalid_cpu.cpuset = vec![1, 1];
assert_eq!(
verify_resource_envelope(invalid_cpu).unwrap_err(),
ResourceEnvelopeVerificationError::InvalidCpu
);
let mut invalid_memory = attestation();
invalid_memory.memory_high = 0;
assert_eq!(
verify_resource_envelope(invalid_memory).unwrap_err(),
ResourceEnvelopeVerificationError::InvalidMemory
);
let mut invalid_time = attestation();
invalid_time.public_time_policy[1] = 0;
assert_eq!(
verify_resource_envelope(invalid_time).unwrap_err(),
ResourceEnvelopeVerificationError::InvalidTime
);
let mut invalid_identity = attestation();
invalid_identity.identity = [0; 32];
assert_eq!(
verify_resource_envelope(invalid_identity).unwrap_err(),
ResourceEnvelopeVerificationError::InvalidIdentity
);
}
}