engine: a refused refresh with a Retry-After escalates to a fresh registration at once

RegRecovery retried a refused refresh once over the same registration
before escalating, on the reading that one 503 may be a hiccup. A
refusal whose Retry-After is at least a second is the network's
considered answer: on O2 UK (2026-10-08, imsd 0.3.10) the keepalive was
refused 503 with Retry-After 414 and the refresh after that wait 503
with Retry-After 772, so 0.3.10 scheduled the fresh registration no
earlier than 414 + 772 s after the keepalive's refusal. Refusals
without such a Retry-After (none, 0, unparsable) keep the two-refusal
rule, so KPN's bare 403 is unchanged.

Tested with the Engine suite, whose checks of a refusal carrying a
Retry-After now expect the fresh step.
This commit is contained in:
Jorijn van der Graaf 2026-10-10 17:18:21 +02:00
commit a00b14a5a9
Signed by: jorijnvdgraaf
GPG key ID: 2937E59CDCC1BCFB
4 changed files with 37 additions and 21 deletions

View file

@ -882,9 +882,9 @@ int main() {
// Retry-After is the network's word: it holds every attempt for its
// own length, next to the backoff, and a Dial cuts only the backoff
r.OnReconnectFailure(1000, F::Refused, 90);
Check(reconnectAt(r, 1000, 1090), "Retry-After 90 beats the 5-s backoff");
Check(r.Fresh() && r.Reconnectable() && freshAt(r, 1000, 1090), "a refusal with a Retry-After escalates at once: the fresh attempt behind the network's wait (O2 2026-10-08), the old SA kept");
r.OnDial();
Check(reconnectAt(r, 1000, 1090), "…and a Dial does not cut it");
Check(freshAt(r, 1000, 1090), "…and a Dial does not cut it");
r.OnReconnectFailure(1090, F::Refused, 3);
Check(r.Fresh() && freshAt(r, 1090, 1100), "a Retry-After shorter than the backoff does not shorten it");
r.OnDial();
@ -957,7 +957,7 @@ int main() {
Check(ra.freshHoldUntil == 5000 + 3600, "…a day's for an hour: the refusal is heard again then");
RegRecovery rb;
rb.OnReconnectFailure(1000, F::Refused, 86400);
Check(reconnectAt(rb, 1000, 1000 + 3600), "a refresh's Retry-After is cut to the hour the same");
Check(freshAt(rb, 1000, 1000 + 3600), "a refresh's Retry-After is cut to the hour the same, the fresh attempt behind it");
RegRecovery h;
h.OnFreshFailure(1000, F::Refused, 60);
Check(h.freshHoldUntil == 1060, "a refused fresh attempt with Retry-After 60 waits 60 s, not the schedule");