diff --git a/cbxp/cbxp.cpp b/cbxp/cbxp.cpp index 5e8618f..48b897e 100644 --- a/cbxp/cbxp.cpp +++ b/cbxp/cbxp.cpp @@ -7,8 +7,8 @@ #include "cbxp_result.h" #include "control_block_explorer.hpp" -const cbxp_result_t* cbxp(const char* control_block_name, - const char* includes_string, bool debug) { +cbxp_result_t* cbxp(const char* control_block_name, const char* includes_string, + const char* filters_string, bool debug) { nlohmann::json control_block_json; std::string control_block = control_block_name; @@ -17,7 +17,7 @@ const cbxp_result_t* cbxp(const char* control_block_name, CBXP::ControlBlockExplorer explorer = CBXP::ControlBlockExplorer(&cbxp_result, debug); - explorer.exploreControlBlock(control_block, includes_string); + explorer.exploreControlBlock(control_block, includes_string, filters_string); return &cbxp_result; } diff --git a/cbxp/cbxp.h b/cbxp/cbxp.h index 4e50bf7..c52baf3 100644 --- a/cbxp/cbxp.h +++ b/cbxp/cbxp.h @@ -7,8 +7,8 @@ extern "C" { #endif -const cbxp_result_t* cbxp(const char* control_block_name, - const char* includes_string, bool debug); +cbxp_result_t* cbxp(const char* control_block_name, const char* includes_string, + const char* filters_string, bool debug); #ifdef __cplusplus } diff --git a/cbxp/control_block_error.hpp b/cbxp/control_block_error.hpp index 65902a0..cdddd3d 100644 --- a/cbxp/control_block_error.hpp +++ b/cbxp/control_block_error.hpp @@ -2,7 +2,7 @@ #define __CONTROL_BLOCK_ERROR_H_ namespace CBXP { -enum Error { BadControlBlock = 1, BadInclude }; +enum Error { BadControlBlock = 1, BadInclude, BadFilter }; class CBXPError : public std::exception { private: Error error_code_; @@ -22,6 +22,11 @@ class IncludeError : public CBXPError { IncludeError() : CBXPError(Error::BadInclude) {} }; +class FilterError : public CBXPError { + public: + FilterError() : CBXPError(Error::BadFilter) {} +}; + } // namespace CBXP #endif diff --git a/cbxp/control_block_explorer.cpp b/cbxp/control_block_explorer.cpp index f1cfd65..d8a6331 100644 --- a/cbxp/control_block_explorer.cpp +++ b/cbxp/control_block_explorer.cpp @@ -17,35 +17,35 @@ namespace CBXP { -std::vector ControlBlockExplorer::createIncludeList( - const std::string& includes_string) { - if (includes_string == "") { +std::vector ControlBlockExplorer::createOptionsList( + const std::string& comma_separated_string) { + if (comma_separated_string == "") { return {}; } - std::vector includes = {}; + std::vector options_list = {}; Logger::getInstance().debug( - "Creating include list from the provided include list string: " + - includes_string); + "Creating options list from the provided comma-separated list string: " + + comma_separated_string); const std::string del = ","; std::string entry; size_t index = 0; - auto pos = includes_string.find(del); + auto pos = comma_separated_string.find(del); while (pos != std::string::npos) { - entry = includes_string.substr(index, pos); - includes.push_back(entry); + entry = comma_separated_string.substr(index, pos); + options_list.push_back(entry); index += pos + 1; - pos = includes_string.substr(index, std::string::npos).find(del); + pos = comma_separated_string.substr(index, std::string::npos).find(del); } - entry = includes_string.substr(index, pos); - includes.push_back(entry); + entry = comma_separated_string.substr(index, pos); + options_list.push_back(entry); Logger::getInstance().debug("Done."); - return includes; + return options_list; } ControlBlockExplorer::ControlBlockExplorer(cbxp_result_t* p_result, @@ -65,9 +65,12 @@ ControlBlockExplorer::ControlBlockExplorer(cbxp_result_t* p_result, } void ControlBlockExplorer::exploreControlBlock( - const std::string& control_block_name, const std::string& includes_string) { + const std::string& control_block_name, const std::string& includes_string, + const std::string& filters_string) { std::vector includes = - ControlBlockExplorer::createIncludeList(includes_string); + ControlBlockExplorer::createOptionsList(includes_string); + std::vector filters = + ControlBlockExplorer::createOptionsList(filters_string); Logger::getInstance().debug("Extracting '" + control_block_name + "' control block data..."); @@ -75,17 +78,17 @@ void ControlBlockExplorer::exploreControlBlock( nlohmann::json control_block_json; try { if (control_block_name == "psa") { - control_block_json = PSA(includes).get(); + control_block_json = PSA(includes, filters).get(); } else if (control_block_name == "cvt") { - control_block_json = CVT(includes).get(); + control_block_json = CVT(includes, filters).get(); } else if (control_block_name == "ecvt") { - control_block_json = ECVT(includes).get(); + control_block_json = ECVT(includes, filters).get(); } else if (control_block_name == "ascb") { - control_block_json = ASCB(includes).get(); + control_block_json = ASCB(includes, filters).get(); } else if (control_block_name == "asvt") { - control_block_json = ASVT(includes).get(); + control_block_json = ASVT(includes, filters).get(); } else if (control_block_name == "assb") { - control_block_json = ASSB(includes).get(); + control_block_json = ASSB(includes, filters).get(); } else { throw ControlBlockError(); } diff --git a/cbxp/control_block_explorer.hpp b/cbxp/control_block_explorer.hpp index 7542b17..aca843e 100644 --- a/cbxp/control_block_explorer.hpp +++ b/cbxp/control_block_explorer.hpp @@ -9,13 +9,14 @@ namespace CBXP { class ControlBlockExplorer { private: cbxp_result_t* p_result_; - static std::vector createIncludeList( - const std::string& includes_string); + static std::vector createOptionsList( + const std::string& comma_separated_string); public: ControlBlockExplorer(cbxp_result_t* p_result, bool debug); void exploreControlBlock(const std::string& control_block_name, - const std::string& includes_string); + const std::string& includes_string, + const std::string& filters_string); }; } // namespace CBXP diff --git a/cbxp/control_blocks/ascb.cpp b/cbxp/control_blocks/ascb.cpp index d2f5fa9..f575129 100644 --- a/cbxp/control_blocks/ascb.cpp +++ b/cbxp/control_blocks/ascb.cpp @@ -42,7 +42,11 @@ nlohmann::json ASCB::get(void* __ptr32 p_control_block) { p_ascb_addr++; continue; } - ascbs.push_back(ASCB::get(reinterpret_cast(*p_ascb_addr))); + nlohmann::json next_ascb = + ASCB::get(reinterpret_cast(*p_ascb_addr)); + if (!next_ascb.is_null()) { + ascbs.push_back(next_ascb); + } p_ascb_addr++; // This SHOULD increment the pointer by 4 bytes. } return ascbs; @@ -53,10 +57,14 @@ nlohmann::json ASCB::get(void* __ptr32 p_control_block) { ascb_json["ascbassb"] = formatter_.getHex(&(p_ascb->ascbassb)); ascb_json["ascbasxb"] = formatter_.getHex(&(p_ascb->ascbasxb)); - for (const auto& [include, include_includes] : include_map_) { + for (const auto& [include, cbxp_options] : options_map_) { if (include == "assb") { ascb_json["ascbassb"] = - CBXP::ASSB(include_includes).get(p_ascb->ascbassb); + CBXP::ASSB(cbxp_options.include_patterns, cbxp_options.filters) + .get(p_ascb->ascbassb); + if (ascb_json["ascbassb"].is_null()) { + return {}; + } } } @@ -90,6 +98,10 @@ nlohmann::json ASCB::get(void* __ptr32 p_control_block) { ascb_json["ascbxtcb"] = formatter_.getHex(&(p_ascb->ascbxtcb)); ascb_json["ascbzcx"] = formatter_.getBitmap(p_ascb->ascbzcx); - return ascb_json; + if (ASCB::matchFilter(ascb_json)) { + return ascb_json; + } else { + return {}; + } } } // namespace CBXP diff --git a/cbxp/control_blocks/ascb.hpp b/cbxp/control_blocks/ascb.hpp index e811530..85a6c86 100644 --- a/cbxp/control_blocks/ascb.hpp +++ b/cbxp/control_blocks/ascb.hpp @@ -8,8 +8,9 @@ namespace CBXP { class ASCB : public ControlBlock { public: nlohmann::json get(void* __ptr32 p_control_block = nullptr) override; - explicit ASCB(const std::vector& includes) - : ControlBlock("ascb", {"assb"}, includes) {} + explicit ASCB(const std::vector& includes, + const std::vector& filters) + : ControlBlock("ascb", {"assb"}, includes, filters) {} }; } // namespace CBXP diff --git a/cbxp/control_blocks/assb.cpp b/cbxp/control_blocks/assb.cpp index 3a12daf..a548d72 100644 --- a/cbxp/control_blocks/assb.cpp +++ b/cbxp/control_blocks/assb.cpp @@ -46,8 +46,9 @@ nlohmann::json ASSB::get(void* __ptr32 p_control_block) { // const struct ascb* __ptr32 p_ascb = reinterpret_cast(*p_ascb_addr); - assbs.push_back( - ASSB::get(reinterpret_cast(p_ascb->ascbassb))); + nlohmann::json next_assb = + ASSB::get(reinterpret_cast(p_ascb->ascbassb)); + if (!next_assb.is_null()) assbs.push_back(next_assb); p_ascb_addr++; // This SHOULD increment the pointer by 4 bytes. } return assbs; @@ -195,6 +196,10 @@ nlohmann::json ASSB::get(void* __ptr32 p_control_block) { formatter_.uint(p_assb->assbinitiatorjobid); assb_json["assbend"] = formatter_.uint(p_assb->assbend); - return assb_json; + if (ASSB::matchFilter(assb_json)) { + return assb_json; + } else { + return {}; + } } } // namespace CBXP diff --git a/cbxp/control_blocks/assb.hpp b/cbxp/control_blocks/assb.hpp index 6692b2f..b376755 100644 --- a/cbxp/control_blocks/assb.hpp +++ b/cbxp/control_blocks/assb.hpp @@ -8,8 +8,9 @@ namespace CBXP { class ASSB : public ControlBlock { public: nlohmann::json get(void* __ptr32 p_control_block = nullptr) override; - explicit ASSB(const std::vector& includes) - : ControlBlock("assb", {}, includes) {} + explicit ASSB(const std::vector& includes, + const std::vector& filters) + : ControlBlock("assb", {}, includes, filters) {} }; } // namespace CBXP diff --git a/cbxp/control_blocks/asvt.cpp b/cbxp/control_blocks/asvt.cpp index 17ad32f..82a5a24 100644 --- a/cbxp/control_blocks/asvt.cpp +++ b/cbxp/control_blocks/asvt.cpp @@ -47,10 +47,10 @@ nlohmann::json ASVT::get(void* __ptr32 p_control_block) { Logger::getInstance().debug("ASVT hex dump:"); Logger::getInstance().hexDump(reinterpret_cast(p_asvt), sizeof(asvt_t)); - for (const auto& [include, include_includes] : include_map_) { + for (const auto& [include, cbxp_options] : options_map_) { if (include == "ascb") { nlohmann::json ascbs_json; - CBXP::ASCB ascb(include_includes); + CBXP::ASCB ascb(cbxp_options.include_patterns, cbxp_options.filters); uint32_t* __ptr32 p_ascb_addr = const_cast( reinterpret_cast(&p_asvt->asvtenty)); for (int i = 0; i < p_asvt->asvtmaxu; i++) { @@ -62,7 +62,9 @@ nlohmann::json ASVT::get(void* __ptr32 p_control_block) { } nlohmann::json ascb_json = ascb.get(reinterpret_cast(*p_ascb_addr)); - ascbs_json.push_back(ascb_json); + if (!ascb_json.is_null()) { + ascbs_json.push_back(ascb_json); + } p_ascb_addr++; // This SHOULD increment the pointer by 4 bytes. } asvt_json["asvtenty"] = ascbs_json; @@ -85,6 +87,10 @@ nlohmann::json ASVT::get(void* __ptr32 p_control_block) { asvt_json["asvtmdsc"] = p_asvt->asvtmdsc; asvt_json["asvtfrst"] = formatter_.getHex(p_asvt->asvtfrst); - return asvt_json; + if (ASVT::matchFilter(asvt_json)) { + return asvt_json; + } else { + return {}; + } } } // namespace CBXP diff --git a/cbxp/control_blocks/asvt.hpp b/cbxp/control_blocks/asvt.hpp index a6e91c9..94eb933 100644 --- a/cbxp/control_blocks/asvt.hpp +++ b/cbxp/control_blocks/asvt.hpp @@ -33,8 +33,9 @@ namespace CBXP { class ASVT : public ControlBlock { public: nlohmann::json get(void* __ptr32 p_control_block = nullptr) override; - explicit ASVT(const std::vector& includes) - : ControlBlock("asvt", {"ascb"}, includes) {} + explicit ASVT(const std::vector& includes, + const std::vector& filters) + : ControlBlock("asvt", {"ascb"}, includes, filters) {} }; } // namespace CBXP diff --git a/cbxp/control_blocks/control_block.cpp b/cbxp/control_blocks/control_block.cpp index 02616ab..6ea2503 100644 --- a/cbxp/control_blocks/control_block.cpp +++ b/cbxp/control_blocks/control_block.cpp @@ -1,11 +1,149 @@ #include "control_block.hpp" +#include + #include #include "control_block_error.hpp" #include "logger.hpp" namespace CBXP { +void ControlBlock::createOptionsMap(const std::vector& includes, + const std::vector& filters) { + createIncludeMap(includes); + createFilterMap(filters); +} + +void ControlBlock::createFilterMap(const std::vector& filters) { + Logger::getInstance().debug("Creating filter map for the '" + + control_block_name_ + "' control block..."); + for (const std::string& filter : filters) { + // Only case; specific non-generic filter + const std::string del = "."; + size_t del_pos = filter.find(del); + if (del_pos != std::string::npos) { + // If there's a "." then separate filter into the control_block + // and its filter + std::string control_block_filter = filter.substr(del_pos + 1); + std::string control_block = filter.substr(0, del_pos); + + // Check to make sure we are including the specified control block + auto it = options_map_.find(control_block); + if (it == options_map_.end()) { + Logger::getInstance().debug("'" + control_block + + "' is not specified in the inclusion list"); + throw FilterError(); + } + options_map_[control_block].filters.push_back(control_block_filter); + } else { + ControlBlock::addCurrentFilter(filter); + } + } + Logger::getInstance().debug("Done"); +} + +void ControlBlock::addCurrentFilter(const std::string& filter) { + std::string filter_key, filter_value; + std::vector delimeters = {"<=", ">=", "<", ">", "="}; + for (std::string del : delimeters) { + size_t del_pos = filter.find(del); + if (del_pos != std::string::npos) { + // If there's a delimeter then separate include into the key and its value + filter_value = filter.substr(del_pos + del.length()); + filter_key = filter.substr(0, del_pos); + if (filter_value == "") { + Logger::getInstance().debug("cannot specify null filter value"); + throw FilterError(); + } + cbxp_filter_t filter_data = {del, filter_value}; + current_filters_[filter_key].push_back(filter_data); + return; + } + } +} + +bool ControlBlock::compare(const nlohmann::json& json_value, + const std::string& filter_value, + const std::string& operation) { + std::string value_str = ""; + try { + uint64_t value_uint; + uint64_t filter_uint; + try { + value_uint = json_value.get(); + filter_uint = std::stoul(filter_value, nullptr, 0); + } catch (...) { + value_str = json_value.get(); + if (value_str.substr(0, 2) == "0x") { + value_uint = std::stoul(value_str, nullptr, 0); + filter_uint = std::stoul(filter_value, nullptr, 0); + } + } + if (value_str != "") { + // Filter is testing strings + if (operation == "=") { + size_t last_non_space = value_str.find_last_not_of(" \t\n\r\f\v"); + value_str.resize(last_non_space + 1); + return (fnmatch(filter_value.c_str(), value_str.c_str(), 0) == 0); + } else { + Logger::getInstance().debug( + "Cannot use <,<=,> or >= with string filter"); + throw FilterError(); + } + } // Filter is testing non-strings + else if (operation == "=") { + return value_uint == filter_uint; + } else if (operation == ">") { + return value_uint > filter_uint; + } else if (operation == "<") { + return value_uint < filter_uint; + } else if (operation == ">=") { + return value_uint >= filter_uint; + } else if (operation == "<=") { + return value_uint <= filter_uint; + } + } catch (...) { + Logger::getInstance().debug( + "Error with type conversions for filter evaluation"); + throw FilterError(); + } + // We should never get here, so it would be good to say "no match" just in + // case + return false; +} + +bool ControlBlock::matchFilter(nlohmann::json& control_block_json) { + for (const auto& [json_key, json_value] : control_block_json.items()) { + if (json_value.is_null()) { + // If any element in our json structure is null, we already failed a + // filter match + return false; + } + } + if (current_filters_.empty()) { + // If the filter map is empty then we want to return the control block + return true; + } + for (const auto& [filter_key, filter_values] : current_filters_) { + if (control_block_json.contains(filter_key)) { + // cppcheck-suppress useStlAlgorithm + for (const cbxp_filter_t& filter_data : filter_values) { + // would require capturing structured bindings to use all_of or none_of + if (!ControlBlock::compare(control_block_json[filter_key], + filter_data.value, filter_data.operation)) { + return false; + } + } + } else { + Logger::getInstance().debug( + "Specified filter key not found in control block json"); + throw FilterError(); + } + } + // If we didn't have a reason to return false, we return true + return true; +} + void ControlBlock::createIncludeMap(const std::vector& includes) { Logger::getInstance().debug("Creating include map for the '" + control_block_name_ + "' control block..."); @@ -24,26 +162,26 @@ void ControlBlock::createIncludeMap(const std::vector& includes) { void ControlBlock::processDoubleAsteriskInclude() { // Any existing entries in the hash map are redundant, so clear them - include_map_.clear(); + options_map_.clear(); for (const std::string& includable : includables_) { // Build a map of all includables_ but with "**" at the next level - include_map_[includable] = {"**"}; + options_map_[includable].include_patterns = {"**"}; } } void ControlBlock::processAsteriskInclude() { - if (include_map_.empty()) { + if (options_map_.empty()) { for (const std::string& includable : includables_) { // Build a map of all includables_ - include_map_[includable] = {}; + options_map_[includable].include_patterns = {}; } } for (const std::string& includable : includables_) { - if (include_map_.find(includable) != include_map_.end()) { + if (options_map_.find(includable) != options_map_.end()) { continue; } // Add all includables_ not already present to the map - include_map_[includable] = {}; + options_map_[includable].include_patterns = {}; } } @@ -65,25 +203,27 @@ void ControlBlock::processExplicitInclude(std::string& include) { control_block_name_ + "' control block"); throw IncludeError(); } - if (include_map_.find(include) == include_map_.end()) { + if (options_map_.find(include) == options_map_.end()) { // If we don't already have this include in our map, add it with its // includes if (include_includes == "") { - include_map_[include] = {}; + options_map_[include].include_patterns = {}; } else { - include_map_[include] = {include_includes}; + options_map_[include].include_patterns = {include_includes}; } } else { // If we DO already have this in our map, then we should add its // includes if they are useful or new - if (std::find(include_map_[include].begin(), include_map_[include].end(), - include_includes) != include_map_[include].end()) { + if (std::find(options_map_[include].include_patterns.begin(), + options_map_[include].include_patterns.end(), + include_includes) != + options_map_[include].include_patterns.end()) { return; } if (include_includes == "") { return; } - include_map_[include].push_back(include_includes); + options_map_[include].include_patterns.push_back(include_includes); } } } // namespace CBXP diff --git a/cbxp/control_blocks/control_block.hpp b/cbxp/control_blocks/control_block.hpp index dcbcb3b..9525de9 100644 --- a/cbxp/control_blocks/control_block.hpp +++ b/cbxp/control_blocks/control_block.hpp @@ -7,6 +7,16 @@ namespace CBXP { +typedef struct { + std::string operation; + std::string value; +} cbxp_filter_t; + +typedef struct { + std::vector include_patterns; + std::vector filters; +} cbxp_options_t; + class ControlBlock { private: const std::string control_block_name_; @@ -14,19 +24,28 @@ class ControlBlock { void processDoubleAsteriskInclude(); void processAsteriskInclude(); void processExplicitInclude(std::string& include); + void addCurrentFilter(const std::string& filter); + void createIncludeMap(const std::vector& includes); + void createFilterMap(const std::vector& filters); + bool compare(const nlohmann::json& json_value, + const std::string& filter_value, const std::string& operation); protected: ControlBlockFieldFormatter formatter_; - std::unordered_map> include_map_; + std::unordered_map options_map_; + std::unordered_map> current_filters_; + bool matchFilter(nlohmann::json& control_block_json); + void createOptionsMap(const std::vector& includes, + const std::vector& filters); public: - void createIncludeMap(const std::vector& includes); virtual nlohmann::json get(void* __ptr32 p_control_block = nullptr) = 0; explicit ControlBlock(const std::string& name, const std::vector& includables, - const std::vector& includes) + const std::vector& includes, + const std::vector& filters) : control_block_name_(name), includables_(includables) { - createIncludeMap(includes); + createOptionsMap(includes, filters); } virtual ~ControlBlock() = default; }; diff --git a/cbxp/control_blocks/cvt.cpp b/cbxp/control_blocks/cvt.cpp index 12107ad..792ed95 100644 --- a/cbxp/control_blocks/cvt.cpp +++ b/cbxp/control_blocks/cvt.cpp @@ -43,11 +43,15 @@ nlohmann::json CVT::get(void* __ptr32 p_control_block) { cvt_json["cvtasvt"] = formatter_.getHex(&p_cvtmap->cvtasvt); cvt_json["cvtecvt"] = formatter_.getHex(&p_cvtmap->cvtecvt); - for (const auto& [include, include_includes] : include_map_) { + for (const auto& [include, cbxp_options] : options_map_) { if (include == "asvt") { - cvt_json["cvtasvt"] = CBXP::ASVT(include_includes).get(p_cvtmap->cvtasvt); + cvt_json["cvtasvt"] = + CBXP::ASVT(cbxp_options.include_patterns, cbxp_options.filters) + .get(p_cvtmap->cvtasvt); } else if (include == "ecvt") { - cvt_json["cvtecvt"] = CBXP::ECVT(include_includes).get(p_cvtmap->cvtecvt); + cvt_json["cvtecvt"] = + CBXP::ECVT(cbxp_options.include_patterns, cbxp_options.filters) + .get(p_cvtmap->cvtecvt); } } @@ -139,6 +143,10 @@ nlohmann::json CVT::get(void* __ptr32 p_control_block) { cvt_json["cvt0pt03"] = formatter_.getHex(p_cvtmap->cvt0pt03); cvt_json["cvt0scr1"] = formatter_.getHex(p_cvtmap->cvt0scr1); - return cvt_json; + if (CVT::matchFilter(cvt_json)) { + return cvt_json; + } else { + return {}; + } } } // namespace CBXP diff --git a/cbxp/control_blocks/cvt.hpp b/cbxp/control_blocks/cvt.hpp index 3111cda..209bf76 100644 --- a/cbxp/control_blocks/cvt.hpp +++ b/cbxp/control_blocks/cvt.hpp @@ -8,8 +8,9 @@ namespace CBXP { class CVT : public ControlBlock { public: nlohmann::json get(void* __ptr32 p_control_block = nullptr) override; - explicit CVT(const std::vector& includes) - : ControlBlock("cvt", {"ecvt", "asvt"}, includes) {} + explicit CVT(const std::vector& includes, + const std::vector& filters) + : ControlBlock("cvt", {"ecvt", "asvt"}, includes, filters) {} }; } // namespace CBXP diff --git a/cbxp/control_blocks/ecvt.cpp b/cbxp/control_blocks/ecvt.cpp index e577624..0142304 100644 --- a/cbxp/control_blocks/ecvt.cpp +++ b/cbxp/control_blocks/ecvt.cpp @@ -176,6 +176,10 @@ nlohmann::json ECVT::get(void* __ptr32 p_control_block) { reinterpret_cast(&p_ecvt->ecvtvser)); ecvt_json["ecvtxtsw"] = formatter_.getHex(p_ecvt->ecvtxtsw); - return ecvt_json; + if (ECVT::matchFilter(ecvt_json)) { + return ecvt_json; + } else { + return {}; + } } } // namespace CBXP diff --git a/cbxp/control_blocks/ecvt.hpp b/cbxp/control_blocks/ecvt.hpp index 9cece25..27cb502 100644 --- a/cbxp/control_blocks/ecvt.hpp +++ b/cbxp/control_blocks/ecvt.hpp @@ -8,8 +8,9 @@ namespace CBXP { class ECVT : public ControlBlock { public: nlohmann::json get(void* __ptr32 p_control_block = nullptr) override; - explicit ECVT(const std::vector& includes) - : ControlBlock("ecvt", {}, includes) {} + explicit ECVT(const std::vector& includes, + const std::vector& filters) + : ControlBlock("ecvt", {}, includes, filters) {} }; } // namespace CBXP #endif diff --git a/cbxp/control_blocks/psa.cpp b/cbxp/control_blocks/psa.cpp index 8feec8c..7d281d2 100644 --- a/cbxp/control_blocks/psa.cpp +++ b/cbxp/control_blocks/psa.cpp @@ -28,9 +28,11 @@ nlohmann::json PSA::get(void* __ptr32 p_control_block) { psa_json["flccvt"] = formatter_.getHex(&p_psa->flccvt); - for (const auto& [include, include_includes] : include_map_) { + for (const auto& [include, cbxp_options] : options_map_) { if (include == "cvt") { - psa_json["flccvt"] = CBXP::CVT(include_includes).get(p_psa->flccvt); + psa_json["flccvt"] = + CBXP::CVT(cbxp_options.include_patterns, cbxp_options.filters) + .get(p_psa->flccvt); } } @@ -61,6 +63,10 @@ nlohmann::json PSA::get(void* __ptr32 p_control_block) { psa_json["psaval"] = formatter_.getBitmap(p_psa->psaval); psa_json["psaxcvt"] = formatter_.getHex(p_psa->psaxcvt); - return psa_json; + if (PSA::matchFilter(psa_json)) { + return psa_json; + } else { + return {}; + } } } // namespace CBXP diff --git a/cbxp/control_blocks/psa.hpp b/cbxp/control_blocks/psa.hpp index 7843b38..d80870b 100644 --- a/cbxp/control_blocks/psa.hpp +++ b/cbxp/control_blocks/psa.hpp @@ -8,8 +8,9 @@ namespace CBXP { class PSA : public ControlBlock { public: nlohmann::json get(void* __ptr32 p_control_block = nullptr) override; - explicit PSA(const std::vector& includes) - : ControlBlock("psa", {"cvt"}, includes) {} + explicit PSA(const std::vector& includes, + const std::vector& filters) + : ControlBlock("psa", {"cvt"}, includes, filters) {} }; } // namespace CBXP #endif diff --git a/cbxp/main.cpp b/cbxp/main.cpp index 19c6ea5..5fc2497 100644 --- a/cbxp/main.cpp +++ b/cbxp/main.cpp @@ -12,7 +12,8 @@ #include "control_block_error.hpp" typedef const cbxp_result_t* (*cbxp_t)(const char* control_block_name, - const char* includes_string, bool debug); + const char* includes_string, + const char* filters_string, bool debug); static void show_usage(const char* argv[]); static void show_dll_errors(); @@ -28,6 +29,9 @@ static void show_usage(const char* argv[]) { << " -i, --include Include additional control " "blocks based on a pattern" << std::endl + << " -f, --filter Filter repeated control " + "blocks using a key-value pair filter" + << std::endl << " -v, --version Show version number" << std::endl << " -h, --help Show usage information" @@ -51,6 +55,11 @@ static void cleanup_and_exit(int exit_rc, void* lib_handle) { exit(exit_rc); } +bool check_for_comma(const std::string& string) { + return std::any_of(string.begin(), string.end(), + [](char c) { return c == ','; }); +} + int main(int argc, const char* argv[]) { // Load 'libcbxp.so' void* lib_handle = dlopen("libcbxp.so", RTLD_NOW); @@ -67,7 +76,8 @@ int main(int argc, const char* argv[]) { } bool debug = false; - std::string control_block_name = "", includes_string = ""; + std::string control_block_name = "", includes_string = "", + filters_string = ""; if (argc < 2) { show_usage(argv); @@ -91,16 +101,19 @@ int main(int argc, const char* argv[]) { for (int i = 1; i < argc; i++) { std::string flag = argv[i]; if (flag == "-d" || flag == "--debug") { - debug = true; + if (!debug) { + debug = true; + } else { + show_usage(argv); + cleanup_and_exit(-1, lib_handle); + } } else if (flag == "-i" || flag == "--include") { if (i + 1 >= argc - 1) { show_usage(argv); cleanup_and_exit(-1, lib_handle); } std::string include = std::string(argv[++i]); - bool has_comma = std::any_of(include.begin(), include.end(), - [](char c) { return c == ','; }); - if (has_comma) { + if (check_for_comma(include)) { std::cerr << "Include patterns cannot contain commas" << std::endl; cleanup_and_exit(-1, lib_handle); } @@ -109,6 +122,21 @@ int main(int argc, const char* argv[]) { } else { includes_string += "," + include; } + } else if (flag == "-f" || flag == "--filter") { + if (i + 1 >= argc - 1) { + show_usage(argv); + cleanup_and_exit(-1, lib_handle); + } + std::string filter = std::string(argv[++i]); + if (check_for_comma(filter)) { + std::cerr << "Filters cannot contain commas" << std::endl; + cleanup_and_exit(-1, lib_handle); + } + if (filters_string == "") { + filters_string = filter; + } else { + filters_string += "," + filter; + } } else { if (i != argc - 1) { show_usage(argv); @@ -126,7 +154,8 @@ int main(int argc, const char* argv[]) { nlohmann::json control_block_json; const cbxp_result_t* cbxp_result = - cbxp(control_block_name.c_str(), includes_string.c_str(), debug); + cbxp(control_block_name.c_str(), includes_string.c_str(), + filters_string.c_str(), debug); if (cbxp_result->return_code == CBXP::Error::BadControlBlock) { std::cerr << "Unknown control block '" << control_block_name @@ -135,6 +164,9 @@ int main(int argc, const char* argv[]) { } else if (cbxp_result->return_code == CBXP::Error::BadInclude) { std::cerr << "A bad include pattern was provided" << std::endl; cleanup_and_exit(-1, lib_handle); + } else if (cbxp_result->return_code == CBXP::Error::BadFilter) { + std::cerr << "A bad filter was provided" << std::endl; + cleanup_and_exit(-1, lib_handle); } else { std::cout << cbxp_result->result_json << std::endl; } diff --git a/cbxp/python/_cbxp.c b/cbxp/python/_cbxp.c index 9b167a0..43847fb 100644 --- a/cbxp/python/_cbxp.c +++ b/cbxp/python/_cbxp.c @@ -16,13 +16,16 @@ static PyObject* call_cbxp(PyObject* self, PyObject* args, PyObject* kwargs) { PyObject* debug_pyobj; const char* control_block; const char* includes_string; + const char* filters_string; Py_ssize_t request_length; bool debug = false; - static char* kwlist[] = {"request", "include", "debug", NULL}; + static char* kwlist[] = {"request", "include", "filters_string", "debug", + NULL}; - if (!PyArg_ParseTupleAndKeywords(args, kwargs, "s|sO", kwlist, &control_block, - &includes_string, &debug_pyobj)) { + if (!PyArg_ParseTupleAndKeywords(args, kwargs, "s|ssO", kwlist, + &control_block, &includes_string, + &filters_string, &debug_pyobj)) { return NULL; } @@ -34,9 +37,10 @@ static PyObject* call_cbxp(PyObject* self, PyObject* args, PyObject* kwargs) { // but we will set this up anyways to be safe. pthread_mutex_lock(&cbxp_mutex); - const cbxp_result_t* result = cbxp(control_block, includes_string, debug); + const cbxp_result_t* result = + cbxp(control_block, includes_string, filters_string, debug); - result_dictionary = Py_BuildValue( + result_dictionary = Py_BuildValue( "{s:s#, s:i}", "result_json", result->result_json, result->result_json_length, "return_code", result->return_code); diff --git a/python/cbxp/_C.pyi b/python/cbxp/_C.pyi index 7ab645e..1d518bd 100644 --- a/python/cbxp/_C.pyi +++ b/python/cbxp/_C.pyi @@ -1,5 +1,6 @@ def call_cbxp( # noqa: N999 control_block: str, includes_string: str, + filters_string: str, debug: bool = False, ) -> dict: ... diff --git a/python/cbxp/cbxp.py b/python/cbxp/cbxp.py index 49e35e7..87ce2c5 100644 --- a/python/cbxp/cbxp.py +++ b/python/cbxp/cbxp.py @@ -8,8 +8,10 @@ class CBXPErrorCode(Enum): """An enum of error and return codes from the cbxp interface""" COMMA_IN_INCLUDE = -1 + COMMA_IN_FILTER = -2 BAD_CONTROL_BLOCK = 1 BAD_INCLUDE = 2 + BAD_CONTROL_BLOCK_FILTER = 3 class CBXPError(Exception): @@ -20,10 +22,14 @@ def __init__(self, return_code: int, control_block_name: str): match self.rc: case CBXPErrorCode.COMMA_IN_INCLUDE.value: message = "Include patterns cannot contain commas" + case CBXPErrorCode.COMMA_IN_FILTER.value: + message = "Filters cannot contain commas" case CBXPErrorCode.BAD_CONTROL_BLOCK.value: message = f"Unknown control block '{control_block_name}' was specified." case CBXPErrorCode.BAD_INCLUDE.value: message = "A bad include pattern was provided" + case CBXPErrorCode.BAD_CONTROL_BLOCK_FILTER.value: + message = "A bad filter was provided" case _: message = "an unknown error occurred" super().__init__(message) @@ -32,14 +38,31 @@ def __init__(self, return_code: int, control_block_name: str): def cbxp( control_block: str, includes: list[str] = None, + control_block_filters: list[str] = None, debug: bool = False, ) -> dict: + # Includes processing if includes is None: includes = [] for include in includes: if "," in include: raise CBXPError(CBXPErrorCode.COMMA_IN_INCLUDE.value, control_block) - response = call_cbxp(control_block.lower(), ",".join(includes).lower(), debug=debug) + + # Filter Processing + if control_block_filters is None: + control_block_filters = [] + for control_block_filter in control_block_filters: + if "," in control_block_filter: + raise CBXPError(CBXPErrorCode.COMMA_IN_FILTER.value, control_block) + + response = call_cbxp( + control_block.lower(), + ",".join(includes), + ",".join(control_block_filters), + debug=debug, + ) if response["return_code"]: raise CBXPError(response["return_code"], control_block) + if response["result_json"] == "null" and control_block_filters != []: + return None return json.loads(response["result_json"]) diff --git a/tests/test.py b/tests/test.py index 7d3351a..59fa6b3 100644 --- a/tests/test.py +++ b/tests/test.py @@ -169,6 +169,127 @@ def test_cbxp_can_extract_cvt_and_include_wildcard_and_asvt_recursive_wildcard( self.assertIs(type(entry), dict) self.assertIs(type(entry["ascbassb"]), dict) + # ============================================================================ + # Filters + # ============================================================================ + def test_cbxp_can_use_basic_filter(self): + cbdata = cbxp("psa", control_block_filter=["psapsa=PSA"]) + self.assertIs(type(cbdata), dict) + + def test_cbxp_can_use_include_filter_with_wildcard_include(self): + cbdata = cbxp( + "psa", + control_block_filter=["cvt.asvt.ascb.assb.assbjbns=*MASTER*"], + includes=["**"], + ) + self.assertIs(type(cbdata), dict) + + def test_cbxp_can_use_include_filter_with_explicit_include(self): + cbdata = cbxp( + "psa", + control_block_filter=["cvt.asvt.ascb.assb.assbjbns=*MASTER*"], + includes=["cvt.asvt.ascb.assb"], + ) + self.assertIs(type(cbdata), dict) + + def test_cbxp_can_use_multiple_include_filters(self): + cbdata = cbxp( + "psa", + control_block_filter=[ + "cvt.asvt.ascb.assb.assbjbns=*master*,cvt.asvt.ascb.ascbasid>0", + ], + includes=["cvt.asvt.ascb.assb"], + ) + self.assertIs(type(cbdata), dict) + + def test_cbxp_can_use_wildcard_filter_with_string(self): + cbdata = cbxp( + "psa", + control_block_filter=["cvt.asvt.ascb.assb.assbjbns=?mas?er?"], + includes=["cvt.asvt.ascb.assb"], + ) + self.assertIs(type(cbdata), dict) + + def test_cbxp_can_use_int_filter_equals(self): + cbdata = cbxp( + "psa", + control_block_filter=["cvt.asvt.ascb.ascbasid=1"], + includes=["cvt.asvt.ascb.assb"], + ) + self.assertIs(type(cbdata), dict) + + def test_cbxp_can_use_int_filter_greater_than(self): + cbdata = cbxp( + "psa", + control_block_filter=["cvt.asvt.ascb.ascbasid>0"], + includes=["cvt.asvt.ascb.assb"], + ) + self.assertIs(type(cbdata), dict) + + def test_cbxp_can_use_int_filter_less_than(self): + cbdata = cbxp( + "psa", + control_block_filter=["cvt.asvt.ascb.ascbasid<2"], + includes=["cvt.asvt.ascb.assb"], + ) + self.assertIs(type(cbdata), dict) + + def test_cbxp_can_use_int_filter_greater_than_or_equals(self): + cbdata = cbxp( + "psa", + control_block_filter=["cvt.asvt.ascb.ascbasid<=2"], + includes=["cvt.asvt.ascb.assb"], + ) + self.assertIs(type(cbdata), dict) + + def test_cbxp_can_use_int_filter_less_than_or_equals(self): + cbdata = cbxp( + "psa", + control_block_filter=["cvt.asvt.ascb.ascbasid>=1"], + includes=["cvt.asvt.ascb.assb"], + ) + self.assertIs(type(cbdata), dict) + + def test_cbxp_can_decimal_filter_for_hex_field_with_equals(self): + cbdata = cbxp( + "cvt", + control_block_filter=["cvtasmvt=2281701376"], + ) + self.assertIs(type(cbdata), dict) + + def test_cbxp_can_use_hex_filter_with_greater_than(self): + cbdata = cbxp( + "cvt", + control_block_filter=["cvtasmvt>0x87FFFFFF"], + ) + self.assertIs(type(cbdata), dict) + + def test_cbxp_can_use_hex_filter_with_less_than(self): + cbdata = cbxp( + "cvt", + control_block_filter=["cvtasmvt<0x88000001"], + ) + self.assertIs(type(cbdata), dict) + + def test_cbxp_can_use_hex_filter_with_greater_than_or_equals(self): + cbdata = cbxp( + "cvt", + control_block_filter=["cvtasmvt>=0x87FFFFFF"], + ) + self.assertIs(type(cbdata), dict) + + def test_cbxp_can_use_hex_filter_with_less_than_or_equals(self): + cbdata = cbxp( + "cvt", + control_block_filter=["cvtasmvt<=0x88000000"], + ) + self.assertIs(type(cbdata), dict) + + def test_cbxp_returns_none_if_no_filter_match( + self, + ): + self.assertIsNone(cbxp("psa", control_block_filter=["psapsa=PSB"])) + # ============================================================================ # Debug Mode # ============================================================================ @@ -236,6 +357,67 @@ def test_cbxp_raises_cbxp_error_when_pattern_cannot_contain_comma_2(self): cbxp("cvt", includes=["asvt,as"]) self.assertEqual("Include patterns cannot contain commas", str(e.exception)) + # ============================================================================ + # Errors: Bad Filters + # ============================================================================ + def test_cbxp_raises_cbxp_error_if_filter_uses_non_included_control_block( + self, + ): + with self.assertRaises(CBXPError) as e: + cbxp( + "psa", + control_block_filter=["cvt.asvt.ascb.assb.assbjbns=*MASTER*"], + ) + self.assertEqual("A bad filter was provided", str(e.exception)) + + def test_cbxp_raises_cbxp_error_if_one_of_two_filters_fails( + self, + ): + with self.assertRaises(CBXPError) as e: + cbxp( + "psa", + includes=["**"], + control_block_filter=[ + "cvt.asvt.ascb.assb.assbjbns=*master*,cvt.asvt.ascb.ascbasid=2", + ], + ) + self.assertEqual( + "No control block was found that matched the provided filter", + str(e.exception), + ) + + def test_cbxp_raises_cbxp_error_if_non_equality_filter_used_with_string( + self, + ): + with self.assertRaises(CBXPError) as e: + cbxp( + "psa", + includes=["**"], + control_block_filter=["cvt.asvt.ascb.assb.assbjbns<*master*"], + ) + self.assertEqual("A bad filter was provided", str(e.exception)) + + def test_cbxp_raises_cbxp_error_if_filter_uses_unknown_key( + self, + ): + with self.assertRaises(CBXPError) as e: + cbxp("psa", control_block_filter=["psapsb=PSA"]) + self.assertEqual("A bad filter was provided", str(e.exception)) + + def test_cbxp_raises_cbxp_error_if_filter_passes_null_value( + self, + ): + with self.assertRaises(CBXPError) as e: + cbxp("psa", control_block_filter=["psapsa="]) + self.assertEqual("A bad filter was provided", str(e.exception)) + + def test_cbxp_raises_cbxp_error_if_filter_has_comma( + self, + ): + with self.assertRaises(CBXPError) as e: + cbxp("psa", control_block_filter=["psapsa=PSA,cvt.cvtasmvt<88000001"]) + self.assertEqual("Filters cannot contain commas", str(e.exception)) + if __name__ == "__main__": unittest.main(verbosity=2, failfast=True, buffer=True) diff --git a/tests/test.sh b/tests/test.sh index 1706df0..5a1734a 100755 --- a/tests/test.sh +++ b/tests/test.sh @@ -44,6 +44,24 @@ run_with_expected_exit_code 0 ./dist/cbxp -i "asvt.*" -i "*" cvt run_with_expected_exit_code 0 ./dist/cbxp -i assb ascb run_with_expected_exit_code 0 ./dist/cbxp -i cvt.ecvt -i cvt.asvt.ascb.assb psa +# Filters +run_with_expected_exit_code 0 ./dist/cbxp -f psapsa=psa psa +run_with_expected_exit_code 0 ./dist/cbxp -f psapsa=junk psa +run_with_expected_exit_code 0 ./dist/cbxp -f "cvt.asvt.ascb.assb.assbjbns=*master*" -i "**" psa +run_with_expected_exit_code 0 ./dist/cbxp -f "cvt.asvt.ascb.assb.assbjbns=*master*" -i cvt.asvt.ascb.assb psa +run_with_expected_exit_code 0 ./dist/cbxp -f "cvt.asvt.ascb.assb.assbjbns=?mas?er?" -i cvt.asvt.ascb.assb psa +run_with_expected_exit_code 0 ./dist/cbxp -f "cvt.asvt.ascb.assb.assbjbns=*master*" -f "cvt.asvt.ascb.ascbasid>0" -i "**" psa +run_with_expected_exit_code 0 ./dist/cbxp -f "cvt.asvt.ascb.ascbasid=1" -i "**" psa +run_with_expected_exit_code 0 ./dist/cbxp -f "cvt.asvt.ascb.ascbasid>0" -i "**" psa +run_with_expected_exit_code 0 ./dist/cbxp -f "cvt.asvt.ascb.ascbasid>=1" -i "**" psa +run_with_expected_exit_code 0 ./dist/cbxp -f "cvt.asvt.ascb.ascbasid<2" -i "**" psa +run_with_expected_exit_code 0 ./dist/cbxp -f "cvt.asvt.ascb.ascbasid<=2" -i "**" psa +run_with_expected_exit_code 0 ./dist/cbxp -f "cvtasmvt<0x88000001" cvt +run_with_expected_exit_code 0 ./dist/cbxp -f "cvtasmvt=2281701376" cvt +run_with_expected_exit_code 0 ./dist/cbxp -f "cvtasmvt>0x87FFFFFF" cvt +run_with_expected_exit_code 0 ./dist/cbxp -f "cvtasmvt<=0x88000000" cvt +run_with_expected_exit_code 0 ./dist/cbxp -f "cvtasmvt>=0x87FFFFFF" cvt + # Debug Mode run_with_expected_exit_code 0 ./dist/cbxp -d psa run_with_expected_exit_code 0 ./dist/cbxp --debug psa @@ -59,6 +77,10 @@ run_with_expected_exit_code 255 ./dist/cbxp run_with_expected_exit_code 255 ./dist/cbxp -x "unknown flag" cvt run_with_expected_exit_code 255 ./dist/cbxp -i cvt run_with_expected_exit_code 255 ./dist/cbxp -i -i cvt psa +run_with_expected_exit_code 255 ./dist/cbxp -d -d psa +run_with_expected_exit_code 255 ./dist/cbxp -f psa +run_with_expected_exit_code 255 ./dist/cbxp -f psapsa=psa +run_with_expected_exit_code 255 ./dist/cbxp --debug -d psa # Errors: Unknown Control Block run_with_expected_exit_code 255 ./dist/cbxp unknown # Errors: Bad Include Patterns @@ -70,6 +92,15 @@ run_with_expected_exit_code 255 ./dist/cbxp -i ecvt ascb run_with_expected_exit_code 255 ./dist/cbxp -i cvt.ecvt -i cvt.ascb psa run_with_expected_exit_code 255 ./dist/cbxp -i cvt.asvt.ascb -i ecvt psa run_with_expected_exit_code 255 ./dist/cbxp -i cvt cvt +# Errors: Bad Filters +run_with_expected_exit_code 255 ./dist/cbxp -f psapsa= psa +run_with_expected_exit_code 255 ./dist/cbxp -f junk=fakeval cvt +run_with_expected_exit_code 255 ./dist/cbxp -f psapsa=psa,cvt.asvt.ascb.ascbasid<2 cvt +run_with_expected_exit_code 255 ./dist/cbxp -i asvt -f junk.jsonkey=fakeval cvt +run_with_expected_exit_code 255 ./dist/cbxp -f cvt.asvt.ascb.assb.assbjbni=junkjob -i cvt.asvt.ascb.assb psa +run_with_expected_exit_code 255 ./dist/cbxp -f "cvt.asvt.ascb.assb.assbjbns=*master*,cvt.asvt.ascb.ascbasid=2" -i "**" psa +run_with_expected_exit_code 255 ./dist/cbxp -f "cvt.asvt.ascb.assb.assbjbns<*master*" -i "**" psa + echo " -------------------------------- " echo " -------------------------------- "