feat(firmware): validate configuration and migrate sensor-driven flight APIs

Validate configuration through its owning schemas and keep board
declarations separate from shared F722 resource ownership. Bound USB
transitions, isolate transport failures and retain restart-only activation.

Migrate navigation and flight consumers with the sensor and schema
contracts. Require explicit installation data and synchronous output
authority; board motor outputs remain inhibited.

Validation: assembled-prefix host tests, strict Clippy, platform/USB tests
and both ARM release builds. Final-source coverage and scoped Miri/Kani
evidence are retained separately from hardware qualification.
This commit is contained in:
Onur Özkan committed 2026-11-07 18:12:11 +03:00
1 parent fb1b8af2d1
commit f9a61a086f
211 files changed
+45657 -12740

No files matched your search

@@ -38,7 +38,7 @@ fn expand_scalars(
checks: field_checks,
} = expand_field(&crate_path, field)?;
descriptors.push(descriptor);
checks.extend(field_checks);
checks.extend(field_checks.iter().cloned());
let id = field.id.expect("scalar configuration fields have IDs");
let ty = &field.ty;
let field_ident = &field.ident;
@@ -90,7 +90,11 @@ fn expand_scalars(
};
let previous = self.#field_ident;
self.#field_ident = next;
if let Err(violation) = self.__validate_local_configuration() {
let validation = (|| -> Result<(), #crate_path::ConfigViolation> {
#(#field_checks)*
Ok(())
})();
if let Err(violation) = validation {
self.#field_ident = previous;
return Err(#crate_path::FieldWriteError::Violation(violation));
}
@@ -272,8 +276,35 @@ fn expand_field(crate_path: &syn::Path, field: &crate::parse::FieldModel) -> Res
Check::Maximum(value) => Some(value),
_ => None,
});
let finite = field
.checks
.iter()
.any(|check| matches!(check, Check::Finite));
let nonzero = field
.checks
.iter()
.any(|check| matches!(check, Check::Nonzero));
let float = matches!(&field.ty, syn::Type::Path(path)
if path.path.is_ident("f32") || path.path.is_ident("f64"));
if float && !finite && (minimum.is_some() || maximum.is_some()) {
let field_ident = &field.ident;
checks.push(quote! {
if self.#field_ident.is_nan() {
return Err(#crate_path::ConfigViolation::field(
#crate_path::FieldId::new(#id),
#crate_path::ValidationRule::Unordered,
));
}
});
}
for check in &field.checks {
checks.push(expand_check(crate_path, &field.ident, id, check.clone()));
checks.push(expand_check(
crate_path,
&field.ident,
id,
check.clone(),
float,
));
}
let key = field.display_key.as_ref().expect("id fields have a key");
let unit = field.unit.as_ref().expect("id fields have a unit");
@@ -302,6 +333,8 @@ fn expand_field(crate_path: &syn::Path, field: &crate::parse::FieldModel) -> Res
maximum: #maximum,
enum_options: #enum_options,
unit: #unit,
finite: #finite,
nonzero: #nonzero,
}
},
checks,
@@ -344,7 +377,18 @@ fn config_value_constructor_tokens(crate_path: &syn::Path, ty: &syn::Type) -> Re
Ok(value)
}
fn expand_check(crate_path: &syn::Path, field: &syn::Ident, id: u16, check: Check) -> TokenStream {
fn expand_check(
crate_path: &syn::Path,
field: &syn::Ident,
id: u16,
check: Check,
float: bool,
) -> TokenStream {
let zero = if float {
quote! { 0.0 }
} else {
quote! { 0 }
};
match check {
Check::Finite => quote! {
if !self.#field.is_finite() {
@@ -355,7 +399,7 @@ fn expand_check(crate_path: &syn::Path, field: &syn::Ident, id: u16, check: Chec
}
},
Check::Nonzero => quote! {
if self.#field == 0 {
if self.#field == #zero {
return Err(#crate_path::ConfigViolation::field(
#crate_path::FieldId::new(#id),
#crate_path::ValidationRule::Zero,
+17 -34
View File
@@ -482,10 +482,12 @@ fn scalar_literal(expression: &Expr, ty: &str) -> Result<ScalarLiteral> {
})?;
Ok(ScalarLiteral::Unsigned(value))
}
"f32" => Ok(ScalarLiteral::Float(f64::from(
float_literal(expression)? as f32
))),
"f64" => float_literal(expression).map(ScalarLiteral::Float),
// Parse at the emitted literal's native precision. Parsing through f64
// first can double-round an f32 bound across an ordering boundary.
"f32" => Ok(ScalarLiteral::Float(f64::from(float_literal::<f32>(
expression,
)?))),
"f64" => float_literal::<f64>(expression).map(ScalarLiteral::Float),
_ => Err(syn::Error::new(
expression.span(),
"configuration metadata supports bool, i32, u8, u16, u32, u64, f32, and f64",
@@ -542,7 +544,11 @@ fn unsigned_literal(expression: &Expr) -> Result<u128> {
}
}
fn float_literal(expression: &Expr) -> Result<f64> {
fn float_literal<T>(expression: &Expr) -> Result<T>
where
T: std::str::FromStr + core::ops::Neg<Output = T>,
T::Err: core::fmt::Display,
{
let (negative, literal) = match expression {
Expr::Lit(expression) => (false, &expression.lit),
Expr::Unary(expression) if matches!(expression.op, UnOp::Neg(_)) => {
@@ -563,10 +569,10 @@ fn float_literal(expression: &Expr) -> Result<f64> {
};
let value = match literal {
Lit::Int(value) => value
.base10_parse::<f64>()
.base10_parse::<T>()
.map_err(|error| syn::Error::new(value.span(), error))?,
Lit::Float(value) => value
.base10_parse::<f64>()
.base10_parse::<T>()
.map_err(|error| syn::Error::new(value.span(), error))?,
_ => {
return Err(syn::Error::new(
@@ -601,30 +607,14 @@ fn require_float(ty: &Type, span: &impl Spanned) -> Result<()> {
}
fn require_numeric(ty: &Type, span: &impl Spanned) -> Result<()> {
if scalar_type_name(ty).is_some_and(|name| {
matches!(
name,
"f32"
| "f64"
| "i8"
| "i16"
| "i32"
| "i64"
| "i128"
| "isize"
| "u8"
| "u16"
| "u32"
| "u64"
| "u128"
| "usize"
)
}) {
if scalar_type_name(ty)
.is_some_and(|name| matches!(name, "f32" | "f64" | "i32" | "u8" | "u16" | "u32" | "u64"))
{
Ok(())
} else {
Err(syn::Error::new(
span.span(),
"nonzero and range require a primitive numeric field",
"nonzero and range require a supported primitive numeric field",
))
}
}
@@ -644,18 +634,11 @@ fn scalar_type_name(ty: &Type) -> Option<&str> {
ident if ident == "bool" => "bool",
ident if ident == "f32" => "f32",
ident if ident == "f64" => "f64",
ident if ident == "i8" => "i8",
ident if ident == "i16" => "i16",
ident if ident == "i32" => "i32",
ident if ident == "i64" => "i64",
ident if ident == "i128" => "i128",
ident if ident == "isize" => "isize",
ident if ident == "u8" => "u8",
ident if ident == "u16" => "u16",
ident if ident == "u32" => "u32",
ident if ident == "u64" => "u64",
ident if ident == "u128" => "u128",
ident if ident == "usize" => "usize",
_ => return None,
})
}