Skip to content

[Bugfix] Compute clock skew from response header arrival time - #3979

Merged
pulimsr merged 5 commits into
mainfrom
fix/clock-skew-response-received-time
Nov 9, 2026
Merged

pulimsr merged 5 commits into
mainfrom
fix/clock-skew-response-received-time

Conversation

@pulimsr

@pulimsr pulimsr commented Nov 4, 2026 •

Copy link
Copy Markdown
Collaborator

Clock skew was computed as the server's Date minus the midpoint of the request round trip, with the received time taken after the body was read. A long-running request with in-sync clocks recorded a false skew (+310 s for a 620 s request), past the SigV4 window. This follows the updated clock skew SEP: skew is now Date minus the time the response headers arrived.
Changes:

  • ComputeClockSkewCandidate uses serverTime - timeResponseReceived. The Age, missing-Date and 15-minute discards are unchanged.
  • HttpRequest gets a per-attempt response-headers-received time, set by the curl, CRT and WinHTTP/WinINet clients before the body is read. Custom HTTP clients that don't set it fall back to the previous capture point.
  • The legacy and smithy clients clear it at the start of each attempt and use it as the received time.
  • clock-skew-test-cases.json is synced with the SEP's shared vectors, including the new 620 s case, and a mock client test checks that a slow body doesn't affect the measured skew.

Check all that applies:

  • Did a review by yourself.
  • Added proper tests to cover this PR. (If tests are not applicable, explain.)
  • Checked if this PR is a breaking (APIs have been changed) change.
  • Checked if this PR will not introduce cross-platform inconsistent behavior.
  • Checked if this PR would require a ReadMe/Wiki update.

Check which platforms you have built SDK on to verify the correctness of this PR.

  • Linux
  • Windows
  • Android
  • MacOS
  • IOS
  • Other Platforms

By submitting this pull request, I confirm that you can use, modify, copy, and redistribute this contribution, under the terms of your choice.

Update clock skew correction to the revised clock skew SEP: the candidate
skew is now serverTime - timeResponseReceived instead of the request
round-trip midpoint, and timeResponseReceived is the raw client time when
the response headers arrive, before the body is read.

The midpoint attributed half of the server processing time to skew, so a
620 s request with in-sync clocks recorded +310 s and poisoned ClientSkew.

- ClockSkew.h: new formula; Age, missing-Date and 15-minute discards unchanged.
- HttpRequest: per-attempt ResponseHeadersReceivedTime, set by the curl,
  CRT and WinHTTP/WinINet clients; falls back to Now() when unset.
- AWSClient (sync/async) and AwsSmithyClientBase clear it per attempt and
  use it as the received time.
- Sync clock-skew-test-cases.json with the SEP's shared vectors and add a
  mock client test for a slow body.
@pulimsr
pulimsr requested a review from Madrigal November 4, 2026 19:26

pRequestCtx->m_timeResponseReceived = Aws::Utils::DateTime::Now();
pRequestCtx->m_timeResponseReceived =
pRequestCtx->m_httpRequest ? Aws::Internal::GetTimeResponseReceived(*pRequestCtx->m_httpRequest) : Aws::Utils::DateTime::Now();

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

under which which conditions would this not have an httpRequest?

const DateTime headersReceived = DateTime::Now();
httpRequest->SetResponseHeadersReceivedTime(headersReceived);
firstResponse->AddHeader("Date", (headersReceived + std::chrono::hours(1)).ToGmtString(DateFormat::RFC822));
std::this_thread::sleep_for(std::chrono::seconds(3));

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd love if there was a way where we don't have to actually sleep on tests, but unfamiliar if there's any time mocking in cpp

ASSERT_TRUE(signingTime.has_value());
const int64_t skewMs = signingTime.value().Millis() - before.Millis();
const int64_t hourMs = std::chrono::milliseconds(std::chrono::hours(1)).count();
EXPECT_GE(skewMs, hourMs - 1100);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why are we comparing -1100,500 range? maybe I'm missing something but it seems somewhat arbitrary

const auto& signingTime = requests.back().GetSigningTimestampOverride();
ASSERT_TRUE(signingTime.has_value());
const DateTime sendTime = signingTime.value() - std::chrono::hours(1);
EXPECT_GE(sendTime.Millis(), before.Millis());

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we are just checking here that the value is greater than X, shouldn't we check that this is within Y range or exactly X?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

moved the range to a single check

@pulimsr
pulimsr added this pull request to the merge queue Nov 9, 2026
Merged via the queue into main with commit 3953b06 Nov 9, 2026
5 of 6 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants