Factor out in helpers PDU payload validation
[e-mobility-charging-stations-simulator.git] / src / charging-station / ocpp / 1.6 / OCPP16ResponseService.ts
index fd972b261b6cc6e7e1334462534f8c0a19161390..984cb1b34c09ab53bfe90d518d9c2e86b8c55b3a 100644 (file)
@@ -1,7 +1,13 @@
 // Partial Copyright Jerome Benoit. 2021. All Rights Reserved.
 
+import fs from 'fs';
+import path from 'path';
+import { fileURLToPath } from 'url';
+
+import { JSONSchemaType } from 'ajv';
+
 import OCPPError from '../../../exception/OCPPError';
-import { JsonType } from '../../../types/JsonType';
+import { JsonObject, JsonType } from '../../../types/JsonType';
 import { OCPP16ChargePointErrorCode } from '../../../types/ocpp/1.6/ChargePointErrorCode';
 import { OCPP16ChargePointStatus } from '../../../types/ocpp/1.6/ChargePointStatus';
 import { OCPP16StandardParametersKey } from '../../../types/ocpp/1.6/Configuration';
@@ -15,7 +21,9 @@ import {
   OCPP16StatusNotificationRequest,
 } from '../../../types/ocpp/1.6/Requests';
 import {
+  DiagnosticsStatusNotificationResponse,
   OCPP16BootNotificationResponse,
+  OCPP16HeartbeatResponse,
   OCPP16RegistrationStatus,
   OCPP16StatusNotificationResponse,
 } from '../../../types/ocpp/1.6/Responses';
@@ -34,6 +42,7 @@ import logger from '../../../utils/Logger';
 import Utils from '../../../utils/Utils';
 import type ChargingStation from '../../ChargingStation';
 import { ChargingStationConfigurationUtils } from '../../ChargingStationConfigurationUtils';
+import { ChargingStationUtils } from '../../ChargingStationUtils';
 import OCPPResponseService from '../OCPPResponseService';
 import { OCPP16ServiceUtils } from './OCPP16ServiceUtils';
 
