From 79cb3f33ecd9baee5f90b8195b72baff0ad143e8 Mon Sep 17 00:00:00 2001 From: Leonard Carcaramo Date: Thu, 6 Nov 2025 07:34:40 -0500 Subject: [PATCH 01/13] Initial Alpha (#19) * Feature/includeparameter (#14) * Add skeleton for inclusion list parameter Extend interface/skeleton for include_list * Input POinter Attempt to add input pointer parameter for control blocks * Definitions outside of if statements * Attempt to map the intended inclusion list * Try Virtual Function to keep Control Block in Explorer * No longer have get return * Attempt to add the meat of the function -Add methods and members to control blocks to get name of control block, specific control blocks within it, and all control blocks within it -add functions in control block explorer to parse inclusion "map" into actionable data name fix * Update psa.cpp Update psa.cpp * Update cvt.cpp * Update asvt.cpp * algorithm cleanup * better cleanup * Update control_block.cpp * Update main.cpp * Finish addressing merge issues and commit hooks -Minor code updates that were lost in merge commit -Bring new code up to standard for cppcheck -Format new code with clang-format * Update _cbxp.c * Streamline Control Block Explorer Class Update control_block_explorer.hpp * Massive refactor -Shave 2 step process down to one -Change serialized json inclusion map to use a vector of strings still split by "dot" operators * Error Handling Logic * BIG UPDATE PR COMMENTS -Switch pre-processing to one hash map function -use try/catch with custom errors rather than passing return codes everywhere -style and name changes -Enforce more rigid parm structure on entry -Fix some behavioral bugs and oversights in inclusion preprocessing -General streamlining and refactoring of functions, methods, classes, etc. * Another Big Refactor -PR comments (mostly style, but streamlining of error code as well) -Reworked base and derived classes to allow for includables to be defined to the base class and include_map to be defined to the base and derived classes * Update ascb.cpp * Update control_block.hpp * . * .. * ... * PR Comments -ASCB pointer deref in ASVT -Minor name changes -Remove double wildcard error -Add control_block_name_ private member and add initialization to constructor -move include_map_ to protected and remove private using statement * Update asvt.cpp * Update asvt.cpp * PR Comments Mostly renaming things streamlining some unnecessary text, parms and strings * Update control_block.cpp * Update main.cpp * Last round of PR comments string compare with == remove vestiges of old mechanisms for control block management name changes minor tweaks * Update cvt.cpp * Update cvt.cpp * Update cvt.cpp * Final comments Update control_block_explorer.cpp * comments * Last Comments * include changes * Last round of comments * debug * Unit testing (#17) * initial commit 1 * cleaned code before include test cases * wrote test cases, need to check with team now * added space after every function * added .value * shell script done * made changes proposed by leonard 1 * PR changes requested by team * added tests to check for ascb and asvt entries whether it be a string or dict * added tests to check for ascb and asvt entries whether it be a string or dict one more place * made minor tweaks * added updates provided by leonard * grouped failure test cases together * grouped error test cases together * removed extra lines * style changes * Feat/oss housekeeping2 (#18) * Set explicit C/C++ standard and cleanup README. Signed-off-by: Leonard Carcaramo * Update contribution guidelines and functional tests. Signed-off-by: Leonard Carcaramo * Cleanup contribution guidelines and debug debug mode. Signed-off-by: Leonard Carcaramo * Cleanup. Signed-off-by: Leonard Carcaramo * Cleanup. Signed-off-by: Leonard Carcaramo * Fix sdist packaging and pyproject.toml metadata. Signed-off-by: Leonard Carcaramo --------- Signed-off-by: Leonard Carcaramo * Fix _C.pyi and removed unused import from cbxp.py. Signed-off-by: Leonard Carcaramo --------- Signed-off-by: Leonard Carcaramo Co-authored-by: Elijah Swift Co-authored-by: Varun Chennamadhava --- .vscode/settings.json | 115 ++++++++-------- CMakeLists.txt | 9 ++ CONTRIBUTING.md | 77 ++++++----- MANIFEST.in | 1 + README.md | 43 ++---- cbxp/cbxp.cpp | 5 +- cbxp/cbxp.h | 3 +- cbxp/control_block_error.hpp | 27 ++++ cbxp/control_block_explorer.cpp | 86 ++++++++---- cbxp/control_block_explorer.hpp | 7 +- cbxp/control_blocks/ascb.cpp | 134 +++++++++--------- cbxp/control_blocks/ascb.hpp | 4 +- cbxp/control_blocks/asvt.cpp | 78 +++++++---- cbxp/control_blocks/asvt.hpp | 4 +- cbxp/control_blocks/control_block.cpp | 90 ++++++++++++ cbxp/control_blocks/control_block.hpp | 19 +++ cbxp/control_blocks/cvt.cpp | 65 +++++---- cbxp/control_blocks/cvt.hpp | 4 +- cbxp/control_blocks/ecvt.cpp | 28 ++-- cbxp/control_blocks/ecvt.hpp | 4 +- cbxp/control_blocks/psa.cpp | 27 ++-- cbxp/control_blocks/psa.hpp | 4 +- cbxp/main.cpp | 98 +++++++++---- cbxp/python/_cbxp.c | 9 +- pyproject.toml | 10 +- python/cbxp/_C.pyi | 2 +- python/cbxp/__init__.py | 1 + python/cbxp/cbxp.py | 32 +++-- setup.py | 5 + tests/test.py | 190 ++++++++++++++++++++++++++ tests/test.sh | 71 ++++++++++ 31 files changed, 908 insertions(+), 344 deletions(-) create mode 100644 cbxp/control_block_error.hpp create mode 100644 cbxp/control_blocks/control_block.cpp create mode 100644 tests/test.py create mode 100755 tests/test.sh diff --git a/.vscode/settings.json b/.vscode/settings.json index 68fdb84..5cd750a 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,61 +1,62 @@ { "files.associations": { - "*.py": "python", - "__bit_reference": "cpp", - "__hash_table": "cpp", - "__locale": "cpp", - "__node_handle": "cpp", - "__split_buffer": "cpp", - "__verbose_abort": "cpp", - "array": "cpp", - "bitset": "cpp", - "cctype": "cpp", - "charconv": "cpp", - "clocale": "cpp", - "cmath": "cpp", - "complex": "cpp", - "cstdarg": "cpp", - "cstdint": "cpp", - "cstdio": "cpp", - "cstdlib": "cpp", - "cstring": "cpp", - "ctime": "cpp", - "cwchar": "cpp", - "cwctype": "cpp", - "deque": "cpp", - "execution": "cpp", - "memory": "cpp", - "initializer_list": "cpp", - "iomanip": "cpp", - "ios": "cpp", - "iosfwd": "cpp", - "iostream": "cpp", - "istream": "cpp", - "limits": "cpp", - "locale": "cpp", - "mutex": "cpp", - "new": "cpp", - "optional": "cpp", - "print": "cpp", - "queue": "cpp", - "ratio": "cpp", - "sstream": "cpp", - "stack": "cpp", - "stdexcept": "cpp", - "streambuf": "cpp", - "string": "cpp", - "string_view": "cpp", - "typeinfo": "cpp", - "unordered_map": "cpp", - "variant": "cpp", - "vector": "cpp", - "algorithm": "cpp", - "__tree": "cpp", - "any": "cpp", - "forward_list": "cpp", - "map": "cpp", - "span": "cpp", - "valarray": "cpp", - "csignal": "cpp" + "*.py": "python", + "__bit_reference": "cpp", + "__hash_table": "cpp", + "__locale": "cpp", + "__node_handle": "cpp", + "__split_buffer": "cpp", + "__verbose_abort": "cpp", + "array": "cpp", + "bitset": "cpp", + "cctype": "cpp", + "charconv": "cpp", + "clocale": "cpp", + "cmath": "cpp", + "complex": "cpp", + "cstdarg": "cpp", + "cstdint": "cpp", + "cstdio": "cpp", + "cstdlib": "cpp", + "cstring": "cpp", + "ctime": "cpp", + "cwchar": "cpp", + "cwctype": "cpp", + "deque": "cpp", + "execution": "cpp", + "memory": "cpp", + "initializer_list": "cpp", + "iomanip": "cpp", + "ios": "cpp", + "iosfwd": "cpp", + "iostream": "cpp", + "istream": "cpp", + "limits": "cpp", + "locale": "cpp", + "mutex": "cpp", + "new": "cpp", + "optional": "cpp", + "print": "cpp", + "queue": "cpp", + "ratio": "cpp", + "sstream": "cpp", + "stack": "cpp", + "stdexcept": "cpp", + "streambuf": "cpp", + "string": "cpp", + "string_view": "cpp", + "typeinfo": "cpp", + "unordered_map": "cpp", + "variant": "cpp", + "vector": "cpp", + "algorithm": "cpp", + "__tree": "cpp", + "any": "cpp", + "forward_list": "cpp", + "map": "cpp", + "span": "cpp", + "valarray": "cpp", + "csignal": "cpp", + "cstddef": "cpp" } } diff --git a/CMakeLists.txt b/CMakeLists.txt index e2346f3..f27e724 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -6,6 +6,8 @@ execute_process( RESULT_VARIABLE uname_result ) +set(CXX_STANDARD 17) + if(uname_output STREQUAL "OS/390\n") set(CMAKE_CXX_COMPILER "ibm-clang++64") else() @@ -86,6 +88,7 @@ add_custom_target( "--suppress='missingIncludeSystem'" "--inline-suppr" "--language=c++" + "--std=c++${CXX_STANDARD}" "--enable=all" "--force" "--check-level=exhaustive" @@ -112,3 +115,9 @@ add_custom_target( "-i" ${CBXP_SRC_ALL} ) + +add_custom_target( + test + COMMAND "./tests/test.sh" + DEPENDS cbxp +) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 55f5dbb..248998f 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -15,11 +15,15 @@ The following are a set of guidelines to help you contribute. * [Adding New Functionality](#adding-new-functionality) - * [Testing](#testing) + * [Branch Naming Conventions](#branch-naming-conventions) * [Fixing Bugs](#fixing-bugs) - * [Branch Naming Conventions](#branch-naming-conventions) + * [Testing](#testing) + + * [Python Interface Testing](#python-interface-testing) + + * [Shell Interface Testing](#shell-interface-testing) * [Style Guidelines](#style-guidelines) @@ -57,47 +61,54 @@ To ensure `clang-format` and `cppcheck` are always run against your code on **ev If you want to continube new functionality, open a GitHub pull request against the `dev` branch with your changes. In the PR, make sure to clearly document the new functionality including why it is valuable. -### Testing - -Testing is currently done in the form of manual funcitional testing. +### Branch Naming Conventions -* To test the **Python Interface**, build the CBXP sdist and wheel, and then install the sdist and wheel to verify that the Python binding works. - ```shell - # Build - python3 -m build - # Install sdist - python3 -m pip install dist/*.tar.gz - # Install wheel - python3 -m pip install dist/*.whl - ``` +Code branches should use the following naming conventions: - ```python - from cbxp import cbxp - psa = cbxp("psa") - ``` -* To test the **CLI**, build the `cbxp` binary using `cmake` and `gmake`, and then execute it with the appropriate arguments. - ```shell - cmake . - gmake - ./dist/cbxp psa - ``` +* `wip/name` *(Work in progress branch that likely won't be finished soon)* +* `feat/name` *(Branch where new functionality or enhancements are being developed)* +* `bug/name` *(Branch where one or more bugs are being fixed)* +* `junk/name` *(Throwaway branch created for experimentation)* ### Fixing Bugs If you fix a bug, open a GitHub pull request against the `dev` branch with the fix. In the PR, make sure to clearly describe the problem and the solution. -### Branch Naming Conventions +### Testing -Code branches should use the following naming conventions: +CBXP is tested using automated functional tests. Test cases for new functionality and bug fixes should be added to [`tests/test.py`](tests/test.py) and [`tests/test.sh`](tests/test.sh). -* `wip/name` *(Work in progress branch that likely won't be finished soon)* -* `feat/name` *(Branch where new functionality or enhancements are being developed)* -* `bug/name` *(Branch where one or more bugs are being fixed)* -* `junk/name` *(Throwaway branch created for experimentation)* +#### Python Interface Testing +1. Add new test cases to [`tests/test.py`](tests/test.py). +2. Build and install the CBXP Python package. Both the **wheel** and **sdist** distributions for all [Python versions supported by CBXP](README.md#minimum-zos--language-versions) should be tested. + + ```shell + # Build + python3 -m build + # Install sdist + python3 -m pip install dist/.tar.gz + # Install wheel + python3 -m pip install dist/.whl + ``` + +3. Run the test suite. + + ```shell + python3 ./tests/test.py + ``` + +#### Shell Interface Testing +1. Add new tests cases to [`tests/test.sh`](tests/test.sh). +2. Run the test suite using `cmake` and `gmake`. + + ```shell + cmake . + gmake test + ``` ## Style Guidelines -:bulb: _These steps can be done automatically using the [pre-commit Hooks](#pre-commit-hooks)._ +:bulb: _`clang-format` can be setup to run automatically using the [pre-commit Hooks](#pre-commit-hooks)._ The use of the `clang-format` code formatter is required. @@ -105,7 +116,7 @@ The following code style conventions should be followed: * Varible names should use snake case *(i.e., `my_variable`)*. * Pointer variables should start with `p_` *(i.e., `p_my_pointer`)*. * Class variables should end with an `_` to help differentiate between class variables and local function variables *(i.e., `my_class_variable_`)*. -* Class name should use pascal case *(i.e., `MyClass`)*. +* Class names should use pascal case *(i.e., `MyClass`)*. * Function names should use camel case *(i.e., `myFunction()`)*. * When calling a class function within the same class that function is a member of, the following syntax should be used to make it clear that a function within the same class is being called. @@ -124,7 +135,7 @@ The following code style conventions should be followed: ## Static Code Analysis -:bulb: _These steps can be done automatically using the [pre-commit Hooks](#pre-commit-hooks)._ +:bulb: _`cppcheck` can be setup to run automatically using the [pre-commit Hooks](#pre-commit-hooks)._ `cppcheck` will be run against all code contributions to ensure that contributions don't inadvertently introduce any vulnerabilities or other significant issues. All contributions must have no `cppcheck` complaints. False positives and minor complaints may be [suppressed](http://cppcheck.net/manual.html#inline-suppressions) when it makes sense to do so, but this should only be done sparingly. All `cppcheck` comlpaints should be evaluated and corrected when it is possible and makes sense to do so. You can run `cppcheck` by running `gmake check`. diff --git a/MANIFEST.in b/MANIFEST.in index c2b3b73..ef3744e 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,3 +1,4 @@ graft externals graft cbxp exclude externals/.clang-format +exclude tests/* diff --git a/README.md b/README.md index a60defe..82e14af 100644 --- a/README.md +++ b/README.md @@ -10,12 +10,10 @@ A unified and standardized interface for extracting z/OS control block data. ## Description -z/OS Control Blocks are in-memory data structures that describe and control countless process, operating system components, and subsystems. Control blocks are unbiquitous, but not very straight forward to access and extract information from. The mission of CBXP *(Control Block EXPlorer)* is to make extracting z/OS control block data straight forward and easy. CBXP accomplishes this by implementing a C/C++ XPLINK ASCII interface for extracting control blocks and post processing them into JSON. This makes it straight forward to integrate with industry standard programming languages and tools, which generally have well documented and understood foreign language intefaces for C/C++, and native and or third party JSON support that makes working with JSON data easy. +z/OS Control Blocks are in-memory data structures that describe and control countless process, operating system components, and subsystems. Control blocks are unbiquitous on z/OS, but not very straight forward to access and extract information from. The mission of CBXP *(Control Block EXPlorer)* is to make it easy to extract z/OS control block data using industry standard tools and methodologies. CBXP accomplishes this by implementing a **C/C++ XPLINK ASCII** interface for extracting control blocks and post processing them into **JSON**. This makes it straight forward to integrate with industry standard programming languages and tools, which generally have well documented and understood foreign language intefaces for C/C++, and native and or third party JSON support that makes working with JSON data easy. CBXP is the successor to the existing [cbxplorer](https://github.com/ambitus/cbexplorer) project. CBXP mainly improves upon this existing work by being implementing in C/C++ so that it is not limited to a specific programming language or tool. CBXP also focuses heavily on providing an interface that is simple and straight forward to use. -You can find information about system level control blocks in the [z/OS MVS Data Areas](https://www.ibm.com/docs/en/zos/3.1.0?topic=zos-mvs) documentation. - ## Getting Started ### Minimum z/OS & Language Versions @@ -29,39 +27,14 @@ All versions of the **IBM Open Enterprise SDK for Python** that are fully suppor * **z/OS Language Environment Runtime Support**: CBXP is compiled using the **IBM Open XL C/C++ 2.1** compiler, which is still fairly new and requires **z/OS Language Environment** service updates for runtime support. * More information can be found in section **5.2.2.2 Operational Requisites** on page **9** in the [Program Directory for IBM Open XL C/C++ 2.1 for z/OS](https://publibfp.dhe.ibm.com/epubs/pdf/i1357012.pdf). +### Interfaces +Currently, the following interfaces are provided for CBXP. Additional interfaces can be added in the future if there are use cases for them. +* [Python Interface](https://ambitus.github.io/cbxp/interfaces/python) +* [Shell Interface](https://ambitus.github.io/cbxp/interfaces/shell) + ### Supported Control Blocks -Currently, CBXP supports the following system level control blocks. CBXP also currently only supports extracting control blocks from memory *(storage)*. We plan on adding support for extracting control blocks from data sets/files and extracting control blocks based on a user defined JSON schema. We also plan on continuously expanding upon the following list of natively supported control blocks. The community is encouraged to assist in these efforts. See the [Contribution Guidelines](https://github.com/ambitus/cbxp/blob/main/CONTRIBUTING.md) for more details. - -* `psa` -* `cvt` -* `ecvt` -* `asvt` -* `ascb` - -### Python Interface -A python binding for CBXP is available for installation via [PyPi](https://pypi.org/project/cbxp/) or download via [GitHub](https://github.com/ambitus/cbxp/releases). - -#### Python Installation *(PyPi)* -```shell -python3 -m pip install cbxp -``` - -#### Python Usage -```python3 -from cbxp import cbxp -psa = cbxp("psa") -``` - -### Shell Interface -A shell-based CLI for CBXP is available for download via [GitHub](https://github.com/ambitus/cbxp/releases). - -#### Installation *(GitHub)* -Wherever you install/extract the CBXP pax file, make sure to add the path to the `bin` directory where the `cbxp` executable resides to `PATH` in `/etc/profile` *(global)* or `~/.profile`/`~/.bashrc` *(local/individual)*. - -#### Shell Usage -```shell -cbxp psa -``` + +Currently, CBXP only has support for extracting a handful of **System-Level Control Blocks** from **Live Memory** *(storage)*. See [Supported Control Blocks](https://ambitus.github.io/cbxp/supported_control_blocks) for more details. ## Help * [GitHub Discussions](https://github.com/ambitus/cbxp/discussions) diff --git a/cbxp/cbxp.cpp b/cbxp/cbxp.cpp index b1b4190..68c5f87 100644 --- a/cbxp/cbxp.cpp +++ b/cbxp/cbxp.cpp @@ -7,7 +7,8 @@ #include "cbxp_result.h" #include "control_block_explorer.hpp" -cbxp_result_t* cbxp(const char* control_block_name, bool debug) { +cbxp_result_t* cbxp(const char* control_block_name, const char* includes_string, + bool debug) { nlohmann::json control_block_json; std::string control_block = control_block_name; @@ -16,7 +17,7 @@ cbxp_result_t* cbxp(const char* control_block_name, bool debug) { CBXP::ControlBlockExplorer explorer = CBXP::ControlBlockExplorer(&cbxp_result, debug); - explorer.exploreControlBlock(control_block); + explorer.exploreControlBlock(control_block, includes_string); return &cbxp_result; } diff --git a/cbxp/cbxp.h b/cbxp/cbxp.h index 220548f..5ac1292 100644 --- a/cbxp/cbxp.h +++ b/cbxp/cbxp.h @@ -16,7 +16,8 @@ avoid memory leaks: result.result_json */ -cbxp_result_t *cbxp(const char *control_block_name, bool debug); +cbxp_result_t* cbxp(const char* control_block_name, const char* includes_string, + bool debug); #ifdef __cplusplus } diff --git a/cbxp/control_block_error.hpp b/cbxp/control_block_error.hpp new file mode 100644 index 0000000..65902a0 --- /dev/null +++ b/cbxp/control_block_error.hpp @@ -0,0 +1,27 @@ +#ifndef __CONTROL_BLOCK_ERROR_H_ +#define __CONTROL_BLOCK_ERROR_H_ + +namespace CBXP { +enum Error { BadControlBlock = 1, BadInclude }; +class CBXPError : public std::exception { + private: + Error error_code_; + + public: + explicit CBXPError(const Error& rc) : error_code_(rc) {} + const int getErrorCode() const { return error_code_; } +}; + +class ControlBlockError : public CBXPError { + public: + ControlBlockError() : CBXPError(Error::BadControlBlock) {} +}; + +class IncludeError : public CBXPError { + public: + IncludeError() : CBXPError(Error::BadInclude) {} +}; + +} // namespace CBXP + +#endif diff --git a/cbxp/control_block_explorer.cpp b/cbxp/control_block_explorer.cpp index b6fd3fe..ea1c95e 100644 --- a/cbxp/control_block_explorer.cpp +++ b/cbxp/control_block_explorer.cpp @@ -5,14 +5,48 @@ #include #include "cbxp.h" +#include "control_block_error.hpp" #include "control_blocks/ascb.hpp" #include "control_blocks/asvt.hpp" +#include "control_blocks/control_block.hpp" #include "control_blocks/cvt.hpp" #include "control_blocks/ecvt.hpp" #include "control_blocks/psa.hpp" #include "logger.hpp" namespace CBXP { + +std::vector ControlBlockExplorer::createIncludeList( + const std::string& includes_string) { + if (includes_string == "") { + return {}; + } + + std::vector includes = {}; + + Logger::getInstance().debug( + "Creating include list from the provided include list string: " + + includes_string); + + const std::string del = ","; + std::string entry; + size_t index = 0; + + auto pos = includes_string.find(del); + + while (pos != std::string::npos) { + entry = includes_string.substr(index, pos); + includes.push_back(entry); + index += pos + 1; + pos = includes_string.substr(index, std::string::npos).find(del); + } + entry = includes_string.substr(index, pos); + includes.push_back(entry); + Logger::getInstance().debug("Done."); + + return includes; +} + ControlBlockExplorer::ControlBlockExplorer(cbxp_result_t* p_result, bool debug) { Logger::getInstance().setDebug(debug); @@ -24,29 +58,36 @@ ControlBlockExplorer::ControlBlockExplorer(cbxp_result_t* p_result, p_result->result_json_length = 0; p_result->result_json = nullptr; - p_result->return_code = -1; + p_result->return_code = 0; - _p_result = p_result; + p_result_ = p_result; } void ControlBlockExplorer::exploreControlBlock( - const std::string& control_block_name) { - nlohmann::json control_block_json = {}; + const std::string& control_block_name, const std::string& includes_string) { + std::vector includes = + ControlBlockExplorer::createIncludeList(includes_string); Logger::getInstance().debug("Extracting '" + control_block_name + "' control block data..."); - if (control_block_name == "psa") { - control_block_json = CBXP::PSA().get(); - } else if (control_block_name == "cvt") { - control_block_json = CBXP::CVT().get(); - } else if (control_block_name == "ecvt") { - control_block_json = CBXP::ECVT().get(); - } else if (control_block_name == "ascb") { - control_block_json = CBXP::ASCB().get(); - } else if (control_block_name == "asvt") { - control_block_json = CBXP::ASVT().get(); - } else { + nlohmann::json control_block_json; + try { + if (control_block_name == "psa") { + control_block_json = PSA(includes).get(); + } else if (control_block_name == "cvt") { + control_block_json = CVT(includes).get(); + } else if (control_block_name == "ecvt") { + control_block_json = ECVT(includes).get(); + } else if (control_block_name == "ascb") { + control_block_json = ASCB(includes).get(); + } else if (control_block_name == "asvt") { + control_block_json = ASVT(includes).get(); + } else { + throw ControlBlockError(); + } + } catch (const CBXPError& e) { + p_result_->return_code = e.getErrorCode(); return; } @@ -57,17 +98,16 @@ void ControlBlockExplorer::exploreControlBlock( Logger::getInstance().debug("Control Block JSON: " + control_block_json_string); - _p_result->result_json_length = control_block_json_string.length(); - _p_result->result_json = new char[_p_result->result_json_length]; - _p_result->result_json[_p_result->result_json_length] = 0; + p_result_->result_json_length = control_block_json_string.length(); + p_result_->result_json = new char[p_result_->result_json_length]; + p_result_->result_json[p_result_->result_json_length] = 0; - Logger::getInstance().debugAllocate(_p_result->result_json, 64, - _p_result->result_json_length); + Logger::getInstance().debugAllocate(p_result_->result_json, 64, + p_result_->result_json_length); - std::strncpy(_p_result->result_json, control_block_json_string.c_str(), - _p_result->result_json_length); + std::strncpy(p_result_->result_json, control_block_json_string.c_str(), + p_result_->result_json_length); - _p_result->return_code = 0; return; } diff --git a/cbxp/control_block_explorer.hpp b/cbxp/control_block_explorer.hpp index f6f1aae..7542b17 100644 --- a/cbxp/control_block_explorer.hpp +++ b/cbxp/control_block_explorer.hpp @@ -8,11 +8,14 @@ namespace CBXP { class ControlBlockExplorer { private: - cbxp_result_t* _p_result; + cbxp_result_t* p_result_; + static std::vector createIncludeList( + const std::string& includes_string); public: ControlBlockExplorer(cbxp_result_t* p_result, bool debug); - void exploreControlBlock(const std::string& control_block_name); + void exploreControlBlock(const std::string& control_block_name, + const std::string& includes_string); }; } // namespace CBXP diff --git a/cbxp/control_blocks/ascb.cpp b/cbxp/control_blocks/ascb.cpp index 7f887a9..e8d7312 100644 --- a/cbxp/control_blocks/ascb.cpp +++ b/cbxp/control_blocks/ascb.cpp @@ -4,95 +4,83 @@ #include #include -#include #include #include #include #include +#include "asvt.hpp" #include "logger.hpp" namespace CBXP { -nlohmann::json ASCB::get() { - const struct psa* __ptr32 p_psa = 0; - - const struct cvtmap* __ptr32 p_cvtmap = - // 'nullPointer' is a false positive because the PSA starts at address 0 - // cppcheck-suppress nullPointer - static_cast(p_psa->flccvt); - const asvt_t* __ptr32 p_asvt = - static_cast(p_cvtmap->cvtasvt); - +nlohmann::json ASCB::get(void* __ptr32 p_control_block) { nlohmann::json ascb_json = {}; + const ascb* __ptr32 p_ascb; + + if (p_control_block == nullptr) { + // PSA starts at address 0 + const struct psa* __ptr32 p_psa = 0; - ascb_json["ascbs"] = std::vector(); - std::vector& ascbs = - ascb_json["ascbs"].get_ref&>(); - ascbs.reserve(p_asvt->asvtmaxu); + const struct cvtmap* __ptr32 p_cvtmap = + // 'nullPointer' is a false positive because the PSA starts at address 0 + // cppcheck-suppress nullPointer + static_cast(p_psa->flccvt); + asvt_t* __ptr32 p_asvt = static_cast(p_cvtmap->cvtasvt); - const uint32_t* __ptr32 p_ascb_addr = - reinterpret_cast(&p_asvt->asvtenty); + ascb_json["ascbs"] = std::vector(); + std::vector& ascbs = + ascb_json["ascbs"].get_ref&>(); + ascbs.reserve(p_asvt->asvtmaxu); - for (int i = 0; i < p_asvt->asvtmaxu; i++) { - if (0x80000000 & *p_ascb_addr) { - Logger::getInstance().debug(formatter_.getHex(p_ascb_addr) + - " is not a valid ASCB address"); - p_ascb_addr++; - continue; + uint32_t* __ptr32 p_ascb_addr = + reinterpret_cast(&p_asvt->asvtenty); + for (int i = 0; i < p_asvt->asvtmaxu; i++) { + if (0x80000000 & *p_ascb_addr) { + Logger::getInstance().debug(formatter_.getHex(p_ascb_addr) + + " is not a valid ASCB address"); + p_ascb_addr++; + continue; + } + ascbs.push_back(ASCB::get(reinterpret_cast(*p_ascb_addr))); + p_ascb_addr++; // This SHOULD increment the pointer by 4 bytes. } - nlohmann::json ascb_entry_json = {}; + return ascbs; + } else { + p_ascb = static_cast(p_control_block); + } - struct ascb* __ptr32 p_ascb = - reinterpret_cast(*p_ascb_addr); + ascb_json["ascbassb"] = formatter_.getHex(&(p_ascb->ascbassb)); + ascb_json["ascbasxb"] = formatter_.getHex(&(p_ascb->ascbasxb)); - Logger::getInstance().debug("ASCB hex dump:"); - Logger::getInstance().hexDump(reinterpret_cast(p_ascb), - sizeof(struct ascb)); + Logger::getInstance().debug("ASCB hex dump:"); + Logger::getInstance().hexDump(reinterpret_cast(p_ascb), + sizeof(struct ascb)); - ascb_entry_json["ascbasid"] = static_cast(p_ascb->ascbasid); - ascb_entry_json["ascbassb"] = - formatter_.getHex(&(p_ascb->ascbassb)); - ascb_entry_json["ascbasxb"] = - formatter_.getHex(&(p_ascb->ascbasxb)); - ascb_entry_json["ascbdcti"] = p_ascb->ascbdcti; - ascb_entry_json["ascbejst"] = formatter_.getBitmap( - reinterpret_cast(&p_ascb->ascbejst)); - ascb_entry_json["ascbflg3"] = - formatter_.getBitmap(p_ascb->ascbflg3); - ascb_entry_json["ascbfw3"] = formatter_.getBitmap( - reinterpret_cast(&p_ascb->ascbfw3)); - ascb_entry_json["ascbjbni"] = - formatter_.getHex(&(p_ascb->ascbjbni)); - ascb_entry_json["ascbjbns"] = - formatter_.getHex(&(p_ascb->ascbjbns)); - ascb_entry_json["ascblsqe"] = p_ascb->ascblsqe; - ascb_entry_json["ascblsqt"] = p_ascb->ascblsqt; - ascb_entry_json["ascbnoft"] = - formatter_.getBitmap(p_ascb->ascbnoft); - ascb_entry_json["ascboucb"] = - formatter_.getHex(&(p_ascb->ascboucb)); - ascb_entry_json["ascbouxb"] = - formatter_.getHex(&(p_ascb->ascbouxb)); - ascb_entry_json["ascbpo1m"] = - formatter_.getBitmap(p_ascb->ascbpo1m); - ascb_entry_json["ascbp1m0"] = - formatter_.getBitmap(p_ascb->ascbp1m0); - ascb_entry_json["ascbrsme"] = - formatter_.getHex(&(p_ascb->ascbrsme)); - ascb_entry_json["ascbsdbf"] = - formatter_.getBitmap(p_ascb->ascbsdbf); - ascb_entry_json["ascbsrbt"] = formatter_.getBitmap( - reinterpret_cast(&p_ascb->ascbsrbt)); - ascb_entry_json["ascbtcbe"] = - formatter_.getBitmap(p_ascb->ascbtcbe); - ascb_entry_json["ascbtcbs"] = p_ascb->ascbtcbs; - ascb_entry_json["ascbxtcb"] = - formatter_.getHex(&(p_ascb->ascbxtcb)); - ascb_entry_json["ascbzcx"] = - formatter_.getBitmap(p_ascb->ascbzcx); - ascbs.push_back(ascb_entry_json); - p_ascb_addr++; // This SHOULD increment the pointer by 4 bytes. - } + ascb_json["ascbascb"] = formatter_.getString(p_ascb->ascbascb, 4); + ascb_json["ascbasid"] = formatter_.getBitmap(p_ascb->ascbasid); + ascb_json["ascbdcti"] = p_ascb->ascbdcti; + ascb_json["ascbejst"] = formatter_.getBitmap( + reinterpret_cast(&p_ascb->ascbejst)); + ascb_json["ascbflg3"] = formatter_.getBitmap(p_ascb->ascbflg3); + ascb_json["ascbfw3"] = formatter_.getBitmap( + reinterpret_cast(&p_ascb->ascbfw3)); + ascb_json["ascbjbni"] = formatter_.getHex(&(p_ascb->ascbjbni)); + ascb_json["ascbjbns"] = formatter_.getHex(&(p_ascb->ascbjbns)); + ascb_json["ascblsqe"] = p_ascb->ascblsqe; + ascb_json["ascblsqt"] = p_ascb->ascblsqt; + ascb_json["ascbnoft"] = formatter_.getBitmap(p_ascb->ascbnoft); + ascb_json["ascboucb"] = formatter_.getHex(&(p_ascb->ascboucb)); + ascb_json["ascbouxb"] = formatter_.getHex(&(p_ascb->ascbouxb)); + ascb_json["ascbpo1m"] = formatter_.getBitmap(p_ascb->ascbpo1m); + ascb_json["ascbp1m0"] = formatter_.getBitmap(p_ascb->ascbp1m0); + ascb_json["ascbrsme"] = formatter_.getHex(&(p_ascb->ascbrsme)); + ascb_json["ascbsdbf"] = formatter_.getBitmap(p_ascb->ascbsdbf); + ascb_json["ascbsrbt"] = formatter_.getBitmap( + reinterpret_cast(&p_ascb->ascbsrbt)); + ascb_json["ascbtcbe"] = formatter_.getBitmap(p_ascb->ascbtcbe); + ascb_json["ascbtcbs"] = p_ascb->ascbtcbs; + ascb_json["ascbxtcb"] = formatter_.getHex(&(p_ascb->ascbxtcb)); + ascb_json["ascbzcx"] = formatter_.getBitmap(p_ascb->ascbzcx); return ascb_json; } diff --git a/cbxp/control_blocks/ascb.hpp b/cbxp/control_blocks/ascb.hpp index 4d7020b..f15b841 100644 --- a/cbxp/control_blocks/ascb.hpp +++ b/cbxp/control_blocks/ascb.hpp @@ -7,7 +7,9 @@ namespace CBXP { class ASCB : public ControlBlock { public: - nlohmann::json get(); + nlohmann::json get(void* __ptr32 p_control_block = nullptr) override; + explicit ASCB(const std::vector& includes) + : ControlBlock("ascb", {}, includes) {} }; } // namespace CBXP diff --git a/cbxp/control_blocks/asvt.cpp b/cbxp/control_blocks/asvt.cpp index c0e8521..17ad32f 100644 --- a/cbxp/control_blocks/asvt.cpp +++ b/cbxp/control_blocks/asvt.cpp @@ -3,30 +3,73 @@ #include #include -#include #include #include #include #include +#include "ascb.hpp" +#include "asvt.hpp" #include "logger.hpp" namespace CBXP { -nlohmann::json ASVT::get() { - struct psa* __ptr32 p_psa = 0; - struct cvtmap* __ptr32 p_cvtmap = - // 'nullPointer' is a false positive because the PSA starts at address 0 - // cppcheck-suppress nullPointer - static_cast(p_psa->flccvt); - const asvt_t* __ptr32 p_asvt = - static_cast(p_cvtmap->cvtasvt); +nlohmann::json ASVT::get(void* __ptr32 p_control_block) { + const asvt_t* __ptr32 p_asvt; + nlohmann::json asvt_json = {}; + + if (p_control_block == nullptr) { + const struct psa* __ptr32 p_psa = 0; + const struct cvtmap* __ptr32 p_cvtmap = + // 'nullPointer' is a false positive because the PSA starts at address 0 + // cppcheck-suppress nullPointer + static_cast(p_psa->flccvt); + p_asvt = static_cast(p_cvtmap->cvtasvt); + } else { + p_asvt = static_cast(p_control_block); + } + + Logger::getInstance().debug("ASCB pointers:"); + Logger::getInstance().hexDump( + reinterpret_cast(&p_asvt->asvtenty), p_asvt->asvtmaxu * 4); + + std::vector ascbs; + ascbs.reserve(p_asvt->asvtmaxu); + const uint32_t* __ptr32 p_ascb = const_cast( + reinterpret_cast(&p_asvt->asvtenty)); + + for (int i = 0; i < p_asvt->asvtmaxu; i++) { + ascbs.push_back(formatter_.getHex(p_ascb)); + p_ascb++; // This SHOULD increment the pointer by 4 bytes. + } + + asvt_json["asvtenty"] = ascbs; Logger::getInstance().debug("ASVT hex dump:"); Logger::getInstance().hexDump(reinterpret_cast(p_asvt), sizeof(asvt_t)); + for (const auto& [include, include_includes] : include_map_) { + if (include == "ascb") { + nlohmann::json ascbs_json; + CBXP::ASCB ascb(include_includes); + uint32_t* __ptr32 p_ascb_addr = const_cast( + reinterpret_cast(&p_asvt->asvtenty)); + for (int i = 0; i < p_asvt->asvtmaxu; i++) { + if (0x80000000 & *p_ascb_addr) { + Logger::getInstance().debug(formatter_.getHex(p_ascb_addr) + + " is not a valid ASCB address"); + p_ascb_addr++; + continue; + } + nlohmann::json ascb_json = + ascb.get(reinterpret_cast(*p_ascb_addr)); + ascbs_json.push_back(ascb_json); + p_ascb_addr++; // This SHOULD increment the pointer by 4 bytes. + } + asvt_json["asvtenty"] = ascbs_json; + } + } // Get fields - nlohmann::json asvt_json = {}; asvt_json["asvthwmasid"] = p_asvt->asvthwmasid; asvt_json["asvtcurhighasid"] = p_asvt->asvtcurhighasid; asvt_json["asvtreua"] = formatter_.getHex(p_asvt->asvtreua); @@ -42,21 +85,6 @@ nlohmann::json ASVT::get() { asvt_json["asvtmdsc"] = p_asvt->asvtmdsc; asvt_json["asvtfrst"] = formatter_.getHex(p_asvt->asvtfrst); - Logger::getInstance().debug("ASCB pointers:"); - Logger::getInstance().hexDump( - reinterpret_cast(&p_asvt->asvtenty), p_asvt->asvtmaxu * 4); - - std::vector ascbs; - ascbs.reserve(p_asvt->asvtmaxu); - const uint32_t* __ptr32 p_ascb = - reinterpret_cast(&p_asvt->asvtenty); - - for (int i = 0; i < p_asvt->asvtmaxu; i++) { - ascbs.push_back(formatter_.getHex(p_ascb)); - p_ascb++; // This SHOULD increment the pointer by 4 bytes. - } - - asvt_json["asvtenty"] = ascbs; return asvt_json; } } // namespace CBXP diff --git a/cbxp/control_blocks/asvt.hpp b/cbxp/control_blocks/asvt.hpp index 188dee9..a6e91c9 100644 --- a/cbxp/control_blocks/asvt.hpp +++ b/cbxp/control_blocks/asvt.hpp @@ -32,7 +32,9 @@ namespace CBXP { class ASVT : public ControlBlock { public: - nlohmann::json get(); + nlohmann::json get(void* __ptr32 p_control_block = nullptr) override; + explicit ASVT(const std::vector& includes) + : ControlBlock("asvt", {"ascb"}, includes) {} }; } // namespace CBXP diff --git a/cbxp/control_blocks/control_block.cpp b/cbxp/control_blocks/control_block.cpp new file mode 100644 index 0000000..02616ab --- /dev/null +++ b/cbxp/control_blocks/control_block.cpp @@ -0,0 +1,90 @@ +#include "control_block.hpp" + +#include + +#include "control_block_error.hpp" +#include "logger.hpp" + +namespace CBXP { +void ControlBlock::createIncludeMap(const std::vector& includes) { + Logger::getInstance().debug("Creating include map for the '" + + control_block_name_ + "' control block..."); + for (std::string include : includes) { + if (include == "**") { + ControlBlock::processDoubleAsteriskInclude(); + return; + } else if (include == "*") { + ControlBlock::processAsteriskInclude(); + } else { + ControlBlock::processExplicitInclude(include); + } + } + Logger::getInstance().debug("Done"); +} + +void ControlBlock::processDoubleAsteriskInclude() { + // Any existing entries in the hash map are redundant, so clear them + include_map_.clear(); + for (const std::string& includable : includables_) { + // Build a map of all includables_ but with "**" at the next level + include_map_[includable] = {"**"}; + } +} + +void ControlBlock::processAsteriskInclude() { + if (include_map_.empty()) { + for (const std::string& includable : includables_) { + // Build a map of all includables_ + include_map_[includable] = {}; + } + } + for (const std::string& includable : includables_) { + if (include_map_.find(includable) != include_map_.end()) { + continue; + } + // Add all includables_ not already present to the map + include_map_[includable] = {}; + } +} + +void ControlBlock::processExplicitInclude(std::string& include) { + // Default case; have to validate against an includable + const std::string del = "."; + std::string include_includes = ""; + size_t del_pos = include.find(del); + if (del_pos != std::string::npos) { + // If there's a "." then separate include into the include and its + // includes + include_includes = include.substr(del_pos + 1); + include.resize(del_pos); + } + if (std::find(includables_.begin(), includables_.end(), include) == + includables_.end()) { + Logger::getInstance().debug("'" + include + + "' is not a known includable for the '" + + control_block_name_ + "' control block"); + throw IncludeError(); + } + if (include_map_.find(include) == include_map_.end()) { + // If we don't already have this include in our map, add it with its + // includes + if (include_includes == "") { + include_map_[include] = {}; + } else { + include_map_[include] = {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()) { + return; + } + if (include_includes == "") { + return; + } + include_map_[include].push_back(include_includes); + } +} +} // namespace CBXP + diff --git a/cbxp/control_blocks/control_block.hpp b/cbxp/control_blocks/control_block.hpp index 36f05bb..dcbcb3b 100644 --- a/cbxp/control_blocks/control_block.hpp +++ b/cbxp/control_blocks/control_block.hpp @@ -8,8 +8,27 @@ namespace CBXP { class ControlBlock { + private: + const std::string control_block_name_; + const std::vector includables_; + void processDoubleAsteriskInclude(); + void processAsteriskInclude(); + void processExplicitInclude(std::string& include); + protected: ControlBlockFieldFormatter formatter_; + std::unordered_map> include_map_; + + 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) + : control_block_name_(name), includables_(includables) { + createIncludeMap(includes); + } + virtual ~ControlBlock() = default; }; } // namespace CBXP diff --git a/cbxp/control_blocks/cvt.cpp b/cbxp/control_blocks/cvt.cpp index 3c4e54a..12107ad 100644 --- a/cbxp/control_blocks/cvt.cpp +++ b/cbxp/control_blocks/cvt.cpp @@ -7,44 +7,64 @@ #include #include +#include "asvt.hpp" +#include "ecvt.hpp" #include "logger.hpp" namespace CBXP { -nlohmann::json CVT::get() { - const struct psa* __ptr32 p_psa = 0; - // 'nullPointer' is a false positive because the PSA starts at address 0 - // cppcheck-suppress-begin nullPointer - const struct cvtmap* __ptr32 p_cvtmap = - static_cast(p_psa->flccvt); - const struct cvtfix* __ptr32 p_cvtfix = - static_cast(p_psa->flccvt); - const struct cvtxtnt2* __ptr32 p_cvtxtnt2 = - static_cast(p_psa->flccvt); - const struct cvtvstgx* __ptr32 p_cvtvstgx = - static_cast(p_psa->flccvt); +nlohmann::json CVT::get(void* __ptr32 p_control_block) { + const struct cvtmap* __ptr32 p_cvtmap; + const struct cvtfix* __ptr32 p_cvtfix; + const struct cvtxtnt2* __ptr32 p_cvtxtnt2; + const struct cvtvstgx* __ptr32 p_cvtvstgx; + nlohmann::json cvt_json = {}; + + if (p_control_block == nullptr) { + // PSA starts at address 0 + const struct psa* __ptr32 p_psa = 0; + // 'nullPointer' is a false positive because the PSA starts at address 0 + // cppcheck-suppress-begin nullPointer + p_cvtmap = static_cast(p_psa->flccvt); + } else { + p_cvtmap = static_cast(p_control_block); + } + p_cvtfix = const_cast( + reinterpret_cast(p_cvtmap)); + p_cvtxtnt2 = const_cast( + reinterpret_cast(p_cvtmap)); + p_cvtvstgx = const_cast( + reinterpret_cast(p_cvtmap)); // cppcheck-suppress-end nullPointer Logger::getInstance().debug("CVT hex dump:"); Logger::getInstance().hexDump(reinterpret_cast(p_cvtmap), sizeof(struct cvtmap)); + cvt_json["cvtasvt"] = formatter_.getHex(&p_cvtmap->cvtasvt); + cvt_json["cvtecvt"] = formatter_.getHex(&p_cvtmap->cvtecvt); + + for (const auto& [include, include_includes] : include_map_) { + if (include == "asvt") { + cvt_json["cvtasvt"] = CBXP::ASVT(include_includes).get(p_cvtmap->cvtasvt); + } else if (include == "ecvt") { + cvt_json["cvtecvt"] = CBXP::ECVT(include_includes).get(p_cvtmap->cvtecvt); + } + } + // Get fields - nlohmann::json cvt_json = {}; - cvt_json["cvtabend"] = formatter_.getHex(p_cvtmap->cvtabend); - cvt_json["cvtamff"] = formatter_.getHex(p_cvtmap->cvtamff); - cvt_json["cvtasmvt"] = formatter_.getHex(p_cvtmap->cvtasmvt); - cvt_json["cvtasvt"] = formatter_.getHex(p_cvtmap->cvtasvt); - cvt_json["cvtbret"] = formatter_.getHex(p_cvtmap->cvtbret); - cvt_json["cvtbsm0f"] = formatter_.getHex(p_cvtmap->cvtbsm0f); - cvt_json["cvtcsd"] = formatter_.getHex(p_cvtmap->cvtcsd); - cvt_json["cvtctlfg"] = formatter_.getBitmap( + cvt_json["cvtabend"] = formatter_.getHex(p_cvtmap->cvtabend); + cvt_json["cvtamff"] = formatter_.getHex(p_cvtmap->cvtamff); + cvt_json["cvtasmvt"] = formatter_.getHex(p_cvtmap->cvtasmvt); + cvt_json["cvtbret"] = formatter_.getHex(p_cvtmap->cvtbret); + cvt_json["cvtbsm0f"] = formatter_.getHex(p_cvtmap->cvtbsm0f); + cvt_json["cvtcsd"] = formatter_.getHex(p_cvtmap->cvtcsd); + cvt_json["cvtctlfg"] = formatter_.getBitmap( reinterpret_cast(&p_cvtmap->cvtctlfg)); cvt_json["cvtdcb"] = formatter_.getBitmap( reinterpret_cast(&p_cvtmap->cvtdcb)); cvt_json["cvtdcpa"] = formatter_.getBitmap(p_cvtmap->cvtdcpa); cvt_json["cvtdfa"] = formatter_.getHex(p_cvtmap->cvtdfa); - cvt_json["cvtecvt"] = formatter_.getHex(p_cvtmap->cvtecvt); cvt_json["cvtedat2"] = formatter_.getBitmap(p_cvtmap->cvtedat2); cvt_json["cvteplps"] = formatter_.getHex(p_cvtvstgx->cvteplps); cvt_json["cvtexit"] = formatter_.getHex(p_cvtmap->cvtexit); @@ -122,4 +142,3 @@ nlohmann::json CVT::get() { return cvt_json; } } // namespace CBXP - diff --git a/cbxp/control_blocks/cvt.hpp b/cbxp/control_blocks/cvt.hpp index 0163a4a..3111cda 100644 --- a/cbxp/control_blocks/cvt.hpp +++ b/cbxp/control_blocks/cvt.hpp @@ -7,7 +7,9 @@ namespace CBXP { class CVT : public ControlBlock { public: - nlohmann::json get(); + nlohmann::json get(void* __ptr32 p_control_block = nullptr) override; + explicit CVT(const std::vector& includes) + : ControlBlock("cvt", {"ecvt", "asvt"}, includes) {} }; } // namespace CBXP diff --git a/cbxp/control_blocks/ecvt.cpp b/cbxp/control_blocks/ecvt.cpp index a3940ac..e577624 100644 --- a/cbxp/control_blocks/ecvt.cpp +++ b/cbxp/control_blocks/ecvt.cpp @@ -12,23 +12,29 @@ #include "logger.hpp" namespace CBXP { -nlohmann::json ECVT::get() { - const struct psa* __ptr32 p_psa = 0; - // Get the address of the CVT from the PSA - const struct cvtmap* __ptr32 p_cvt = - // 'nullPointer' is a false positive because the PSA starts at address 0 - // cppcheck-suppress nullPointer - static_cast(p_psa->flccvt); - // Get the address of the EVCT from the CVT - const struct ecvt* __ptr32 p_ecvt = - static_cast(p_cvt->cvtecvt); +nlohmann::json ECVT::get(void* __ptr32 p_control_block) { + const struct ecvt* __ptr32 p_ecvt; + nlohmann::json ecvt_json = {}; + + if (p_control_block == nullptr) { + // PSA starts at address 0 + const struct psa* __ptr32 p_psa = 0; + // Get the address of the CVT from the PSA + const struct cvtmap* __ptr32 p_cvt = + // 'nullPointer' is a false positive because the PSA starts at address 0 + // cppcheck-suppress nullPointer + static_cast(p_psa->flccvt); + // Get the address of the EVCT from the CVT + p_ecvt = static_cast(p_cvt->cvtecvt); + } else { + p_ecvt = static_cast(p_control_block); + } Logger::getInstance().debug("ECVT hex dump:"); Logger::getInstance().hexDump(reinterpret_cast(p_ecvt), sizeof(struct ecvt)); // Get Fields - nlohmann::json ecvt_json = {}; ecvt_json["ecvt_boostinfo"] = formatter_.getBitmap( diff --git a/cbxp/control_blocks/ecvt.hpp b/cbxp/control_blocks/ecvt.hpp index 6f12393..9cece25 100644 --- a/cbxp/control_blocks/ecvt.hpp +++ b/cbxp/control_blocks/ecvt.hpp @@ -7,7 +7,9 @@ namespace CBXP { class ECVT : public ControlBlock { public: - nlohmann::json get(); + nlohmann::json get(void* __ptr32 p_control_block = nullptr) override; + explicit ECVT(const std::vector& includes) + : ControlBlock("ecvt", {}, includes) {} }; } // namespace CBXP #endif diff --git a/cbxp/control_blocks/psa.cpp b/cbxp/control_blocks/psa.cpp index 2b00f88..8feec8c 100644 --- a/cbxp/control_blocks/psa.cpp +++ b/cbxp/control_blocks/psa.cpp @@ -6,28 +6,40 @@ #include #include +#include "cvt.hpp" #include "logger.hpp" namespace CBXP { -nlohmann::json PSA::get() { - // PSA starts at address 0 - const struct psa* __ptr32 p_psa = 0; +nlohmann::json PSA::get(void* __ptr32 p_control_block) { + const struct psa* __ptr32 p_psa; + nlohmann::json psa_json = {}; + + if (p_control_block == nullptr) { + // PSA starts at address 0 + p_psa = 0; + } else { + p_psa = static_cast(p_control_block); + } Logger::getInstance().debug("PSA hex dump:"); Logger::getInstance().hexDump( reinterpret_cast(p_psa), sizeof(struct psa) / 2, true); // Only the first "page" of the PSA is not fetch-protected + psa_json["flccvt"] = formatter_.getHex(&p_psa->flccvt); + + for (const auto& [include, include_includes] : include_map_) { + if (include == "cvt") { + psa_json["flccvt"] = CBXP::CVT(include_includes).get(p_psa->flccvt); + } + } + // Get fields - nlohmann::json psa_json = {}; - // 'nullPointer' is a false positive because the PSA starts at address 0 - // cppcheck-suppress-begin nullPointer psa_json["psapsa"] = formatter_.getString(p_psa->psapsa, 4); psa_json["flcsvilc"] = static_cast(p_psa->flcsvilc); psa_json["flcrnpsw_bitstring"] = formatter_.getBitmap(p_psa->flcrnpsw); psa_json["flcrnpsw_hex"] = formatter_.getHex(p_psa->flcrnpsw + 4); - psa_json["flccvt"] = formatter_.getHex(p_psa->flccvt); psa_json["flcsopsw"] = formatter_.getPswSmall(p_psa->flcsopsw); psa_json["flcarch"] = formatter_.getBitmap( reinterpret_cast(&p_psa->flcarch)); @@ -48,7 +60,6 @@ nlohmann::json PSA::get() { psa_json["psatrvt"] = formatter_.getHex(p_psa->psatrvt); psa_json["psaval"] = formatter_.getBitmap(p_psa->psaval); psa_json["psaxcvt"] = formatter_.getHex(p_psa->psaxcvt); - // cppcheck-suppress-end nullPointer return psa_json; } diff --git a/cbxp/control_blocks/psa.hpp b/cbxp/control_blocks/psa.hpp index 12f6485..7843b38 100644 --- a/cbxp/control_blocks/psa.hpp +++ b/cbxp/control_blocks/psa.hpp @@ -7,7 +7,9 @@ namespace CBXP { class PSA : public ControlBlock { public: - nlohmann::json get(); + nlohmann::json get(void* __ptr32 p_control_block = nullptr) override; + explicit PSA(const std::vector& includes) + : ControlBlock("psa", {"cvt"}, includes) {} }; } // namespace CBXP #endif diff --git a/cbxp/main.cpp b/cbxp/main.cpp index 8038d1a..fabb910 100644 --- a/cbxp/main.cpp +++ b/cbxp/main.cpp @@ -4,46 +4,84 @@ #include #include "cbxp_result.h" +#include "control_block_error.hpp" #include "control_block_explorer.hpp" static void show_usage(const char* argv[]) { - std::cout << "Usage: " << std::endl; - std::cout << " " << argv[0] - << " [ENABLE_DEBUG_LOGGING: -d/--debug] (default false)" - << " [CONTROL_BLOCK]" << std::endl; - std::cout << " " << argv[0] << " [SHOW_VERSION: -v/--version]" << std::endl; - std::cout << " " << argv[0] << " [SHOW_USAGE]: -h/--help" << std::endl; + std::cout << "Usage: " << argv[0] << " [options] " << std::endl + << std::endl; + + std::cout << "Options:" << std::endl + << " -d, --debug Write debug messages" + << std::endl + << " -i, --include Include additional control " + "blocks based on a pattern" + << std::endl + << " -v, --version Show version number" + << std::endl + << " -h, --help Show usage information" + << std::endl + << std::endl; } int main(int argc, const char* argv[]) { - bool debug = false; + bool debug = false; + std::string control_block_name = "", includes_string = ""; - if (argc == 3) { - if ((std::strcmp(argv[1], "-d") == 0) || - (std::strcmp(argv[1], "--debug") == 0)) { - debug = true; - } else { - show_usage(argv); - return -1; - } - } else if (argc != 2) { + if (argc < 2) { show_usage(argv); return -1; } - if (std::strcmp(argv[1], "-v") == 0 || - std::strcmp(argv[1], "--version") == 0) { - std::cout << "CBXP " << VERSION << std::endl; - return 0; + if (argc == 2) { + if (std::strcmp(argv[1], "-v") == 0 || + std::strcmp(argv[1], "--version") == 0) { + std::cout << "CBXP " << VERSION << std::endl; + return 0; + } + + if (std::strcmp(argv[1], "-h") == 0 || + std::strcmp(argv[1], "--help") == 0) { + show_usage(argv); + return 0; + } + } + + for (int i = 1; i < argc; i++) { + std::string flag = argv[i]; + if (flag == "-d" || flag == "--debug") { + debug = true; + } else if (flag == "-i" || flag == "--include") { + if (i + 1 >= argc - 1) { + show_usage(argv); + return -1; + } + std::string include = std::string(argv[++i]); + bool has_comma = std::any_of(include.begin(), include.end(), + [](char c) { return c == ','; }); + if (has_comma) { + std::cerr << "Include patterns cannot contain commas" << std::endl; + return -1; + } + if (includes_string == "") { + includes_string = include; + } else { + includes_string += "," + include; + } + } else { + if (i != argc - 1) { + show_usage(argv); + return -1; + } + control_block_name = std::string(argv[i]); + } } - if (std::strcmp(argv[1], "-h") == 0 || std::strcmp(argv[1], "--help") == 0) { + if (control_block_name == "") { show_usage(argv); - return 0; + return -1; } - std::string control_block_name = argv[argc - 1]; - nlohmann::json control_block_json; static cbxp_result_t cbxp_result = {nullptr, 0, -1}; @@ -51,14 +89,18 @@ int main(int argc, const char* argv[]) { CBXP::ControlBlockExplorer explorer = CBXP::ControlBlockExplorer(&cbxp_result, debug); - explorer.exploreControlBlock(control_block_name); + explorer.exploreControlBlock(control_block_name, includes_string); - if (cbxp_result.return_code == -1) { - std::cout << "Unknown control block '" << control_block_name + if (cbxp_result.return_code == CBXP::Error::BadControlBlock) { + std::cerr << "Unknown control block '" << control_block_name << "' was specified." << std::endl; + return -1; + } else if (cbxp_result.return_code == CBXP::Error::BadInclude) { + std::cerr << "A bad include pattern was provided" << std::endl; + return -1; } else { std::cout << cbxp_result.result_json << std::endl; } - return cbxp_result.return_code; + return 0; } diff --git a/cbxp/python/_cbxp.c b/cbxp/python/_cbxp.c index dea4c98..90fe08c 100644 --- a/cbxp/python/_cbxp.c +++ b/cbxp/python/_cbxp.c @@ -15,13 +15,14 @@ static PyObject* call_cbxp(PyObject* self, PyObject* args, PyObject* kwargs) { PyObject* result_dictionary; PyObject* debug_pyobj; const char* control_block; + const char* includes_string; Py_ssize_t request_length; bool debug = false; - static char* kwlist[] = {"request", "debug", NULL}; + static char* kwlist[] = {"request", "include", "debug", NULL}; - if (!PyArg_ParseTupleAndKeywords(args, kwargs, "s|O", kwlist, &control_block, - &debug_pyobj)) { + if (!PyArg_ParseTupleAndKeywords(args, kwargs, "s|sO", kwlist, &control_block, + &includes_string, &debug_pyobj)) { return NULL; } @@ -33,7 +34,7 @@ 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); - cbxp_result_t* result = cbxp(control_block, debug); + cbxp_result_t* result = cbxp(control_block, includes_string, debug); result_dictionary = Py_BuildValue( "{s:s#, s:i}", "result_json", result->result_json, diff --git a/pyproject.toml b/pyproject.toml index 0f8ea59..e1a81c4 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -38,11 +38,11 @@ build-backend = "setuptools.build_meta" ] [project.urls] - Homepage = "https://github.ibm.com/lcarcaramo/CBExplorer/" - Documentation = "https://github.ibm.com/lcarcaramo/CBExplorer/" - Source = "https://github.ibm.com/lcarcaramo/CBExplorer/" - Issues = "https://github.ibm.com/lcarcaramo/CBExplorer/issues" - "Release Notes" = "https://github.ibm.com/lcarcaramo/CBExplorer/milestones?state=closed" + Homepage = "https://ambitus.github.io/cbxp/" + Documentation = "https://ambitus.github.io/cbxp/interfaces/python/" + Source = "https://github.com/ambitus/cbxp" + Issues = "https://github.com/ambitus/cbxp/issues" + "Release Notes" = "https://github.com/ambitus/cbxp/milestones?state=closed" [tool.setuptools.packages.find] where = ["python"] diff --git a/python/cbxp/_C.pyi b/python/cbxp/_C.pyi index e830ecb..d82dd77 100644 --- a/python/cbxp/_C.pyi +++ b/python/cbxp/_C.pyi @@ -1 +1 @@ -def call_cbxp(self, control_block: str, debug: bool=False) -> dict: ... +def call_cbxp(control_block: str, includes_string: str, debug: bool = False) -> dict: ... diff --git a/python/cbxp/__init__.py b/python/cbxp/__init__.py index d64ce22..99af136 100644 --- a/python/cbxp/__init__.py +++ b/python/cbxp/__init__.py @@ -1 +1,2 @@ from .cbxp import cbxp as cbxp +from .cbxp import CBXPError as CBXPError diff --git a/python/cbxp/cbxp.py b/python/cbxp/cbxp.py index ea1d890..bd37b4b 100644 --- a/python/cbxp/cbxp.py +++ b/python/cbxp/cbxp.py @@ -1,23 +1,39 @@ import json +from enum import Enum from cbxp._C import call_cbxp +class CBXPErrorCode(Enum): + """An enum of error and return codes from the cbxp interface""" + COMMA_IN_INCLUDE = -1 + BAD_CONTROL_BLOCK = 1 + BAD_INCLUDE = 2 + class CBXPError(Exception): """A class of errors for return codes from the cbxp interface""" - def __init__(self, return_code, control_block_name): + def __init__(self, return_code: int, control_block_name: str): self.rc = return_code match self.rc: - case -1: - message = f"control block '{control_block_name}' is unknown" + case CBXPErrorCode.COMMA_IN_INCLUDE.value: + message = "Include patterns 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 _: message = "an unknown error occurred" super().__init__(message) - - -def cbxp(control_block_name: str, debug: bool = False) -> dict: - response = call_cbxp(control_block_name, debug=debug) +def cbxp( + control_block: str, + includes: list[str] = [], + debug: bool = False +) -> dict: + 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) if response['return_code']: - raise CBXPError(response['return_code'], control_block_name) + raise CBXPError(response['return_code'], control_block) return json.loads(response['result_json']) diff --git a/setup.py b/setup.py index 32cdee0..5d5588f 100644 --- a/setup.py +++ b/setup.py @@ -1,5 +1,6 @@ """Build Python extension.""" +import os from glob import glob from setuptools import Extension, setup @@ -8,6 +9,10 @@ def main(): """Python extension build entrypoint.""" + os.environ["CC"] = "ibm-clang64" + os.environ["CFLAGS"] = "-std=c11" + os.environ["CXX"] = "ibm-clang++64" + os.environ["CXXFLAGS"] = "-std=c++17" setup_args = { "ext_modules": [ Extension( diff --git a/tests/test.py b/tests/test.py new file mode 100644 index 0000000..ac541ed --- /dev/null +++ b/tests/test.py @@ -0,0 +1,190 @@ +import unittest +from cbxp import cbxp, CBXPError + +class TestCBXP(unittest.TestCase): + + # ============================================================================ + # Basic Usage + # ============================================================================ + def test_cbxp_can_extract_psa(self): + cbdata = cbxp("psa") + self.assertIs(type(cbdata), dict) + + def test_cbxp_can_extract_cvt(self): + cbdata = cbxp("cvt") + self.assertIs(type(cbdata), dict) + + def test_cbxp_can_extract_ecvt(self): + cbdata = cbxp("ecvt") + self.assertIs(type(cbdata), dict) + + def test_cbxp_can_extract_asvt(self): + cbdata = cbxp("asvt") + self.assertIs(type(cbdata), dict) + + def test_cbxp_can_extract_ascb(self): + cbdata = cbxp("ascb") + self.assertIs(type(cbdata), list) + for entry in cbdata: + self.assertIs(type(entry), dict) + + # ============================================================================ + # Include Patterns + # ============================================================================ + def test_cbxp_can_extract_the_psa_and_include_the_cvt(self): + cbdata = cbxp("psa", includes=["cvt"]) + self.assertIs(type(cbdata), dict) + self.assertIs(type(cbdata["flccvt"]), dict) + + def test_cbxp_can_extract_the_cvt_and_include_the_ecvt(self): + cbdata = cbxp("cvt", includes=["ecvt"]) + self.assertIs(type(cbdata), dict) + self.assertIs(type(cbdata["cvtecvt"]), dict) + + def test_cbxp_can_extract_the_cvt_and_include_the_asvt(self): + cbdata = cbxp("cvt", includes=["asvt"]) + self.assertIs(type(cbdata), dict) + self.assertIs(type(cbdata["cvtasvt"]), dict) + self.assertIs(type(cbdata["cvtasvt"]["asvtenty"]), list) + for entry in cbdata["cvtasvt"]["asvtenty"]: + self.assertIs(type(entry), str) + + def test_cbxp_can_extract_the_asvt_and_include_the_ascb(self): + cbdata = cbxp("asvt", includes=["ascb"]) + self.assertIs(type(cbdata), dict) + self.assertIs(type(cbdata["asvtenty"]), list) + for entry in cbdata["asvtenty"]: + self.assertIs(type(entry), dict) + + def test_cbxp_can_extract_the_psa_and_include_the_cvt_ecvt(self): + cbdata = cbxp("psa", includes=["cvt.ecvt"]) + self.assertIs(type(cbdata), dict) + self.assertIs(type(cbdata["flccvt"]), dict) + self.assertIs(type(cbdata["flccvt"]["cvtecvt"]), dict) + + def test_cbxp_can_extract_the_psa_and_include_the_cvt_ecvt_ascb(self): + cbdata = cbxp("psa", includes=["cvt.asvt.ascb"]) + self.assertIs(type(cbdata), dict) + self.assertIs(type(cbdata["flccvt"]), dict) + self.assertIs(type(cbdata["flccvt"]["cvtasvt"]), dict) + self.assertIs(type(cbdata["flccvt"]["cvtasvt"]["asvtenty"]), list) + for entry in cbdata["flccvt"]["cvtasvt"]["asvtenty"]: + self.assertIs(type(entry), dict) + + def test_cbxp_can_extract_the_cvt_and_include_the_asvt_ascb(self): + cbdata = cbxp("cvt", includes=["asvt.ascb"]) + self.assertIs(type(cbdata), dict) + self.assertIs(type(cbdata["cvtasvt"]), dict) + self.assertIs(type(cbdata["cvtasvt"]["asvtenty"]), list) + for entry in cbdata["cvtasvt"]["asvtenty"]: + self.assertIs(type(entry), dict) + + def test_cbxp_include_can_extract_cvt_and_include_pattern_ecvt_and_asvt(self): + cbdata = cbxp("cvt", includes=["ecvt", "asvt"]) + self.assertIs(type(cbdata), dict) + self.assertIs(type(cbdata["cvtecvt"]), dict) + self.assertIs(type(cbdata["cvtasvt"]), dict) + self.assertIs(type(cbdata["cvtasvt"]["asvtenty"]), list) + for entry in cbdata["cvtasvt"]["asvtenty"]: + self.assertIs(type(entry), str) + + def test_cbxp_include_can_extract_psa_and_include_pattern_ecvt_asvt_and_cvt_asvt_ascb(self): + cbdata = cbxp("psa", includes=["cvt.ecvt", "cvt.asvt.ascb"]) + self.assertIs(type(cbdata), dict) + self.assertIs(type(cbdata["flccvt"]), dict) + self.assertIs(type(cbdata["flccvt"]["cvtecvt"]), dict) + self.assertIs(type(cbdata["flccvt"]["cvtasvt"]), dict) + self.assertIs(type(cbdata["flccvt"]["cvtasvt"]["asvtenty"]), list) + for entry in cbdata["flccvt"]["cvtasvt"]["asvtenty"]: + self.assertIs(type(entry), dict) + + def test_cbxp_can_extract_psa_and_include_cvt_recursive_wildcard(self): + cbdata = cbxp("psa", includes=["cvt.**"]) + self.assertIs(type(cbdata), dict) + self.assertIs(type(cbdata["flccvt"]), dict) + self.assertIs(type(cbdata["flccvt"]["cvtecvt"]), dict) + self.assertIs(type(cbdata["flccvt"]["cvtasvt"]), dict) + self.assertIs(type(cbdata["flccvt"]["cvtasvt"]["asvtenty"]), list) + for entry in cbdata["flccvt"]["cvtasvt"]["asvtenty"]: + self.assertIs(type(entry), dict) + + def test_cbxp_can_extract_psa_and_include_cvt_wildcard(self): + cbdata = cbxp("psa", includes=["cvt.*"]) + self.assertIs(type(cbdata), dict) + self.assertIs(type(cbdata["flccvt"]), dict) + self.assertIs(type(cbdata["flccvt"]["cvtecvt"]), dict) + self.assertIs(type(cbdata["flccvt"]["cvtasvt"]), dict) + self.assertIs(type(cbdata["flccvt"]["cvtasvt"]["asvtenty"]), list) + for entry in cbdata["flccvt"]["cvtasvt"]["asvtenty"]: + self.assertIs(type(entry), str) + + def test_cbxp_can_extract_cvt_and_include_wildcard_and_asvt_wildcard(self): + cbdata = cbxp("cvt", includes=["*", "asvt.*"]) + self.assertIs(type(cbdata), dict) + self.assertIs(type(cbdata["cvtecvt"]), dict) + self.assertIs(type(cbdata["cvtasvt"]), dict) + self.assertIs(type(cbdata["cvtasvt"]["asvtenty"]), list) + for entry in cbdata["cvtasvt"]["asvtenty"]: + self.assertIs(type(entry), dict) + + # ============================================================================ + # Debug Mode + # ============================================================================ + def test_cbxp_can_run_in_debug_mode(self): + cbdata = cbxp("psa", debug=True) + self.assertIs(type(cbdata), dict) + + # ============================================================================ + # Errors: Unknown Control Block + # ============================================================================ + def test_cbxp_raises_cbxp_error_when_unknown_control_block_is_provided(self): + with self.assertRaises(CBXPError) as e: + cbxp("unknown") + self.assertEqual("Unknown control block 'unknown' was specified.", str(e.exception)) + + # ============================================================================ + # Errors: Bad Include Patterns + # ============================================================================ + def test_cbxp_raises_cbxp_error_if_asvt_ascb_is_included_when_extracting_the_psa(self): + with self.assertRaises(CBXPError) as e: + cbxp("psa", includes=["asvt.ascb"]) + self.assertEqual("A bad include pattern was provided", str(e.exception)) + + def test_cbxp_raises_cbxp_error_if_ascb_is_included_when_extracting_the_psa(self): + with self.assertRaises(CBXPError) as e: + cbxp("psa", includes=["ascb"]) + self.assertEqual("A bad include pattern was provided", str(e.exception)) + + def test_cbxp_raises_cbxp_error_if_ecvt_is_included_when_extracting_the_ascb(self): + with self.assertRaises(CBXPError) as e: + cbxp("ascb", includes=["ecvt"]) + self.assertEqual("A bad include pattern was provided", str(e.exception)) + + def test_cbxp_raises_cbxp_error_if_ect_ecvt_and_cvt_ascb_is_included_when_extracting_the_psa(self): + with self.assertRaises(CBXPError) as e: + cbdata = cbxp("psa", includes=["cvt.ecvt", "cvt.ascb"]) + self.assertEqual("A bad include pattern was provided", str(e.exception)) + + def test_cbxp_raises_cbxp_error_if_ecvt_and_cvt_asvt_ascb_is_included_when_extracting_the_psa(self): + with self.assertRaises(CBXPError) as e: + cbdata = cbxp("psa", includes=["ecvt", "cvt.asvt.ascb"]) + self.assertEqual("A bad include pattern was provided", str(e.exception)) + + def test_cbxp_raises_cbxp_error_if_cvt_is_included_when_extracting_the_cvt(self): + with self.assertRaises(CBXPError) as e: + cbdata = cbxp("cvt", includes=["cvt"]) + self.assertEqual("A bad include pattern was provided", str(e.exception)) + + def test_cbxp_raises_cbxp_error_when_pattern_cannot_contain_comma_1(self): + with self.assertRaises(CBXPError) as e: + cbdata = cbxp("cvt", includes=["asvt,ascb"]) + self.assertEqual("Include patterns cannot contain commas", str(e.exception)) + + def test_cbxp_raises_cbxp_error_when_pattern_cannot_contain_comma_2(self): + with self.assertRaises(CBXPError) as e: + cbdata = cbxp("cvt", includes=["asvt,as"]) + self.assertEqual("Include patterns 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 new file mode 100755 index 0000000..b05ee54 --- /dev/null +++ b/tests/test.sh @@ -0,0 +1,71 @@ +#!/bin/bash + +run_with_expected_exit_code() { + expected_code=$1 + shift + echo "Running: $* (expected exit code: $expected_code)" + + "$@" > /dev/null + return_code=$? + + if [ $return_code -eq $expected_code ]; then + echo "Command exited with $return_code as expected." + else + echo "Unexpected exit code: got $return_code, expected $expected_code" + exit 1 + fi + echo +} + +# Basic Usage +run_with_expected_exit_code 0 ./dist/cbxp psa +run_with_expected_exit_code 0 ./dist/cbxp cvt +run_with_expected_exit_code 0 ./dist/cbxp ecvt +run_with_expected_exit_code 0 ./dist/cbxp asvt +run_with_expected_exit_code 0 ./dist/cbxp ascb +# Include Patterns +run_with_expected_exit_code 0 ./dist/cbxp -i cvt psa +run_with_expected_exit_code 0 ./dist/cbxp --include cvt psa +run_with_expected_exit_code 0 ./dist/cbxp -i ecvt cvt +run_with_expected_exit_code 0 ./dist/cbxp -i asvt cvt +run_with_expected_exit_code 0 ./dist/cbxp -i ascb asvt +run_with_expected_exit_code 0 ./dist/cbxp -i cvt.ecvt psa +run_with_expected_exit_code 0 ./dist/cbxp -i cvt.asvt.ascb psa +run_with_expected_exit_code 0 ./dist/cbxp -i asvt.ascb cvt +run_with_expected_exit_code 0 ./dist/cbxp -i ecvt -i asvt cvt +run_with_expected_exit_code 0 ./dist/cbxp -i cvt.ecvt -i cvt.asvt.ascb psa +run_with_expected_exit_code 0 ./dist/cbxp -i "cvt.**" psa +run_with_expected_exit_code 0 ./dist/cbxp -i "cvt.*" psa +run_with_expected_exit_code 0 ./dist/cbxp -i "asvt.*" -i "*" cvt +# Debug Mode +run_with_expected_exit_code 0 ./dist/cbxp -d psa +run_with_expected_exit_code 0 ./dist/cbxp --debug psa +# Show Usage +run_with_expected_exit_code 0 ./dist/cbxp -h +run_with_expected_exit_code 0 ./dist/cbxp --help +# Show Version +run_with_expected_exit_code 0 ./dist/cbxp -v +run_with_expected_exit_code 0 ./dist/cbxp --version + +# Errors: Bad Usage +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 +# Errors: Unknown Control Block +run_with_expected_exit_code 255 ./dist/cbxp unknown +# Errors: Bad Include Patterns +run_with_expected_exit_code 255 ./dist/cbxp -i asvt,ascb cvt +run_with_expected_exit_code 255 ./dist/cbxp -i asvt,as cvt +run_with_expected_exit_code 255 ./dist/cbxp -i asvt.ascb psa +run_with_expected_exit_code 255 ./dist/cbxp -i ascb psa +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 + +echo " -------------------------------- " +echo " -------------------------------- " +echo " All tests completed successfully" +echo " -------------------------------- " +echo " -------------------------------- " From 954478d556899aa935c972e3c78b385d503d4891 Mon Sep 17 00:00:00 2001 From: Elijah Swift Date: Tue, 6 Jan 2026 14:54:50 -0500 Subject: [PATCH 02/13] Squashed Commit Signed-off-by: Elijah Swift --- cbxp/cbxp.cpp | 6 +- cbxp/cbxp.h | 4 +- cbxp/control_block_error.hpp | 7 +- cbxp/control_block_explorer.cpp | 45 ++++----- cbxp/control_block_explorer.hpp | 7 +- cbxp/control_blocks/ascb.cpp | 19 +++- cbxp/control_blocks/ascb.hpp | 5 +- cbxp/control_blocks/assb.cpp | 11 ++- cbxp/control_blocks/assb.hpp | 5 +- cbxp/control_blocks/asvt.cpp | 12 ++- cbxp/control_blocks/asvt.hpp | 5 +- cbxp/control_blocks/control_block.cpp | 130 ++++++++++++++++++++++++++ cbxp/control_blocks/control_block.hpp | 11 ++- cbxp/control_blocks/cvt.cpp | 12 ++- cbxp/control_blocks/cvt.hpp | 5 +- cbxp/control_blocks/ecvt.cpp | 6 +- cbxp/control_blocks/ecvt.hpp | 5 +- cbxp/control_blocks/psa.cpp | 9 +- cbxp/control_blocks/psa.hpp | 5 +- cbxp/main.cpp | 39 +++++++- cbxp/python/_cbxp.c | 14 ++- python/cbxp/_C.pyi | 1 + python/cbxp/__init__.py | 1 - python/cbxp/cbxp.py | 28 +++++- tests/test.py | 53 +++++++++++ tests/test.sh | 13 +++ 26 files changed, 388 insertions(+), 70 deletions(-) diff --git a/cbxp/cbxp.cpp b/cbxp/cbxp.cpp index 5e8618f..959ae80 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* filter_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, filter_string); return &cbxp_result; } diff --git a/cbxp/cbxp.h b/cbxp/cbxp.h index 4e50bf7..f2db256 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* filter_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..e661f5a 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::createList( + const std::string& comma_separated_string) { + if (comma_separated_string == "") { return {}; } - std::vector includes = {}; + std::vector list = {}; Logger::getInstance().debug( - "Creating include list from the provided include list string: " + - includes_string); + "Creating 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); + 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); + list.push_back(entry); Logger::getInstance().debug("Done."); - return includes; + return 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::createList(includes_string); + std::vector filters = + ControlBlockExplorer::createList(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..dff3eae 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 createList( + 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 76f46d2..71e0fed 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 new_ascb = + ASCB::get(reinterpret_cast(*p_ascb_addr)); + if (!new_ascb.is_null()) { + ascbs.push_back(new_ascb); + } p_ascb_addr++; // This SHOULD increment the pointer by 4 bytes. } return ascbs; @@ -55,8 +59,11 @@ nlohmann::json ASCB::get(void* __ptr32 p_control_block) { for (const auto& [include, include_includes] : include_map_) { if (include == "assb") { - ascb_json["ascbassb"] = - CBXP::ASSB(include_includes).get(p_ascb->ascbassb); + ascb_json["ascbassb"] = CBXP::ASSB(include_includes, filter_map_[include]) + .get(p_ascb->ascbassb); + if (ascb_json["ascbassb"].is_null()) { + return {}; + } } } @@ -90,6 +97,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..c576c22 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 new_assb = + ASSB::get(reinterpret_cast(p_ascb->ascbassb)); + if (!new_assb.is_null()) assbs.push_back(new_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..6f49460 100644 --- a/cbxp/control_blocks/asvt.cpp +++ b/cbxp/control_blocks/asvt.cpp @@ -50,7 +50,7 @@ nlohmann::json ASVT::get(void* __ptr32 p_control_block) { for (const auto& [include, include_includes] : include_map_) { if (include == "ascb") { nlohmann::json ascbs_json; - CBXP::ASCB ascb(include_includes); + CBXP::ASCB ascb(include_includes, filter_map_[include]); 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..69c332f 100644 --- a/cbxp/control_blocks/control_block.cpp +++ b/cbxp/control_blocks/control_block.cpp @@ -1,11 +1,141 @@ #include "control_block.hpp" +#include + #include #include "control_block_error.hpp" #include "logger.hpp" namespace CBXP { +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 + bool control_block_in_inclusion = false; + for (const auto& [include, include_includes] : include_map_) { + if (control_block == include) { + control_block_in_inclusion = true; + } + } + if (!control_block_in_inclusion) { + Logger::getInstance().debug("'" + control_block + + "' is not specified in the inclusion list"); + throw FilterError(); + } + filter_map_[control_block].push_back(control_block_filter); + } else { + ControlBlock::processFilterValue(filter); + } + } + Logger::getInstance().debug("Done"); +} + +void ControlBlock::processFilterValue(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); + current_filters_[filter_key].push_back(filter_value); + current_filters_[filter_key].push_back(del); + return; + } + } +} + +bool ControlBlock::testValue(const nlohmann::json& json_value, + const std::string& filter_value, + const std::string& operation) { + std::string value_str; + try { + try { + int value_int = json_value.get(); + int filter_int = std::stoi(filter_value); + if (operation == "=") { + return value_int == filter_int; + } else if (operation == ">") { + return value_int > filter_int; + } else if (operation == "<") { + return value_int < filter_int; + } else if (operation == ">=") { + return value_int >= filter_int; + } else if (operation == "<=") { + return value_int <= filter_int; + } + } catch (...) { + value_str = json_value.get(); + if (value_str.substr(0, 2) == "0x") { + unsigned long value_ulong = std::stoul(value_str.substr(2)); + unsigned long filter_ulong = std::stoul(filter_value); + if (operation == "=") { + return value_ulong == filter_ulong; + } else if (operation == ">") { + return value_ulong > filter_ulong; + } else if (operation == "<") { + return value_ulong < filter_ulong; + } else if (operation == ">=") { + return value_ulong >= filter_ulong; + } else if (operation == "<=") { + return value_ulong <= filter_ulong; + } + } + } + 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); + std::transform(value_str.begin(), value_str.end(), value_str.begin(), + [](unsigned char c) { return std::tolower(c); }); + return (fnmatch(filter_value.c_str(), value_str.c_str(), 0) == 0); + } else { + Logger::getInstance().debug("Cannot use <,<=,> or >= with string filter"); + throw FilterError(); + } + } catch (...) { + Logger::getInstance().debug( + "Error with type conversions for filter evaluation"); + throw FilterError(); + } +} + +bool ControlBlock::matchFilter(nlohmann::json& control_block_json) { + 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)) { + for (size_t i = 0; i < filter_values.size(); i += 2) { + const std::string& filter_value = filter_values[i]; + const std::string& operation = filter_values[i + 1]; + if (!ControlBlock::testValue(control_block_json[filter_key], + filter_value, 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..."); diff --git a/cbxp/control_blocks/control_block.hpp b/cbxp/control_blocks/control_block.hpp index dcbcb3b..28f2ace 100644 --- a/cbxp/control_blocks/control_block.hpp +++ b/cbxp/control_blocks/control_block.hpp @@ -14,19 +14,28 @@ class ControlBlock { void processDoubleAsteriskInclude(); void processAsteriskInclude(); void processExplicitInclude(std::string& include); + void processFilterValue(const std::string& filter); + bool testValue(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> filter_map_; + std::unordered_map> current_filters_; + bool matchFilter(nlohmann::json& control_block_json); public: void createIncludeMap(const std::vector& includes); + void createFilterMap(const std::vector& filters); 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); + createFilterMap(filters); } virtual ~ControlBlock() = default; }; diff --git a/cbxp/control_blocks/cvt.cpp b/cbxp/control_blocks/cvt.cpp index 12107ad..922f639 100644 --- a/cbxp/control_blocks/cvt.cpp +++ b/cbxp/control_blocks/cvt.cpp @@ -45,9 +45,11 @@ nlohmann::json CVT::get(void* __ptr32 p_control_block) { for (const auto& [include, include_includes] : include_map_) { if (include == "asvt") { - cvt_json["cvtasvt"] = CBXP::ASVT(include_includes).get(p_cvtmap->cvtasvt); + cvt_json["cvtasvt"] = CBXP::ASVT(include_includes, filter_map_[include]) + .get(p_cvtmap->cvtasvt); } else if (include == "ecvt") { - cvt_json["cvtecvt"] = CBXP::ECVT(include_includes).get(p_cvtmap->cvtecvt); + cvt_json["cvtecvt"] = CBXP::ECVT(include_includes, filter_map_[include]) + .get(p_cvtmap->cvtecvt); } } @@ -139,6 +141,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..4530197 100644 --- a/cbxp/control_blocks/psa.cpp +++ b/cbxp/control_blocks/psa.cpp @@ -30,7 +30,8 @@ nlohmann::json PSA::get(void* __ptr32 p_control_block) { for (const auto& [include, include_includes] : include_map_) { if (include == "cvt") { - psa_json["flccvt"] = CBXP::CVT(include_includes).get(p_psa->flccvt); + psa_json["flccvt"] = + CBXP::CVT(include_includes, filter_map_[include]).get(p_psa->flccvt); } } @@ -61,6 +62,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..64a1eb8 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" @@ -67,7 +71,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,7 +96,12 @@ 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); @@ -109,6 +119,17 @@ 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 (filters_string == "") { + filters_string = filter; + } else { + filters_string += "," + filter; + } } else { if (i != argc - 1) { show_usage(argv); @@ -125,8 +146,10 @@ int main(int argc, const char* argv[]) { nlohmann::json control_block_json; + size_t null_length = strlen("null\0"); 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 +158,14 @@ 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 if (cbxp_result->result_json_length == null_length && + filters_string != "") { + std::cerr << "No control block was found that matched the provided filter" + << 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/__init__.py b/python/cbxp/__init__.py index c17b34a..448e3f8 100644 --- a/python/cbxp/__init__.py +++ b/python/cbxp/__init__.py @@ -1,3 +1,2 @@ from .cbxp import CBXPError as CBXPError from .cbxp import cbxp as cbxp -from .cbxp import CBXPError as CBXPError diff --git a/python/cbxp/cbxp.py b/python/cbxp/cbxp.py index 49e35e7..115eedb 100644 --- a/python/cbxp/cbxp.py +++ b/python/cbxp/cbxp.py @@ -8,8 +8,11 @@ class CBXPErrorCode(Enum): """An enum of error and return codes from the cbxp interface""" COMMA_IN_INCLUDE = -1 + COMMA_IN_FILTER = -2 + NO_FILTER_MATCH = -3 BAD_CONTROL_BLOCK = 1 BAD_INCLUDE = 2 + BAD_CONTROL_BLOCK_FILTER = 3 class CBXPError(Exception): @@ -20,10 +23,16 @@ 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 CBXPErrorCode.NO_FILTER_MATCH.value: + message = "No control block was found that matched the provided filter" case _: message = "an unknown error occurred" super().__init__(message) @@ -32,14 +41,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).lower(), + ",".join(control_block_filters).lower(), + debug=debug, + ) if response["return_code"]: raise CBXPError(response["return_code"], control_block) + if response["result_json"] == "null" and control_block_filters != []: + raise CBXPError(CBXPErrorCode.NO_FILTER_MATCH.value, control_block) return json.loads(response["result_json"]) diff --git a/tests/test.py b/tests/test.py index 7d3351a..9d4260a 100644 --- a/tests/test.py +++ b/tests/test.py @@ -169,6 +169,29 @@ 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.assbjbni=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.assbjbni=MASTER"], + includes=["cvt.asvt.ascb.assb"], + ) + self.assertIs(type(cbdata), dict) + # ============================================================================ # Debug Mode # ============================================================================ @@ -236,6 +259,36 @@ 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.assbjbni=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_no_filter_match( + self, + ): + with self.assertRaises(CBXPError) as e: + cbxp("psa", control_block_filter=["psapsa=PSB"]) + self.assertEqual( + "No control block was found that matched the provided filter", + 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..f41ad7c 100755 --- a/tests/test.sh +++ b/tests/test.sh @@ -44,6 +44,11 @@ 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 cvt.asvt.ascb.assb.assbjbni=master -i "**" psa +run_with_expected_exit_code 0 ./dist/cbxp -f cvt.asvt.ascb.assb.assbjbni=master -i cvt.asvt.ascb.assb psa + # Debug Mode run_with_expected_exit_code 0 ./dist/cbxp -d psa run_with_expected_exit_code 0 ./dist/cbxp --debug psa @@ -59,6 +64,9 @@ 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 # Errors: Unknown Control Block run_with_expected_exit_code 255 ./dist/cbxp unknown # Errors: Bad Include Patterns @@ -70,6 +78,11 @@ 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=junk psa +run_with_expected_exit_code 255 ./dist/cbxp -f junk=fakeval cvt +run_with_expected_exit_code 255 ./dist/cbxp -i asvt -f junk.jsonkey=fakeval cvt + echo " -------------------------------- " echo " -------------------------------- " From 1d9179550dc3d00d0cb093a47705cd5368bfe037 Mon Sep 17 00:00:00 2001 From: Elijah Swift Date: Wed, 7 Jan 2026 14:22:12 -0500 Subject: [PATCH 03/13] change to ensure filter check fails empty lists Signed-off-by: Elijah Swift --- cbxp/control_blocks/ascb.cpp | 2 +- cbxp/control_blocks/control_block.cpp | 7 +++++++ tests/test.py | 6 +++--- tests/test.sh | 5 +++-- 4 files changed, 14 insertions(+), 6 deletions(-) diff --git a/cbxp/control_blocks/ascb.cpp b/cbxp/control_blocks/ascb.cpp index 71e0fed..795f7c4 100644 --- a/cbxp/control_blocks/ascb.cpp +++ b/cbxp/control_blocks/ascb.cpp @@ -72,7 +72,7 @@ nlohmann::json ASCB::get(void* __ptr32 p_control_block) { sizeof(struct ascb)); ascb_json["ascbascb"] = formatter_.getString(p_ascb->ascbascb, 4); - ascb_json["ascbasid"] = formatter_.getBitmap(p_ascb->ascbasid); + ascb_json["ascbasid"] = p_ascb->ascbasn; ascb_json["ascbdcti"] = p_ascb->ascbdcti; ascb_json["ascbejst"] = formatter_.getBitmap( reinterpret_cast(&p_ascb->ascbejst)); diff --git a/cbxp/control_blocks/control_block.cpp b/cbxp/control_blocks/control_block.cpp index 69c332f..f354ae2 100644 --- a/cbxp/control_blocks/control_block.cpp +++ b/cbxp/control_blocks/control_block.cpp @@ -112,6 +112,13 @@ bool ControlBlock::testValue(const nlohmann::json& json_value, } 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; diff --git a/tests/test.py b/tests/test.py index 9d4260a..d2e3a46 100644 --- a/tests/test.py +++ b/tests/test.py @@ -179,7 +179,7 @@ def test_cbxp_can_use_basic_filter(self): def test_cbxp_can_use_include_filter_with_wildcard_include(self): cbdata = cbxp( "psa", - control_block_filter=["cvt.asvt.ascb.assb.assbjbni=MASTER"], + control_block_filter=["cvt.asvt.ascb.assb.assbjbns=*MASTER*"], includes=["**"], ) self.assertIs(type(cbdata), dict) @@ -187,7 +187,7 @@ def test_cbxp_can_use_include_filter_with_wildcard_include(self): def test_cbxp_can_use_include_filter_with_explicit_include(self): cbdata = cbxp( "psa", - control_block_filter=["cvt.asvt.ascb.assb.assbjbni=MASTER"], + control_block_filter=["cvt.asvt.ascb.assb.assbjbns=*MASTER*"], includes=["cvt.asvt.ascb.assb"], ) self.assertIs(type(cbdata), dict) @@ -268,7 +268,7 @@ def test_cbxp_raises_cbxp_error_if_filter_uses_non_included_control_block( with self.assertRaises(CBXPError) as e: cbxp( "psa", - control_block_filter=["cvt.asvt.ascb.assb.assbjbni=MASTER"], + control_block_filter=["cvt.asvt.ascb.assb.assbjbns=*MASTER*"], ) self.assertEqual("A bad filter was provided", str(e.exception)) diff --git a/tests/test.sh b/tests/test.sh index f41ad7c..e758483 100755 --- a/tests/test.sh +++ b/tests/test.sh @@ -46,8 +46,8 @@ 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 cvt.asvt.ascb.assb.assbjbni=master -i "**" psa -run_with_expected_exit_code 0 ./dist/cbxp -f cvt.asvt.ascb.assb.assbjbni=master -i cvt.asvt.ascb.assb 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 # Debug Mode run_with_expected_exit_code 0 ./dist/cbxp -d psa @@ -82,6 +82,7 @@ run_with_expected_exit_code 255 ./dist/cbxp -i cvt cvt run_with_expected_exit_code 255 ./dist/cbxp -f psapsa=junk psa run_with_expected_exit_code 255 ./dist/cbxp -f junk=fakeval 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 echo " -------------------------------- " From 1388e38beea4bf1400f04bfdf55363ff110cd431 Mon Sep 17 00:00:00 2001 From: Elijah Swift Date: Wed, 7 Jan 2026 15:18:27 -0500 Subject: [PATCH 04/13] More robust unit testing Signed-off-by: Elijah Swift --- tests/test.py | 120 ++++++++++++++++++++++++++++++++++++++++++++++++++ tests/test.sh | 15 +++++++ 2 files changed, 135 insertions(+) diff --git a/tests/test.py b/tests/test.py index d2e3a46..df453c4 100644 --- a/tests/test.py +++ b/tests/test.py @@ -192,6 +192,99 @@ def test_cbxp_can_use_include_filter_with_explicit_include(self): ) 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_use_ulong_filter_with_equals(self): + cbdata = cbxp( + "cvt", + control_block_filter=["cvtasmvt=88000000"], + ) + self.assertIs(type(cbdata), dict) + + def test_cbxp_can_use_ulong_filter_with_greater_than(self): + cbdata = cbxp( + "cvt", + control_block_filter=["cvtasmvt>87FFFFFF"], + ) + self.assertIs(type(cbdata), dict) + + def test_cbxp_can_use_ulong_filter_with_less_than(self): + cbdata = cbxp( + "cvt", + control_block_filter=["cvtasmvt<88000001"], + ) + self.assertIs(type(cbdata), dict) + + def test_cbxp_can_use_ulong_filter_with_greater_than_or_equals(self): + cbdata = cbxp( + "cvt", + control_block_filter=["cvtasmvt>=87FFFFFF"], + ) + self.assertIs(type(cbdata), dict) + + def test_cbxp_can_use_ulong_filter_with_less_than_or_equals(self): + cbdata = cbxp( + "cvt", + control_block_filter=["cvtasmvt<=88000000"], + ) + self.assertIs(type(cbdata), dict) + # ============================================================================ # Debug Mode # ============================================================================ @@ -272,6 +365,33 @@ def test_cbxp_raises_cbxp_error_if_filter_uses_non_included_control_block( ) 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, ): diff --git a/tests/test.sh b/tests/test.sh index e758483..76d1ede 100755 --- a/tests/test.sh +++ b/tests/test.sh @@ -48,6 +48,18 @@ run_with_expected_exit_code 0 ./dist/cbxp -i cvt.ecvt -i cvt.asvt.ascb.assb psa run_with_expected_exit_code 0 ./dist/cbxp -f psapsa=psa 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*,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<88000001" cvt +run_with_expected_exit_code 0 ./dist/cbxp -f "cvtasmvt=88000000" cvt +run_with_expected_exit_code 0 ./dist/cbxp -f "cvtasmvt>87FFFFFF" cvt +run_with_expected_exit_code 0 ./dist/cbxp -f "cvtasmvt<=88000000" cvt +run_with_expected_exit_code 0 ./dist/cbxp -f "cvtasmvt>=87FFFFFF" cvt # Debug Mode run_with_expected_exit_code 0 ./dist/cbxp -d psa @@ -67,6 +79,7 @@ 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 55 ./dist/cbxp --debug -d psa # Errors: Unknown Control Block run_with_expected_exit_code 255 ./dist/cbxp unknown # Errors: Bad Include Patterns @@ -83,6 +96,8 @@ run_with_expected_exit_code 255 ./dist/cbxp -f psapsa=junk psa run_with_expected_exit_code 255 ./dist/cbxp -f junk=fakeval 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 " -------------------------------- " From a6b42c9596c6107b2ebb4a0abf02e703c05de9f8 Mon Sep 17 00:00:00 2001 From: Elijah Swift Date: Wed, 7 Jan 2026 15:26:19 -0500 Subject: [PATCH 05/13] trailing commas in python Signed-off-by: Elijah Swift --- tests/test.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/test.py b/tests/test.py index df453c4..95aa218 100644 --- a/tests/test.py +++ b/tests/test.py @@ -196,7 +196,7 @@ 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" + "cvt.asvt.ascb.assb.assbjbns=*master*,cvt.asvt.ascb.ascbasid>0", ], includes=["cvt.asvt.ascb.assb"], ) @@ -373,7 +373,7 @@ def test_cbxp_raises_cbxp_error_if_one_of_two_filters_fails( "psa", includes=["**"], control_block_filter=[ - "cvt.asvt.ascb.assb.assbjbns=*master*,cvt.asvt.ascb.ascbasid=2" + "cvt.asvt.ascb.assb.assbjbns=*master*,cvt.asvt.ascb.ascbasid=2", ], ) self.assertEqual( From ee8fd479b8851f0adf2a4874c783b0d6a940d564 Mon Sep 17 00:00:00 2001 From: Elijah Swift Date: Wed, 14 Jan 2026 17:08:27 -0500 Subject: [PATCH 06/13] PR Comments Signed-off-by: Elijah Swift --- cbxp/cbxp.cpp | 4 +- cbxp/cbxp.h | 2 +- cbxp/control_block_explorer.cpp | 16 ++-- cbxp/control_block_explorer.hpp | 2 +- cbxp/control_blocks/ascb.cpp | 6 +- cbxp/control_blocks/assb.cpp | 4 +- cbxp/control_blocks/control_block.cpp | 129 +++++++++++++------------- cbxp/control_blocks/control_block.hpp | 30 ++++-- cbxp/main.cpp | 19 ++-- python/cbxp/cbxp.py | 9 +- tests/test.py | 41 ++++---- tests/test.sh | 16 ++-- 12 files changed, 151 insertions(+), 127 deletions(-) diff --git a/cbxp/cbxp.cpp b/cbxp/cbxp.cpp index 959ae80..48b897e 100644 --- a/cbxp/cbxp.cpp +++ b/cbxp/cbxp.cpp @@ -8,7 +8,7 @@ #include "control_block_explorer.hpp" cbxp_result_t* cbxp(const char* control_block_name, const char* includes_string, - const char* filter_string, bool debug) { + const char* filters_string, bool debug) { nlohmann::json control_block_json; std::string control_block = control_block_name; @@ -17,7 +17,7 @@ cbxp_result_t* cbxp(const char* control_block_name, const char* includes_string, CBXP::ControlBlockExplorer explorer = CBXP::ControlBlockExplorer(&cbxp_result, debug); - explorer.exploreControlBlock(control_block, includes_string, filter_string); + explorer.exploreControlBlock(control_block, includes_string, filters_string); return &cbxp_result; } diff --git a/cbxp/cbxp.h b/cbxp/cbxp.h index f2db256..c52baf3 100644 --- a/cbxp/cbxp.h +++ b/cbxp/cbxp.h @@ -8,7 +8,7 @@ extern "C" { #endif cbxp_result_t* cbxp(const char* control_block_name, const char* includes_string, - const char* filter_string, bool debug); + const char* filters_string, bool debug); #ifdef __cplusplus } diff --git a/cbxp/control_block_explorer.cpp b/cbxp/control_block_explorer.cpp index e661f5a..d8a6331 100644 --- a/cbxp/control_block_explorer.cpp +++ b/cbxp/control_block_explorer.cpp @@ -17,16 +17,16 @@ namespace CBXP { -std::vector ControlBlockExplorer::createList( +std::vector ControlBlockExplorer::createOptionsList( const std::string& comma_separated_string) { if (comma_separated_string == "") { return {}; } - std::vector list = {}; + std::vector options_list = {}; Logger::getInstance().debug( - "Creating list from the provided comma-separated list string: " + + "Creating options list from the provided comma-separated list string: " + comma_separated_string); const std::string del = ","; @@ -37,15 +37,15 @@ std::vector ControlBlockExplorer::createList( while (pos != std::string::npos) { entry = comma_separated_string.substr(index, pos); - list.push_back(entry); + options_list.push_back(entry); index += pos + 1; pos = comma_separated_string.substr(index, std::string::npos).find(del); } entry = comma_separated_string.substr(index, pos); - list.push_back(entry); + options_list.push_back(entry); Logger::getInstance().debug("Done."); - return list; + return options_list; } ControlBlockExplorer::ControlBlockExplorer(cbxp_result_t* p_result, @@ -68,9 +68,9 @@ void ControlBlockExplorer::exploreControlBlock( const std::string& control_block_name, const std::string& includes_string, const std::string& filters_string) { std::vector includes = - ControlBlockExplorer::createList(includes_string); + ControlBlockExplorer::createOptionsList(includes_string); std::vector filters = - ControlBlockExplorer::createList(filters_string); + ControlBlockExplorer::createOptionsList(filters_string); Logger::getInstance().debug("Extracting '" + control_block_name + "' control block data..."); diff --git a/cbxp/control_block_explorer.hpp b/cbxp/control_block_explorer.hpp index dff3eae..aca843e 100644 --- a/cbxp/control_block_explorer.hpp +++ b/cbxp/control_block_explorer.hpp @@ -9,7 +9,7 @@ namespace CBXP { class ControlBlockExplorer { private: cbxp_result_t* p_result_; - static std::vector createList( + static std::vector createOptionsList( const std::string& comma_separated_string); public: diff --git a/cbxp/control_blocks/ascb.cpp b/cbxp/control_blocks/ascb.cpp index 795f7c4..2c6dbeb 100644 --- a/cbxp/control_blocks/ascb.cpp +++ b/cbxp/control_blocks/ascb.cpp @@ -42,10 +42,10 @@ nlohmann::json ASCB::get(void* __ptr32 p_control_block) { p_ascb_addr++; continue; } - nlohmann::json new_ascb = + nlohmann::json next_ascb = ASCB::get(reinterpret_cast(*p_ascb_addr)); - if (!new_ascb.is_null()) { - ascbs.push_back(new_ascb); + if (!next_ascb.is_null()) { + ascbs.push_back(next_ascb); } p_ascb_addr++; // This SHOULD increment the pointer by 4 bytes. } diff --git a/cbxp/control_blocks/assb.cpp b/cbxp/control_blocks/assb.cpp index c576c22..a548d72 100644 --- a/cbxp/control_blocks/assb.cpp +++ b/cbxp/control_blocks/assb.cpp @@ -46,9 +46,9 @@ nlohmann::json ASSB::get(void* __ptr32 p_control_block) { // const struct ascb* __ptr32 p_ascb = reinterpret_cast(*p_ascb_addr); - nlohmann::json new_assb = + nlohmann::json next_assb = ASSB::get(reinterpret_cast(p_ascb->ascbassb)); - if (!new_assb.is_null()) assbs.push_back(new_assb); + if (!next_assb.is_null()) assbs.push_back(next_assb); p_ascb_addr++; // This SHOULD increment the pointer by 4 bytes. } return assbs; diff --git a/cbxp/control_blocks/control_block.cpp b/cbxp/control_blocks/control_block.cpp index f354ae2..e53f29f 100644 --- a/cbxp/control_blocks/control_block.cpp +++ b/cbxp/control_blocks/control_block.cpp @@ -8,6 +8,12 @@ #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..."); @@ -23,7 +29,7 @@ void ControlBlock::createFilterMap(const std::vector& filters) { // Check to make sure we are including the specified control block bool control_block_in_inclusion = false; - for (const auto& [include, include_includes] : include_map_) { + for (const auto& [include, options] : options_map_) { if (control_block == include) { control_block_in_inclusion = true; } @@ -33,15 +39,15 @@ void ControlBlock::createFilterMap(const std::vector& filters) { "' is not specified in the inclusion list"); throw FilterError(); } - filter_map_[control_block].push_back(control_block_filter); + options_map_[control_block].filters.push_back(control_block_filter); } else { - ControlBlock::processFilterValue(filter); + ControlBlock::addCurrentFilter(filter); } } Logger::getInstance().debug("Done"); } -void ControlBlock::processFilterValue(const std::string& filter) { +void ControlBlock::addCurrentFilter(const std::string& filter) { std::string filter_key, filter_value; std::vector delimeters = {"<=", ">=", "<", ">", "="}; for (std::string del : delimeters) { @@ -50,59 +56,56 @@ void ControlBlock::processFilterValue(const std::string& filter) { // 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); - current_filters_[filter_key].push_back(filter_value); - current_filters_[filter_key].push_back(del); + 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::testValue(const nlohmann::json& json_value, - const std::string& filter_value, - const std::string& operation) { - std::string value_str; +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 { - int value_int = json_value.get(); - int filter_int = std::stoi(filter_value); - if (operation == "=") { - return value_int == filter_int; - } else if (operation == ">") { - return value_int > filter_int; - } else if (operation == "<") { - return value_int < filter_int; - } else if (operation == ">=") { - return value_int >= filter_int; - } else if (operation == "<=") { - return value_int <= filter_int; - } + 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") { - unsigned long value_ulong = std::stoul(value_str.substr(2)); - unsigned long filter_ulong = std::stoul(filter_value); - if (operation == "=") { - return value_ulong == filter_ulong; - } else if (operation == ">") { - return value_ulong > filter_ulong; - } else if (operation == "<") { - return value_ulong < filter_ulong; - } else if (operation == ">=") { - return value_ulong >= filter_ulong; - } else if (operation == "<=") { - return value_ulong <= filter_ulong; - } + value_uint = std::stoul(value_str, nullptr, 0); + filter_uint = std::stoul(filter_value, nullptr, 0); } } - 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); - std::transform(value_str.begin(), value_str.end(), value_str.begin(), - [](unsigned char c) { return std::tolower(c); }); - return (fnmatch(filter_value.c_str(), value_str.c_str(), 0) == 0); - } else { - Logger::getInstance().debug("Cannot use <,<=,> or >= with string filter"); - throw FilterError(); + 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( @@ -125,11 +128,11 @@ bool ControlBlock::matchFilter(nlohmann::json& control_block_json) { } for (const auto& [filter_key, filter_values] : current_filters_) { if (control_block_json.contains(filter_key)) { - for (size_t i = 0; i < filter_values.size(); i += 2) { - const std::string& filter_value = filter_values[i]; - const std::string& operation = filter_values[i + 1]; - if (!ControlBlock::testValue(control_block_json[filter_key], - filter_value, operation)) { + // 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; } } @@ -161,26 +164,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 = {}; } } @@ -202,25 +205,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 28f2ace..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,28 +24,28 @@ class ControlBlock { void processDoubleAsteriskInclude(); void processAsteriskInclude(); void processExplicitInclude(std::string& include); - void processFilterValue(const std::string& filter); - bool testValue(const nlohmann::json& json_value, - const std::string& filter_value, const std::string& operation); + 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> filter_map_; - std::unordered_map> current_filters_; + 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); - void createFilterMap(const std::vector& filters); 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& filters) : control_block_name_(name), includables_(includables) { - createIncludeMap(includes); - createFilterMap(filters); + createOptionsMap(includes, filters); } virtual ~ControlBlock() = default; }; diff --git a/cbxp/main.cpp b/cbxp/main.cpp index 64a1eb8..5fc2497 100644 --- a/cbxp/main.cpp +++ b/cbxp/main.cpp @@ -55,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); @@ -108,9 +113,7 @@ int main(int argc, const char* 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); } @@ -125,6 +128,10 @@ int main(int argc, const char* 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 { @@ -146,7 +153,6 @@ int main(int argc, const char* argv[]) { nlohmann::json control_block_json; - size_t null_length = strlen("null\0"); const cbxp_result_t* cbxp_result = cbxp(control_block_name.c_str(), includes_string.c_str(), filters_string.c_str(), debug); @@ -161,11 +167,6 @@ int main(int argc, const char* argv[]) { } 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 if (cbxp_result->result_json_length == null_length && - filters_string != "") { - std::cerr << "No control block was found that matched the provided filter" - << std::endl; - cleanup_and_exit(-1, lib_handle); } else { std::cout << cbxp_result->result_json << std::endl; } diff --git a/python/cbxp/cbxp.py b/python/cbxp/cbxp.py index 115eedb..87ce2c5 100644 --- a/python/cbxp/cbxp.py +++ b/python/cbxp/cbxp.py @@ -9,7 +9,6 @@ class CBXPErrorCode(Enum): COMMA_IN_INCLUDE = -1 COMMA_IN_FILTER = -2 - NO_FILTER_MATCH = -3 BAD_CONTROL_BLOCK = 1 BAD_INCLUDE = 2 BAD_CONTROL_BLOCK_FILTER = 3 @@ -31,8 +30,6 @@ def __init__(self, return_code: int, control_block_name: str): message = "A bad include pattern was provided" case CBXPErrorCode.BAD_CONTROL_BLOCK_FILTER.value: message = "A bad filter was provided" - case CBXPErrorCode.NO_FILTER_MATCH.value: - message = "No control block was found that matched the provided filter" case _: message = "an unknown error occurred" super().__init__(message) @@ -60,12 +57,12 @@ def cbxp( response = call_cbxp( control_block.lower(), - ",".join(includes).lower(), - ",".join(control_block_filters).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 != []: - raise CBXPError(CBXPErrorCode.NO_FILTER_MATCH.value, control_block) + return None return json.loads(response["result_json"]) diff --git a/tests/test.py b/tests/test.py index 95aa218..59fa6b3 100644 --- a/tests/test.py +++ b/tests/test.py @@ -250,41 +250,46 @@ def test_cbxp_can_use_int_filter_less_than_or_equals(self): ) self.assertIs(type(cbdata), dict) - def test_cbxp_can_use_ulong_filter_with_equals(self): + def test_cbxp_can_decimal_filter_for_hex_field_with_equals(self): cbdata = cbxp( "cvt", - control_block_filter=["cvtasmvt=88000000"], + control_block_filter=["cvtasmvt=2281701376"], ) self.assertIs(type(cbdata), dict) - def test_cbxp_can_use_ulong_filter_with_greater_than(self): + def test_cbxp_can_use_hex_filter_with_greater_than(self): cbdata = cbxp( "cvt", - control_block_filter=["cvtasmvt>87FFFFFF"], + control_block_filter=["cvtasmvt>0x87FFFFFF"], ) self.assertIs(type(cbdata), dict) - def test_cbxp_can_use_ulong_filter_with_less_than(self): + def test_cbxp_can_use_hex_filter_with_less_than(self): cbdata = cbxp( "cvt", - control_block_filter=["cvtasmvt<88000001"], + control_block_filter=["cvtasmvt<0x88000001"], ) self.assertIs(type(cbdata), dict) - def test_cbxp_can_use_ulong_filter_with_greater_than_or_equals(self): + def test_cbxp_can_use_hex_filter_with_greater_than_or_equals(self): cbdata = cbxp( "cvt", - control_block_filter=["cvtasmvt>=87FFFFFF"], + control_block_filter=["cvtasmvt>=0x87FFFFFF"], ) self.assertIs(type(cbdata), dict) - def test_cbxp_can_use_ulong_filter_with_less_than_or_equals(self): + def test_cbxp_can_use_hex_filter_with_less_than_or_equals(self): cbdata = cbxp( "cvt", - control_block_filter=["cvtasmvt<=88000000"], + 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 # ============================================================================ @@ -399,15 +404,19 @@ def test_cbxp_raises_cbxp_error_if_filter_uses_unknown_key( cbxp("psa", control_block_filter=["psapsb=PSA"]) self.assertEqual("A bad filter was provided", str(e.exception)) - def test_cbxp_raises_cbxp_error_if_no_filter_match( + def test_cbxp_raises_cbxp_error_if_filter_passes_null_value( self, ): with self.assertRaises(CBXPError) as e: - cbxp("psa", control_block_filter=["psapsa=PSB"]) - self.assertEqual( - "No control block was found that matched the provided filter", - str(e.exception), - ) + 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__": diff --git a/tests/test.sh b/tests/test.sh index 76d1ede..a562a0b 100755 --- a/tests/test.sh +++ b/tests/test.sh @@ -46,6 +46,7 @@ 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 @@ -55,11 +56,11 @@ run_with_expected_exit_code 0 ./dist/cbxp -f "cvt.asvt.ascb.ascbasid>0" -i "**" 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<88000001" cvt -run_with_expected_exit_code 0 ./dist/cbxp -f "cvtasmvt=88000000" cvt -run_with_expected_exit_code 0 ./dist/cbxp -f "cvtasmvt>87FFFFFF" cvt -run_with_expected_exit_code 0 ./dist/cbxp -f "cvtasmvt<=88000000" cvt -run_with_expected_exit_code 0 ./dist/cbxp -f "cvtasmvt>=87FFFFFF" cvt +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 @@ -79,7 +80,7 @@ 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 55 ./dist/cbxp --debug -d 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 @@ -92,8 +93,9 @@ 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=junk psa +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 From a2222704d76125695ef18eb458cb136e4e70ec1c Mon Sep 17 00:00:00 2001 From: Elijah Swift Date: Wed, 14 Jan 2026 17:20:22 -0500 Subject: [PATCH 07/13] Complete PR Comments Signed-off-by: Elijah Swift --- cbxp/control_blocks/ascb.cpp | 7 ++++--- cbxp/control_blocks/asvt.cpp | 4 ++-- cbxp/control_blocks/control_block.cpp | 9 ++------- cbxp/control_blocks/cvt.cpp | 12 +++++++----- cbxp/control_blocks/psa.cpp | 5 +++-- 5 files changed, 18 insertions(+), 19 deletions(-) diff --git a/cbxp/control_blocks/ascb.cpp b/cbxp/control_blocks/ascb.cpp index 2c6dbeb..f575129 100644 --- a/cbxp/control_blocks/ascb.cpp +++ b/cbxp/control_blocks/ascb.cpp @@ -57,10 +57,11 @@ 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, filter_map_[include]) - .get(p_ascb->ascbassb); + ascb_json["ascbassb"] = + CBXP::ASSB(cbxp_options.include_patterns, cbxp_options.filters) + .get(p_ascb->ascbassb); if (ascb_json["ascbassb"].is_null()) { return {}; } diff --git a/cbxp/control_blocks/asvt.cpp b/cbxp/control_blocks/asvt.cpp index 6f49460..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, filter_map_[include]); + 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++) { diff --git a/cbxp/control_blocks/control_block.cpp b/cbxp/control_blocks/control_block.cpp index e53f29f..10dc251 100644 --- a/cbxp/control_blocks/control_block.cpp +++ b/cbxp/control_blocks/control_block.cpp @@ -28,13 +28,8 @@ void ControlBlock::createFilterMap(const std::vector& filters) { std::string control_block = filter.substr(0, del_pos); // Check to make sure we are including the specified control block - bool control_block_in_inclusion = false; - for (const auto& [include, options] : options_map_) { - if (control_block == include) { - control_block_in_inclusion = true; - } - } - if (!control_block_in_inclusion) { + 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(); diff --git a/cbxp/control_blocks/cvt.cpp b/cbxp/control_blocks/cvt.cpp index 922f639..792ed95 100644 --- a/cbxp/control_blocks/cvt.cpp +++ b/cbxp/control_blocks/cvt.cpp @@ -43,13 +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, filter_map_[include]) - .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, filter_map_[include]) - .get(p_cvtmap->cvtecvt); + cvt_json["cvtecvt"] = + CBXP::ECVT(cbxp_options.include_patterns, cbxp_options.filters) + .get(p_cvtmap->cvtecvt); } } diff --git a/cbxp/control_blocks/psa.cpp b/cbxp/control_blocks/psa.cpp index 4530197..7d281d2 100644 --- a/cbxp/control_blocks/psa.cpp +++ b/cbxp/control_blocks/psa.cpp @@ -28,10 +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, filter_map_[include]).get(p_psa->flccvt); + CBXP::CVT(cbxp_options.include_patterns, cbxp_options.filters) + .get(p_psa->flccvt); } } From edae8070e52466fb1629fd1cb944e7ffae80c46d Mon Sep 17 00:00:00 2001 From: Elijah Swift Date: Wed, 14 Jan 2026 17:23:14 -0500 Subject: [PATCH 08/13] Fix an error in tests Signed-off-by: Elijah Swift --- cbxp/control_blocks/control_block.cpp | 3 +++ tests/test.sh | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/cbxp/control_blocks/control_block.cpp b/cbxp/control_blocks/control_block.cpp index 10dc251..6ea2503 100644 --- a/cbxp/control_blocks/control_block.cpp +++ b/cbxp/control_blocks/control_block.cpp @@ -107,6 +107,9 @@ bool ControlBlock::compare(const nlohmann::json& json_value, "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) { diff --git a/tests/test.sh b/tests/test.sh index a562a0b..5a1734a 100755 --- a/tests/test.sh +++ b/tests/test.sh @@ -50,7 +50,7 @@ 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*,cvt.asvt.ascb.ascbasid>0" -i "**" 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 From 563adfad8e0145ac048f3ff1e66b03bfc44dbc89 Mon Sep 17 00:00:00 2001 From: Elijah Swift Date: Thu, 15 Jan 2026 10:06:37 -0500 Subject: [PATCH 09/13] Fix another error in tests Signed-off-by: Elijah Swift --- cbxp/control_blocks/control_block.cpp | 1 + tests/test.sh | 1 - 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/cbxp/control_blocks/control_block.cpp b/cbxp/control_blocks/control_block.cpp index 6ea2503..5aafdf9 100644 --- a/cbxp/control_blocks/control_block.cpp +++ b/cbxp/control_blocks/control_block.cpp @@ -77,6 +77,7 @@ bool ControlBlock::compare(const nlohmann::json& json_value, if (value_str.substr(0, 2) == "0x") { value_uint = std::stoul(value_str, nullptr, 0); filter_uint = std::stoul(filter_value, nullptr, 0); + value_str = ""; } } if (value_str != "") { diff --git a/tests/test.sh b/tests/test.sh index 5a1734a..0d8b79c 100755 --- a/tests/test.sh +++ b/tests/test.sh @@ -97,7 +97,6 @@ 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 From c90292f8a5a6729a9371e6854edb26d121dfeb73 Mon Sep 17 00:00:00 2001 From: Elijah Swift Date: Thu, 15 Jan 2026 10:12:04 -0500 Subject: [PATCH 10/13] Streamline control block parameters Signed-off-by: Elijah Swift --- cbxp/control_block_explorer.cpp | 19 +++++++++---------- cbxp/control_blocks/ascb.hpp | 5 ++--- cbxp/control_blocks/assb.hpp | 5 ++--- cbxp/control_blocks/asvt.hpp | 5 ++--- cbxp/control_blocks/control_block.hpp | 5 ++--- cbxp/control_blocks/cvt.hpp | 5 ++--- cbxp/control_blocks/ecvt.hpp | 5 ++--- cbxp/control_blocks/psa.hpp | 5 ++--- 8 files changed, 23 insertions(+), 31 deletions(-) diff --git a/cbxp/control_block_explorer.cpp b/cbxp/control_block_explorer.cpp index d8a6331..e044d26 100644 --- a/cbxp/control_block_explorer.cpp +++ b/cbxp/control_block_explorer.cpp @@ -67,10 +67,9 @@ ControlBlockExplorer::ControlBlockExplorer(cbxp_result_t* p_result, void ControlBlockExplorer::exploreControlBlock( const std::string& control_block_name, const std::string& includes_string, const std::string& filters_string) { - std::vector includes = - ControlBlockExplorer::createOptionsList(includes_string); - std::vector filters = - ControlBlockExplorer::createOptionsList(filters_string); + cbxp_options_t cbxp_options = { + ControlBlockExplorer::createOptionsList(includes_string), + ControlBlockExplorer::createOptionsList(filters_string)}; Logger::getInstance().debug("Extracting '" + control_block_name + "' control block data..."); @@ -78,17 +77,17 @@ void ControlBlockExplorer::exploreControlBlock( nlohmann::json control_block_json; try { if (control_block_name == "psa") { - control_block_json = PSA(includes, filters).get(); + control_block_json = PSA(cbxp_options).get(); } else if (control_block_name == "cvt") { - control_block_json = CVT(includes, filters).get(); + control_block_json = CVT(cbxp_options).get(); } else if (control_block_name == "ecvt") { - control_block_json = ECVT(includes, filters).get(); + control_block_json = ECVT(cbxp_options).get(); } else if (control_block_name == "ascb") { - control_block_json = ASCB(includes, filters).get(); + control_block_json = ASCB(cbxp_options).get(); } else if (control_block_name == "asvt") { - control_block_json = ASVT(includes, filters).get(); + control_block_json = ASVT(cbxp_options).get(); } else if (control_block_name == "assb") { - control_block_json = ASSB(includes, filters).get(); + control_block_json = ASSB(cbxp_options).get(); } else { throw ControlBlockError(); } diff --git a/cbxp/control_blocks/ascb.hpp b/cbxp/control_blocks/ascb.hpp index 85a6c86..c658db9 100644 --- a/cbxp/control_blocks/ascb.hpp +++ b/cbxp/control_blocks/ascb.hpp @@ -8,9 +8,8 @@ namespace CBXP { class ASCB : public ControlBlock { public: nlohmann::json get(void* __ptr32 p_control_block = nullptr) override; - explicit ASCB(const std::vector& includes, - const std::vector& filters) - : ControlBlock("ascb", {"assb"}, includes, filters) {} + explicit ASCB(const cbxp_options_t& cbxp_options) + : ControlBlock("ascb", {"assb"}, cbxp_options) {} }; } // namespace CBXP diff --git a/cbxp/control_blocks/assb.hpp b/cbxp/control_blocks/assb.hpp index b376755..5046d8f 100644 --- a/cbxp/control_blocks/assb.hpp +++ b/cbxp/control_blocks/assb.hpp @@ -8,9 +8,8 @@ namespace CBXP { class ASSB : public ControlBlock { public: nlohmann::json get(void* __ptr32 p_control_block = nullptr) override; - explicit ASSB(const std::vector& includes, - const std::vector& filters) - : ControlBlock("assb", {}, includes, filters) {} + explicit ASSB(const cbxp_options_t& cbxp_options) + : ControlBlock("assb", {}, cbxp_options) {} }; } // namespace CBXP diff --git a/cbxp/control_blocks/asvt.hpp b/cbxp/control_blocks/asvt.hpp index 94eb933..15268bb 100644 --- a/cbxp/control_blocks/asvt.hpp +++ b/cbxp/control_blocks/asvt.hpp @@ -33,9 +33,8 @@ namespace CBXP { class ASVT : public ControlBlock { public: nlohmann::json get(void* __ptr32 p_control_block = nullptr) override; - explicit ASVT(const std::vector& includes, - const std::vector& filters) - : ControlBlock("asvt", {"ascb"}, includes, filters) {} + explicit ASVT(const cbxp_options_t& cbxp_options) + : ControlBlock("asvt", {"ascb"}, cbxp_options) {} }; } // namespace CBXP diff --git a/cbxp/control_blocks/control_block.hpp b/cbxp/control_blocks/control_block.hpp index 9525de9..2e22b7b 100644 --- a/cbxp/control_blocks/control_block.hpp +++ b/cbxp/control_blocks/control_block.hpp @@ -42,10 +42,9 @@ class ControlBlock { 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& filters) + const cbxp_options_t& cbxp_options) : control_block_name_(name), includables_(includables) { - createOptionsMap(includes, filters); + createOptionsMap(cbxp_options.include_patterns, cbxp_options.filters); } virtual ~ControlBlock() = default; }; diff --git a/cbxp/control_blocks/cvt.hpp b/cbxp/control_blocks/cvt.hpp index 209bf76..e65e3c7 100644 --- a/cbxp/control_blocks/cvt.hpp +++ b/cbxp/control_blocks/cvt.hpp @@ -8,9 +8,8 @@ namespace CBXP { class CVT : public ControlBlock { public: nlohmann::json get(void* __ptr32 p_control_block = nullptr) override; - explicit CVT(const std::vector& includes, - const std::vector& filters) - : ControlBlock("cvt", {"ecvt", "asvt"}, includes, filters) {} + explicit CVT(const cbxp_options_t& cbxp_options) + : ControlBlock("cvt", {"ecvt", "asvt"}, cbxp_options) {} }; } // namespace CBXP diff --git a/cbxp/control_blocks/ecvt.hpp b/cbxp/control_blocks/ecvt.hpp index 27cb502..ff452b3 100644 --- a/cbxp/control_blocks/ecvt.hpp +++ b/cbxp/control_blocks/ecvt.hpp @@ -8,9 +8,8 @@ namespace CBXP { class ECVT : public ControlBlock { public: nlohmann::json get(void* __ptr32 p_control_block = nullptr) override; - explicit ECVT(const std::vector& includes, - const std::vector& filters) - : ControlBlock("ecvt", {}, includes, filters) {} + explicit ECVT(const cbxp_options_t& cbxp_options) + : ControlBlock("ecvt", {}, cbxp_options) {} }; } // namespace CBXP #endif diff --git a/cbxp/control_blocks/psa.hpp b/cbxp/control_blocks/psa.hpp index d80870b..d53b53d 100644 --- a/cbxp/control_blocks/psa.hpp +++ b/cbxp/control_blocks/psa.hpp @@ -8,9 +8,8 @@ namespace CBXP { class PSA : public ControlBlock { public: nlohmann::json get(void* __ptr32 p_control_block = nullptr) override; - explicit PSA(const std::vector& includes, - const std::vector& filters) - : ControlBlock("psa", {"cvt"}, includes, filters) {} + explicit PSA(const cbxp_options_t& cbxp_options) + : ControlBlock("psa", {"cvt"}, cbxp_options) {} }; } // namespace CBXP #endif From a36a245c0824c7a48bfb93409c6ebbe20b8c25e1 Mon Sep 17 00:00:00 2001 From: Elijah Swift Date: Thu, 15 Jan 2026 10:14:31 -0500 Subject: [PATCH 11/13] Streamline control block parameters part 2 Signed-off-by: Elijah Swift --- cbxp/control_blocks/ascb.cpp | 4 +--- cbxp/control_blocks/asvt.cpp | 2 +- cbxp/control_blocks/cvt.cpp | 8 ++------ cbxp/control_blocks/psa.cpp | 4 +--- 4 files changed, 5 insertions(+), 13 deletions(-) diff --git a/cbxp/control_blocks/ascb.cpp b/cbxp/control_blocks/ascb.cpp index f575129..93f7c60 100644 --- a/cbxp/control_blocks/ascb.cpp +++ b/cbxp/control_blocks/ascb.cpp @@ -59,9 +59,7 @@ nlohmann::json ASCB::get(void* __ptr32 p_control_block) { for (const auto& [include, cbxp_options] : options_map_) { if (include == "assb") { - ascb_json["ascbassb"] = - CBXP::ASSB(cbxp_options.include_patterns, cbxp_options.filters) - .get(p_ascb->ascbassb); + ascb_json["ascbassb"] = CBXP::ASSB(cbxp_options).get(p_ascb->ascbassb); if (ascb_json["ascbassb"].is_null()) { return {}; } diff --git a/cbxp/control_blocks/asvt.cpp b/cbxp/control_blocks/asvt.cpp index 82a5a24..907356d 100644 --- a/cbxp/control_blocks/asvt.cpp +++ b/cbxp/control_blocks/asvt.cpp @@ -50,7 +50,7 @@ nlohmann::json ASVT::get(void* __ptr32 p_control_block) { for (const auto& [include, cbxp_options] : options_map_) { if (include == "ascb") { nlohmann::json ascbs_json; - CBXP::ASCB ascb(cbxp_options.include_patterns, cbxp_options.filters); + CBXP::ASCB ascb(cbxp_options); uint32_t* __ptr32 p_ascb_addr = const_cast( reinterpret_cast(&p_asvt->asvtenty)); for (int i = 0; i < p_asvt->asvtmaxu; i++) { diff --git a/cbxp/control_blocks/cvt.cpp b/cbxp/control_blocks/cvt.cpp index 792ed95..c3697bf 100644 --- a/cbxp/control_blocks/cvt.cpp +++ b/cbxp/control_blocks/cvt.cpp @@ -45,13 +45,9 @@ nlohmann::json CVT::get(void* __ptr32 p_control_block) { for (const auto& [include, cbxp_options] : options_map_) { if (include == "asvt") { - cvt_json["cvtasvt"] = - CBXP::ASVT(cbxp_options.include_patterns, cbxp_options.filters) - .get(p_cvtmap->cvtasvt); + cvt_json["cvtasvt"] = CBXP::ASVT(cbxp_options).get(p_cvtmap->cvtasvt); } else if (include == "ecvt") { - cvt_json["cvtecvt"] = - CBXP::ECVT(cbxp_options.include_patterns, cbxp_options.filters) - .get(p_cvtmap->cvtecvt); + cvt_json["cvtecvt"] = CBXP::ECVT(cbxp_options).get(p_cvtmap->cvtecvt); } } diff --git a/cbxp/control_blocks/psa.cpp b/cbxp/control_blocks/psa.cpp index 7d281d2..310af43 100644 --- a/cbxp/control_blocks/psa.cpp +++ b/cbxp/control_blocks/psa.cpp @@ -30,9 +30,7 @@ nlohmann::json PSA::get(void* __ptr32 p_control_block) { for (const auto& [include, cbxp_options] : options_map_) { if (include == "cvt") { - psa_json["flccvt"] = - CBXP::CVT(cbxp_options.include_patterns, cbxp_options.filters) - .get(p_psa->flccvt); + psa_json["flccvt"] = CBXP::CVT(cbxp_options).get(p_psa->flccvt); } } From 2c3d1ae217eb1437083cfdd8359c01a4f9937a91 Mon Sep 17 00:00:00 2001 From: Elijah Swift Date: Thu, 15 Jan 2026 10:27:23 -0500 Subject: [PATCH 12/13] Fix python tests Signed-off-by: Elijah Swift --- tests/test.py | 75 +++++++++++++++++++++++++-------------------------- tests/test.sh | 8 +++--- 2 files changed, 41 insertions(+), 42 deletions(-) diff --git a/tests/test.py b/tests/test.py index 59fa6b3..e753980 100644 --- a/tests/test.py +++ b/tests/test.py @@ -173,13 +173,13 @@ def test_cbxp_can_extract_cvt_and_include_wildcard_and_asvt_recursive_wildcard( # Filters # ============================================================================ def test_cbxp_can_use_basic_filter(self): - cbdata = cbxp("psa", control_block_filter=["psapsa=PSA"]) + cbdata = cbxp("psa", control_block_filters=["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*"], + control_block_filters=["cvt.asvt.ascb.assb.assbjbns=*MASTER*"], includes=["**"], ) self.assertIs(type(cbdata), dict) @@ -187,7 +187,7 @@ def test_cbxp_can_use_include_filter_with_wildcard_include(self): def test_cbxp_can_use_include_filter_with_explicit_include(self): cbdata = cbxp( "psa", - control_block_filter=["cvt.asvt.ascb.assb.assbjbns=*MASTER*"], + control_block_filters=["cvt.asvt.ascb.assb.assbjbns=*MASTER*"], includes=["cvt.asvt.ascb.assb"], ) self.assertIs(type(cbdata), dict) @@ -195,8 +195,9 @@ def test_cbxp_can_use_include_filter_with_explicit_include(self): 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", + control_block_filters=[ + "cvt.asvt.ascb.assb.assbjbns=*MASTER*", + "cvt.asvt.ascb.ascbasid>0", ], includes=["cvt.asvt.ascb.assb"], ) @@ -205,7 +206,7 @@ def test_cbxp_can_use_multiple_include_filters(self): def test_cbxp_can_use_wildcard_filter_with_string(self): cbdata = cbxp( "psa", - control_block_filter=["cvt.asvt.ascb.assb.assbjbns=?mas?er?"], + control_block_filters=["cvt.asvt.ascb.assb.assbjbns=?MAS?ER?"], includes=["cvt.asvt.ascb.assb"], ) self.assertIs(type(cbdata), dict) @@ -213,7 +214,7 @@ def test_cbxp_can_use_wildcard_filter_with_string(self): def test_cbxp_can_use_int_filter_equals(self): cbdata = cbxp( "psa", - control_block_filter=["cvt.asvt.ascb.ascbasid=1"], + control_block_filters=["cvt.asvt.ascb.ascbasid=1"], includes=["cvt.asvt.ascb.assb"], ) self.assertIs(type(cbdata), dict) @@ -221,7 +222,7 @@ def test_cbxp_can_use_int_filter_equals(self): def test_cbxp_can_use_int_filter_greater_than(self): cbdata = cbxp( "psa", - control_block_filter=["cvt.asvt.ascb.ascbasid>0"], + control_block_filters=["cvt.asvt.ascb.ascbasid>0"], includes=["cvt.asvt.ascb.assb"], ) self.assertIs(type(cbdata), dict) @@ -229,7 +230,7 @@ def test_cbxp_can_use_int_filter_greater_than(self): def test_cbxp_can_use_int_filter_less_than(self): cbdata = cbxp( "psa", - control_block_filter=["cvt.asvt.ascb.ascbasid<2"], + control_block_filters=["cvt.asvt.ascb.ascbasid<2"], includes=["cvt.asvt.ascb.assb"], ) self.assertIs(type(cbdata), dict) @@ -237,7 +238,7 @@ def test_cbxp_can_use_int_filter_less_than(self): def test_cbxp_can_use_int_filter_greater_than_or_equals(self): cbdata = cbxp( "psa", - control_block_filter=["cvt.asvt.ascb.ascbasid<=2"], + control_block_filters=["cvt.asvt.ascb.ascbasid<=2"], includes=["cvt.asvt.ascb.assb"], ) self.assertIs(type(cbdata), dict) @@ -245,7 +246,7 @@ def test_cbxp_can_use_int_filter_greater_than_or_equals(self): def test_cbxp_can_use_int_filter_less_than_or_equals(self): cbdata = cbxp( "psa", - control_block_filter=["cvt.asvt.ascb.ascbasid>=1"], + control_block_filters=["cvt.asvt.ascb.ascbasid>=1"], includes=["cvt.asvt.ascb.assb"], ) self.assertIs(type(cbdata), dict) @@ -253,42 +254,56 @@ def test_cbxp_can_use_int_filter_less_than_or_equals(self): def test_cbxp_can_decimal_filter_for_hex_field_with_equals(self): cbdata = cbxp( "cvt", - control_block_filter=["cvtasmvt=2281701376"], + control_block_filters=["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"], + control_block_filters=["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"], + control_block_filters=["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"], + control_block_filters=["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"], + control_block_filters=["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"])) + self.assertIsNone(cbxp("psa", control_block_filters=["psapsa=PSB"])) + + def test_cbxp_returns_none_if_one_of_two_filters_fails( + self, + ): + self.assertIsNone( + cbxp( + "psa", + includes=["**"], + control_block_filters=[ + "cvt.asvt.ascb.assb.assbjbns=*MASTER*", + "cvt.asvt.ascb.ascbasid=2", + ], + ) + ) # ============================================================================ # Debug Mode @@ -366,26 +381,10 @@ def test_cbxp_raises_cbxp_error_if_filter_uses_non_included_control_block( with self.assertRaises(CBXPError) as e: cbxp( "psa", - control_block_filter=["cvt.asvt.ascb.assb.assbjbns=*MASTER*"], + control_block_filters=["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, ): @@ -393,7 +392,7 @@ def test_cbxp_raises_cbxp_error_if_non_equality_filter_used_with_string( cbxp( "psa", includes=["**"], - control_block_filter=["cvt.asvt.ascb.assb.assbjbns<*master*"], + control_block_filters=["cvt.asvt.ascb.assb.assbjbns<*master*"], ) self.assertEqual("A bad filter was provided", str(e.exception)) @@ -401,21 +400,21 @@ 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"]) + cbxp("psa", control_block_filters=["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="]) + cbxp("psa", control_block_filters=["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"]) + cbxp("psa", control_block_filters=["psapsa=PSA,cvt.cvtasmvt<88000001"]) self.assertEqual("Filters cannot contain commas", str(e.exception)) diff --git a/tests/test.sh b/tests/test.sh index 0d8b79c..7efbcfd 100755 --- a/tests/test.sh +++ b/tests/test.sh @@ -47,10 +47,10 @@ 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.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 From cc9e32f4d8d5e4dbf88bfcc483a5f612810b09cd Mon Sep 17 00:00:00 2001 From: Elijah Swift Date: Thu, 15 Jan 2026 10:29:49 -0500 Subject: [PATCH 13/13] Fix python tests to ruff standards Signed-off-by: Elijah Swift --- tests/test.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test.py b/tests/test.py index e753980..1ad9334 100644 --- a/tests/test.py +++ b/tests/test.py @@ -302,7 +302,7 @@ def test_cbxp_returns_none_if_one_of_two_filters_fails( "cvt.asvt.ascb.assb.assbjbns=*MASTER*", "cvt.asvt.ascb.ascbasid=2", ], - ) + ), ) # ============================================================================