From 6ddebec575aa8c9c5a357c673cbd8e8a5cc5769d Mon Sep 17 00:00:00 2001 From: salmonik Date: Thu, 29 Oct 2026 21:43:24 +0100 Subject: v2.3.2: group hosts by certificate fingerprint --- src/client/ui.rs | 28 ++++++++++++++++++++++++++-- 1 file changed, 26 insertions(+), 2 deletions(-) (limited to 'src/client') diff --git a/src/client/ui.rs b/src/client/ui.rs index 1a35db1..0fe78ae 100644 --- a/src/client/ui.rs +++ b/src/client/ui.rs @@ -247,14 +247,38 @@ pub fn first_contact_lines(addr: &str, fingerprint: &str) -> Vec { /// Format remembered hosts so a 64-character fingerprint does not wrap on an /// 80-column terminal. +/// Render remembered hosts. +/// +/// Two addresses that present the same certificate key are the same server, so +/// they are grouped under one fingerprint: `127.0.0.1:11525` and +/// `10.0.0.100:11525` reached through different networks still show as one +/// entry. Hosts without a fingerprint are never merged, since nothing proves +/// they are related. pub fn host_lines(hosts: &[(String, Option)]) -> Vec { if hosts.is_empty() { return vec!["No remembered hosts.".to_string()]; } - let mut lines = vec!["Remembered hosts:".to_string()]; + let mut groups: Vec<(Option<&str>, Vec<&str>)> = Vec::new(); for (host, fingerprint) in hosts { - lines.push(format!(" {host}")); + match fingerprint { + Some(fingerprint) => { + match groups + .iter_mut() + .find(|(key, _)| *key == Some(fingerprint.as_str())) + { + Some((_, addresses)) => addresses.push(host), + None => groups.push((Some(fingerprint), vec![host])), + } + } + // An unknown key proves nothing about identity. + None => groups.push((None, vec![host])), + } + } + + let mut lines = vec!["Remembered hosts:".to_string()]; + for (fingerprint, addresses) in groups { + lines.push(format!(" {}", addresses.join(", "))); match fingerprint { Some(fingerprint) => lines.push(format!(" fingerprint: {fingerprint}")), None => lines.push(" (no fingerprint)".to_string()), -- cgit v1.2.3