]> Piment Noir Git Repositories - e-mobility-charging-stations-simulator.git/commit
fix(utils): clamp computeExponentialBackOffDelay output to setTimeout-safe range...
authorJérôme Benoit <jerome.benoit@piment-noir.org>
Sun, 5 Jul 2026 18:30:45 +0000 (20:30 +0200)
committerGitHub <noreply@github.com>
Sun, 5 Jul 2026 18:30:45 +0000 (20:30 +0200)
commita91880ad922d2e4f2e8272a37606daf17c1b09a7
treeca53bc4f8160e95ddcc182556ee5db7859016f15
parent0b0352f495d0ba2ad4915d3187c3554b1da8f6eb
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.

Quality gates: typecheck clean, lint clean, tests/utils/Utils.test.ts
54/54 pass.

Diff: +34/-3 across 2 files.

Reviews applied (3 rounds, 4 agents each): 8 substantive findings
across v1+v2 addressed; v3 saturation HIGH declared by 4/4 agents
(0 substantive findings, 6 false positives rejected via caller-trace).
src/utils/Utils.ts
tests/utils/Utils.test.ts