From e3018bc4b27b43106073e4c4cda031cc37715027 Mon Sep 17 00:00:00 2001 From: =?utf8?q?J=C3=A9r=C3=B4me=20Benoit?= Date: Thu, 18 Aug 2022 08:50:52 +0200 Subject: [PATCH] Incoming requests payload validation with JSON schemas (#135) --- README.md | 1 + package-lock.json | 48 ++-- package.json | 3 + rollup.config.mjs | 11 +- .../ocpp/1.6/ChangeAvailability.json | 18 ++ .../ocpp/1.6/ChangeConfiguration.json | 18 ++ .../json-schemas/ocpp/1.6/ClearCache.json | 8 + .../ocpp/1.6/ClearChargingProfile.json | 23 ++ .../ocpp/1.6/GetConfiguration.json | 16 ++ .../json-schemas/ocpp/1.6/GetDiagnostics.json | 28 +++ .../ocpp/1.6/RemoteStartTransaction.json | 105 +++++++++ .../ocpp/1.6/RemoteStopTransaction.json | 13 + src/assets/json-schemas/ocpp/1.6/Reset.json | 15 ++ .../ocpp/1.6/SetChargingProfile.json | 101 ++++++++ .../json-schemas/ocpp/1.6/TriggerMessage.json | 25 ++ .../ocpp/1.6/UnlockConnector.json | 13 + src/charging-station/ChargingStation.ts | 4 + .../ocpp/1.6/OCPP16IncomingRequestService.ts | 222 ++++++++++++++++-- .../ocpp/1.6/OCPP16RequestService.ts | 4 +- .../ocpp/1.6/OCPP16ResponseService.ts | 4 +- .../ocpp/OCPPIncomingRequestService.ts | 39 ++- .../ocpp/OCPPRequestService.ts | 4 +- .../ocpp/OCPPResponseService.ts | 2 + src/types/ChargingStationTemplate.ts | 3 +- src/types/ocpp/1.6/Requests.ts | 32 +-- src/types/ocpp/ErrorType.ts | 11 +- src/types/ocpp/Requests.ts | 48 ++-- 27 files changed, 722 insertions(+), 97 deletions(-) create mode 100644 src/assets/json-schemas/ocpp/1.6/ChangeAvailability.json create mode 100644 src/assets/json-schemas/ocpp/1.6/ChangeConfiguration.json create mode 100644 src/assets/json-schemas/ocpp/1.6/ClearCache.json create mode 100644 src/assets/json-schemas/ocpp/1.6/ClearChargingProfile.json create mode 100644 src/assets/json-schemas/ocpp/1.6/GetConfiguration.json create mode 100644 src/assets/json-schemas/ocpp/1.6/GetDiagnostics.json create mode 100644 src/assets/json-schemas/ocpp/1.6/RemoteStartTransaction.json create mode 100644 src/assets/json-schemas/ocpp/1.6/RemoteStopTransaction.json create mode 100644 src/assets/json-schemas/ocpp/1.6/Reset.json create mode 100644 src/assets/json-schemas/ocpp/1.6/SetChargingProfile.json create mode 100644 src/assets/json-schemas/ocpp/1.6/TriggerMessage.json create mode 100644 src/assets/json-schemas/ocpp/1.6/UnlockConnector.json diff --git a/README.md b/README.md index 97c30052..eae3b391 100644 --- 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 | diff --git a/package-lock.json b/package-lock.json index 35fa624b..432c5d90 100644 --- a/package-lock.json +++ b/package-lock.json @@ -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", @@ -2535,7 +2538,6 @@ "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", @@ -2547,11 +2549,23 @@ "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" }, @@ -6688,8 +6702,7 @@ "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", @@ -9295,8 +9308,7 @@ "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", @@ -14366,7 +14378,6 @@ "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" } @@ -16991,7 +17002,6 @@ "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" } @@ -17000,7 +17010,6 @@ "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" } @@ -19734,7 +19743,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", @@ -19742,11 +19750,16 @@ "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" } @@ -23081,8 +23094,7 @@ "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", @@ -25085,8 +25097,7 @@ "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", @@ -28922,8 +28933,7 @@ "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", @@ -30982,7 +30992,6 @@ "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" }, @@ -30990,8 +30999,7 @@ "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==" } } }, diff --git a/package.json b/package.json index e5f5a8bb..4b2b0514 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/rollup.config.mjs b/rollup.config.mjs index 1cb306ab..3ab0886e 100644 --- a/rollup.config.mjs +++ b/rollup.config.mjs @@ -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 index 00000000..6cd6c456 --- /dev/null +++ b/src/assets/json-schemas/ocpp/1.6/ChangeAvailability.json @@ -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 index 00000000..607bae16 --- /dev/null +++ b/src/assets/json-schemas/ocpp/1.6/ChangeConfiguration.json @@ -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 index 00000000..8f3d7821 --- /dev/null +++ b/src/assets/json-schemas/ocpp/1.6/ClearCache.json @@ -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 index 00000000..f6b1c01e --- /dev/null +++ b/src/assets/json-schemas/ocpp/1.6/ClearChargingProfile.json @@ -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 index 00000000..4134be7d --- /dev/null +++ b/src/assets/json-schemas/ocpp/1.6/GetConfiguration.json @@ -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 index 00000000..92c6f585 --- /dev/null +++ b/src/assets/json-schemas/ocpp/1.6/GetDiagnostics.json @@ -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 index 00000000..7789af4b --- /dev/null +++ b/src/assets/json-schemas/ocpp/1.6/RemoteStartTransaction.json @@ -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 index 00000000..a02c1186 --- /dev/null +++ b/src/assets/json-schemas/ocpp/1.6/RemoteStopTransaction.json @@ -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 index 00000000..c31cf109 --- /dev/null +++ b/src/assets/json-schemas/ocpp/1.6/Reset.json @@ -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 index 00000000..0ba9e853 --- /dev/null +++ b/src/assets/json-schemas/ocpp/1.6/SetChargingProfile.json @@ -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 index 00000000..a4ef5399 --- /dev/null +++ b/src/assets/json-schemas/ocpp/1.6/TriggerMessage.json @@ -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 index 00000000..483c898d --- /dev/null +++ b/src/assets/json-schemas/ocpp/1.6/UnlockConnector.json @@ -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"] +} diff --git a/src/charging-station/ChargingStation.ts b/src/charging-station/ChargingStation.ts index d7dcdcee..d57dc592 100644 --- a/src/charging-station/ChargingStation.ts +++ b/src/charging-station/ChargingStation.ts @@ -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)) { diff --git a/src/charging-station/ocpp/1.6/OCPP16IncomingRequestService.ts b/src/charging-station/ocpp/1.6/OCPP16IncomingRequestService.ts index 6624b9ca..64314475 100644 --- a/src/charging-station/ocpp/1.6/OCPP16IncomingRequestService.ts +++ b/src/charging-station/ocpp/1.6/OCPP16IncomingRequestService.ts @@ -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; + private resetJsonSchema: JSONSchemaType; + private clearCacheJsonSchema: JSONSchemaType; + private getConfigurationJsonSchema: JSONSchemaType; + private changeConfigurationJsonSchema: JSONSchemaType; + private unlockConnectorJsonSchema: JSONSchemaType; + private getDiagnosticsJsonSchema: JSONSchemaType; + private setChargingProfileJsonSchema: JSONSchemaType; + private clearChargingProfileJsonSchema: JSONSchemaType; + private changeAvailabilityJsonSchema: JSONSchemaType; + private remoteStartTransactionJsonSchema: JSONSchemaType; + private remoteStopTransactionJsonSchema: JSONSchemaType; + private triggerMessageJsonSchema: JSONSchemaType; 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; + 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; + 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; + 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; + 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; + 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; + 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; + 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; + 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; + 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; + 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; + 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; } 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 => { 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 { + 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 { + 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 { + 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 { + 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 { + 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, diff --git a/src/charging-station/ocpp/1.6/OCPP16RequestService.ts b/src/charging-station/ocpp/1.6/OCPP16RequestService.ts index aa0ca4f1..8638ec05 100644 --- a/src/charging-station/ocpp/1.6/OCPP16RequestService.ts +++ b/src/charging-station/ocpp/1.6/OCPP16RequestService.ts @@ -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 ); diff --git a/src/charging-station/ocpp/1.6/OCPP16ResponseService.ts b/src/charging-station/ocpp/1.6/OCPP16ResponseService.ts index 9093187d..7eef0270 100644 --- a/src/charging-station/ocpp/1.6/OCPP16ResponseService.ts +++ b/src/charging-station/ocpp/1.6/OCPP16ResponseService.ts @@ -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 diff --git a/src/charging-station/ocpp/OCPPIncomingRequestService.ts b/src/charging-station/ocpp/OCPPIncomingRequestService.ts index 5955b6c9..d55a7453 100644 --- a/src/charging-station/ocpp/OCPPIncomingRequestService.ts +++ b/src/charging-station/ocpp/OCPPIncomingRequestService.ts @@ -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(this: new () => T): T { @@ -25,7 +35,7 @@ export default abstract class OCPPIncomingRequestService { params: HandleErrorParams = { 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( + chargingStation: ChargingStation, + commandName: IncomingRequestCommand, + schema: JSONSchemaType, + 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, diff --git a/src/charging-station/ocpp/OCPPRequestService.ts b/src/charging-station/ocpp/OCPPRequestService.ts index 6695fcbc..9bf06e93 100644 --- a/src/charging-station/ocpp/OCPPRequestService.ts +++ b/src/charging-station/ocpp/OCPPRequestService.ts @@ -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 ); } diff --git a/src/charging-station/ocpp/OCPPResponseService.ts b/src/charging-station/ocpp/OCPPResponseService.ts index ffcb888f..e2cad645 100644 --- a/src/charging-station/ocpp/OCPPResponseService.ts +++ b/src/charging-station/ocpp/OCPPResponseService.ts @@ -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; diff --git a/src/types/ChargingStationTemplate.ts b/src/types/ChargingStationTemplate.ts index 6acca89c..71d84bc8 100644 --- a/src/types/ChargingStationTemplate.ts +++ b/src/types/ChargingStationTemplate.ts @@ -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; diff --git a/src/types/ocpp/1.6/Requests.ts b/src/types/ocpp/1.6/Requests.ts index 8eb0657e..145947ae 100644 --- a/src/types/ocpp/1.6/Requests.ts +++ b/src/types/ocpp/1.6/Requests.ts @@ -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; diff --git a/src/types/ocpp/ErrorType.ts b/src/types/ocpp/ErrorType.ts index 4dd459d7..e8762380 100644 --- a/src/types/ocpp/ErrorType.ts +++ b/src/types/ocpp/ErrorType.ts @@ -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', } diff --git a/src/types/ocpp/Requests.ts b/src/types/ocpp/Requests.ts index e931e83f..ed9dac1a 100644 --- a/src/types/ocpp/Requests.ts +++ b/src/types/ocpp/Requests.ts @@ -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; - -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; + 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; -- 2.34.1