diff --git a/crates/lanparty-client-win/src/main.rs b/crates/lanparty-client-win/src/main.rs index 970f55b..b564c72 100644 --- a/crates/lanparty-client-win/src/main.rs +++ b/crates/lanparty-client-win/src/main.rs @@ -35,6 +35,8 @@ const TAP_INTERFACE_METRIC: u32 = 9_000; const RELAY_ROUTE_VERIFY_INTERVAL: Duration = Duration::from_secs(5); #[cfg(windows)] const CLIENT_DIAGNOSTICS_INTERVAL: Duration = Duration::from_secs(10); +const MAX_CLIENT_SHUTDOWN_MESSAGE_BYTES: usize = 1024; +const CLIENT_SHUTDOWN_TRUNCATION_SUFFIX: &str = "..."; #[derive(Debug, Parser)] #[command( @@ -221,10 +223,28 @@ async fn main() -> Result<()> { } fn client_shutdown_message(run_result: &Result<()>) -> String { - match run_result { + let message = match run_result { Ok(()) => "client shutting down".to_owned(), Err(error) => format!("client shutting down after error: {error:#}"), + }; + + truncate_client_shutdown_message(&message) +} + +fn truncate_client_shutdown_message(message: &str) -> String { + if message.len() <= MAX_CLIENT_SHUTDOWN_MESSAGE_BYTES { + return message.to_owned(); } + + let suffix_len = CLIENT_SHUTDOWN_TRUNCATION_SUFFIX.len(); + let mut end = MAX_CLIENT_SHUTDOWN_MESSAGE_BYTES - suffix_len; + while !message.is_char_boundary(end) { + end -= 1; + } + + let mut truncated = message[..end].to_owned(); + truncated.push_str(CLIENT_SHUTDOWN_TRUNCATION_SUFFIX); + truncated } #[cfg(windows)] @@ -1296,6 +1316,18 @@ mod tests { ); } + #[test] + fn bounds_runtime_errors_in_shutdown_message() { + let detail = "x".repeat(MAX_CLIENT_SHUTDOWN_MESSAGE_BYTES * 2); + let error = anyhow::anyhow!("{detail}").context("bridge stopped"); + + let message = client_shutdown_message(&Err(error)); + + assert_eq!(message.len(), MAX_CLIENT_SHUTDOWN_MESSAGE_BYTES); + assert!(message.starts_with("client shutting down after error: bridge stopped: ")); + assert!(message.ends_with(CLIENT_SHUTDOWN_TRUNCATION_SUFFIX)); + } + #[test] fn formats_client_diagnostics_status_line() { let diagnostics = ClientDiagnostics::new(