X-Git-Url: https://git.piment-noir.org/?a=blobdiff_plain;f=src%2Fcharging-station%2Focpp%2FOCPPRequestService.ts;h=0d95ffa5493f8f4b06677c43f7cf47231ff9ee30;hb=c9a4f9ea603df09541c6837fee50012256fb46ad;hp=6b58e2adba0b0b06fbb514a2960d3d6eb80c3fe6;hpb=4598878063d62259a6046a870dfa455b9d0027ca;p=e-mobility-charging-stations-simulator.git diff --git a/src/charging-station/ocpp/OCPPRequestService.ts b/src/charging-station/ocpp/OCPPRequestService.ts index 6b58e2ad..0d95ffa5 100644 --- a/src/charging-station/ocpp/OCPPRequestService.ts +++ b/src/charging-station/ocpp/OCPPRequestService.ts @@ -32,12 +32,13 @@ 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; this.ajv = new Ajv({ + keywords: ['javaType'], multipleOfPrecision: 2, }); ajvFormats(this.ajv); @@ -49,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( @@ -127,7 +129,7 @@ export default abstract class OCPPRequestService { } } - protected validateRequestPayload( + private validateRequestPayload( chargingStation: ChargingStation, commandName: RequestCommand | IncomingRequestCommand, payload: T @@ -135,12 +137,15 @@ 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); - this.convertDateToISOString(payload); + const validate = this.ajv.compile(this.jsonSchemas.get(commandName as RequestCommand)); + payload = Utils.cloneObject(payload); + OCPPServiceUtils.convertDateToISOString(payload); if (validate(payload)) { return true; } @@ -157,6 +162,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, @@ -194,47 +240,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); } @@ -284,9 +335,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); @@ -333,7 +385,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, @@ -344,7 +396,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 @@ -385,16 +441,6 @@ 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, @@ -402,9 +448,4 @@ export default abstract class OCPPRequestService { commandParams?: JsonType, params?: RequestParams ): Promise; - - protected abstract getRequestPayloadValidationSchema( - chargingStation: ChargingStation, - commandName: RequestCommand | IncomingRequestCommand - ): JSONSchemaType | false; }