Skip to content

Commit cb63e77

Browse files
committed
Feature: testing + can-parse improvment for CHECKFRAME operations
can-parse CHECKFRAME now works for single frame check and whole DB checks. The CppCAN::analysis namespace contains now is_frame_layout_ok which can return a diagnosis of the errors that have been identified. assert_frame_layout() which was declared but not defined has seen its signature changed and its definition created. Finally, testing has been added with different example DBC files: both BigEndian and LittleEndian signals.
1 parent 49f6a78 commit cb63e77

15 files changed

+538
-132
lines changed

CMakeLists.txt

Lines changed: 42 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,9 @@ cmake_minimum_required(VERSION 3.7.0)
22

33
project(CPP-CAN-Parser)
44

5-
65
include(CTest)
6+
include(GenerateExportHeader)
7+
78

89
set(CPPPARSER_INCLUDE_DIRECTORY
910
${CMAKE_CURRENT_LIST_DIR}/include
@@ -12,7 +13,7 @@ set(CPPPARSER_INCLUDE_DIRECTORY
1213
set(CPPPARSER_SRC_FILES
1314
src/models/CANDatabase.cpp
1415
src/models/CANFrame.cpp
15-
src/models/CANSignal.cpp
16+
src/models/CANSignal.cpp
1617
src/parsing/DBCParser.cpp
1718
src/parsing/ParsingUtils.cpp
1819
src/parsing/Tokenizer.cpp
@@ -22,16 +23,51 @@ set(CPP_CAN_PARSER_COMPILATION_TYPE SHARED)
2223
if(CPP_CAN_PARSER_USE_STATIC)
2324
set(CPP_CAN_PARSER_COMPILATION_TYPE STATIC)
2425
endif()
25-
26-
add_library(cpp-can-parser
26+
27+
set(CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS True)
28+
add_library(cpp-can-parser
2729
${CPP_CAN_PARSER_COMPILATION_TYPE}
2830
${CPPPARSER_SRC_FILES})
2931
target_include_directories(cpp-can-parser
30-
PUBLIC ${CPPPARSER_INCLUDE_DIRECTORY})
32+
PUBLIC ${CPPPARSER_INCLUDE_DIRECTORY}
33+
${CMAKE_CURRENT_BINARY_DIR}/exports/)
34+
generate_export_header(cpp-can-parser
35+
BASE_NAME cpp_can_parser
36+
EXPORT_FILE_NAME ${CMAKE_CURRENT_BINARY_DIR}/exports/cpp_can_parser_export.h)
37+
install(FILES cpp-can-parser
38+
${CMAKE_CURRENT_BINARY_DIR}/cpp_can_parser_export.h
39+
DESTINATION ${CMAKE_CURRENT_BINARY_DIR}/include)
3140

3241
add_executable(can-parse
3342
utils/can-parse/can-parse.cpp
3443
utils/can-parse/print-frame.cpp
3544
utils/can-parse/print-single-frame.cpp
3645
utils/can-parse/check-frame.cpp)
37-
target_link_libraries(can-parse cpp-can-parser)
46+
target_link_libraries(can-parse cpp-can-parser)
47+
48+
if(BUILD_TESTING)
49+
file(COPY tests/dbc-files/
50+
DESTINATION dbc-files/)
51+
52+
add_executable(cpc-test-parsing
53+
tests/test-parsing.cpp)
54+
target_link_libraries(cpc-test-parsing PUBLIC cpp-can-parser)
55+
56+
add_test(NAME cpc-test-parsing
57+
COMMAND cpc-test-parsing)
58+
59+
add_test(NAME cpc-checkframe-1
60+
COMMAND can-parse checkframe dbc-files/single-frame-1.dbc)
61+
62+
add_test(NAME cpc-checkframe-2
63+
COMMAND can-parse checkframe dbc-files/single-frame-2.dbc)
64+
65+
add_test(NAME cpc-checkframe-big-endian-1
66+
COMMAND can-parse checkframe 294 dbc-files/big-endian-1.dbc)
67+
68+
add_test(NAME cpc-checkframe-big-endian-2
69+
COMMAND can-parse checkframe 1807 dbc-files/big-endian-1.dbc)
70+
71+
add_test(NAME cpc-checkframe-big-endian-3
72+
COMMAND can-parse checkframe 1800 dbc-files/big-endian-1.dbc)
73+
endif()

include/CANDatabase.h

Lines changed: 12 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
#include <exception>
77
#include <map>
88

9+
#include "cpp_can_parser_export.h"
910
#include "CANFrame.h"
1011
#include "CANDatabaseException.h"
1112

@@ -23,13 +24,13 @@
2324
* If the database was parsed from a file, the filename() method can be used to
2425
* retrieve the name of the source file.
2526
*/
26-
class CANDatabase {
27+
class CPP_CAN_PARSER_EXPORT CANDatabase {
2728
public:
2829
/**
2930
* @brief A parsing warning and its location
3031
*/
3132
struct parsing_warning {
32-
int line;
33+
unsigned long long line;
3334
std::string description;
3435
};
3536

@@ -73,7 +74,7 @@ class CANDatabase {
7374
/**
7475
* @brief Creates a CANDatabase object with no source file
7576
*/
76-
CANDatabase() = default;
77+
CANDatabase();
7778

7879
/**
7980
* @brief Creates a CANDatabase object that has been constructed from a file
@@ -85,23 +86,24 @@ class CANDatabase {
8586
* Creates a copy of the database: the individual frames are deep copied so there is no
8687
* shared memory betwwen the two databases.
8788
*/
88-
CANDatabase(const CANDatabase&) = default;
89+
CANDatabase(const CANDatabase&);
8990

9091
/**
9192
* @brief Makes a copy of the given database
9293
*/
93-
CANDatabase& operator=(const CANDatabase&) = default;
94+
CANDatabase& operator=(const CANDatabase&);
9495

9596
/**
9697
* @brief Moves a CANDatabase object. The CANFrame objects are NOT deep copied.
9798
*/
98-
CANDatabase(CANDatabase&&) = default;
99+
CANDatabase(CANDatabase&&);
99100

100101
/**
101-
* @see CANDatabase(const CANDatabase&&)
102+
* @see CANDatabase(CANDatabase&&)
102103
*/
103-
CANDatabase& operator=(CANDatabase&&) = default;
104+
CANDatabase& operator=(CANDatabase&&);
104105

106+
~CANDatabase();
105107
public:
106108
/**
107109
* @brief Get the frame with the given frame name
@@ -195,11 +197,8 @@ class CANDatabase {
195197
void removeFrame(const std::string& name);
196198

197199
private:
198-
std::string filename_;
199-
container_type map_; // Index by CAN ID
200-
201-
std::map<unsigned long long, IDKey> intKeyIndex_;
202-
std::map<std::string, IDKey> strKeyIndex_;
200+
class CANDatabaseImpl;
201+
CANDatabaseImpl* impl;
203202
};
204203

205204
#endif

include/CANDatabaseAnalysis.h

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,14 +7,24 @@ namespace CppCAN {
77
namespace analysis {
88
/**
99
* @brief Analyses the frame signals to see if some signals are overlapping
10+
* @param src The CANFrame instance to inspect
1011
* @return true if no overlapping is detected, false otherwise
1112
*/
1213
bool is_frame_layout_ok(const CANFrame& src);
1314

15+
/**
16+
* @brief Overload of is_frame_layout_ok() that outputs a diagnosis of the
17+
* problematic signals if a layout error is detected.
18+
* @param src The CANFrame instance to inspect
19+
* @param diagnosis Filled with the names of all the overlapping signals
20+
* @return true if no overlapping is detected, false otherwise
21+
*/
22+
bool is_frame_layout_ok(const CANFrame& src, std::vector<std::string>& diagnosis);
23+
1424
/**
1525
* @brief Like is_frame_layout_ok() but throws a CANDatabaseException if the layout is ill-formed
1626
*/
17-
void assert_frame_layout();
27+
void assert_frame_layout(const CANFrame& src);
1828
}
1929
} // namespace CppCAN
2030

src/analysis/CANFrameAnalysis.cpp

Lines changed: 86 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,33 @@
11
#include "CANDatabaseAnalysis.h"
2+
#include "CANDatabaseException.h"
23
#include <algorithm>
34
#include <cmath>
45
#include <tuple>
6+
#include <set>
57

6-
// For each entry: (byte, lr start bit, lr end bit)
8+
/**
9+
* For each entry: byte, [lr_start_bit, lr_end_bit)
10+
* Few explanations about the attributes:
11+
* lr_start_bit: Start bit position in the byte (included)
12+
* lr_end_bit: End bit position in the byte (excluded)
13+
*/
714
struct SignalRange {
815
uint8_t byte;
9-
uint8_t lr_start_bit;
10-
uint8_t lr_end_bit;
16+
char lr_start_bit;
17+
char lr_end_bit;
1118
};
1219

1320
using SignalRanges = std::vector<SignalRange>;
1421

15-
/**
16-
* Few explanations about the attributes:
17-
* lr_start_bit: Start bit position reading from left to right (ie bit 7 of byte 1 is 0, bit 7 of byte 2 is 9, ...)
18-
* lr_end_bit: same but for the end bit.
19-
*/
22+
static void
23+
convert_to_big_endian_layout(SignalRanges& src) {
24+
// src is assumed to be LittleEndian
25+
for(SignalRange& r : src) {
26+
r.lr_start_bit = 7 - r.lr_start_bit;
27+
r.lr_end_bit = 7 - r.lr_end_bit;
28+
}
29+
}
30+
2031
struct SignalLayoutEntry {
2132
SignalLayoutEntry() = delete;
2233
SignalLayoutEntry(const CANSignal* src, SignalRanges&& r)
@@ -35,52 +46,56 @@ struct SignalLayoutEntry {
3546

3647
SignalRanges big_endian_ranges(const CANSignal& src) {
3748
SignalRanges result;
38-
49+
3950
// For BigEndian signals, the start bit already represents the left mostbit
40-
// of the signal. Therefore, it is only required to transform it into a "human-readable"
41-
// value, ie. when looking at the frame from left to right.
42-
// ----------------- -----------------
43-
// |*|*|*|*|*|*|*|*| |*|*|*|*|*|*|*|*|
44-
// ----------------- -----------------
45-
// 0 7 instead of 7 0
46-
//
47-
// Example: Start bit 4 becomes LR start bit 3
48-
unsigned bitsAnalyzed = 0;
49-
bool is_start_byte = 0;
51+
// ----------------- -----------------
52+
// |*|*|*|*|*|*|*|*| |*|*|*|*|*|*|*|*|
53+
// ----------------- -----------------
54+
// 7 0 15 8
55+
56+
unsigned bitsLeft = src.length();
57+
unsigned currentPos = src.start_bit();
5058

51-
for(unsigned current_byte = src.start_bit() / 8; bitsAnalyzed < src.length(); current_byte++, is_start_byte = false) {
52-
unsigned lbit = is_start_byte ? (7 - src.start_bit() % 8) : 0;
53-
unsigned rbit = std::min(7u, src.start_bit() - bitsAnalyzed);
59+
for(unsigned current_byte = src.start_bit() / 8; bitsLeft > 0; current_byte++) {
60+
char lbit = currentPos % 8;
61+
char rbit = std::max<char>(-1, lbit - bitsLeft);
5462

5563
// The static_cast are not "necessary" but it removes some warnings
5664
result.push_back({ static_cast<uint8_t>(current_byte),
57-
static_cast<uint8_t>(lbit),
58-
static_cast<uint8_t>(rbit) });
65+
lbit, rbit });
66+
67+
bitsLeft -= lbit - rbit;
68+
currentPos += (lbit - rbit);
5969
}
6070

6171
return result;
6272
}
6373

6474
SignalRanges little_endian_ranges(const CANSignal& src) {
75+
// For LittleEndian signals, act like the bits are reversed in the byte:
76+
// ----------------- -----------------
77+
// |*|*|*|*|*|*|*|*| |*|*|*|*|*|*|*|*|
78+
// ----------------- -----------------
79+
// 0 7 8 15
80+
//
81+
// The signal can be found from the start bit + read to the right.
6582
SignalRanges result;
6683

67-
// For LittleEndian signals, the start bit represents the LSB of the signal
68-
// which does not really represent anything in terms of layout. So we need
69-
// to compute the ranges for each byte individually anyway.
70-
84+
if(src.length() == 0) // length is 0, we return an empty result.
85+
return result;
86+
7187
unsigned bitsLeft = src.length();
72-
bool is_start_byte = 0;
73-
74-
for(unsigned current_byte = src.start_bit() / 8; bitsLeft > 0; current_byte++, is_start_byte = false) {
75-
unsigned lbit = 7 -( src.start_bit() + bitsLeft);
76-
unsigned rbit = 7 - src.start_bit();
88+
unsigned currentPos = src.start_bit();
89+
for(unsigned current_byte = src.start_bit() / 8; bitsLeft > 0; current_byte++) {
90+
char lbit = currentPos % 8;
91+
char rbit = std::min<char>(lbit + bitsLeft, 8);
7792

7893
// The static_cast are not "necessary" but it removes some warnings
7994
result.push_back({ static_cast<uint8_t>(current_byte),
80-
static_cast<uint8_t>(lbit),
81-
static_cast<uint8_t>(rbit) });
95+
lbit, rbit });
8296

83-
bitsLeft -= (rbit - lbit);
97+
bitsLeft -= rbit - lbit;
98+
currentPos += rbit - lbit;
8499
}
85100

86101
return result;
@@ -91,14 +106,14 @@ std::vector<SignalLayoutEntry> compute_layout(const CANFrame& src) {
91106

92107
for(const auto& signal: src) {
93108
const CANSignal& sig = signal.second;
94-
int lr_start_bit, lr_end_bit;
95109

96110
if(sig.endianness() == CANSignal::BigEndian) {
97111
auto ranges = big_endian_ranges(sig);
98112
result.emplace_back(&sig, std::move(ranges));
99113
}
100114
else {
101115
auto ranges = little_endian_ranges(sig);
116+
convert_to_big_endian_layout(ranges);
102117
result.emplace_back(&sig, std::move(ranges));
103118
}
104119
}
@@ -118,7 +133,7 @@ bool overlap(const SignalLayoutEntry& e1, const SignalLayoutEntry& e2) {
118133
// ordered.first is the leftmost SignalRange in the byte
119134
// ordered.second is the rightmost SignalRange in the byte
120135
auto ordered = std::minmax(r1, r2, [](const SignalRange& r, const SignalRange& rr) {
121-
return r.lr_start_bit < rr.lr_start_bit;
136+
return r.lr_start_bit > rr.lr_start_bit;
122137
});
123138

124139
// No overlapping if the last bit of the leftmost is before the first
@@ -135,13 +150,43 @@ bool CppCAN::analysis::is_frame_layout_ok(const CANFrame& src) {
135150
auto layout = compute_layout(src);
136151

137152
for(size_t i = 0; i < layout.size(); i++) {
138-
const SignalLayoutEntry& e = layout[i];
139-
140153
for(size_t j = i + 1; j < layout.size(); j++) {
141-
if(overlap(layout[i], layout[j]))
154+
if(overlap(layout[i], layout[j])) {
142155
return false;
156+
}
143157
}
144158
}
145159

146160
return true;
161+
}
162+
163+
bool CppCAN::analysis::is_frame_layout_ok(const CANFrame& src, std::vector<std::string>& diagnosis) {
164+
auto layout = compute_layout(src);
165+
diagnosis.clear();
166+
167+
std::set<size_t> diagnosis_indices;
168+
auto report_issue = [&diagnosis, &diagnosis_indices](size_t idx, const CANSignal& sig) {
169+
if(diagnosis_indices.count(idx) == 0) {
170+
diagnosis_indices.insert(idx);
171+
diagnosis.push_back(sig.name());
172+
}
173+
};
174+
175+
for(size_t i = 0; i < layout.size(); i++) {
176+
for(size_t j = i + 1; j < layout.size(); j++) {
177+
if(overlap(layout[i], layout[j])) {
178+
report_issue(i, *layout[i].src_signal);
179+
report_issue(j, *layout[j].src_signal);
180+
}
181+
}
182+
}
183+
184+
return diagnosis_indices.size() == 0;
185+
}
186+
187+
void CppCAN::analysis::assert_frame_layout(const CANFrame& src) {
188+
if(!is_frame_layout_ok(src)) {
189+
std::string text = "assert_frame_layout() failed for frame \"" + src.name() + "\"";
190+
throw CANDatabaseException(text);
191+
}
147192
}

0 commit comments

Comments
 (0)