Line data Source code
1 : // Copyright 2024 Accenture.
2 :
3 : #pragma once
4 :
5 : #include "uds/DiagReturnCode.h"
6 : #include "uds/UdsConstants.h"
7 : #include "uds/session/DiagSession.h"
8 :
9 : #include <etl/uncopyable.h>
10 : #include <util/estd/assert.h>
11 :
12 : #include <platform/estdint.h>
13 :
14 : namespace http
15 : {
16 : namespace html
17 : {
18 : class UdsController;
19 : }
20 : } // namespace http
21 :
22 : namespace uds
23 : {
24 : class IncomingDiagConnection;
25 : class IDiagAuthenticator;
26 : class IDiagSessionManager;
27 : class DiagSubSession;
28 : class Service;
29 : class DiagJobRoot;
30 :
31 : /**
32 : * Common base class for diagnosis jobs
33 : *
34 : *
35 : *
36 : * \section Design
37 : * AbstractDiagJobs are elements of a tree-data-structure that represents
38 : * the ECUs complete diagnosis applications when fully configured. Each
39 : * AbstractDiagJob knows the <b>full</b> request it implements and which
40 : * part of the request needs to be provided by a parent node.
41 : *
42 : * \section Example
43 : * A class ReadVIN would implement the request 0x22,0xF1,0x90 requiring 1 prefix
44 : * byte, i.e. 0x22. This byte could be provided by a ReadDataByIdentifier class.
45 : */
46 : class AbstractDiagJob : public ::etl::uncopyable
47 : {
48 : public:
49 : using DiagSessionMask = DiagSession::DiagSessionMask;
50 :
51 : static uint8_t const EMPTY_REQUEST = 0U;
52 :
53 : static uint8_t const EMPTY_RESPONSE = 0U;
54 :
55 : static uint8_t const VARIABLE_REQUEST_LENGTH = 0xFFU;
56 :
57 : static uint8_t const VARIABLE_RESPONSE_LENGTH = 0xFFU;
58 :
59 : /**
60 : * Error codes method invocations may return
61 : */
62 : enum ErrorCode
63 : {
64 : /** AbstractDiagJob has been successfully added */
65 : JOB_ADDED,
66 : /** An error occurred adding AbstractDiagJob */
67 : JOB_NOT_ADDED
68 : };
69 :
70 : /**
71 : * Possible results of sending a response
72 : */
73 : enum ResponseSendResult
74 : {
75 : /** the response has been successfully transmitted */
76 : RESPONSE_SENT,
77 : /** an error occurred sending the response */
78 : RESPONSE_SEND_FAILED
79 : };
80 :
81 : /**
82 : * Constructor
83 : * \param implementedRequest uint8_t array which contains the
84 : * request implemented by this AbstractDiagJob.
85 : * \param requestLength Size of pImplementedRequest which is equal
86 : * to the total length of the request implemented by this
87 : * AbstractDiagJob.
88 : * \param prefixLength Number of bytes of pImplementedRequest that
89 : * this AbstractDiagJob requires to be checked by a parent
90 : * AbstractDiagJob.
91 : * \param sessionMask Mask of DiagSessions in which this job is active.
92 : */
93 :
94 521 : AbstractDiagJob(
95 : uint8_t const* const implementedRequest,
96 : uint8_t const requestLength,
97 : uint8_t const prefixLength,
98 : DiagSessionMask const sessionMask = DiagSession::ALL_SESSIONS())
99 521 : : fpImplementedRequest(implementedRequest)
100 521 : , fpFirstChild(nullptr)
101 521 : , fpNextJob(nullptr)
102 521 : , fAllowedSessions(sessionMask)
103 521 : , fResponseLength(VARIABLE_RESPONSE_LENGTH)
104 521 : , fRequestLength(requestLength)
105 521 : , fPrefixLength(prefixLength)
106 521 : , fRequestPayloadLength(VARIABLE_REQUEST_LENGTH)
107 521 : , fDefaultDiagReturnCode(DiagReturnCode::ISO_GENERAL_REJECT)
108 521 : , fSuppressPositiveResponseBitEnabled(false)
109 : {
110 521 : if (requestLength > 0U)
111 : {
112 319 : estd_assert(requestLength > prefixLength);
113 : }
114 520 : }
115 :
116 : /**
117 : * Constructor
118 : * \param implementedRequest uint8_t array which contains the
119 : * request implemented by this AbstractDiagJob
120 : * \param requestLength Size of pImplementedRequest which is equal
121 : * to the total length of the request implemented by this
122 : * AbstractDiagJob
123 : * \param prefixLength Number of bytes of pImplementedRequest that
124 : * this AbstractDiagJob requires to be checked by a parent
125 : * AbstractDiagJob
126 : * \param requestPayloadLength Number of bytes of the request payload,
127 : * (excluding prefixLength) pass VARIABLE_REQUEST_LENGTH if your diagnosis
128 : * job has variable length
129 : * \param responseLength Number of bytes required for your response, pass
130 : * VARIABLE_RESPONSE_LENGTH if the response has variable length
131 : * \param sessionMask Mask of DiagSessions in which this job is active
132 : */
133 337 : AbstractDiagJob(
134 : uint8_t const* const implementedRequest,
135 : uint8_t const requestLength,
136 : uint8_t const prefixLength,
137 : uint8_t const requestPayloadLength,
138 : uint8_t const responseLength,
139 : DiagSessionMask const sessionMask = DiagSession::ALL_SESSIONS())
140 337 : : fpImplementedRequest(implementedRequest)
141 337 : , fpFirstChild(nullptr)
142 337 : , fpNextJob(nullptr)
143 337 : , fAllowedSessions(sessionMask)
144 337 : , fResponseLength(responseLength)
145 337 : , fRequestLength(requestLength)
146 337 : , fPrefixLength(prefixLength)
147 337 : , fRequestPayloadLength(requestPayloadLength)
148 337 : , fDefaultDiagReturnCode(DiagReturnCode::ISO_GENERAL_REJECT)
149 337 : , fSuppressPositiveResponseBitEnabled(false)
150 : {
151 337 : if (requestLength > 0U)
152 : {
153 335 : estd_assert(requestLength > prefixLength);
154 : }
155 336 : }
156 :
157 : #ifdef UNIT_TEST
158 892 : virtual ~AbstractDiagJob() {}
159 : #endif
160 :
161 : /**
162 : * Sets the default instance of IDiagSessionManager which will be used by
163 : * all instances of AbstractDiagJob
164 : * \param sessionManager
165 : */
166 : static void setDefaultDiagSessionManager(IDiagSessionManager& sessionManager);
167 :
168 : /**
169 : * Tells this AbstractDiagJob to execute a given request
170 : * \param connection IncomingDiagConnection that triggers the request
171 : * \param request Array containing the request
172 : * \param requestLength Length of request
173 : *
174 : * \section Behaviour
175 : * This method is the only public interface to give a incoming
176 : * request to this AbstractDiagJob. It first calls verify and in case
177 : * verify returns a DiagReturnCode::Type other than NOT_RESPONSIBLE, it is
178 : * assumed that the request belongs to this job,
179 : * process will be called and after that execute will return.
180 : *
181 : * \see verify()
182 : * \see process()
183 : */
184 : DiagReturnCode::Type
185 : execute(IncomingDiagConnection& connection, uint8_t const request[], uint16_t requestLength);
186 :
187 : /**
188 : * Adds an AbstractDiagJob as child node
189 : * \param job AbstractDiagJob to add as child node
190 : * \return
191 : * - JOB_ADDED: job has been added as a child node
192 : * - JOB_NOT_ADDED: job could not be added as child node
193 : */
194 : ErrorCode addAbstractDiagJob(AbstractDiagJob& job);
195 :
196 : /**
197 : * Removes an AbstractDiagJob from to tree structure
198 : * \param job AbstractDiagJob to remove
199 : */
200 : void removeAbstractDiagJob(AbstractDiagJob& job);
201 :
202 : /**
203 : * Callback that gets invoked when a response on a IncomingDiagConnection
204 : * has been sent
205 : * \param connection IncomingDiagConnection on which the response has
206 : * been sent
207 : * \param result ResponseSendResult indicating status of transmission
208 : *
209 : * \section Default
210 : * The default implementation terminates the connection. So if more
211 : * responses are to be sent, this method must be overwritten.
212 : */
213 : virtual void responseSent(IncomingDiagConnection& connection, ResponseSendResult result);
214 :
215 : static IDiagAuthenticator const& getDefaultDiagAuthenticator();
216 :
217 : uint32_t getRequestId() const;
218 :
219 : protected:
220 : friend class AsyncDiagHelper;
221 : friend class AbstractAsyncDiagJob;
222 :
223 : friend bool operator==(AbstractDiagJob const&, AbstractDiagJob const&);
224 :
225 : /**
226 : * Checks is a AbstractDiagJob requires a certain prefix and thus is
227 : * child of the calling AbstractDiagJob
228 : * \param pPrefix Array containing a provided prefix
229 : * \param length Length of pPrefix
230 : * \return
231 : * - true: This job is a child of pPrefix
232 : * - false: This job is not a child of pPrefix
233 : */
234 : bool isChild(uint8_t const prefix[], uint16_t length) const;
235 :
236 : bool isFamily(uint8_t const prefix[], uint16_t length) const;
237 :
238 : /**
239 : * Constructor that creates a copy of an given AbstractDiagJob
240 : * \param pJob
241 : *
242 : * This constructor is provided to give the possibility to create
243 : * wrapper classes of an AbstractDiagJob.
244 : *
245 : * \see AsyncDiagJob
246 : */
247 36 : explicit AbstractDiagJob(AbstractDiagJob const* const pJob)
248 36 : : fpImplementedRequest(pJob->fpImplementedRequest)
249 36 : , fpFirstChild(nullptr)
250 36 : , fpNextJob(nullptr)
251 36 : , fAllowedSessions(pJob->fAllowedSessions)
252 36 : , fResponseLength(pJob->fResponseLength)
253 36 : , fRequestLength(pJob->fRequestLength)
254 36 : , fPrefixLength(pJob->fPrefixLength)
255 36 : , fRequestPayloadLength(pJob->fRequestPayloadLength)
256 36 : , fDefaultDiagReturnCode(DiagReturnCode::ISO_GENERAL_REJECT)
257 36 : , fSuppressPositiveResponseBitEnabled(false)
258 36 : {}
259 :
260 : /**
261 : * Verifies a given request with respect to the responsibility of this job
262 : * \param request Array containing the request to verify
263 : * \param requestLength Length of request
264 : * \return Result of verification
265 : * - NOT_RESPONSIBLE: This job is not responsible for handling the
266 : * request. The next job will be asked.
267 : * - OK: This job wants to handle the request. The number of bytes
268 : * specified by fRequestLength will be added to the IncomingDiagConnection
269 : * and thus be part of a positive response. process() will be called afterwards.
270 : * - Any other DiagReturnCode::Type will result in a negative response
271 : * being sent using the DiagReturnCode.
272 : *
273 : * \note
274 : * This method must be implemented by every inheriting class.
275 : *
276 : * \see process()
277 : * \see IncomingDiagConnection::addIdentifier()
278 : */
279 : virtual DiagReturnCode::Type verify(uint8_t const request[], uint16_t requestLength) = 0;
280 :
281 : /**
282 : * Processes a given request after it has been successfully verified.
283 : * \param connection IncomingDiagConnection from which the request has been
284 : * received.
285 : * \param request Array containing the request to verify
286 : * \param requestLength Length of request
287 : * \return Result of verification
288 : * - OK: This job wants to handle the request and will send a
289 : * response later (either positive or negative).
290 : * and thus be part of a positive response. process() will be called afterwards.
291 : * - Any other DiagReturnCode::Type will result in a negative response
292 : * being sent using the DiagReturnCode.
293 : *
294 : * \note
295 : * Any leaf node must overwrite this method. The default implementation
296 : * just traverses the diagnosis tree.
297 : *
298 : * \see verify()
299 : * \see IncomingDiagConnection
300 : */
301 : virtual DiagReturnCode::Type process(
302 : IncomingDiagConnection& connection, uint8_t const* const request, uint16_t requestLength);
303 :
304 : /**
305 : * Compares two byte arrays.
306 : * \param data1 First array to compare
307 : * \param data2 Second array to compare
308 : * \param length Number of bytes to compare
309 : * \return
310 : * - true: Both arrays are equal
311 : * - false: Arrays are not equal
312 : *
313 : * It is assumed that both data1 and data2 have at least length bytes.
314 : */
315 : static bool compare(uint8_t const data1[], uint8_t const data2[], uint16_t length);
316 :
317 : /**
318 : * \return Pointer to next job
319 : * - nullptr: No next AbstractDiagJob exists
320 : * - else: Pointer to next AbstractDiagJob
321 : */
322 : AbstractDiagJob* getNextJob() const;
323 : /**
324 : * Get pointer to next job for a given abstract diag job
325 : * \param job Reference to job to get next pointer for
326 : * \return Pointer to next job
327 : * - nullptr: No next AbstractDiagJob exists
328 : * - else: pointer To next AbstractDiagJob
329 : */
330 : static AbstractDiagJob* getNextJob(AbstractDiagJob& job);
331 :
332 : /**
333 : * Get pointer to static reference of
334 : * the first child instance of root
335 : * \return Pointer to child job
336 : */
337 : static DiagJobRoot* getDiagJobRoot();
338 :
339 : /**
340 : * Sets pointer to next AbstractDiagJob
341 : * \param pJob Pointer to AbstractDiagJob that will be a sibling
342 : */
343 : void setNextJob(AbstractDiagJob* pJob);
344 :
345 : /**
346 : * \return Active session
347 : */
348 : DiagSession const& getSession() const;
349 :
350 : DiagReturnCode::Type getDefaultDiagReturnCode() const;
351 :
352 : void setDefaultDiagReturnCode(DiagReturnCode::Type returnCode);
353 :
354 : DiagSession::DiagSessionMask const getAllowedSessions() const;
355 :
356 : uint8_t const* getImplementedRequest() const;
357 :
358 : uint8_t getRequestLength() const;
359 :
360 : virtual IDiagSessionManager& getDiagSessionManager();
361 :
362 : virtual IDiagSessionManager const& getDiagSessionManager() const;
363 :
364 : virtual IDiagAuthenticator const& getDiagAuthenticator() const;
365 :
366 : /**
367 : * Enables suppressPositiveResponse. Call this from constructor if
368 : * this job supports the suppressPositiveResponse bit.
369 : */
370 82 : inline void enableSuppressPositiveResponse(bool const set = true)
371 : {
372 82 : fSuppressPositiveResponseBitEnabled = set;
373 82 : }
374 :
375 1 : inline void setEnableSuppressPositiveResponse(AbstractDiagJob& job) const
376 : {
377 1 : job.fSuppressPositiveResponseBitEnabled = fSuppressPositiveResponseBitEnabled;
378 1 : }
379 :
380 : /**
381 : * Reset a variable holding an incoming connection and return the previous value.
382 : * \param connection Reference to variable storing pointer to connection
383 : * \return Pointer to previous connection
384 : */
385 : static IncomingDiagConnection* getAndResetConnection(IncomingDiagConnection*& connection);
386 :
387 : private:
388 : friend class Service;
389 : friend class ServiceWithAuthentication;
390 : friend class DiagJobRoot;
391 : friend class ::http::html::UdsController;
392 :
393 : /** Mask with suppress positive response bit set */
394 : static uint8_t const SUPPRESS_POSITIVE_RESPONSE_MASK = static_cast<uint8_t>(0x80U);
395 :
396 : static IDiagSessionManager* sfpSessionManager;
397 :
398 : static DiagJobRoot* sfpDiagJobRoot;
399 :
400 : void acceptJob(
401 : IncomingDiagConnection& connection, uint8_t const request[], uint16_t const requestLength);
402 : /**
403 : * Does suppress positive response bit handling
404 : */
405 : void checkSuppressPositiveResponseBit(
406 : IncomingDiagConnection& connection, uint8_t const request[]) const;
407 :
408 : /** Array containing the request implemented by this job */
409 : uint8_t const* const fpImplementedRequest;
410 : /** Pointer to first child. A child has fpImplementedRequest as its prefix */
411 : AbstractDiagJob* fpFirstChild;
412 : /** Pointer to next job that has the same prefix */
413 : AbstractDiagJob* fpNextJob;
414 : /** Mask with bits set for session in which this job may be executed */
415 : DiagSession::DiagSessionMask const fAllowedSessions;
416 : /** Required length of response */
417 : uint8_t const fResponseLength;
418 : /** Length of fpImplementedRequest */
419 : uint8_t const fRequestLength;
420 : /** Number of bytes of fpImplementedRequest that need to be implemented by prior job in the tree
421 : */
422 : uint8_t const fPrefixLength;
423 : /** Length of the total request (fpImplementedRequest + parameters) */
424 : uint8_t const fRequestPayloadLength;
425 : /** Default DiagReturnCode::Type for a non leaf node */
426 : DiagReturnCode::Type fDefaultDiagReturnCode;
427 : /** Indication if positive response bit handling is enabled */
428 : bool fSuppressPositiveResponseBitEnabled;
429 : };
430 :
431 : /**
432 : * Compares two instances of AbstractDiagJob.
433 : * \param x First AbstractDiagJob to compare
434 : * \param y Second AbstractDiagJob to compare
435 : * \return
436 : * - true: Jobs implement the same request and have the same prefix
437 : * - false: Otherwise
438 : */
439 : bool operator==(AbstractDiagJob const& x, AbstractDiagJob const& y);
440 :
441 1219 : inline AbstractDiagJob* AbstractDiagJob::getNextJob() const { return fpNextJob; }
442 :
443 15 : inline AbstractDiagJob* AbstractDiagJob::getNextJob(AbstractDiagJob& job) { return job.fpNextJob; }
444 :
445 9 : inline DiagReturnCode::Type AbstractDiagJob::getDefaultDiagReturnCode() const
446 : {
447 9 : return fDefaultDiagReturnCode;
448 : }
449 :
450 233 : inline void AbstractDiagJob::setDefaultDiagReturnCode(DiagReturnCode::Type const returnCode)
451 : {
452 233 : fDefaultDiagReturnCode = returnCode;
453 233 : }
454 :
455 8 : inline DiagSession::DiagSessionMask const AbstractDiagJob::getAllowedSessions() const
456 : {
457 8 : return fAllowedSessions;
458 : }
459 :
460 50 : inline uint8_t const* AbstractDiagJob::getImplementedRequest() const
461 : {
462 50 : return fpImplementedRequest;
463 : }
464 :
465 161 : inline uint8_t AbstractDiagJob::getRequestLength() const { return fRequestLength; }
466 :
467 : inline IncomingDiagConnection*
468 3 : AbstractDiagJob::getAndResetConnection(IncomingDiagConnection*& connection)
469 : {
470 3 : IncomingDiagConnection* const prevConnection = connection;
471 3 : connection = nullptr;
472 3 : return prevConnection;
473 : }
474 :
475 : } // namespace uds
|