diff options
| author | Luke Hoersten <[email protected]> | 2026-07-28 17:38:06 -0500 |
|---|---|---|
| committer | Luke Hoersten <[email protected]> | 2026-07-28 17:38:06 -0500 |
| commit | 29943e536f2be420a9d4042cd26d7b7eddf71438 (patch) | |
| tree | 3083eafc75877243777a8257dd4a46e88ee8a3cf /src/output.rs | |
| parent | 28a660e8bacac84523601f67547b2671058d9b20 (diff) | |
Address remaining audit findings: input hardening and cleanup
Security:
- SNTP replies must echo a random nonce in their originate timestamp, and
unsynchronized (LI=3) or invalid-stratum servers are rejected, so an
off-path spoofer cannot defeat the clock-trust gate
- The storage directory is created 0700 but an existing one is tightened
only when it is provably ours (holds our artifacts or is empty); a
misconfigured path like /tmp under root is left alone with a warning
- Device-supplied vendor/product names are stripped of control characters
at ingestion so a device cannot inject terminal escapes
Cleanup:
- ensure_fabric splits into decision logic plus bootstrap_fabric
- read_device_names merges the two name readers; a From impl replaces the
hand-mapped IdentityReport; run_sync uses map/transpose with an extracted
manual_instant; pick_interface runs once; print_json escapes its fallback;
device_name dedups the vendor/product join; identity_status ignores
leftover .tmp blobs; dead parameters and repeated random-id code removed
36 unit tests, clippy clean.
Diffstat (limited to 'src/output.rs')
| -rw-r--r-- | src/output.rs | 59 |
1 files changed, 42 insertions, 17 deletions
diff --git a/src/output.rs b/src/output.rs index e2c91ec..0a8a536 100644 --- a/src/output.rs +++ b/src/output.rs @@ -116,6 +116,26 @@ pub enum IdentityReport { }, } +impl From<crate::controller::IdentityStatus> for IdentityReport { + fn from(status: crate::controller::IdentityStatus) -> Self { + use crate::controller::IdentityStatus as S; + match status { + S::NotCreated => Self::NotCreated, + S::Created { + fabric_id, + controller_node_id, + } => Self::Created { + fabric_id: fabric_id.to_string(), + controller_node_id: controller_node_id.to_string(), + }, + S::Unreadable => Self::Unreadable, + S::Inconsistent(reason) => Self::Inconsistent { + reason: reason.to_string(), + }, + } + } +} + #[derive(Debug, Serialize)] #[serde(rename_all = "camelCase")] pub struct DstTransition { @@ -174,7 +194,12 @@ impl Output { pub fn print_json(&self) { match serde_json::to_string_pretty(self) { Ok(body) => println!("{body}"), - Err(error) => println!("{{\"error\": \"serialize output: {error}\"}}"), + // Build the fallback with the serializer too, so the error text + // is escaped and stdout stays valid JSON even here. + Err(error) => println!( + "{}", + serde_json::json!({ "error": format!("serialize output: {error}") }) + ), } } @@ -198,6 +223,17 @@ fn display_instant(at: Option<Timestamp>) -> String { at.map_or_else(|| "never".into(), |at| at.to_string()) } +/// The device's vendor and product names joined for display, or `None` when +/// neither is known; each caller supplies its own fallback text. +fn device_name(vendor: Option<&str>, product: Option<&str>) -> Option<String> { + let joined = [vendor, product] + .into_iter() + .flatten() + .collect::<Vec<_>>() + .join(" "); + (!joined.is_empty()).then_some(joined) +} + fn render_status(report: &StatusReport) { println!( "Configuration: loaded from {}", @@ -271,19 +307,11 @@ fn render_status(report: &StatusReport) { report.matter_time_now_microseconds ); for node in &report.nodes { - let name = [node.vendor_name.as_deref(), node.product_name.as_deref()] - .into_iter() - .flatten() - .collect::<Vec<_>>() - .join(" "); + let name = device_name(node.vendor_name.as_deref(), node.product_name.as_deref()); println!( "Node {}: {}", node.node_id, - if name.is_empty() { - "(no cached device info)" - } else { - &name - } + name.as_deref().unwrap_or("(no cached device info)") ); println!( " Last connection: {}", @@ -399,14 +427,11 @@ fn render_commissioned(outcome: &CommissionOutcome) { println!(" Fabric ID: {}", outcome.fabric_id); println!( " Device: {}", - [ + device_name( outcome.vendor_name.as_deref(), outcome.product_name.as_deref() - ] - .into_iter() - .flatten() - .collect::<Vec<_>>() - .join(" ") + ) + .unwrap_or_default() ); println!(); println!("The device remains paired with its primary ecosystem; this controller"); |
