fix(utils): clamp computeExponentialBackOffDelay output to setTimeout-safe range (#1951)
Fix a latent overflow bug in `computeExponentialBackOffDelay` that turned intended exponential backoff into a tight-loop retry storm at high retry counts.
The function returned `baseDelayMs * 2^retry + jitter` unclamped. With
`baseDelayMs = 100` (default) it overflows `MAX_SETINTERVAL_DELAY_MS`
(2_147_483_647 ms, ~24.8 days) at retry ≥25; with `baseDelayMs = 60_000`
(BootNotification interval default) it overflows at retry ≥16. Node.js
`setTimeout` silently coerces out-of-range delays to 1 ms per its docs,
producing a tight-loop reconnect/retry storm instead of the intended
exponential wait.
Fix: wrap the return value with `clampToSafeTimerValue()` inside
`computeExponentialBackOffDelay`. Single-site DRY defense covering all
5 present and future callers.
Call sites verified (5):
- ChargingStation.ts:1608 getReconnectDelay — vulnerable
(wsConnectionRetryCount unbounded when autoReconnectMaxRetries = -1)
- ChargingStation.ts:2532 onOpen BootNotification retry — vulnerable
(registrationRetryCount unbounded when registrationMaxRetries = -1)
- ChargingStation.ts:2788 sendMessageBuffer — vulnerable
(messageIdx unbounded)
- OCPP20ServiceUtils.ts:253 computeReconnectDelay — safe by
construction (maxRetries: RetryBackOffRepeatTimes)
- OCPP20CertSigningRetryManager.ts:96 scheduleNextRetry — safe by
construction (maxRetries: CertSigningRepeatTimes)
Extends `computeExponentialBackOffDelay` JSDoc `@returns` to advertise
the clamp guarantee explicitly:
`delay in milliseconds, guaranteed within [0, Constants.MAX_SETINTERVAL_DELAY_MS]`.
Regression tests added at retry 25 (base 100 ms), retry 30 (extreme),
and a 20-sample jitter loop at retry 25 with `jitterPercent: 0.2`.
At retry ≥25 the pre-jitter delay already exceeds the ceiling, so the
jitter loop asserts strict equality with MAX (deterministic).
Priority-3 anchor comments preserved: mathematical formula anchor for
the overflow boundary and clamp-after-jitter ordering invariant.