refactor: move firmware workspace under firmware

This commit is contained in:
Onur Özkan committed 2026-08-10 16:58:55 +03:00
1 parent 153b0bf262
commit f03fc119a4
222 files changed
+56 -13

No files matched your search

+200
View File
@@ -0,0 +1,200 @@
//! Transport-neutral typed configuration fields and descriptors.
use crate::{ConfigViolation, ValidationRule};
use kaptan_log::KaptanError;
/// A stable, schema-local identity for one editable configuration field.
///
/// IDs are never reused within a schema generation. They are compact wire
/// identities; display strings belong to metadata and diagnostics only.
#[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd, Hash)]
#[repr(transparent)]
pub struct FieldId(u16);
impl FieldId {
/// Creates a stable field identity.
pub const fn new(value: u16) -> Self {
assert!(
value != 0 && value != u16::MAX,
"reserved configuration field id"
);
Self(value)
}
/// Parses a field identity received from an untrusted transport.
pub const fn try_new(value: u16) -> Option<Self> {
if value == 0 || value == u16::MAX {
None
} else {
Some(Self(value))
}
}
/// Returns the transport representation.
pub const fn get(self) -> u16 {
self.0
}
}
/// The scalar representation of a configuration value.
///
/// The variants deliberately preserve native widths: durations, for example,
/// remain `u64` microseconds instead of being lossy display-unit conversions.
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum ConfigValue {
/// Boolean value.
Bool(bool),
/// Signed 32-bit integer.
I32(i32),
/// Unsigned 32-bit integer.
U32(u32),
/// Unsigned 64-bit integer.
U64(u64),
/// IEEE-754 single-precision value.
F32(f32),
/// IEEE-754 double-precision value.
F64(f64),
}
impl ConfigValue {
/// The declared value kind.
pub const fn kind(self) -> ValueKind {
match self {
Self::Bool(_) => ValueKind::Bool,
Self::I32(_) => ValueKind::I32,
Self::U32(_) => ValueKind::U32,
Self::U64(_) => ValueKind::U64,
Self::F32(_) => ValueKind::F32,
Self::F64(_) => ValueKind::F64,
}
}
/// Canonical scalar bits for compact transport encodings.
pub const fn raw_bits(self) -> u64 {
match self {
Self::Bool(value) => value as u64,
Self::I32(value) => value as u32 as u64,
Self::U32(value) => value as u64,
Self::U64(value) => value,
Self::F32(value) => value.to_bits() as u64,
Self::F64(value) => value.to_bits(),
}
}
}
/// The scalar kind carried in a descriptor or typed protocol message.
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
#[repr(u8)]
pub enum ValueKind {
/// Boolean.
Bool = 1,
/// Signed 32-bit integer.
I32 = 2,
/// Unsigned 32-bit integer.
U32 = 3,
/// Unsigned 64-bit integer.
U64 = 4,
/// Single-precision float.
F32 = 5,
/// Double-precision float.
F64 = 6,
}
/// When a committed field may affect the active runtime.
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
#[repr(u8)]
pub enum ApplyPolicy {
/// Reserved for individually proven live-safe changes.
Live = 1,
/// Reserved for controlled output-inhibited reconfiguration.
DisarmedReset = 2,
/// P1 policy: the field takes effect only at the next boot.
RestartRequired = 3,
}
/// Static metadata for one configuration field.
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct FieldDescriptor {
/// Stable schema-local identity.
pub id: FieldId,
/// GCS display key; never used as a wire identity.
pub display_key: &'static str,
/// Scalar storage type.
pub kind: ValueKind,
/// Inclusive lower bound when the value is ordered.
pub minimum: Option<ConfigValue>,
/// Inclusive upper bound when the value is ordered.
pub maximum: Option<ConfigValue>,
/// Firmware-build default.
pub default: ConfigValue,
/// Human-readable unit for schema metadata.
pub unit: &'static str,
/// Activation policy advertised to a control station.
pub apply_policy: ApplyPolicy,
}
/// Failure while changing one transaction field.
#[derive(Clone, Copy, Debug, PartialEq, KaptanError)]
pub enum FieldWriteError {
/// No descriptor exposes the requested identity.
UnknownField(FieldId),
/// The supplied scalar type differs from the descriptor type.
WrongType {
/// Requested field.
field: FieldId,
/// Expected descriptor kind.
expected: ValueKind,
/// Supplied scalar kind.
actual: ValueKind,
},
/// A locally validated scalar violated a stable rule.
Violation(ConfigViolation),
}
impl FieldWriteError {
/// Returns the typed local validation issue, if this was one.
pub const fn violation(self) -> Option<ConfigViolation> {
match self {
Self::Violation(violation) => Some(violation),
Self::UnknownField(_) | Self::WrongType { .. } => None,
}
}
/// Returns a field-local non-finite error.
pub const fn non_finite(field: FieldId) -> Self {
Self::Violation(ConfigViolation::field(field, ValidationRule::NonFinite))
}
}
/// Vehicle-specific editable candidate model.
///
/// Implementations expose every operator-editable scalar through the static
/// descriptor registry. A field write performs only local validation; complete
/// relational and capability validation happens through [`Self::validate`].
pub trait ConfigModel: Clone {
/// Immutable configuration consumed by the runtime at boot.
type Active;
/// Immutable compiled board/vehicle capability contract.
type Context;
/// Exact identifier for the current externally transferable schema.
const SCHEMA_ID: [u8; 16];
/// Returns the descriptor at a stable zero-based schema index.
fn descriptor(index: u16) -> Option<&'static FieldDescriptor>;
/// Number of fields exposed by the compiled schema.
fn field_count() -> u16;
/// Returns a candidate value by stable ID.
fn field_value(&self, id: FieldId) -> Option<ConfigValue>;
/// Returns one value from the immutable boot-active view.
fn active_field_value(active: &Self::Active, id: FieldId) -> Option<ConfigValue>;
/// Applies exactly one locally valid scalar to this candidate.
fn set_field(&mut self, id: FieldId, value: ConfigValue) -> Result<(), FieldWriteError>;
/// Fully validates and converts the candidate to an immutable active view.
fn validate(&self, context: &Self::Context) -> Result<Self::Active, ConfigViolation>;
}