perf: cache only JSON payload validation functions
[e-mobility-charging-stations-simulator.git] / src / charging-station / ocpp / 2.0 / OCPP20ResponseService.ts
CommitLineData
a19b897d 1// Partial Copyright Jerome Benoit. 2021-2024. All Rights Reserved.
953d6b02 2
24d15716 3import type { ValidateFunction } from 'ajv'
953d6b02 4
66a7748d
JB
5import { OCPP20ServiceUtils } from './OCPP20ServiceUtils.js'
6import { type ChargingStation, addConfigurationKey } from '../../../charging-station/index.js'
7import { OCPPError } from '../../../exception/index.js'
b3fc3ff5 8import {
268a74bb 9 ErrorType,
268a74bb 10 type JsonType,
268a74bb
JB
11 type OCPP20BootNotificationResponse,
12 type OCPP20ClearCacheResponse,
13 type OCPP20HeartbeatResponse,
b3fc3ff5 14 OCPP20IncomingRequestCommand,
857d8dd9 15 OCPP20OptionalVariableName,
b3fc3ff5 16 OCPP20RequestCommand,
268a74bb
JB
17 type OCPP20StatusNotificationResponse,
18 OCPPVersion,
19 RegistrationStatusEnumType,
66a7748d
JB
20 type ResponseHandler
21} from '../../../types/index.js'
22import { logger } from '../../../utils/index.js'
23import { OCPPResponseService } from '../OCPPResponseService.js'
953d6b02 24
66a7748d 25const moduleName = 'OCPP20ResponseService'
953d6b02 26
268a74bb 27export class OCPP20ResponseService extends OCPPResponseService {
24d15716 28 public jsonSchemasIncomingRequestResponseValidateFunction: Map<
66a7748d 29 OCPP20IncomingRequestCommand,
24d15716 30 ValidateFunction<JsonType>
66a7748d 31 >
b3fc3ff5 32
24d15716 33 protected jsonSchemasValidateFunction: Map<OCPP20RequestCommand, ValidateFunction<JsonType>>
66a7748d 34 private readonly responseHandlers: Map<OCPP20RequestCommand, ResponseHandler>
953d6b02 35
66a7748d 36 public constructor () {
5199f9fd
JB
37 // if (new.target.name === moduleName) {
38 // throw new TypeError(`Cannot construct ${new.target.name} instances directly`)
b768993d 39 // }
66a7748d 40 super(OCPPVersion.VERSION_20)
d270cc87 41 this.responseHandlers = new Map<OCPP20RequestCommand, ResponseHandler>([
a37fc6dc
JB
42 [
43 OCPP20RequestCommand.BOOT_NOTIFICATION,
66a7748d 44 this.handleResponseBootNotification.bind(this) as ResponseHandler
a37fc6dc 45 ],
65b5177e
JB
46 [OCPP20RequestCommand.HEARTBEAT, this.emptyResponseHandler],
47 [OCPP20RequestCommand.STATUS_NOTIFICATION, this.emptyResponseHandler]
66a7748d 48 ])
24d15716 49 this.jsonSchemasValidateFunction = new Map<OCPP20RequestCommand, ValidateFunction<JsonType>>([
d270cc87
JB
50 [
51 OCPP20RequestCommand.BOOT_NOTIFICATION,
24d15716
JB
52 this.ajv
53 .compile(
54 OCPP20ServiceUtils.parseJsonSchemaFile<OCPP20BootNotificationResponse>(
55 'assets/json-schemas/ocpp/2.0/BootNotificationResponse.json',
56 moduleName,
57 'constructor'
58 )
59 )
60 .bind(this)
d270cc87 61 ],
81533a20
JB
62 [
63 OCPP20RequestCommand.HEARTBEAT,
24d15716
JB
64 this.ajv
65 .compile(
66 OCPP20ServiceUtils.parseJsonSchemaFile<OCPP20HeartbeatResponse>(
67 'assets/json-schemas/ocpp/2.0/HeartbeatResponse.json',
68 moduleName,
69 'constructor'
70 )
71 )
72 .bind(this)
81533a20 73 ],
6e939d9e
JB
74 [
75 OCPP20RequestCommand.STATUS_NOTIFICATION,
24d15716
JB
76 this.ajv
77 .compile(
78 OCPP20ServiceUtils.parseJsonSchemaFile<OCPP20StatusNotificationResponse>(
79 'assets/json-schemas/ocpp/2.0/StatusNotificationResponse.json',
80 moduleName,
81 'constructor'
82 )
83 )
84 .bind(this)
66a7748d
JB
85 ]
86 ])
24d15716
JB
87 this.jsonSchemasIncomingRequestResponseValidateFunction = new Map<
88 OCPP20IncomingRequestCommand,
89 ValidateFunction<JsonType>
90 >([
02887891
JB
91 [
92 OCPP20IncomingRequestCommand.CLEAR_CACHE,
24d15716
JB
93 this.ajv
94 .compile(
95 OCPP20ServiceUtils.parseJsonSchemaFile<OCPP20ClearCacheResponse>(
96 'assets/json-schemas/ocpp/2.0/ClearCacheResponse.json',
97 moduleName,
98 'constructor'
99 )
100 )
101 .bind(this)
66a7748d
JB
102 ]
103 ])
ba9a56a6 104 this.validatePayload = this.validatePayload.bind(this)
953d6b02
JB
105 }
106
9429aa42 107 public async responseHandler<ReqType extends JsonType, ResType extends JsonType>(
953d6b02
JB
108 chargingStation: ChargingStation,
109 commandName: OCPP20RequestCommand,
9429aa42 110 payload: ResType,
66a7748d 111 requestPayload: ReqType
953d6b02 112 ): Promise<void> {
66a7748d 113 if (chargingStation.isRegistered() || commandName === OCPP20RequestCommand.BOOT_NOTIFICATION) {
953d6b02 114 if (
66a7748d
JB
115 this.responseHandlers.has(commandName) &&
116 OCPP20ServiceUtils.isRequestCommandSupported(chargingStation, commandName)
953d6b02
JB
117 ) {
118 try {
66a7748d
JB
119 this.validatePayload(chargingStation, commandName, payload)
120 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
121 await this.responseHandlers.get(commandName)!(chargingStation, payload, requestPayload)
953d6b02
JB
122 } catch (error) {
123 logger.error(
124 `${chargingStation.logPrefix()} ${moduleName}.responseHandler: Handle response error:`,
66a7748d
JB
125 error
126 )
127 throw error
953d6b02
JB
128 }
129 } else {
130 // Throw exception
131 throw new OCPPError(
132 ErrorType.NOT_IMPLEMENTED,
133 `${commandName} is not implemented to handle response PDU ${JSON.stringify(
134 payload,
4ed03b6e 135 undefined,
66a7748d 136 2
953d6b02
JB
137 )}`,
138 commandName,
66a7748d
JB
139 payload
140 )
953d6b02
JB
141 }
142 } else {
143 throw new OCPPError(
144 ErrorType.SECURITY_ERROR,
145 `${commandName} cannot be issued to handle response PDU ${JSON.stringify(
146 payload,
4ed03b6e 147 undefined,
66a7748d 148 2
439fc71b 149 )} while the charging station is not registered on the central server.`,
953d6b02 150 commandName,
66a7748d
JB
151 payload
152 )
953d6b02
JB
153 }
154 }
155
66a7748d 156 private validatePayload (
953d6b02
JB
157 chargingStation: ChargingStation,
158 commandName: OCPP20RequestCommand,
66a7748d 159 payload: JsonType
953d6b02 160 ): boolean {
24d15716
JB
161 if (this.jsonSchemasValidateFunction.has(commandName)) {
162 return this.validateResponsePayload(chargingStation, commandName, payload)
953d6b02
JB
163 }
164 logger.warn(
24d15716 165 `${chargingStation.logPrefix()} ${moduleName}.validatePayload: No JSON schema validation function found for command '${commandName}' PDU validation`
66a7748d
JB
166 )
167 return false
953d6b02 168 }
d270cc87 169
66a7748d 170 private handleResponseBootNotification (
d270cc87 171 chargingStation: ChargingStation,
66a7748d 172 payload: OCPP20BootNotificationResponse
d270cc87
JB
173 ): void {
174 if (payload.status === RegistrationStatusEnumType.ACCEPTED) {
f2d5e3d9 175 addConfigurationKey(
268a74bb 176 chargingStation,
857d8dd9 177 OCPP20OptionalVariableName.HeartbeatInterval,
268a74bb 178 payload.interval.toString(),
abe9e9dd 179 {},
66a7748d
JB
180 { overwrite: true, save: true }
181 )
182 OCPP20ServiceUtils.startHeartbeatInterval(chargingStation, payload.interval)
d270cc87
JB
183 }
184 if (Object.values(RegistrationStatusEnumType).includes(payload.status)) {
185 const logMsg = `${chargingStation.logPrefix()} Charging station in '${
186 payload.status
66a7748d 187 }' state on the central server`
d270cc87
JB
188 payload.status === RegistrationStatusEnumType.REJECTED
189 ? logger.warn(logMsg)
66a7748d 190 : logger.info(logMsg)
d270cc87
JB
191 } else {
192 logger.error(
44eb6026 193 `${chargingStation.logPrefix()} Charging station boot notification response received: %j with undefined registration status`,
66a7748d
JB
194 payload
195 )
d270cc87
JB
196 }
197 }
953d6b02 198}