From 13e1bcc36900488a7ab28938f4b90731fd6a8133 Mon Sep 17 00:00:00 2001 From: "Zhidong Peng (HE/HIM)" Date: Tue, 14 Apr 2026 13:16:44 -0700 Subject: [PATCH 1/7] GPA service to use host-date-time for signed http requests --- proxy_agent/src/key_keeper/key.rs | 22 ++++++- proxy_agent_shared/src/hyper_client.rs | 53 ++++++++++++++++ proxy_agent_shared/src/misc_helpers.rs | 86 +++++++++++++++++++++++++- 3 files changed, 158 insertions(+), 3 deletions(-) diff --git a/proxy_agent/src/key_keeper/key.rs b/proxy_agent/src/key_keeper/key.rs index e8c2b61d..7a2bf662 100644 --- a/proxy_agent/src/key_keeper/key.rs +++ b/proxy_agent/src/key_keeper/key.rs @@ -36,9 +36,9 @@ use hyper::Uri; use proxy_agent_shared::hyper_client; use proxy_agent_shared::logger::LoggerLevel; use serde_derive::{Deserialize, Serialize}; -use std::ffi::OsString; use std::fmt::{Display, Formatter}; use std::{collections::HashMap, path::PathBuf}; +use std::{ffi::OsString, time::Duration}; const AUDIT_MODE: &str = "audit"; const ENFORCE_MODE: &str = "enforce"; @@ -729,7 +729,11 @@ impl Display for KeyAction { const STATUS_URL: &str = "/secure-channel/status"; const KEY_URL: &str = "/secure-channel/key"; +const HOST_DATE_TIME_SYNC_MAX_AGE: Duration = Duration::from_secs(60); +/// Get the current status of the key from the secure channel. +/// This function will perform a GET request to the secure channel status endpoint. +/// If the host time sync is stale, it will use `get_and_sync_host_time` to update the host time. pub async fn get_status(host: &str, port: u16) -> Result { let endpoint = hyper_client::HostEndpoint::new(host, port, STATUS_URL); let mut headers = HashMap::new(); @@ -737,8 +741,22 @@ pub async fn get_status(host: &str, port: u16) -> Result { hyper_client::METADATA_HEADER.to_string(), "True ".to_string(), ); + let status: KeyStatus = - hyper_client::get(&endpoint, &headers, None, None, logger::write_warning).await?; + if proxy_agent_shared::misc_helpers::host_time_sync_is_stale(HOST_DATE_TIME_SYNC_MAX_AGE) { + hyper_client::get_and_sync_host_time( + &endpoint, + &headers, + None, + None, + logger::write_warning, + ) + .await? + .0 + } else { + hyper_client::get(&endpoint, &headers, None, None, logger::write_warning).await? + }; + status.validate()?; Ok(status) diff --git a/proxy_agent_shared/src/hyper_client.rs b/proxy_agent_shared/src/hyper_client.rs index 8fe933a6..5740ec5c 100644 --- a/proxy_agent_shared/src/hyper_client.rs +++ b/proxy_agent_shared/src/hyper_client.rs @@ -142,6 +142,59 @@ where read_response_body(response).await } +/// Performs a GET request and updates the shared host-time sync state from +/// `x-ms-azure-host-date` response header if available and parseable. +/// +/// Returns `Ok(true)` when sync state was updated, `Ok(false)` when header is +/// missing/invalid, and `Err` on transport/server status failure. +pub async fn get_and_sync_host_time( + endpoint: &HostEndpoint, + headers: &HashMap, + key_guid: Option, + key: Option, + log_fun: F, +) -> Result<(T, bool)> +where + T: DeserializeOwned, + F: Fn(String) + Send + 'static, +{ + let request = build_request(Method::GET, endpoint, headers, None, key_guid, key)?; + let response = send_request(&endpoint.host, endpoint.port, request, log_fun).await?; + + let status = response.status(); + if !status.is_success() { + return Err(Error::Hyper(HyperErrorType::ServerError( + endpoint.to_string(), + status, + ))); + } + + let host_time_synced = sync_host_time_from_headers(response.headers()); + let body = read_response_body(response).await?; + Ok((body, host_time_synced)) +} + +/// Try to sync host time from a response header map. +/// Returns true when sync is updated successfully. +pub fn sync_host_time_from_headers(headers: &hyper::HeaderMap) -> bool { + // first try custom date header + if let Some(host_date) = headers.get(DATE_HEADER) { + if let Ok(host_date_rfc1123) = host_date.to_str() { + return misc_helpers::sync_host_utc_time_from_rfc1123_string(host_date_rfc1123); + } + } + + // fallback to standard HTTP date header + if let Some(host_date) = headers.get(hyper::header::DATE) { + if let Ok(host_date_rfc1123) = host_date.to_str() { + return misc_helpers::sync_host_utc_time_from_rfc1123_string(host_date_rfc1123); + } + } + + // return false if no valid date header found + false +} + pub async fn read_response_body( mut response: hyper::Response, ) -> Result diff --git a/proxy_agent_shared/src/misc_helpers.rs b/proxy_agent_shared/src/misc_helpers.rs index 9378c1b0..5f022dbf 100644 --- a/proxy_agent_shared/src/misc_helpers.rs +++ b/proxy_agent_shared/src/misc_helpers.rs @@ -11,6 +11,8 @@ use std::{ fs::{self, File}, path::{Path, PathBuf}, process::Command, + sync::RwLock, + time::Instant, }; use thread_id; use time::{format_description, OffsetDateTime, PrimitiveDateTime}; @@ -48,6 +50,14 @@ static RFC1123_FORMAT: std::sync::LazyLock>> = + std::sync::LazyLock::new(|| RwLock::new(None)); + pub fn get_date_time_string_with_milliseconds() -> String { let time_str = OffsetDateTime::now_utc() .format(&*ISO8601_MILLIS_FORMAT) @@ -63,11 +73,56 @@ pub fn get_date_time_string() -> String { } pub fn get_date_time_rfc1123_string() -> String { - OffsetDateTime::now_utc() + get_current_utc_time_synced() .format(&*RFC1123_FORMAT) .expect("Failed to format RFC1123 date") } +/// Update host-time sync state from a RFC1123 datetime string. +/// Returns true when sync state is updated successfully, false otherwise. +pub fn sync_host_utc_time_from_rfc1123_string(host_utc_rfc1123: &str) -> bool { + let Ok(parsed_host_utc) = PrimitiveDateTime::parse(host_utc_rfc1123, &*RFC1123_FORMAT) else { + return false; + }; + + let Ok(mut state) = HOST_TIME_SYNC_STATE.write() else { + return false; + }; + + *state = Some(HostTimeSyncState { + synced_host_utc: parsed_host_utc.assume_utc(), + synced_instant: Instant::now(), + }); + true +} + +/// Returns true when current host-time sync state is older than `max_age`. +/// If there is no host-time sync state yet, this returns true. +pub fn host_time_sync_is_stale(max_age: std::time::Duration) -> bool { + let Ok(state) = HOST_TIME_SYNC_STATE.read() else { + return true; + }; + state + .as_ref() + .is_none_or(|synced| synced.synced_instant.elapsed() > max_age) +} + +fn get_current_utc_time_synced() -> OffsetDateTime { + let Ok(state) = HOST_TIME_SYNC_STATE.read() else { + return OffsetDateTime::now_utc(); + }; + + let Some(synced) = state.as_ref() else { + return OffsetDateTime::now_utc(); + }; + + let elapsed = synced.synced_instant.elapsed(); + match time::Duration::try_from(elapsed) { + Ok(elapsed_time) => synced.synced_host_utc + elapsed_time, + Err(_) => OffsetDateTime::now_utc(), + } +} + pub fn get_date_time_unix_nano() -> i128 { OffsetDateTime::now_utc().unix_timestamp_nanos() } @@ -471,6 +526,7 @@ mod tests { use std::env; use std::fs; use std::path::PathBuf; + use std::time::Duration; #[derive(Serialize, Deserialize)] struct TestStruct { @@ -823,4 +879,32 @@ mod tests { "Should fail to parse invalid datetime string" ); } + + #[test] + fn sync_host_utc_time_from_rfc1123_string_test() { + let host_time = "Mon, 01 Jan 2024 00:00:00 GMT"; + assert!( + super::sync_host_utc_time_from_rfc1123_string(host_time), + "Expected valid host RFC1123 time to update sync state" + ); + + assert!( + !super::host_time_sync_is_stale(Duration::from_secs(3600)), + "Sync state should not be stale right after update" + ); + + std::thread::sleep(Duration::from_millis(1)); + assert!( + super::host_time_sync_is_stale(Duration::from_millis(0)), + "Sync state should be stale when max_age is zero" + ); + } + + #[test] + fn sync_host_utc_time_from_rfc1123_string_invalid_input_test() { + assert!( + !super::sync_host_utc_time_from_rfc1123_string("invalid-rfc1123"), + "Expected invalid host RFC1123 time to fail" + ); + } } From 2832d232709574f50b5c9c74fb7d8757abef8b9c Mon Sep 17 00:00:00 2001 From: "Zhidong Peng (HE/HIM)" Date: Tue, 14 Apr 2026 13:52:27 -0700 Subject: [PATCH 2/7] add logging --- proxy_agent/src/key_keeper/key.rs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/proxy_agent/src/key_keeper/key.rs b/proxy_agent/src/key_keeper/key.rs index 7a2bf662..2efb711f 100644 --- a/proxy_agent/src/key_keeper/key.rs +++ b/proxy_agent/src/key_keeper/key.rs @@ -729,7 +729,7 @@ impl Display for KeyAction { const STATUS_URL: &str = "/secure-channel/status"; const KEY_URL: &str = "/secure-channel/key"; -const HOST_DATE_TIME_SYNC_MAX_AGE: Duration = Duration::from_secs(60); +const HOST_DATE_TIME_SYNC_MAX_AGE: Duration = Duration::from_secs(60 * 15); /// Get the current status of the key from the secure channel. /// This function will perform a GET request to the secure channel status endpoint. @@ -744,15 +744,16 @@ pub async fn get_status(host: &str, port: u16) -> Result { let status: KeyStatus = if proxy_agent_shared::misc_helpers::host_time_sync_is_stale(HOST_DATE_TIME_SYNC_MAX_AGE) { - hyper_client::get_and_sync_host_time( + let (key_status, host_time_synced) = hyper_client::get_and_sync_host_time( &endpoint, &headers, None, None, logger::write_warning, ) - .await? - .0 + .await?; + logger::write(format!("Host time synced: {host_time_synced}",)); + key_status } else { hyper_client::get(&endpoint, &headers, None, None, logger::write_warning).await? }; From 5a56e16c924a2dbd93fa26aebf178f5e6d76a24f Mon Sep 17 00:00:00 2001 From: "Zhidong Peng (HE/HIM)" Date: Wed, 15 Apr 2026 12:42:57 -0700 Subject: [PATCH 3/7] fix typo --- .github/actions/spelling/expect.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/actions/spelling/expect.txt b/.github/actions/spelling/expect.txt index 8a5c2865..eaaeca46 100644 --- a/.github/actions/spelling/expect.txt +++ b/.github/actions/spelling/expect.txt @@ -206,6 +206,7 @@ openprocess oneshot opencode opensource +parseable PERCPU pgpkey pgrep From d6d01d6ffe6e33fbb0ff8325aa4aee982d7abe47 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 23 Apr 2026 12:50:28 -0700 Subject: [PATCH 4/7] Bump rand from 0.8.5 to 0.8.6 (#339) Bumps [rand](https://github.com/rust-random/rand) from 0.8.5 to 0.8.6. - [Release notes](https://github.com/rust-random/rand/releases) - [Changelog](https://github.com/rust-random/rand/blob/0.8.6/CHANGELOG.md) - [Commits](https://github.com/rust-random/rand/compare/0.8.5...0.8.6) --- updated-dependencies: - dependency-name: rand dependency-version: 0.8.6 dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Cargo.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 1df43fca..cec20c17 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -980,9 +980,9 @@ dependencies = [ [[package]] name = "rand" -version = "0.8.5" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +checksum = "5ca0ecfa931c29007047d1bc58e623ab12e5590e8c7cc53200d5202b69266d8a" dependencies = [ "libc", "rand_chacha", From 1bf4b3f64d98138a581a13c90cf576f7416c2895 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 23 Apr 2026 20:06:24 +0000 Subject: [PATCH 5/7] Bump openssl from 0.10.73 to 0.10.78 (#338) Bumps [openssl](https://github.com/rust-openssl/rust-openssl) from 0.10.73 to 0.10.78. - [Release notes](https://github.com/rust-openssl/rust-openssl/releases) - [Commits](https://github.com/rust-openssl/rust-openssl/compare/openssl-v0.10.73...openssl-v0.10.78) --- updated-dependencies: - dependency-name: openssl dependency-version: 0.10.78 dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Zhidong Peng --- Cargo.lock | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index cec20c17..ce02cf76 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -824,9 +824,9 @@ checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" [[package]] name = "openssl" -version = "0.10.73" +version = "0.10.78" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8505734d46c8ab1e19a1dce3aef597ad87dcb4c37e7188231769bd6bd51cebf8" +checksum = "f38c4372413cdaaf3cc79dd92d29d7d9f5ab09b51b10dded508fb90bb70b9222" dependencies = [ "bitflags", "cfg-if", @@ -859,9 +859,9 @@ dependencies = [ [[package]] name = "openssl-sys" -version = "0.9.109" +version = "0.9.114" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90096e2e47630d78b7d1c20952dc621f957103f8bc2c8359ec81290d75238571" +checksum = "13ce1245cd07fcc4cfdb438f7507b0c7e4f3849a69fd84d52374c66d83741bb6" dependencies = [ "cc", "libc", From 7e627743faffd4c13a3bbb432c90c4fd390a77f7 Mon Sep 17 00:00:00 2001 From: Zhidong Peng Date: Thu, 23 Apr 2026 21:21:27 +0000 Subject: [PATCH 6/7] resolve comments Co-authored-by: Copilot --- proxy_agent/src/key_keeper/key.rs | 45 ++++++---- proxy_agent_shared/src/hyper_client.rs | 119 ++++++++++++++++++------- proxy_agent_shared/src/misc_helpers.rs | 19 +++- 3 files changed, 130 insertions(+), 53 deletions(-) diff --git a/proxy_agent/src/key_keeper/key.rs b/proxy_agent/src/key_keeper/key.rs index 2efb711f..cc850e55 100644 --- a/proxy_agent/src/key_keeper/key.rs +++ b/proxy_agent/src/key_keeper/key.rs @@ -729,11 +729,11 @@ impl Display for KeyAction { const STATUS_URL: &str = "/secure-channel/status"; const KEY_URL: &str = "/secure-channel/key"; -const HOST_DATE_TIME_SYNC_MAX_AGE: Duration = Duration::from_secs(60 * 15); +const HOST_DATE_TIME_DRIFT_MAX_AGE: Duration = Duration::from_secs(60 * 15); /// Get the current status of the key from the secure channel. -/// This function will perform a GET request to the secure channel status endpoint. -/// If the host time sync is stale, it will use `get_and_sync_host_time` to update the host time. +/// This function will perform a single GET request to the secure channel status endpoint. +/// If the host time sync is stale, it will sync the host time from the response headers. pub async fn get_status(host: &str, port: u16) -> Result { let endpoint = hyper_client::HostEndpoint::new(host, port, STATUS_URL); let mut headers = HashMap::new(); @@ -742,22 +742,31 @@ pub async fn get_status(host: &str, port: u16) -> Result { "True ".to_string(), ); - let status: KeyStatus = - if proxy_agent_shared::misc_helpers::host_time_sync_is_stale(HOST_DATE_TIME_SYNC_MAX_AGE) { - let (key_status, host_time_synced) = hyper_client::get_and_sync_host_time( - &endpoint, - &headers, - None, - None, - logger::write_warning, - ) - .await?; - logger::write(format!("Host time synced: {host_time_synced}",)); - key_status - } else { - hyper_client::get(&endpoint, &headers, None, None, logger::write_warning).await? - }; + let request = hyper_client::build_request(Method::GET, &endpoint, &headers, None, None, None)?; + + let response = hyper_client::send_request( + &endpoint.host, + endpoint.port, + request, + logger::write_warning, + ) + .await?; + + if !response.status().is_success() { + return Err(proxy_agent_shared::error::Error::Hyper( + proxy_agent_shared::error::HyperErrorType::ServerError( + endpoint.to_string(), + response.status(), + ), + ))?; + } + + if proxy_agent_shared::misc_helpers::host_time_sync_is_stale(HOST_DATE_TIME_DRIFT_MAX_AGE) { + let host_time_synced = hyper_client::sync_host_time_from_headers(response.headers()); + logger::write(format!("Host time synced: {host_time_synced}")); + } + let status: KeyStatus = hyper_client::read_response_body(response).await?; status.validate()?; Ok(status) diff --git a/proxy_agent_shared/src/hyper_client.rs b/proxy_agent_shared/src/hyper_client.rs index 5740ec5c..71175448 100644 --- a/proxy_agent_shared/src/hyper_client.rs +++ b/proxy_agent_shared/src/hyper_client.rs @@ -142,39 +142,10 @@ where read_response_body(response).await } -/// Performs a GET request and updates the shared host-time sync state from -/// `x-ms-azure-host-date` response header if available and parseable. -/// -/// Returns `Ok(true)` when sync state was updated, `Ok(false)` when header is -/// missing/invalid, and `Err` on transport/server status failure. -pub async fn get_and_sync_host_time( - endpoint: &HostEndpoint, - headers: &HashMap, - key_guid: Option, - key: Option, - log_fun: F, -) -> Result<(T, bool)> -where - T: DeserializeOwned, - F: Fn(String) + Send + 'static, -{ - let request = build_request(Method::GET, endpoint, headers, None, key_guid, key)?; - let response = send_request(&endpoint.host, endpoint.port, request, log_fun).await?; - - let status = response.status(); - if !status.is_success() { - return Err(Error::Hyper(HyperErrorType::ServerError( - endpoint.to_string(), - status, - ))); - } - - let host_time_synced = sync_host_time_from_headers(response.headers()); - let body = read_response_body(response).await?; - Ok((body, host_time_synced)) -} - /// Try to sync host time from a response header map. +/// The function first looks for a custom date header (x-ms-azure-host-date) and +/// if not found, falls back to the standard HTTP date header. +/// if custom date header is present but invalid, it will not fall back to standard date header /// Returns true when sync is updated successfully. pub fn sync_host_time_from_headers(headers: &hyper::HeaderMap) -> bool { // first try custom date header @@ -592,7 +563,7 @@ mod tests { use crate::{ host_clients::{imds_client::ImdsClient, wire_server_client::WireServerClient}, logger::logger_manager, - server_mock, + misc_helpers, server_mock, }; use tokio_util::sync::CancellationToken; @@ -661,4 +632,86 @@ mod tests { cancellation_token.cancel(); } + + #[test] + fn sync_host_time_from_headers_tests() { + // should return false when no date headers are present + let headers = hyper::HeaderMap::new(); + assert!( + !super::sync_host_time_from_headers(&headers), + "should return false when no date headers are present" + ); + + // should return true with valid custom date header + let mut headers = hyper::HeaderMap::new(); + headers.insert( + super::DATE_HEADER, + "Wed, 23 Apr 2025 12:00:00 GMT".parse().unwrap(), + ); + assert!( + super::sync_host_time_from_headers(&headers), + "should return true with valid custom date header" + ); + + // should return true with valid standard Date header + let mut headers = hyper::HeaderMap::new(); + headers.insert( + hyper::header::DATE, + "Wed, 23 Apr 2025 12:00:00 GMT".parse().unwrap(), + ); + assert!( + super::sync_host_time_from_headers(&headers), + "should return true with valid standard Date header" + ); + + // when both headers are present but invalid, should return false without panic (to_str() succeeds but RFC1123 parse fails) + let mut headers = hyper::HeaderMap::new(); + headers.insert(super::DATE_HEADER, "not-a-valid-date".parse().unwrap()); + headers.insert(hyper::header::DATE, "also-not-valid".parse().unwrap()); + assert!( + !super::sync_host_time_from_headers(&headers), + "should return false when both headers have invalid dates" + ); + + // When the custom header is present but has an invalid date string, + // the function returns false immediately (does not fall back to standard Date header) + // because to_str() succeeds but the RFC1123 parse fails. + let mut headers = hyper::HeaderMap::new(); + headers.insert(super::DATE_HEADER, "not-a-valid-date".parse().unwrap()); + headers.insert( + hyper::header::DATE, + "Wed, 23 Apr 2025 12:00:00 GMT".parse().unwrap(), + ); + assert!( + !super::sync_host_time_from_headers(&headers), + "should return false without falling back when custom header has invalid date" + ); + + // when both headers are present and valid, should return true (sync from custom header takes precedence) + // and not fallback to standard Date header + let mut headers = hyper::HeaderMap::new(); + let custom_host_time = "Wed, 23 Apr 2025 12:00:00 GMT"; + headers.insert(super::DATE_HEADER, custom_host_time.parse().unwrap()); + headers.insert( + hyper::header::DATE, + "Thu, 24 Apr 2025 12:00:00 GMT".parse().unwrap(), + ); + // Both are valid; the function should return true + assert!( + super::sync_host_time_from_headers(&headers), + "should return true when both headers are present" + ); + // verify the sync time is from the custom header, not the standard Date header (which is 1 day later) + let sync_time = misc_helpers::parse_rfc1123_to_offset_datetime( + &misc_helpers::get_date_time_rfc1123_string(), + ) + .unwrap(); + let expected_sync_time = + misc_helpers::parse_rfc1123_to_offset_datetime(custom_host_time).unwrap(); + let diff = sync_time - expected_sync_time; + assert!( + diff < time::Duration::seconds(5), + "sync time should be close to the custom header time" + ); + } } diff --git a/proxy_agent_shared/src/misc_helpers.rs b/proxy_agent_shared/src/misc_helpers.rs index 5f022dbf..9d121c0d 100644 --- a/proxy_agent_shared/src/misc_helpers.rs +++ b/proxy_agent_shared/src/misc_helpers.rs @@ -81,7 +81,7 @@ pub fn get_date_time_rfc1123_string() -> String { /// Update host-time sync state from a RFC1123 datetime string. /// Returns true when sync state is updated successfully, false otherwise. pub fn sync_host_utc_time_from_rfc1123_string(host_utc_rfc1123: &str) -> bool { - let Ok(parsed_host_utc) = PrimitiveDateTime::parse(host_utc_rfc1123, &*RFC1123_FORMAT) else { + let Ok(parsed_host_utc) = parse_rfc1123_to_offset_datetime(host_utc_rfc1123) else { return false; }; @@ -90,12 +90,22 @@ pub fn sync_host_utc_time_from_rfc1123_string(host_utc_rfc1123: &str) -> bool { }; *state = Some(HostTimeSyncState { - synced_host_utc: parsed_host_utc.assume_utc(), + synced_host_utc: parsed_host_utc, synced_instant: Instant::now(), }); true } +pub fn parse_rfc1123_to_offset_datetime(rfc1123_str: &str) -> Result { + PrimitiveDateTime::parse(rfc1123_str, &*RFC1123_FORMAT) + .map(|dt| dt.assume_utc()) + .map_err(|e| { + Error::ParseDateTimeStringError(format!( + "Failed to parse RFC1123 datetime string '{rfc1123_str}': {e}" + )) + }) +} + /// Returns true when current host-time sync state is older than `max_age`. /// If there is no host-time sync state yet, this returns true. pub fn host_time_sync_is_stale(max_age: std::time::Duration) -> bool { @@ -898,6 +908,11 @@ mod tests { super::host_time_sync_is_stale(Duration::from_millis(0)), "Sync state should be stale when max_age is zero" ); + + // reset sync state to None for other tests + let _ = super::HOST_TIME_SYNC_STATE + .write() + .map(|mut state| *state = None); } #[test] From 1cbe24a7975758d9125742f8669c4bcc9deee200 Mon Sep 17 00:00:00 2001 From: Zhidong Peng Date: Thu, 23 Apr 2026 21:35:18 +0000 Subject: [PATCH 7/7] fix spelling --- .github/actions/spelling/expect.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/actions/spelling/expect.txt b/.github/actions/spelling/expect.txt index eaaeca46..6e010cc7 100644 --- a/.github/actions/spelling/expect.txt +++ b/.github/actions/spelling/expect.txt @@ -302,6 +302,7 @@ testurl tgid THH thiserror +Thu timedout timeup tlsv