X-Git-Url: https://git.piment-noir.org/?a=blobdiff_plain;f=src%2Fcharging-station%2Focpp%2FOCPPRequestService.ts;h=f561a44635c16f4088503dbf01961143faca484f;hb=b2a0452d368cfc43bbacc4991de59afa64f662a4;hp=e4890a91d8a6a88e2c8d282fcd603c2b2c31baef;hpb=98fc1389a2464ce8738047f8990731ae31938ee5;p=e-mobility-charging-stations-simulator.git diff --git a/src/charging-station/ocpp/OCPPRequestService.ts b/src/charging-station/ocpp/OCPPRequestService.ts index e4890a91..f561a446 100644 --- a/src/charging-station/ocpp/OCPPRequestService.ts +++ b/src/charging-station/ocpp/OCPPRequestService.ts @@ -1,6 +1,8 @@ import Ajv, { type JSONSchemaType } from 'ajv'; import ajvFormats from 'ajv-formats'; +import type OCPPResponseService from './OCPPResponseService'; +import { OCPPServiceUtils } from './OCPPServiceUtils'; import OCPPError from '../../exception/OCPPError'; import PerformanceStatistics from '../../performance/PerformanceStatistics'; import type { EmptyObject } from '../../types/EmptyObject'; @@ -23,8 +25,6 @@ import Constants from '../../utils/Constants'; import logger from '../../utils/Logger'; import Utils from '../../utils/Utils'; import type ChargingStation from '../ChargingStation'; -import type OCPPResponseService from './OCPPResponseService'; -import { OCPPServiceUtils } from './OCPPServiceUtils'; const moduleName = 'OCPPRequestService'; @@ -32,8 +32,8 @@ export default abstract class OCPPRequestService { private static instance: OCPPRequestService | null = null; private readonly version: OCPPVersion; private readonly ajv: Ajv; - private readonly ocppResponseService: OCPPResponseService; + protected abstract jsonSchemas: Map>; protected constructor(version: OCPPVersion, ocppResponseService: OCPPResponseService) { this.version = version; @@ -50,6 +50,7 @@ export default abstract class OCPPRequestService { this.internalSendMessage.bind(this); this.buildMessageToSend.bind(this); this.validateRequestPayload.bind(this); + this.validateIncomingRequestResponsePayload.bind(this); } public static getInstance( @@ -112,6 +113,7 @@ export default abstract class OCPPRequestService { params: RequestParams = { skipBufferingOnError: false, triggerMessage: false, + throwError: false, } ): Promise { try { @@ -124,11 +126,13 @@ export default abstract class OCPPRequestService { params ); } catch (error) { - this.handleSendMessageError(chargingStation, commandName, error as Error); + this.handleSendMessageError(chargingStation, commandName, error as Error, { + throwError: params.throwError, + }); } } - protected validateRequestPayload( + private validateRequestPayload( chargingStation: ChargingStation, commandName: RequestCommand | IncomingRequestCommand, payload: T @@ -136,11 +140,14 @@ export default abstract class OCPPRequestService { if (chargingStation.getPayloadSchemaValidation() === false) { return true; } - const schema = this.getRequestPayloadValidationSchema(chargingStation, commandName); - if (schema === false) { + if (this.jsonSchemas.has(commandName as RequestCommand) === false) { + logger.warn( + `${chargingStation.logPrefix()} ${moduleName}.validateRequestPayload: No JSON schema found for command '${commandName}' PDU validation` + ); return true; } - const validate = this.ajv.compile(schema); + const validate = this.ajv.compile(this.jsonSchemas.get(commandName as RequestCommand)); + payload = Utils.cloneObject(payload); OCPPServiceUtils.convertDateToISOString(payload); if (validate(payload)) { return true; @@ -158,6 +165,47 @@ export default abstract class OCPPRequestService { ); } + private validateIncomingRequestResponsePayload( + chargingStation: ChargingStation, + commandName: RequestCommand | IncomingRequestCommand, + payload: T + ): boolean { + if (chargingStation.getPayloadSchemaValidation() === false) { + return true; + } + if ( + this.ocppResponseService.jsonIncomingRequestResponseSchemas.has( + commandName as IncomingRequestCommand + ) === false + ) { + logger.warn( + `${chargingStation.logPrefix()} ${moduleName}.validateIncomingRequestResponsePayload: No JSON schema found for command '${commandName}' PDU validation` + ); + return true; + } + const validate = this.ajv.compile( + this.ocppResponseService.jsonIncomingRequestResponseSchemas.get( + commandName as IncomingRequestCommand + ) + ); + payload = Utils.cloneObject(payload); + OCPPServiceUtils.convertDateToISOString(payload); + if (validate(payload)) { + return true; + } + logger.error( + `${chargingStation.logPrefix()} ${moduleName}.validateIncomingRequestResponsePayload: Command '${commandName}' reponse PDU is invalid: %j`, + validate.errors + ); + // OCPPError usage here is debatable: it's an error in the OCPP stack but not targeted to sendError(). + throw new OCPPError( + OCPPServiceUtils.ajvErrorsToErrorType(validate.errors), + 'Response PDU is invalid', + commandName, + JSON.stringify(validate.errors, null, 2) + ); + } + private async internalSendMessage( chargingStation: ChargingStation, messageId: string, @@ -195,47 +243,52 @@ export default abstract class OCPPRequestService { if (chargingStation.getEnableStatistics() === true) { chargingStation.performanceStatistics.addRequestStatistic(commandName, messageType); } + let sendError = false; // Check if wsConnection opened if (chargingStation.isWebSocketConnectionOpened() === true) { - // Yes: Send Message const beginId = PerformanceStatistics.beginMeasure(commandName as string); - // FIXME: Handle sending error - chargingStation.wsConnection.send(messageToSend); + try { + chargingStation.wsConnection.send(messageToSend); + } catch (error) { + sendError = true; + } PerformanceStatistics.endMeasure(commandName as string, beginId); logger.debug( `${chargingStation.logPrefix()} >> Command '${commandName}' sent ${this.getMessageTypeString( messageType )} payload: ${messageToSend}` ); - } else if (params.skipBufferingOnError === false) { - // Buffer it + } + const wsClosedOrErrored = + chargingStation.isWebSocketConnectionOpened() === false || sendError === true; + if (wsClosedOrErrored && params.skipBufferingOnError === false) { + // Buffer chargingStation.bufferMessage(messageToSend); + // Reject and keep request in the cache + return reject( + new OCPPError( + ErrorType.GENERIC_ERROR, + `WebSocket closed or errored for buffered message id '${messageId}' with content '${messageToSend}'`, + commandName, + (messagePayload as JsonObject)?.details ?? {} + ) + ); + } else if (wsClosedOrErrored) { const ocppError = new OCPPError( ErrorType.GENERIC_ERROR, - `WebSocket closed for buffered message id '${messageId}' with content '${messageToSend}'`, + `WebSocket closed or errored for non buffered message id '${messageId}' with content '${messageToSend}'`, commandName, (messagePayload as JsonObject)?.details ?? {} ); - if (messageType === MessageType.CALL_MESSAGE) { - // Reject it but keep the request in the cache + // Reject response + if (messageType !== MessageType.CALL_MESSAGE) { return reject(ocppError); } + // Reject and remove request from the cache return errorCallback(ocppError, false); - } else { - // Reject it - return errorCallback( - new OCPPError( - ErrorType.GENERIC_ERROR, - `WebSocket closed for non buffered message id '${messageId}' with content '${messageToSend}'`, - commandName, - (messagePayload as JsonObject)?.details ?? {} - ), - false - ); } - // Response? + // Resolve response if (messageType !== MessageType.CALL_MESSAGE) { - // Yes: send Ok return resolve(messagePayload); } @@ -285,9 +338,10 @@ export default abstract class OCPPRequestService { ); } logger.error( - `${chargingStation.logPrefix()} Error occurred when calling command ${commandName} with message data ${JSON.stringify( - messagePayload - )}:`, + `${chargingStation.logPrefix()} Error occurred at ${self.getMessageTypeString( + messageType + )} command ${commandName} with PDU %j:`, + messagePayload, error ); chargingStation.requests.delete(messageId); @@ -334,7 +388,7 @@ export default abstract class OCPPRequestService { commandName, messagePayload as JsonType, ]); - this.validateRequestPayload(chargingStation, commandName, messagePayload as JsonType); + this.validateRequestPayload(chargingStation, commandName, messagePayload as JsonObject); messageToSend = JSON.stringify([ messageType, messageId, @@ -345,7 +399,11 @@ export default abstract class OCPPRequestService { // Response case MessageType.CALL_RESULT_MESSAGE: // Build response - // FIXME: Validate response payload + this.validateIncomingRequestResponsePayload( + chargingStation, + commandName, + messagePayload as JsonObject + ); messageToSend = JSON.stringify([messageType, messageId, messagePayload] as Response); break; // Error Message @@ -393,9 +451,4 @@ export default abstract class OCPPRequestService { commandParams?: JsonType, params?: RequestParams ): Promise; - - protected abstract getRequestPayloadValidationSchema( - chargingStation: ChargingStation, - commandName: RequestCommand | IncomingRequestCommand - ): JSONSchemaType | false; }