Closes #16
This commit is contained in:
1 parent
7022c84941
commit
c3d3bd51fb
72 files changed
+3703
-2484
No files matched your search
@@ -8,6 +8,7 @@ SOURCES += \
|
||||
$$PWD/tree_node.cpp
|
||||
|
||||
HEADERS += \
|
||||
$$PWD/signal_layout.h \
|
||||
$$PWD/definition_key.h \
|
||||
$$PWD/dbc_diagnostic.h \
|
||||
$$PWD/multiplexing.h \
|
||||
|
||||
@@ -53,5 +53,5 @@ DecodeDatabase DbcDecodeBuilder::Build(const DbcDatabase &source) const {
|
||||
frames.push_back(std::move(targetFrame));
|
||||
}
|
||||
|
||||
return DecodeDatabase(std::move(frames));
|
||||
return DecodeDatabase(frames);
|
||||
}
|
||||
@@ -24,7 +24,7 @@ public:
|
||||
* @brief Build runtime decode database.
|
||||
* @param source Parsed DBC database.
|
||||
* @return Immutable runtime snapshot independent of source lifetime.
|
||||
* @throws std::invalid_argument for duplicate frame keys or signal names.
|
||||
* @throws std::invalid_argument for invalid metadata or duplicate keys.
|
||||
*/
|
||||
DecodeDatabase Build(const DbcDatabase &source) const;
|
||||
};
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
*/
|
||||
|
||||
#include "dbc_decoder.h"
|
||||
#include "signal_layout.h"
|
||||
#include <cmath>
|
||||
#include <utility>
|
||||
|
||||
@@ -58,11 +59,8 @@ bool DbcDecoder::IsValidDefinition(const DecodeSignal &signal, std::uint8_t dlc)
|
||||
signal.multiplexing.role != MultiplexRole::Branch))
|
||||
return false;
|
||||
|
||||
// Use widened coordinates to avoid overflow on malformed start bits.
|
||||
const std::uint64_t start = signal.byteOrder == ByteOrder::Intel
|
||||
? signal.startBit
|
||||
: std::uint64_t(signal.startBit / 8U) * 8U + 7U - signal.startBit % 8U;
|
||||
return start + signal.length <= std::uint64_t(dlc) * 8U;
|
||||
return IsValidSignalLayout(signal.startBit, signal.length,
|
||||
signal.byteOrder == ByteOrder::Intel, dlc);
|
||||
}
|
||||
|
||||
DbcDecoder::SelectorState DbcDecoder::ReadSelector(const DecodeFrame &definition,
|
||||
@@ -154,8 +152,8 @@ bool DbcDecoder::ExtractIntel(const std::vector<std::uint8_t> &data,
|
||||
value = 0U;
|
||||
|
||||
for (std::uint32_t bitIndex = 0U; bitIndex < length; ++bitIndex) {
|
||||
const std::uint32_t absoluteBit = startBit + bitIndex;
|
||||
const std::uint32_t byteIndex = absoluteBit / 8U;
|
||||
const std::uint64_t absoluteBit = std::uint64_t(startBit) + bitIndex;
|
||||
const std::uint64_t byteIndex = absoluteBit / 8U;
|
||||
const std::uint32_t bitInByte = absoluteBit % 8U;
|
||||
|
||||
if (byteIndex >= data.size())
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
#include "dbc_statement_handler.h"
|
||||
#include "text_utils.h"
|
||||
#include "signal_layout.h"
|
||||
#include <charconv>
|
||||
#include <cmath>
|
||||
#include <limits>
|
||||
@@ -356,13 +357,9 @@ void DbcSignalHandler::Handle(const std::string &line, DbcParseContext &context)
|
||||
if (FindSignalByName(*context.currentFrame, signal.name) != nullptr)
|
||||
throw std::runtime_error("Duplicate signal name '" + signal.name +
|
||||
"' in frame '" + context.currentFrame->name + "'.");
|
||||
const std::uint64_t payloadBits = static_cast<std::uint64_t>(context.currentFrame->dlc) * 8U;
|
||||
std::uint64_t bit = signal.startBit;
|
||||
for (std::uint32_t index = 0U; index < signal.length; ++index) {
|
||||
if (bit >= payloadBits)
|
||||
throw std::runtime_error("Signal exceeds the frame payload length.");
|
||||
bit = NextSignalBit(bit, signal.isLittleEndian);
|
||||
}
|
||||
if (!IsValidSignalLayout(signal.startBit, signal.length, signal.isLittleEndian,
|
||||
context.currentFrame->dlc))
|
||||
throw std::runtime_error("Signal exceeds the frame payload length.");
|
||||
context.currentFrame->signals.push_back(std::move(signal));
|
||||
context.signalLines.back().push_back(context.lineNumber);
|
||||
}
|
||||
|
||||
+88
-11
@@ -2,27 +2,104 @@
|
||||
// Copyright (c) 2026 Dim Himro
|
||||
|
||||
#include "decode_database.h"
|
||||
#include "signal_layout.h"
|
||||
#include <bitset>
|
||||
#include <cmath>
|
||||
#include <limits>
|
||||
#include <stdexcept>
|
||||
#include <unordered_set>
|
||||
#include <utility>
|
||||
|
||||
DecodeDatabase::Storage::Storage(std::vector<DecodeFrame> definitions)
|
||||
: frames(std::move(definitions)) {
|
||||
frameIndexByKey.reserve(frames.size());
|
||||
for (std::size_t index = 0U; index < frames.size(); ++index) {
|
||||
const DecodeFrame &frame = frames[index];
|
||||
if (!frameIndexByKey.emplace(frame.GetKey(), index).second)
|
||||
throw std::invalid_argument("Duplicate runtime frame key");
|
||||
namespace {
|
||||
constexpr std::size_t MaximumPayloadBits = std::numeric_limits<std::uint8_t>::max() * 8U;
|
||||
using SignalBits = std::bitset<MaximumPayloadBits>;
|
||||
|
||||
void ValidateSignal(const DecodeSignal &signal, std::uint8_t dlc) {
|
||||
if (signal.byteOrder != ByteOrder::Intel && signal.byteOrder != ByteOrder::Motorola)
|
||||
throw std::invalid_argument("Invalid signal byte order: " + signal.name);
|
||||
if (signal.valueType != ValueType::Unsigned && signal.valueType != ValueType::Signed)
|
||||
throw std::invalid_argument("Invalid signal value type: " + signal.name);
|
||||
if (signal.multiplexing.role != MultiplexRole::Unconditional &&
|
||||
signal.multiplexing.role != MultiplexRole::Selector &&
|
||||
signal.multiplexing.role != MultiplexRole::Branch)
|
||||
throw std::invalid_argument("Invalid multiplexing role: " + signal.name);
|
||||
if (!IsValidSignalLayout(signal.startBit, signal.length,
|
||||
signal.byteOrder == ByteOrder::Intel, dlc))
|
||||
throw std::invalid_argument("Invalid signal length or payload position: " + signal.name);
|
||||
if (!std::isfinite(signal.factor) || !std::isfinite(signal.offset) ||
|
||||
!std::isfinite(signal.minimum) || !std::isfinite(signal.maximum) ||
|
||||
signal.minimum > signal.maximum)
|
||||
throw std::invalid_argument("Invalid signal numeric parameters: " + signal.name);
|
||||
}
|
||||
|
||||
SignalBits GetSignalBits(const DecodeSignal &signal) {
|
||||
SignalBits bits;
|
||||
const bool littleEndian = signal.byteOrder == ByteOrder::Intel;
|
||||
const std::uint64_t start = SignalStartCoordinate(signal.startBit, littleEndian);
|
||||
for (std::uint32_t index = 0U; index < signal.length; ++index) {
|
||||
const std::uint64_t coordinate = start + index;
|
||||
const std::uint64_t bit = littleEndian ? coordinate
|
||||
: coordinate / 8U * 8U + 7U - coordinate % 8U;
|
||||
bits.set(static_cast<std::size_t>(bit));
|
||||
}
|
||||
return bits;
|
||||
}
|
||||
|
||||
bool AreMutuallyExclusive(const DecodeSignal &first, const DecodeSignal &second) {
|
||||
return first.multiplexing.role == MultiplexRole::Branch &&
|
||||
second.multiplexing.role == MultiplexRole::Branch &&
|
||||
first.multiplexing.value != second.multiplexing.value;
|
||||
}
|
||||
|
||||
void ValidateFrame(const DecodeFrame &frame) {
|
||||
const std::uint32_t maximumId = frame.isExtended ? 0x1FFFFFFFU : 0x7FFU;
|
||||
if (frame.canId > maximumId)
|
||||
throw std::invalid_argument("Invalid normalized CAN ID: " + std::to_string(frame.canId));
|
||||
|
||||
std::unordered_set<std::string> names;
|
||||
const DecodeSignal *selector = nullptr;
|
||||
for (const DecodeSignal &signal: frame.signals) {
|
||||
if (!names.insert(signal.name).second)
|
||||
throw std::invalid_argument("Duplicate runtime signal key");
|
||||
throw std::invalid_argument("Duplicate runtime signal key: " + signal.name);
|
||||
ValidateSignal(signal, frame.dlc);
|
||||
if (signal.multiplexing.role == MultiplexRole::Selector) {
|
||||
if (selector != nullptr || signal.valueType != ValueType::Unsigned)
|
||||
throw std::invalid_argument("Multiplexing requires one unsigned selector per frame");
|
||||
selector = &signal;
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<SignalBits> layouts;
|
||||
layouts.reserve(frame.signals.size());
|
||||
for (std::size_t index = 0U; index < frame.signals.size(); ++index) {
|
||||
const DecodeSignal &signal = frame.signals[index];
|
||||
if (signal.multiplexing.role == MultiplexRole::Branch &&
|
||||
(selector == nullptr ||
|
||||
(selector->length < 64U && signal.multiplexing.value >= (std::uint64_t(1U) << selector->length))))
|
||||
throw std::invalid_argument("Missing selector or branch value outside selector range: " + signal.name);
|
||||
const SignalBits bits = GetSignalBits(signal);
|
||||
for (std::size_t previous = 0U; previous < index; ++previous) {
|
||||
if (!AreMutuallyExclusive(signal, frame.signals[previous]) && (bits & layouts[previous]).any())
|
||||
throw std::invalid_argument("Overlapping signals: " + signal.name);
|
||||
}
|
||||
layouts.push_back(bits);
|
||||
}
|
||||
}
|
||||
}// namespace
|
||||
|
||||
DecodeDatabase::Storage::Storage(const std::vector<DecodeFrame> &definitions)
|
||||
: frames(definitions) {
|
||||
// Validate the complete snapshot before creating its lookup index.
|
||||
for (const DecodeFrame &frame: frames)
|
||||
ValidateFrame(frame);
|
||||
frameIndexByKey.reserve(frames.size());
|
||||
for (std::size_t index = 0U; index < frames.size(); ++index) {
|
||||
if (!frameIndexByKey.emplace(frames[index].GetKey(), index).second)
|
||||
throw std::invalid_argument("Duplicate runtime frame key");
|
||||
}
|
||||
}
|
||||
|
||||
DecodeDatabase::DecodeDatabase(std::vector<DecodeFrame> frames)
|
||||
: storage(std::make_shared<const Storage>(std::move(frames))) {
|
||||
DecodeDatabase::DecodeDatabase(const std::vector<DecodeFrame> &frames)
|
||||
: storage(std::make_shared<const Storage>(frames)) {
|
||||
}
|
||||
|
||||
const std::vector<DecodeFrame> &DecodeDatabase::GetFrames() const {
|
||||
|
||||
@@ -88,11 +88,11 @@ class DecodeDatabase {
|
||||
public:
|
||||
DecodeDatabase() = default;
|
||||
/**
|
||||
* Freeze definitions and build the frame index.
|
||||
* Do not retain mutable aliases into moved input vectors.
|
||||
* @throws std::invalid_argument for duplicate frame keys or signal names.
|
||||
* Copy and validate definitions before building the frame index.
|
||||
* Input aliases cannot modify the snapshot, even for rvalue input.
|
||||
* @throws std::invalid_argument for invalid metadata or duplicate keys.
|
||||
*/
|
||||
explicit DecodeDatabase(std::vector<DecodeFrame> frames);
|
||||
explicit DecodeDatabase(const std::vector<DecodeFrame> &frames);
|
||||
|
||||
/** Borrowed view, valid while this snapshot remains alive and unchanged. */
|
||||
const std::vector<DecodeFrame> &GetFrames() const;
|
||||
@@ -105,7 +105,7 @@ private:
|
||||
std::vector<DecodeFrame> frames;
|
||||
std::unordered_map<FrameKey, std::size_t, FrameKeyHasher> frameIndexByKey;
|
||||
|
||||
explicit Storage(std::vector<DecodeFrame> definitions);
|
||||
explicit Storage(const std::vector<DecodeFrame> &definitions);
|
||||
};
|
||||
|
||||
std::shared_ptr<const Storage> storage;
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
#ifndef SIGNAL_LAYOUT_H
|
||||
#define SIGNAL_LAYOUT_H
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
/** Linear coordinate in the signal's bit order, widened before arithmetic. */
|
||||
inline std::uint64_t SignalStartCoordinate(std::uint32_t startBit, bool littleEndian) {
|
||||
return littleEndian ? std::uint64_t(startBit)
|
||||
: std::uint64_t(startBit / 8U) * 8U + 7U - startBit % 8U;
|
||||
}
|
||||
|
||||
inline bool IsValidSignalLayout(std::uint32_t startBit, std::uint32_t length,
|
||||
bool littleEndian, std::uint8_t dlc) {
|
||||
return length >= 1U && length <= 64U &&
|
||||
SignalStartCoordinate(startBit, littleEndian) + length <= std::uint64_t(dlc) * 8U;
|
||||
}
|
||||
|
||||
#endif
|
||||
Reference in new issue
Block a user