From 4598878063d62259a6046a870dfa455b9d0027ca Mon Sep 17 00:00:00 2001 From: =?utf8?q?J=C3=A9r=C3=B4me=20Benoit?= Date: Sat, 7 Jan 2023 17:12:43 +0100 Subject: [PATCH] Fixes to OCPP command payload validation: MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit + Fix multipleOf precision + Convert to ISO 8601 string date object in payload before validation Signed-off-by: Jérôme Benoit --- src/charging-station/ChargingStation.ts | 2 +- .../ocpp/1.6/OCPP16IncomingRequestService.ts | 2 +- .../ocpp/1.6/OCPP16RequestService.ts | 34 +++++++------------ .../ocpp/1.6/OCPP16ResponseService.ts | 2 +- .../ocpp/2.0/OCPP20IncomingRequestService.ts | 2 +- .../ocpp/2.0/OCPP20RequestService.ts | 34 +++++++------------ .../ocpp/2.0/OCPP20ResponseService.ts | 2 +- .../ocpp/OCPPIncomingRequestService.ts | 6 ++-- .../ocpp/OCPPRequestService.ts | 31 ++++++++++++++--- .../ocpp/OCPPResponseService.ts | 6 ++-- 10 files changed, 66 insertions(+), 55 deletions(-) diff --git a/src/charging-station/ChargingStation.ts b/src/charging-station/ChargingStation.ts index c3c984f8..587ca02f 100644 --- a/src/charging-station/ChargingStation.ts +++ b/src/charging-station/ChargingStation.ts @@ -1780,7 +1780,7 @@ export default class ChargingStation { logger.error( `${this.logPrefix()} Charging profile id ${ matchingChargingProfile.chargingProfileId - } limit is greater than connector id ${connectorId} maximum, dump charging profiles' stack: %j`, + } limit ${limit} is greater than connector id ${connectorId} maximum ${connectorMaximumPower}, dump charging profiles' stack: %j`, this.getConnectorStatus(connectorId).chargingProfiles ); limit = connectorMaximumPower; diff --git a/src/charging-station/ocpp/1.6/OCPP16IncomingRequestService.ts b/src/charging-station/ocpp/1.6/OCPP16IncomingRequestService.ts index 377405f4..92bd399c 100644 --- a/src/charging-station/ocpp/1.6/OCPP16IncomingRequestService.ts +++ b/src/charging-station/ocpp/1.6/OCPP16IncomingRequestService.ts @@ -382,7 +382,7 @@ export default class OCPP16IncomingRequestService extends OCPPIncomingRequestSer commandName: OCPP16IncomingRequestCommand, commandPayload: JsonType ): boolean { - if (this.jsonSchemas.has(commandName)) { + if (this.jsonSchemas.has(commandName) === true) { return this.validateIncomingRequestPayload( chargingStation, commandName, diff --git a/src/charging-station/ocpp/1.6/OCPP16RequestService.ts b/src/charging-station/ocpp/1.6/OCPP16RequestService.ts index f28f5f94..65866409 100644 --- a/src/charging-station/ocpp/1.6/OCPP16RequestService.ts +++ b/src/charging-station/ocpp/1.6/OCPP16RequestService.ts @@ -154,7 +154,6 @@ export default class OCPP16RequestService extends OCPPRequestService { ], ]); this.buildRequestPayload.bind(this); - this.validatePayload.bind(this); } public async requestHandler( @@ -169,7 +168,6 @@ export default class OCPP16RequestService extends OCPPRequestService { commandName, commandParams ); - this.validatePayload(chargingStation, commandName, requestPayload); return (await this.sendMessage( chargingStation, Utils.generateUUID(), @@ -187,6 +185,19 @@ export default class OCPP16RequestService extends OCPPRequestService { ); } + protected getRequestPayloadValidationSchema( + chargingStation: ChargingStation, + commandName: OCPP16RequestCommand + ): JSONSchemaType | false { + if (this.jsonSchemas.has(commandName) === true) { + return this.jsonSchemas.get(commandName); + } + logger.warn( + `${chargingStation.logPrefix()} ${moduleName}.getPayloadValidationSchema: No JSON schema found for command ${commandName} PDU validation` + ); + return false; + } + private buildRequestPayload( chargingStation: ChargingStation, commandName: OCPP16RequestCommand, @@ -295,23 +306,4 @@ export default class OCPP16RequestService extends OCPPRequestService { ); } } - - private validatePayload( - chargingStation: ChargingStation, - commandName: OCPP16RequestCommand, - requestPayload: Request - ): boolean { - if (this.jsonSchemas.has(commandName)) { - return this.validateRequestPayload( - chargingStation, - commandName, - this.jsonSchemas.get(commandName), - requestPayload - ); - } - logger.warn( - `${chargingStation.logPrefix()} ${moduleName}.validatePayload: No JSON schema found for command ${commandName} PDU validation` - ); - return false; - } } diff --git a/src/charging-station/ocpp/1.6/OCPP16ResponseService.ts b/src/charging-station/ocpp/1.6/OCPP16ResponseService.ts index 7898ce6e..65cbefd3 100644 --- a/src/charging-station/ocpp/1.6/OCPP16ResponseService.ts +++ b/src/charging-station/ocpp/1.6/OCPP16ResponseService.ts @@ -238,7 +238,7 @@ export default class OCPP16ResponseService extends OCPPResponseService { commandName: OCPP16RequestCommand, payload: JsonType ): boolean { - if (this.jsonSchemas.has(commandName)) { + if (this.jsonSchemas.has(commandName) === true) { return this.validateResponsePayload( chargingStation, commandName, diff --git a/src/charging-station/ocpp/2.0/OCPP20IncomingRequestService.ts b/src/charging-station/ocpp/2.0/OCPP20IncomingRequestService.ts index 890d28d1..96a66f1a 100644 --- a/src/charging-station/ocpp/2.0/OCPP20IncomingRequestService.ts +++ b/src/charging-station/ocpp/2.0/OCPP20IncomingRequestService.ts @@ -141,7 +141,7 @@ export default class OCPP20IncomingRequestService extends OCPPIncomingRequestSer commandName: OCPP20IncomingRequestCommand, commandPayload: JsonType ): boolean { - if (this.jsonSchemas.has(commandName)) { + if (this.jsonSchemas.has(commandName) === true) { return this.validateIncomingRequestPayload( chargingStation, commandName, diff --git a/src/charging-station/ocpp/2.0/OCPP20RequestService.ts b/src/charging-station/ocpp/2.0/OCPP20RequestService.ts index e23b1e76..eb98bd15 100644 --- a/src/charging-station/ocpp/2.0/OCPP20RequestService.ts +++ b/src/charging-station/ocpp/2.0/OCPP20RequestService.ts @@ -47,7 +47,6 @@ export default class OCPP20RequestService extends OCPPRequestService { ], ]); this.buildRequestPayload.bind(this); - this.validatePayload.bind(this); } public async requestHandler( @@ -62,7 +61,6 @@ export default class OCPP20RequestService extends OCPPRequestService { commandName, commandParams ); - this.validatePayload(chargingStation, commandName, requestPayload); return (await this.sendMessage( chargingStation, Utils.generateUUID(), @@ -80,6 +78,19 @@ export default class OCPP20RequestService extends OCPPRequestService { ); } + protected getRequestPayloadValidationSchema( + chargingStation: ChargingStation, + commandName: OCPP20RequestCommand + ): JSONSchemaType | false { + if (this.jsonSchemas.has(commandName) === true) { + return this.jsonSchemas.get(commandName); + } + logger.warn( + `${chargingStation.logPrefix()} ${moduleName}.getPayloadValidationSchema: No JSON schema found for command ${commandName} PDU validation` + ); + return false; + } + private buildRequestPayload( chargingStation: ChargingStation, commandName: OCPP20RequestCommand, @@ -121,23 +132,4 @@ export default class OCPP20RequestService extends OCPPRequestService { ); } } - - private validatePayload( - chargingStation: ChargingStation, - commandName: OCPP20RequestCommand, - requestPayload: Request - ): boolean { - if (this.jsonSchemas.has(commandName)) { - return this.validateRequestPayload( - chargingStation, - commandName, - this.jsonSchemas.get(commandName), - requestPayload - ); - } - logger.warn( - `${chargingStation.logPrefix()} ${moduleName}.validatePayload: No JSON schema found for command ${commandName} PDU validation` - ); - return false; - } } diff --git a/src/charging-station/ocpp/2.0/OCPP20ResponseService.ts b/src/charging-station/ocpp/2.0/OCPP20ResponseService.ts index d4636f6f..38b8c247 100644 --- a/src/charging-station/ocpp/2.0/OCPP20ResponseService.ts +++ b/src/charging-station/ocpp/2.0/OCPP20ResponseService.ts @@ -105,7 +105,7 @@ export default class OCPP20ResponseService extends OCPPResponseService { commandName: OCPP20RequestCommand, payload: JsonType ): boolean { - if (this.jsonSchemas.has(commandName)) { + if (this.jsonSchemas.has(commandName) === true) { return this.validateResponsePayload( chargingStation, commandName, diff --git a/src/charging-station/ocpp/OCPPIncomingRequestService.ts b/src/charging-station/ocpp/OCPPIncomingRequestService.ts index 6015d4d9..952e740e 100644 --- a/src/charging-station/ocpp/OCPPIncomingRequestService.ts +++ b/src/charging-station/ocpp/OCPPIncomingRequestService.ts @@ -22,7 +22,9 @@ export default abstract class OCPPIncomingRequestService { protected constructor(version: OCPPVersion) { this.version = version; - this.ajv = new Ajv(); + this.ajv = new Ajv({ + multipleOfPrecision: 2, + }); ajvFormats(this.ajv); this.asyncResource = new AsyncResource(moduleName); this.incomingRequestHandler.bind(this); @@ -71,7 +73,7 @@ export default abstract class OCPPIncomingRequestService { return true; } logger.error( - `${chargingStation.logPrefix()} ${moduleName}.validateIncomingRequestPayload: Incoming request PDU is invalid: %j`, + `${chargingStation.logPrefix()} ${moduleName}.validateIncomingRequestPayload: Command '${commandName}' incoming request PDU is invalid: %j`, validate.errors ); throw new OCPPError( diff --git a/src/charging-station/ocpp/OCPPRequestService.ts b/src/charging-station/ocpp/OCPPRequestService.ts index c089c86a..6b58e2ad 100644 --- a/src/charging-station/ocpp/OCPPRequestService.ts +++ b/src/charging-station/ocpp/OCPPRequestService.ts @@ -37,7 +37,9 @@ export default abstract class OCPPRequestService { protected constructor(version: OCPPVersion, ocppResponseService: OCPPResponseService) { this.version = version; - this.ajv = new Ajv(); + this.ajv = new Ajv({ + multipleOfPrecision: 2, + }); ajvFormats(this.ajv); this.ocppResponseService = ocppResponseService; this.requestHandler.bind(this); @@ -127,19 +129,23 @@ export default abstract class OCPPRequestService { protected validateRequestPayload( chargingStation: ChargingStation, - commandName: RequestCommand, - schema: JSONSchemaType, + commandName: RequestCommand | IncomingRequestCommand, payload: T ): boolean { if (chargingStation.getPayloadSchemaValidation() === false) { return true; } + const schema = this.getRequestPayloadValidationSchema(chargingStation, commandName); + if (schema === false) { + return true; + } const validate = this.ajv.compile(schema); + this.convertDateToISOString(payload); if (validate(payload)) { return true; } logger.error( - `${chargingStation.logPrefix()} ${moduleName}.validateRequestPayload: Request PDU is invalid: %j`, + `${chargingStation.logPrefix()} ${moduleName}.validateRequestPayload: Command '${commandName}' request PDU is invalid: %j`, validate.errors ); // OCPPError usage here is debatable: it's an error in the OCPP stack but not targeted to sendError(). @@ -327,6 +333,7 @@ export default abstract class OCPPRequestService { commandName, messagePayload as JsonType, ]); + this.validateRequestPayload(chargingStation, commandName, messagePayload as JsonType); messageToSend = JSON.stringify([ messageType, messageId, @@ -337,6 +344,7 @@ export default abstract class OCPPRequestService { // Response case MessageType.CALL_RESULT_MESSAGE: // Build response + // FIXME: Validate response payload messageToSend = JSON.stringify([messageType, messageId, messagePayload] as Response); break; // Error Message @@ -377,6 +385,16 @@ export default abstract class OCPPRequestService { } } + private convertDateToISOString(obj: T): void { + for (const k in obj) { + if (obj[k] instanceof Date) { + (obj as JsonObject)[k] = (obj[k] as Date).toISOString(); + } else if (obj[k] !== null && typeof obj[k] === 'object') { + this.convertDateToISOString(obj[k] as T); + } + } + } + // eslint-disable-next-line @typescript-eslint/no-unused-vars public abstract requestHandler( chargingStation: ChargingStation, @@ -384,4 +402,9 @@ export default abstract class OCPPRequestService { commandParams?: JsonType, params?: RequestParams ): Promise; + + protected abstract getRequestPayloadValidationSchema( + chargingStation: ChargingStation, + commandName: RequestCommand | IncomingRequestCommand + ): JSONSchemaType | false; } diff --git a/src/charging-station/ocpp/OCPPResponseService.ts b/src/charging-station/ocpp/OCPPResponseService.ts index 414eb8a8..adcdacae 100644 --- a/src/charging-station/ocpp/OCPPResponseService.ts +++ b/src/charging-station/ocpp/OCPPResponseService.ts @@ -18,7 +18,9 @@ export default abstract class OCPPResponseService { protected constructor(version: OCPPVersion) { this.version = version; - this.ajv = new Ajv(); + this.ajv = new Ajv({ + multipleOfPrecision: 2, + }); ajvFormats(this.ajv); this.responseHandler.bind(this); this.validateResponsePayload.bind(this); @@ -45,7 +47,7 @@ export default abstract class OCPPResponseService { return true; } logger.error( - `${chargingStation.logPrefix()} ${moduleName}.validateResponsePayload: Response PDU is invalid: %j`, + `${chargingStation.logPrefix()} ${moduleName}.validateResponsePayload: Command '${commandName}' response PDU is invalid: %j`, validate.errors ); throw new OCPPError( -- 2.34.1