Fix strict boolean checks
[e-mobility-charging-stations-simulator.git] / src / charging-station / ChargingStationUtils.ts
CommitLineData
8114d10e
JB
1import crypto from 'crypto';
2import path from 'path';
3import { fileURLToPath } from 'url';
4
5import moment from 'moment';
6
7import BaseError from '../exception/BaseError';
981ebfbe
JB
8import type { ChargingStationInfo } from '../types/ChargingStationInfo';
9import {
492cf6ab 10 AmpereUnits,
981ebfbe 11 type ChargingStationTemplate,
492cf6ab
JB
12 CurrentType,
13 Voltage,
14} from '../types/ChargingStationTemplate';
6c1761d4 15import type { SampledValueTemplate } from '../types/MeasurandPerPhaseSampledValueTemplates';
8114d10e 16import { ChargingProfileKindType, RecurrencyKindType } from '../types/ocpp/1.6/ChargingProfile';
6c1761d4 17import type { ChargingProfile, ChargingSchedulePeriod } from '../types/ocpp/ChargingProfile';
492cf6ab 18import { StandardParametersKey } from '../types/ocpp/Configuration';
8114d10e 19import { MeterValueMeasurand, MeterValuePhase } from '../types/ocpp/MeterValues';
65554cc3 20import {
981ebfbe 21 type BootNotificationRequest,
65554cc3
JB
22 IncomingRequestCommand,
23 RequestCommand,
24} from '../types/ocpp/Requests';
17ac262c 25import { WorkerProcessType } from '../types/Worker';
8114d10e
JB
26import Configuration from '../utils/Configuration';
27import Constants from '../utils/Constants';
17ac262c 28import logger from '../utils/Logger';
8114d10e 29import Utils from '../utils/Utils';
ada189a8 30import type ChargingStation from './ChargingStation';
8114d10e 31import { ChargingStationConfigurationUtils } from './ChargingStationConfigurationUtils';
17ac262c 32
91a4f151
JB
33const moduleName = 'ChargingStationUtils';
34
17ac262c 35export class ChargingStationUtils {
d5bd1c00
JB
36 private constructor() {
37 // This is intentional
38 }
39
17ac262c
JB
40 public static getChargingStationId(
41 index: number,
42 stationTemplate: ChargingStationTemplate
43 ): string {
44 // In case of multiple instances: add instance index to charging station id
45 const instanceIndex = process.env.CF_INSTANCE_INDEX ?? 0;
46 const idSuffix = stationTemplate.nameSuffix ?? '';
47 const idStr = '000000000' + index.toString();
ccb1d6e9 48 return stationTemplate?.fixedName
17ac262c
JB
49 ? stationTemplate.baseName
50 : stationTemplate.baseName +
51 '-' +
52 instanceIndex.toString() +
53 idStr.substring(idStr.length - 4) +
54 idSuffix;
55 }
56
fa7bccf4 57 public static getHashId(index: number, stationTemplate: ChargingStationTemplate): string {
17ac262c 58 const hashBootNotificationRequest = {
fa7bccf4
JB
59 chargePointModel: stationTemplate.chargePointModel,
60 chargePointVendor: stationTemplate.chargePointVendor,
61 ...(!Utils.isUndefined(stationTemplate.chargeBoxSerialNumberPrefix) && {
62 chargeBoxSerialNumber: stationTemplate.chargeBoxSerialNumberPrefix,
17ac262c 63 }),
fa7bccf4
JB
64 ...(!Utils.isUndefined(stationTemplate.chargePointSerialNumberPrefix) && {
65 chargePointSerialNumber: stationTemplate.chargePointSerialNumberPrefix,
17ac262c 66 }),
fa7bccf4
JB
67 ...(!Utils.isUndefined(stationTemplate.firmwareVersion) && {
68 firmwareVersion: stationTemplate.firmwareVersion,
17ac262c 69 }),
fa7bccf4
JB
70 ...(!Utils.isUndefined(stationTemplate.iccid) && { iccid: stationTemplate.iccid }),
71 ...(!Utils.isUndefined(stationTemplate.imsi) && { imsi: stationTemplate.imsi }),
72 ...(!Utils.isUndefined(stationTemplate.meterSerialNumberPrefix) && {
73 meterSerialNumber: stationTemplate.meterSerialNumberPrefix,
17ac262c 74 }),
fa7bccf4
JB
75 ...(!Utils.isUndefined(stationTemplate.meterType) && {
76 meterType: stationTemplate.meterType,
17ac262c
JB
77 }),
78 };
79 return crypto
80 .createHash(Constants.DEFAULT_HASH_ALGORITHM)
fa7bccf4
JB
81 .update(
82 JSON.stringify(hashBootNotificationRequest) +
83 ChargingStationUtils.getChargingStationId(index, stationTemplate)
84 )
17ac262c
JB
85 .digest('hex');
86 }
87
fa7bccf4
JB
88 public static getTemplateMaxNumberOfConnectors(stationTemplate: ChargingStationTemplate): number {
89 const templateConnectors = stationTemplate?.Connectors;
90 if (!templateConnectors) {
91 return -1;
92 }
93 return Object.keys(templateConnectors).length;
94 }
95
96 public static checkTemplateMaxConnectors(
97 templateMaxConnectors: number,
98 templateFile: string,
99 logPrefix: string
100 ): void {
101 if (templateMaxConnectors === 0) {
102 logger.warn(
103 `${logPrefix} Charging station information from template ${templateFile} with empty connectors configuration`
104 );
105 } else if (templateMaxConnectors < 0) {
106 logger.error(
107 `${logPrefix} Charging station information from template ${templateFile} with no connectors configuration defined`
108 );
109 }
110 }
111
112 public static getConfiguredNumberOfConnectors(
113 index: number,
114 stationTemplate: ChargingStationTemplate
115 ): number {
116 let configuredMaxConnectors: number;
117 if (!Utils.isEmptyArray(stationTemplate.numberOfConnectors)) {
118 const numberOfConnectors = stationTemplate.numberOfConnectors as number[];
119 // Distribute evenly the number of connectors
120 configuredMaxConnectors = numberOfConnectors[(index - 1) % numberOfConnectors.length];
121 } else if (!Utils.isUndefined(stationTemplate.numberOfConnectors)) {
122 configuredMaxConnectors = stationTemplate.numberOfConnectors as number;
123 } else {
124 configuredMaxConnectors = stationTemplate?.Connectors[0]
125 ? ChargingStationUtils.getTemplateMaxNumberOfConnectors(stationTemplate) - 1
126 : ChargingStationUtils.getTemplateMaxNumberOfConnectors(stationTemplate);
127 }
128 return configuredMaxConnectors;
129 }
130
131 public static checkConfiguredMaxConnectors(
132 configuredMaxConnectors: number,
133 templateFile: string,
134 logPrefix: string
135 ): void {
136 if (configuredMaxConnectors <= 0) {
137 logger.warn(
138 `${logPrefix} Charging station information from template ${templateFile} with ${configuredMaxConnectors} connectors`
139 );
140 }
141 }
142
17ac262c
JB
143 public static createBootNotificationRequest(
144 stationInfo: ChargingStationInfo
145 ): BootNotificationRequest {
146 return {
147 chargePointModel: stationInfo.chargePointModel,
148 chargePointVendor: stationInfo.chargePointVendor,
149 ...(!Utils.isUndefined(stationInfo.chargeBoxSerialNumber) && {
150 chargeBoxSerialNumber: stationInfo.chargeBoxSerialNumber,
151 }),
152 ...(!Utils.isUndefined(stationInfo.chargePointSerialNumber) && {
153 chargePointSerialNumber: stationInfo.chargePointSerialNumber,
154 }),
155 ...(!Utils.isUndefined(stationInfo.firmwareVersion) && {
156 firmwareVersion: stationInfo.firmwareVersion,
157 }),
158 ...(!Utils.isUndefined(stationInfo.iccid) && { iccid: stationInfo.iccid }),
159 ...(!Utils.isUndefined(stationInfo.imsi) && { imsi: stationInfo.imsi }),
160 ...(!Utils.isUndefined(stationInfo.meterSerialNumber) && {
161 meterSerialNumber: stationInfo.meterSerialNumber,
162 }),
163 ...(!Utils.isUndefined(stationInfo.meterType) && {
164 meterType: stationInfo.meterType,
165 }),
166 };
167 }
168
169 public static workerPoolInUse(): boolean {
170 return [WorkerProcessType.DYNAMIC_POOL, WorkerProcessType.STATIC_POOL].includes(
cf2a5d9b 171 Configuration.getWorker().processType
17ac262c
JB
172 );
173 }
174
175 public static workerDynamicPoolInUse(): boolean {
cf2a5d9b 176 return Configuration.getWorker().processType === WorkerProcessType.DYNAMIC_POOL;
17ac262c
JB
177 }
178
17ac262c
JB
179 public static warnDeprecatedTemplateKey(
180 template: ChargingStationTemplate,
181 key: string,
182 templateFile: string,
183 logPrefix: string,
184 logMsgToAppend = ''
185 ): void {
186 if (!Utils.isUndefined(template[key])) {
17ac262c
JB
187 logger.warn(
188 `${logPrefix} Deprecated template key '${key}' usage in file '${templateFile}'${
189 logMsgToAppend && '. ' + logMsgToAppend
190 }`
191 );
192 }
193 }
194
195 public static convertDeprecatedTemplateKey(
196 template: ChargingStationTemplate,
197 deprecatedKey: string,
198 key: string
199 ): void {
200 if (!Utils.isUndefined(template[deprecatedKey])) {
201 template[key] = template[deprecatedKey] as unknown;
202 delete template[deprecatedKey];
203 }
204 }
205
fa7bccf4
JB
206 public static stationTemplateToStationInfo(
207 stationTemplate: ChargingStationTemplate
208 ): ChargingStationInfo {
209 stationTemplate = Utils.cloneObject(stationTemplate);
210 delete stationTemplate.power;
211 delete stationTemplate.powerUnit;
212 delete stationTemplate.Configuration;
213 delete stationTemplate.AutomaticTransactionGenerator;
214 delete stationTemplate.chargeBoxSerialNumberPrefix;
215 delete stationTemplate.chargePointSerialNumberPrefix;
fec4d204 216 delete stationTemplate.meterSerialNumberPrefix;
51c83d6f 217 return stationTemplate as unknown as ChargingStationInfo;
fa7bccf4
JB
218 }
219
220 public static createStationInfoHash(stationInfo: ChargingStationInfo): void {
ccb1d6e9 221 delete stationInfo.infoHash;
7c72977b 222 stationInfo.infoHash = crypto
ccb1d6e9
JB
223 .createHash(Constants.DEFAULT_HASH_ALGORITHM)
224 .update(JSON.stringify(stationInfo))
225 .digest('hex');
17ac262c
JB
226 }
227
228 public static createSerialNumber(
fa7bccf4 229 stationTemplate: ChargingStationTemplate,
fec4d204 230 stationInfo: ChargingStationInfo = {} as ChargingStationInfo,
fa7bccf4
JB
231 params: {
232 randomSerialNumberUpperCase?: boolean;
233 randomSerialNumber?: boolean;
234 } = {
17ac262c
JB
235 randomSerialNumberUpperCase: true,
236 randomSerialNumber: true,
237 }
238 ): void {
239 params = params ?? {};
240 params.randomSerialNumberUpperCase = params?.randomSerialNumberUpperCase ?? true;
241 params.randomSerialNumber = params?.randomSerialNumber ?? true;
fa7bccf4
JB
242 const serialNumberSuffix = params?.randomSerialNumber
243 ? ChargingStationUtils.getRandomSerialNumberSuffix({
244 upperCase: params.randomSerialNumberUpperCase,
245 })
246 : '';
fec4d204
JB
247 stationInfo.chargePointSerialNumber =
248 stationTemplate?.chargePointSerialNumberPrefix &&
249 stationTemplate.chargePointSerialNumberPrefix + serialNumberSuffix;
250 stationInfo.chargeBoxSerialNumber =
251 stationTemplate?.chargeBoxSerialNumberPrefix &&
252 stationTemplate.chargeBoxSerialNumberPrefix + serialNumberSuffix;
253 stationInfo.meterSerialNumber =
254 stationTemplate?.meterSerialNumberPrefix &&
255 stationTemplate.meterSerialNumberPrefix + serialNumberSuffix;
256 }
257
258 public static propagateSerialNumber(
259 stationTemplate: ChargingStationTemplate,
260 stationInfoSrc: ChargingStationInfo,
261 stationInfoDst: ChargingStationInfo = {} as ChargingStationInfo
262 ) {
263 if (!stationInfoSrc || !stationTemplate) {
baf93dda
JB
264 throw new BaseError(
265 'Missing charging station template or existing configuration to propagate serial number'
266 );
fec4d204
JB
267 }
268 stationTemplate?.chargePointSerialNumberPrefix && stationInfoSrc?.chargePointSerialNumber
269 ? (stationInfoDst.chargePointSerialNumber = stationInfoSrc.chargePointSerialNumber)
270 : stationInfoDst?.chargePointSerialNumber && delete stationInfoDst.chargePointSerialNumber;
271 stationTemplate?.chargeBoxSerialNumberPrefix && stationInfoSrc?.chargeBoxSerialNumber
272 ? (stationInfoDst.chargeBoxSerialNumber = stationInfoSrc.chargeBoxSerialNumber)
273 : stationInfoDst?.chargeBoxSerialNumber && delete stationInfoDst.chargeBoxSerialNumber;
274 stationTemplate?.meterSerialNumberPrefix && stationInfoSrc?.meterSerialNumber
275 ? (stationInfoDst.meterSerialNumber = stationInfoSrc.meterSerialNumber)
276 : stationInfoDst?.meterSerialNumber && delete stationInfoDst.meterSerialNumber;
17ac262c
JB
277 }
278
279 public static getAmperageLimitationUnitDivider(stationInfo: ChargingStationInfo): number {
280 let unitDivider = 1;
281 switch (stationInfo.amperageLimitationUnit) {
282 case AmpereUnits.DECI_AMPERE:
283 unitDivider = 10;
284 break;
285 case AmpereUnits.CENTI_AMPERE:
286 unitDivider = 100;
287 break;
288 case AmpereUnits.MILLI_AMPERE:
289 unitDivider = 1000;
290 break;
291 }
292 return unitDivider;
293 }
294
295 /**
296 * Charging profiles should already be sorted by connectorId and stack level (highest stack level has priority)
297 *
298 * @param {ChargingProfile[]} chargingProfiles
299 * @param {string} logPrefix
300 * @returns {{ limit, matchingChargingProfile }}
301 */
302 public static getLimitFromChargingProfiles(
303 chargingProfiles: ChargingProfile[],
304 logPrefix: string
305 ): {
306 limit: number;
307 matchingChargingProfile: ChargingProfile;
308 } | null {
91a4f151 309 const debugLogMsg = `${logPrefix} ${moduleName}.getLimitFromChargingProfiles: Matching charging profile found for power limitation: %j`;
17ac262c
JB
310 for (const chargingProfile of chargingProfiles) {
311 // Set helpers
312 const currentMoment = moment();
313 const chargingSchedule = chargingProfile.chargingSchedule;
314 // Check type (recurring) and if it is already active
315 // Adjust the daily recurring schedule to today
316 if (
317 chargingProfile.chargingProfileKind === ChargingProfileKindType.RECURRING &&
318 chargingProfile.recurrencyKind === RecurrencyKindType.DAILY &&
319 currentMoment.isAfter(chargingSchedule.startSchedule)
320 ) {
321 const currentDate = new Date();
322 chargingSchedule.startSchedule = new Date(chargingSchedule.startSchedule);
323 chargingSchedule.startSchedule.setFullYear(
324 currentDate.getFullYear(),
325 currentDate.getMonth(),
326 currentDate.getDate()
327 );
328 // Check if the start of the schedule is yesterday
329 if (moment(chargingSchedule.startSchedule).isAfter(currentMoment)) {
330 chargingSchedule.startSchedule.setDate(currentDate.getDate() - 1);
331 }
332 } else if (moment(chargingSchedule.startSchedule).isAfter(currentMoment)) {
333 return null;
334 }
335 // Check if the charging profile is active
336 if (
337 moment(chargingSchedule.startSchedule)
338 .add(chargingSchedule.duration, 's')
339 .isAfter(currentMoment)
340 ) {
341 let lastButOneSchedule: ChargingSchedulePeriod;
342 // Search the right schedule period
343 for (const schedulePeriod of chargingSchedule.chargingSchedulePeriod) {
344 // Handling of only one period
345 if (
346 chargingSchedule.chargingSchedulePeriod.length === 1 &&
347 schedulePeriod.startPeriod === 0
348 ) {
349 const result = {
350 limit: schedulePeriod.limit,
351 matchingChargingProfile: chargingProfile,
352 };
91a4f151 353 logger.debug(debugLogMsg, result);
17ac262c
JB
354 return result;
355 }
356 // Find the right schedule period
357 if (
358 moment(chargingSchedule.startSchedule)
359 .add(schedulePeriod.startPeriod, 's')
360 .isAfter(currentMoment)
361 ) {
362 // Found the schedule: last but one is the correct one
363 const result = {
364 limit: lastButOneSchedule.limit,
365 matchingChargingProfile: chargingProfile,
366 };
91a4f151 367 logger.debug(debugLogMsg, result);
17ac262c
JB
368 return result;
369 }
370 // Keep it
371 lastButOneSchedule = schedulePeriod;
372 // Handle the last schedule period
373 if (
374 schedulePeriod.startPeriod ===
375 chargingSchedule.chargingSchedulePeriod[
376 chargingSchedule.chargingSchedulePeriod.length - 1
377 ].startPeriod
378 ) {
379 const result = {
380 limit: lastButOneSchedule.limit,
381 matchingChargingProfile: chargingProfile,
382 };
91a4f151 383 logger.debug(debugLogMsg, result);
17ac262c
JB
384 return result;
385 }
386 }
387 }
388 }
389 return null;
390 }
391
492cf6ab
JB
392 public static getDefaultVoltageOut(
393 currentType: CurrentType,
394 templateFile: string,
395 logPrefix: string
396 ): Voltage {
fc040c43 397 const errMsg = `Unknown ${currentType} currentOutType in template file ${templateFile}, cannot define default voltage out`;
492cf6ab
JB
398 let defaultVoltageOut: number;
399 switch (currentType) {
400 case CurrentType.AC:
401 defaultVoltageOut = Voltage.VOLTAGE_230;
402 break;
403 case CurrentType.DC:
404 defaultVoltageOut = Voltage.VOLTAGE_400;
405 break;
406 default:
fc040c43 407 logger.error(`${logPrefix} ${errMsg}`);
6c8f5d90 408 throw new BaseError(errMsg);
492cf6ab
JB
409 }
410 return defaultVoltageOut;
411 }
412
413 public static getSampledValueTemplate(
414 chargingStation: ChargingStation,
415 connectorId: number,
416 measurand: MeterValueMeasurand = MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER,
417 phase?: MeterValuePhase
418 ): SampledValueTemplate | undefined {
419 const onPhaseStr = phase ? `on phase ${phase} ` : '';
9383b2b1 420 if (Constants.SUPPORTED_MEASURANDS.includes(measurand) === false) {
492cf6ab
JB
421 logger.warn(
422 `${chargingStation.logPrefix()} Trying to get unsupported MeterValues measurand '${measurand}' ${onPhaseStr}in template on connectorId ${connectorId}`
423 );
424 return;
425 }
426 if (
427 measurand !== MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER &&
428 !ChargingStationConfigurationUtils.getConfigurationKey(
429 chargingStation,
430 StandardParametersKey.MeterValuesSampledData
431 )?.value.includes(measurand)
432 ) {
433 logger.debug(
434 `${chargingStation.logPrefix()} Trying to get MeterValues measurand '${measurand}' ${onPhaseStr}in template on connectorId ${connectorId} not found in '${
435 StandardParametersKey.MeterValuesSampledData
436 }' OCPP parameter`
437 );
438 return;
439 }
440 const sampledValueTemplates: SampledValueTemplate[] =
441 chargingStation.getConnectorStatus(connectorId).MeterValues;
442 for (
443 let index = 0;
444 !Utils.isEmptyArray(sampledValueTemplates) && index < sampledValueTemplates.length;
445 index++
446 ) {
447 if (
9383b2b1 448 Constants.SUPPORTED_MEASURANDS.includes(
492cf6ab
JB
449 sampledValueTemplates[index]?.measurand ??
450 MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
9383b2b1 451 ) === false
492cf6ab
JB
452 ) {
453 logger.warn(
454 `${chargingStation.logPrefix()} Unsupported MeterValues measurand '${measurand}' ${onPhaseStr}in template on connectorId ${connectorId}`
455 );
456 } else if (
457 phase &&
458 sampledValueTemplates[index]?.phase === phase &&
459 sampledValueTemplates[index]?.measurand === measurand &&
460 ChargingStationConfigurationUtils.getConfigurationKey(
461 chargingStation,
462 StandardParametersKey.MeterValuesSampledData
9383b2b1 463 )?.value.includes(measurand) === true
492cf6ab
JB
464 ) {
465 return sampledValueTemplates[index];
466 } else if (
467 !phase &&
468 !sampledValueTemplates[index].phase &&
469 sampledValueTemplates[index]?.measurand === measurand &&
470 ChargingStationConfigurationUtils.getConfigurationKey(
471 chargingStation,
472 StandardParametersKey.MeterValuesSampledData
9383b2b1 473 )?.value.includes(measurand) === true
492cf6ab
JB
474 ) {
475 return sampledValueTemplates[index];
476 } else if (
477 measurand === MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER &&
478 (!sampledValueTemplates[index].measurand ||
479 sampledValueTemplates[index].measurand === measurand)
480 ) {
481 return sampledValueTemplates[index];
482 }
483 }
484 if (measurand === MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER) {
fc040c43
JB
485 const errorMsg = `Missing MeterValues for default measurand '${measurand}' in template on connectorId ${connectorId}`;
486 logger.error(`${chargingStation.logPrefix()} ${errorMsg}`);
6c8f5d90 487 throw new BaseError(errorMsg);
492cf6ab
JB
488 }
489 logger.debug(
490 `${chargingStation.logPrefix()} No MeterValues for measurand '${measurand}' ${onPhaseStr}in template on connectorId ${connectorId}`
491 );
492 }
493
fa7bccf4
JB
494 public static getAuthorizationFile(stationInfo: ChargingStationInfo): string | undefined {
495 return (
496 stationInfo.authorizationFile &&
497 path.join(
0d8140bd 498 path.resolve(path.dirname(fileURLToPath(import.meta.url)), '../'),
fa7bccf4
JB
499 'assets',
500 path.basename(stationInfo.authorizationFile)
501 )
502 );
503 }
504
ada189a8
JB
505 public static isRequestCommandSupported(
506 command: RequestCommand,
7645760b 507 chargingStation: ChargingStation
65554cc3 508 ): boolean {
ada189a8 509 const isRequestCommand = Object.values(RequestCommand).includes(command);
9383b2b1
JB
510 if (
511 isRequestCommand === true &&
512 !chargingStation.stationInfo?.commandsSupport?.outgoingCommands
513 ) {
ada189a8 514 return true;
9383b2b1
JB
515 } else if (
516 isRequestCommand === true &&
517 chargingStation.stationInfo?.commandsSupport?.outgoingCommands
518 ) {
ada189a8
JB
519 return chargingStation.stationInfo?.commandsSupport?.outgoingCommands[command] ?? false;
520 }
521 logger.error(`${chargingStation.logPrefix()} Unknown outgoing OCPP command '${command}'`);
522 return false;
523 }
524
525 public static isIncomingRequestCommandSupported(
526 command: IncomingRequestCommand,
527 chargingStation: ChargingStation
528 ): boolean {
529 const isIncomingRequestCommand = Object.values(IncomingRequestCommand).includes(command);
65554cc3 530 if (
9383b2b1 531 isIncomingRequestCommand === true &&
7645760b 532 !chargingStation.stationInfo?.commandsSupport?.incomingCommands
65554cc3
JB
533 ) {
534 return true;
7645760b 535 } else if (
9383b2b1 536 isIncomingRequestCommand === true &&
7645760b
JB
537 chargingStation.stationInfo?.commandsSupport?.incomingCommands
538 ) {
ada189a8 539 return chargingStation.stationInfo?.commandsSupport?.incomingCommands[command] ?? false;
65554cc3 540 }
ada189a8 541 logger.error(`${chargingStation.logPrefix()} Unknown incoming OCPP command '${command}'`);
7645760b 542 return false;
65554cc3
JB
543 }
544
17ac262c
JB
545 private static getRandomSerialNumberSuffix(params?: {
546 randomBytesLength?: number;
547 upperCase?: boolean;
548 }): string {
549 const randomSerialNumberSuffix = crypto
550 .randomBytes(params?.randomBytesLength ?? 16)
551 .toString('hex');
552 if (params?.upperCase) {
553 return randomSerialNumberSuffix.toUpperCase();
554 }
555 return randomSerialNumberSuffix;
556 }
557}