Requests PDU validation (#139)
authorJérôme Benoit <jerome.benoit@sap.com>
Thu, 18 Aug 2022 19:28:03 +0000 (21:28 +0200)
committerGitHub <noreply@github.com>
Thu, 18 Aug 2022 19:28:03 +0000 (21:28 +0200)
14 files changed:
src/assets/json-schemas/ocpp/1.6/Authorize.json [new file with mode: 0644]
src/assets/json-schemas/ocpp/1.6/BootNotification.json [new file with mode: 0644]
src/assets/json-schemas/ocpp/1.6/DiagnosticsStatusNotification.json [new file with mode: 0644]
src/assets/json-schemas/ocpp/1.6/DiagnosticsStatusNotificationResponse.json [new file with mode: 0644]
src/assets/json-schemas/ocpp/1.6/Heartbeat.json [new file with mode: 0644]
src/assets/json-schemas/ocpp/1.6/MeterValues.json [new file with mode: 0644]
src/assets/json-schemas/ocpp/1.6/StartTransaction.json [new file with mode: 0644]
src/assets/json-schemas/ocpp/1.6/StatusNotification.json [new file with mode: 0644]
src/assets/json-schemas/ocpp/1.6/StopTransaction.json [new file with mode: 0644]
src/charging-station/ocpp/1.6/OCPP16IncomingRequestService.ts
src/charging-station/ocpp/1.6/OCPP16RequestService.ts
src/charging-station/ocpp/1.6/OCPP16ResponseService.ts
src/charging-station/ocpp/OCPPRequestService.ts
src/charging-station/ocpp/OCPPResponseService.ts

diff --git a/src/assets/json-schemas/ocpp/1.6/Authorize.json b/src/assets/json-schemas/ocpp/1.6/Authorize.json
new file mode 100644 (file)
index 0000000..13ade1b
--- /dev/null
@@ -0,0 +1,14 @@
+{
+  "$schema": "http://json-schema.org/draft-04/schema#",
+  "id": "urn:OCPP:1.6:2019:12:AuthorizeRequest",
+  "title": "AuthorizeRequest",
+  "type": "object",
+  "properties": {
+    "idTag": {
+      "type": "string",
+      "maxLength": 20
+    }
+  },
+  "additionalProperties": false,
+  "required": ["idTag"]
+}
diff --git a/src/assets/json-schemas/ocpp/1.6/BootNotification.json b/src/assets/json-schemas/ocpp/1.6/BootNotification.json
new file mode 100644 (file)
index 0000000..d1106f4
--- /dev/null
@@ -0,0 +1,46 @@
+{
+  "$schema": "http://json-schema.org/draft-04/schema#",
+  "id": "urn:OCPP:1.6:2019:12:BootNotificationRequest",
+  "title": "BootNotificationRequest",
+  "type": "object",
+  "properties": {
+    "chargePointVendor": {
+      "type": "string",
+      "maxLength": 20
+    },
+    "chargePointModel": {
+      "type": "string",
+      "maxLength": 20
+    },
+    "chargePointSerialNumber": {
+      "type": "string",
+      "maxLength": 50
+    },
+    "chargeBoxSerialNumber": {
+      "type": "string",
+      "maxLength": 50
+    },
+    "firmwareVersion": {
+      "type": "string",
+      "maxLength": 50
+    },
+    "iccid": {
+      "type": "string",
+      "maxLength": 20
+    },
+    "imsi": {
+      "type": "string",
+      "maxLength": 20
+    },
+    "meterType": {
+      "type": "string",
+      "maxLength": 25
+    },
+    "meterSerialNumber": {
+      "type": "string",
+      "maxLength": 50
+    }
+  },
+  "additionalProperties": false,
+  "required": ["chargePointVendor", "chargePointModel"]
+}
diff --git a/src/assets/json-schemas/ocpp/1.6/DiagnosticsStatusNotification.json b/src/assets/json-schemas/ocpp/1.6/DiagnosticsStatusNotification.json
new file mode 100644 (file)
index 0000000..0939f1a
--- /dev/null
@@ -0,0 +1,15 @@
+{
+  "$schema": "http://json-schema.org/draft-04/schema#",
+  "id": "urn:OCPP:1.6:2019:12:DiagnosticsStatusNotificationRequest",
+  "title": "DiagnosticsStatusNotificationRequest",
+  "type": "object",
+  "properties": {
+    "status": {
+      "type": "string",
+      "additionalProperties": false,
+      "enum": ["Idle", "Uploaded", "UploadFailed", "Uploading"]
+    }
+  },
+  "additionalProperties": false,
+  "required": ["status"]
+}
diff --git a/src/assets/json-schemas/ocpp/1.6/DiagnosticsStatusNotificationResponse.json b/src/assets/json-schemas/ocpp/1.6/DiagnosticsStatusNotificationResponse.json
new file mode 100644 (file)
index 0000000..a15b244
--- /dev/null
@@ -0,0 +1,8 @@
+{
+  "$schema": "http://json-schema.org/draft-04/schema#",
+  "id": "urn:OCPP:1.6:2019:12:DiagnosticsStatusNotificationResponse",
+  "title": "DiagnosticsStatusNotificationResponse",
+  "type": "object",
+  "properties": {},
+  "additionalProperties": false
+}
diff --git a/src/assets/json-schemas/ocpp/1.6/Heartbeat.json b/src/assets/json-schemas/ocpp/1.6/Heartbeat.json
new file mode 100644 (file)
index 0000000..490a224
--- /dev/null
@@ -0,0 +1,8 @@
+{
+  "$schema": "http://json-schema.org/draft-04/schema#",
+  "id": "urn:OCPP:1.6:2019:12:HeartbeatRequest",
+  "title": "HeartbeatRequest",
+  "type": "object",
+  "properties": {},
+  "additionalProperties": false
+}
diff --git a/src/assets/json-schemas/ocpp/1.6/MeterValues.json b/src/assets/json-schemas/ocpp/1.6/MeterValues.json
new file mode 100644 (file)
index 0000000..792a76c
--- /dev/null
@@ -0,0 +1,123 @@
+{
+  "$schema": "http://json-schema.org/draft-04/schema#",
+  "id": "urn:OCPP:1.6:2019:12:MeterValuesRequest",
+  "title": "MeterValuesRequest",
+  "type": "object",
+  "properties": {
+    "connectorId": {
+      "type": "integer"
+    },
+    "transactionId": {
+      "type": "integer"
+    },
+    "meterValue": {
+      "type": "array",
+      "items": {
+        "type": "object",
+        "properties": {
+          "timestamp": {
+            "type": "string",
+            "format": "date-time"
+          },
+          "sampledValue": {
+            "type": "array",
+            "items": {
+              "type": "object",
+              "properties": {
+                "value": {
+                  "type": "string"
+                },
+                "context": {
+                  "type": "string",
+                  "additionalProperties": false,
+                  "enum": [
+                    "Interruption.Begin",
+                    "Interruption.End",
+                    "Sample.Clock",
+                    "Sample.Periodic",
+                    "Transaction.Begin",
+                    "Transaction.End",
+                    "Trigger",
+                    "Other"
+                  ]
+                },
+                "format": {
+                  "type": "string",
+                  "additionalProperties": false,
+                  "enum": ["Raw", "SignedData"]
+                },
+                "measurand": {
+                  "type": "string",
+                  "additionalProperties": false,
+                  "enum": [
+                    "Energy.Active.Export.Register",
+                    "Energy.Active.Import.Register",
+                    "Energy.Reactive.Export.Register",
+                    "Energy.Reactive.Import.Register",
+                    "Energy.Active.Export.Interval",
+                    "Energy.Active.Import.Interval",
+                    "Energy.Reactive.Export.Interval",
+                    "Energy.Reactive.Import.Interval",
+                    "Power.Active.Export",
+                    "Power.Active.Import",
+                    "Power.Offered",
+                    "Power.Reactive.Export",
+                    "Power.Reactive.Import",
+                    "Power.Factor",
+                    "Current.Import",
+                    "Current.Export",
+                    "Current.Offered",
+                    "Voltage",
+                    "Frequency",
+                    "Temperature",
+                    "SoC",
+                    "RPM"
+                  ]
+                },
+                "phase": {
+                  "type": "string",
+                  "additionalProperties": false,
+                  "enum": ["L1", "L2", "L3", "N", "L1-N", "L2-N", "L3-N", "L1-L2", "L2-L3", "L3-L1"]
+                },
+                "location": {
+                  "type": "string",
+                  "additionalProperties": false,
+                  "enum": ["Cable", "EV", "Inlet", "Outlet", "Body"]
+                },
+                "unit": {
+                  "type": "string",
+                  "additionalProperties": false,
+                  "enum": [
+                    "Wh",
+                    "kWh",
+                    "varh",
+                    "kvarh",
+                    "W",
+                    "kW",
+                    "VA",
+                    "kVA",
+                    "var",
+                    "kvar",
+                    "A",
+                    "V",
+                    "K",
+                    "Celcius",
+                    "Celsius",
+                    "Fahrenheit",
+                    "Percent"
+                  ]
+                }
+              },
+              "additionalProperties": false,
+              "required": ["value"]
+            }
+          }
+        },
+        "additionalProperties": false,
+        "required": ["timestamp", "sampledValue"]
+      }
+    }
+  },
+  "additionalProperties": false,
+  "required": ["connectorId", "meterValue"]
+}
diff --git a/src/assets/json-schemas/ocpp/1.6/StartTransaction.json b/src/assets/json-schemas/ocpp/1.6/StartTransaction.json
new file mode 100644 (file)
index 0000000..74bcfcf
--- /dev/null
@@ -0,0 +1,27 @@
+{
+  "$schema": "http://json-schema.org/draft-04/schema#",
+  "id": "urn:OCPP:1.6:2019:12:StartTransactionRequest",
+  "title": "StartTransactionRequest",
+  "type": "object",
+  "properties": {
+    "connectorId": {
+      "type": "integer"
+    },
+    "idTag": {
+      "type": "string",
+      "maxLength": 20
+    },
+    "meterStart": {
+      "type": "integer"
+    },
+    "reservationId": {
+      "type": "integer"
+    },
+    "timestamp": {
+      "type": "string",
+      "format": "date-time"
+    }
+  },
+  "additionalProperties": false,
+  "required": ["connectorId", "idTag", "meterStart", "timestamp"]
+}
diff --git a/src/assets/json-schemas/ocpp/1.6/StatusNotification.json b/src/assets/json-schemas/ocpp/1.6/StatusNotification.json
new file mode 100644 (file)
index 0000000..4157af5
--- /dev/null
@@ -0,0 +1,66 @@
+{
+  "$schema": "http://json-schema.org/draft-04/schema#",
+  "id": "urn:OCPP:1.6:2019:12:StatusNotificationRequest",
+  "title": "StatusNotificationRequest",
+  "type": "object",
+  "properties": {
+    "connectorId": {
+      "type": "integer"
+    },
+    "errorCode": {
+      "type": "string",
+      "additionalProperties": false,
+      "enum": [
+        "ConnectorLockFailure",
+        "EVCommunicationError",
+        "GroundFailure",
+        "HighTemperature",
+        "InternalError",
+        "LocalListConflict",
+        "NoError",
+        "OtherError",
+        "OverCurrentFailure",
+        "PowerMeterFailure",
+        "PowerSwitchFailure",
+        "ReaderFailure",
+        "ResetFailure",
+        "UnderVoltage",
+        "OverVoltage",
+        "WeakSignal"
+      ]
+    },
+    "info": {
+      "type": "string",
+      "maxLength": 50
+    },
+    "status": {
+      "type": "string",
+      "additionalProperties": false,
+      "enum": [
+        "Available",
+        "Preparing",
+        "Charging",
+        "SuspendedEVSE",
+        "SuspendedEV",
+        "Finishing",
+        "Reserved",
+        "Unavailable",
+        "Faulted"
+      ]
+    },
+    "timestamp": {
+      "type": "string",
+      "format": "date-time"
+    },
+    "vendorId": {
+      "type": "string",
+      "maxLength": 255
+    },
+    "vendorErrorCode": {
+      "type": "string",
+      "maxLength": 50
+    }
+  },
+  "additionalProperties": false,
+  "required": ["connectorId", "errorCode", "status"]
+}
diff --git a/src/assets/json-schemas/ocpp/1.6/StopTransaction.json b/src/assets/json-schemas/ocpp/1.6/StopTransaction.json
new file mode 100644 (file)
index 0000000..db4de22
--- /dev/null
@@ -0,0 +1,147 @@
+{
+  "$schema": "http://json-schema.org/draft-04/schema#",
+  "id": "urn:OCPP:1.6:2019:12:StopTransactionRequest",
+  "title": "StopTransactionRequest",
+  "type": "object",
+  "properties": {
+    "idTag": {
+      "type": "string",
+      "maxLength": 20
+    },
+    "meterStop": {
+      "type": "integer"
+    },
+    "timestamp": {
+      "type": "string",
+      "format": "date-time"
+    },
+    "transactionId": {
+      "type": "integer"
+    },
+    "reason": {
+      "type": "string",
+      "additionalProperties": false,
+      "enum": [
+        "EmergencyStop",
+        "EVDisconnected",
+        "HardReset",
+        "Local",
+        "Other",
+        "PowerLoss",
+        "Reboot",
+        "Remote",
+        "SoftReset",
+        "UnlockCommand",
+        "DeAuthorized"
+      ]
+    },
+    "transactionData": {
+      "type": "array",
+      "items": {
+        "type": "object",
+        "properties": {
+          "timestamp": {
+            "type": "string",
+            "format": "date-time"
+          },
+          "sampledValue": {
+            "type": "array",
+            "items": {
+              "type": "object",
+              "properties": {
+                "value": {
+                  "type": "string"
+                },
+                "context": {
+                  "type": "string",
+                  "additionalProperties": false,
+                  "enum": [
+                    "Interruption.Begin",
+                    "Interruption.End",
+                    "Sample.Clock",
+                    "Sample.Periodic",
+                    "Transaction.Begin",
+                    "Transaction.End",
+                    "Trigger",
+                    "Other"
+                  ]
+                },
+                "format": {
+                  "type": "string",
+                  "additionalProperties": false,
+                  "enum": ["Raw", "SignedData"]
+                },
+                "measurand": {
+                  "type": "string",
+                  "additionalProperties": false,
+                  "enum": [
+                    "Energy.Active.Export.Register",
+                    "Energy.Active.Import.Register",
+                    "Energy.Reactive.Export.Register",
+                    "Energy.Reactive.Import.Register",
+                    "Energy.Active.Export.Interval",
+                    "Energy.Active.Import.Interval",
+                    "Energy.Reactive.Export.Interval",
+                    "Energy.Reactive.Import.Interval",
+                    "Power.Active.Export",
+                    "Power.Active.Import",
+                    "Power.Offered",
+                    "Power.Reactive.Export",
+                    "Power.Reactive.Import",
+                    "Power.Factor",
+                    "Current.Import",
+                    "Current.Export",
+                    "Current.Offered",
+                    "Voltage",
+                    "Frequency",
+                    "Temperature",
+                    "SoC",
+                    "RPM"
+                  ]
+                },
+                "phase": {
+                  "type": "string",
+                  "additionalProperties": false,
+                  "enum": ["L1", "L2", "L3", "N", "L1-N", "L2-N", "L3-N", "L1-L2", "L2-L3", "L3-L1"]
+                },
+                "location": {
+                  "type": "string",
+                  "additionalProperties": false,
+                  "enum": ["Cable", "EV", "Inlet", "Outlet", "Body"]
+                },
+                "unit": {
+                  "type": "string",
+                  "additionalProperties": false,
+                  "enum": [
+                    "Wh",
+                    "kWh",
+                    "varh",
+                    "kvarh",
+                    "W",
+                    "kW",
+                    "VA",
+                    "kVA",
+                    "var",
+                    "kvar",
+                    "A",
+                    "V",
+                    "K",
+                    "Celcius",
+                    "Fahrenheit",
+                    "Percent"
+                  ]
+                }
+              },
+              "additionalProperties": false,
+              "required": ["value"]
+            }
+          }
+        },
+        "additionalProperties": false,
+        "required": ["timestamp", "sampledValue"]
+      }
+    }
+  },
+  "additionalProperties": false,
+  "required": ["transactionId", "timestamp", "meterStop"]
+}
index 64314475c8b42ea7ff6e90fd8a854b2d808e7540..3238722568d9cc92bae51cd1476a1e90a10d0b77 100644 (file)
@@ -9,7 +9,7 @@ import { Client, FTPResponse } from 'basic-ftp';
 import tar from 'tar';
 
 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 {
@@ -88,18 +88,7 @@ const moduleName = 'OCPP16IncomingRequestService';
 
 export default class OCPP16IncomingRequestService extends OCPPIncomingRequestService {
   private incomingRequestHandlers: Map<OCPP16IncomingRequestCommand, IncomingRequestHandler>;
-  private resetJsonSchema: JSONSchemaType<ResetRequest>;
-  private clearCacheJsonSchema: JSONSchemaType<OCPP16ClearCacheRequest>;
-  private getConfigurationJsonSchema: JSONSchemaType<GetConfigurationRequest>;
-  private changeConfigurationJsonSchema: JSONSchemaType<ChangeConfigurationRequest>;
-  private unlockConnectorJsonSchema: JSONSchemaType<UnlockConnectorRequest>;
-  private getDiagnosticsJsonSchema: JSONSchemaType<GetDiagnosticsRequest>;
-  private setChargingProfileJsonSchema: JSONSchemaType<SetChargingProfileRequest>;
-  private clearChargingProfileJsonSchema: JSONSchemaType<ClearChargingProfileRequest>;
-  private changeAvailabilityJsonSchema: JSONSchemaType<ChangeAvailabilityRequest>;
-  private remoteStartTransactionJsonSchema: JSONSchemaType<RemoteStartTransactionRequest>;
-  private remoteStopTransactionJsonSchema: JSONSchemaType<RemoteStopTransactionRequest>;
-  private triggerMessageJsonSchema: JSONSchemaType<OCPP16TriggerMessageRequest>;
+  private jsonSchemas: Map<OCPP16IncomingRequestCommand, JSONSchemaType<JsonObject>>;
 
   public constructor() {
     if (new.target?.name === moduleName) {
@@ -141,114 +130,152 @@ export default class OCPP16IncomingRequestService extends OCPPIncomingRequestSer
       [OCPP16IncomingRequestCommand.GET_DIAGNOSTICS, this.handleRequestGetDiagnostics.bind(this)],
       [OCPP16IncomingRequestCommand.TRIGGER_MESSAGE, this.handleRequestTriggerMessage.bind(this)],
     ]);
-    this.resetJsonSchema = JSON.parse(
-      fs.readFileSync(
-        path.resolve(
-          path.dirname(fileURLToPath(import.meta.url)),
-          '../../../assets/json-schemas/ocpp/1.6/Reset.json'
-        ),
-        'utf8'
-      )
-    ) as JSONSchemaType<ResetRequest>;
-    this.clearCacheJsonSchema = JSON.parse(
-      fs.readFileSync(
-        path.resolve(
-          path.dirname(fileURLToPath(import.meta.url)),
-          '../../../assets/json-schemas/ocpp/1.6/ClearCache.json'
-        ),
-        'utf8'
-      )
-    ) as JSONSchemaType<OCPP16ClearCacheRequest>;
-    this.getConfigurationJsonSchema = JSON.parse(
-      fs.readFileSync(
-        path.resolve(
-          path.dirname(fileURLToPath(import.meta.url)),
-          '../../../assets/json-schemas/ocpp/1.6/GetConfiguration.json'
-        ),
-        'utf8'
-      )
-    ) as JSONSchemaType<GetConfigurationRequest>;
-    this.changeConfigurationJsonSchema = JSON.parse(
-      fs.readFileSync(
-        path.resolve(
-          path.dirname(fileURLToPath(import.meta.url)),
-          '../../../assets/json-schemas/ocpp/1.6/ChangeConfiguration.json'
-        ),
-        'utf8'
-      )
-    ) as JSONSchemaType<ChangeConfigurationRequest>;
-    this.unlockConnectorJsonSchema = JSON.parse(
-      fs.readFileSync(
-        path.resolve(
-          path.dirname(fileURLToPath(import.meta.url)),
-          '../../../assets/json-schemas/ocpp/1.6/UnlockConnector.json'
-        ),
-        'utf8'
-      )
-    ) as JSONSchemaType<UnlockConnectorRequest>;
-    this.getDiagnosticsJsonSchema = JSON.parse(
-      fs.readFileSync(
-        path.resolve(
-          path.dirname(fileURLToPath(import.meta.url)),
-          '../../../assets/json-schemas/ocpp/1.6/GetDiagnostics.json'
-        ),
-        'utf8'
-      )
-    ) as JSONSchemaType<GetDiagnosticsRequest>;
-    this.setChargingProfileJsonSchema = JSON.parse(
-      fs.readFileSync(
-        path.resolve(
-          path.dirname(fileURLToPath(import.meta.url)),
-          '../../../assets/json-schemas/ocpp/1.6/SetChargingProfile.json'
-        ),
-        'utf8'
-      )
-    ) as JSONSchemaType<SetChargingProfileRequest>;
-    this.clearChargingProfileJsonSchema = JSON.parse(
-      fs.readFileSync(
-        path.resolve(
-          path.dirname(fileURLToPath(import.meta.url)),
-          '../../../assets/json-schemas/ocpp/1.6/ClearChargingProfile.json'
-        ),
-        'utf8'
-      )
-    ) as JSONSchemaType<ClearChargingProfileRequest>;
-    this.changeAvailabilityJsonSchema = JSON.parse(
-      fs.readFileSync(
-        path.resolve(
-          path.dirname(fileURLToPath(import.meta.url)),
-          '../../../assets/json-schemas/ocpp/1.6/ChangeAvailability.json'
-        ),
-        'utf8'
-      )
-    ) as JSONSchemaType<ChangeAvailabilityRequest>;
-    this.remoteStartTransactionJsonSchema = JSON.parse(
-      fs.readFileSync(
-        path.resolve(
-          path.dirname(fileURLToPath(import.meta.url)),
-          '../../../assets/json-schemas/ocpp/1.6/RemoteStartTransaction.json'
-        ),
-        'utf8'
-      )
-    ) as JSONSchemaType<RemoteStartTransactionRequest>;
-    this.remoteStopTransactionJsonSchema = JSON.parse(
-      fs.readFileSync(
-        path.resolve(
-          path.dirname(fileURLToPath(import.meta.url)),
-          '../../../assets/json-schemas/ocpp/1.6/RemoteStopTransaction.json'
-        ),
-        'utf8'
-      )
-    ) as JSONSchemaType<RemoteStopTransactionRequest>;
-    this.triggerMessageJsonSchema = JSON.parse(
-      fs.readFileSync(
-        path.resolve(
-          path.dirname(fileURLToPath(import.meta.url)),
-          '../../../assets/json-schemas/ocpp/1.6/TriggerMessage.json'
-        ),
-        'utf8'
-      )
-    ) as JSONSchemaType<OCPP16TriggerMessageRequest>;
+    this.jsonSchemas = new Map<OCPP16IncomingRequestCommand, JSONSchemaType<JsonObject>>([
+      [
+        OCPP16IncomingRequestCommand.RESET,
+        JSON.parse(
+          fs.readFileSync(
+            path.resolve(
+              path.dirname(fileURLToPath(import.meta.url)),
+              '../../../assets/json-schemas/ocpp/1.6/Reset.json'
+            ),
+            'utf8'
+          )
+        ) as JSONSchemaType<ResetRequest>,
+      ],
+      [
+        OCPP16IncomingRequestCommand.CLEAR_CACHE,
+        JSON.parse(
+          fs.readFileSync(
+            path.resolve(
+              path.dirname(fileURLToPath(import.meta.url)),
+              '../../../assets/json-schemas/ocpp/1.6/ClearCache.json'
+            ),
+            'utf8'
+          )
+        ) as JSONSchemaType<OCPP16ClearCacheRequest>,
+      ],
+      [
+        OCPP16IncomingRequestCommand.UNLOCK_CONNECTOR,
+        JSON.parse(
+          fs.readFileSync(
+            path.resolve(
+              path.dirname(fileURLToPath(import.meta.url)),
+              '../../../assets/json-schemas/ocpp/1.6/UnlockConnector.json'
+            ),
+            'utf8'
+          )
+        ) as JSONSchemaType<UnlockConnectorRequest>,
+      ],
+      [
+        OCPP16IncomingRequestCommand.GET_CONFIGURATION,
+        JSON.parse(
+          fs.readFileSync(
+            path.resolve(
+              path.dirname(fileURLToPath(import.meta.url)),
+              '../../../assets/json-schemas/ocpp/1.6/GetConfiguration.json'
+            ),
+            'utf8'
+          )
+        ) as JSONSchemaType<GetConfigurationRequest>,
+      ],
+      [
+        OCPP16IncomingRequestCommand.CHANGE_CONFIGURATION,
+        JSON.parse(
+          fs.readFileSync(
+            path.resolve(
+              path.dirname(fileURLToPath(import.meta.url)),
+              '../../../assets/json-schemas/ocpp/1.6/ChangeConfiguration.json'
+            ),
+            'utf8'
+          )
+        ) as JSONSchemaType<ChangeConfigurationRequest>,
+      ],
+      [
+        OCPP16IncomingRequestCommand.GET_DIAGNOSTICS,
+        JSON.parse(
+          fs.readFileSync(
+            path.resolve(
+              path.dirname(fileURLToPath(import.meta.url)),
+              '../../../assets/json-schemas/ocpp/1.6/GetDiagnostics.json'
+            ),
+            'utf8'
+          )
+        ) as JSONSchemaType<GetDiagnosticsRequest>,
+      ],
+      [
+        OCPP16IncomingRequestCommand.SET_CHARGING_PROFILE,
+        JSON.parse(
+          fs.readFileSync(
+            path.resolve(
+              path.dirname(fileURLToPath(import.meta.url)),
+              '../../../assets/json-schemas/ocpp/1.6/SetChargingProfile.json'
+            ),
+            'utf8'
+          )
+        ) as JSONSchemaType<SetChargingProfileRequest>,
+      ],
+      [
+        OCPP16IncomingRequestCommand.CLEAR_CHARGING_PROFILE,
+        JSON.parse(
+          fs.readFileSync(
+            path.resolve(
+              path.dirname(fileURLToPath(import.meta.url)),
+              '../../../assets/json-schemas/ocpp/1.6/ClearChargingProfile.json'
+            ),
+            'utf8'
+          )
+        ) as JSONSchemaType<ClearChargingProfileRequest>,
+      ],
+      [
+        OCPP16IncomingRequestCommand.CHANGE_AVAILABILITY,
+        JSON.parse(
+          fs.readFileSync(
+            path.resolve(
+              path.dirname(fileURLToPath(import.meta.url)),
+              '../../../assets/json-schemas/ocpp/1.6/ChangeAvailability.json'
+            ),
+            'utf8'
+          )
+        ) as JSONSchemaType<ChangeAvailabilityRequest>,
+      ],
+      [
+        OCPP16IncomingRequestCommand.REMOTE_START_TRANSACTION,
+        JSON.parse(
+          fs.readFileSync(
+            path.resolve(
+              path.dirname(fileURLToPath(import.meta.url)),
+              '../../../assets/json-schemas/ocpp/1.6/RemoteStartTransaction.json'
+            ),
+            'utf8'
+          )
+        ) as JSONSchemaType<RemoteStartTransactionRequest>,
+      ],
+      [
+        OCPP16IncomingRequestCommand.REMOTE_STOP_TRANSACTION,
+        JSON.parse(
+          fs.readFileSync(
+            path.resolve(
+              path.dirname(fileURLToPath(import.meta.url)),
+              '../../../assets/json-schemas/ocpp/1.6/RemoteStopTransaction.json'
+            ),
+            'utf8'
+          )
+        ) as JSONSchemaType<RemoteStopTransactionRequest>,
+      ],
+      [
+        OCPP16IncomingRequestCommand.TRIGGER_MESSAGE,
+        JSON.parse(
+          fs.readFileSync(
+            path.resolve(
+              path.dirname(fileURLToPath(import.meta.url)),
+              '../../../assets/json-schemas/ocpp/1.6/TriggerMessage.json'
+            ),
+            'utf8'
+          )
+        ) as JSONSchemaType<OCPP16TriggerMessageRequest>,
+      ],
+    ]);
   }
 
   public async incomingRequestHandler(
@@ -284,6 +311,18 @@ export default class OCPP16IncomingRequestService extends OCPPIncomingRequestSer
         ChargingStationUtils.isIncomingRequestCommandSupported(commandName, chargingStation)
       ) {
         try {
+          if (this.jsonSchemas.has(commandName)) {
+            this.validateIncomingRequestPayload(
+              chargingStation,
+              commandName,
+              this.jsonSchemas.get(commandName),
+              commandPayload
+            );
+          } else {
+            logger.warn(
+              `${chargingStation.logPrefix()} ${moduleName}.incomingRequestHandler: No JSON schema found for command ${commandName} PDU validation`
+            );
+          }
           // Call the method to build the response
           response = await this.incomingRequestHandlers.get(commandName)(
             chargingStation,
@@ -333,12 +372,6 @@ export default class OCPP16IncomingRequestService extends OCPPIncomingRequestSer
     chargingStation: ChargingStation,
     commandPayload: ResetRequest
   ): DefaultResponse {
-    this.validateIncomingRequestPayload(
-      chargingStation,
-      OCPP16IncomingRequestCommand.RESET,
-      this.resetJsonSchema,
-      commandPayload
-    );
     // eslint-disable-next-line @typescript-eslint/no-misused-promises
     setImmediate(async (): Promise<void> => {
       await chargingStation.reset((commandPayload.type + 'Reset') as OCPP16StopTransactionReason);
@@ -353,16 +386,7 @@ export default class OCPP16IncomingRequestService extends OCPPIncomingRequestSer
     return Constants.OCPP_RESPONSE_ACCEPTED;
   }
 
-  private handleRequestClearCache(
-    chargingStation: ChargingStation,
-    commandPayload: OCPP16ClearCacheRequest
-  ): DefaultResponse {
-    this.validateIncomingRequestPayload(
-      chargingStation,
-      OCPP16IncomingRequestCommand.CLEAR_CACHE,
-      this.clearCacheJsonSchema,
-      commandPayload
-    );
+  private handleRequestClearCache(): DefaultResponse {
     return Constants.OCPP_RESPONSE_ACCEPTED;
   }
 
@@ -370,12 +394,6 @@ export default class OCPP16IncomingRequestService extends OCPPIncomingRequestSer
     chargingStation: ChargingStation,
     commandPayload: UnlockConnectorRequest
   ): Promise<UnlockConnectorResponse> {
-    this.validateIncomingRequestPayload(
-      chargingStation,
-      OCPP16IncomingRequestCommand.UNLOCK_CONNECTOR,
-      this.unlockConnectorJsonSchema,
-      commandPayload
-    );
     const connectorId = commandPayload.connectorId;
     if (connectorId === 0) {
       logger.error(
@@ -435,12 +453,6 @@ export default class OCPP16IncomingRequestService extends OCPPIncomingRequestSer
     chargingStation: ChargingStation,
     commandPayload: GetConfigurationRequest
   ): GetConfigurationResponse {
-    this.validateIncomingRequestPayload(
-      chargingStation,
-      OCPP16IncomingRequestCommand.GET_CONFIGURATION,
-      this.getConfigurationJsonSchema,
-      commandPayload
-    );
     const configurationKey: OCPPConfigurationKey[] = [];
     const unknownKey: string[] = [];
     if (Utils.isEmptyArray(commandPayload.key)) {
@@ -490,12 +502,6 @@ export default class OCPP16IncomingRequestService extends OCPPIncomingRequestSer
     chargingStation: ChargingStation,
     commandPayload: ChangeConfigurationRequest
   ): ChangeConfigurationResponse {
-    this.validateIncomingRequestPayload(
-      chargingStation,
-      OCPP16IncomingRequestCommand.CHANGE_CONFIGURATION,
-      this.changeConfigurationJsonSchema,
-      commandPayload
-    );
     const keyToChange = ChargingStationConfigurationUtils.getConfigurationKey(
       chargingStation,
       commandPayload.key,
@@ -550,12 +556,6 @@ export default class OCPP16IncomingRequestService extends OCPPIncomingRequestSer
     chargingStation: ChargingStation,
     commandPayload: SetChargingProfileRequest
   ): SetChargingProfileResponse {
-    this.validateIncomingRequestPayload(
-      chargingStation,
-      OCPP16IncomingRequestCommand.SET_CHARGING_PROFILE,
-      this.setChargingProfileJsonSchema,
-      commandPayload
-    );
     if (
       !OCPP16ServiceUtils.checkFeatureProfile(
         chargingStation,
@@ -605,12 +605,6 @@ export default class OCPP16IncomingRequestService extends OCPPIncomingRequestSer
     chargingStation: ChargingStation,
     commandPayload: ClearChargingProfileRequest
   ): ClearChargingProfileResponse {
-    this.validateIncomingRequestPayload(
-      chargingStation,
-      OCPP16IncomingRequestCommand.CLEAR_CHARGING_PROFILE,
-      this.clearChargingProfileJsonSchema,
-      commandPayload
-    );
     if (
       !OCPP16ServiceUtils.checkFeatureProfile(
         chargingStation,
@@ -692,12 +686,6 @@ export default class OCPP16IncomingRequestService extends OCPPIncomingRequestSer
     chargingStation: ChargingStation,
     commandPayload: ChangeAvailabilityRequest
   ): Promise<ChangeAvailabilityResponse> {
-    this.validateIncomingRequestPayload(
-      chargingStation,
-      OCPP16IncomingRequestCommand.CHANGE_AVAILABILITY,
-      this.changeAvailabilityJsonSchema,
-      commandPayload
-    );
     const connectorId: number = commandPayload.connectorId;
     if (!chargingStation.getConnectorStatus(connectorId)) {
       logger.error(
@@ -759,12 +747,6 @@ export default class OCPP16IncomingRequestService extends OCPPIncomingRequestSer
     chargingStation: ChargingStation,
     commandPayload: RemoteStartTransactionRequest
   ): Promise<DefaultResponse> {
-    this.validateIncomingRequestPayload(
-      chargingStation,
-      OCPP16IncomingRequestCommand.REMOTE_START_TRANSACTION,
-      this.remoteStartTransactionJsonSchema,
-      commandPayload
-    );
     const transactionConnectorId = commandPayload.connectorId;
     const connectorStatus = chargingStation.getConnectorStatus(transactionConnectorId);
     if (transactionConnectorId) {
@@ -976,12 +958,6 @@ export default class OCPP16IncomingRequestService extends OCPPIncomingRequestSer
     chargingStation: ChargingStation,
     commandPayload: RemoteStopTransactionRequest
   ): Promise<DefaultResponse> {
-    this.validateIncomingRequestPayload(
-      chargingStation,
-      OCPP16IncomingRequestCommand.REMOTE_STOP_TRANSACTION,
-      this.remoteStopTransactionJsonSchema,
-      commandPayload
-    );
     const transactionId = commandPayload.transactionId;
     for (const connectorId of chargingStation.connectors.keys()) {
       if (
@@ -1040,12 +1016,6 @@ export default class OCPP16IncomingRequestService extends OCPPIncomingRequestSer
     chargingStation: ChargingStation,
     commandPayload: GetDiagnosticsRequest
   ): Promise<GetDiagnosticsResponse> {
-    this.validateIncomingRequestPayload(
-      chargingStation,
-      OCPP16IncomingRequestCommand.GET_DIAGNOSTICS,
-      this.getDiagnosticsJsonSchema,
-      commandPayload
-    );
     if (
       !OCPP16ServiceUtils.checkFeatureProfile(
         chargingStation,
@@ -1166,12 +1136,6 @@ export default class OCPP16IncomingRequestService extends OCPPIncomingRequestSer
     chargingStation: ChargingStation,
     commandPayload: OCPP16TriggerMessageRequest
   ): OCPP16TriggerMessageResponse {
-    this.validateIncomingRequestPayload(
-      chargingStation,
-      OCPP16IncomingRequestCommand.TRIGGER_MESSAGE,
-      this.triggerMessageJsonSchema,
-      commandPayload
-    );
     if (
       !OCPP16ServiceUtils.checkFeatureProfile(
         chargingStation,
index 8638ec0519ec8bfae0fa4c812cb72edf2158146b..29ea6f393288efd0afaf539b2b8d4351340da56a 100644 (file)
@@ -1,11 +1,30 @@
 // 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 { JsonObject, JsonType } from '../../../types/JsonType';
-import { OCPP16RequestCommand } from '../../../types/ocpp/1.6/Requests';
+import { OCPP16MeterValuesRequest } from '../../../types/ocpp/1.6/MeterValues';
+import {
+  DiagnosticsStatusNotificationRequest,
+  OCPP16BootNotificationRequest,
+  OCPP16HeartbeatRequest,
+  OCPP16RequestCommand,
+  OCPP16StatusNotificationRequest,
+} from '../../../types/ocpp/1.6/Requests';
+import {
+  OCPP16AuthorizeRequest,
+  OCPP16StartTransactionRequest,
+  OCPP16StopTransactionRequest,
+} from '../../../types/ocpp/1.6/Transaction';
 import { ErrorType } from '../../../types/ocpp/ErrorType';
 import { RequestParams } from '../../../types/ocpp/Requests';
 import Constants from '../../../utils/Constants';
+import logger from '../../../utils/Logger';
 import Utils from '../../../utils/Utils';
 import type ChargingStation from '../../ChargingStation';
 import { ChargingStationUtils } from '../../ChargingStationUtils';
@@ -16,11 +35,111 @@ import { OCPP16ServiceUtils } from './OCPP16ServiceUtils';
 const moduleName = 'OCPP16RequestService';
 
 export default class OCPP16RequestService extends OCPPRequestService {
+  private jsonSchemas: Map<OCPP16RequestCommand, JSONSchemaType<JsonObject>>;
+
   public constructor(ocppResponseService: OCPPResponseService) {
     if (new.target?.name === moduleName) {
       throw new TypeError(`Cannot construct ${new.target?.name} instances directly`);
     }
     super(ocppResponseService);
+    this.jsonSchemas = new Map<OCPP16RequestCommand, JSONSchemaType<JsonObject>>([
+      [
+        OCPP16RequestCommand.AUTHORIZE,
+        JSON.parse(
+          fs.readFileSync(
+            path.resolve(
+              path.dirname(fileURLToPath(import.meta.url)),
+              '../../../assets/json-schemas/ocpp/1.6/Authorize.json'
+            ),
+            'utf8'
+          )
+        ) as JSONSchemaType<OCPP16AuthorizeRequest>,
+      ],
+      [
+        OCPP16RequestCommand.BOOT_NOTIFICATION,
+        JSON.parse(
+          fs.readFileSync(
+            path.resolve(
+              path.dirname(fileURLToPath(import.meta.url)),
+              '../../../assets/json-schemas/ocpp/1.6/BootNotification.json'
+            ),
+            'utf8'
+          )
+        ) as JSONSchemaType<OCPP16BootNotificationRequest>,
+      ],
+      [
+        OCPP16RequestCommand.DIAGNOSTICS_STATUS_NOTIFICATION,
+        JSON.parse(
+          fs.readFileSync(
+            path.resolve(
+              path.dirname(fileURLToPath(import.meta.url)),
+              '../../../assets/json-schemas/ocpp/1.6/DiagnosticsStatusNotification.json'
+            ),
+            'utf8'
+          )
+        ) as JSONSchemaType<DiagnosticsStatusNotificationRequest>,
+      ],
+      [
+        OCPP16RequestCommand.HEARTBEAT,
+        JSON.parse(
+          fs.readFileSync(
+            path.resolve(
+              path.dirname(fileURLToPath(import.meta.url)),
+              '../../../assets/json-schemas/ocpp/1.6/Heartbeat.json'
+            ),
+            'utf8'
+          )
+        ) as JSONSchemaType<OCPP16HeartbeatRequest>,
+      ],
+      [
+        OCPP16RequestCommand.METER_VALUES,
+        JSON.parse(
+          fs.readFileSync(
+            path.resolve(
+              path.dirname(fileURLToPath(import.meta.url)),
+              '../../../assets/json-schemas/ocpp/1.6/MeterValues.json'
+            ),
+            'utf8'
+          )
+        ) as JSONSchemaType<OCPP16MeterValuesRequest>,
+      ],
+      [
+        OCPP16RequestCommand.STATUS_NOTIFICATION,
+        JSON.parse(
+          fs.readFileSync(
+            path.resolve(
+              path.dirname(fileURLToPath(import.meta.url)),
+              '../../../assets/json-schemas/ocpp/1.6/StatusNotification.json'
+            ),
+            'utf8'
+          )
+        ) as JSONSchemaType<OCPP16StatusNotificationRequest>,
+      ],
+      [
+        OCPP16RequestCommand.START_TRANSACTION,
+        JSON.parse(
+          fs.readFileSync(
+            path.resolve(
+              path.dirname(fileURLToPath(import.meta.url)),
+              '../../../assets/json-schemas/ocpp/1.6/StartTransaction.json'
+            ),
+            'utf8'
+          )
+        ) as JSONSchemaType<OCPP16StartTransactionRequest>,
+      ],
+      [
+        OCPP16RequestCommand.STOP_TRANSACTION,
+        JSON.parse(
+          fs.readFileSync(
+            path.resolve(
+              path.dirname(fileURLToPath(import.meta.url)),
+              '../../../assets/json-schemas/ocpp/1.6/StopTransaction.json'
+            ),
+            'utf8'
+          )
+        ) as JSONSchemaType<OCPP16StopTransactionRequest>,
+      ],
+    ]);
   }
 
   public async requestHandler<Request extends JsonType, Response extends JsonType>(
@@ -30,10 +149,27 @@ export default class OCPP16RequestService extends OCPPRequestService {
     params?: RequestParams
   ): Promise<Response> {
     if (ChargingStationUtils.isRequestCommandSupported(commandName, chargingStation)) {
+      const requestPayload = this.buildRequestPayload<Request>(
+        chargingStation,
+        commandName,
+        commandParams
+      );
+      if (this.jsonSchemas.has(commandName)) {
+        this.validateRequestPayload(
+          chargingStation,
+          commandName,
+          this.jsonSchemas.get(commandName),
+          requestPayload
+        );
+      } else {
+        logger.warn(
+          `${chargingStation.logPrefix()} ${moduleName}.requestHandler: No JSON schema found for command ${commandName} PDU validation`
+        );
+      }
       return (await this.sendMessage(
         chargingStation,
         Utils.generateUUID(),
-        this.buildRequestPayload<Request>(chargingStation, commandName, commandParams),
+        requestPayload,
         commandName,
         params
       )) as unknown as Response;
index 3372e1036c1ce464e7e1700fb87cca114177170a..e3e87d4c59d31180e1b0e1a3cbda1c1482c17d68 100644 (file)
@@ -7,7 +7,7 @@ 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';
@@ -21,6 +21,7 @@ import {
   OCPP16StatusNotificationRequest,
 } from '../../../types/ocpp/1.6/Requests';
 import {
+  DiagnosticsStatusNotificationResponse,
   OCPP16BootNotificationResponse,
   OCPP16HeartbeatResponse,
   OCPP16RegistrationStatus,
@@ -49,13 +50,7 @@ const moduleName = 'OCPP16ResponseService';
 
 export default class OCPP16ResponseService extends OCPPResponseService {
   private responseHandlers: Map<OCPP16RequestCommand, ResponseHandler>;
-  private bootNotificationResponseJsonSchema: JSONSchemaType<OCPP16BootNotificationResponse>;
-  private heartbeatResponseJsonSchema: JSONSchemaType<OCPP16HeartbeatResponse>;
-  private authorizeResponseJsonSchema: JSONSchemaType<OCPP16AuthorizeResponse>;
-  private startTransactionResponseJsonSchema: JSONSchemaType<OCPP16StartTransactionResponse>;
-  private stopTransactionResponseJsonSchema: JSONSchemaType<OCPP16StopTransactionResponse>;
-  private statusNotificationResponseJsonSchema: JSONSchemaType<OCPP16StatusNotificationResponse>;
-  private meterValuesResponseJsonSchema: JSONSchemaType<OCPP16MeterValuesResponse>;
+  private jsonSchemas: Map<OCPP16RequestCommand, JSONSchemaType<JsonObject>>;
 
   public constructor() {
     if (new.target?.name === moduleName) {
@@ -64,76 +59,112 @@ 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>,
+      ],
     ]);
-    this.bootNotificationResponseJsonSchema = JSON.parse(
-      fs.readFileSync(
-        path.resolve(
-          path.dirname(fileURLToPath(import.meta.url)),
-          '../../../assets/json-schemas/ocpp/1.6/BootNotificationResponse.json'
-        ),
-        'utf8'
-      )
-    ) as JSONSchemaType<OCPP16BootNotificationResponse>;
-    this.heartbeatResponseJsonSchema = JSON.parse(
-      fs.readFileSync(
-        path.resolve(
-          path.dirname(fileURLToPath(import.meta.url)),
-          '../../../assets/json-schemas/ocpp/1.6/HeartbeatResponse.json'
-        ),
-        'utf8'
-      )
-    ) as JSONSchemaType<OCPP16HeartbeatResponse>;
-    this.authorizeResponseJsonSchema = JSON.parse(
-      fs.readFileSync(
-        path.resolve(
-          path.dirname(fileURLToPath(import.meta.url)),
-          '../../../assets/json-schemas/ocpp/1.6/AuthorizeResponse.json'
-        ),
-        'utf8'
-      )
-    ) as JSONSchemaType<OCPP16AuthorizeResponse>;
-    this.startTransactionResponseJsonSchema = JSON.parse(
-      fs.readFileSync(
-        path.resolve(
-          path.dirname(fileURLToPath(import.meta.url)),
-          '../../../assets/json-schemas/ocpp/1.6/StartTransactionResponse.json'
-        ),
-        'utf8'
-      )
-    ) as JSONSchemaType<OCPP16StartTransactionResponse>;
-    this.stopTransactionResponseJsonSchema = JSON.parse(
-      fs.readFileSync(
-        path.resolve(
-          path.dirname(fileURLToPath(import.meta.url)),
-          '../../../assets/json-schemas/ocpp/1.6/StopTransactionResponse.json'
-        ),
-        'utf8'
-      )
-    ) as JSONSchemaType<OCPP16StopTransactionResponse>;
-    this.statusNotificationResponseJsonSchema = JSON.parse(
-      fs.readFileSync(
-        path.resolve(
-          path.dirname(fileURLToPath(import.meta.url)),
-          '../../../assets/json-schemas/ocpp/1.6/StatusNotificationResponse.json'
-        ),
-        'utf8'
-      )
-    ) as JSONSchemaType<OCPP16StatusNotificationResponse>;
-    this.meterValuesResponseJsonSchema = JSON.parse(
-      fs.readFileSync(
-        path.resolve(
-          path.dirname(fileURLToPath(import.meta.url)),
-          '../../../assets/json-schemas/ocpp/1.6/MeterValuesResponse.json'
-        ),
-        'utf8'
-      )
-    ) as JSONSchemaType<OCPP16MeterValuesResponse>;
   }
 
   public async responseHandler(
@@ -148,6 +179,18 @@ export default class OCPP16ResponseService extends OCPPResponseService {
         ChargingStationUtils.isRequestCommandSupported(commandName, chargingStation)
       ) {
         try {
+          if (this.jsonSchemas.has(commandName)) {
+            this.validateResponsePayload(
+              chargingStation,
+              commandName,
+              this.jsonSchemas.get(commandName),
+              payload
+            );
+          } else {
+            logger.warn(
+              `${chargingStation.logPrefix()} ${moduleName}.responseHandler: No JSON schema found for command ${commandName} PDU validation`
+            );
+          }
           await this.responseHandlers.get(commandName)(chargingStation, payload, requestPayload);
         } catch (error) {
           logger.error(chargingStation.logPrefix() + ' Handle request response error: %j', error);
@@ -184,12 +227,6 @@ export default class OCPP16ResponseService extends OCPPResponseService {
     chargingStation: ChargingStation,
     payload: OCPP16BootNotificationResponse
   ): void {
-    this.validateResponsePayload(
-      chargingStation,
-      OCPP16RequestCommand.BOOT_NOTIFICATION,
-      this.bootNotificationResponseJsonSchema,
-      payload
-    );
     if (payload.status === OCPP16RegistrationStatus.ACCEPTED) {
       ChargingStationConfigurationUtils.addConfigurationKey(
         chargingStation,
@@ -225,29 +262,11 @@ export default class OCPP16ResponseService extends OCPPResponseService {
     }
   }
 
-  private handleResponseHeartbeat(
-    chargingStation: ChargingStation,
-    payload: OCPP16HeartbeatResponse
-  ): void {
-    this.validateResponsePayload(
-      chargingStation,
-      OCPP16RequestCommand.HEARTBEAT,
-      this.heartbeatResponseJsonSchema,
-      payload
-    );
-  }
-
   private handleResponseAuthorize(
     chargingStation: ChargingStation,
     payload: OCPP16AuthorizeResponse,
     requestPayload: OCPP16AuthorizeRequest
   ): void {
-    this.validateResponsePayload(
-      chargingStation,
-      OCPP16RequestCommand.AUTHORIZE,
-      this.authorizeResponseJsonSchema,
-      payload
-    );
     let authorizeConnectorId: number;
     for (const connectorId of chargingStation.connectors.keys()) {
       if (
@@ -281,12 +300,6 @@ export default class OCPP16ResponseService extends OCPPResponseService {
     payload: OCPP16StartTransactionResponse,
     requestPayload: OCPP16StartTransactionRequest
   ): Promise<void> {
-    this.validateResponsePayload(
-      chargingStation,
-      OCPP16RequestCommand.START_TRANSACTION,
-      this.startTransactionResponseJsonSchema,
-      payload
-    );
     const connectorId = requestPayload.connectorId;
 
     let transactionConnectorId: number;
@@ -496,12 +509,6 @@ export default class OCPP16ResponseService extends OCPPResponseService {
     payload: OCPP16StopTransactionResponse,
     requestPayload: OCPP16StopTransactionRequest
   ): Promise<void> {
-    this.validateResponsePayload(
-      chargingStation,
-      OCPP16RequestCommand.STOP_TRANSACTION,
-      this.stopTransactionResponseJsonSchema,
-      payload
-    );
     const transactionConnectorId = chargingStation.getConnectorIdByTransactionId(
       requestPayload.transactionId
     );
@@ -581,28 +588,4 @@ export default class OCPP16ResponseService extends OCPPResponseService {
       );
     }
   }
-
-  private handleResponseStatusNotification(
-    chargingStation: ChargingStation,
-    payload: OCPP16StatusNotificationResponse
-  ): void {
-    this.validateResponsePayload(
-      chargingStation,
-      OCPP16RequestCommand.STATUS_NOTIFICATION,
-      this.statusNotificationResponseJsonSchema,
-      payload
-    );
-  }
-
-  private handleResponseMeterValues(
-    chargingStation: ChargingStation,
-    payload: OCPP16MeterValuesResponse
-  ): void {
-    this.validateResponsePayload(
-      chargingStation,
-      OCPP16RequestCommand.METER_VALUES,
-      this.meterValuesResponseJsonSchema,
-      payload
-    );
-  }
 }
index 9bf06e935d04c41f95e12ba30b1ad2d8fb7ffa25..74ea7a8d547f8eada5b202b6d178b53f4835544a 100644 (file)
@@ -1,3 +1,7 @@
+import { JSONSchemaType } from 'ajv';
+import Ajv from 'ajv-draft-04';
+import ajvFormats from 'ajv-formats';
+
 import OCPPError from '../../exception/OCPPError';
 import PerformanceStatistics from '../../performance/PerformanceStatistics';
 import { EmptyObject } from '../../types/EmptyObject';
@@ -23,6 +27,7 @@ const moduleName = 'OCPPRequestService';
 
 export default abstract class OCPPRequestService {
   private static instance: OCPPRequestService | null = null;
+  private ajv: Ajv;
 
   private readonly ocppResponseService: OCPPResponseService;
 
@@ -31,6 +36,8 @@ export default abstract class OCPPRequestService {
     this.requestHandler.bind(this);
     this.sendResponse.bind(this);
     this.sendError.bind(this);
+    this.ajv = new Ajv();
+    ajvFormats(this.ajv);
   }
 
   public static getInstance<T extends OCPPRequestService>(
@@ -107,6 +114,31 @@ export default abstract class OCPPRequestService {
     }
   }
 
+  protected validateRequestPayload<T extends JsonType>(
+    chargingStation: ChargingStation,
+    commandName: RequestCommand,
+    schema: JSONSchemaType<T>,
+    payload: T
+  ): boolean {
+    if (!chargingStation.getPayloadSchemaValidation()) {
+      return true;
+    }
+    const validate = this.ajv.compile(schema);
+    if (validate(payload)) {
+      return true;
+    }
+    logger.error(
+      `${chargingStation.logPrefix()} ${moduleName}.validateRequestPayload: Request PDU is invalid: %j`,
+      validate.errors
+    );
+    throw new OCPPError(
+      ErrorType.FORMATION_VIOLATION,
+      'Request PDU is invalid',
+      commandName,
+      JSON.stringify(validate.errors, null, 2)
+    );
+  }
+
   private async internalSendMessage(
     chargingStation: ChargingStation,
     messageId: string,
index 905d972a3bc2348eee28bc6d9d347e4e8ec9e6b7..afef76b6df7360aac48e644dab255e6ae1b73d92 100644 (file)
@@ -52,6 +52,9 @@ export default abstract class OCPPResponseService {
     );
   }
 
+  // eslint-disable-next-line @typescript-eslint/no-empty-function
+  protected emptyResponseHandler() {}
+
   public abstract responseHandler(
     chargingStation: ChargingStation,
     commandName: RequestCommand,