Incoming requests payload validation with JSON schemas (#135)
authorJérôme Benoit <jerome.benoit@sap.com>
Thu, 18 Aug 2022 06:50:52 +0000 (08:50 +0200)
committerGitHub <noreply@github.com>
Thu, 18 Aug 2022 06:50:52 +0000 (08:50 +0200)
27 files changed:
README.md
package-lock.json
package.json
rollup.config.mjs
src/assets/json-schemas/ocpp/1.6/ChangeAvailability.json [new file with mode: 0644]
src/assets/json-schemas/ocpp/1.6/ChangeConfiguration.json [new file with mode: 0644]
src/assets/json-schemas/ocpp/1.6/ClearCache.json [new file with mode: 0644]
src/assets/json-schemas/ocpp/1.6/ClearChargingProfile.json [new file with mode: 0644]
src/assets/json-schemas/ocpp/1.6/GetConfiguration.json [new file with mode: 0644]
src/assets/json-schemas/ocpp/1.6/GetDiagnostics.json [new file with mode: 0644]
src/assets/json-schemas/ocpp/1.6/RemoteStartTransaction.json [new file with mode: 0644]
src/assets/json-schemas/ocpp/1.6/RemoteStopTransaction.json [new file with mode: 0644]
src/assets/json-schemas/ocpp/1.6/Reset.json [new file with mode: 0644]
src/assets/json-schemas/ocpp/1.6/SetChargingProfile.json [new file with mode: 0644]
src/assets/json-schemas/ocpp/1.6/TriggerMessage.json [new file with mode: 0644]
src/assets/json-schemas/ocpp/1.6/UnlockConnector.json [new file with mode: 0644]
src/charging-station/ChargingStation.ts
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/OCPPIncomingRequestService.ts
src/charging-station/ocpp/OCPPRequestService.ts
src/charging-station/ocpp/OCPPResponseService.ts
src/types/ChargingStationTemplate.ts
src/types/ocpp/1.6/Requests.ts
src/types/ocpp/ErrorType.ts
src/types/ocpp/Requests.ts

index 97c3005237fa5d1161e8c3233ebe55869184660a..eae3b3918f8b70e9807d8bf025ac8d0967861874 100644 (file)
--- a/README.md
+++ b/README.md
@@ -159,6 +159,7 @@ But the modifications to test have to be done to the files in the build target d
 | amperageLimitationUnit             | A/cA/dA/mA | A                                                                 | string                                                                                                                             | charging stations amperage limit unit                                                                                                                                                                 |
 | enableStatistics                   | true/false | true                                                              | boolean                                                                                                                            | enable charging stations statistics                                                                                                                                                                   |
 | mayAuthorizeAtRemoteStart          | true/false | true                                                              | boolean                                                                                                                            | always send authorize at remote start transaction when AuthorizeRemoteTxRequests is enabled                                                                                                           |
+| payloadSchemaValidation            | true/false | true                                                              | boolean                                                                                                                            | validate OCPP commands PDU against OCA JSON schemas                                                                                                                                                   |
 | beginEndMeterValues                | true/false | false                                                             | boolean                                                                                                                            | enable Transaction.{Begin,End} MeterValues                                                                                                                                                            |
 | outOfOrderEndMeterValues           | true/false | false                                                             | boolean                                                                                                                            | send Transaction.End MeterValues out of order. Need to relax OCPP specifications strict compliance ('ocppStrictCompliance' parameter)                                                                 |
 | meteringPerTransaction             | true/false | true                                                              | boolean                                                                                                                            | enable metering history on a per transaction basis                                                                                                                                                    |
index 35fa624bc9b68882178b7a1814fdc61eb12494df..432c5d90bec722e104525b0ce7a6f2d61e5c1b41 100644 (file)
@@ -13,6 +13,9 @@
         "@mikro-orm/mariadb": "^5.3.1",
         "@mikro-orm/reflection": "^5.3.1",
         "@mikro-orm/sqlite": "^5.3.1",
+        "ajv": "^8.11.0",
+        "ajv-draft-04": "^1.0.0",
+        "ajv-formats": "^2.1.1",
         "basic-ftp": "^5.0.1",
         "chalk": "^4.1.2",
         "express": "^4.18.1",
       "version": "8.11.0",
       "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.11.0.tgz",
       "integrity": "sha512-wGgprdCvMalC0BztXvitD2hC04YffAvtsUn93JbGXYLAtCUO4xd17mCCZQxUOItiBwZvJScWo8NIvQMQ71rdpg==",
-      "dev": true,
       "dependencies": {
         "fast-deep-equal": "^3.1.1",
         "json-schema-traverse": "^1.0.0",
         "url": "https://github.com/sponsors/epoberezkin"
       }
     },
