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