@@ -41,6 +50,7 @@ const moduleName = 'OCPP16ResponseService';
 
 export default class OCPP16ResponseService extends OCPPResponseService {
   private responseHandlers: Map<OCPP16RequestCommand, ResponseHandler>;
+  private jsonSchemas: Map<OCPP16RequestCommand, JSONSchemaType<JsonObject>>;
 
   public constructor() {
     if (new.target?.name === moduleName) {
@@ -49,12 +59,111 @@ export default class OCPP16ResponseService extends OCPPResponseService {
     super();
     this.responseHandlers = new Map<OCPP16RequestCommand, ResponseHandler>([
       [OCPP16RequestCommand.BOOT_NOTIFICATION, this.handleResponseBootNotification.bind(this)],
-      [OCPP16RequestCommand.HEARTBEAT, this.handleResponseHeartbeat.bind(this)],
+      [OCPP16RequestCommand.HEARTBEAT, this.emptyResponseHandler.bind(this)],
       [OCPP16RequestCommand.AUTHORIZE, this.handleResponseAuthorize.bind(this)],
       [OCPP16RequestCommand.START_TRANSACTION, this.handleResponseStartTransaction.bind(this)],
       [OCPP16RequestCommand.STOP_TRANSACTION, this.handleResponseStopTransaction.bind(this)],
-      [OCPP16RequestCommand.STATUS_NOTIFICATION, this.handleResponseStatusNotification.bind(this)],
-      [OCPP16RequestCommand.METER_VALUES, this.handleResponseMeterValues.bind(this)],
+      [OCPP16RequestCommand.STATUS_NOTIFICATION, this.emptyResponseHandler.bind(this)],
+      [OCPP16RequestCommand.METER_VALUES, this.emptyResponseHandler.bind(this)],
+      [OCPP16RequestCommand.DIAGNOSTICS_STATUS_NOTIFICATION, this.emptyResponseHandler.bind(this)],
+    ]);
+    this.jsonSchemas = new Map<OCPP16RequestCommand, JSONSchemaType<JsonObject>>([
+      [
+        OCPP16RequestCommand.BOOT_NOTIFICATION,
+        JSON.parse(
+          fs.readFileSync(
+            path.resolve(
+              path.dirname(fileURLToPath(import.meta.url)),
+              '../../../assets/json-schemas/ocpp/1.6/BootNotificationResponse.json'
+            ),
+            'utf8'
+          )
+        ) as JSONSchemaType<OCPP16BootNotificationResponse>,
+      ],
+      [
+        OCPP16RequestCommand.HEARTBEAT,
+        JSON.parse(
+          fs.readFileSync(
+            path.resolve(
+              path.dirname(fileURLToPath(import.meta.url)),
+              '../../../assets/json-schemas/ocpp/1.6/HeartbeatResponse.json'
+            ),
+            'utf8'
+          )
+        ) as JSONSchemaType<OCPP16HeartbeatResponse>,
+      ],
+      [
+        OCPP16RequestCommand.AUTHORIZE,
+        JSON.parse(
+          fs.readFileSync(
+            path.resolve(
+              path.dirname(fileURLToPath(import.meta.url)),
+              '../../../assets/json-schemas/ocpp/1.6/AuthorizeResponse.json'
+            ),
+            'utf8'
+          )
+        ) as JSONSchemaType<OCPP16AuthorizeResponse>,
+      ],
+      [
+        OCPP16RequestCommand.START_TRANSACTION,
+        JSON.parse(
+          fs.readFileSync(
+            path.resolve(
+              path.dirname(fileURLToPath(import.meta.url)),
+              '../../../assets/json-schemas/ocpp/1.6/StartTransactionResponse.json'
+            ),
+            'utf8'
+          )
+        ) as JSONSchemaType<OCPP16StartTransactionResponse>,
+      ],
+      [
+        OCPP16RequestCommand.STOP_TRANSACTION,
+        JSON.parse(
+          fs.readFileSync(
+            path.resolve(
+              path.dirname(fileURLToPath(import.meta.url)),
+              '../../../assets/json-schemas/ocpp/1.6/StopTransactionResponse.json'
+            ),
+            'utf8'
+          )
+        ) as JSONSchemaType<OCPP16StopTransactionResponse>,
+      ],
+      [
+        OCPP16RequestCommand.STATUS_NOTIFICATION,
+        JSON.parse(
+          fs.readFileSync(
+            path.resolve(
+              path.dirname(fileURLToPath(import.meta.url)),
+              '../../../assets/json-schemas/ocpp/1.6/StatusNotificationResponse.json'
+            ),
+            'utf8'
+          )
+        ) as JSONSchemaType<OCPP16StatusNotificationResponse>,
+      ],
+      [
+        OCPP16RequestCommand.METER_VALUES,
+        JSON.parse(
+          fs.readFileSync(
+            path.resolve(
+              path.dirname(fileURLToPath(import.meta.url)),
+              '../../../assets/json-schemas/ocpp/1.6/MeterValuesResponse.json'
+            ),
+            'utf8'
+          )
+        ) as JSONSchemaType<OCPP16MeterValuesResponse>,
+      ],
+      [
+        OCPP16RequestCommand.DIAGNOSTICS_STATUS_NOTIFICATION,
+        JSON.parse(
+          fs.readFileSync(
+            path.resolve(
+              path.dirname(fileURLToPath(import.meta.url)),
+              '../../../assets/json-schemas/ocpp/1.6/DiagnosticsStatusNotificationResponse.json'
+            ),
+            'utf8'
+          )
+        ) as JSONSchemaType<DiagnosticsStatusNotificationResponse>,
+      ],
     ]);
   }
 
@@ -65,8 +174,12 @@ export default class OCPP16ResponseService extends OCPPResponseService {
     requestPayload: JsonType
   ): Promise<void> {
     if (chargingStation.isRegistered() || commandName === OCPP16RequestCommand.BOOT_NOTIFICATION) {
-      if (this.responseHandlers.has(commandName)) {
+      if (
+        this.responseHandlers.has(commandName) &&
+        ChargingStationUtils.isRequestCommandSupported(commandName, chargingStation)
+      ) {
         try {
+          this.validatePayload(chargingStation, commandName, payload);
           await this.responseHandlers.get(commandName)(chargingStation, payload, requestPayload);
         } catch (error) {
           logger.error(chargingStation.logPrefix() + ' Handle request response error: %j', error);
@@ -76,25 +189,46 @@ export default class OCPP16ResponseService extends OCPPResponseService {
         // Throw exception
         throw new OCPPError(
           ErrorType.NOT_IMPLEMENTED,
-          `${commandName} is not implemented to handle request response payload ${JSON.stringify(
+          `${commandName} is not implemented to handle request response PDU ${JSON.stringify(
             payload,
             null,
             2
           )}`,
-          commandName
+          commandName,
+          payload
         );
       }
     } else {
       throw new OCPPError(
         ErrorType.SECURITY_ERROR,
-        `${commandName} cannot be issued to handle request response payload ${JSON.stringify(
+        `${commandName} cannot be issued to handle request response PDU ${JSON.stringify(
           payload,
           null,
           2
         )} while the charging station is not registered on the central server. `,
-        commandName
+        commandName,
+        payload
+      );
+    }
+  }
+
+  private validatePayload(
+    chargingStation: ChargingStation,
+    commandName: OCPP16RequestCommand,
+    payload: JsonType
+  ): boolean {
+    if (this.jsonSchemas.has(commandName)) {
+      return this.validateResponsePayload(
+        chargingStation,
+        commandName,
+        this.jsonSchemas.get(commandName),
+        payload
       );
     }
+    logger.warn(
+      `${chargingStation.logPrefix()} ${moduleName}.responseHandler: No JSON schema found for command ${commandName} PDU validation`
+    );
+    return false;
   }
 
   private handleResponseBootNotification(
@@ -136,9 +270,6 @@ export default class OCPP16ResponseService extends OCPPResponseService {
     }
   }
 
-  // eslint-disable-next-line @typescript-eslint/no-empty-function
-  private handleResponseHeartbeat(): void {}
-
   private handleResponseAuthorize(
     chargingStation: ChargingStation,
     payload: OCPP16AuthorizeResponse,
@@ -311,7 +442,7 @@ export default class OCPP16ResponseService extends OCPPResponseService {
         >(chargingStation, OCPP16RequestCommand.METER_VALUES, {
           connectorId,
           transactionId: payload.transactionId,
-          meterValue: chargingStation.getConnectorStatus(connectorId).transactionBeginMeterValue,
+          meterValue: [chargingStation.getConnectorStatus(connectorId).transactionBeginMeterValue],
         }));
       await chargingStation.ocppRequestService.requestHandler<
         OCPP16StatusNotificationRequest,
@@ -407,11 +538,13 @@ export default class OCPP16ResponseService extends OCPPResponseService {
         >(chargingStation, OCPP16RequestCommand.METER_VALUES, {
           connectorId: transactionConnectorId,
           transactionId: requestPayload.transactionId,
-          meterValue: OCPP16ServiceUtils.buildTransactionEndMeterValue(
-            chargingStation,
-            transactionConnectorId,
-            requestPayload.meterStop
-          ),
+          meterValue: [
+            OCPP16ServiceUtils.buildTransactionEndMeterValue(
+              chargingStation,
+              transactionConnectorId,
+              requestPayload.meterStop
+            ),
+          ],
         }));
       if (
         !chargingStation.isChargingStationAvailable() ||
@@ -463,10 +596,4 @@ export default class OCPP16ResponseService extends OCPPResponseService {
       );
     }
   }
-
-  // eslint-disable-next-line @typescript-eslint/no-empty-function
-  private handleResponseStatusNotification(): void {}
-
-  // eslint-disable-next-line @typescript-eslint/no-empty-function
-  private handleResponseMeterValues(): void {}
 }