nowMsTrusted method
Trusted "now", in UTC epoch milliseconds. Never goes backward
(rewind-proof, same guarantee nowMsClamped() gives), and never
permanently locks onto a suspicious far-future jump (see class doc).
Migrates from the legacy StorageKeys.maxMsSeen watermark on first
ever call (IDEA-40 slice 4) — an install already running the plain
nowMsClamped-backed clock keeps its existing rewind-protection
floor instead of restarting from a lower value, which would both
look like a spurious backward jump and could hand back a window an
old exploit already burned.
Implementation
int nowMsTrusted() {
final storage = StorageService.to;
final sample = _sampleNow();
final storedBaseline = storage.getInt(
StorageKeys.trustedClockBaselineMs,
def: 0,
);
// A dedicated existence check — NOT `prevWallMs == 0` — is required
// here: 0 is a perfectly legitimate wall-clock reading in tests (and,
// in principle, epoch 0 in production), so treating it as the "never
// initialized" sentinel would make any real sample that happens to
// land back on the stored 0 re-run first-call migration instead of
// being classified normally.
final hasPrevSample = storage.allKeys().contains(
StorageKeys.trustedClockPrevWallMs,
);
if (!hasPrevSample) {
// First ever sample for this install — nothing to classify yet.
// Migrate from the legacy watermark (see doc above) if higher than
// this fresh wall-clock reading.
final legacyWatermark = storage.getInt(StorageKeys.maxMsSeen, def: 0);
final baseline = legacyWatermark > sample.wallMs
? legacyWatermark
: sample.wallMs;
_persist(sample, baseline);
return baseline;
}
final prevWallMs = storage.getInt(
StorageKeys.trustedClockPrevWallMs,
def: 0,
);
final prevMonotonicMs = storage.getInt(
StorageKeys.trustedClockPrevMonotonicMs,
def: 0,
);
final judgement = classifyClockSample(
previous: ClockSample(wallMs: prevWallMs, monotonicMs: prevMonotonicMs),
current: sample,
normalTolerance: normalTolerance,
suspiciousJumpThreshold: suspiciousJumpThreshold,
);
_lastJudgement = judgement;
switch (judgement) {
case ClockJudgement.normal:
case ClockJudgement.reboot:
// Both a plain forward tick AND a reboot advance the baseline
// when the new wall reading is ahead of it — a reboot can't be
// cross-checked against monotonic elapsed time (the whole point
// of "reboot"), so it degrades to the same rewind-safe-only
// guarantee `nowMsClamped()` already gives (documented
// limitation, not a new one: `clamped_clock.dart` is equally
// unable to catch a kill-app-wind-clock-reopen cycle).
//
// The new sample becomes the trusted "previous" going forward —
// ONLY for a trusted judgement. See the other 2 cases below for
// why an untrusted sample must never become that reference.
final newBaseline = sample.wallMs > storedBaseline
? sample.wallMs
: storedBaseline;
_persist(sample, newBaseline);
return newBaseline;
case ClockJudgement.rewind:
case ClockJudgement.suspiciousForwardJump:
// Neither the baseline NOR the "previous sample" reference
// advances here — this is what makes recovery possible (IDEA-40's
// actual fix). If a quarantined jump's bogus wall reading were
// persisted as the new "previous", the very next honest sample
// (wall clock corrected back near reality) would itself look
// like a huge REWIND relative to that bogus reference and get
// quarantined too — a self-inflicted permanent lock, exactly the
// failure mode this class exists to avoid. Keeping the last
// TRUSTED sample as the comparison point means a corrected clock
// is compared against reality, not against the bad reading.
return storedBaseline;
}
}