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