-
Notifications
You must be signed in to change notification settings - Fork 684
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add Layer Build and Validation for DoIP (Diagnostic over IP) Support #1655
base: dev
Are you sure you want to change the base?
Conversation
As per the contributing guidelines, please retarget the PR to the |
Observed several issues in the CI pipelines, likely due to missing definitions for |
Packet++/header/ProtocolType.h
Outdated
/** | ||
* Diagnostic over IP protocol (DOIP) | ||
*/ | ||
const ProtocolType DOIP = 38; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ID 38 is already taken by DHCPv6
protocol. The next available ID is 58, please add it after GTPv2
@@ -437,7 +437,6 @@ namespace pcpp | |||
|
|||
HttpResponseStatusCode() = default; | |||
|
|||
// cppcheck-suppress noExplicitConstructor |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why removing this cppcheck-suppress
comment?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Cppcheck flags this as an incorrect suppression on my local machine. Let me know if you think I should revert it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That is interesting. Which version of CPPcheck are you using?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
2.7 as recommanded in CONTRIBUTING.md and still flags incorrect suppression, I'm keeping this changes so can I commit my recents updates,
3rdParty/json/include/json.hpp
Outdated
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why update this file?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
same thing as my previous comment, I only removed some suppress-checks detected as incorrect
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you add one pcap file with all of these packets?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yes sure, I'll revert suppress-checks and add pcap file containig all tested packets in next PR, also try to cover some missing checks based on codecov feedback
Codecov ReportAttention: Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## dev #1655 +/- ##
==========================================
+ Coverage 83.11% 83.53% +0.41%
==========================================
Files 277 283 +6
Lines 48207 50037 +1830
Branches 10192 10470 +278
==========================================
+ Hits 40069 41796 +1727
- Misses 7243 7301 +58
- Partials 895 940 +45
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Sentry. |
@raissi-oussema are you planning to continue working on this PR? |
Hi, I was engaged with other tasks, but I plan to get back to this PR soon. Thanks for your understanding! |
.improve maps searchs for doipEnumsToStrings .cover more uses cases based on codecov feedback
Design suggestions or code improvements are always welcome and greatly appreciated. |
@raissi-oussema to make it easier to review, do you think you can add some documentation on the DoIP protocol to the PR body? It'd mostly be helpful to get more details on the header structure and different possible message |
@seladb I need support for CI pipelines, I can't figure out why are they still failing. And a clear documentation was successfully added to PR body to make it easier for you to start the code review. |
Doxigen pipeline: XDP pipeline: VS pipeline: |
What could be the problem for dioxygen pipeline, doipLayer.h is well documented [line 42] ? |
// implement abstract methods | ||
|
||
/** | ||
* TODO, parse UDS layer |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this for another PR? If so, should the remaining data be parsed as a generic payload layer for now?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
m_NextLayer is intended to be the UDS layer, which has not been implemented yet. In the future, as more knowledge is gained, either I or another contributor may add this functionality. For now, I suggest parsing it as a generic layer, as you mentioned.
PS: the nextLayer will be parsed only when the payloadType is 0x8001.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
your feedback is highly appertiated, what do you think about adding this snippet of code:
void parseNextLayer() override
{
if (getPayloadType() == DoIpPayloadTypes::DIAGNOSTIC_MESSAGE_TYPE)
{
size_t headerLen = sizeof(doiphdr);
if (m_DataLen <= headerLen + 2 /*source address size*/ + 2 /*target address size*/)
return;
uint8_t* payload = m_Data + (headerLen + 2 + 2);
size_t payloadLen = m_DataLen - (headerLen + 2 + 2);
m_NextLayer = new PayloadLayer(payload, payloadLen, this, m_Packet);
return;
}
}`
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
your feedback is highly appertiated, what do you think about adding this snippet of code:
void parseNextLayer() override { if (getPayloadType() == DoIpPayloadTypes::DIAGNOSTIC_MESSAGE_TYPE) { size_t headerLen = sizeof(doiphdr); if (m_DataLen <= headerLen + 2 /*source address size*/ + 2 /*target address size*/) return; uint8_t* payload = m_Data + (headerLen + 2 + 2); size_t payloadLen = m_DataLen - (headerLen + 2 + 2); m_NextLayer = new PayloadLayer(payload, payloadLen, this, m_Packet); return; } }`
Seems good. Can the source address
and target address
used in that type of message be accessed from this layer? Since we are excluding it from the generic payload.
A minor tip, having headerLen
be marked as constexpr
might allow some compiler optimizations (such as the arithmetic using it + a literal being computed during compilation and hardcoded if possible).
Also why the return statement that is at the end of the block anyway?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@Dimi1010 , this approach appears both safer and cleaner. It allows us to construct a DiagnosticMessage directly from the current layer, providing direct access to the diagnostic data. Using this data, we can then build a generic PayloadLayer.
void parseNextLayer() override
{
DiagnosticMessageData diagnosticMessage;
if (diagnosticMessage.buildFromLayer(this))
{
m_NextLayer = new PayloadLayer(diagnosticMessage.diagnosticData.data(),
diagnosticMessage.diagnosticData.size(), this, m_Packet);
}
}
buildFromLayer
safely parses the current layer and verifies whether it represents a valid diagnostic message.
what do you think ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sure, but my question stands. DiagnosticMessageData
has two other members (sourceAddress
and targetAddress
) which at the moment I don't see how the user can access them easily. They have neither accessors in the current DoIPLayer
or are included as part of the UDSLayer
(currently PayloadLayer
).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
sourceAddress
and targetAddress
are public members of DiagnosticMessageData class just like diagnosticData and they are not part of the UDS layer:
uint16_t sourceAddress; /**< Source address of the message. */
uint16_t targetAddress; /**< Target address for the diagnostic message. */
std::vector<uint8_t> diagnosticData; /**< Diagnostic message data with dynamic length. */
the user can access these fields by the method buildFromLayer
.
I've made this dummy function
just to show how to access to these fields :
void DoIpLayer::resolveDiagMessageFields()
{
DiagnosticMessageData diagnosticMessage;
if (diagnosticMessage.buildFromLayer(this))
{
uint16_t srcAddr = diagnosticMessage.sourceAddress;
uint16_t targetAddr = diagnosticMessage.targetAddress;
std::vector<uint8_t> diagData = diagnosticMessage.diagnosticData;
}
}
Do you think this implementation need more improvement ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok, if i am understanding correctly the sequence is this?
- User somehow receives a
DoIPLayer
from aPacket
- User checks the payload type (via
DoIPLayer::getPayloadType()
) - Depending on the payload type user uses
T::buildFromLayer(DoIPLayer)
(T being the corresponding message struct) to populate the data from the layer into the struct.
Am I understanding the flow correctly?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yes you are absolutely correct.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sure, that works fine. 👍
Co-authored-by: Dimitar Krastev <[email protected]>
Co-authored-by: Dimitar Krastev <[email protected]>
// implement abstract methods | ||
|
||
/** | ||
* TODO, parse UDS layer |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sure, but my question stands. DiagnosticMessageData
has two other members (sourceAddress
and targetAddress
) which at the moment I don't see how the user can access them easily. They have neither accessors in the current DoIPLayer
or are included as part of the UDSLayer
(currently PayloadLayer
).
… as generic PayloadLayeruse byte shifting instead of reinterprete_cast for field crafting
@Dimi1010 Any additional feedback regarding design logic or code implementation is highly appreciated. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Left a comment on the enum to string mappings. Everything else seems fine to me. It is just that and making the doxygen CI happy.
Packet++/header/DoIpEnumToString.h
Outdated
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
IMO, it would be better to have the enum to string maps be converted to functions with switch statements. It would allow to expose a cleaner and uniform public API (toString(EnumType value)
or toDescriptionString(EnumType value)
) for the different enums. I also ran a benchmark and it does end up slightly faster (at least for DoIpProtocolVersion
) due to some compilers producing a jump table (GCC and Clang do, MSVC doesn't).
The functions can then be folded into DoIpEnums.h
with the implementations into DoIpEnums.cpp
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for your feedback, @Dimi1010
In my humble opinion, unordered_map looks much cleaner and more declarative compared to switch statements, which can sometimes feel cluttered with redundant return or break statements. I’ve also ensured that edge cases, like missing keys, are handled appropriately whenever I use these maps.
To be honest, I haven’t focused much on the potential performance impact, as I believe the effect with these small enums would be negligible, though I understand it might require additional effort.
That said, if the difference in performance isn’t significant, I think we can keep it as it is. However, if you strongly feel this change is necessary, I’ll plan to prepare a fix over the weekend.
Thank you for your understanding!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sure. Not necessarily a strong opinion on the switches
. They can stay declared as maps.
Something else that occurred to me. The maps are declared as static
in this header. For a global variable to be declared static
that mean that is has internal linkage in the translation unit. For a header that means that each translation unit that header is included in, gets its own enum to string map. This is an issue as it leads duplicated maps in the different translation units. Please remove the static
keyword from the maps.
Additionally, Are the maps supposed to be part of the public API or to be used only through the methods in DoIpLayerData
?
- If they aren't supposed to be part of the public API, please put them in the
pcpp::internal
namespace. - If they are to be part of the public API, I think that maybe having a function encapsulate the search/conversion would be better, even if the conversion is done with maps, as it detaches the implementation details of the conversion from the public API. What do you think?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@Dimi1010 , thank you for your feedback!
Once again, your remarks are clear and insightful. The EnumToStringMaps are designed to be included only in DoIpLayerData, which avoids duplicate data in this context. However, for future designs, if anyone tries to use it in another translation unit, it could lead to increased memory consumption. I’ll definitely remove the static keyword as you suggested.
For the second question, yes, these maps are specific to the DoIP context, so they aren’t intended to be part of the public API. I’ll move them to the pcpp::internal namespace for better clarity and data organization.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM
DoIP Protocol Overview
The Diagnostic over IP (DoIP) protocol is used in automotive diagnostic systems to facilitate communication between diagnostic tools and ECUs (Electronic Control Units) over IP-based networks. It enables remote diagnostics, configuration, and software updates over Ethernet, offering an efficient and scalable solution for modern vehicles.
Header Structure (8 bytes)
Pyload types / code / structure