From 366b5763c1651064bd7f602abd7b4813bc3ea024 Mon Sep 17 00:00:00 2001 From: Francesco D'Agostino Date: Thu, 25 Jun 2026 15:11:17 +0200 Subject: [PATCH 1/3] general code review --- CMakeLists.txt | 106 ++++++++---- include/Klv.h | 125 -------------- include/Klv.hpp | 174 +++++++++++++++++++ include/KlvCommon.hpp | 16 ++ include/KlvParser.hpp | 144 ++++++++-------- src/Klv.cpp | 266 +++++++++++++++++------------ src/KlvParser.cpp | 376 +++++++++++++++++++++++------------------ src/main.cpp | 8 - test/KlvParserTest.cpp | 140 ++++++++------- test/KlvTest.cpp | 77 ++++++++- 10 files changed, 858 insertions(+), 574 deletions(-) delete mode 100644 include/Klv.h create mode 100644 include/Klv.hpp create mode 100644 include/KlvCommon.hpp delete mode 100644 src/main.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 6cda737..0af801d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,51 +1,93 @@ # CMAkeLists.txt file for mumpi project -cmake_minimum_required(VERSION 3.0.0) - -# Options. Turn on with 'cmake -Dvarname=ON'. -option(test "Build all tests." OFF) # makes boolean 'test' available -set(CMAKE_BUILD_TYPE Debug) +cmake_minimum_required(VERSION 3.28.0) # Project -project(klv) - -# Compiler settings -if (CMAKE_VERSION VERSION_LESS "3.1") - if (CMAKE_CXX_COMPILER_ID STREQUAL "GNU") - set (CMAKE_CXX_FLAGS "--std=gnu++11 ${CMAKE_CXX_FLAGS}") - endif () - else () - set (CMAKE_CXX_STANDARD 11) -endif () -set(CMAKE_EXPORT_COMPILE_COMMANDS 1) +project(klv + VERSION 0.0.1 + DESCRIPTION "libklv" + LANGUAGES CXX C + ) + +# If not set, the latest supported standard for your compiler is used +# You can later set fine-grained standards for each target using `target_compile_features` +# Note: linking together projects compiled with different C++ standards may work, but +# it is not recommended because of possible issues with ABI +set(CMAKE_CXX_STANDARD 23) +set(CMAKE_C_STANDARD 23) + +# Strongly encouraged to enable this globally to avoid conflicts between +# -Wpedantic being enabled and -std=c++23 and -std=gnu++23 for example +# when compiling with PCH enabled +set(CMAKE_CXX_EXTENSIONS ON ) +set(CMAKE_CXX_STANDARD_REQUIRED ON ) +set(CMAKE_C_EXTENSIONS OFF) +set(CMAKE_C_STANDARD_REQUIRED ON ) + +set(CMAKE_EXPORT_COMPILE_COMMANDS ON ) + +# Set compiler options using this form since one of the dependency is +# using an old version of CMake and is not compatibile with expression generator. +if(CMAKE_C_COMPILER_ID STREQUAL "GNU") + set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} -O0 -g3 -ggdb") + set(CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE} -O3") +elseif(CMAKE_C_COMPILER_ID STREQUAL "Clang" OR CMAKE_C_COMPILER_ID STREQUAL "AppleClang") + set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} -O0 -g") + set(CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE} -O3") +elseif(CMAKE_C_COMPILER_ID STREQUAL "MSVC") + set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} /Od /Zi") + set(CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE} /O2") +endif() + +set(POSITION_INDEPENDENT_CODE ON) + +find_package(PkgConfig REQUIRED) +set(CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/cmake) +message(STATUS "${PROJECT_SOURCE_DIR}") + +# Options. Turn on with 'cmake -Dvarname=ON'. +option(LIBKLV_ENABLE_STATIC "Enable static library. Default is dynamic." ON ) +option(LIBKLV_ENABLE_DEBUG "Enable/Disable debug information." OFF) +option(LIBKLV_BUILD_TESTS "Build all tests." OFF) # Check for packages INCLUDE(FindPkgConfig) -pkg_check_modules(LOG4CPP "log4cpp") # INCLUDES -include_directories(${LOG4CPP_INCLUDE_DIRS}) -include_directories(include) +include_directories( ${CMAKE_CURRENT_SOURCE_DIR}/include/ ) # SOURCES #Can manually add the sources using the set command as follows: -#set(SOURCES src/mainapp.cpp src/Student.cpp) file(GLOB SOURCES "src/*.cpp") -add_library(klv SHARED ${SOURCES}) +if (LIBKLV_ENABLE_STATIC) + add_library(klv STATIC ${SOURCES}) +else(LIBKLV_ENABLE_STATIC) + add_library(klv SHARED ${SOURCES}) +endif(LIBKLV_ENABLE_STATIC) -# LINKING -# target_link_libraries(libklv z) # zlib -# target_link_libraries(libklv m) # math -target_link_libraries(klv ${LOG4CPP_LIBRARIES}) +if (LIBKLV_ENABLE_DEBUG) + target_compile_definitions(klv PRIVATE ENABLE_KLV_DEBUG=1 ) +else(LIBKLV_ENABLE_DEBUG) + target_compile_definitions(klv PRIVATE ENABLE_KLV_DEBUG=0 ) +endif(LIBKLV_ENABLE_DEBUG) + +if(CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang") + set(LIBKLV_WARNINGS -Wall -Wextra -Wpedantic -Wshadow -Wconversion -Wunused ) +elseif(MSVC) + set(LIBKLV_WARNINGS /W4 /permissive ) +endif() + +target_compile_options(klv PRIVATE ${LIBKLV_WARNINGS}) -# TESTING # TESTING -enable_testing() -add_subdirectory("${PROJECT_SOURCE_DIR}/deps/googletest") +if(LIBKLV_BUILD_TESTS) + enable_testing() + add_subdirectory("${PROJECT_SOURCE_DIR}/deps/googletest") -file(GLOB TESTS "test/*.cpp") + file(GLOB TESTS "test/*.cpp") -add_executable(runUnitTests ${TESTS}) -target_link_libraries(runUnitTests gtest gtest_main gmock klv) -add_test(NAME libklv-test COMMAND runUnitTests) + add_executable(runUnitTests ${TESTS}) + target_link_libraries(runUnitTests gtest gtest_main gmock klv) + add_test(NAME libklv-test COMMAND runUnitTests) +endif(LIBKLV_BUILD_TESTS) \ No newline at end of file diff --git a/include/Klv.h b/include/Klv.h deleted file mode 100644 index 79d158a..0000000 --- a/include/Klv.h +++ /dev/null @@ -1,125 +0,0 @@ -#ifndef KLV_H -#define KLV_H - -#include -#include -#include - -// Is this necessary? No rule saying KLV has a max payload size... -#define MAX_KLV_VALUE_SIZE 4294967296 -#define KLV_KEY_SIZE 16 - -// Key definitions -#define SMPTE_KLV_UL_HEADER_LEN 4 -const uint8_t SMPTE_KLV_UL_HEADER[] = {0x06, 0x0E, 0x2B, 0x34}; // SMPTE KLV Universal Label (UL) Header - // SMPTE KLV headers will ALWAYS start with this - -/** - * @brief Simple class representing a Key-Length-Value (KLV). - * - * Only universal keys (16-bytes long) are supported. - * Data lengths are Basic-Encoding-Rules (BER) encoded. - * - * This class represents KLV in two ways. - * 1. As a simple data structure where the Key, Length, and Value can be accessed - * as std::vector member variables - * 2. As a tree, since the value field in the KLV may have sub-KLV items. - * - * A Map can be used to index each KLV with their key - * - * References: - * SMPTE 336-2007 - * ST 0601.8 - UAS Datalink Local Metadata Set - */ -class KLV { - -public: - KLV() {} - KLV(const std::vector key, const std::vector val); - KLV(const std::vector key, const std::vector len, const std::vector val); - virtual ~KLV(); - - std::vector getKey() const { return this->key; } - std::vector getLenEncoded() const { return this->len_encoded; } - std::vector getValue() const { return this->value; } - unsigned long getLen() const { return this->len; } - unsigned long getBerLen() const { return ber_len; } - - KLV* getParent() const { return this->parent; } - KLV* getChild() const { return this->child; } - KLV* getPrevious() const { return this->previous_sibling; } - KLV* getNext() const { return this->next_sibling; } - - void setParent(KLV* parent) { this->parent = parent; } - void setChild(KLV* child) { this->child = child; } - void setPreviousSibling(KLV* previous) { this->previous_sibling = previous; } - void setNextSibling(KLV* next) { this->next_sibling = next; } - - std::vector toBytes(); - std::unordered_map, KLV> indexToMap(); - void addToMap(std::unordered_map, KLV> &map); - - // operator overloads - bool operator==(const KLV &other) const { - return (key == other.key - && len_encoded == other.len_encoded - && value == other.value - && parent == other.parent - && child == other.child - && previous_sibling == other.previous_sibling - && next_sibling == other.next_sibling); - } - - // hashing functor - struct hash { - std::size_t operator()(const KLV& k) const { - using std::hash; - - // unchecked - // { - // int hash = 17; - // hash = hash * 31 + firstField.GetHashCode(); - // hash = hash * 31 + secondField.GetHashCode(); - // return hash; - // } - int res = 17; - for(auto b : k.key) - res = res * 31 + hash()(b); - for(auto b : k.len_encoded) - res = res * 31 + hash()(b); - for(auto b : k.value) - res = res * 31 + hash()(b); - res = res * 31 + hash()(k.len); - res = res * 31 + hash()(k.ber_len); - return res; - } - }; - -private: - std::vector key; /// Key (1,2,4, or 16 bytes in length) (typically 16-byte universal key or BER-OID for LDS tags) - std::vector len_encoded; /// Data length (BER) (short & long form) - std::vector value; /// Value (variable-length) - unsigned long len; /// Data length in human-readable format - unsigned long ber_len; /// Length of BER len field - KLV* parent; /// parent KLV node, NULL if on top level branch - KLV* child; /// first child in branch, NULL if leave node - KLV* previous_sibling;/// previous KLV node on branch, NULL if none. Typically if first node in branch, this will be NULL - KLV* next_sibling; /// next KLV node on branch, NULL if none -}; - - -// specialize the hash function for std::vector so that it may be used as a key in STL maps -namespace std { - template <> - struct hash> { - std::size_t operator()(const std::vector& k) const { - int res = 17; - for(auto b : k) - res = res * 31 + std::hash()(b); - return res; - } - }; -} - - -#endif // KLV_H diff --git a/include/Klv.hpp b/include/Klv.hpp new file mode 100644 index 0000000..4404744 --- /dev/null +++ b/include/Klv.hpp @@ -0,0 +1,174 @@ +#ifndef KLV_HPP +#define KLV_HPP + +#include "KlvCommon.hpp" + +#include +#include +#include +#include +#include +#include +#include + +// Is this necessary? No rule saying KLV has a max payload size... +#define MAX_KLV_VALUE_SIZE 4294967296 +#define KLV_KEY_SIZE 16 + +// Key definitions +#define SMPTE_KLV_UL_HEADER_LEN 4 +const std::array SMPTE_KLV_UL_HEADER = {0x06, 0x0E, 0x2B, 0x34}; // SMPTE KLV Universal Label (UL) Header + // SMPTE KLV headers will ALWAYS start with this + +/** hash function for an std::vector */ +struct vector_hash_t +{ + constexpr std::size_t operator()(const std::vector& vector) const noexcept(true) + { + auto view = std::string_view(reinterpret_cast(vector.data()), vector.size()); + return std::hash{}(view); + } +}; + +/** + * @brief Simple class representing a Key-Length-Value (KLV). + * + * Only universal keys (16-bytes long) are supported. + * Data lengths are Basic-Encoding-Rules (BER) encoded. + * + * This class represents KLV in two ways. + * 1. As a simple data structure where the Key, Length, and Value can be accessed + * as std::vector member variables + * 2. As a tree, since the value field in the KLV may have sub-KLV items. + * + * A Map can be used to index each KLV with their key + * + * References: + * SMPTE 336-2007 + * ST 0601.8 - UAS Datalink Local Metadata Set + */ +class KLV : public std::enable_shared_from_this +{ +public: + using klv_map_t = std::unordered_map, KLV, vector_hash_t>; + + /***/ + KLV() noexcept(true); + /***/ + KLV(std::vector&& key, std::vector&& val) noexcept(true); + /***/ + KLV(std::vector&& key, std::vector&& len, std::vector&& val) noexcept(false); + /***/ + KLV(const std::vector& key, const std::vector& val) noexcept(true); + /***/ + KLV(const std::vector& key, const std::vector& len, const std::vector& val) noexcept(false); + /***/ + virtual ~KLV() noexcept(true); + + /***/ + constexpr const std::vector& getKey() const noexcept(true) + { return m_key; } + /***/ + constexpr const std::vector& getLenEncoded() const noexcept(true) + { return m_len_encoded; } + /***/ + constexpr const std::vector& getValue() const noexcept(true) + { return m_value; } + /***/ + constexpr uint64_t getLen() const noexcept(true) + { return m_len; } + /***/ + constexpr uint64_t getBerLen() const noexcept(true) + { return m_ber_len; } + + /** + * Decode a well-formed BER-OID key into uint64_t. Returns false for + * empty keys, keys longer than 10 bytes, invalid continuation encodings, + * or values that overflow uint64_t. `out` is left unchanged on failure. + */ + bool getTagAsInt(uint64_t& out) const noexcept(true); + + constexpr std::shared_ptr getParent() const noexcept(true) + { return m_parent; } + constexpr std::shared_ptr getChild() const noexcept(true) + { return m_child; } + constexpr std::shared_ptr getPrevious() const noexcept(true) + { return m_previous_sibling; } + constexpr std::shared_ptr getNext() const noexcept(true) + { return m_next_sibling; } + + constexpr void setParent(std::shared_ptr parent) noexcept(true) + { m_parent = parent; } + constexpr void setChild(std::shared_ptr child) noexcept(true) + { m_child = child; } + constexpr void setPreviousSibling(std::shared_ptr previous) noexcept(true) + { m_previous_sibling = previous; } + constexpr void setNextSibling(std::shared_ptr next) noexcept(true) + { m_next_sibling = next; } + + /** + * Retrun a vector with a copy of internal arrays key,len,values. + * Note: Evalutate to use toBytesViews when possible. + */ + std::vector toBytes() const noexcept(true); + /* Return a view with zero-copy approach that reader can see as a continuous array of bytes. */ + constexpr auto toBytesView() const noexcept(true) + { + auto views_array = std::array{ std::span{m_key}, std::span{m_len_encoded}, std::span{m_value} }; + return views_array | std::views::join; + } + + klv_map_t indexToMap() const noexcept(true); + void addToMap(klv_map_t &map) const noexcept(true); + + // operator overloads + constexpr bool operator==(const KLV &other) const noexcept(true) + { + return (m_key == other.m_key + && m_len_encoded == other.m_len_encoded + && m_value == other.m_value + && m_parent == other.m_parent + && m_child == other.m_child + && m_previous_sibling == other.m_previous_sibling + && m_next_sibling == other.m_next_sibling); + } + + // hashing functor + struct hash + { + constexpr std::size_t operator()(const KLV& klv) const noexcept(true) + { + std::size_t seed = 0x9e3779b9; /* Golden Ratio */ + + for (const uint8_t byte : klv.toBytesView()) + { + seed ^= static_cast(byte) + 0x9e3779b9 + (seed << 6) + (seed >> 2); + } + + auto mix_primitive = [&seed](unsigned long value) noexcept(true) + { seed ^= std::hash{}(value) + 0x9e3779b9 + (seed << 6) + (seed >> 2); }; + + mix_primitive(klv.m_len); + mix_primitive(klv.m_ber_len); + + return seed; + } + }; + +private: + /***/ + void init() noexcept(false); + +private: + std::vector m_key; /// Key (1,2,4, or 16 bytes in length) (typically 16-byte universal key or BER-OID for LDS tags) + std::vector m_len_encoded; /// Data length (BER) (short & long form) + std::vector m_value; /// Value (variable-length) + uint64_t m_len; /// Data length in human-readable format + uint64_t m_ber_len; /// Length of BER len field + std::shared_ptr m_parent; /// parent KLV node, NULL if on top level branch + std::shared_ptr m_child; /// first child in branch, NULL if leave node + std::shared_ptr m_previous_sibling;/// previous KLV node on branch, NULL if none. Typically if first node in branch, this will be NULL + std::shared_ptr m_next_sibling; /// next KLV node on branch, NULL if none +}; + +#endif /* KLV_HPP */ diff --git a/include/KlvCommon.hpp b/include/KlvCommon.hpp new file mode 100644 index 0000000..dd54d12 --- /dev/null +++ b/include/KlvCommon.hpp @@ -0,0 +1,16 @@ +#ifndef KLV_COMMON_HPP +#define KLV_COMMON_HPP + +#include + +#if defined(ENABLE_KLV_DEBUG) && (ENABLE_KLV_DEBUG == 1) + #define KLV_LOG(format, ...) \ + do { \ + std::printf("[KLV DEBUG] [%s:%d] " format "\n", \ + __FILE__, __LINE__, ##__VA_ARGS__); \ + } while(0) +#else + #define KLV_LOG(format, ...) do {} while(0) +#endif + +#endif /* KLV_COMMON_HPP */ \ No newline at end of file diff --git a/include/KlvParser.hpp b/include/KlvParser.hpp index 545cc07..b96bb7f 100644 --- a/include/KlvParser.hpp +++ b/include/KlvParser.hpp @@ -6,91 +6,95 @@ // // -#ifndef KlvParser_hpp -#define KlvParser_hpp +#ifndef KLV_PARSER_HPP +#define KLV_PARSER_HPP #include #include -#include "Klv.h" +#include "Klv.hpp" /** * KLV Parser */ -class KlvParser { - +class KlvParser +{ public: - /** - * Encoding method for keys - */ - enum KeyEncoding { - KEY_ENCODING_1_BYTE, - KEY_ENCODING_2_BYTE, - KEY_ENCODING_4_BYTE, - KEY_ENCODING_16_BYTE, - KEY_ENCODING_BER_OID - }; - + /** + * Encoding method for keys + */ + enum class KeyEncoding : uint8_t + { + KEY_ENCODING_1_BYTE, + KEY_ENCODING_2_BYTE, + KEY_ENCODING_4_BYTE, + KEY_ENCODING_16_BYTE, + KEY_ENCODING_BER_OID + }; - /** - * Constructs a new KLV parser. Since keys can be encoded using different methods, - * a vector of KeyEncodings must be passed to allow the parser to know how to decode - * keys correctly. A vector is used to allow different encodings for different levels - * of KLV when parsing embedded KLV triplets. For example, the root/base KLV may use a - * universal 16-byte key, and the embedded KLVs may use a local data set encoding of - * BER-OID. If no KeyEncoding is specified, then it is assumed that 16-byte keys are - * used at the root/base key and BER-OID encoding is used for the embedded KLVs. - * - * @param key_encodings vector of KeyEncodings to be used when parsing - */ - KlvParser(std::vector key_encodings); - virtual ~KlvParser(); + /** + * Constructs a new KLV parser. Since keys can be encoded using different methods, + * a vector of KeyEncodings must be passed to allow the parser to know how to decode + * keys correctly. A vector is used to allow different encodings for different levels + * of KLV when parsing embedded KLV triplets. For example, the root/base KLV may use a + * universal 16-byte key, and the embedded KLVs may use a local data set encoding of + * BER-OID. If no KeyEncoding is specified, then it is assumed that 16-byte keys are + * used at the root/base key and BER-OID encoding is used for the embedded KLVs. + * + * @param key_encodings vector of KeyEncodings to be used when parsing + */ + KlvParser(std::vector key_encodings) noexcept(true); + /***/ + virtual ~KlvParser() noexcept(true); - /** - * Parses a single byte and returns the parsed KLV (if successful). If partial - * KLV (i.e. not enough bytes) then the parser is left in a "partial" state - * where if this method is called again with the remaining bytes, this method - * will then return the parsed KLV. If invalid KLV is parsed, then an exception - * will be thrown and the return value will be NULL. - * - * @param byte byte to parse - * @return the new kLV parsed from the input bytes. NULL if no KLV was - * parsed or error occured. Ownership is transfered to the caller. - */ - virtual KLV* parseByte(uint8_t byte); + /** + * Parses a single byte and returns the parsed KLV (if successful). If partial + * KLV (i.e. not enough bytes) then the parser is left in a "partial" state + * where if this method is called again with the remaining bytes, this method + * will then return the parsed KLV. If invalid KLV is parsed, then an exception + * will be thrown and the return value will be NULL. + * + * @param byte byte to parse + * @return the new kLV parsed from the input bytes. NULL if no KLV was + * parsed or error occured. Ownership is transfered to the caller. + */ + virtual std::shared_ptr parseByte(uint8_t byte) noexcept(true); protected: - bool checkIfContainsKlvKey(std::vector data); - void resetFields(); + /***/ + bool checkIfContainsKlvKey(const std::vector& data) noexcept(true); + /***/ + void resetFields() noexcept(true); - /** - * Parser state enum - */ - enum State { - STATE_INIT, /// init state - STATE_KEY, /// read KLV 16-byte universal key - STATE_LEN_HEADER, /// read first byte in BER-encoded length field - STATE_LEN, /// read entire BER-encoded length field - STATE_VALUE /// read value field - }; + /** + * Parser state enum + */ + enum class State : uint8_t + { + STATE_INIT, /// init state + STATE_KEY, /// read KLV 16-byte universal key + STATE_LEN_HEADER, /// read first byte in BER-encoded length field + STATE_LEN, /// read entire BER-encoded length field + STATE_VALUE /// read value field + }; - long ctr; /// counter for bytes read by this parser - State state; /// parser state - std::vector key_encodings; /// encoding to use for keys - std::vector key; /// 16-byte universal key - std::vector len; /// BER-encoded length - std::vector val; /// value +private: + long m_ctr; /// counter for bytes read by this parser + State m_state; /// parser state + std::vector m_key_encodings; /// encoding to use for keys + std::vector m_key; /// 16-byte universal key + std::vector m_len; /// BER-encoded length + std::vector m_val; /// value - bool ber_long_form; /// true if BER length field is long form, false if short form - unsigned long ber_len; /// length of BER-encoded length field in bytes - unsigned long num_ber_len_bytes_read; /// number of bytes read for BER length field - unsigned long val_len; /// length of value field in bytes - - KLV* parent; /// parent KLV node, NULL if on top level branch - KLV* child; /// first child in branch, NULL if leave node - KLV* previous_sibling;/// previous KLV node on branch, NULL if none. Typically if first node in branch, this will be NULL - KLV* next_sibling; /// next KLV node on branch, NULL if none -}; + bool m_ber_long_form; /// true if BER length field is long form, false if short form + uint64_t m_ber_len; /// length of BER-encoded length field in bytes + uint64_t m_num_ber_len_bytes_read; /// number of bytes read for BER length field + uint64_t m_val_len; /// length of value field in bytes + std::shared_ptr m_parent; /// parent KLV node, NULL if on top level branch + std::shared_ptr m_child; /// first child in branch, NULL if leave node + std::shared_ptr m_previous_sibling; /// previous KLV node on branch, NULL if none. Typically if first node in branch, this will be NULL + std::shared_ptr m_next_sibling; /// next KLV node on branch, NULL if none +}; -#endif /* KlvParser_hpp */ +#endif /* KLV_PARSER_HPP */ diff --git a/src/Klv.cpp b/src/Klv.cpp index 584d3cc..f9c27c6 100644 --- a/src/Klv.cpp +++ b/src/Klv.cpp @@ -1,8 +1,32 @@ -#include "Klv.h" -#include -#include +#include "Klv.hpp" + #include +KLV::KLV() noexcept(true) + : m_key{}, m_len_encoded{}, m_value{}, + m_len{0}, m_ber_len{0}, + m_parent{nullptr}, m_child{nullptr}, m_previous_sibling{nullptr}, m_next_sibling{nullptr} +{} + +KLV::KLV( std::vector&& key, std::vector&& val) noexcept(true) + : m_key{std::move(key)}, m_len_encoded{}, m_value{std::move(val)}, + m_len{0}, m_ber_len{0}, + m_parent{nullptr}, m_child{nullptr}, m_previous_sibling{nullptr}, m_next_sibling{nullptr} +{ + // TODO: calc BER encoded length and store it in lenEncoded field + + // TODO: figure out BER length + // TODO: figure out human-readable len +} + +KLV::KLV( std::vector&& key, std::vector&& len, std::vector&& val) noexcept(false) + : m_key{std::move(key)}, m_len_encoded{ std::move(len) }, m_value{std::move(val)}, + m_len{0}, m_ber_len{0}, + m_parent{nullptr}, m_child{nullptr}, m_previous_sibling{nullptr}, m_next_sibling{nullptr} +{ + init(); +} + /** * @brief Convinience constructor to create a KLV object with a specified universal key * and data buffer. This will also encode the length (BER). @@ -10,16 +34,11 @@ * @param key 16-byte global unique identifier * @param val data buffer */ -KLV::KLV(const std::vector key, const std::vector val) { - this->key = key; - this->value = val; - - // TODO: calc BER encoded length and store it in lenEncoded field - - - - // TODO: figure out BER length - // TODO: figure out human-readable len +KLV::KLV( const std::vector& key, const std::vector& val) noexcept(true) + : m_key{key}, m_len_encoded{}, m_value{val}, + m_len{0}, m_ber_len{0}, + m_parent{nullptr}, m_child{nullptr}, m_previous_sibling{nullptr}, m_next_sibling{nullptr} +{ } /** @@ -29,70 +48,106 @@ KLV::KLV(const std::vector key, const std::vector val) { * @param len BER-encoded length * @param val data buffer */ -KLV::KLV(const std::vector key, const std::vector len, const std::vector val) { - this->key = key; - this->len_encoded = len; - this->value = val; - this->parent = NULL; - this->child = NULL; - this->next_sibling = NULL; - this->previous_sibling = NULL; - - // BER encoding has a short form and long form - // Most significant bit (bit 7) is the short/long form flag - // 0 - short form - // 1 - long form - - // BER Short Form Length encoding - // - // example short form BER length: - // [ 0 1 0 0 1 1 0 0 ] - // bit 7 6 5 4 3 2 1 0 - // Length field using the short form are represented using a single byte (8 - // bits). The most significant bit in this byte signals that the long form is being used. The last - // seven bits depict the number of bytes that follow the BER encoded length. - // Short form BER encoding is form value fields less than 128 bytes in length - - // BER Long Form Length encoding - // - // example long form BER length: - // [1 0 0 0 0 0 0 1 1 1 0 0 1 0 0 1] - // bit 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 - // The long form encodes length field using multiple bytes. The first byte - // indicates long form encoding as well as the number of subsequent bytes that represent the length. - // The bytes that follow the leading byte are the encoding of an unsigned binary integer equal to the - // number of bytes in the packet. - // Long form BER encoding is for value fields more than 128 bytes in length - - if(len.size() > 1) { - // BER long form - - // get the length of the BER field in bytes - this->ber_len = len[0] & 0b01111111; - - if(ber_len != len.size() - 1) { - throw std::invalid_argument("len argument encoded length " + std::to_string(ber_len) + " did not match actual length " + std::to_string(len.size() - 1)); - } - - // iterate through the len vector for that many bytes - this->len = len[1]; - for(int i = 1; i < ber_len; i++) { - this->len <<= 8; - this->len |= len[2+i]; - } - - } else { - // BER short form - this->len = len[0]; - } +KLV::KLV(const std::vector& key, const std::vector& len, const std::vector& val) noexcept(false) + : m_key{key}, m_len_encoded{len}, m_value{val}, + m_len{0}, m_ber_len{0}, + m_parent{nullptr}, m_child{nullptr}, m_previous_sibling{nullptr}, m_next_sibling{nullptr} +{ + init(); } /** * @brief Destructor */ -KLV::~KLV() { - // this class will not delete it's parent, children, or siblings - // the external owner of this class must handle that +KLV::~KLV() noexcept(true) +{ + // this class will not delete it's parent, children, or siblings + // the external owner of this class must handle that +} + +/***/ +void KLV::init() noexcept(false) +{ + // BER encoding has a short form and long form + // Most significant bit (bit 7) is the short/long form flag + // 0 - short form + // 1 - long form + + // BER Short Form Length encoding + // + // example short form BER length: + // [ 0 1 0 0 1 1 0 0 ] + // bit 7 6 5 4 3 2 1 0 + // Length field using the short form are represented using a single byte (8 + // bits). The most significant bit in this byte signals that the long form is being used. The last + // seven bits depict the number of bytes that follow the BER encoded length. + // Short form BER encoding is form value fields less than 128 bytes in length + + // BER Long Form Length encoding + // + // example long form BER length: + // [1 0 0 0 0 0 0 1 1 1 0 0 1 0 0 1] + // bit 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 + // The long form encodes length field using multiple bytes. The first byte + // indicates long form encoding as well as the number of subsequent bytes that represent the length. + // The bytes that follow the leading byte are the encoding of an unsigned binary integer equal to the + // number of bytes in the packet. + // Long form BER encoding is for value fields more than 128 bytes in length + + if(m_len_encoded.size() > 1) + { + // BER long form + + // get the length of the BER field in bytes + m_ber_len = m_len_encoded[0] & 0b01111111; + + if(m_ber_len != m_len_encoded.size() - 1) + { + throw std::invalid_argument("len argument encoded length " + std::to_string(m_ber_len) + " did not match actual length " + std::to_string(m_len_encoded.size() - 1)); + } + + // iterate through the len vector for that many bytes + m_len = m_len_encoded[1]; + for(size_t i = 1; i < m_ber_len; i++) + { + m_len <<= 8; + m_len |= m_len_encoded[1+i]; + } + } + else + { + // BER short form + m_len = m_len_encoded[0]; + } +} + +bool KLV::getTagAsInt(uint64_t& out) const noexcept(true) +{ + // widest BER-OID key that can fit in uint64_t. + if (m_key.empty() || m_key.size() > 10) + { return false; } + + uint64_t acc = 0; + for (size_t i = 0; i < m_key.size(); i++) + { + const uint8_t byte = m_key[i]; + const bool is_last_byte = (i + 1 == m_key.size()); + const bool has_continuation_bit = (byte & 0x80) != 0; + + // Valid BER-OID: continuation bit set on every byte except the last. + if (has_continuation_bit == is_last_byte) + { return false; } + + // Reject any BER-OID sequence whose next 7-bit shift would overflow + // uint64_t (payload bits past bit 63 would otherwise be dropped). + if ((acc >> (64 - 7)) != 0) + { return false; } + + acc = (acc << 7) | (byte & 0x7f); + } + + out = acc; + return true; } /** @@ -100,44 +155,41 @@ KLV::~KLV() { * * @return Vector of encoded bytes. Empty vector if key, len, or value field has not been set. */ -std::vector KLV::toBytes() { - std::vector buffer; - if(key.size() > 0 && len_encoded.size() > 0 && value.size() > 0) { - // append Key, Len, and Value to buffer - buffer.insert(buffer.end(), key.begin(), key.end()); - buffer.insert(buffer.end(), len_encoded.begin(), len_encoded.end()); - buffer.insert(buffer.end(), value.begin(), value.end()); - } - return buffer; +std::vector KLV::toBytes() const noexcept(true) +{ + std::vector buffer; + + const size_t total_size = m_key.size() + m_len_encoded.size() + m_value.size(); + if(total_size > 0) + { + buffer.resize(total_size); + + auto current_pos = buffer.begin(); + current_pos = std::copy(m_key.begin() , m_key.end() , current_pos); + current_pos = std::copy(m_len_encoded.begin(), m_len_encoded.end(), current_pos); + std::copy(m_value.begin() , m_value.end() , current_pos); + } + return buffer; } -std::unordered_map, KLV> KLV::indexToMap() { - std::unordered_map, KLV> map; +KLV::klv_map_t KLV::indexToMap() const noexcept(true) +{ + KLV::klv_map_t map; - // iterate through the tree from this KLV and add each KLV node to the map - // KLV* node = next_sibling; - // while(node != NULL) { - // // map.emplace(node->getKey(), *node); - // map[node->getKey()] = *node; + addToMap(map); - // if(node->getChild() != NULL) { - // // will need a recursive function that takes in a KLV and adds it to map - // } - - // node = node->getNext(); - // } - addToMap(map); - - return map; + return map; } -void KLV::addToMap(std::unordered_map, KLV> &map) { - map[getKey()] = *this; - - // recursive depth-first add to map - KLV* node = child; - while(node != NULL) { - node->addToMap(map); - node = node->getNext(); - } -} \ No newline at end of file +void KLV::addToMap(KLV::klv_map_t &map) const noexcept(true) +{ + map[getKey()] = *this; + + // recursive depth-first add to map + std::shared_ptr node = m_child; + while(node != NULL) + { + node->addToMap(map); + node = node->getNext(); + } +} diff --git a/src/KlvParser.cpp b/src/KlvParser.cpp index 7d705c9..5292485 100644 --- a/src/KlvParser.cpp +++ b/src/KlvParser.cpp @@ -9,6 +9,7 @@ #include "KlvParser.hpp" #include #include +#include /** * Constructs a new KLV parser. Since keys can be encoded using different methods, @@ -21,14 +22,15 @@ * * @param key_encodings vector of KeyEncodings to be used when parsing */ -KlvParser::KlvParser(std::vector key_encodings) { - ctr = 0; - resetFields(); - this->key_encodings = key_encodings; +KlvParser::KlvParser(std::vector key_encodings) noexcept(true) +{ + m_ctr = 0; + resetFields(); + m_key_encodings = key_encodings; } -KlvParser::~KlvParser() { - +KlvParser::~KlvParser() noexcept(true) +{ } /** @@ -42,118 +44,157 @@ KlvParser::~KlvParser() { * @return the new kLV parsed from the input bytes. NULL if no KLV was * parsed or error occured. Ownership is transfered to the caller. */ -KLV* KlvParser::parseByte(uint8_t byte) { - ctr++; - printf("PARSING BYTE %ld : %x\n", ctr, byte); - switch(state) { - case STATE_INIT: { // init state - // keep parsing until last 16 bytes match with KLV universal key - // keep parsing until first 4 bytes of the 16-byte KLV universal key is detected - // store last 16 bytes in key - // keep parsing until first 4 bytes of the 16-byte key match the 4-byte KLV unversal key header - key.push_back(byte); - - // handle looking for KEY depending on how its encoded - switch(key_encodings[0]) { - case KEY_ENCODING_1_BYTE: { - state = STATE_KEY; - printf("KlvParser transitioning to STATE_KEY\n"); - break; - } - case KEY_ENCODING_2_BYTE: { - if(key.size() == 2) { - state = STATE_KEY; - printf("KlvParser transitioning to STATE_KEY\n"); - } - break; - } - case KEY_ENCODING_4_BYTE: { - if(key.size() == 4) { - state = STATE_KEY; - printf("KlvParser transitioning to STATE_KEY\n"); - } - break; - } - case KEY_ENCODING_16_BYTE: { - if(key.size() > 16) - key.erase(key.begin()); - - if(checkIfContainsKlvKey(key)) { - // found the 4-byte KLV universal key header at beginning - state = STATE_KEY; - printf("KlvParser transitioning to STATE_KEY\n"); - } - break; - } - case KEY_ENCODING_BER_OID: { - // keep parsing until we read a byte where bit 8 is 0 (indicating we've read the LSB of the BER-OID key) - // TODO: the KLV class actually should have a human-readable tag due to this encoding technique - if(!(byte & 0b10000000)) { - state = STATE_KEY; - printf("KlvParser transitioning to STATE_KEY\n"); - } - break; - } +std::shared_ptr KlvParser::parseByte(uint8_t byte) noexcept(true) +{ + m_ctr++; + + KLV_LOG("PARSING BYTE %ld : %x", m_ctr, byte); + switch(m_state) + { + // init state + case State::STATE_INIT: + { + // keep parsing until last 16 bytes match with KLV universal key + // keep parsing until first 4 bytes of the 16-byte KLV universal key is detected + // store last 16 bytes in key + // keep parsing until first 4 bytes of the 16-byte key match the 4-byte KLV unversal key header + m_key.push_back(byte); + + // handle looking for KEY depending on how its encoded + switch(m_key_encodings[0]) + { + case KeyEncoding::KEY_ENCODING_1_BYTE: + { + m_state = State::STATE_KEY; + KLV_LOG("KlvParser transitioning to STATE_KEY"); + }; break; + + case KeyEncoding::KEY_ENCODING_2_BYTE: + { + if(m_key.size() == 2) + { + m_state = State::STATE_KEY; + KLV_LOG("KlvParser transitioning to STATE_KEY"); + } + }; break; + + case KeyEncoding::KEY_ENCODING_4_BYTE: + { + if(m_key.size() == 4) + { + m_state = State::STATE_KEY; + KLV_LOG("KlvParser transitioning to STATE_KEY"); + } + }; break; + + case KeyEncoding::KEY_ENCODING_16_BYTE: + { + if(m_key.size() > 16) + m_key.erase(m_key.begin()); + + if(checkIfContainsKlvKey(m_key)) + { + // found the 4-byte KLV universal key header at beginning + m_state = State::STATE_KEY; + KLV_LOG("KlvParser transitioning to STATE_KEY"); + } + }; break; + + case KeyEncoding::KEY_ENCODING_BER_OID: + { + // keep parsing until we read a byte where bit 8 is 0 (indicating we've read the LSB of the BER-OID key) + // TODO: the KLV class actually should have a human-readable tag due to this encoding technique + if(!(byte & 0b10000000)) + { + m_state = State::STATE_KEY; + KLV_LOG("KlvParser transitioning to STATE_KEY"); + } + }; break; + default: - // not supposed to be here :-) - break; - } + { + // not supposed to be here :-) + }; break; + } /* switch(key_encodings[0]) */ + }; break; - break; - } + // read KLV 16-byte universal key + case State::STATE_KEY: + { + // read byte, and figure out if long or short BER form + m_len.push_back(byte); + m_ber_long_form = (bool) (byte & 0b10000000); - case STATE_KEY: { // read KLV 16-byte universal key - // read byte, and figure out if long or short BER form - len.push_back(byte); - ber_long_form = (bool) (byte & 0b10000000); - - if(ber_long_form) { - state = STATE_LEN_HEADER; - ber_len = byte & 0b01111111; - val_len = 0; - printf("BER-Len field is long-form\n"); - printf("BER len: %ld\n", ber_len); - printf("KlvParser transitioning to STATE_LEN_HEADER\n"); - } else { - state = STATE_LEN; - val_len = byte & 0b01111111; - printf("BER-Len field is short-form\n"); - printf("Value length: %ld\n", val_len); - printf("KlvParser transitioning to STATE_LEN\n"); + if(m_ber_long_form) + { + m_state = State::STATE_LEN_HEADER; + m_ber_len = byte & 0b01111111; + m_val_len = 0; + KLV_LOG("BER-Len field is long-form"); + KLV_LOG("BER len: %ld", m_ber_len); + KLV_LOG("KlvParser transitioning to STATE_LEN_HEADER"); + } + else + { + m_val_len = byte & 0b01111111; + KLV_LOG("Value length: %ld", m_val_len); + if(m_val_len == 0) + { + KLV_LOG("BER-Len field is zero-length (ZLI)"); + std::shared_ptr _klv = std::make_shared(m_key, m_len, m_val); + resetFields(); + return _klv; } - break; + else + { + m_state = State::STATE_LEN; + KLV_LOG("BER-Len field is short-form"); + KLV_LOG("KlvParser transitioning to STATE_LEN"); + } + } + break; } - case STATE_LEN_HEADER: { // read first byte in BER-encoded length field - // keep parsing for ber_len bytes and store into val_len - len.push_back(byte); - val_len <<= 8; - val_len |= byte; - num_ber_len_bytes_read++; - if(num_ber_len_bytes_read == ber_len) { - state = STATE_LEN; - printf("Value length: %ld\n", val_len); - printf("KlvParser transitioning to STATE_LEN\n"); - } - break; + /* read first byte in BER-encoded length field */ + case State::STATE_LEN_HEADER: + { + // keep parsing for ber_len bytes and store into val_len + m_len.push_back(byte); + m_val_len <<= 8; + m_val_len |= byte; + m_num_ber_len_bytes_read++; + if(m_num_ber_len_bytes_read == m_ber_len) + { + m_state = State::STATE_LEN; + KLV_LOG("Value length: %ld", m_val_len); + KLV_LOG("KlvParser transitioning to STATE_LEN"); + } + break; } - case STATE_LEN: { // read entire BER-encoded length field - // keep parsing for val_len bytes and store into val - val.push_back(byte); - if(val.size() == val_len) { - state = STATE_VALUE; - printf("KlvParser transitioning to STATE_VALUE\n"); - } else { - break; - } + /* read entire BER-encoded length field */ + case State::STATE_LEN: + { + // keep parsing for val_len bytes and store into val + m_val.push_back(byte); + if(m_val.size() == m_val_len) + { + m_state = State::STATE_VALUE; + KLV_LOG("KlvParser transitioning to STATE_VALUE"); + [[fallthrough]]; + } + else + { + break; + } } - case STATE_VALUE: { // read value field + /* read value field */ + case State::STATE_VALUE: + { // construct KLV object // TODO: use smart pointer here and transfer ownership to caller - KLV *klv = new KLV(key, len, val); - + std::shared_ptr _klv = std::make_shared(m_key, m_len, m_val); // for right now this will only construct the "root" part of the incoming KLV // ...and not recurse into the tree if there is embedded KLV @@ -171,49 +212,62 @@ KLV* KlvParser::parseByte(uint8_t byte) { // check if last 16 bytes are KLV key // if so then create a sub_klv_parser and then start parsing using those 16 bytes then the next bytes coming in - printf("key_encodings.size() : %ld\n", key_encodings.size()); + KLV_LOG("key_encodings.size() : %ld", m_key_encodings.size()); // key_encodings.erase(key_encodings.begin()); - if(key_encodings.size() > 1) { - printf("Creating sub_klv_parser...\n"); - KlvParser sub_klv_parser(std::vector(key_encodings.begin()+1, key_encodings.end())); - std::vector sub_klvs; - KLV* sub_klv = NULL; - int i = 0; - - // parse the sub-KLVs - for(i = 0; i < val.size(); i++) { - sub_klv = sub_klv_parser.parseByte(val[i]); - if(sub_klv != NULL) { - printf("new sub KLV\n"); - sub_klvs.push_back(sub_klv); - sub_klv = NULL; - } + if(m_key_encodings.size() > 1) + { + KLV_LOG("Creating sub_klv_parser..."); + KlvParser sub_klv_parser(std::vector(m_key_encodings.begin()+1, m_key_encodings.end())); + std::vector> sub_klvs; + std::shared_ptr sub_klv = nullptr; + size_t i = 0; + + // parse the sub-KLVs + for(i = 0; i < m_val.size(); i++) + { + sub_klv = sub_klv_parser.parseByte(m_val[i]); + if(sub_klv != nullptr) + { + KLV_LOG("new sub KLV"); + sub_klvs.push_back(sub_klv); + sub_klv = NULL; } + } + if(sub_klvs.size() > 0) + { // assign child of THIS klv to the first child in the vector - klv->setChild(sub_klvs[0]); + _klv->setChild(sub_klvs[0]); // assign the next and previous sibling fields and the parent field in each of the sub_klvs - for(i = 0; i < sub_klvs.size(); i++) { - if(sub_klvs.size() > 1) { - if(i == 0) { - // beginning - sub_klvs[i]->setNextSibling(sub_klvs[i+1]); - } else if(i == sub_klvs.size()) { - // end - sub_klvs[i]->setPreviousSibling(sub_klvs[i-1]); - } else { - sub_klvs[i]->setNextSibling(sub_klvs[i+1]); - sub_klvs[i]->setPreviousSibling(sub_klvs[i-1]); - } + for(i = 0; i < sub_klvs.size(); i++) + { + if(sub_klvs.size() > 1) + { + if(i == 0) + { + // beginning + sub_klvs[i]->setNextSibling(sub_klvs[i+1]); + } + else if(i == (sub_klvs.size() - 1)) + { + // end + sub_klvs[i]->setPreviousSibling(sub_klvs[i-1]); + } + else + { + sub_klvs[i]->setNextSibling(sub_klvs[i+1]); + sub_klvs[i]->setPreviousSibling(sub_klvs[i-1]); } - sub_klvs[i]->setParent(klv); + } + sub_klvs[i]->setParent(_klv); } + } } - + // reset state machine back to STATE_INIT and return parsed KLV resetFields(); - return klv; + return _klv; } default: @@ -221,29 +275,31 @@ KLV* KlvParser::parseByte(uint8_t byte) { break; } // end switch(state) - return NULL; + return nullptr; } -bool KlvParser::checkIfContainsKlvKey(std::vector data) { - // TODO: come up with faster optimized method - return ((std::search(data.begin(), data.end(), - SMPTE_KLV_UL_HEADER, SMPTE_KLV_UL_HEADER+SMPTE_KLV_UL_HEADER_LEN)) == data.begin() - && data.size() == 16); +bool KlvParser::checkIfContainsKlvKey(const std::vector& data) noexcept(true) +{ + if (data.size() != 16) [[unlikely]] + return false; + + return std::memcmp(data.data(), SMPTE_KLV_UL_HEADER.data(), SMPTE_KLV_UL_HEADER_LEN) == 0; } -void KlvParser::resetFields() { - state = STATE_INIT; - ber_len = 0; - val_len = 0; - num_ber_len_bytes_read = 0; - ber_long_form = false; - - key.clear(); - len.clear(); - val.clear(); - - child = NULL; - parent = NULL; - next_sibling = NULL; - previous_sibling = NULL; -} \ No newline at end of file +void KlvParser::resetFields() noexcept(true) +{ + m_state = State::STATE_INIT; + m_ber_len = 0; + m_val_len = 0; + m_num_ber_len_bytes_read = 0; + m_ber_long_form = false; + + m_key.clear(); + m_len.clear(); + m_val.clear(); + + m_child = nullptr; + m_parent = nullptr; + m_next_sibling = nullptr; + m_previous_sibling = nullptr; +} diff --git a/src/main.cpp b/src/main.cpp deleted file mode 100644 index 96dd8eb..0000000 --- a/src/main.cpp +++ /dev/null @@ -1,8 +0,0 @@ -#include -#include - -int main(int argc, char* argv[]) -{ - std::string foo; - return 0; -} diff --git a/test/KlvParserTest.cpp b/test/KlvParserTest.cpp index f390aba..a8be44f 100644 --- a/test/KlvParserTest.cpp +++ b/test/KlvParserTest.cpp @@ -29,88 +29,86 @@ class KlvParserTest : public ::testing::Test { }; -static void printByteVector(const std::vector vec) { - printf("{ "); - for(uint8_t const &b : vec) { - printf("%02x, ", b); - } - printf("}"); +static void printByteVector(const std::vector& vec) +{ + printf("{ "); + for(uint8_t const &b : vec) { + printf("%02x, ", b); + } + printf("}"); } -TEST_F(KlvParserTest, TestParsePkt) { - // test parse a single packet - - // key: 0x06, 0x0E, 0x2B, 0x34, 0x02, 0x0B, 0x01, 0x01, 0x0E, 0x01, 0x03, 0x01, 0x01, 0x00, 0x00, 0x00 - // len: 0x81, 0x90 (144 bytes) - // val: the rest - std::vector test_pkt = { 0x06, 0x0E, 0x2B, 0x34, 0x02, 0x0B, 0x01, 0x01, 0x0E, 0x01, 0x03, 0x01, 0x01, 0x00, 0x00, 0x00, 0x81, 0x90, 0x02, 0x08, 0x00, 0x04, 0x6C, 0xAE, 0x70, 0xF9, 0x80, 0xCF, 0x41, 0x01, 0x01, 0x05, 0x02, 0xE1, 0x91, 0x06, 0x02, 0x06, 0x0D, 0x07, 0x02, 0x0A, 0xE1, 0x0B, 0x02, 0x49, 0x52, 0x0C, 0x0E, 0x47, 0x65, 0x6F, 0x64, 0x65, 0x74, 0x69, 0x63, 0x20, 0x57, 0x47, 0x53, 0x38, 0x34, 0x0D, 0x04, 0x4D, 0xCC, 0x41, 0x90, 0x0E, 0x04, 0xB1, 0xD0, 0x3D, 0x96, 0x0F, 0x02, 0x1B, 0x2E, 0x10, 0x02, 0x00, 0x84, 0x11, 0x02, 0x00, 0x4A, 0x12, 0x04, 0xE7, 0x23, 0x0B, 0x61, 0x13, 0x04, 0xFD, 0xE8, 0x63, 0x8E, 0x14, 0x04, 0x03, 0x0B, 0xC7, 0x1C, 0x15, 0x04, 0x00, 0x9F, 0xB9, 0x38, 0x16, 0x04, 0x00, 0x00, 0x01, 0xF8, 0x17, 0x04, 0x4D, 0xEC, 0xDA, 0xF4, 0x18, 0x04, 0xB1, 0xBC, 0x81, 0x74, 0x19, 0x02, 0x0B, 0x8A, 0x28, 0x04, 0x4D, 0xEC, 0xDA, 0xF4, 0x29, 0x04, 0xB1, 0xBC, 0x81, 0x74, 0x2A, 0x02, 0x0B, 0x8A, 0x38, 0x01, 0x31, 0x39, 0x04, 0x00, 0x9F, 0x85, 0x4D, 0x01, 0x02, 0xB7, 0xEB }; +TEST_F(KlvParserTest, TestParsePkt) +{ + // test parse a single packet - KlvParser parser({KlvParser::KEY_ENCODING_16_BYTE}); + // key: 0x06, 0x0E, 0x2B, 0x34, 0x02, 0x0B, 0x01, 0x01, 0x0E, 0x01, 0x03, 0x01, 0x01, 0x00, 0x00, 0x00 + // len: 0x81, 0x90 (144 bytes) + // val: the rest + std::vector test_pkt = { 0x06, 0x0E, 0x2B, 0x34, 0x02, 0x0B, 0x01, 0x01, 0x0E, 0x01, 0x03, 0x01, 0x01, 0x00, 0x00, 0x00, 0x81, 0x90, 0x02, 0x08, 0x00, 0x04, 0x6C, 0xAE, 0x70, 0xF9, 0x80, 0xCF, 0x41, 0x01, 0x01, 0x05, 0x02, 0xE1, 0x91, 0x06, 0x02, 0x06, 0x0D, 0x07, 0x02, 0x0A, 0xE1, 0x0B, 0x02, 0x49, 0x52, 0x0C, 0x0E, 0x47, 0x65, 0x6F, 0x64, 0x65, 0x74, 0x69, 0x63, 0x20, 0x57, 0x47, 0x53, 0x38, 0x34, 0x0D, 0x04, 0x4D, 0xCC, 0x41, 0x90, 0x0E, 0x04, 0xB1, 0xD0, 0x3D, 0x96, 0x0F, 0x02, 0x1B, 0x2E, 0x10, 0x02, 0x00, 0x84, 0x11, 0x02, 0x00, 0x4A, 0x12, 0x04, 0xE7, 0x23, 0x0B, 0x61, 0x13, 0x04, 0xFD, 0xE8, 0x63, 0x8E, 0x14, 0x04, 0x03, 0x0B, 0xC7, 0x1C, 0x15, 0x04, 0x00, 0x9F, 0xB9, 0x38, 0x16, 0x04, 0x00, 0x00, 0x01, 0xF8, 0x17, 0x04, 0x4D, 0xEC, 0xDA, 0xF4, 0x18, 0x04, 0xB1, 0xBC, 0x81, 0x74, 0x19, 0x02, 0x0B, 0x8A, 0x28, 0x04, 0x4D, 0xEC, 0xDA, 0xF4, 0x29, 0x04, 0xB1, 0xBC, 0x81, 0x74, 0x2A, 0x02, 0x0B, 0x8A, 0x38, 0x01, 0x31, 0x39, 0x04, 0x00, 0x9F, 0x85, 0x4D, 0x01, 0x02, 0xB7, 0xEB }; - // for each byte in the test packet, pass it through the parser until we get a valid Klv object - KLV* parsed_klv = NULL; - int i; - for(i = 0; i < test_pkt.size(); i++) { - parsed_klv = parser.parseByte(test_pkt[i]); - - if(parsed_klv != NULL) - break; - } + KlvParser parser({KlvParser::KeyEncoding::KEY_ENCODING_16_BYTE}); - // check if entire test_pkt was read - EXPECT_GE(i+1, test_pkt.size()); + // for each byte in the test packet, pass it through the parser until we get a valid Klv object + std::shared_ptr parsed_klv{nullptr}; + int i; + for(i = 0; i < test_pkt.size(); i++) { + parsed_klv = parser.parseByte(test_pkt[i]); + + if(parsed_klv != nullptr) + break; + } - // check if we actually parsed a valid KLV - ASSERT_TRUE(parsed_klv != NULL); + // check if entire test_pkt was read + EXPECT_GE(i+1, test_pkt.size()); - std::vector test_key = {0x06, 0x0E, 0x2B, 0x34, 0x02, 0x0B, 0x01, 0x01, 0x0E, 0x01, 0x03, 0x01, 0x01, 0x00, 0x00, 0x00}; - std::vector test_len = {0x81, 0x90}; - std::vector test_val = {0x02, 0x08, 0x00, 0x04, 0x6C, 0xAE, 0x70, 0xF9, 0x80, 0xCF, 0x41, 0x01, 0x01, 0x05, 0x02, 0xE1, 0x91, 0x06, 0x02, 0x06, 0x0D, 0x07, 0x02, 0x0A, 0xE1, 0x0B, 0x02, 0x49, 0x52, 0x0C, 0x0E, 0x47, 0x65, 0x6F, 0x64, 0x65, 0x74, 0x69, 0x63, 0x20, 0x57, 0x47, 0x53, 0x38, 0x34, 0x0D, 0x04, 0x4D, 0xCC, 0x41, 0x90, 0x0E, 0x04, 0xB1, 0xD0, 0x3D, 0x96, 0x0F, 0x02, 0x1B, 0x2E, 0x10, 0x02, 0x00, 0x84, 0x11, 0x02, 0x00, 0x4A, 0x12, 0x04, 0xE7, 0x23, 0x0B, 0x61, 0x13, 0x04, 0xFD, 0xE8, 0x63, 0x8E, 0x14, 0x04, 0x03, 0x0B, 0xC7, 0x1C, 0x15, 0x04, 0x00, 0x9F, 0xB9, 0x38, 0x16, 0x04, 0x00, 0x00, 0x01, 0xF8, 0x17, 0x04, 0x4D, 0xEC, 0xDA, 0xF4, 0x18, 0x04, 0xB1, 0xBC, 0x81, 0x74, 0x19, 0x02, 0x0B, 0x8A, 0x28, 0x04, 0x4D, 0xEC, 0xDA, 0xF4, 0x29, 0x04, 0xB1, 0xBC, 0x81, 0x74, 0x2A, 0x02, 0x0B, 0x8A, 0x38, 0x01, 0x31, 0x39, 0x04, 0x00, 0x9F, 0x85, 0x4D, 0x01, 0x02, 0xB7, 0xEB }; + // check if we actually parsed a valid KLV + ASSERT_TRUE(parsed_klv != nullptr); - EXPECT_THAT(test_key, ::testing::ContainerEq(parsed_klv->getKey())); - EXPECT_THAT(test_len, ::testing::ContainerEq(parsed_klv->getLenEncoded())); - EXPECT_THAT(test_val, ::testing::ContainerEq(parsed_klv->getValue())); + std::vector test_key = {0x06, 0x0E, 0x2B, 0x34, 0x02, 0x0B, 0x01, 0x01, 0x0E, 0x01, 0x03, 0x01, 0x01, 0x00, 0x00, 0x00}; + std::vector test_len = {0x81, 0x90}; + std::vector test_val = {0x02, 0x08, 0x00, 0x04, 0x6C, 0xAE, 0x70, 0xF9, 0x80, 0xCF, 0x41, 0x01, 0x01, 0x05, 0x02, 0xE1, 0x91, 0x06, 0x02, 0x06, 0x0D, 0x07, 0x02, 0x0A, 0xE1, 0x0B, 0x02, 0x49, 0x52, 0x0C, 0x0E, 0x47, 0x65, 0x6F, 0x64, 0x65, 0x74, 0x69, 0x63, 0x20, 0x57, 0x47, 0x53, 0x38, 0x34, 0x0D, 0x04, 0x4D, 0xCC, 0x41, 0x90, 0x0E, 0x04, 0xB1, 0xD0, 0x3D, 0x96, 0x0F, 0x02, 0x1B, 0x2E, 0x10, 0x02, 0x00, 0x84, 0x11, 0x02, 0x00, 0x4A, 0x12, 0x04, 0xE7, 0x23, 0x0B, 0x61, 0x13, 0x04, 0xFD, 0xE8, 0x63, 0x8E, 0x14, 0x04, 0x03, 0x0B, 0xC7, 0x1C, 0x15, 0x04, 0x00, 0x9F, 0xB9, 0x38, 0x16, 0x04, 0x00, 0x00, 0x01, 0xF8, 0x17, 0x04, 0x4D, 0xEC, 0xDA, 0xF4, 0x18, 0x04, 0xB1, 0xBC, 0x81, 0x74, 0x19, 0x02, 0x0B, 0x8A, 0x28, 0x04, 0x4D, 0xEC, 0xDA, 0xF4, 0x29, 0x04, 0xB1, 0xBC, 0x81, 0x74, 0x2A, 0x02, 0x0B, 0x8A, 0x38, 0x01, 0x31, 0x39, 0x04, 0x00, 0x9F, 0x85, 0x4D, 0x01, 0x02, 0xB7, 0xEB }; - if(parsed_klv != NULL) - delete parsed_klv; + EXPECT_THAT(test_key, ::testing::ContainerEq(parsed_klv->getKey())); + EXPECT_THAT(test_len, ::testing::ContainerEq(parsed_klv->getLenEncoded())); + EXPECT_THAT(test_val, ::testing::ContainerEq(parsed_klv->getValue())); } -TEST_F(KlvParserTest, TestTree) { - // test that the tree is constructed correctly - - std::vector test_pkt_uas = {0x06, 0x0E, 0x2B, 0x34, 0x02, 0x0B, 0x01, 0x01, 0x0E, 0x01, 0x03, 0x01, 0x01, 0x00, 0x00, 0x00, 0x81, 0x90, 0x02, 0x08, 0x00, 0x04, 0x6C, 0xAE, 0x70, 0xF9, 0x80, 0xCF, 0x41, 0x01, 0x01, 0x05, 0x02, 0xE1, 0x91, 0x06, 0x02, 0x06, 0x0D, 0x07, 0x02, 0x0A, 0xE1, 0x0B, 0x02, 0x49, 0x52, 0x0C, 0x0E, 0x47, 0x65, 0x6F, 0x64, 0x65, 0x74, 0x69, 0x63, 0x20, 0x57, 0x47, 0x53, 0x38, 0x34, 0x0D, 0x04, 0x4D, 0xCC, 0x41, 0x90, 0x0E, 0x04, 0xB1, 0xD0, 0x3D, 0x96, 0x0F, 0x02, 0x1B, 0x2E, 0x10, 0x02, 0x00, 0x84, 0x11, 0x02, 0x00, 0x4A, 0x12, 0x04, 0xE7, 0x23, 0x0B, 0x61, 0x13, 0x04, 0xFD, 0xE8, 0x63, 0x8E, 0x14, 0x04, 0x03, 0x0B, 0xC7, 0x1C, 0x15, 0x04, 0x00, 0x9F, 0xB9, 0x38, 0x16, 0x04, 0x00, 0x00, 0x01, 0xF8, 0x17, 0x04, 0x4D, 0xEC, 0xDA, 0xF4, 0x18, 0x04, 0xB1, 0xBC, 0x81, 0x74, 0x19, 0x02, 0x0B, 0x8A, 0x28, 0x04, 0x4D, 0xEC, 0xDA, 0xF4, 0x29, 0x04, 0xB1, 0xBC, 0x81, 0x74, 0x2A, 0x02, 0x0B, 0x8A, 0x38, 0x01, 0x31, 0x39, 0x04, 0x00, 0x9F, 0x85, 0x4D, 0x01, 0x02, 0xB7, 0xEB}; - - // TODO: need to create a test packet that has sub-klv elements in it - KlvParser parser({KlvParser::KEY_ENCODING_16_BYTE, KlvParser::KEY_ENCODING_BER_OID}); - - // for each byte in the test packet, pass it through the parser until we get a valid Klv object - KLV* parsed_klv = NULL; - int i; - for(i = 0; i < test_pkt_uas.size(); i++) { - parsed_klv = parser.parseByte(test_pkt_uas[i]); - - if(parsed_klv != NULL) - break; - } - - // check if entire test_pkt was read - EXPECT_GE(i+1, test_pkt_uas.size()); - - // check if we actually parsed a valid KLV - ASSERT_TRUE(parsed_klv != NULL); - - auto map = parsed_klv->indexToMap(); - - printf("map.size() : %ld\n", map.size()); - for(auto pair : map) { - printByteVector(pair.first); - printf(" - "); - printByteVector(pair.second.getValue()); - printf("\n"); - } - - if(parsed_klv != NULL) - delete parsed_klv; +TEST_F(KlvParserTest, TestTree) +{ + // test that the tree is constructed correctly + + std::vector test_pkt_uas = {0x06, 0x0E, 0x2B, 0x34, 0x02, 0x0B, 0x01, 0x01, 0x0E, 0x01, 0x03, 0x01, 0x01, 0x00, 0x00, 0x00, 0x81, 0x90, 0x02, 0x08, 0x00, 0x04, 0x6C, 0xAE, 0x70, 0xF9, 0x80, 0xCF, 0x41, 0x01, 0x01, 0x05, 0x02, 0xE1, 0x91, 0x06, 0x02, 0x06, 0x0D, 0x07, 0x02, 0x0A, 0xE1, 0x0B, 0x02, 0x49, 0x52, 0x0C, 0x0E, 0x47, 0x65, 0x6F, 0x64, 0x65, 0x74, 0x69, 0x63, 0x20, 0x57, 0x47, 0x53, 0x38, 0x34, 0x0D, 0x04, 0x4D, 0xCC, 0x41, 0x90, 0x0E, 0x04, 0xB1, 0xD0, 0x3D, 0x96, 0x0F, 0x02, 0x1B, 0x2E, 0x10, 0x02, 0x00, 0x84, 0x11, 0x02, 0x00, 0x4A, 0x12, 0x04, 0xE7, 0x23, 0x0B, 0x61, 0x13, 0x04, 0xFD, 0xE8, 0x63, 0x8E, 0x14, 0x04, 0x03, 0x0B, 0xC7, 0x1C, 0x15, 0x04, 0x00, 0x9F, 0xB9, 0x38, 0x16, 0x04, 0x00, 0x00, 0x01, 0xF8, 0x17, 0x04, 0x4D, 0xEC, 0xDA, 0xF4, 0x18, 0x04, 0xB1, 0xBC, 0x81, 0x74, 0x19, 0x02, 0x0B, 0x8A, 0x28, 0x04, 0x4D, 0xEC, 0xDA, 0xF4, 0x29, 0x04, 0xB1, 0xBC, 0x81, 0x74, 0x2A, 0x02, 0x0B, 0x8A, 0x38, 0x01, 0x31, 0x39, 0x04, 0x00, 0x9F, 0x85, 0x4D, 0x01, 0x02, 0xB7, 0xEB}; + + // TODO: need to create a test packet that has sub-klv elements in it + KlvParser parser({KlvParser::KeyEncoding::KEY_ENCODING_16_BYTE, KlvParser::KeyEncoding::KEY_ENCODING_BER_OID}); + + // for each byte in the test packet, pass it through the parser until we get a valid Klv object + std::shared_ptr parsed_klv{nullptr}; + int i; + for(i = 0; i < test_pkt_uas.size(); i++) + { + parsed_klv = parser.parseByte(test_pkt_uas[i]); + + if(parsed_klv != nullptr) + break; + } + + // check if entire test_pkt was read + EXPECT_GE(i+1, test_pkt_uas.size()); + + // check if we actually parsed a valid KLV + ASSERT_TRUE(parsed_klv != nullptr); + + auto map = parsed_klv->indexToMap(); + + printf("map.size() : %ld\n", map.size()); + for(auto pair : map) { + printByteVector(pair.first); + printf(" - "); + printByteVector(pair.second.getValue()); + printf("\n"); + } } TEST_F(KlvParserTest, TestParseMultiplePkts) { diff --git a/test/KlvTest.cpp b/test/KlvTest.cpp index f717ddb..3182a3f 100644 --- a/test/KlvTest.cpp +++ b/test/KlvTest.cpp @@ -3,7 +3,7 @@ #include "gtest/gtest.h" #include "gmock/gmock.h" -#include "Klv.h" +#include "Klv.hpp" class KlvTest : public ::testing::Test { protected: @@ -47,3 +47,78 @@ TEST_F(KlvTest, TestConstruction) { EXPECT_THAT(test_klv.getLenEncoded(), ::testing::ContainerEq(len)); EXPECT_THAT(test_klv.getValue(), ::testing::ContainerEq(val)); } + +TEST_F(KlvTest, GetTagAsInt_SingleByte) { + const KLV klv(std::vector{0x05}, std::vector{0x00}); + uint64_t tag = 0; + EXPECT_TRUE(klv.getTagAsInt(tag)); + EXPECT_EQ(tag, 5u); +} + +TEST_F(KlvTest, GetTagAsInt_MultiByte) { + // 0x81 0x00 is BER-OID for 128 (1000'0001 0000'0000 -> bits 0000001 0000000). + const KLV klv(std::vector{0x81, 0x00}, std::vector{0x00}); + uint64_t tag = 0; + EXPECT_TRUE(klv.getTagAsInt(tag)); + EXPECT_EQ(tag, 128u); + + // 0xC0 0x80 0x00 is BER-OID for 0x100000 = 1048576. + const KLV klv2(std::vector{0xC0, 0x80, 0x00}, std::vector{0x00}); + EXPECT_TRUE(klv2.getTagAsInt(tag)); + EXPECT_EQ(tag, 0x100000u); +} + +TEST_F(KlvTest, GetTagAsInt_EmptyKey) { + const KLV klv; + uint64_t tag = 0xABCD; + EXPECT_FALSE(klv.getTagAsInt(tag)); + EXPECT_EQ(tag, 0xABCDu); // unchanged on failure +} + +TEST_F(KlvTest, GetTagAsInt_UniversalKeyRejected) { + // 16-byte universal root keys exceed what getTagAsInt can decode into + // a uint64_t without overflow, so they are rejected here. + const std::vector universal = { + 0x06, 0x0E, 0x2B, 0x34, 0x02, 0x0B, 0x01, 0x01, + 0x0E, 0x01, 0x03, 0x01, 0x01, 0x00, 0x00, 0x00 + }; + const KLV klv(universal, std::vector{0x00}); + uint64_t tag = 0xABCD; + EXPECT_FALSE(klv.getTagAsInt(tag)); + EXPECT_EQ(tag, 0xABCDu); +} + +TEST_F(KlvTest, GetTagAsInt_NonContinuationFollowedByMore) { + // 0x05 has the continuation bit clear, so 0x2A cannot follow it. + const KLV klv(std::vector{0x05, 0x2A}, std::vector{0x00}); + uint64_t tag = 0xFEED; + EXPECT_FALSE(klv.getTagAsInt(tag)); + EXPECT_EQ(tag, 0xFEEDu); +} + +TEST_F(KlvTest, GetTagAsInt_ContinuationWithNoSuccessor) { + // 0x95 has the continuation bit set but nothing follows. + const KLV klv(std::vector{0x95}, std::vector{0x00}); + uint64_t tag = 0xFEED; + EXPECT_FALSE(klv.getTagAsInt(tag)); + EXPECT_EQ(tag, 0xFEEDu); +} + +TEST_F(KlvTest, GetTagAsInt_FullUint64Range) { + // UINT64_MAX encodes as 10 BER-OID bytes: one payload bit in the first + // byte, 7 in each of the remaining nine. + const KLV klv(std::vector{0x81, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F}, + std::vector{0x00}); + uint64_t tag = 0; + EXPECT_TRUE(klv.getTagAsInt(tag)); + EXPECT_EQ(tag, UINT64_MAX); +} + +TEST_F(KlvTest, GetTagAsInt_OverflowRejected) { + // First byte has payload 0x02, so the total exceeds UINT64_MAX. + const KLV klv(std::vector{0x82, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F}, + std::vector{0x00}); + uint64_t tag = 0xFEED; + EXPECT_FALSE(klv.getTagAsInt(tag)); + EXPECT_EQ(tag, 0xFEEDu); +} From 4e5d9987fdb3568262da896e2fa2b68a9f0aa439 Mon Sep 17 00:00:00 2001 From: Francesco D'Agostino Date: Fri, 26 Jun 2026 14:07:23 +0200 Subject: [PATCH 2/3] add PIC and installation dir --- CMakeLists.txt | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index 0af801d..a5e377c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -65,6 +65,8 @@ else(LIBKLV_ENABLE_STATIC) add_library(klv SHARED ${SOURCES}) endif(LIBKLV_ENABLE_STATIC) +set_property(TARGET klv PROPERTY POSITION_INDEPENDENT_CODE ON) + if (LIBKLV_ENABLE_DEBUG) target_compile_definitions(klv PRIVATE ENABLE_KLV_DEBUG=1 ) else(LIBKLV_ENABLE_DEBUG) @@ -79,6 +81,9 @@ endif() target_compile_options(klv PRIVATE ${LIBKLV_WARNINGS}) +# Rule to install the library target +install(TARGETS klv LIBRARY DESTINATION "${CMAKE_INSTALL_LIBDIR}/") + # TESTING if(LIBKLV_BUILD_TESTS) From 622b5706b3b6e4590638f0fb7eb2060fb149f33a Mon Sep 17 00:00:00 2001 From: Francesco D'Agostino Date: Fri, 26 Jun 2026 14:15:30 +0200 Subject: [PATCH 3/3] updated cmake version and compiler version --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 20e47cd..634a4ca 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@ from MPEG2-TS video. ## Dependencies -* Compiler that supports C++14 +* Compiler that supports C++23 * [Google Test](https://github.com/google/googletest) * [CMake](https://cmake.org/)