use std::alloc::Layout;
use super::fixed_core::FixedWriterEntry;
use super::{
fixed_core::Inner,
linux_backend::LinuxFileBackend,
termination::{FixedProfileDigest, WriterTerminationProof, writer_component_work_proof},
};
const BLOCKS: usize = 4;
const BYTES: usize = 1_024;
const COMMANDS: usize = 6;
const PATH: usize = 256;
const DIRENT_BYTES: usize = 4_096;
const ENTRIES: usize = 32;
const SOURCE_DOMAIN: &[u8] = b"saddle.observability.generated-layout.source-closure.v1";
const BUILD_DOMAIN: &[u8] = b"saddle.observability.generated-layout.build-static.v1";
const PROFILE_SCHEMA_DOMAIN: &[u8] = b"saddle.observability.generated-layout.profile-schema.v1";
const CONFIG_SCHEMA_DOMAIN: &[u8] = b"saddle.observability.fixed-file-config.schema.v1";
const CONFIG_IDENTITY_DOMAIN: &[u8] = b"saddle.observability.fixed-file-config.identity.v1";
const ISSUER_DOMAIN: &[u8] = b"saddle.observability.generated-layout.issuer.v1";
const LEAF_DOMAIN: &[u8] = b"saddle.observability.generated-layout.leaf.v1";
const PROFILE_SCHEMA: &[u8] =
b"blocks,block_bytes,commands,path_bytes,dirent_bytes,entries;fixed_file_config_schema,fixed_file_config_identity,rotate_bytes,retained_files,retention_age_ms,sync_bytes,sync_interval_ms;inner,writer,backend,termination;size,align,offset;aggregate_size,aggregate_align";
const CONFIG_SCHEMA: &[u8] =
b"saddle.observability.fixed-file-config/1;rotate_bytes,retained_files,retention_age_ms,sync_bytes,sync_interval_ms";
const CONFIG: [u64; 5] = [
128 * 1024 * 1024,
8,
7 * 24 * 60 * 60 * 1_000,
4 * 1024 * 1024,
1_000,
];
const FIXED_CORE_SOURCE: &[u8] = include_bytes!("fixed_core.rs");
const BACKEND_SOURCE: &[u8] = include_bytes!("linux_backend.rs");
const TERMINATION_SOURCE: &[u8] = include_bytes!("termination.rs");
mod sealed {
pub trait Sealed {}
}
#[doc(hidden)]
pub trait ObservabilityGeneratedLayoutSource: sealed::Sealed + Sized {
fn identities(&self) -> [[u8; 32]; 4];
fn profile(&self) -> [usize; 6];
fn config_schema_identity(&self) -> [u8; 32];
fn config_identity(&self) -> [u8; 32];
fn config(&self) -> [u64; 5];
fn sizes(&self) -> [usize; 4];
fn alignments(&self) -> [usize; 4];
fn offsets(&self) -> [usize; 4];
fn aggregate_size(&self) -> usize;
fn aggregate_alignment(&self) -> usize;
fn leaf_identity(&self) -> [u8; 32];
}
#[doc(hidden)]
#[derive(Debug)]
pub struct ObservabilityGeneratedLayoutObservation {
identities: [[u8; 32]; 4],
profile: [usize; 6],
config_schema_identity: [u8; 32],
config_identity: [u8; 32],
config: [u64; 5],
sizes: [usize; 4],
alignments: [usize; 4],
offsets: [usize; 4],
aggregate_size: usize,
aggregate_alignment: usize,
leaf_identity: [u8; 32],
}
impl sealed::Sealed for ObservabilityGeneratedLayoutObservation {}
impl ObservabilityGeneratedLayoutSource for ObservabilityGeneratedLayoutObservation {
fn identities(&self) -> [[u8; 32]; 4] {
self.identities
}
fn profile(&self) -> [usize; 6] {
self.profile
}
fn config_schema_identity(&self) -> [u8; 32] {
self.config_schema_identity
}
fn config_identity(&self) -> [u8; 32] {
self.config_identity
}
fn config(&self) -> [u64; 5] {
self.config
}
fn sizes(&self) -> [usize; 4] {
self.sizes
}
fn alignments(&self) -> [usize; 4] {
self.alignments
}
fn offsets(&self) -> [usize; 4] {
self.offsets
}
fn aggregate_size(&self) -> usize {
self.aggregate_size
}
fn aggregate_alignment(&self) -> usize {
self.aggregate_alignment
}
fn leaf_identity(&self) -> [u8; 32] {
self.leaf_identity
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum GeneratedLayoutError {
Overflow,
InvalidLayout,
}
type ReleaseInner = Inner<BLOCKS, BYTES, COMMANDS>;
type ReleaseWriter = FixedWriterEntry<BLOCKS, BYTES, COMMANDS, PATH, DIRENT_BYTES, ENTRIES>;
type ReleaseBackend = LinuxFileBackend<PATH, DIRENT_BYTES, ENTRIES>;
#[doc(hidden)]
pub fn observe_release_fixed_core_layout()
-> Result<ObservabilityGeneratedLayoutObservation, GeneratedLayoutError> {
let layouts = [
Layout::new::<ReleaseInner>(),
Layout::new::<ReleaseWriter>(),
Layout::new::<ReleaseBackend>(),
Layout::new::<WriterTerminationProof>(),
];
let sizes = layouts.map(|layout| layout.size());
let alignments = layouts.map(|layout| layout.align());
if sizes.contains(&0) || alignments.iter().any(|align| !align.is_power_of_two()) {
return Err(GeneratedLayoutError::InvalidLayout);
}
let mut aggregate = layouts[0];
let mut offsets = [0; 4];
for index in 1..layouts.len() {
let (next, offset) = aggregate
.extend(layouts[index])
.map_err(|_| GeneratedLayoutError::Overflow)?;
aggregate = next;
offsets[index] = offset;
}
aggregate = aggregate.pad_to_align();
let profile = [BLOCKS, BYTES, COMMANDS, PATH, DIRENT_BYTES, ENTRIES];
let config_schema_identity = digest_bytes(CONFIG_SCHEMA_DOMAIN, [CONFIG_SCHEMA]);
let config_identity = fixed_config_identity(config_schema_identity, CONFIG);
let identities = derive_identities(
profile,
config_schema_identity,
config_identity,
CONFIG,
sizes,
alignments,
offsets,
aggregate,
)?;
let leaf_identity = identity(
identities,
profile,
config_schema_identity,
config_identity,
CONFIG,
sizes,
alignments,
offsets,
aggregate,
)?;
Ok(ObservabilityGeneratedLayoutObservation {
identities,
profile,
config_schema_identity,
config_identity,
config: CONFIG,
sizes,
alignments,
offsets,
aggregate_size: aggregate.size(),
aggregate_alignment: aggregate.align(),
leaf_identity,
})
}
#[allow(clippy::too_many_arguments)]
fn identity(
identities: [[u8; 32]; 4],
profile: [usize; 6],
config_schema_identity: [u8; 32],
config_identity: [u8; 32],
config: [u64; 5],
sizes: [usize; 4],
alignments: [usize; 4],
offsets: [usize; 4],
aggregate: Layout,
) -> Result<[u8; 32], GeneratedLayoutError> {
let mut digest = FixedProfileDigest::new();
digest.write(LEAF_DOMAIN);
for value in identities {
digest.write(&value);
}
digest.write(&config_schema_identity);
digest.write(&config_identity);
for value in config {
digest.write(&value.to_le_bytes());
}
for value in profile
.into_iter()
.chain(sizes)
.chain(alignments)
.chain(offsets)
.chain([aggregate.size(), aggregate.align()])
{
digest
.write_usize(value)
.map_err(|_| GeneratedLayoutError::Overflow)?;
}
Ok(digest.finish())
}
#[allow(clippy::too_many_arguments)]
fn derive_identities(
profile: [usize; 6],
config_schema_identity: [u8; 32],
config_identity: [u8; 32],
config: [u64; 5],
sizes: [usize; 4],
alignments: [usize; 4],
offsets: [usize; 4],
aggregate: Layout,
) -> Result<[[u8; 32]; 4], GeneratedLayoutError> {
let source = digest_bytes(
SOURCE_DOMAIN,
[
FIXED_CORE_SOURCE,
BACKEND_SOURCE,
TERMINATION_SOURCE,
include_bytes!("generated_layout.rs").as_slice(),
],
);
let schema = digest_bytes(PROFILE_SCHEMA_DOMAIN, [PROFILE_SCHEMA]);
let work = writer_component_work_proof().identity();
let build = build_identity(
source,
env!("CARGO_PKG_NAME").as_bytes(),
env!("CARGO_PKG_VERSION").as_bytes(),
std::env::consts::ARCH.as_bytes(),
std::env::consts::OS.as_bytes(),
work,
config_schema_identity,
config_identity,
config,
profile,
sizes,
alignments,
offsets,
aggregate,
)?;
let mut issuer = FixedProfileDigest::new();
issuer.write(ISSUER_DOMAIN);
for value in [
source,
build,
schema,
work,
config_schema_identity,
config_identity,
] {
issuer.write(&value);
}
Ok([source, build, schema, issuer.finish()])
}
#[allow(clippy::too_many_arguments)]
fn build_identity(
source: [u8; 32],
package: &[u8],
version: &[u8],
arch: &[u8],
os: &[u8],
work: [u8; 32],
config_schema_identity: [u8; 32],
config_identity: [u8; 32],
config: [u64; 5],
profile: [usize; 6],
sizes: [usize; 4],
alignments: [usize; 4],
offsets: [usize; 4],
aggregate: Layout,
) -> Result<[u8; 32], GeneratedLayoutError> {
let mut digest = FixedProfileDigest::new();
digest.write(BUILD_DOMAIN);
for value in [
&source[..],
package,
version,
arch,
os,
&work[..],
&config_schema_identity,
&config_identity,
] {
digest.write(value);
}
for value in config {
digest.write(&value.to_le_bytes());
}
for value in profile
.into_iter()
.chain(sizes)
.chain(alignments)
.chain(offsets)
.chain([aggregate.size(), aggregate.align()])
{
digest
.write_usize(value)
.map_err(|_| GeneratedLayoutError::Overflow)?;
}
Ok(digest.finish())
}
fn fixed_config_identity(schema: [u8; 32], config: [u64; 5]) -> [u8; 32] {
let mut digest = FixedProfileDigest::new();
digest.write(CONFIG_IDENTITY_DOMAIN);
digest.write(&schema);
for value in config {
digest.write(&value.to_le_bytes());
}
digest.finish()
}
fn digest_bytes<const N: usize>(domain: &[u8], values: [&[u8]; N]) -> [u8; 32] {
let mut digest = FixedProfileDigest::new();
digest.write(domain);
for value in values {
digest.write(value);
}
digest.finish()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn release_observation_measures_concrete_types_and_checked_padding() {
let observed = observe_release_fixed_core_layout().unwrap();
assert_eq!(observed.sizes[0], size_of::<ReleaseInner>());
assert_eq!(observed.alignments[0], align_of::<ReleaseInner>());
assert_eq!(observed.sizes[1], size_of::<ReleaseWriter>());
assert_eq!(observed.alignments[2], align_of::<ReleaseBackend>());
assert_eq!(observed.sizes[3], size_of::<WriterTerminationProof>());
assert_eq!(observed.offsets[0], 0);
assert!(observed.aggregate_size >= observed.offsets[3] + observed.sizes[3]);
assert_eq!(observed.aggregate_size % observed.aggregate_alignment, 0);
assert_ne!(observed.leaf_identity, [0; 32]);
}
#[test]
fn alignment_or_layout_drift_changes_leaf_identity() {
let observed = observe_release_fixed_core_layout().unwrap();
let mut alignment_drift = observed.alignments;
alignment_drift[0] = alignment_drift[0].checked_mul(2).unwrap();
let changed_alignment = identity(
observed.identities,
observed.profile,
observed.config_schema_identity,
observed.config_identity,
observed.config,
observed.sizes,
alignment_drift,
observed.offsets,
Layout::from_size_align(observed.aggregate_size, observed.aggregate_alignment).unwrap(),
)
.unwrap();
assert_ne!(changed_alignment, observed.leaf_identity);
let mut layout_drift = observed.offsets;
layout_drift[1] += observed.alignments[1];
let changed_layout = identity(
observed.identities,
observed.profile,
observed.config_schema_identity,
observed.config_identity,
observed.config,
observed.sizes,
observed.alignments,
layout_drift,
Layout::from_size_align(observed.aggregate_size, observed.aggregate_alignment).unwrap(),
)
.unwrap();
assert_ne!(changed_layout, observed.leaf_identity);
}
#[test]
fn source_build_and_schema_drift_are_independently_bound() {
let observed = observe_release_fixed_core_layout().unwrap();
let source_drift = digest_bytes(SOURCE_DOMAIN, [b"foreign source".as_slice()]);
assert_ne!(source_drift, observed.identities[0]);
let layout =
Layout::from_size_align(observed.aggregate_size, observed.aggregate_alignment).unwrap();
let build_drift = build_identity(
observed.identities[0],
env!("CARGO_PKG_NAME").as_bytes(),
b"foreign-version",
std::env::consts::ARCH.as_bytes(),
std::env::consts::OS.as_bytes(),
writer_component_work_proof().identity(),
observed.config_schema_identity,
observed.config_identity,
observed.config,
observed.profile,
observed.sizes,
observed.alignments,
observed.offsets,
layout,
)
.unwrap();
assert_ne!(build_drift, observed.identities[1]);
let schema_drift = digest_bytes(PROFILE_SCHEMA_DOMAIN, [b"foreign schema".as_slice()]);
assert_ne!(schema_drift, observed.identities[2]);
}
#[test]
fn fixed_file_config_value_and_schema_drift_change_identity() {
let observed = observe_release_fixed_core_layout().unwrap();
let mut values = observed.config;
values[3] += 1;
assert_ne!(
fixed_config_identity(observed.config_schema_identity, values),
observed.config_identity
);
let foreign_schema = digest_bytes(CONFIG_SCHEMA_DOMAIN, [b"foreign config".as_slice()]);
assert_ne!(foreign_schema, observed.config_schema_identity);
assert_ne!(
fixed_config_identity(foreign_schema, observed.config),
observed.config_identity
);
}
}