130 lines
6.0 KiB
C++
130 lines
6.0 KiB
C++
// SPDX-License-Identifier: MIT
|
|
// Copyright (c) 2026 Dim Himro
|
|
|
|
#include "decode_database.h"
|
|
#include "signal_layout.h"
|
|
#include <bitset>
|
|
#include <cmath>
|
|
#include <limits>
|
|
#include <stdexcept>
|
|
#include <unordered_set>
|
|
|
|
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: " + 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(const std::vector<DecodeFrame> &frames)
|
|
: storage(std::make_shared<const Storage>(frames)) {
|
|
}
|
|
|
|
const std::vector<DecodeFrame> &DecodeDatabase::GetFrames() const {
|
|
static const std::vector<DecodeFrame> empty;
|
|
return storage ? storage->frames : empty;
|
|
}
|
|
|
|
std::shared_ptr<const DecodeFrame> DecodeDatabase::FindFrame(const FrameKey &key) const {
|
|
if (!storage)
|
|
return nullptr;
|
|
const std::unordered_map<FrameKey, std::size_t, FrameKeyHasher>::const_iterator it =
|
|
storage->frameIndexByKey.find(key);
|
|
if (it == storage->frameIndexByKey.end())
|
|
return nullptr;
|
|
return std::shared_ptr<const DecodeFrame>(storage, &storage->frames[it->second]);
|
|
}
|
|
|
|
std::shared_ptr<const DecodeSignal> DecodeDatabase::FindSignal(const SignalKey &key) const {
|
|
const std::shared_ptr<const DecodeFrame> frame = FindFrame(key.frame);
|
|
if (!frame)
|
|
return nullptr;
|
|
for (const DecodeSignal &signal: frame->signals) {
|
|
if (signal.name == key.name)
|
|
return std::shared_ptr<const DecodeSignal>(frame, &signal);
|
|
}
|
|
return nullptr;
|
|
}
|