DBC Framework
DBC parsing and CAN signal decoding framework
Loading...
Searching...
No Matches
decode_database.cpp
1// SPDX-License-Identifier: MIT
2// Copyright (c) 2026 Dim Himro
3
4#include "decode_database.h"
5#include "j1939.h"
6#include "multiplex_graph.h"
7#include "signal_layout.h"
8#include <bitset>
9#include <cmath>
10#include <limits>
11#include <stdexcept>
12#include <unordered_set>
13
14namespace {
15 constexpr std::size_t MaximumPayloadBits = std::numeric_limits<std::uint8_t>::max() * 8U;
16 using SignalBits = std::bitset<MaximumPayloadBits>;
17
18 void ValidateSignal(const DecodeSignal &signal, std::uint8_t dlc) {
19 if (signal.byteOrder != ByteOrder::Intel && signal.byteOrder != ByteOrder::Motorola)
20 throw std::invalid_argument("Invalid signal byte order: " + signal.name);
21 if (signal.valueType != ValueType::Unsigned && signal.valueType != ValueType::Signed)
22 throw std::invalid_argument("Invalid signal value type: " + signal.name);
23 if (signal.multiplexing.role != MultiplexRole::Unconditional &&
24 signal.multiplexing.role != MultiplexRole::Selector &&
25 signal.multiplexing.role != MultiplexRole::Branch &&
26 signal.multiplexing.role != MultiplexRole::BranchSelector)
27 throw std::invalid_argument("Invalid multiplexing role: " + signal.name);
28 if (!IsValidSignalLayout(signal.startBit, signal.length,
29 signal.byteOrder == ByteOrder::Intel, dlc))
30 throw std::invalid_argument("Invalid signal length or payload position: " + signal.name);
31 if (!std::isfinite(signal.factor) || !std::isfinite(signal.offset) ||
32 !std::isfinite(signal.minimum) || !std::isfinite(signal.maximum) ||
33 signal.minimum > signal.maximum)
34 throw std::invalid_argument("Invalid signal numeric parameters: " + signal.name);
35 }
36
37 SignalBits GetSignalBits(const DecodeSignal &signal) {
38 SignalBits bits;
39 const bool littleEndian = signal.byteOrder == ByteOrder::Intel;
40 const std::uint64_t start = SignalStartCoordinate(signal.startBit, littleEndian);
41 for (std::uint32_t index = 0U; index < signal.length; ++index) {
42 const std::uint64_t coordinate = start + index;
43 const std::uint64_t bit = littleEndian ? coordinate
44 : coordinate / 8U * 8U + 7U - coordinate % 8U;
45 bits.set(static_cast<std::size_t>(bit));
46 }
47 return bits;
48 }
49
50 dbc_detail::MultiplexGraph ValidateFrame(const DecodeFrame &frame) {
51 const std::uint32_t maximumId = frame.isExtended ? 0x1FFFFFFFU : 0x7FFU;
52 if (frame.canId > maximumId)
53 throw std::invalid_argument("Invalid normalized CAN ID: " + std::to_string(frame.canId));
54
55 if (frame.hasPgn && (!frame.isExtended || frame.pgn != ExtractJ1939Pgn(frame.canId)))
56 throw std::invalid_argument("Invalid J1939 PGN metadata: " + frame.name);
57
58 std::unordered_set<std::string> names;
59
60 for (const DecodeSignal &signal: frame.signals) {
61 if (!names.insert(signal.name).second)
62 throw std::invalid_argument("Duplicate runtime signal key: " + signal.name);
63 ValidateSignal(signal, frame.dlc);
64 if (IsMultiplexSelector(signal.multiplexing.role) && signal.valueType != ValueType::Unsigned)
65 throw std::invalid_argument("Multiplex selectors must be unsigned.");
66 }
67 const dbc_detail::MultiplexGraph graph(frame.signals);
68
69 std::vector<SignalBits> layouts;
70 layouts.reserve(frame.signals.size());
71 for (std::size_t index = 0U; index < frame.signals.size(); ++index) {
72 const DecodeSignal &signal = frame.signals[index];
73 const SignalBits bits = GetSignalBits(signal);
74 for (std::size_t previous = 0U; previous < index; ++previous) {
75 if (!graph.AreMutuallyExclusive(index, previous) && (bits & layouts[previous]).any())
76 throw std::invalid_argument("Overlapping signals: " + signal.name);
77 }
78 layouts.push_back(bits);
79 }
80 return graph;
81 }
82}// namespace
83
84DecodeDatabase::Storage::Storage(const std::vector<DecodeFrame> &definitions)
85 : frames(definitions) {
86 // Validate the complete snapshot before creating its lookup index.
87 std::unordered_set<FrameKey, FrameKeyHasher> keys;
88 keys.reserve(frames.size());
89 multiplexGraphs.reserve(frames.size());
90 for (const DecodeFrame &frame: frames) {
91 multiplexGraphs.push_back(ValidateFrame(frame));
92 if (!keys.insert(frame.GetKey()).second)
93 throw std::invalid_argument("Duplicate runtime frame key");
94 }
95 frameIndexByKey.reserve(frames.size());
96 for (std::size_t index = 0U; index < frames.size(); ++index) {
97 frameIndexByKey.emplace(frames[index].GetKey(), index);
98 if (frames[index].hasPgn)
99 frameIndicesByPgn[frames[index].pgn].push_back(index);
100 }
101}
102
103DecodeDatabase::DecodeDatabase(const std::vector<DecodeFrame> &frames)
104 : storage(std::make_shared<const Storage>(frames)) {
105}
106
107const std::vector<DecodeFrame> &DecodeDatabase::GetFrames() const {
108 static const std::vector<DecodeFrame> empty;
109 return storage ? storage->frames : empty;
110}
111
112std::shared_ptr<const DecodeFrame> DecodeDatabase::FindFrame(const FrameKey &key) const {
113 if (!storage)
114 return nullptr;
115 const std::unordered_map<FrameKey, std::size_t, FrameKeyHasher>::const_iterator it =
116 storage->frameIndexByKey.find(key);
117 if (it == storage->frameIndexByKey.end())
118 return nullptr;
119 return std::shared_ptr<const DecodeFrame>(storage, &storage->frames[it->second]);
120}
121
123 PgnLookupResult result;
124 if (!storage)
125 return result;
126 const std::unordered_map<std::uint32_t, std::vector<std::size_t>>::const_iterator it =
127 storage->frameIndicesByPgn.find(pgn);
128 if (it == storage->frameIndicesByPgn.end())
129 return result;
130 if (it->second.size() != 1U) {
131 result.ambiguous = true;
132 return result;
133 }
134 result.definition = std::shared_ptr<const DecodeFrame>(storage, &storage->frames[it->second[0]]);
135 return result;
136}
137
138std::shared_ptr<const DecodeSignal> DecodeDatabase::FindSignal(const SignalKey &key) const {
139 const std::shared_ptr<const DecodeFrame> frame = FindFrame(key.frame);
140 if (!frame)
141 return nullptr;
142 for (const DecodeSignal &signal: frame->signals) {
143 if (signal.name == key.name)
144 return std::shared_ptr<const DecodeSignal>(frame, &signal);
145 }
146 return nullptr;
147}
148
149const dbc_detail::MultiplexGraph &DecodeDatabase::GetMultiplexGraph(const FrameKey &key) const {
150 return storage->multiplexGraphs[storage->frameIndexByKey.at(key)];
151}
std::shared_ptr< const DecodeSignal > FindSignal(const SignalKey &key) const
Return an owning signal handle, or an empty handle for a missing key.
std::shared_ptr< const DecodeFrame > FindFrame(const FrameKey &key) const
const std::vector< DecodeFrame > & GetFrames() const
PgnLookupResult FindFrameByPgn(std::uint32_t pgn) const
Runtime definitions and immutable indexed database.
Runtime-ready frame definition.
FrameKey GetKey() const
Return the stable normalized frame identifier.
std::uint32_t pgn
std::uint8_t dlc
std::uint32_t canId
std::string name
Runtime-ready signal definition.
std::string name
ValueType valueType
std::uint32_t startBit
ByteOrder byteOrder
std::uint32_t length
Multiplexing multiplexing
Stable frame identifier: normalized CAN ID and frame type.
std::shared_ptr< const DecodeFrame > definition
FrameKey frame
std::string name