Skip to main content

rite_runtime/
system_info.rs

1//! Structured machine, build, and environment facts surfaced to frontends.
2//!
3//! Two distinct shapes live here, on opposite sides of the data-flow boundary
4//! described in `docs/development/runtime-and-frontend.md`:
5//!
6//! - [`SystemInfo`] is **static identity**: the build that is running and the
7//!   host it runs on. Gathered once at startup. The same typed host shape
8//!   ([`HostInfo`]) is what the `machine_info` action records to the
9//!   transcript, so the model is defined once and shared.
10//! - [`Environment`] is the **live device inventory** (disks today;
11//!   peripherals and network later). It is deliberately shaped to be
12//!   re-emitted: a future observer can resend it and a frontend replaces its
13//!   view wholesale. Items carry a stable key for later diffing.
14//!
15//! Both travel as UI-only signals ([`crate::UiSignal`]); neither is ever
16//! written to the transcript. Recording machine identity as evidence is the
17//! `machine_info` action's job, not the frontend's.
18
19use std::fmt;
20
21use serde::{Deserialize, Serialize};
22
23/// Failure to parse one of this module's string-backed enums.
24///
25/// Shared across the enums defined here, mirroring the pattern in
26/// `rite-sdk` (`rite_sdk::ParseError`), which is not constructible outside
27/// its own crate.
28#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
29#[error("unknown value: {0:?}")]
30pub struct ParseError(String);
31
32/// Static build and host identity for the System tab and
33/// `rite version --verbose`.
34///
35/// Assembled by the CLI, the only crate that holds the build-time constants,
36/// and echoed to the frontend once via [`crate::UiSignal::SystemInfo`].
37#[derive(Debug, Clone, Serialize, Deserialize)]
38pub struct SystemInfo {
39    /// Identity of the running binary.
40    pub build: BuildInfo,
41    /// Identity of the host it runs on.
42    pub host: HostInfo,
43    /// Linked cryptographic backend libraries.
44    pub backends: Vec<BackendVersion>,
45}
46
47/// Identity of the running `rite` binary. A property of the artifact, set at
48/// compile time; the strongly verifiable end of the trust boundary (checkable
49/// against a release tag and build provenance).
50#[derive(Debug, Clone, Serialize, Deserialize)]
51pub struct BuildInfo {
52    /// Crate version (`CARGO_PKG_VERSION`).
53    pub version: String,
54    /// Git commit the binary was built from, or `"unknown"`.
55    pub commit: String,
56    /// Commit date, or `"unknown"`.
57    pub commit_date: String,
58    /// Date the binary was built.
59    pub build_date: String,
60    /// Target triple.
61    pub target: String,
62    /// Build profile (`debug` / `release`).
63    pub profile: String,
64    /// Enabled cargo features, comma-separated.
65    pub features: String,
66    /// Compiler version string.
67    pub rustc: String,
68}
69
70/// A linked cryptographic backend library and its runtime version.
71#[derive(Debug, Clone, Serialize, Deserialize)]
72pub struct BackendVersion {
73    /// Provider key (e.g. `"openssl"`).
74    pub provider: String,
75    /// Runtime-linked library version.
76    pub version: String,
77    /// How the library was linked (e.g. `"vendored"` / `"system"`).
78    pub source: Option<String>,
79}
80
81/// Identity of the host machine. A property of the environment, the weakly
82/// verifiable end of the trust boundary (an operator assertion that software
83/// can only partly corroborate).
84///
85/// Optional fields are populated depending on the gathering scope: a cheap
86/// startup snapshot fills only the always-present basics, while the
87/// `machine_info` action fills the full set.
88#[derive(Debug, Clone, Serialize, Deserialize)]
89pub struct HostInfo {
90    /// CPU architecture (`std::env::consts::ARCH`). Always present.
91    pub arch: String,
92    /// OS name (e.g. `"Linux"`, `"Darwin"`).
93    pub os: Option<String>,
94    /// Long OS version string.
95    pub os_version: Option<String>,
96    /// Kernel version.
97    pub kernel_version: Option<String>,
98    /// Hostname.
99    pub hostname: Option<String>,
100    /// Hashed machine identifier (SHA-256), when available.
101    pub machine_id: Option<String>,
102    /// CPU brand string.
103    pub cpu_model: Option<String>,
104    /// Platform security posture, when gathered at full scope.
105    pub hardening: Option<Hardening>,
106}
107
108/// Snapshot of platform memory- and DMA-protection features.
109#[derive(Debug, Clone, Serialize, Deserialize)]
110pub struct Hardening {
111    /// Hardware RAM encryption (AMD SME, Intel TME, Apple Secure Enclave).
112    pub ram_encryption: FeatureCheck,
113    /// IOMMU-based DMA protection.
114    pub dma_protection: FeatureCheck,
115    /// Zeroing of freed pages.
116    pub freed_page_zeroing: FeatureCheck,
117}
118
119/// A single security-feature determination: a machine-comparable status plus
120/// optional human context.
121#[derive(Debug, Clone, Serialize, Deserialize)]
122pub struct FeatureCheck {
123    /// Whether the feature is active.
124    pub status: FeatureStatus,
125    /// Human-readable context (e.g. `"AMD SME"`, `"enable TME in BIOS"`).
126    pub detail: Option<String>,
127}
128
129impl FeatureCheck {
130    /// Construct a check with no detail.
131    #[must_use]
132    pub fn new(status: FeatureStatus) -> Self {
133        Self {
134            status,
135            detail: None,
136        }
137    }
138
139    /// Construct a check carrying human context.
140    #[must_use]
141    pub fn with_detail(status: FeatureStatus, detail: impl Into<String>) -> Self {
142        Self {
143            status,
144            detail: Some(detail.into()),
145        }
146    }
147}
148
149/// Status of a platform security feature.
150///
151/// Serialized via its [`Display`](fmt::Display) form so the transcript carries
152/// a stable, comparable token rather than a sentence. Follows the
153/// Display/serde alignment convention used by `rite-sdk` enums.
154#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
155#[serde(into = "String", try_from = "String")]
156#[non_exhaustive]
157pub enum FeatureStatus {
158    /// Present and active.
159    Active,
160    /// Supported by the platform but currently off.
161    Inactive,
162    /// Not available on this hardware or platform.
163    Unavailable,
164    /// Not a meaningful concept on this platform.
165    NotApplicable,
166    /// Could not be determined.
167    Unknown,
168}
169
170impl fmt::Display for FeatureStatus {
171    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
172        let s = match self {
173            FeatureStatus::Active => "active",
174            FeatureStatus::Inactive => "inactive",
175            FeatureStatus::Unavailable => "unavailable",
176            FeatureStatus::NotApplicable => "not_applicable",
177            FeatureStatus::Unknown => "unknown",
178        };
179        f.write_str(s)
180    }
181}
182
183impl std::str::FromStr for FeatureStatus {
184    type Err = ParseError;
185
186    fn from_str(s: &str) -> Result<Self, Self::Err> {
187        match s {
188            "active" => Ok(Self::Active),
189            "inactive" => Ok(Self::Inactive),
190            "unavailable" => Ok(Self::Unavailable),
191            "not_applicable" => Ok(Self::NotApplicable),
192            "unknown" => Ok(Self::Unknown),
193            _ => Err(ParseError(s.to_owned())),
194        }
195    }
196}
197
198impl From<FeatureStatus> for String {
199    fn from(s: FeatureStatus) -> String {
200        s.to_string()
201    }
202}
203
204impl TryFrom<String> for FeatureStatus {
205    type Error = ParseError;
206
207    fn try_from(s: String) -> Result<Self, Self::Error> {
208        s.parse()
209    }
210}
211
212/// Live inventory of the machine's attached devices.
213///
214/// UI-only and never serialized. Shaped to be **re-emitted**: a frontend
215/// replaces its whole view on each [`crate::UiSignal::Environment`], so a
216/// future live observer is purely additive. Each item carries a stable key
217/// (e.g. [`Disk::name`]) for later diffing.
218#[derive(Debug, Clone, Default)]
219pub struct Environment {
220    /// Block devices and their mounts.
221    pub disks: Vec<Disk>,
222    // Future: peripherals (USB / smartcard / TPM), network interfaces.
223}
224
225/// The one-shot UI snapshots emitted at ceremony start: static system
226/// identity and the initial device environment.
227///
228/// Assembled by the CLI (the only crate holding the build-time constants) and
229/// handed to the executor, which echoes the two pieces as
230/// [`crate::UiSignal::SystemInfo`] and [`crate::UiSignal::Environment`]. The
231/// runtime treats this purely as a pass-through; it never inspects the
232/// contents.
233#[derive(Debug, Clone)]
234pub struct StartupSnapshot {
235    /// Static build and host identity.
236    pub system: SystemInfo,
237    /// Initial device environment.
238    pub environment: Environment,
239}
240
241impl StartupSnapshot {
242    /// A placeholder snapshot with no resolved build or host data. Used by
243    /// runtime tests, which do not exercise the System tab.
244    #[cfg(test)]
245    pub(crate) fn placeholder() -> Self {
246        let unknown = || "unknown".to_string();
247        Self {
248            system: SystemInfo {
249                build: BuildInfo {
250                    version: unknown(),
251                    commit: unknown(),
252                    commit_date: unknown(),
253                    build_date: unknown(),
254                    target: unknown(),
255                    profile: unknown(),
256                    features: String::new(),
257                    rustc: unknown(),
258                },
259                host: HostInfo {
260                    arch: std::env::consts::ARCH.to_string(),
261                    os: None,
262                    os_version: None,
263                    kernel_version: None,
264                    hostname: None,
265                    machine_id: None,
266                    cpu_model: None,
267                    hardening: None,
268                },
269                backends: Vec::new(),
270            },
271            environment: Environment::default(),
272        }
273    }
274}
275
276/// A block device and its mount, as shown in the System tab.
277#[derive(Debug, Clone)]
278pub struct Disk {
279    /// Device name; the stable key for diffing across re-emissions.
280    pub name: String,
281    /// Mount point path.
282    pub mount_point: String,
283    /// Filesystem type, when known.
284    pub file_system: Option<String>,
285    /// Total capacity in bytes.
286    pub total_bytes: u64,
287    /// Free space in bytes.
288    pub available_bytes: u64,
289    /// Whether the device is removable.
290    pub removable: bool,
291    /// Device kind (e.g. `"SSD"`, `"HDD"`), when known.
292    pub kind: Option<String>,
293}
294
295#[cfg(test)]
296mod tests {
297    use super::*;
298
299    #[test]
300    fn feature_status_round_trips_through_display_and_serde() {
301        for status in [
302            FeatureStatus::Active,
303            FeatureStatus::Inactive,
304            FeatureStatus::Unavailable,
305            FeatureStatus::NotApplicable,
306            FeatureStatus::Unknown,
307        ] {
308            let text = status.to_string();
309            assert_eq!(text.parse::<FeatureStatus>().expect("parses"), status);
310            let json = serde_json::to_string(&status).expect("serializes");
311            assert_eq!(json, format!("\"{text}\""));
312            let back: FeatureStatus = serde_json::from_str(&json).expect("deserializes");
313            assert_eq!(back, status);
314        }
315    }
316
317    #[test]
318    fn unknown_feature_status_string_is_rejected() {
319        assert!("bogus".parse::<FeatureStatus>().is_err());
320    }
321}