diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 20dcf3f..1b81835 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -16,11 +16,20 @@ jobs: steps: - uses: actions/checkout@v3 + - name: install fast_float dependency + run: | + cd ${{github.workspace}} + git clone https://github.com/fastfloat/fast_float.git + mkdir fast_float/build + cd fast_float/build + cmake -DCMAKE_INSTALL_PREFIX="${{github.workspace}}/installation" .. + make install + - name: Configure CMake run: | mkdir build cd build - cmake -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} .. + cmake -DFastFloat_DIR="${{github.workspace}}/installation/share/cmake/FastFloat" -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} .. - name: Build run: | diff --git a/CMakeLists.txt b/CMakeLists.txt index 1bd0b8a..0522203 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,5 +1,4 @@ -cmake_minimum_required(VERSION 3.23) -# FILE_SET needs cmake 3.23 +cmake_minimum_required(VERSION 3.16) project(dbc VERSION 0.1.1 DESCRIPTION "C++ DBC Parser") @@ -21,6 +20,17 @@ include(GNUInstallDirs) set(CMAKE_CXX_STANDARD 11) set(CMAKE_CXX_STANDARD_REQUIRED True) +find_package(FastFloat QUIET) +if (NOT ${FastFloat_FOUND}) + include(FetchContent) + FetchContent_Declare( + FastFloat + GIT_REPOSITORY https://github.com/fastfloat/fast_float.git + GIT_TAG 1ea4f27b2aeee2859a1354a3c24cff52a116cad1 + ) + FetchContent_MakeAvailable(FastFloat) +endif() + set(GCC_COMPILE_FLAGS "-Wextra -Wall -Wfloat-equal -Wundef -Wshadow \ -Wpointer-arith -Wcast-align -Wstrict-prototypes -Wwrite-strings \ -Waggregate-return -Wcast-qual -Wswitch-default -Wswitch-enum -Wconversion \ @@ -60,10 +70,14 @@ endif() add_subdirectory(doc) add_library(${PROJECT_NAME} STATIC ${SOURCE}) -target_sources(${PROJECT_NAME} INTERFACE FILE_SET HEADERS - TYPE HEADERS - BASE_DIRS ${PROJECT_SOURCE_DIR}/include/libdbc - FILES ${HEADER_FILES}) +target_link_libraries(${PROJECT_NAME} FastFloat::fast_float) + +if (${CMAKE_MINOR_VERSION} GREATER_EQUAL 23) + target_sources(${PROJECT_NAME} INTERFACE FILE_SET HEADERS + TYPE HEADERS + BASE_DIRS ${PROJECT_SOURCE_DIR}/include/libdbc + FILES ${HEADER_FILES}) +endif() add_custom_target(release WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} @@ -76,11 +90,15 @@ install(TARGETS ${PROJECT_NAME} DESTINATION ${CMAKE_INSTALL_LIBDIR}) # install headers -install(TARGETS ${PROJECT_NAME} - FILE_SET HEADERS - DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/lib${PROJECT_NAME} - INCLUDES DESTINATION ${CMAKE_INSTALL_INCLUDEDIR} -) +if (${CMAKE_MINOR_VERSION} GREATER_EQUAL 23) + install(TARGETS ${PROJECT_NAME} + FILE_SET HEADERS + DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/lib${PROJECT_NAME} + INCLUDES DESTINATION ${CMAKE_INSTALL_INCLUDEDIR} + ) +else() + install(DIRECTORY ${PROJECT_SOURCE_DIR}/include/libdbc DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}) +endif() # Generate pkg-config file configure_file(${PROJECT_NAME}.pc.in ${PROJECT_NAME}.pc @ONLY) diff --git a/doc/CMakeLists.txt b/doc/CMakeLists.txt index 666f5b7..1827d5d 100644 --- a/doc/CMakeLists.txt +++ b/doc/CMakeLists.txt @@ -13,6 +13,7 @@ if(BUILD_DOCUMENTATION) configure_file(${doxyfile_in} ${doxyfile} @ONLY) add_custom_target(doc + ALL COMMAND ${DOXYGEN_EXECUTABLE} ${doxyfile} WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} COMMENT "Generating API documentation with Doxygen" diff --git a/include/libdbc/dbc.hpp b/include/libdbc/dbc.hpp index 8b3c0cf..7fd3200 100644 --- a/include/libdbc/dbc.hpp +++ b/include/libdbc/dbc.hpp @@ -24,7 +24,7 @@ namespace libdbc { class DbcParser : public Parser { public: - DbcParser(); + DbcParser(); virtual ~DbcParser() = default; @@ -34,6 +34,8 @@ namespace libdbc { std::vector get_nodes() const; std::vector get_messages() const; + Message::ParseSignalsStatus parseMessage(const uint32_t id, const std::vector& data, std::vector& out_values); + private: std::string version; std::vector nodes; diff --git a/include/libdbc/message.hpp b/include/libdbc/message.hpp index 37d021b..12ab43c 100644 --- a/include/libdbc/message.hpp +++ b/include/libdbc/message.hpp @@ -3,21 +3,45 @@ #include #include +#include #include #include namespace libdbc { struct Message { - uint32_t id; - std::string name; - uint8_t size; - std::string node; - std::vector signals; - Message() = delete; explicit Message(uint32_t id, const std::string& name, uint8_t size, const std::string& node); - virtual bool operator==(const Message& rhs) const; + enum class ParseSignalsStatus { + Success, + ErrorMessageToLong, + ErrorBigEndian, + ErrorUnknownID, + ErrorInvalidConversion, + }; + + /*! + * \brief parseSignals + * \param data + * \param values + * \return + */ + ParseSignalsStatus parseSignals(const std::vector& data, std::vector &values) const; + + void appendSignal(const Signal& signal); + const std::vector signals() const; + uint32_t id() const; + + virtual bool operator==(const Message& rhs) const; + + private: + uint32_t m_id; + std::string m_name; + uint8_t m_size; + std::string m_node; + std::vector m_signals; + + friend std::ostream& operator<<(std::ostream& os, const Message& dt); }; std::ostream& operator<< (std::ostream &out, const Message& msg); diff --git a/include/libdbc/signal.hpp b/include/libdbc/signal.hpp index 085994e..2252700 100644 --- a/include/libdbc/signal.hpp +++ b/include/libdbc/signal.hpp @@ -25,6 +25,7 @@ namespace libdbc { explicit Signal(std::string name, bool is_multiplexed, uint32_t start_bit, uint32_t size, bool is_bigendian, bool is_signed, double factor, double offset, double min, double max, std::string unit, std::vector recievers); virtual bool operator==(const Signal& rhs) const; + bool operator< (const Signal& rhs) const; }; std::ostream& operator<< (std::ostream &out, const Signal& sig); diff --git a/src/dbc.cpp b/src/dbc.cpp index 905f237..d0646c8 100644 --- a/src/dbc.cpp +++ b/src/dbc.cpp @@ -1,6 +1,7 @@ #include #include #include +#include #include @@ -28,12 +29,12 @@ const auto whiteSpace = "\\s"; namespace libdbc { - DbcParser::DbcParser() : version(""), nodes(), + DbcParser::DbcParser() : version(""), nodes(), version_re("^(VERSION)\\s\"(.*)\""), bit_timing_re("^(BS_:)"), name_space_re("^(NS_)\\s\\:"), node_re("^(BU_:)\\s((?:[\\w]+?\\s?)*)"), message_re("^(BO_)\\s(\\d+)\\s(\\w+)\\:\\s(\\d+)\\s(\\w+|Vector__XXX)"), // NOTE: No multiplex support yet - signal_re(std::string(whiteSpace) + + signal_re(std::string("^") + whiteSpace + signalIdentifierPattern + whiteSpace + namePattern + @@ -57,11 +58,13 @@ namespace libdbc { } - void DbcParser::parse_file(const std::string& file) { + void DbcParser::parse_file(const std::string& file) { std::ifstream s(file.c_str()); std::string line; std::vector lines; + messages.clear(); + parse_dbc_header(s); parse_dbc_nodes(s); @@ -71,8 +74,7 @@ namespace libdbc { lines.push_back(line); } - parse_dbc_messages(lines); - + parse_dbc_messages(lines); } std::string DbcParser::get_version() const { @@ -87,6 +89,13 @@ namespace libdbc { return messages; } + Message::ParseSignalsStatus DbcParser::parseMessage(const uint32_t id, const std::vector& data, std::vector& out_values) { + for (const auto& message: messages) { + if (message.id() == id) + return message.parseSignals(data, out_values); + } + return Message::ParseSignalsStatus::ErrorUnknownID; + } void DbcParser::parse_dbc_header(std::istream& file_stream) { std::string line; @@ -150,17 +159,21 @@ namespace libdbc { bool is_bigendian = (std::stoul(match.str(5)) == 0); bool is_signed = (match.str(6) == "-"); // Alternate groups because a group is for the decimal portion - double factor = std::stod(match.str(7)); - double offset = std::stod(match.str(9)); - double min = std::stod(match.str(11)); - double max = std::stod(match.str(13)); + double factor; + fast_float::from_chars(match.str(7).data(), match.str(7).data() + match.str(7).size(), factor); + double offset; + fast_float::from_chars(match.str(9).data(), match.str(9).data() + match.str(9).size(), offset); + double min; + fast_float::from_chars(match.str(11).data(), match.str(11).data() + match.str(11).size(), min); + double max; + fast_float::from_chars(match.str(13).data(), match.str(13).data() + match.str(13).size(), max); std::string unit = match.str(15); std::vector receivers; utils::String::split(match.str(16), receivers, ','); Signal sig(name, is_multiplexed, start_bit, size, is_bigendian, is_signed, factor, offset, min, max, unit, receivers); - messages.back().signals.push_back(sig); + messages.back().appendSignal(sig); } } diff --git a/src/message.cpp b/src/message.cpp index 35956f6..73abf8e 100644 --- a/src/message.cpp +++ b/src/message.cpp @@ -1,19 +1,73 @@ +#include #include namespace libdbc { Message::Message(uint32_t id, const std::string& name, uint8_t size, const std::string& node) : - id(id), name(name), size(size), node(node) {} + m_id(id), m_name(name), m_size(size), m_node(node) {} bool Message::operator==(const Message& rhs) const { - return (this->id == rhs.id) && (this->name == rhs.name) && - (this->size == rhs.size) && (this->node == rhs.node); - } + return (m_id == rhs.id()) && (m_name == rhs.m_name) && + (m_size == rhs.m_size) && (m_node == rhs.m_node); + } + + Message::ParseSignalsStatus Message::parseSignals(const std::vector& data, std::vector& values) const { + int size = data.size(); + if (size > 8) + return ParseSignalsStatus::ErrorMessageToLong; // not supported yet + + uint64_t data_little_endian = 0; + uint64_t data_big_endian = 0; + for (int i=0; i < size; i++) { + data_little_endian |= ((uint64_t)data[i]) << i * 8; + data_big_endian = (data_big_endian << 8) | (uint64_t)data[i]; + } + + // TODO: does this also work on a big endian machine? + + const uint32_t len = size * 8; + uint64_t v = 0; + for (const auto& signal: m_signals) { + if (signal.is_bigendian) { + uint32_t start_bit = 8* (signal.start_bit / 8) + (7 - (signal.start_bit % 8)); // Calculation taken from python CAN + v = data_big_endian << start_bit; + v = v >> (len - signal.size); + } else { + const uint32_t shiftLeft = (len - (signal.size + signal.start_bit)); + v = data_little_endian << shiftLeft; + v = v >> (shiftLeft + signal.start_bit); + } + + if (signal.is_signed && signal.size > 1) { + switch (signal.size) { + case 8: values.push_back((int8_t)v * signal.factor + signal.offset); break; + case 16: values.push_back((int16_t)v * signal.factor + signal.offset); break; + case 32: values.push_back((int32_t)v * signal.factor + signal.offset); break; + case 64: values.push_back((int64_t)v * signal.factor + signal.offset); break; + default: return ParseSignalsStatus::ErrorInvalidConversion; + } + } else + values.push_back(v * signal.factor + signal.offset); + } + return ParseSignalsStatus::Success; + } + + void Message::appendSignal(const Signal& signal) { + m_signals.push_back(signal); + } + + const std::vector Message::signals() const { + return m_signals; + } + + uint32_t Message::id() const { + return m_id; + } - std::ostream& operator<< (std::ostream &out, const Message& msg) { - out << "Message: {id: " << msg.id << ", "; - out << "name: " << msg.name << ", "; - out << "size: " << msg.size << ", "; - out << "node: " << msg.node << "}"; + std::ostream& operator<< (std::ostream &out, const Message& msg) { + out << "Message: {id: " << msg.id() << ", "; + out << "name: " << msg.m_name << ", "; + out << "size: " << msg.m_size << ", "; + out << "node: " << msg.m_node << "}"; return out; } -} \ No newline at end of file +} diff --git a/src/signal.cpp b/src/signal.cpp index 1124fc3..0aa79e9 100644 --- a/src/signal.cpp +++ b/src/signal.cpp @@ -12,6 +12,9 @@ namespace libdbc { (this->unit == rhs.unit) && (this->receivers == rhs.receivers); } + bool Signal::operator< (const Signal& rhs) const { + return start_bit < rhs.start_bit; + } std::ostream& operator<< (std::ostream &out, const Signal& sig) { out << "Signal {name: " << sig.name << ", "; diff --git a/src/utils.cpp b/src/utils.cpp index 773b802..824e041 100644 --- a/src/utils.cpp +++ b/src/utils.cpp @@ -72,4 +72,4 @@ namespace utils { return start == end ? std::string() : line.substr(start, end - start + 1); } -} // Namespace Utils \ No newline at end of file +} // Namespace Utils diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index b76812e..d856c08 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -10,15 +10,28 @@ FetchContent_Declare( FetchContent_MakeAvailable(Catch2) include(Catch) -list(APPEND TEST_SOURCES - test_dbc.cpp - test_utils.cpp) +add_executable(dbcParserTests test_dbc.cpp test_utils.cpp common.cpp) +target_compile_definitions(dbcParserTests PRIVATE TESTDBCFILES_PATH="${CMAKE_CURRENT_SOURCE_DIR}/dbcs") +target_link_libraries(dbcParserTests PRIVATE dbc Catch2::Catch2WithMain) +if (${CMAKE_MINOR_VERSION} GREATER_EQUAL 23) + target_sources(dbcParserTests PRIVATE FILE_SET HEADERS + TYPE HEADERS + BASE_DIRS ${CMAKE_CURRENT_SOURCE_DIR} + FILES defines.hpp) +else() + target_include_directories(dbcParserTests PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}) +endif() +catch_discover_tests(dbcParserTests) -add_executable(tests ${TEST_SOURCES}) -target_compile_definitions(tests PRIVATE TESTDBCFILES_PATH="${CMAKE_CURRENT_SOURCE_DIR}/dbcs") -target_link_libraries(tests PRIVATE dbc Catch2::Catch2WithMain) -target_sources(tests INTERFACE FILE_SET HEADERS - TYPE HEADERS - BASE_DIRS ${CMAKE_CURRENT_SOURCE_DIR} - FILES defines.hpp) -catch_discover_tests(tests) +add_executable(dbcParserParsemessageTests test_parseMessage.cpp common.cpp) +target_compile_definitions(dbcParserParsemessageTests PRIVATE TESTDBCFILES_PATH="${CMAKE_CURRENT_SOURCE_DIR}/dbcs") +target_link_libraries(dbcParserParsemessageTests PRIVATE dbc Catch2::Catch2WithMain) +if (${CMAKE_MINOR_VERSION} GREATER_EQUAL 23) + target_sources(dbcParserParsemessageTests PRIVATE FILE_SET HEADERS + TYPE HEADERS + BASE_DIRS ${CMAKE_CURRENT_SOURCE_DIR} + FILES defines.hpp) +else() + target_include_directories(dbcParserParsemessageTests PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}) +endif() +catch_discover_tests(dbcParserParsemessageTests) diff --git a/test/common.cpp b/test/common.cpp new file mode 100644 index 0000000..9459242 --- /dev/null +++ b/test/common.cpp @@ -0,0 +1,16 @@ +#include "common.hpp" +#include "defines.hpp" + + +bool create_tmp_dbc_with(const char* filename, const char* content) +{ + auto* file = std::fopen(filename, "w"); + if (!file) { + return false; + } + + std::fputs(PRIMITIVE_DBC.c_str(), file); + std::fputs(content, file); + std::fclose(file); + return true; +} diff --git a/test/common.hpp b/test/common.hpp new file mode 100644 index 0000000..074fc25 --- /dev/null +++ b/test/common.hpp @@ -0,0 +1,6 @@ +#ifndef COMMON_H +#define COMMON_H + +bool create_tmp_dbc_with(const char* filename, const char* content); + +#endif // COMMON_H diff --git a/test/test_dbc.cpp b/test/test_dbc.cpp index 04dddb2..dd519eb 100644 --- a/test/test_dbc.cpp +++ b/test/test_dbc.cpp @@ -1,16 +1,9 @@ #include +#include +#include #include "defines.hpp" #include - -void create_tmp_dbc_with(const char* filename, const char* content) -{ - auto* file = std::fopen(filename, "w"); - CHECK(file); - - std::fputs(PRIMITIVE_DBC.c_str(), file); - std::fputs(content, file); - std::fclose(file); -} +#include "common.hpp" TEST_CASE("Testing dbc file loading error issues", "[fileio][error]") { @@ -54,7 +47,7 @@ TEST_CASE("Testing dbc file loading", "[fileio]") { std::vector receivers{"DBG"}; libdbc::Signal sig("IO_DEBUG_test_unsigned", false, 0, 8, false, false, 1, 0, 0, 0, "", receivers); - msg.signals.push_back(sig); + msg.appendSignal(sig); std::vector msgs = {msg}; @@ -66,7 +59,7 @@ TEST_CASE("Testing dbc file loading", "[fileio]") { REQUIRE(parser->get_messages() == msgs); - REQUIRE(parser->get_messages().front().signals == msg.signals); + REQUIRE(parser->get_messages().front().signals() == msg.signals()); } } @@ -89,13 +82,13 @@ TEST_CASE("Testing big endian, little endian") { parser.parse_file(filename); REQUIRE(parser.get_messages().size() == 1); - REQUIRE(parser.get_messages().at(0).signals.size() == 2); + REQUIRE(parser.get_messages().at(0).signals().size() == 2); { - const auto signal = parser.get_messages().at(0).signals.at(0); + const auto signal = parser.get_messages().at(0).signals().at(0); REQUIRE(signal.is_bigendian == true); } { - const auto signal = parser.get_messages().at(0).signals.at(1); + const auto signal = parser.get_messages().at(0).signals().at(1); REQUIRE(signal.is_bigendian == false); } } @@ -110,34 +103,34 @@ TEST_CASE("Testing negative values") { SG_ Sig4 : 7|16@0- (1,-10) [0|32767] "" Vector__XXX)"); auto parser = libdbc::DbcParser(); - parser.parse_file(std::string(filename)); + parser.parse_file(filename); REQUIRE(parser.get_messages().size() == 1); - REQUIRE(parser.get_messages().at(0).signals.size() == 4); + REQUIRE(parser.get_messages().at(0).signals().size() == 4); SECTION("Evaluating first message") { - const auto signal = parser.get_messages().at(0).signals.at(0); + const auto signal = parser.get_messages().at(0).signals().at(0); REQUIRE(signal.factor == 0.1); REQUIRE(signal.offset == 0); REQUIRE(signal.min == -3276.8); REQUIRE(signal.max == -3276.7); } SECTION("Evaluating second message") { - const auto signal = parser.get_messages().at(0).signals.at(1); + const auto signal = parser.get_messages().at(0).signals().at(1); REQUIRE(signal.factor == 0.1); REQUIRE(signal.offset == 0); REQUIRE(signal.min == -3276.8); REQUIRE(signal.max == -3276.7); } SECTION("Evaluating third message"){ - const auto signal = parser.get_messages().at(0).signals.at(2); + const auto signal = parser.get_messages().at(0).signals().at(2); REQUIRE(signal.factor == 10); REQUIRE(signal.offset == 0); REQUIRE(signal.min == -3276.8); REQUIRE(signal.max == -3276.7); } SECTION("Evaluating fourth message"){ - const auto signal = parser.get_messages().at(0).signals.at(3); + const auto signal = parser.get_messages().at(0).signals().at(3); REQUIRE(signal.factor == 1); REQUIRE(signal.offset == -10); REQUIRE(signal.min == 0); @@ -145,7 +138,6 @@ TEST_CASE("Testing negative values") { } } - TEST_CASE("Special characters in unit") { const auto* filename = std::tmpnam(NULL); @@ -154,12 +146,12 @@ TEST_CASE("Special characters in unit") { auto parser = libdbc::DbcParser(); - parser.parse_file(std::string(filename)); + parser.parse_file(filename); REQUIRE(parser.get_messages().size() == 1); - REQUIRE(parser.get_messages().at(0).signals.size() == 1); + REQUIRE(parser.get_messages().at(0).signals().size() == 1); SECTION("Checking that signal with special characters as unit is parsed correctly") { - const auto signal = parser.get_messages().at(0).signals.at(0); + const auto signal = parser.get_messages().at(0).signals().at(0); REQUIRE(signal.unit.compare("Km/h") == 0); } } diff --git a/test/test_parseMessage.cpp b/test/test_parseMessage.cpp new file mode 100644 index 0000000..5543578 --- /dev/null +++ b/test/test_parseMessage.cpp @@ -0,0 +1,143 @@ +#include +#include +#include + +#include + +#include "common.hpp" + +// Testing of parsing messages + +TEST_CASE("Parse Message Unknown ID") { + libdbc::DbcParser parser; + + const auto dbcContent = R"(BO_ 234 MSG1: 8 Vector__XXX + SG_ Msg1Sig1 : 0|8@0+ (1,0) [-3276.8|-3276.7] "C" Vector__XXX + SG_ MsgSig2 : 8|8@0+ (1,0) [-3276.8|-3276.7] "C" Vector__XXX +BO_ 123 MSG2: 8 Vector__XXX + SG_ Msg2Sig1 : 0|8@0+ (1,0) [-3276.8|-3276.7] "C" Vector__XXX + SG_ Msg2Sig1 : 8|8@0+ (1,0) [-3276.8|-3276.7] "C" Vector__XXX +)"; + + const auto* filename = std::tmpnam(NULL); + CHECK(create_tmp_dbc_with(filename, dbcContent)); + + parser.parse_file(filename); + + SECTION("Evaluating unknown message id") { + std::vector out_values; + CHECK(parser.parseMessage(578, std::vector({0xFF, 0xA2}), out_values) == libdbc::Message::ParseSignalsStatus::ErrorUnknownID); + } +} + +TEST_CASE("Parse Message Big Number not aligned little endian") { + libdbc::DbcParser parser; + + const auto dbcContent = R"(BO_ 337 STATUS: 8 Vector__XXX + SG_ Value6 : 27|3@1+ (1,0) [0|7] "" Vector__XXX + SG_ Value5 : 16|11@1+ (0.1,-102) [-102|102] "%" Vector__XXX + SG_ Value2 : 8|2@1+ (1,0) [0|2] "" Vector__XXX + SG_ Value3 : 10|1@1+ (1,0) [0|1] "" Vector__XXX + SG_ Value7 : 30|2@1+ (1,0) [0|3] "" Vector__XXX + SG_ Value4 : 11|4@1+ (1,0) [0|3] "" Vector__XXX + SG_ Value1 : 0|8@1+ (1,0) [0|204] "Km/h" Vector__XXX +)"; + + const auto* filename = std::tmpnam(NULL); + CHECK(create_tmp_dbc_with(filename, dbcContent)); + + parser.parse_file(filename); + + SECTION("Evaluating first message") { + std::vector out_values; + CHECK(parser.parseMessage(337, std::vector({0, 4, 252, 19, 0, 0, 0, 0}), out_values) == libdbc::Message::ParseSignalsStatus::Success); + std::vector refData{2, 0, 0, 1, 0, 0, 0}; + CHECK(refData.size() == 7); + CHECK(out_values.size() == refData.size()); + for (int i=0; i < refData.size(); i++) { + CHECK(out_values.at(i) == refData.at(i)); + } + } + + SECTION("Evaluating second message") { + std::vector out_values; + CHECK(parser.parseMessage(337, std::vector({47, 4, 60, 29, 0, 0, 0, 0}), out_values) == libdbc::Message::ParseSignalsStatus::Success); + std::vector refData{3, 32, 0, 1, 0, 0, 47}; + CHECK(refData.size() == 7); + CHECK(out_values.size() == refData.size()); + for (int i=0; i < refData.size(); i++) { + CHECK(out_values.at(i) == refData.at(i)); + } + } + + SECTION("Evaluating third message") { + std::vector out_values; + CHECK(parser.parseMessage(337, std::vector({57, 4, 250, 29, 0, 0, 0, 0}), out_values) == libdbc::Message::ParseSignalsStatus::Success); + std::vector refData{3, 51, 0, 1, 0, 0, 57}; + CHECK(refData.size() == 7); + CHECK(out_values.size() == refData.size()); + for (int i=0; i < refData.size(); i++) { + CHECK(out_values.at(i) == refData.at(i)); + } + } +} + +TEST_CASE("Parse Message little endian") { + const auto* filename = std::tmpnam(NULL); + + create_tmp_dbc_with(filename, R"(BO_ 541 STATUS: 8 DEVICE1 + SG_ Temperature : 48|16@1+ (0.01,-40) [-40|125] "C" DEVICE1 + SG_ SOH : 0|16@1+ (0.01,0) [0|100] "%" DEVICE1 + SG_ SOE : 32|16@1+ (0.01,0) [0|100] "%" DEVICE1 + SG_ SOC : 16|16@1+ (0.01,0) [0|100] "%" DEVICE1)"); + + libdbc::DbcParser p; + p.parse_file(filename); + + std::vector data{0x08, 0x27, 0xa3, 0x22, 0xe5, 0x1f, 0x45, 0x14}; // little endian + std::vector result_values; + REQUIRE(p.parseMessage(0x21d, data, result_values) == libdbc::Message::ParseSignalsStatus::Success); + REQUIRE(result_values.size() == 4); + + REQUIRE(Catch::Approx(result_values.at(0)) == 11.89); + REQUIRE(Catch::Approx(result_values.at(1)) == 99.92); + REQUIRE(Catch::Approx(result_values.at(2)) == 81.65); + REQUIRE(Catch::Approx(result_values.at(3)) == 88.67); +} + +TEST_CASE("Parse Message big endian signed values") { + const auto* filename = std::tmpnam(NULL); + create_tmp_dbc_with(filename, R"(BO_ 545 MSG: 8 BMS2 + SG_ Sig1 : 62|1@0+ (1,0) [0|0] "" Vector__XXX + SG_ Sig2 : 49|2@0+ (1,0) [0|0] "" Vector__XXX + SG_ Sig3 : 39|16@0- (0.1,0) [0|0] "A" Vector__XXX + SG_ Sig4 : 60|1@0+ (1,0) [0|0] "" Vector__XXX + SG_ Sig5 : 55|1@0+ (1,0) [0|0] "" Vector__XXX + SG_ Sig6 : 58|1@0+ (1,0) [0|0] "" Vector__XXX + SG_ Sig7 : 59|1@0+ (1,0) [0|0] "" Vector__XXX + SG_ Sig8 : 57|1@0+ (1,0) [0|0] "" Vector__XXX + SG_ Sig9 : 56|1@0+ (1,0) [0|0] "" Vector__XXX + SG_ Sig10 : 61|1@0+ (1,0) [0|0] "" Vector__XXX + SG_ Sig11 : 7|16@0+ (0.001,0) [0|65.535] "V" Vector__XXX + SG_ Sig12 : 23|16@0+ (0.1,0) [0|6553.5] "A" Vector__XXX)"); + + libdbc::DbcParser p; + p.parse_file(filename); + + std::vector data{13, 177, 0, 216, 251, 180, 0, 31}; // big endian + std::vector result_values; + REQUIRE(p.parseMessage(545, data, result_values) == libdbc::Message::ParseSignalsStatus::Success); + REQUIRE(result_values.size() == 12); + REQUIRE(Catch::Approx(result_values.at(0)) == 0); + REQUIRE(Catch::Approx(result_values.at(1)) == 0); + REQUIRE(Catch::Approx(result_values.at(2)) == -110); + REQUIRE(Catch::Approx(result_values.at(3)) == 1); + REQUIRE(Catch::Approx(result_values.at(4)) == 0); + REQUIRE(Catch::Approx(result_values.at(5)) == 1); + REQUIRE(Catch::Approx(result_values.at(6)) == 1); + REQUIRE(Catch::Approx(result_values.at(7)) == 1); + REQUIRE(Catch::Approx(result_values.at(8)) == 1); + REQUIRE(Catch::Approx(result_values.at(9)) == 0); + REQUIRE(Catch::Approx(result_values.at(10)) == 3.5050); + REQUIRE(Catch::Approx(result_values.at(11)) == 21.6); +} diff --git a/third_party/bitstream/CMakeLists.txt b/third_party/bitstream/CMakeLists.txt new file mode 100644 index 0000000..043d10f --- /dev/null +++ b/third_party/bitstream/CMakeLists.txt @@ -0,0 +1,15 @@ +cmake_minimum_required(VERSION 3.16) +# FILE_SET needs cmake 3.23 + +project(bitstream) + +add_library(${PROJECT_NAME} STATIC bitstream.c) + +if (${CMAKE_MINOR_VERSION} GREATER_EQUAL 23) + target_sources(${PROJECT_NAME} INTERFACE FILE_SET HEADERS + TYPE HEADERS + BASE_DIRS ${PROJECT_SOURCE_DIR} + FILES bitstream.h) +else() + target_include_directories(${PROJECT_NAME} INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}) +endif() diff --git a/third_party/bitstream/Readme.md b/third_party/bitstream/Readme.md new file mode 100644 index 0000000..3704131 --- /dev/null +++ b/third_party/bitstream/Readme.md @@ -0,0 +1,2 @@ +Bitstream reader. +Files copied from https://github.com/eerimoq/bitstruct/tree/master/bitstruct diff --git a/third_party/bitstream/bitstream.c b/third_party/bitstream/bitstream.c new file mode 100644 index 0000000..cc68ed8 --- /dev/null +++ b/third_party/bitstream/bitstream.c @@ -0,0 +1,608 @@ +/** + * The MIT License (MIT) + * + * Copyright (c) 2019 Erik Moqvist + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, copy, + * modify, merge, publish, distribute, sublicense, and/or sell copies + * of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include "bitstream.h" + +void bitstream_writer_init(struct bitstream_writer_t *self_p, + uint8_t *buf_p) +{ + self_p->buf_p = buf_p; + self_p->byte_offset = 0; + self_p->bit_offset = 0; +} + +int bitstream_writer_size_in_bits(struct bitstream_writer_t *self_p) +{ + return (8 * self_p->byte_offset + self_p->bit_offset); +} + +int bitstream_writer_size_in_bytes(struct bitstream_writer_t *self_p) +{ + return (self_p->byte_offset + (self_p->bit_offset + 7) / 8); +} + +void bitstream_writer_write_bit(struct bitstream_writer_t *self_p, + int value) +{ + if (self_p->bit_offset == 0) { + self_p->buf_p[self_p->byte_offset] = (value << 7); + self_p->bit_offset = 1; + } else { + self_p->buf_p[self_p->byte_offset] |= (value << (8 - self_p->bit_offset - 1)); + + if (self_p->bit_offset == 7) { + self_p->bit_offset = 0; + self_p->byte_offset++; + } else { + self_p->bit_offset++; + } + } +} + +void bitstream_writer_write_bytes(struct bitstream_writer_t *self_p, + const uint8_t *buf_p, + int length) +{ + int i; + uint8_t *dst_p; + + dst_p = &self_p->buf_p[self_p->byte_offset]; + + if (self_p->bit_offset == 0) { + memcpy(dst_p, buf_p, sizeof(uint8_t) * length); + } else { + for (i = 0; i < length; i++) { + dst_p[i] |= (buf_p[i] >> self_p->bit_offset); + dst_p[i + 1] = (uint8_t)(buf_p[i] << (8 - self_p->bit_offset)); + } + } + + self_p->byte_offset += length; +} + +void bitstream_writer_write_u8(struct bitstream_writer_t *self_p, + uint8_t value) +{ + if (self_p->bit_offset == 0) { + self_p->buf_p[self_p->byte_offset] = value; + } else { + self_p->buf_p[self_p->byte_offset] |= (value >> self_p->bit_offset); + self_p->buf_p[self_p->byte_offset + 1] = + (uint8_t)(value << (8 - self_p->bit_offset)); + } + + self_p->byte_offset++; +} + +void bitstream_writer_write_u16(struct bitstream_writer_t *self_p, + uint16_t value) +{ + if (self_p->bit_offset == 0) { + self_p->buf_p[self_p->byte_offset] = (value >> 8); + } else { + self_p->buf_p[self_p->byte_offset] |= (value >> (8 + self_p->bit_offset)); + self_p->buf_p[self_p->byte_offset + 2] = + (uint8_t)(value << (8 - self_p->bit_offset)); + value >>= self_p->bit_offset; + } + + self_p->buf_p[self_p->byte_offset + 1] = (uint8_t)value; + self_p->byte_offset += 2; +} + +void bitstream_writer_write_u32(struct bitstream_writer_t *self_p, + uint32_t value) +{ + int i; + + if (self_p->bit_offset == 0) { + self_p->buf_p[self_p->byte_offset] = (value >> 24); + } else { + self_p->buf_p[self_p->byte_offset] |= (value >> (24 + self_p->bit_offset)); + self_p->buf_p[self_p->byte_offset + 4] = + (uint8_t)(value << (8 - self_p->bit_offset)); + value >>= self_p->bit_offset; + } + + for (i = 3; i > 0; i--) { + self_p->buf_p[self_p->byte_offset + i] = value; + value >>= 8; + } + + self_p->byte_offset += 4; +} + +void bitstream_writer_write_u64(struct bitstream_writer_t *self_p, + uint64_t value) +{ + int i; + + + if (self_p->bit_offset == 0) { + self_p->buf_p[self_p->byte_offset] = (value >> 56); + } else { + self_p->buf_p[self_p->byte_offset] |= (value >> (56 + self_p->bit_offset)); + self_p->buf_p[self_p->byte_offset + 8] = + (uint8_t)(value << (8 - self_p->bit_offset)); + value >>= self_p->bit_offset; + } + + for (i = 7; i > 0; i--) { + self_p->buf_p[self_p->byte_offset + i] = (uint8_t)value; + value >>= 8; + } + + self_p->byte_offset += 8; +} + +void bitstream_writer_write_u64_bits(struct bitstream_writer_t *self_p, + uint64_t value, + int number_of_bits) +{ + int i; + int first_byte_bits; + int last_byte_bits; + int full_bytes; + + if (number_of_bits == 0) { + return; + } + + /* Align beginning. */ + first_byte_bits = (8 - self_p->bit_offset); + + if (first_byte_bits != 8) { + if (number_of_bits < first_byte_bits) { + self_p->buf_p[self_p->byte_offset] |= + (uint8_t)(value << (first_byte_bits - number_of_bits)); + self_p->bit_offset += number_of_bits; + } else { + self_p->buf_p[self_p->byte_offset] |= (value >> (number_of_bits + - first_byte_bits)); + self_p->byte_offset++; + self_p->bit_offset = 0; + } + + number_of_bits -= first_byte_bits; + + if (number_of_bits <= 0) { + return; + } + } + + /* Align end. */ + last_byte_bits = (number_of_bits % 8); + full_bytes = (number_of_bits / 8); + + if (last_byte_bits != 0) { + self_p->buf_p[self_p->byte_offset + full_bytes] = + (uint8_t)(value << (8 - last_byte_bits)); + value >>= last_byte_bits; + self_p->bit_offset = last_byte_bits; + } + + /* Copy middle bytes. */ + for (i = full_bytes; i > 0; i--) { + self_p->buf_p[self_p->byte_offset + i - 1] = (uint8_t)value; + value >>= 8; + } + + self_p->byte_offset += full_bytes; +} + +void bitstream_writer_write_repeated_bit(struct bitstream_writer_t *self_p, + int value, + int length) +{ + int rest; + + if (value != 0) { + value = 0xff; + } + + rest = (length % 8); + bitstream_writer_write_u64_bits(self_p, value & ((1 << rest) - 1), rest); + bitstream_writer_write_repeated_u8(self_p, value, length / 8); +} + +void bitstream_writer_write_repeated_u8(struct bitstream_writer_t *self_p, + uint8_t value, + int length) +{ + int i; + + for (i = 0; i < length; i++) { + bitstream_writer_write_u8(self_p, value); + } +} + +void bitstream_writer_insert_bit(struct bitstream_writer_t *self_p, + int value) +{ + struct bitstream_writer_bounds_t bounds; + + bitstream_writer_bounds_save(&bounds, + self_p, + (8 * self_p->byte_offset) + self_p->bit_offset, + 1); + bitstream_writer_write_bit(self_p, value); + bitstream_writer_bounds_restore(&bounds); +} + +void bitstream_writer_insert_bytes(struct bitstream_writer_t *self_p, + const uint8_t *buf_p, + int length) +{ + struct bitstream_writer_bounds_t bounds; + + bitstream_writer_bounds_save(&bounds, + self_p, + (8 * self_p->byte_offset) + self_p->bit_offset, + 8 * length); + bitstream_writer_write_bytes(self_p, buf_p, length); + bitstream_writer_bounds_restore(&bounds); +} + +void bitstream_writer_insert_u8(struct bitstream_writer_t *self_p, + uint8_t value) +{ + struct bitstream_writer_bounds_t bounds; + + bitstream_writer_bounds_save(&bounds, + self_p, + (8 * self_p->byte_offset) + self_p->bit_offset, + 8); + bitstream_writer_write_u8(self_p, value); + bitstream_writer_bounds_restore(&bounds); +} + +void bitstream_writer_insert_u16(struct bitstream_writer_t *self_p, + uint16_t value) +{ + struct bitstream_writer_bounds_t bounds; + + bitstream_writer_bounds_save(&bounds, + self_p, + (8 * self_p->byte_offset) + self_p->bit_offset, + 16); + bitstream_writer_write_u16(self_p, value); + bitstream_writer_bounds_restore(&bounds); +} + +void bitstream_writer_insert_u32(struct bitstream_writer_t *self_p, + uint32_t value) +{ + struct bitstream_writer_bounds_t bounds; + + bitstream_writer_bounds_save(&bounds, + self_p, + (8 * self_p->byte_offset) + self_p->bit_offset, + 32); + bitstream_writer_write_u32(self_p, value); + bitstream_writer_bounds_restore(&bounds); +} + +void bitstream_writer_insert_u64(struct bitstream_writer_t *self_p, + uint64_t value) +{ + struct bitstream_writer_bounds_t bounds; + + bitstream_writer_bounds_save(&bounds, + self_p, + (8 * self_p->byte_offset) + self_p->bit_offset, + 64); + bitstream_writer_write_u64(self_p, value); + bitstream_writer_bounds_restore(&bounds); +} + +void bitstream_writer_insert_u64_bits(struct bitstream_writer_t *self_p, + uint64_t value, + int number_of_bits) +{ + struct bitstream_writer_bounds_t bounds; + + bitstream_writer_bounds_save(&bounds, + self_p, + (8 * self_p->byte_offset) + self_p->bit_offset, + number_of_bits); + bitstream_writer_write_u64_bits(self_p, value, number_of_bits); + bitstream_writer_bounds_restore(&bounds); +} + +void bitstream_writer_seek(struct bitstream_writer_t *self_p, + int offset) +{ + offset += ((8 * self_p->byte_offset) + self_p->bit_offset); + self_p->byte_offset = (offset / 8); + self_p->bit_offset = (offset % 8); +} + +void bitstream_writer_bounds_save(struct bitstream_writer_bounds_t *self_p, + struct bitstream_writer_t *writer_p, + int bit_offset, + int length) +{ + int number_of_bits; + + self_p->writer_p = writer_p; + number_of_bits = (bit_offset % 8); + + if (number_of_bits == 0) { + self_p->first_byte_offset = -1; + } else { + self_p->first_byte_offset = (bit_offset / 8); + self_p->first_byte = writer_p->buf_p[self_p->first_byte_offset]; + self_p->first_byte &= (0xff00 >> number_of_bits); + } + + number_of_bits = ((bit_offset + length) % 8); + + if (number_of_bits == 0) { + self_p->last_byte_offset = -1; + } else { + self_p->last_byte_offset = ((bit_offset + length) / 8); + self_p->last_byte = writer_p->buf_p[self_p->last_byte_offset]; + self_p->last_byte &= ~(0xff00 >> number_of_bits); + writer_p->buf_p[self_p->last_byte_offset] = 0; + } + + if (self_p->first_byte_offset != -1) { + writer_p->buf_p[self_p->first_byte_offset] = 0; + } +} + +void bitstream_writer_bounds_restore(struct bitstream_writer_bounds_t *self_p) +{ + if (self_p->first_byte_offset != -1) { + self_p->writer_p->buf_p[self_p->first_byte_offset] |= self_p->first_byte; + } + + if (self_p->last_byte_offset != -1) { + self_p->writer_p->buf_p[self_p->last_byte_offset] |= self_p->last_byte; + } +} + +void bitstream_reader_init(struct bitstream_reader_t *self_p, + const uint8_t *buf_p) +{ + self_p->buf_p = buf_p; + self_p->byte_offset = 0; + self_p->bit_offset = 0; +} + +int bitstream_reader_read_bit(struct bitstream_reader_t *self_p) +{ + int value; + + if (self_p->bit_offset == 0) { + value = (self_p->buf_p[self_p->byte_offset] >> 7); + self_p->bit_offset = 1; + } else { + value = ((self_p->buf_p[self_p->byte_offset] >> (7 - self_p->bit_offset)) & 0x1); + + if (self_p->bit_offset == 7) { + self_p->bit_offset = 0; + self_p->byte_offset++; + } else { + self_p->bit_offset++; + } + } + + return (value); +} + +void bitstream_reader_read_bytes(struct bitstream_reader_t *self_p, + uint8_t *buf_p, + int length) +{ + int i; + const uint8_t *src_p; + + src_p = &self_p->buf_p[self_p->byte_offset]; + + if (self_p->bit_offset == 0) { + memcpy(buf_p, src_p, sizeof(uint8_t) * length); + } else { + for (i = 0; i < length; i++) { + buf_p[i] = (src_p[i] << self_p->bit_offset); + buf_p[i] |= (src_p[i + 1] >> (8 - self_p->bit_offset)); + } + } + + self_p->byte_offset += length; +} + +uint8_t bitstream_reader_read_u8(struct bitstream_reader_t *self_p) +{ + uint8_t value; + + value = (self_p->buf_p[self_p->byte_offset] << self_p->bit_offset); + self_p->byte_offset++; + + if (self_p->bit_offset != 0) { + value |= (self_p->buf_p[self_p->byte_offset] >> (8 - self_p->bit_offset)); + } + + return (value); +} + +uint16_t bitstream_reader_read_u16(struct bitstream_reader_t *self_p) +{ + uint16_t value; + int i; + int offset; + const uint8_t *src_p; + + src_p = &self_p->buf_p[self_p->byte_offset]; + offset = (16 + self_p->bit_offset); + value = 0; + + for (i = 0; i < 2; i++) { + offset -= 8; + value |= ((uint16_t)src_p[i] << offset); + } + + if (offset != 0) { + value |= (src_p[2] >> (8 - offset)); + } + + self_p->byte_offset += 2; + + return (value); +} + +uint32_t bitstream_reader_read_u32(struct bitstream_reader_t *self_p) +{ + uint32_t value; + int i; + int offset; + const uint8_t *src_p; + + src_p = &self_p->buf_p[self_p->byte_offset]; + offset = (32 + self_p->bit_offset); + value = 0; + + for (i = 0; i < 4; i++) { + offset -= 8; + value |= ((uint32_t)src_p[i] << offset); + } + + if (offset != 0) { + value |= (src_p[4] >> (8 - offset)); + } + + self_p->byte_offset += 4; + + return (value); +} + +uint64_t bitstream_reader_read_u64(struct bitstream_reader_t *self_p) +{ + uint64_t value; + int i; + int offset; + const uint8_t *src_p; + + src_p = &self_p->buf_p[self_p->byte_offset]; + offset = (64 + self_p->bit_offset); + value = 0; + + for (i = 0; i < 8; i++) { + offset -= 8; + value |= ((uint64_t)src_p[i] << offset); + } + + if (offset != 0) { + value |= ((uint64_t)src_p[8] >> (8 - offset)); + } + + self_p->byte_offset += 8; + + return (value); +} + +uint64_t bitstream_reader_read_u64_bits(struct bitstream_reader_t *self_p, + int number_of_bits) +{ + uint64_t value; + int i; + int first_byte_bits; + int last_byte_bits; + int full_bytes; + + if (number_of_bits == 0) { + return (0); + } + + /* Align beginning. */ + first_byte_bits = (8 - self_p->bit_offset); + + if (first_byte_bits != 8) { + if (number_of_bits < first_byte_bits) { + value = (self_p->buf_p[self_p->byte_offset] >> (first_byte_bits + - number_of_bits)); + value &= ((1 << number_of_bits) - 1); + self_p->bit_offset += number_of_bits; + } else { + value = self_p->buf_p[self_p->byte_offset]; + value &= ((1 << first_byte_bits) - 1); + self_p->byte_offset++; + self_p->bit_offset = 0; + } + + number_of_bits -= first_byte_bits; + + if (number_of_bits <= 0) { + return (value); + } + } else { + value = 0; + } + + /* Copy middle bytes. */ + full_bytes = (number_of_bits / 8); + + for (i = 0; i < full_bytes; i++) { + value <<= 8; + value |= self_p->buf_p[self_p->byte_offset + i]; + } + + /* Last byte. */ + last_byte_bits = (number_of_bits % 8); + + if (last_byte_bits != 0) { + value <<= last_byte_bits; + value |= (self_p->buf_p[self_p->byte_offset + full_bytes] + >> (8 - last_byte_bits)); + self_p->bit_offset = last_byte_bits; + } + + self_p->byte_offset += full_bytes; + + return (value); +} + +void bitstream_reader_seek(struct bitstream_reader_t *self_p, + int offset) +{ + offset += ((8 * self_p->byte_offset) + self_p->bit_offset); + self_p->byte_offset = (offset / 8); + self_p->bit_offset = (offset % 8); +} + +int bitstream_reader_tell(struct bitstream_reader_t *self_p) +{ + return ((8 * self_p->byte_offset) + self_p->bit_offset); +} + +#ifdef __cplusplus +} +#endif diff --git a/third_party/bitstream/bitstream.h b/third_party/bitstream/bitstream.h new file mode 100644 index 0000000..9ced7b9 --- /dev/null +++ b/third_party/bitstream/bitstream.h @@ -0,0 +1,178 @@ +/** + * The MIT License (MIT) + * + * Copyright (c) 2019 Erik Moqvist + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, copy, + * modify, merge, publish, distribute, sublicense, and/or sell copies + * of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#ifndef BITSTREAM_H +#define BITSTREAM_H + +#ifdef __cplusplus +extern "C" { +#endif // __cplusplus + +#include + +#define BITSTREAM_VERSION "0.8.0" + +struct bitstream_writer_t { + uint8_t *buf_p; + int byte_offset; + int bit_offset; +}; + +struct bitstream_writer_bounds_t { + struct bitstream_writer_t *writer_p; + int first_byte_offset; + uint8_t first_byte; + int last_byte_offset; + uint8_t last_byte; +}; + +struct bitstream_reader_t { + const uint8_t *buf_p; + int byte_offset; + int bit_offset; +}; + +/* + * The writer. + */ + +void bitstream_writer_init(struct bitstream_writer_t *self_p, + uint8_t *buf_p); + +int bitstream_writer_size_in_bits(struct bitstream_writer_t *self_p); + +int bitstream_writer_size_in_bytes(struct bitstream_writer_t *self_p); + +/* Write bits to the stream. Clears each byte before bits are + written. */ +void bitstream_writer_write_bit(struct bitstream_writer_t *self_p, + int value); + +void bitstream_writer_write_bytes(struct bitstream_writer_t *self_p, + const uint8_t *buf_p, + int length); + +void bitstream_writer_write_u8(struct bitstream_writer_t *self_p, + uint8_t value); + +void bitstream_writer_write_u16(struct bitstream_writer_t *self_p, + uint16_t value); + +void bitstream_writer_write_u32(struct bitstream_writer_t *self_p, + uint32_t value); + +void bitstream_writer_write_u64(struct bitstream_writer_t *self_p, + uint64_t value); + +/* Upper unused bits must be zero. */ +void bitstream_writer_write_u64_bits(struct bitstream_writer_t *self_p, + uint64_t value, + int number_of_bits); + +void bitstream_writer_write_repeated_bit(struct bitstream_writer_t *self_p, + int value, + int length); + +void bitstream_writer_write_repeated_u8(struct bitstream_writer_t *self_p, + uint8_t value, + int length); + +/* Insert bits into the stream. Leaves all other bits unmodified. */ +void bitstream_writer_insert_bit(struct bitstream_writer_t *self_p, + int value); + +void bitstream_writer_insert_bytes(struct bitstream_writer_t *self_p, + const uint8_t *buf_p, + int length); + +void bitstream_writer_insert_u8(struct bitstream_writer_t *self_p, + uint8_t value); + +void bitstream_writer_insert_u16(struct bitstream_writer_t *self_p, + uint16_t value); + +void bitstream_writer_insert_u32(struct bitstream_writer_t *self_p, + uint32_t value); + +void bitstream_writer_insert_u64(struct bitstream_writer_t *self_p, + uint64_t value); + +void bitstream_writer_insert_u64_bits(struct bitstream_writer_t *self_p, + uint64_t value, + int number_of_bits); + +/* Move write position. Seeking backwards makes the written size + smaller. Use write with care after seek, as seek does not clear + bytes. */ +void bitstream_writer_seek(struct bitstream_writer_t *self_p, + int offset); + +/* Save-restore first and last bytes in given range, so write can be + used in given range. */ +void bitstream_writer_bounds_save(struct bitstream_writer_bounds_t *self_p, + struct bitstream_writer_t *writer_p, + int bit_offset, + int length); + +void bitstream_writer_bounds_restore(struct bitstream_writer_bounds_t *self_p); + +/* + * The reader. + */ + +void bitstream_reader_init(struct bitstream_reader_t *self_p, + const uint8_t *buf_p); + +/* Read bits from the stream. */ +int bitstream_reader_read_bit(struct bitstream_reader_t *self_p); + +void bitstream_reader_read_bytes(struct bitstream_reader_t *self_p, + uint8_t *buf_p, + int length); + +uint8_t bitstream_reader_read_u8(struct bitstream_reader_t *self_p); + +uint16_t bitstream_reader_read_u16(struct bitstream_reader_t *self_p); + +uint32_t bitstream_reader_read_u32(struct bitstream_reader_t *self_p); + +uint64_t bitstream_reader_read_u64(struct bitstream_reader_t *self_p); + +uint64_t bitstream_reader_read_u64_bits(struct bitstream_reader_t *self_p, + int number_of_bits); + +/* Move read position. */ +void bitstream_reader_seek(struct bitstream_reader_t *self_p, + int offset); + +/* Get read position. */ +int bitstream_reader_tell(struct bitstream_reader_t *self_p); + +#ifdef __cplusplus +} +#endif // __cplusplus + +#endif // BITSTREAM_H