7e92a9ca6517e374d8de7ab88170cd04b1be8726
[e-mobility-charging-stations-simulator.git] / src / charging-station / ocpp / 2.0 / OCPP20IncomingRequestService.ts
1 // Partial Copyright Jerome Benoit. 2021-2023. All Rights Reserved.
2
3 import fs from 'fs';
4 import path from 'path';
5 import { fileURLToPath } from 'url';
6
7 import type { JSONSchemaType } from 'ajv';
8
9 import { OCPP20ServiceUtils } from './OCPP20ServiceUtils';
10 import OCPPError from '../../../exception/OCPPError';
11 import type { JsonObject, JsonType } from '../../../types/JsonType';
12 import {
13 type OCPP20ClearCacheRequest,
14 OCPP20IncomingRequestCommand,
15 } from '../../../types/ocpp/2.0/Requests';
16 import { ErrorType } from '../../../types/ocpp/ErrorType';
17 import { OCPPVersion } from '../../../types/ocpp/OCPPVersion';
18 import type { IncomingRequestHandler } from '../../../types/ocpp/Requests';
19 import logger from '../../../utils/Logger';
20 import type ChargingStation from '../../ChargingStation';
21 import OCPPIncomingRequestService from '../OCPPIncomingRequestService';
22
23 const moduleName = 'OCPP20IncomingRequestService';
24
25 export default class OCPP20IncomingRequestService extends OCPPIncomingRequestService {
26 protected jsonSchemas: Map<OCPP20IncomingRequestCommand, JSONSchemaType<JsonObject>>;
27 private incomingRequestHandlers: Map<OCPP20IncomingRequestCommand, IncomingRequestHandler>;
28
29 public constructor() {
30 if (new.target?.name === moduleName) {
31 throw new TypeError(`Cannot construct ${new.target?.name} instances directly`);
32 }
33 super(OCPPVersion.VERSION_20);
34 this.incomingRequestHandlers = new Map<OCPP20IncomingRequestCommand, IncomingRequestHandler>([
35 [OCPP20IncomingRequestCommand.CLEAR_CACHE, this.handleRequestClearCache.bind(this)],
36 ]);
37 this.jsonSchemas = new Map<OCPP20IncomingRequestCommand, JSONSchemaType<JsonObject>>([
38 [
39 OCPP20IncomingRequestCommand.CLEAR_CACHE,
40 this.parseJsonSchemaFile<OCPP20ClearCacheRequest>(
41 '../../../assets/json-schemas/ocpp/2.0/ClearCacheRequest.json'
42 ),
43 ],
44 ]);
45 this.validatePayload.bind(this);
46 }
47
48 public async incomingRequestHandler(
49 chargingStation: ChargingStation,
50 messageId: string,
51 commandName: OCPP20IncomingRequestCommand,
52 commandPayload: JsonType
53 ): Promise<void> {
54 let response: JsonType;
55 if (
56 chargingStation.getOcppStrictCompliance() === true &&
57 chargingStation.isInPendingState() === true &&
58 (commandName === OCPP20IncomingRequestCommand.REQUEST_START_TRANSACTION ||
59 commandName === OCPP20IncomingRequestCommand.REQUEST_STOP_TRANSACTION)
60 ) {
61 throw new OCPPError(
62 ErrorType.SECURITY_ERROR,
63 `${commandName} cannot be issued to handle request PDU ${JSON.stringify(
64 commandPayload,
65 null,
66 2
67 )} while the charging station is in pending state on the central server`,
68 commandName,
69 commandPayload
70 );
71 }
72 if (
73 chargingStation.isRegistered() === true ||
74 (chargingStation.getOcppStrictCompliance() === false &&
75 chargingStation.isInUnknownState() === true)
76 ) {
77 if (
78 this.incomingRequestHandlers.has(commandName) === true &&
79 OCPP20ServiceUtils.isIncomingRequestCommandSupported(chargingStation, commandName) === true
80 ) {
81 try {
82 this.validatePayload(chargingStation, commandName, commandPayload);
83 // Call the method to build the response
84 response = await this.incomingRequestHandlers.get(commandName)(
85 chargingStation,
86 commandPayload
87 );
88 } catch (error) {
89 // Log
90 logger.error(
91 `${chargingStation.logPrefix()} ${moduleName}.incomingRequestHandler: Handle incoming request error:`,
92 error
93 );
94 throw error;
95 }
96 } else {
97 // Throw exception
98 throw new OCPPError(
99 ErrorType.NOT_IMPLEMENTED,
100 `${commandName} is not implemented to handle request PDU ${JSON.stringify(
101 commandPayload,
102 null,
103 2
104 )}`,
105 commandName,
106 commandPayload
107 );
108 }
109 } else {
110 throw new OCPPError(
111 ErrorType.SECURITY_ERROR,
112 `${commandName} cannot be issued to handle request PDU ${JSON.stringify(
113 commandPayload,
114 null,
115 2
116 )} while the charging station is not registered on the central server.`,
117 commandName,
118 commandPayload
119 );
120 }
121 // Send the built response
122 await chargingStation.ocppRequestService.sendResponse(
123 chargingStation,
124 messageId,
125 response,
126 commandName
127 );
128 }
129
130 private validatePayload(
131 chargingStation: ChargingStation,
132 commandName: OCPP20IncomingRequestCommand,
133 commandPayload: JsonType
134 ): boolean {
135 if (this.jsonSchemas.has(commandName) === true) {
136 return this.validateIncomingRequestPayload(
137 chargingStation,
138 commandName,
139 this.jsonSchemas.get(commandName),
140 commandPayload
141 );
142 }
143 logger.warn(
144 `${chargingStation.logPrefix()} ${moduleName}.validatePayload: No JSON schema found for command '${commandName}' PDU validation`
145 );
146 return false;
147 }
148
149 private parseJsonSchemaFile<T extends JsonType>(relativePath: string): JSONSchemaType<T> {
150 return JSON.parse(
151 fs.readFileSync(
152 path.resolve(path.dirname(fileURLToPath(import.meta.url)), relativePath),
153 'utf8'
154 )
155 ) as JSONSchemaType<T>;
156 }
157 }