DBC Framework
DBC parsing and CAN signal decoding framework
Loading...
Searching...
No Matches
dbc_statement_handler.cpp
1// SPDX-License-Identifier: MIT
2#include "dbc_statement_handler.h"
3#include <cctype>
4#include <charconv>
5#include <cmath>
6#include <limits>
7#include <regex>
8#include <sstream>
9#include <stdexcept>
10
11namespace {
12 std::uint32_t ParseDecimal(const std::string &text,
13 std::uint32_t maximum,
14 const char *field) {
15 std::uint32_t value = 0U;
16 const auto parsed = std::from_chars(text.data(), text.data() + text.size(), value);
17 if (text.empty() || parsed.ec != std::errc() ||
18 parsed.ptr != text.data() + text.size() || value > maximum)
19 throw std::runtime_error(std::string("Invalid ") + field + ": " + text);
20 return value;
21 }
22
28 std::string TrimText(const std::string &text) {
29 std::string::size_type begin = 0U;
30 while ((begin < text.size()) &&
31 std::isspace(static_cast<unsigned char>(text[begin])))
32 ++begin;
33
34 std::string::size_type end = text.size();
35 while ((end > begin) &&
36 std::isspace(static_cast<unsigned char>(text[end - 1U])))
37 --end;
38
39 return text.substr(begin, end - begin);
40 }
41 FrameInfo *FindFrameById(DbcDatabase &, std::uint32_t, bool);
42 SignalInfo *FindSignalByName(FrameInfo &, const std::string &);
43}// namespace
44
45namespace {
46 std::vector<std::string> SplitReceivers(const std::string &text) {
47 std::vector<std::string> receivers;
48 std::string token;
49 std::istringstream stream(text);
50
51 while (std::getline(stream, token, ',')) {
52 token = TrimText(token);
53 if (!token.empty())
54 receivers.push_back(token);
55 }
56
57 return receivers;
58 }
59
60 void NormalizeCanId(std::uint32_t rawCanId,
61 std::uint32_t &normalizedCanId,
62 bool &isExtended) {
63 /*
64 * DBC commonly stores extended identifiers with bit 31 set.
65 * Example:
66 * raw id = 0x80000000 | actual_29_bit_id
67 */
68 if ((rawCanId & 0x80000000U) != 0U) {
69 isExtended = true;
70 normalizedCanId = (rawCanId & 0x1FFFFFFFU);
71 } else {
72 isExtended = (rawCanId > 0x7FFU);
73 normalizedCanId = rawCanId;
74 }
75 }
76
77 std::uint32_t TryExtractPgn(std::uint32_t canId, bool isExtended, bool &hasPgn) {
78 hasPgn = false;
79
80 if (!isExtended)
81 return 0U;
82
83 if ((canId & 0x1FFFFFFFU) != canId)
84 return 0U;
85
86 const std::uint32_t pf = (canId >> 16U) & 0xFFU;
87 const std::uint32_t ps = (canId >> 8U) & 0xFFU;
88 const std::uint32_t dp = (canId >> 24U) & 0x01U;
89
90 std::uint32_t pgn = 0U;
91
92 if (pf < 240U)
93 pgn = (dp << 16U) | (pf << 8U);
94 else
95 pgn = (dp << 16U) | (pf << 8U) | ps;
96
97 hasPgn = true;
98 return pgn;
99 }
100
101}// namespace
102
103FrameInfo DbcFrameHandler::ParseFrameLine(const std::string &line) {
104 /*
105 * Example:
106 * BO_ 256 EngineData: 8 EEC1
107 */
108
109 std::istringstream stream(line);
110 std::string keyword;
111 std::string idText;
112 if (!(stream >> keyword >> idText) || keyword != "BO_")
113 throw std::runtime_error("Missing CAN ID in frame definition.");
114
115 const std::uint32_t rawCanId = ParseDecimal(
116 idText, std::numeric_limits<std::uint32_t>::max(), "CAN ID");
117 // Only bit 31 may accompany the actual 29-bit identifier.
118 if ((rawCanId & 0x60000000U) != 0U)
119 throw std::runtime_error("CAN ID exceeds the 29-bit identifier range.");
120
121 std::string nameText;
122 if (!std::getline(stream, nameText, ':') || stream.eof())
123 throw std::runtime_error("Missing ':' in frame definition.");
124 std::istringstream nameStream(nameText);
125 FrameInfo frame;
126 std::string extra;
127 if (!(nameStream >> frame.name) || (nameStream >> extra))
128 throw std::runtime_error("Missing or invalid frame name.");
129
130 std::string lengthText;
131 if (!(stream >> lengthText))
132 throw std::runtime_error("Missing payload length in frame definition.");
133 frame.dlc = static_cast<std::uint8_t>(ParseDecimal(
134 lengthText, std::numeric_limits<std::uint8_t>::max(), "payload length"));
135 if (!(stream >> frame.transmitter))
136 throw std::runtime_error("Missing transmitter in frame definition.");
137 if (stream >> extra)
138 throw std::runtime_error("Unexpected trailing field in frame definition: " + extra);
139
140 NormalizeCanId(rawCanId, frame.canId, frame.isExtended);
141 frame.pgn = TryExtractPgn(frame.canId, frame.isExtended, frame.hasPgn);
142
143 return frame;
144}
145
146SignalInfo DbcSignalHandler::ParseSignalLine(const std::string &line) {
147 // Match the complete statement before converting any numeric fields.
148 static const std::regex syntax(
149 R"dbc(^SG_[ \t]+([^:]+):[ \t]*([^| \t]+)[ \t]*\|[ \t]*([^@ \t]+)[ \t]*@[ \t]*([01])([+-])[ \t]*\‍([ \t]*([^, \t]+)[ \t]*,[ \t]*([^ )\t]+)[ \t]*\)[ \t]*\[[ \t]*([^| \t]+)[ \t]*\|[ \t]*([^\] \t]+)[ \t]*\][ \t]*"([^"\r\n]*)"[ \t]+([^ \t,]+(?:[ \t]*,[ \t]*[^ \t,]+)*)[ \t]*$)dbc");
150 std::smatch fields;
151 if (!std::regex_match(line, fields, syntax))
152 throw std::runtime_error("Malformed or unsupported SG_ signal definition.");
153
154 SignalInfo signal;
155 signal.name = TrimText(fields[1].str());
156 if (signal.name.empty() || signal.name.find_first_of(" \t\r\n") != std::string::npos)
157 throw std::runtime_error("Invalid signal name or unsupported multiplexing syntax.");
158
159 signal.startBit = ParseDecimal(fields[2].str(),
160 std::numeric_limits<std::uint32_t>::max(), "signal start bit");
161 signal.length = ParseDecimal(fields[3].str(), 64U, "signal length");
162 if (signal.length == 0U)
163 throw std::runtime_error("Signal length must be within 1..64 bits.");
164 signal.isLittleEndian = fields[4].str() == "1";
165 signal.isSigned = fields[5].str() == "-";
166
167 const auto parsePhysical = [](const std::string &text, const char *field) {
168 static const std::regex number(
169 R"(^[+-]?(?:[0-9]+(?:\.[0-9]*)?|\.[0-9]+)(?:[eE][+-]?[0-9]+)?$)");
170 if (!std::regex_match(text, number))
171 throw std::runtime_error(std::string("Invalid ") + field + ": " + text);
172 std::size_t consumed = 0U;
173 double value;
174 try {
175 value = std::stod(text, &consumed);
176 } catch (const std::exception &) {
177 throw std::runtime_error(std::string("Invalid ") + field + ": " + text);
178 }
179 if (consumed != text.size() || !std::isfinite(value))
180 throw std::runtime_error(std::string("Invalid ") + field + ": " + text);
181 return value;
182 };
183 signal.factor = parsePhysical(fields[6].str(), "signal factor");
184 signal.offset = parsePhysical(fields[7].str(), "signal offset");
185 signal.minimum = parsePhysical(fields[8].str(), "signal minimum");
186 signal.maximum = parsePhysical(fields[9].str(), "signal maximum");
187 if (signal.minimum > signal.maximum)
188 throw std::runtime_error("Signal minimum exceeds maximum.");
189 signal.unit = fields[10].str();
190 signal.receivers = SplitReceivers(fields[11].str());
191 return signal;
192}
193
194void DbcCommentHandler::ParseCommentLine(const std::string &line, DbcDatabase &database) {
195 /*
196 * Examples:
197 * CM_ BO_ 256 "Frame comment";
198 * CM_ SG_ 256 EngineSpeed "Signal comment";
199 */
200
201 std::istringstream stream(line);
202 std::string token;
203 stream >> token;
204
205 if (token != "CM_")
206 return;
207
208 stream >> token;
209
210 if (token == "BO_") {
211 std::uint32_t rawCanId = 0U;
212 std::uint32_t canId = 0U;
213 bool isExtended = false;
214
215 stream >> rawCanId;
216 NormalizeCanId(rawCanId, canId, isExtended);
217
218 const std::string::size_type quoteBegin = line.find('"');
219 const std::string::size_type quoteEnd = line.rfind('"');
220
221 if ((quoteBegin == std::string::npos) ||
222 (quoteEnd == std::string::npos) ||
223 (quoteEnd <= quoteBegin))
224 return;
225
226 FrameInfo *frame = FindFrameById(database, canId, isExtended);
227 if (frame != nullptr)
228 frame->comment = line.substr(quoteBegin + 1U, quoteEnd - quoteBegin - 1U);
229 } else if (token == "SG_") {
230 std::uint32_t rawCanId = 0U;
231 std::uint32_t canId = 0U;
232 bool isExtended = false;
233 std::string signalName;
234
235 stream >> rawCanId;
236 stream >> signalName;
237
238 NormalizeCanId(rawCanId, canId, isExtended);
239
240 const std::string::size_type quoteBegin = line.find('"');
241 const std::string::size_type quoteEnd = line.rfind('"');
242
243 if ((quoteBegin == std::string::npos) ||
244 (quoteEnd == std::string::npos) ||
245 (quoteEnd <= quoteBegin))
246 return;
247
248 FrameInfo *frame = FindFrameById(database, canId, isExtended);
249 if (frame != nullptr) {
250 SignalInfo *signal = FindSignalByName(*frame, signalName);
251 if (signal != nullptr)
252 signal->comment = line.substr(quoteBegin + 1U, quoteEnd - quoteBegin - 1U);
253 }
254 }
255}
256
257namespace {
258 FrameInfo *FindFrameById(DbcDatabase &database,
259 std::uint32_t canId,
260 bool isExtended) {
261 for (std::size_t index = 0U; index < database.frames.size(); ++index) {
262 if ((database.frames[index].canId == canId) &&
263 (database.frames[index].isExtended == isExtended))
264 return &database.frames[index];
265 }
266
267 return nullptr;
268 }
269
270 SignalInfo *FindSignalByName(FrameInfo &frame, const std::string &signalName) {
271 for (std::size_t index = 0U; index < frame.signals.size(); ++index) {
272 if (frame.signals[index].name == signalName)
273 return &frame.signals[index];
274 }
275
276 return nullptr;
277 }
278}// namespace
279
280void DbcFrameHandler::Handle(const std::string &line, DbcParseContext &context) const {
281 context.database.frames.push_back(ParseFrameLine(line));
282 context.currentFrame = &context.database.frames.back();
283}
284
285void DbcSignalHandler::Handle(const std::string &line, DbcParseContext &context) const {
286 if (context.currentFrame == nullptr)
287 throw std::runtime_error("Signal found before any frame definition.");
288 SignalInfo signal = ParseSignalLine(line);
289 const std::uint64_t payloadBits = static_cast<std::uint64_t>(context.currentFrame->dlc) * 8U;
290 std::uint64_t bit = signal.startBit;
291 for (std::uint32_t index = 0U; index < signal.length; ++index) {
292 if (bit >= payloadBits)
293 throw std::runtime_error("Signal exceeds the frame payload length.");
294 if (signal.isLittleEndian)
295 ++bit;
296 else
297 bit = (bit % 8U == 0U) ? bit + 15U : bit - 1U;
298 }
299 context.currentFrame->signals.push_back(std::move(signal));
300}
301
302void DbcCommentHandler::Handle(const std::string &line, DbcParseContext &context) const {
303 if (line.compare(0U, 4U, "CM_ ") == 0)
304 ParseCommentLine(line, context.database);
305}
306
307DbcStatementDispatcher::DbcStatementDispatcher() {
308 RegisterType<DbcFrameHandler>("BO_");
309 RegisterType<DbcSignalHandler>("SG_");
310 RegisterType<DbcCommentHandler>("CM_");
311}
312
313bool DbcStatementDispatcher::Dispatch(const std::string &line,
314 DbcParseContext &context) const {
315 std::istringstream stream(line);
316 std::string keyword;
317 stream >> keyword;
318 const auto handler = m_handlers.find(keyword);
319 if (handler == m_handlers.end())
320 return false;
321 handler->second->Handle(line, context);
322 return true;
323}
bool Dispatch(const std::string &line, DbcParseContext &context) const
Parsed DBC content stored in a simple internal form.
std::vector< FrameInfo > frames
Describes one CAN frame from a DBC file.
Definition frame_info.h:24
bool isExtended
Definition frame_info.h:27
bool hasPgn
Definition frame_info.h:29
std::string transmitter
Definition frame_info.h:31
std::uint8_t dlc
Definition frame_info.h:30
std::string name
Definition frame_info.h:25
std::string comment
Definition frame_info.h:32
std::uint32_t pgn
Definition frame_info.h:28
std::uint32_t canId
Definition frame_info.h:26
Describes one signal inside a DBC frame.
Definition signal_info.h:22
std::uint32_t length
Definition signal_info.h:25
std::string unit
Definition signal_info.h:32
std::vector< std::string > receivers
Definition signal_info.h:33
std::string name
Definition signal_info.h:23
bool isSigned
Definition signal_info.h:27
double offset
Definition signal_info.h:29
std::string comment
Definition signal_info.h:34
std::uint32_t startBit
Definition signal_info.h:24
double minimum
Definition signal_info.h:30
double maximum
Definition signal_info.h:31
double factor
Definition signal_info.h:28
bool isLittleEndian
Definition signal_info.h:26