Fixes to OCPP commands PDU validation code:
[e-mobility-charging-stations-simulator.git] / src / charging-station / ocpp / 2.0 / OCPP20ResponseService.ts
1 // Partial Copyright Jerome Benoit. 2021-2023. All Rights Reserved.
2
3 import fs from 'fs';
4 import path from 'path';
5 import { fileURLToPath } from 'url';
6
7 import type { JSONSchemaType } from 'ajv';
8
9 import OCPPError from '../../../exception/OCPPError';
10 import type { JsonObject, JsonType } from '../../../types/JsonType';
11 import {
12 OCPP20IncomingRequestCommand,
13 OCPP20RequestCommand,
14 } from '../../../types/ocpp/2.0/Requests';
15 import type { OCPP20BootNotificationResponse } from '../../../types/ocpp/2.0/Responses';
16 import { ErrorType } from '../../../types/ocpp/ErrorType';
17 import { OCPPVersion } from '../../../types/ocpp/OCPPVersion';
18 import { RegistrationStatusEnumType, ResponseHandler } from '../../../types/ocpp/Responses';
19 import logger from '../../../utils/Logger';
20 import type ChargingStation from '../../ChargingStation';
21 import OCPPResponseService from '../OCPPResponseService';
22 import { OCPP20ServiceUtils } from './OCPP20ServiceUtils';
23
24 const moduleName = 'OCPP20ResponseService';
25
26 export default class OCPP20ResponseService extends OCPPResponseService {
27 public jsonIncomingRequestResponseSchemas: Map<
28 OCPP20IncomingRequestCommand,
29 JSONSchemaType<JsonObject>
30 >;
31
32 private responseHandlers: Map<OCPP20RequestCommand, ResponseHandler>;
33 private jsonSchemas: Map<OCPP20RequestCommand, JSONSchemaType<JsonObject>>;
34
35 public constructor() {
36 if (new.target?.name === moduleName) {
37 throw new TypeError(`Cannot construct ${new.target?.name} instances directly`);
38 }
39 super(OCPPVersion.VERSION_20);
40 this.responseHandlers = new Map<OCPP20RequestCommand, ResponseHandler>([
41 [OCPP20RequestCommand.BOOT_NOTIFICATION, this.handleResponseBootNotification.bind(this)],
42 ]);
43 this.jsonSchemas = new Map<OCPP20RequestCommand, JSONSchemaType<JsonObject>>([
44 [
45 OCPP20RequestCommand.BOOT_NOTIFICATION,
46 JSON.parse(
47 fs.readFileSync(
48 path.resolve(
49 path.dirname(fileURLToPath(import.meta.url)),
50 '../../../assets/json-schemas/ocpp/2.0/BootNotificationResponse.json'
51 ),
52 'utf8'
53 )
54 ) as JSONSchemaType<OCPP20BootNotificationResponse>,
55 ],
56 ]);
57 this.jsonIncomingRequestResponseSchemas = new Map();
58 this.validatePayload.bind(this);
59 }
60
61 public async responseHandler(
62 chargingStation: ChargingStation,
63 commandName: OCPP20RequestCommand,
64 payload: JsonType,
65 requestPayload: JsonType
66 ): Promise<void> {
67 if (
68 chargingStation.isRegistered() === true ||
69 commandName === OCPP20RequestCommand.BOOT_NOTIFICATION
70 ) {
71 if (
72 this.responseHandlers.has(commandName) === true &&
73 OCPP20ServiceUtils.isRequestCommandSupported(chargingStation, commandName) === true
74 ) {
75 try {
76 this.validatePayload(chargingStation, commandName, payload);
77 await this.responseHandlers.get(commandName)(chargingStation, payload, requestPayload);
78 } catch (error) {
79 logger.error(
80 `${chargingStation.logPrefix()} ${moduleName}.responseHandler: Handle response error:`,
81 error
82 );
83 throw error;
84 }
85 } else {
86 // Throw exception
87 throw new OCPPError(
88 ErrorType.NOT_IMPLEMENTED,
89 `${commandName} is not implemented to handle response PDU ${JSON.stringify(
90 payload,
91 null,
92 2
93 )}`,
94 commandName,
95 payload
96 );
97 }
98 } else {
99 throw new OCPPError(
100 ErrorType.SECURITY_ERROR,
101 `${commandName} cannot be issued to handle response PDU ${JSON.stringify(
102 payload,
103 null,
104 2
105 )} while the charging station is not registered on the central server.`,
106 commandName,
107 payload
108 );
109 }
110 }
111
112 private validatePayload(
113 chargingStation: ChargingStation,
114 commandName: OCPP20RequestCommand,
115 payload: JsonType
116 ): boolean {
117 if (this.jsonSchemas.has(commandName) === true) {
118 return this.validateResponsePayload(
119 chargingStation,
120 commandName,
121 this.jsonSchemas.get(commandName),
122 payload
123 );
124 }
125 logger.warn(
126 `${chargingStation.logPrefix()} ${moduleName}.validatePayload: No JSON schema found for command '${commandName}' PDU validation`
127 );
128 return false;
129 }
130
131 private handleResponseBootNotification(
132 chargingStation: ChargingStation,
133 payload: OCPP20BootNotificationResponse
134 ): void {
135 if (payload.status === RegistrationStatusEnumType.ACCEPTED) {
136 // ChargingStationConfigurationUtils.addConfigurationKey(
137 // chargingStation,
138 // OCPP16StandardParametersKey.HeartbeatInterval,
139 // payload.interval.toString(),
140 // {},
141 // { overwrite: true, save: true }
142 // );
143 // ChargingStationConfigurationUtils.addConfigurationKey(
144 // chargingStation,
145 // OCPP16StandardParametersKey.HeartBeatInterval,
146 // payload.interval.toString(),
147 // { visible: false },
148 // { overwrite: true, save: true }
149 // );
150 chargingStation.heartbeatSetInterval
151 ? chargingStation.restartHeartbeat()
152 : chargingStation.startHeartbeat();
153 }
154 if (Object.values(RegistrationStatusEnumType).includes(payload.status)) {
155 const logMsg = `${chargingStation.logPrefix()} Charging station in '${
156 payload.status
157 }' state on the central server`;
158 payload.status === RegistrationStatusEnumType.REJECTED
159 ? logger.warn(logMsg)
160 : logger.info(logMsg);
161 } else {
162 logger.error(
163 chargingStation.logPrefix() +
164 ' Charging station boot notification response received: %j with undefined registration status',
165 payload
166 );
167 }
168 }
169 }