+    "node_modules/ajv-draft-04": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/ajv-draft-04/-/ajv-draft-04-1.0.0.tgz",
+      "integrity": "sha512-mv00Te6nmYbRp5DCwclxtt7yV/joXJPGS7nM+97GdxvuttCOfgI3K4U25zboyeX0O+myI8ERluxQe5wljMmVIw==",
+      "peerDependencies": {
+        "ajv": "^8.5.0"
+      },
+      "peerDependenciesMeta": {
+        "ajv": {
+          "optional": true
+        }
+      }
+    },
     "node_modules/ajv-formats": {
       "version": "2.1.1",
       "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz",
       "integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==",
-      "dev": true,
       "dependencies": {
         "ajv": "^8.0.0"
       },
     "node_modules/fast-deep-equal": {
       "version": "3.1.3",
       "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
-      "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
-      "dev": true
+      "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="
     },
     "node_modules/fast-diff": {
       "version": "1.2.0",
     "node_modules/json-schema-traverse": {
       "version": "1.0.0",
       "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz",
-      "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==",
-      "dev": true
+      "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug=="
     },
     "node_modules/json-schema-typed": {
       "version": "7.0.3",
       "version": "2.0.2",
       "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz",
       "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==",
-      "dev": true,
       "engines": {
         "node": ">=0.10.0"
       }
       "version": "4.4.1",
       "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz",
       "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==",
-      "dev": true,
       "dependencies": {
         "punycode": "^2.1.0"
       }
       "version": "2.1.1",
       "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz",
       "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==",
-      "dev": true,
       "engines": {
         "node": ">=6"
       }
       "version": "8.11.0",
       "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.11.0.tgz",
       "integrity": "sha512-wGgprdCvMalC0BztXvitD2hC04YffAvtsUn93JbGXYLAtCUO4xd17mCCZQxUOItiBwZvJScWo8NIvQMQ71rdpg==",
-      "dev": true,
       "requires": {
         "fast-deep-equal": "^3.1.1",
         "json-schema-traverse": "^1.0.0",
         "uri-js": "^4.2.2"
       }
     },
+    "ajv-draft-04": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/ajv-draft-04/-/ajv-draft-04-1.0.0.tgz",
+      "integrity": "sha512-mv00Te6nmYbRp5DCwclxtt7yV/joXJPGS7nM+97GdxvuttCOfgI3K4U25zboyeX0O+myI8ERluxQe5wljMmVIw==",
+      "requires": {}
+    },
     "ajv-formats": {
       "version": "2.1.1",
       "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz",
       "integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==",
-      "dev": true,
       "requires": {
         "ajv": "^8.0.0"
       }
     "fast-deep-equal": {
       "version": "3.1.3",
       "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
-      "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
-      "dev": true
+      "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="
     },
     "fast-diff": {
       "version": "1.2.0",
     "json-schema-traverse": {
       "version": "1.0.0",
       "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz",
-      "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==",
-      "dev": true
+      "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug=="
     },
     "json-schema-typed": {
       "version": "7.0.3",
     "require-from-string": {
       "version": "2.0.2",
       "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz",
-      "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==",
-      "dev": true
+      "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw=="
     },
     "require-main-filename": {
       "version": "2.0.0",
       "version": "4.4.1",
       "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz",
       "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==",
-      "dev": true,
       "requires": {
         "punycode": "^2.1.0"
       },
         "punycode": {
           "version": "2.1.1",
           "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz",
-          "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==",
-          "dev": true
+          "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A=="
         }
       }
     },
index e5f5a8bb7885e629f8657911e343ea1581854111..4b2b0514d1d92c2e817d704cbe0fa2d080efb8f9 100644 (file)
@@ -84,6 +84,9 @@
     "@mikro-orm/mariadb": "^5.3.1",
     "@mikro-orm/reflection": "^5.3.1",
     "@mikro-orm/sqlite": "^5.3.1",
+    "ajv": "^8.11.0",
+    "ajv-draft-04": "^1.0.0",
+    "ajv-formats": "^2.1.1",
     "basic-ftp": "^5.0.1",
     "chalk": "^4.1.2",
     "express": "^4.18.1",
index 1cb306ab7d16c050cadf465951004e189f32c089..3ab0886e357ae04aebb05d6e15065cfbdd7414b6 100644 (file)
@@ -33,13 +33,16 @@ export default {
     },
   ],
   external: [
+    '@mikro-orm/core',
+    '@mikro-orm/reflection',
+    'ajv',
+    'ajv-draft-04',
+    'ajv-formats',
     'basic-ftp',
     'chalk',
     'crypto',
     'express',
     'fs',
-    '@mikro-orm/core',
-    '@mikro-orm/reflection',
     'mnemonist/lru-map-with-delete',
     'moment',
     'mongodb',
@@ -51,11 +54,11 @@ export default {
     'tar',
     'url',
     'uuid',
-    'ws',
+    'winston',
     'winston-daily-rotate-file',
     'winston/lib/winston/transports/index.js',
-    'winston',
     'worker_threads',
+    'ws',
   ],
   plugins: [
     json(),
diff --git a/src/assets/json-schemas/ocpp/1.6/ChangeAvailability.json b/src/assets/json-schemas/ocpp/1.6/ChangeAvailability.json
new file mode 100644 (file)
index 0000000..6cd6c45
--- /dev/null
@@ -0,0 +1,18 @@
+{
+  "$schema": "http://json-schema.org/draft-04/schema#",
+  "id": "urn:OCPP:1.6:2019:12:ChangeAvailabilityRequest",
+  "title": "ChangeAvailabilityRequest",
+  "type": "object",
+  "properties": {
+    "connectorId": {
+      "type": "integer"
+    },
+    "type": {
+      "type": "string",
+      "additionalProperties": false,
+      "enum": ["Inoperative", "Operative"]
+    }
+  },
+  "additionalProperties": false,
+  "required": ["connectorId", "type"]
+}
diff --git a/src/assets/json-schemas/ocpp/1.6/ChangeConfiguration.json b/src/assets/json-schemas/ocpp/1.6/ChangeConfiguration.json
new file mode 100644 (file)
index 0000000..607bae1
--- /dev/null
@@ -0,0 +1,18 @@
+{
+  "$schema": "http://json-schema.org/draft-04/schema#",
+  "id": "urn:OCPP:1.6:2019:12:ChangeConfigurationRequest",
+  "title": "ChangeConfigurationRequest",
+  "type": "object",
+  "properties": {
+    "key": {
+      "type": "string",
+      "maxLength": 50
+    },
+    "value": {
+      "type": "string",
+      "maxLength": 500
+    }
+  },
+  "additionalProperties": false,
+  "required": ["key", "value"]
+}
diff --git a/src/assets/json-schemas/ocpp/1.6/ClearCache.json b/src/assets/json-schemas/ocpp/1.6/ClearCache.json
new file mode 100644 (file)
index 0000000..8f3d782
--- /dev/null
@@ -0,0 +1,8 @@
+{
+  "$schema": "http://json-schema.org/draft-04/schema#",
+  "id": "urn:OCPP:1.6:2019:12:ClearCacheRequest",
+  "title": "ClearCacheRequest",
+  "type": "object",
+  "properties": {},
+  "additionalProperties": false
+}
diff --git a/src/assets/json-schemas/ocpp/1.6/ClearChargingProfile.json b/src/assets/json-schemas/ocpp/1.6/ClearChargingProfile.json
new file mode 100644 (file)
index 0000000..f6b1c01
--- /dev/null
@@ -0,0 +1,23 @@
+{
+  "$schema": "http://json-schema.org/draft-04/schema#",
+  "id": "urn:OCPP:1.6:2019:12:ClearChargingProfileRequest",
+  "title": "ClearChargingProfileRequest",
+  "type": "object",
+  "properties": {
+    "id": {
+      "type": "integer"
+    },
+    "connectorId": {
+      "type": "integer"
+    },
+    "chargingProfilePurpose": {
+      "type": "string",
+      "additionalProperties": false,
+      "enum": ["ChargePointMaxProfile", "TxDefaultProfile", "TxProfile"]
+    },
+    "stackLevel": {
+      "type": "integer"
+    }
+  },
+  "additionalProperties": false
+}
diff --git a/src/assets/json-schemas/ocpp/1.6/GetConfiguration.json b/src/assets/json-schemas/ocpp/1.6/GetConfiguration.json
new file mode 100644 (file)
index 0000000..4134be7
--- /dev/null
@@ -0,0 +1,16 @@
+{
+  "$schema": "http://json-schema.org/draft-04/schema#",
+  "id": "urn:OCPP:1.6:2019:12:GetConfigurationRequest",
+  "title": "GetConfigurationRequest",
+  "type": "object",
+  "properties": {
+    "key": {
+      "type": "array",
+      "items": {
+        "type": "string",
+        "maxLength": 50
+      }
+    }
+  },
+  "additionalProperties": false
+}
diff --git a/src/assets/json-schemas/ocpp/1.6/GetDiagnostics.json b/src/assets/json-schemas/ocpp/1.6/GetDiagnostics.json
new file mode 100644 (file)
index 0000000..92c6f58
--- /dev/null
@@ -0,0 +1,28 @@
+{
+  "$schema": "http://json-schema.org/draft-04/schema#",
+  "id": "urn:OCPP:1.6:2019:12:GetDiagnosticsRequest",
+  "title": "GetDiagnosticsRequest",
+  "type": "object",
+  "properties": {
+    "location": {
+      "type": "string",
+      "format": "uri"
+    },
+    "retries": {
+      "type": "integer"
+    },
+    "retryInterval": {
+      "type": "integer"
+    },
+    "startTime": {
+      "type": "string",
+      "format": "date-time"
+    },
+    "stopTime": {
+      "type": "string",
+      "format": "date-time"
+    }
+  },
+  "additionalProperties": false,
+  "required": ["location"]
+}
diff --git a/src/assets/json-schemas/ocpp/1.6/RemoteStartTransaction.json b/src/assets/json-schemas/ocpp/1.6/RemoteStartTransaction.json
new file mode 100644 (file)
index 0000000..7789af4
--- /dev/null
@@ -0,0 +1,105 @@
+{
+  "$schema": "http://json-schema.org/draft-04/schema#",
+  "id": "urn:OCPP:1.6:2019:12:RemoteStartTransactionRequest",
+  "title": "RemoteStartTransactionRequest",
+  "type": "object",
+  "properties": {
+    "connectorId": {
+      "type": "integer"
+    },
+    "idTag": {
+      "type": "string",
+      "maxLength": 20
+    },
+    "chargingProfile": {
+      "type": "object",
+      "properties": {
+        "chargingProfileId": {
+          "type": "integer"
+        },
+        "transactionId": {
+          "type": "integer"
+        },
+        "stackLevel": {
+          "type": "integer"
+        },
+        "chargingProfilePurpose": {
+          "type": "string",
+          "additionalProperties": false,
+          "enum": ["ChargePointMaxProfile", "TxDefaultProfile", "TxProfile"]
+        },
+        "chargingProfileKind": {
+          "type": "string",
+          "additionalProperties": false,
+          "enum": ["Absolute", "Recurring", "Relative"]
+        },
+        "recurrencyKind": {
+          "type": "string",
+          "additionalProperties": false,
+          "enum": ["Daily", "Weekly"]
+        },
+        "validFrom": {
+          "type": "string",
+          "format": "date-time"
+        },
+        "validTo": {
+          "type": "string",
+          "format": "date-time"
+        },
+        "chargingSchedule": {
+          "type": "object",
+          "properties": {
+            "duration": {
+              "type": "integer"
+            },
+            "startSchedule": {
+              "type": "string",
+              "format": "date-time"
+            },
+            "chargingRateUnit": {
+              "type": "string",
+              "additionalProperties": false,
+              "enum": ["A", "W"]
+            },
+            "chargingSchedulePeriod": {
+              "type": "array",
+              "items": {
+                "type": "object",
+                "properties": {
+                  "startPeriod": {
+                    "type": "integer"
+                  },
+                  "limit": {
+                    "type": "number",
+                    "multipleOf": 0.1
+                  },
+                  "numberPhases": {
+                    "type": "integer"
+                  }
+                },
+                "additionalProperties": false,
+                "required": ["startPeriod", "limit"]
+              }
+            },
+            "minChargingRate": {
+              "type": "number",
+              "multipleOf": 0.1
+            }
+          },
+          "additionalProperties": false,
+          "required": ["chargingRateUnit", "chargingSchedulePeriod"]
+        }
+      },
+      "additionalProperties": false,
+      "required": [
+        "chargingProfileId",
+        "stackLevel",
+        "chargingProfilePurpose",
+        "chargingProfileKind",
+        "chargingSchedule"
+      ]
+    }
+  },
+  "additionalProperties": false,
+  "required": ["idTag"]
+}
diff --git a/src/assets/json-schemas/ocpp/1.6/RemoteStopTransaction.json b/src/assets/json-schemas/ocpp/1.6/RemoteStopTransaction.json
new file mode 100644 (file)
index 0000000..a02c118
--- /dev/null
@@ -0,0 +1,13 @@
+{
+  "$schema": "http://json-schema.org/draft-04/schema#",
+  "id": "urn:OCPP:1.6:2019:12:RemoteStopTransactionRequest",
+  "title": "RemoteStopTransactionRequest",
+  "type": "object",
+  "properties": {
+    "transactionId": {
+      "type": "integer"
+    }
+  },
+  "additionalProperties": false,
+  "required": ["transactionId"]
+}
diff --git a/src/assets/json-schemas/ocpp/1.6/Reset.json b/src/assets/json-schemas/ocpp/1.6/Reset.json
new file mode 100644 (file)
index 0000000..c31cf10
--- /dev/null
@@ -0,0 +1,15 @@
+{
+  "$schema": "http://json-schema.org/draft-04/schema#",
+  "id": "urn:OCPP:1.6:2019:12:ResetRequest",
+  "title": "ResetRequest",
+  "type": "object",
+  "properties": {
+    "type": {
+      "type": "string",
+      "additionalProperties": false,
+      "enum": ["Hard", "Soft"]
+    }
+  },
+  "additionalProperties": false,
+  "required": ["type"]
+}
diff --git a/src/assets/json-schemas/ocpp/1.6/SetChargingProfile.json b/src/assets/json-schemas/ocpp/1.6/SetChargingProfile.json
new file mode 100644 (file)
index 0000000..0ba9e85
--- /dev/null
@@ -0,0 +1,101 @@
+{
+  "$schema": "http://json-schema.org/draft-04/schema#",
+  "id": "urn:OCPP:1.6:2019:12:SetChargingProfileRequest",
+  "title": "SetChargingProfileRequest",
+  "type": "object",
+  "properties": {
+    "connectorId": {
+      "type": "integer"
+    },
+    "csChargingProfiles": {
+      "type": "object",
+      "properties": {
+        "chargingProfileId": {
+          "type": "integer"
+        },
+        "transactionId": {
+          "type": "integer"
+        },
+        "stackLevel": {
+          "type": "integer"
+        },
+        "chargingProfilePurpose": {
+          "type": "string",
+          "additionalProperties": false,
+          "enum": ["ChargePointMaxProfile", "TxDefaultProfile", "TxProfile"]
+        },
+        "chargingProfileKind": {
+          "type": "string",
+          "additionalProperties": false,
+          "enum": ["Absolute", "Recurring", "Relative"]
+        },
+        "recurrencyKind": {
+          "type": "string",
+          "additionalProperties": false,
+          "enum": ["Daily", "Weekly"]
+        },
+        "validFrom": {
+          "type": "string",
+          "format": "date-time"
+        },
+        "validTo": {
+          "type": "string",
+          "format": "date-time"
+        },
+        "chargingSchedule": {
+          "type": "object",
+          "properties": {
+            "duration": {
+              "type": "integer"
+            },
+            "startSchedule": {
+              "type": "string",
+              "format": "date-time"
+            },
+            "chargingRateUnit": {
+              "type": "string",
+              "additionalProperties": false,
+              "enum": ["A", "W"]
+            },
+            "chargingSchedulePeriod": {
+              "type": "array",
+              "items": {
+                "type": "object",
+                "properties": {
+                  "startPeriod": {
+                    "type": "integer"
+                  },
+                  "limit": {
+                    "type": "number",
+                    "multipleOf": 0.1
+                  },
+                  "numberPhases": {
+                    "type": "integer"
+                  }
+                },
+                "additionalProperties": false,
+                "required": ["startPeriod", "limit"]
+              }
+            },
+            "minChargingRate": {
+              "type": "number",
+              "multipleOf": 0.1
+            }
+          },
+          "additionalProperties": false,
+          "required": ["chargingRateUnit", "chargingSchedulePeriod"]
+        }
+      },
+      "additionalProperties": false,
+      "required": [
+        "chargingProfileId",
+        "stackLevel",
+        "chargingProfilePurpose",
+        "chargingProfileKind",
+        "chargingSchedule"
+      ]
+    }
+  },
+  "additionalProperties": false,
+  "required": ["connectorId", "csChargingProfiles"]
+}
diff --git a/src/assets/json-schemas/ocpp/1.6/TriggerMessage.json b/src/assets/json-schemas/ocpp/1.6/TriggerMessage.json
new file mode 100644 (file)
index 0000000..a4ef539
--- /dev/null
@@ -0,0 +1,25 @@
+{
+  "$schema": "http://json-schema.org/draft-04/schema#",
+  "id": "urn:OCPP:1.6:2019:12:TriggerMessageRequest",
+  "title": "TriggerMessageRequest",
+  "type": "object",
+  "properties": {
+    "requestedMessage": {
+      "type": "string",
+      "additionalProperties": false,
+      "enum": [
+        "BootNotification",
+        "DiagnosticsStatusNotification",
+        "FirmwareStatusNotification",
+        "Heartbeat",
+        "MeterValues",
+        "StatusNotification"
+      ]
+    },
+    "connectorId": {
+      "type": "integer"
+    }
+  },
+  "additionalProperties": false,
+  "required": ["requestedMessage"]
+}
diff --git a/src/assets/json-schemas/ocpp/1.6/UnlockConnector.json b/src/assets/json-schemas/ocpp/1.6/UnlockConnector.json
new file mode 100644 (file)
index 0000000..483c898
--- /dev/null
@@ -0,0 +1,13 @@
+{
+  "$schema": "http://json-schema.org/draft-04/schema#",
+  "id": "urn:OCPP:1.6:2019:12:UnlockConnectorRequest",
+  "title": "UnlockConnectorRequest",
+  "type": "object",
+  "properties": {
+    "connectorId": {
+      "type": "integer"
+    }
+  },
+  "additionalProperties": false,
+  "required": ["connectorId"]
+}
index d7dcdceec9428543c5957738912b541a61d47ddd..d57dc5921194aff3ba3c8fcfcec23e7085ca86af 100644 (file)
@@ -178,6 +178,10 @@ export default class ChargingStation {
     return this.stationInfo.mayAuthorizeAtRemoteStart ?? true;
   }
 
+  public getPayloadSchemaValidation(): boolean | undefined {
+    return this.stationInfo.payloadSchemaValidation ?? true;
+  }
+
   public getNumberOfPhases(stationInfo?: ChargingStationInfo): number | undefined {
     const localStationInfo: ChargingStationInfo = stationInfo ?? this.stationInfo;
     switch (this.getCurrentOutType(stationInfo)) {
index 6624b9ca01d284cf0b9b1cae06c868f1f2649d66..64314475c8b42ea7ff6e90fd8a854b2d808e7540 100644 (file)
@@ -4,6 +4,7 @@ import fs from 'fs';
 import path from 'path';
 import { URL, fileURLToPath } from 'url';
 
+import { JSONSchemaType } from 'ajv';
 import { Client, FTPResponse } from 'basic-ftp';
 import tar from 'tar';
 
@@ -34,6 +35,7 @@ import {
   MessageTrigger,
   OCPP16AvailabilityType,
   OCPP16BootNotificationRequest,
+  OCPP16ClearCacheRequest,
   OCPP16HeartbeatRequest,
   OCPP16IncomingRequestCommand,
   OCPP16RequestCommand,
@@ -86,6 +88,18 @@ 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>;
 
   public constructor() {
     if (new.target?.name === moduleName) {
@@ -127,6 +141,114 @@ 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>;
   }
 
   public async incomingRequestHandler(
@@ -144,7 +266,7 @@ export default class OCPP16IncomingRequestService extends OCPPIncomingRequestSer
     ) {
       throw new OCPPError(
         ErrorType.SECURITY_ERROR,
-        `${commandName} cannot be issued to handle request payload ${JSON.stringify(
+        `${commandName} cannot be issued to handle request PDU ${JSON.stringify(
           commandPayload,
           null,
           2
@@ -176,7 +298,7 @@ export default class OCPP16IncomingRequestService extends OCPPIncomingRequestSer
         // Throw exception
         throw new OCPPError(
           ErrorType.NOT_IMPLEMENTED,
-          `${commandName} is not implemented to handle request payload ${JSON.stringify(
+          `${commandName} is not implemented to handle request PDU ${JSON.stringify(
             commandPayload,
             null,
             2
@@ -188,7 +310,7 @@ export default class OCPP16IncomingRequestService extends OCPPIncomingRequestSer
     } else {
       throw new OCPPError(
         ErrorType.SECURITY_ERROR,
-        `${commandName} cannot be issued to handle request payload ${JSON.stringify(
+        `${commandName} cannot be issued to handle request PDU ${JSON.stringify(
           commandPayload,
           null,
           2
@@ -211,6 +333,12 @@ 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);
@@ -225,7 +353,16 @@ export default class OCPP16IncomingRequestService extends OCPPIncomingRequestSer
     return Constants.OCPP_RESPONSE_ACCEPTED;
   }
 
-  private handleRequestClearCache(): DefaultResponse {
+  private handleRequestClearCache(
+    chargingStation: ChargingStation,
+    commandPayload: OCPP16ClearCacheRequest
+  ): DefaultResponse {
+    this.validateIncomingRequestPayload(
+      chargingStation,
+      OCPP16IncomingRequestCommand.CLEAR_CACHE,
+      this.clearCacheJsonSchema,
+      commandPayload
+    );
     return Constants.OCPP_RESPONSE_ACCEPTED;
   }
 
@@ -233,6 +370,12 @@ 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(
@@ -292,6 +435,12 @@ 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)) {
@@ -341,23 +490,12 @@ export default class OCPP16IncomingRequestService extends OCPPIncomingRequestSer
     chargingStation: ChargingStation,
     commandPayload: ChangeConfigurationRequest
   ): ChangeConfigurationResponse {
-    // JSON request fields type sanity check
-    if (!Utils.isString(commandPayload.key)) {
-      logger.error(
-        `${chargingStation.logPrefix()} ${
-          OCPP16IncomingRequestCommand.CHANGE_CONFIGURATION
-        } request key field is not a string:`,
-        commandPayload
-      );
-    }
-    if (!Utils.isString(commandPayload.value)) {
-      logger.error(
-        `${chargingStation.logPrefix()} ${
-          OCPP16IncomingRequestCommand.CHANGE_CONFIGURATION
-        } request value field is not a string:`,
-        commandPayload
-      );
-    }
+    this.validateIncomingRequestPayload(
+      chargingStation,
+      OCPP16IncomingRequestCommand.CHANGE_CONFIGURATION,
+      this.changeConfigurationJsonSchema,
+      commandPayload
+    );
     const keyToChange = ChargingStationConfigurationUtils.getConfigurationKey(
       chargingStation,
       commandPayload.key,
@@ -412,6 +550,12 @@ 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,
@@ -461,6 +605,12 @@ 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,
@@ -542,6 +692,12 @@ 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(
@@ -603,6 +759,12 @@ 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) {
@@ -814,6 +976,12 @@ 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 (
@@ -872,6 +1040,12 @@ 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,
@@ -992,6 +1166,12 @@ 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 aa0ca4f16bf5a7230364e9ceb2b025f8ebbf240b..8638ec0519ec8bfae0fa4c812cb72edf2158146b 100644 (file)
@@ -92,8 +92,8 @@ export default class OCPP16RequestService extends OCPPRequestService {
         // Sanity check
         if (!Array.isArray(commandParams?.meterValue)) {
           throw new OCPPError(
-            ErrorType.TYPERAINT_VIOLATION,
-            `${moduleName}.buildRequestPayload ${commandName}: Invalid array type for meterValue payload field`,
+            ErrorType.TYPE_CONSTRAINT_VIOLATION,
+            `${moduleName}.buildRequestPayload ${commandName}: Invalid array type for meterValue PDU field`,
             commandName,
             commandParams
           );
index 9093187d38a23c10f527932eca65702cc206acac..7eef0270865e752ed836f45051b97b3a8999f423 100644 (file)
@@ -80,7 +80,7 @@ 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
@@ -92,7 +92,7 @@ export default class OCPP16ResponseService extends OCPPResponseService {
     } 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
index 5955b6c9d8fa19859eaf3d05dbaea9fcc6aae1cb..d55a745353db4e5939ad3ec1264b3ae407d8322c 100644 (file)
@@ -1,14 +1,24 @@
+import { JSONSchemaType } from 'ajv';
+import Ajv from 'ajv-draft-04';
+import ajvFormats from 'ajv-formats';
+
+import OCPPError from '../../exception/OCPPError';
 import { HandleErrorParams } from '../../types/Error';
 import { JsonType } from '../../types/JsonType';
+import { ErrorType } from '../../types/ocpp/ErrorType';
 import { IncomingRequestCommand } from '../../types/ocpp/Requests';
 import logger from '../../utils/Logger';
 import type ChargingStation from '../ChargingStation';
 
+const moduleName = 'OCPPIncomingRequestService';
+
 export default abstract class OCPPIncomingRequestService {
   private static instance: OCPPIncomingRequestService | null = null;
+  private ajv: Ajv;
 
   protected constructor() {
-    // This is intentional
+    this.ajv = new Ajv();
+    ajvFormats(this.ajv);
   }
 
   public static getInstance<T extends OCPPIncomingRequestService>(this: new () => T): T {
@@ -25,7 +35,7 @@ export default abstract class OCPPIncomingRequestService {
     params: HandleErrorParams<T> = { throwError: true }
   ): T {
     logger.error(
-      chargingStation.logPrefix() + ' Incoming request command %s error: %j',
+      `${chargingStation.logPrefix()} ${moduleName}.handleIncomingRequestError: Incoming request command %s error: %j`,
       commandName,
       error
     );
@@ -40,6 +50,31 @@ export default abstract class OCPPIncomingRequestService {
     }
   }
 
+  protected validateIncomingRequestPayload<T extends JsonType>(
+    chargingStation: ChargingStation,
+    commandName: IncomingRequestCommand,
+    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}.validateIncomingRequestPayload: Incoming request PDU is invalid: %j`,
+      validate.errors
+    );
+    throw new OCPPError(
+      ErrorType.FORMATION_VIOLATION,
+      'Incoming request PDU is invalid',
+      commandName,
+      JSON.stringify(validate.errors, null, 2)
+    );
+  }
+
   public abstract incomingRequestHandler(
     chargingStation: ChargingStation,
     messageId: string,
index 6695fcbc010e451a85740966da781f8fee392165..9bf06e935d04c41f95e12ba30b1ad2d8fb7ffa25 100644 (file)
@@ -19,6 +19,8 @@ import Utils from '../../utils/Utils';
 import type ChargingStation from '../ChargingStation';
 import type OCPPResponseService from './OCPPResponseService';
 
+const moduleName = 'OCPPRequestService';
+
 export default abstract class OCPPRequestService {
   private static instance: OCPPRequestService | null = null;
 
@@ -253,7 +255,7 @@ export default abstract class OCPPRequestService {
     }
     throw new OCPPError(
       ErrorType.SECURITY_ERROR,
-      `Cannot send command ${commandName} payload when the charging station is in ${chargingStation.getRegistrationStatus()} state on the central server`,
+      `Cannot send command ${commandName} PDU when the charging station is in ${chargingStation.getRegistrationStatus()} state on the central server`,
       commandName
     );
   }
index ffcb888fe422bf35bb24549205ff4dfbc3ba09f5..e2cad6451d1c08959e19fb9d33a6e62e3347cd87 100644 (file)
@@ -2,6 +2,8 @@ import { JsonType } from '../../types/JsonType';
 import { RequestCommand } from '../../types/ocpp/Requests';
 import type ChargingStation from '../ChargingStation';
 
+const moduleName = 'OCPPResponseService';
+
 export default abstract class OCPPResponseService {
   private static instance: OCPPResponseService | null = null;
 
index 6acca89cb449cf2baba2e05ef5e579c3c80acba8..71d84bc80056a3b477ca3f7ce03c94508ef70a72 100644 (file)
@@ -81,7 +81,8 @@ export default interface ChargingStationTemplate {
   reconnectExponentialDelay?: boolean;
   registrationMaxRetries?: number;
   enableStatistics?: boolean;
-  mayAuthorizeAtRemoteStart: boolean;
+  mayAuthorizeAtRemoteStart?: boolean;
+  payloadSchemaValidation?: boolean;
   amperageLimitationOcppKey?: string;
   amperageLimitationUnit?: AmpereUnits;
   beginEndMeterValues?: boolean;
index 8eb0657e1570f42e06e346aca7c2d63918037e9f..145947aec338ba0d6b557b4954a51102d73be4a9 100644 (file)
@@ -17,21 +17,6 @@ export enum OCPP16RequestCommand {
   DIAGNOSTICS_STATUS_NOTIFICATION = 'DiagnosticsStatusNotification',
 }
 
-export enum OCPP16IncomingRequestCommand {
-  RESET = 'Reset',
-  CLEAR_CACHE = 'ClearCache',
-  CHANGE_AVAILABILITY = 'ChangeAvailability',
-  UNLOCK_CONNECTOR = 'UnlockConnector',
-  GET_CONFIGURATION = 'GetConfiguration',
-  CHANGE_CONFIGURATION = 'ChangeConfiguration',
-  SET_CHARGING_PROFILE = 'SetChargingProfile',
-  CLEAR_CHARGING_PROFILE = 'ClearChargingProfile',
-  REMOTE_START_TRANSACTION = 'RemoteStartTransaction',
-  REMOTE_STOP_TRANSACTION = 'RemoteStopTransaction',
-  GET_DIAGNOSTICS = 'GetDiagnostics',
-  TRIGGER_MESSAGE = 'TriggerMessage',
-}
-
 export type OCPP16HeartbeatRequest = EmptyObject;
 
 export interface OCPP16BootNotificationRequest extends JsonObject {
@@ -56,6 +41,23 @@ export interface OCPP16StatusNotificationRequest extends JsonObject {
   vendorErrorCode?: string;
 }
 
+export enum OCPP16IncomingRequestCommand {
+  RESET = 'Reset',
+  CLEAR_CACHE = 'ClearCache',
+  CHANGE_AVAILABILITY = 'ChangeAvailability',
+  UNLOCK_CONNECTOR = 'UnlockConnector',
+  GET_CONFIGURATION = 'GetConfiguration',
+  CHANGE_CONFIGURATION = 'ChangeConfiguration',
+  SET_CHARGING_PROFILE = 'SetChargingProfile',
+  CLEAR_CHARGING_PROFILE = 'ClearChargingProfile',
+  REMOTE_START_TRANSACTION = 'RemoteStartTransaction',
+  REMOTE_STOP_TRANSACTION = 'RemoteStopTransaction',
+  GET_DIAGNOSTICS = 'GetDiagnostics',
+  TRIGGER_MESSAGE = 'TriggerMessage',
+}
+
+export type OCPP16ClearCacheRequest = EmptyObject;
+
 export interface ChangeConfigurationRequest extends JsonObject {
   key: string | OCPP16StandardParametersKey;
   value: string;
index 4dd459d7bdc680077a7798d04fd8fe072bc7b4cb..e8762380b5872b9cddebc3d33f0f3075170dd834 100644 (file)
@@ -11,12 +11,13 @@ export enum ErrorType {
   SECURITY_ERROR = 'SecurityError',
   // Payload for Action is syntactically incorrect or not conform the PDU structure for Action
   FORMATION_VIOLATION = 'FormationViolation',
+  FORMAT_VIOLATION = 'FormatViolation',
   // Payload is syntactically correct but at least one field contains an invalid value
-  PROPERTY_RAINT_VIOLATION = 'PropertyraintViolation',
-  // Payload for Action is syntactically correct but at least one of the fields violates occurrence raints
-  OCCURRENCE_RAINT_VIOLATION = 'OccurrenceraintViolation',
-  // Payload for Action is syntactically correct but at least one of the fields violates data type raints (e.g. "somestring" = 12)
-  TYPERAINT_VIOLATION = 'TyperaintViolation',
+  PROPERTY_CONSTRAINT_VIOLATION = 'PropertyConstraintViolation',
+  // Payload for Action is syntactically correct but at least one of the fields violates occurrence constraints
+  OCCURRENCE_CONSTRAINT_VIOLATION = 'OccurrenceConstraintViolation',
+  // Payload for Action is syntactically correct but at least one of the fields violates data type constraints (e.g. "somestring" = 12)
+  TYPE_CONSTRAINT_VIOLATION = 'TypeConstraintViolation',
   // Any other error not covered by the previous ones
   GENERIC_ERROR = 'GenericError',
 }
index e931e83fcbbee1a4ea8a7161a8e00bd823c01678..ed9dac1a71fe2d8de071adb90093a945de149eff 100644 (file)
@@ -13,8 +13,25 @@ import {
 } from './1.6/Requests';
 import { MessageType } from './MessageType';
 
+export type RequestCommand = OCPP16RequestCommand;
+
+export const RequestCommand = {
+  ...OCPP16RequestCommand,
+};
+
 export type OutgoingRequest = [MessageType.CALL_MESSAGE, string, RequestCommand, JsonType];
 
+export interface RequestParams {
+  skipBufferingOnError?: boolean;
+  triggerMessage?: boolean;
+}
+
+export type IncomingRequestCommand = OCPP16IncomingRequestCommand;
+
+export const IncomingRequestCommand = {
+  ...OCPP16IncomingRequestCommand,
+};
+
 export type IncomingRequest = [MessageType.CALL_MESSAGE, string, IncomingRequestCommand, JsonType];
 
 export type CachedRequest = [
@@ -24,18 +41,6 @@ export type CachedRequest = [
   JsonType
 ];
 
-export type IncomingRequestHandler = (
-  chargingStation: ChargingStation,
-  commandPayload: JsonType
-) => JsonType | Promise<JsonType>;
-
-export type ResponseType = JsonType | OCPPError;
-
-export interface RequestParams {
-  skipBufferingOnError?: boolean;
-  triggerMessage?: boolean;
-}
-
 export type BootNotificationRequest = OCPP16BootNotificationRequest;
 
 export type HeartbeatRequest = OCPP16HeartbeatRequest;
@@ -44,26 +49,21 @@ export type StatusNotificationRequest = OCPP16StatusNotificationRequest;
 
 export type MeterValuesRequest = OCPP16MeterValuesRequest;
 
+export type IncomingRequestHandler = (
+  chargingStation: ChargingStation,
+  commandPayload: JsonType
+) => JsonType | Promise<JsonType>;
+
 export type AvailabilityType = OCPP16AvailabilityType;
 
 export const AvailabilityType = {
   ...OCPP16AvailabilityType,
 };
 
-export type RequestCommand = OCPP16RequestCommand;
-
-export const RequestCommand = {
-  ...OCPP16RequestCommand,
-};
-
-export type IncomingRequestCommand = OCPP16IncomingRequestCommand;
-
-export const IncomingRequestCommand = {
-  ...OCPP16IncomingRequestCommand,
-};
-
 export type DiagnosticsStatus = OCPP16DiagnosticsStatus;
 
 export const DiagnosticsStatus = {
   ...OCPP16DiagnosticsStatus,
 };
+
+export type ResponseType = JsonType | OCPPError;