buildStartedMessage,
buildStoppedMessage,
buildUpdatedMessage,
+ clampToSafeTimerValue,
clone,
Configuration,
Constants,
error
)
})
- }, heartbeatInterval)
+ }, clampToSafeTimerValue(heartbeatInterval))
logger.info(
`${this.logPrefix()} Heartbeat started every ${formatDurationMilliSeconds(
heartbeatInterval
error
)
})
- }, interval)
+ }, clampToSafeTimerValue(interval))
} else {
logger.error(
`${this.logPrefix()} Charging station ${
private startWebSocketPing (): void {
const webSocketPingInterval = this.getWebSocketPingInterval()
if (webSocketPingInterval > 0 && this.wsPingSetInterval == null) {
- this.wsPingSetInterval = setInterval(() => {
- if (this.isWebSocketConnectionOpened()) {
- this.wsConnection?.ping()
- }
- }, secondsToMilliseconds(webSocketPingInterval))
+ this.wsPingSetInterval = setInterval(
+ () => {
+ if (this.isWebSocketConnectionOpened()) {
+ this.wsConnection?.ping()
+ }
+ },
+ clampToSafeTimerValue(secondsToMilliseconds(webSocketPingInterval))
+ )
logger.info(
`${this.logPrefix()} WebSocket ping started every ${formatDurationSeconds(
webSocketPingInterval
static readonly MAX_RANDOM_INTEGER = 281474976710655 // 2^48 - 1 (randomInit() limit)
+ // Node.js setInterval/setTimeout maximum safe delay value (2^31-1 ms ≈ 24.8 days)
+ // Values exceeding this limit cause Node.js to reset the delay to 1ms
+ static readonly MAX_SETINTERVAL_DELAY = 2147483647 // Ms
+
static readonly OCPP_VALUE_ABSOLUTE_MAX_LENGTH = 2500
static readonly PERFORMANCE_RECORDS_TABLE = 'performance_records'
type UUIDv4,
WebSocketCloseEventStatusString,
} from '../types/index.js'
+import { Constants } from './Constants.js'
type NonEmptyArray<T> = [T, ...T[]]
type ReadonlyNonEmptyArray<T> = readonly [T, ...(readonly T[])]
return delay + randomSum
}
+/**
+ * Clamps a timer delay value to the safe range for Node.js setInterval/setTimeout.
+ * @param delayMs - The delay value in milliseconds.
+ * @returns The clamped delay value, guaranteed to be within [0, 2^31-1] ms.
+ * @see https://nodejs.org/api/timers.html#settimeoutcallback-delay-args
+ */
+export const clampToSafeTimerValue = (delayMs: number): number => {
+ return Math.min(Math.max(0, delayMs), Constants.MAX_SETINTERVAL_DELAY)
+}
+
/**
* Generates a cryptographically secure random number in the [0,1[ range
* @returns A number in the [0,1[ range
} from './MessageChannelUtils.js'
export { average, max, median, min, percentile, std } from './StatisticUtils.js'
export {
+ clampToSafeTimerValue,
clone,
convertToBoolean,
convertToDate,
import { JSRuntime, runtime } from '../../scripts/runtime.js'
import { Constants } from '../../src/utils/Constants.js'
import {
+ clampToSafeTimerValue,
clone,
convertToBoolean,
convertToDate,
expect(isArraySorted<number>([1, 2, 3, 5, 4], (a, b) => a - b)).toBe(false)
expect(isArraySorted<number>([2, 1, 3, 4, 5], (a, b) => a - b)).toBe(false)
})
+
+ await it('Verify clampToSafeTimerValue()', () => {
+ expect(clampToSafeTimerValue(0)).toBe(0)
+ expect(clampToSafeTimerValue(1000)).toBe(1000)
+ expect(clampToSafeTimerValue(Constants.MAX_SETINTERVAL_DELAY)).toBe(
+ Constants.MAX_SETINTERVAL_DELAY
+ )
+ expect(clampToSafeTimerValue(Constants.MAX_SETINTERVAL_DELAY + 1)).toBe(
+ Constants.MAX_SETINTERVAL_DELAY
+ )
+ expect(clampToSafeTimerValue(Number.MAX_SAFE_INTEGER)).toBe(Constants.MAX_SETINTERVAL_DELAY)
+ expect(clampToSafeTimerValue(-1)).toBe(0)
+ expect(clampToSafeTimerValue(-1000)).toBe(0)
+ })
})