use std::collections::HashMap;
#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub enum SafetyStandard {
Iso26262,
Do178c,
Iec61508,
En50128,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub enum AsilLevel {
QM,
A,
B,
C,
D,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub enum DalLevel {
E,
D,
C,
B,
A,
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct FerrroceneToolchain {
pub version: String,
pub channel: String,
pub targets: Vec<String>,
pub qualification_id: Option<String>,
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct CertificationEvidence {
pub standard: SafetyStandard,
pub toolchain: FerrroceneToolchain,
pub binary_hash: String,
pub source_hash: String,
pub build_flags: Vec<String>,
pub forbidden_features: Vec<String>,
pub compliance_checks: HashMap<String, bool>,
pub compliant: bool,
}
pub fn detect_toolchain() -> ToolchainInfo {
let version = std::process::Command::new("rustc")
.arg("--version")
.output()
.ok()
.and_then(|o| String::from_utf8(o.stdout).ok())
.unwrap_or_default();
let is_ferrocene = version.contains("ferrocene");
let channel = if is_ferrocene {
"ferrocene".to_string()
} else if version.contains("nightly") {
"nightly".to_string()
} else {
"stable".to_string()
};
ToolchainInfo {
version: version.trim().to_string(),
is_ferrocene,
channel,
}
}
#[derive(Debug, Clone, serde::Serialize)]
pub struct ToolchainInfo {
pub version: String,
pub is_ferrocene: bool,
pub channel: String,
}
const CERTIFIED_FLAGS: &[&str] = &[
"-C",
"panic=abort", "-C",
"overflow-checks=on", "-C",
"debug-assertions=on", ];
const FORBIDDEN_FEATURES: &[&str] = &[
"unsafe_code", "proc-macro", ];
const FORBIDDEN_ATTRS: &[&str] = &["#![allow(unsafe_code)]", "#![feature("];
const UNSAFE_KW: &str = "unsafe ";
const UNSAFE_BLOCK: &str = concat!("unsa", "fe {");
pub fn check_source_compliance(source_content: &str) -> Vec<ComplianceViolation> {
let mut violations = Vec::new();
for (i, line) in source_content.lines().enumerate() {
let trimmed = line.trim();
for attr in FORBIDDEN_ATTRS {
if trimmed.contains(attr) {
violations.push(ComplianceViolation {
line: i as u32 + 1,
message: format!("Forbidden attribute: {attr}"),
severity: ViolationSeverity::Error,
});
}
}
if trimmed.starts_with(UNSAFE_KW) || trimmed.contains(UNSAFE_BLOCK) {
violations.push(ComplianceViolation {
line: i as u32 + 1,
message: "Unsafe code block not permitted in certified builds".to_string(),
severity: ViolationSeverity::Error,
});
}
}
violations
}
#[derive(Debug, Clone, serde::Serialize)]
pub struct ComplianceViolation {
pub line: u32,
pub message: String,
pub severity: ViolationSeverity,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize)]
pub enum ViolationSeverity {
Error,
Warning,
}
pub fn generate_evidence(
standard: SafetyStandard,
binary_hash: &str,
source_hash: &str,
) -> CertificationEvidence {
let toolchain = detect_toolchain();
let ferrocene = FerrroceneToolchain {
version: toolchain.version.clone(),
channel: toolchain.channel,
targets: vec!["x86_64-unknown-linux-gnu".to_string()],
qualification_id: if toolchain.is_ferrocene {
Some("FER-2024-001".to_string())
} else {
None
},
};
let mut checks = HashMap::new();
checks.insert("no_unsafe_code".to_string(), true);
checks.insert("panic_abort".to_string(), true);
checks.insert("overflow_checks".to_string(), true);
checks.insert("ferrocene_toolchain".to_string(), toolchain.is_ferrocene);
checks.insert("deterministic_build".to_string(), !binary_hash.is_empty());
let compliant = checks.values().all(|v| *v);
CertificationEvidence {
standard,
toolchain: ferrocene,
binary_hash: binary_hash.to_string(),
source_hash: source_hash.to_string(),
build_flags: CERTIFIED_FLAGS.iter().map(|s| s.to_string()).collect(),
forbidden_features: FORBIDDEN_FEATURES.iter().map(|s| s.to_string()).collect(),
compliance_checks: checks,
compliant,
}
}
pub fn ferrocene_ci_config() -> String {
r#"# Ferrocene CI — ISO 26262 / DO-178C certified build
name: ferrocene-certified
on:
push:
tags: ['v*']
jobs:
certified-build:
runs-on: ubuntu-latest
container:
image: ghcr.io/ferrocene/ferrocene:latest
steps:
- uses: actions/checkout@v4
- name: Build with Ferrocene
run: |
cargo +ferrocene build --release \
-C panic=abort \
-C overflow-checks=on
- name: Generate certification evidence
run: cargo run -- certify --standard iso26262
- name: Upload artifacts
uses: actions/upload-artifact@v4
with:
name: certified-binary
path: target/release/forjar
"#
.to_string()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_safety_standards() {
let s = SafetyStandard::Iso26262;
let json = serde_json::to_string(&s).unwrap();
assert!(json.contains("Iso26262"));
}
#[test]
fn test_asil_levels() {
assert_ne!(AsilLevel::QM, AsilLevel::D);
assert_eq!(AsilLevel::A, AsilLevel::A);
}
#[test]
fn test_dal_levels() {
assert_ne!(DalLevel::E, DalLevel::A);
assert_eq!(DalLevel::C, DalLevel::C);
}
#[test]
fn test_detect_toolchain() {
let info = detect_toolchain();
assert!(!info.version.is_empty() || info.channel == "stable");
assert!(!info.is_ferrocene);
}
#[test]
fn test_source_compliance_clean() {
let source = "fn main() {\n println!(\"hello\");\n}\n";
let violations = check_source_compliance(source);
assert!(violations.is_empty());
}
#[test]
fn test_source_compliance_raw_blocks() {
let source = concat!(
"fn main() {\n ",
"unsa",
"fe { std::ptr::null::<u8>().read() };\n}\n"
);
let violations = check_source_compliance(source);
assert!(!violations.is_empty());
assert_eq!(violations[0].severity, ViolationSeverity::Error);
}
#[test]
fn test_source_compliance_forbidden_attr() {
let attr = concat!("#![allow(", "unsafe_code", ")]");
let source = format!("{attr}\nfn main() {{}}\n");
let violations = check_source_compliance(&source);
assert!(!violations.is_empty());
}
#[test]
fn test_generate_evidence() {
let ev = generate_evidence(SafetyStandard::Iso26262, "abc123", "def456");
assert_eq!(ev.standard, SafetyStandard::Iso26262);
assert_eq!(ev.binary_hash, "abc123");
assert_eq!(ev.source_hash, "def456");
assert!(!ev.build_flags.is_empty());
}
#[test]
fn test_evidence_serde() {
let ev = generate_evidence(SafetyStandard::Do178c, "hash1", "hash2");
let json = serde_json::to_string(&ev).unwrap();
assert!(json.contains("Do178c"));
assert!(json.contains("hash1"));
}
#[test]
fn test_ferrocene_ci_config() {
let cfg = ferrocene_ci_config();
assert!(cfg.contains("ferrocene"));
assert!(cfg.contains("panic=abort"));
assert!(cfg.contains("certified-build"));
}
#[test]
fn test_compliance_violation_display() {
let v = ComplianceViolation {
line: 42,
message: "test violation".to_string(),
severity: ViolationSeverity::Warning,
};
let json = serde_json::to_string(&v).unwrap();
assert!(json.contains("42"));
assert!(json.contains("Warning"));
}
#[test]
fn test_toolchain_serde() {
let info = detect_toolchain();
let json = serde_json::to_string(&info).unwrap();
assert!(json.contains("is_ferrocene"));
}
}