src.nth.io/

summaryrefslogtreecommitdiff
path: root/src/output.rs
diff options
context:
space:
mode:
authorLuke Hoersten <[email protected]>2026-07-28 17:38:06 -0500
committerLuke Hoersten <[email protected]>2026-07-28 17:38:06 -0500
commit29943e536f2be420a9d4042cd26d7b7eddf71438 (patch)
tree3083eafc75877243777a8257dd4a46e88ee8a3cf /src/output.rs
parent28a660e8bacac84523601f67547b2671058d9b20 (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.rs59
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");