diff --git a/.github/workflows/clang-format.yml b/.github/workflows/clang-format.yml index dab77c7..8d3e58d 100644 --- a/.github/workflows/clang-format.yml +++ b/.github/workflows/clang-format.yml @@ -8,13 +8,13 @@ jobs: steps: - uses: actions/checkout@v4 - name: Install clang-format - run: sudo apt install -y cmake clang-format-19 + run: sudo apt install -y cmake clang-format-20 - name: clang-format - # Create symbolic link to clang-format-19 and + # Create symbolic link to clang-format-20 and # add it to PATH so that 'clang-format' can - # be used to run 'clang-format-19'. + # be used to run 'clang-format-20'. run: | - ln -s $(which clang-format-19) clang-format + ln -s $(which clang-format-20) clang-format export PATH=$PWD:$PATH cmake . make lint diff --git a/CMakeLists.txt b/CMakeLists.txt index 282dbd0..6bd2eba 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,7 +1,7 @@ cmake_minimum_required(VERSION 3.15) -project(CBXP VERSION 0.0.3 - DESCRIPTION "A unified and standardized interface for extracting z/OS control block data." +project(CBXP VERSION 0.0.4 + DESCRIPTION "A unified and standardized interface for extracting and formatting z/OS control block data." LANGUAGES C CXX) set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "./dist") @@ -39,12 +39,15 @@ endif() # ============================================================================ file( GLOB CBXP_SRC_LIB - "cbxp/cbxp.cpp" - "cbxp/control_block_explorer.cpp" - "cbxp/logger.cpp" + "cbxp/*.cpp" "cbxp/control_blocks/*.cpp" ) +file( + GLOB CBXP_SRC_CLI + "cbxp/cli/*.cpp" +) + file( GLOB CBXP_SRC_ALL "cbxp/*.cpp" @@ -77,7 +80,7 @@ target_link_options(libcbxp PUBLIC ${LINK_OPTIONS}) # ============================================================================ # Build '/bin/cbxp' # ============================================================================ -add_executable(cbxp "cbxp/main.cpp") +add_executable(cbxp ${CBXP_SRC_CLI}) target_link_libraries(cbxp libcbxp) @@ -92,36 +95,32 @@ target_compile_options(cbxp PUBLIC ${COMPILE_OPTIONS}) target_link_options(cbxp PUBLIC ${LINK_OPTIONS}) # ============================================================================ -# Packaging +# Install # ============================================================================ -add_custom_target( - package - COMMAND "mkdir" - "-p" - "cbxp-${CMAKE_PROJECT_VERSION}/bin" - "cbxp-${CMAKE_PROJECT_VERSION}/lib" - "cbxp-${CMAKE_PROJECT_VERSION}/include" - COMMAND "cp" - "LICENSE" - "cbxp-${CMAKE_PROJECT_VERSION}/" - COMMAND "cp" - "NOTICES" - "cbxp-${CMAKE_PROJECT_VERSION}/" - COMMAND "cp" - "./dist/cbxp" - "cbxp-${CMAKE_PROJECT_VERSION}/bin/" - COMMAND "cp" - "./dist/libcbxp.a" - "cbxp-${CMAKE_PROJECT_VERSION}/lib/" - COMMAND "cp" - "./cbxp/cbxp.h" - "cbxp-${CMAKE_PROJECT_VERSION}/include/" - COMMAND "pax" - "-x" "pax" - "-wzvf" - "./dist/cbxp-${CMAKE_PROJECT_VERSION}.pax.Z" - "cbxp-${CMAKE_PROJECT_VERSION}/*" - DEPENDS libcbxp cbxp +if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT) + SET(CMAKE_INSTALL_PREFIX "~/cbxp-${CMAKE_PROJECT_VERSION}" CACHE PATH "install path" FORCE) +endif(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT) + +include(GNUInstallDirs) + +install( + FILES "LICENSE" "NOTICES" + DESTINATION ${CMAKE_INSTALL_PREFIX} +) + +install( + TARGETS cbxp + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} +) + +install( + TARGETS libcbxp + ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} +) + +install( + FILES "./cbxp/cbxp.h" + DESTINATION ${CMAKE_INSTALL_INCLUDEDIR} ) # ============================================================================ diff --git a/Jenkinsfile b/Jenkinsfile index 281a168..be7bbe3 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -89,13 +89,13 @@ pipeline { stage('Lint') { steps { echo "Linting with clang-format ..." - sh "gmake lint" + sh "cmake --build . --target lint --parallel" } } stage('Cppcheck') { steps { echo "Running cppcheck ..." - sh "gmake check" + sh "cmake --build . --target check --parallel" } } stage('Create Python Distribution Metadata') { @@ -118,7 +118,7 @@ pipeline { echo "Building '${wheel}' and '${tar}' ..." sh """ - ${python} -m pip install build>=1.3.0 + ${python} -m pip install build>=1.5.0 ${python} -m build """ @@ -139,25 +139,17 @@ pipeline { clean_python_environment() clean_git_repo() } - // Shell/C/C++ pax distribution + // CLI/C/C++ distribution def cbxp_version = get_cbxp_version() - def pax = "cbxp-${cbxp_version}.pax.Z" - echo "Building '${pax}' ..." + echo "Installing testing CBXP '${cbxp_version}' ..." sh """ - cmake . - gmake package - """ - - echo "Install testing '${pax}' ..." - sh """ - mkdir install-test - cd install-test - pax -rf ../dist/${pax} - ls -alT cbxp-${cbxp_version}/* + cmake . --install-prefix ${env.WORKSPACE}/install-test + cmake --build . --parallel + cmake --install . """ echo "'Function testing './dist/cbxp' ..." - sh "gmake test" + sh "cmake --build . --target test --parallel" clean_git_repo() } @@ -213,7 +205,9 @@ def create_python_executables_and_wheels_map(python_versions) { "cbxp-${cbxp_version}-cp3${python_version}-cp3${python_version}-zos.whl" ), "wheelPublish": ( - "cbxp-${cbxp_version}-cp3${python_version}-cp3${python_version}-zos.whl" + // New wheel naming convention does not work with PyPi so we + // do still need to rename the wheel file. + "cbxp-${cbxp_version}-cp3${python_version}-none-any.whl" ), "tarPublish": "cbxp-${cbxp_version}.tar.gz" ] @@ -334,14 +328,11 @@ def publish( echo "Building '${wheel_default}' ..." sh """ - ${python} -m pip install build>=1.2.2 + ${python} -m pip install build>=1.5.0 ${python} -m build -w """ - // Rename wheel file if the old naming convention is being used - if (wheel_default != wheel_publish) { - sh "mv ./dist/${wheel_default} ./dist/${wheel_publish}" - } + sh "mv ./dist/${wheel_default} ./dist/${wheel_publish}" if (tar_built == false) { tar_publish = python_executables_and_wheels_map[python]["tarPublish"] @@ -354,29 +345,31 @@ def publish( upload_asset(release_id, wheel_publish) echo "Adding sha256 checksum for '${wheel_publish}' to ${checksums_file}..." - sh "cd dist && sha256sum -t ${wheel_publish} >> ${checksums_file}" + sh "cd dist && sha256sum ${wheel_publish} >> ${checksums_file}" } echo "Uploading '${tar_publish}' to '${release_title}' GitHub release ..." upload_asset(release_id, tar_publish) echo "Adding sha256 checksum for '${tar_publish}' to ${checksums_file}..." - sh "cd dist && sha256sum -t ${tar_publish} >> ${checksums_file}" + sh "cd dist && sha256sum ${tar_publish} >> ${checksums_file}" - // Build and publish Shell/C/C++ interface pax + // Build and publish CLI/C/C++ interface pax def cbxp_version = get_cbxp_version() def pax = "cbxp-${cbxp_version}.pax.Z" echo "Building '${pax}' ..." sh """ - cmake . - gmake package + cmake . --install-prefix ${env.WORKSPACE}/cbxp-${cbxp_version} + cmake --build . --parallel + cmake --install . + pax -x pax -wzvf ./dist/${pax} cbxp-${cbxp_version}/* """ echo "Uploading '${pax}' to '${release_title}' GitHub release ..." upload_asset(release_id, pax) echo "Adding sha256 checksum for '${pax}' to ${checksums_file}..." - sh "cd dist && sha256sum -t ${pax} >> ${checksums_file}" + sh "cd dist && sha256sum ${pax} >> ${checksums_file}" echo "Uploading '${checksums_file}' to '${release_title}' GitHub release ..." upload_asset(release_id, checksums_file) @@ -422,7 +415,7 @@ def build_description(python_executables_and_wheels_map, release_tag, release_no + "> :warning: _Requires z/OS Open XL C/C++ 2.2 compiler._\\n" + "```\\ncurl -O -L https://github.com/ambitus/cbxp/releases/download/${release_tag}/${tar} " + "&& python3 -m pip install ${tar}\\n```\\n" - + "## Shell/C/C++ Interface Installation\\n" + + "## CLI/C/C++ Interface Installation\\n" + "```\\ncurl -O -L https://github.com/ambitus/cbxp/releases/download/${release_tag}/${pax} " + "&& pax -rf ${pax}\\n```\\n" ) diff --git a/README.md b/README.md index 8066e67..b9ac010 100644 --- a/README.md +++ b/README.md @@ -9,18 +9,18 @@ # CBXP (Control Block EXPlorer) -A unified and standardized interface for extracting z/OS control block data. +A unified and standardized interface for extracting and formatting 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 ubiquitous 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 interfaces 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 ubiquitous 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 and format 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 block data and post processing it 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 interfaces 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 implemented 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. ## Getting Started ### Minimum z/OS & Language Versions -Currently, CBXP is being developed on **z/OS 3.1**. We hope to eventually support all z/OS versions that are fully supported by IBM. +Currently, CBXP is being developed on **z/OS 3.2**. We hope to eventually support all z/OS versions that are fully supported by IBM. * [z/OS Product Lifecycle](https://www.ibm.com/support/pages/lifecycle/search/?q=5655-ZOS,%205650-ZOS) All versions of the **IBM Open Enterprise SDK for Python** that are fully supported by IBM are supported by CBXP. @@ -33,12 +33,12 @@ All versions of the **IBM Open Enterprise SDK for Python** that are fully suppor ### 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) -* [CLI Interface](https://ambitus.github.io/cbxp/interfaces/shell) +* [CLI Interface](https://ambitus.github.io/cbxp/interfaces/cli) * [C/C++ Interface](https://ambitus.github.io/cbxp/interfaces/c_cpp) ### Supported Control Blocks -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. +Currently, CBXP only has support for extracting and formatting a handful of **System-Level Control Blocks**. 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 4aa2e0d..2ae70e0 100644 --- a/cbxp/cbxp.cpp +++ b/cbxp/cbxp.cpp @@ -1,28 +1,54 @@ #include "cbxp.h" -#include -#include #include #include "control_block_explorer.hpp" #include "logger.hpp" -cbxp_result_t* cbxp(const char* control_block, const char* includes_string, - const char* filters_string, bool debug) { - nlohmann::json control_block_json; +cbxp_result_t* cbxp_extract(const char* control_block_name, + const size_t control_block_name_length, + const char* includes, const size_t includes_length, + const char* filters, const size_t filters_length, + bool debug) { + std::string control_block_name_string; + std::string includes_string_string; + std::string filters_string_string; - std::string control_block_cpp_string; - std::string includes_string_cpp_string; - std::string filters_string_cpp_string; - - if (control_block != nullptr) { - control_block_cpp_string = control_block; + if (control_block_name != nullptr) { + control_block_name_string.assign(control_block_name, + control_block_name_length); + } + if (includes != nullptr) { + includes_string_string.assign(includes, includes_length); } - if (includes_string != nullptr) { - includes_string_cpp_string = includes_string; + if (filters != nullptr) { + filters_string_string.assign(filters, filters_length); } - if (filters_string != nullptr) { - filters_string_cpp_string = filters_string; + + CBXP::Logger::getInstance().setDebug(debug); + + cbxp_result_t* p_cbxp_result = new cbxp_result_t(); + CBXP::Logger::getInstance().debugAllocate(p_cbxp_result, 64, + sizeof(cbxp_result_t)); + + CBXP::ControlBlockExplorer explorer = + CBXP::ControlBlockExplorer(p_cbxp_result); + + explorer.extractControlBlock(control_block_name_string, + includes_string_string, filters_string_string); + + return p_cbxp_result; +} + +cbxp_result_t* cbxp_format(const char* control_block_name, + const size_t control_block_name_length, + const void* data, const size_t data_length, + bool debug) { + std::string control_block_name_string; + + if (control_block_name != nullptr) { + control_block_name_string.assign(control_block_name, + control_block_name_length); } CBXP::Logger::getInstance().setDebug(debug); @@ -34,9 +60,7 @@ cbxp_result_t* cbxp(const char* control_block, const char* includes_string, CBXP::ControlBlockExplorer explorer = CBXP::ControlBlockExplorer(p_cbxp_result); - explorer.exploreControlBlock(control_block_cpp_string, - includes_string_cpp_string, - filters_string_cpp_string); + explorer.formatControlBlock(control_block_name_string, data, data_length); return p_cbxp_result; } diff --git a/cbxp/cbxp.h b/cbxp/cbxp.h index 6ce0e1b..359c16b 100644 --- a/cbxp/cbxp.h +++ b/cbxp/cbxp.h @@ -2,19 +2,29 @@ #define __CBXP_H_ #ifdef __cplusplus +#include extern "C" { #else #include +#include #endif typedef struct { char* result_json; int result_json_length; - int return_code; + unsigned int return_code; } cbxp_result_t; -cbxp_result_t* cbxp(const char* control_block, const char* includes_string, - const char* filters_string, bool debug); +cbxp_result_t* cbxp_extract(const char* control_block_name, + const size_t control_block_name_length, + const char* includes, const size_t includes_length, + const char* filters, const size_t filters_length, + bool debug); + +cbxp_result_t* cbxp_format(const char* control_block_name, + const size_t control_block_name_length, + const void* data, const size_t data_length, + bool debug); void cbxp_free(cbxp_result_t* cbxp_result, bool debug); diff --git a/cbxp/cli/command_processor.cpp b/cbxp/cli/command_processor.cpp new file mode 100644 index 0000000..75725f4 --- /dev/null +++ b/cbxp/cli/command_processor.cpp @@ -0,0 +1,460 @@ +#define _POSIX_SOURCE +#define _XOPEN_SOURCE_EXTENDED 1 + +#include "command_processor.hpp" + +#include +#include + +#include +#include +#include +#include + +#include "cbxp.h" +#include "control_block_error.hpp" + +namespace CBXP { + +void CommandProcessor::showASCIIArt() { + std::string ansi_bold = "\e[1m"; + std::string ansi_blue_bold = "\e[1;34m"; + std::string ansi_reset = "\e[0m"; + + // clang-format off + const std::vector logo_ascii_art = { + " __________ ", + " | |", + " | 1010 |", + " | |", + " | {} |", + " |__________|" + }; + + const std::vector cbxp_ascii_art = { + " _____ ____ __ __ _____ ", + " / ____|| _ \\\\ \\ / /| __ \\", + " | | | |_) |\\ V / | |__) |", + " | | | _ < > < | ___/", + " | |____ | |_) |/ . \\ | |", + " \\_____||____//_/ \\_\\|_|" + }; + // clang-format on + + for (auto i = 0; i < logo_ascii_art.size(); i++) { + if (isatty(fileno(stdout))) { + std::cout << ansi_bold << logo_ascii_art[i] << ansi_blue_bold + << cbxp_ascii_art[i] << ansi_reset << std::endl; + } else { + std::cout << logo_ascii_art[i] << cbxp_ascii_art[i] << std::endl; + } + } + + std::cout << std::endl; +} + +void CommandProcessor::showGeneralUsage() const { + CommandProcessor::showASCIIArt(); + std::cout << "Full CLI documentation is available at: " + "https://ambitus.github.io/cbxp/interfaces/cli/" + << std::endl + << std::endl; + + std::cout << "Usage:" << std::endl + << " " << argv_[0] << " [command]" << std::endl + << std::endl; + + std::cout << "Available Commands:" << std::endl + << "extract Extract and format control " + "block data from live memory" + << std::endl + << "format Format control block data " + "from a file/pipe" + << std::endl + << std::endl; + + std::cout << "Flags:" << std::endl + << " -d, --debug Write debug messages" + << std::endl + << " -v, --version Show version number" + << std::endl + << " -h, --help Show usage information" + << std::endl + << std::endl + << "Use \"" << argv_[0] + << " [command] --help\" for more information about a command." + << std::endl; +} + +void CommandProcessor::showExtractUsage() const { + std::cout << "Extract and format control block data from live memory." + << std::endl + << std::endl; + + std::cout << "Usage:" << std::endl + << " " << argv_[0] << " extract [flags] " + << std::endl + << std::endl; + + std::cout << "Examples: " << std::endl + << " # Extract the PSA control block from live memory and write " + "debug messages." + << std::endl + << " " << argv_[0] << " extract -d psa" << std::endl + << std::endl + << " # Extract the CVT control block from live memory, including " + "the ECVT, ASVT," + << std::endl + << " # and all known control blocks that are pointed to directly " + "by the ASVT " + << std::endl + << " # control block." << std::endl + << " " << argv_[0] << " extract -i ecvt -i 'asvt.*' cvt" + << std::endl + << std::endl + << " # Extract all ASSB control blocks from live memory where the " + "control block" + << std::endl + << " # field 'ASSBJBNI' matches the filter value 'IBMUSER'." + << std::endl + << " " << argv_[0] << " extract -f assb.assbjbni=IBMUSER assb" + << std::endl + << std::endl; + + std::cout << "Flags:" << std::endl + << " -i, --include Include additional control " + "blocks based on a pattern" + << std::endl + << " -f, --filter Filter repeated control " + "block data" + << std::endl + << std::endl; + + std::cout << "Global Flags:" << std::endl + << " -d, --debug Write debug messages" + << std::endl + << " -h, --help Show usage information" + << std::endl; +} + +void CommandProcessor::showFormatUsage() const { + std::cout << "Format control block data from a file/pipe." << std::endl + << std::endl; + + std::cout << "Usage:" << std::endl + << " " << argv_[0] << " format [flags] " + << std::endl + << std::endl; + + std::cout << "Examples: " << std::endl + << " # Format CVT control block data from a file and write " + "debug messages." + << std::endl + << " " << argv_[0] << " format -F /path/to/cvt.bin -d cvt" + << std::endl + << std::endl + << " # Format CVT control block data from a pipe." << std::endl + << " cat /path/to/cvt.bin | " << argv_[0] << " format cvt" + << std::endl + << std::endl + << " # Format ASCB control block data from a file at an offset " + "of 0x40 bytes." + << std::endl + << " " << argv_[0] + << " format -F /path/to/ascboffset.bin -o 0x40 ascb" << std::endl + << std::endl + << " # Format ASCB control block data from a data set at an " + "offset of 64 bytes." + << std::endl + << " " << argv_[0] << " format -F \"//'HLQ.ASCBOFST'\" -o 64 ascb" + << std::endl + << std::endl; + + std::cout << "Flags:" << std::endl + << " -F, --file Format control block data " + "from a specified file or dataset" + << std::endl + << " -o, --offset Specify an offset into " + "the provided data to start formatting at" + << std::endl + << std::endl; + + std::cout << "Global Flags:" << std::endl + << " -d, --debug Write debug messages" + << std::endl + << " -h, --help Show usage information" + << std::endl; +} + +void CommandProcessor::process() { + CommandProcessor::processGlobalFlags(); + + if (command_ == "extract") { + if (global_options_.help) { + CommandProcessor::showExtractUsage(); + throw CLIExitSuccess(); + } else { + CommandProcessor::processExtractFlags(); + } + } else if (command_ == "format") { + if (global_options_.help) { + CommandProcessor::showFormatUsage(); + throw CLIExitSuccess(); + } else { + CommandProcessor::processFormatFlags(); + } + } + + if (control_block_name_ == "" || control_block_name_[0] == '-') { + std::cerr << ERROR_CONTROL_BLOCK_EXPECTED_ << std::endl; + throw CLIExitFailure(); + } +} + +void CommandProcessor::processGlobalFlags() { + if (argc_ < 2) { + CommandProcessor::showGeneralUsage(); + throw CLIExitFailure(); + } + + if (std::strcmp(argv_[1], "-v") == 0 || + std::strcmp(argv_[1], "--version") == 0) { + std::cout << "CBXP " << VERSION << std::endl; + throw CLIExitSuccess(); + } else if (std::strcmp(argv_[1], "-h") == 0 || + std::strcmp(argv_[1], "--help") == 0) { + CommandProcessor::showGeneralUsage(); + throw CLIExitSuccess(); + } + + command_ = argv_[1]; + + if (command_ == "format" || command_ == "extract") { + if (argc_ < 3) { + std::cerr << ERROR_CONTROL_BLOCK_EXPECTED_ << std::endl; + throw CLIExitFailure(); + } + } else if (command_[0] != '-') { + std::cerr << ERROR_UNKNOWN_COMMMAND_ << command_ << std::endl; + throw CLIExitFailure(); + } else if (!CommandProcessor::isGlobalFlag(command_)) { + std::cerr << ERROR_UNKNOWN_FLAG_ << command_ << std::endl; + throw CLIExitFailure(); + } else { + CommandProcessor::showGeneralUsage(); + throw CLIExitFailure(); + } + + for (int i = 2; i < argc_; i++) { + std::string flag = argv_[i]; + if (flag == "-d" || flag == "--debug") { + global_options_.debug = true; + } + if (flag == "-h" || flag == "--help") { + global_options_.help = true; + } + } + + control_block_name_ = std::string(argv_[argc_ - 1]); +} + +void CommandProcessor::processExtractFlags() { + for (int i = 2; i < argc_; i++) { + std::string flag = argv_[i]; + if (flag == "-i" || flag == "--include") { + if (i + 1 >= argc_ - 1 || argv_[i + 1][0] == '-') { + std::cerr << ERROR_FLAG_NEEDS_AN_ARGUMENT_ << flag << std::endl; + throw CLIExitFailure(); + } + std::string include = std::string(argv_[++i]); + if (CommandProcessor::checkForComma(include)) { + std::cerr << ERROR_COMMA_IN_INCLUDE_ << std::endl; + throw CLIExitFailure(); + } + if (extract_options_.include == "") { + extract_options_.include = include; + } else { + extract_options_.include += "," + include; + } + } else if (flag == "-f" || flag == "--filter") { + if (i + 1 >= argc_ - 1 || argv_[i + 1][0] == '-') { + std::cerr << ERROR_FLAG_NEEDS_AN_ARGUMENT_ << flag << std::endl; + throw CLIExitFailure(); + } + std::string filter = std::string(argv_[++i]); + if (CommandProcessor::checkForComma(filter)) { + std::cerr << ERROR_COMMA_IN_FILTER_ << std::endl; + throw CLIExitFailure(); + } + if (extract_options_.filter == "") { + extract_options_.filter = filter; + } else { + extract_options_.filter += "," + filter; + } + } else if (i != argc_ - 1 || flag[0] == '-') { + if (CommandProcessor::isGlobalFlag(flag)) { + continue; + } + std::cerr << ERROR_UNKNOWN_FLAG_ << flag << std::endl; + throw CLIExitFailure(); + } + } +} + +void CommandProcessor::processFormatFlags() { + format_options_.file = ""; + + for (int i = 2; i < argc_; i++) { + std::string flag = argv_[i]; + if (flag == "-F" || flag == "--file") { + if (i + 1 >= argc_ - 1 || argv_[i + 1][0] == '-') { + std::cerr << ERROR_FLAG_NEEDS_AN_ARGUMENT_ << flag << std::endl; + throw CLIExitFailure(); + } + CommandProcessor::readFormatDataFromFile(std::string(argv_[++i])); + } else if (flag == "-o" || flag == "--offset") { + if (i + 1 >= argc_ - 1 || argv_[i + 1][0] == '-') { + std::cerr << ERROR_FLAG_NEEDS_AN_ARGUMENT_ << flag << std::endl; + throw CLIExitFailure(); + } + std::string offset = argv_[++i]; + if (offset.find('.') != std::string::npos) { + std::cerr << ERROR_OFFSET_MUST_BE_A_POSITIVE_INTEGER_ << std::endl; + throw CLIExitFailure(); + } + try { + format_options_.offset = std::stoul(offset, nullptr, 0); + } + // Standard exceptions for stoul + catch (const std::invalid_argument& e) { + // Offset not positive or not integer + std::cerr << ERROR_OFFSET_MUST_BE_A_POSITIVE_INTEGER_ << std::endl; + throw CLIExitFailure(); + } catch (const std::out_of_range& e) { + // Offset too large + std::cerr << ERROR_OFFSET_TOO_LARGE_ << std::endl; + throw CLIExitFailure(); + } + } else if (i != argc_ - 1 || flag[0] == '-') { + if (CommandProcessor::isGlobalFlag(flag)) { + continue; + } + std::cerr << ERROR_UNKNOWN_FLAG_ << flag << std::endl; + throw CLIExitFailure(); + } + } + struct pollfd pfd = {STDIN_FILENO, POLLIN, 0}; + if (poll(&pfd, 1, 0) > 0 && (pfd.revents & POLLIN)) { + CommandProcessor::readFormatDataFromPipe(); + } + if (format_options_.data_buffer.empty()) { + std::cerr << ERROR_FILE_OR_PIPE_EXPECTED_ << std::endl; + throw CLIExitFailure(); + } + + if (format_options_.offset > format_options_.data_buffer.size()) { + std::cerr << ERROR_OFFSET_TOO_LARGE_ << std::endl; + throw CLIExitFailure(); + } +} + +void CommandProcessor::readFormatDataFromPipe() { + if (!format_options_.file.empty()) { + std::cerr << ERROR_FILE_AND_PIPE_CANT_BE_USED_TOGETHER_ << std::endl; + throw CLIExitFailure(); + } + format_options_.data_buffer.assign((std::istreambuf_iterator(std::cin)), + (std::istreambuf_iterator())); + if (!format_options_.data_buffer.empty()) { + // When auto conversion is enabled, binary data being piped into the CBXP + // CLI will be auto converted to ASCII, so we need to undo the auto + // conversion. This may not work correctly if auto conversion is not + // enabled. It is recommended to have '_BPXK_AUTOCVT=ON' set when using the + // CBXP CLI. + std::string env_p(std::getenv("_BPXK_AUTOCVT")); + if (!env_p.empty() && (env_p == "ON" || env_p == "ALL")) { + __a2e_l(format_options_.data_buffer.data(), + format_options_.data_buffer.size()); + } + } +} + +void CommandProcessor::readFormatDataFromFile(const std::string& file_path) { + format_options_.file = file_path; + std::ifstream file(format_options_.file, std::ios::binary | std::ios::ate); + if (!file.is_open()) { + std::cerr << ERROR_OPENING_FILE_ << format_options_.file << std::endl; + throw CLIExitFailure(); + } + std::streamsize size = file.tellg(); + file.seekg(0, std::ios::beg); // Move back to start + + format_options_.data_buffer.resize(size); + file.read(format_options_.data_buffer.data(), size); +} + +bool CommandProcessor::isGlobalFlag(const std::string& flag) { + if (flag == "-d" || flag == "--debug") { + return true; + } + if (flag == "-h" || flag == "--help") { + return true; + } + return false; +} + +bool CommandProcessor::checkForComma(const std::string& string) { + return std::any_of(string.begin(), string.end(), + [](char c) { return c == ','; }); +} + +void CommandProcessor::run() { + cbxp_result_t* cbxp_result; + + if (command_ == "extract") { + cbxp_result = cbxp_extract( + control_block_name_.c_str(), control_block_name_.length(), + extract_options_.include.c_str(), extract_options_.include.length(), + extract_options_.filter.c_str(), extract_options_.filter.length(), + global_options_.debug); + } else { + cbxp_result = + cbxp_format(control_block_name_.c_str(), control_block_name_.length(), + static_cast(format_options_.data_buffer.data() + + format_options_.offset), + format_options_.data_buffer.size() - format_options_.offset, + global_options_.debug); + } + + unsigned int return_code = cbxp_result->return_code; + + if (return_code == 0) { + std::cout << cbxp_result->result_json << std::endl; + } + + cbxp_free(cbxp_result, global_options_.debug); + + switch (return_code) { + case 0: + break; + case CBXP::Error::BadControlBlock: + std::cerr << ERROR_UNKNOWN_CONTROL_BLOCK_ << control_block_name_ + << std::endl; + throw CLIExitFailure(); + case CBXP::Error::BadInclude: + std::cerr << ERROR_BAD_INCLUDE_ << std::endl; + throw CLIExitFailure(); + case CBXP::Error::BadFilter: + std::cerr << ERROR_BAD_FILTER_ << std::endl; + throw CLIExitFailure(); + case CBXP::Error::DataTooSmall: + std::cerr << ERROR_DATA_TOO_SMALL_ << control_block_name_ << std::endl; + throw CLIExitFailure(); + default: + std::cerr << ERROR_UNKNOWN_ERROR_ << control_block_name_ << std::endl; + throw CLIExitFailure(); + } + throw CLIExitSuccess(); +} + +} // namespace CBXP diff --git a/cbxp/cli/command_processor.hpp b/cbxp/cli/command_processor.hpp new file mode 100644 index 0000000..32ba26e --- /dev/null +++ b/cbxp/cli/command_processor.hpp @@ -0,0 +1,96 @@ +#ifndef __CBXP_CLI_H_ +#define __CBXP_CLI_H_ + +#include +#include +#include +#include + +namespace CBXP { + +class CLIExitSuccess : public std::exception {}; + +class CLIExitFailure : public std::exception {}; + +typedef struct { + bool debug; + bool help; +} cbxp_global_options_t; + +typedef struct { + std::string include; + std::string filter; +} cbxp_extract_options_t; + +typedef struct { + std::string file; + std::vector data_buffer; + size_t offset; +} cbxp_format_options_t; + +class CommandProcessor { + private: + // Error Strings + // General + const std::string ERROR_FLAG_NEEDS_AN_ARGUMENT_ = "Flag needs an argument: "; + const std::string ERROR_UNKNOWN_COMMMAND_ = "Unknown command: "; + const std::string ERROR_UNKNOWN_FLAG_ = "Unknown flag: "; + const std::string ERROR_CONTROL_BLOCK_EXPECTED_ = + "Positional argument expected"; + const std::string ERROR_UNKNOWN_ERROR_ = "An unknown error occurred"; + // Format + const std::string ERROR_OFFSET_MUST_BE_A_POSITIVE_INTEGER_ = + "Offset must be a positive integer"; + const std::string ERROR_OFFSET_TOO_LARGE_ = + "Offset is too large for data provided"; + const std::string ERROR_OPENING_FILE_ = "Error opening file: "; + const std::string ERROR_FILE_OR_PIPE_EXPECTED_ = + "File or pipe expected for \"format\" command"; + const std::string ERROR_FILE_AND_PIPE_CANT_BE_USED_TOGETHER_ = + "File and pipe cannot be used together"; + // Extract + const std::string ERROR_COMMA_IN_INCLUDE_ = + "Include patterns cannot contain commas"; + const std::string ERROR_COMMA_IN_FILTER_ = + "Include patterns cannot contain commas"; + // Return Codes + const std::string ERROR_UNKNOWN_CONTROL_BLOCK_ = "Unknown control block: "; + const std::string ERROR_BAD_INCLUDE_ = "A bad include pattern was provided"; + const std::string ERROR_BAD_FILTER_ = "A bad filter was provided"; + const std::string ERROR_DATA_TOO_SMALL_ = + "Data provided is not large enough for specified control block: "; + + std::string command_; + std::string control_block_name_; + int argc_; + const char** argv_; + cbxp_global_options_t global_options_; + cbxp_extract_options_t extract_options_; + cbxp_format_options_t format_options_; + static void showASCIIArt(); + void showGeneralUsage() const; + void showExtractUsage() const; + void showFormatUsage() const; + void process(); + void processGlobalFlags(); + void processFormatFlags(); + void processExtractFlags(); + void readFormatDataFromPipe(); + void readFormatDataFromFile(const std::string& file_path); + static bool isGlobalFlag(const std::string& flag); + static bool checkForComma(const std::string& string); + + public: + explicit CommandProcessor(int argc, const char* argv[]) + : argc_(argc), + argv_(argv), + global_options_({false, false}), + extract_options_({"", ""}), + format_options_({"", {}, 0}) { + CommandProcessor::process(); + }; + void run(); +}; +} // namespace CBXP + +#endif diff --git a/cbxp/cli/main.cpp b/cbxp/cli/main.cpp new file mode 100644 index 0000000..9197471 --- /dev/null +++ b/cbxp/cli/main.cpp @@ -0,0 +1,15 @@ +#include "command_processor.hpp" + +int main(int argc, const char* argv[]) { + try { + CBXP::CommandProcessor command_processor = + CBXP::CommandProcessor(argc, argv); + command_processor.run(); + } catch (const CBXP::CLIExitFailure& e) { + return -1; + } catch (const CBXP::CLIExitSuccess& e) { + return 0; + } + + return 0; +} diff --git a/cbxp/control_block_error.hpp b/cbxp/control_block_error.hpp index bbdeee4..09f8d3a 100644 --- a/cbxp/control_block_error.hpp +++ b/cbxp/control_block_error.hpp @@ -2,14 +2,21 @@ #define __CONTROL_BLOCK_ERROR_H_ namespace CBXP { -enum Error { BadControlBlock = 1, BadInclude = 2, BadFilter = 3 }; +enum Error { + BadControlBlock = 1, + BadInclude = 2, + BadFilter = 3, + DataTooSmall = 4, + NullDataPtr = 5 +}; + 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_; } + const unsigned int getErrorCode() const { return error_code_; } }; class ControlBlockError : public CBXPError { @@ -27,6 +34,11 @@ class FilterError : public CBXPError { FilterError() : CBXPError(Error::BadFilter) {} }; +class DataLengthError : public CBXPError { + public: + DataLengthError() : CBXPError(Error::DataTooSmall) {} +}; + } // namespace CBXP #endif diff --git a/cbxp/control_block_explorer.cpp b/cbxp/control_block_explorer.cpp index 0748756..548bd42 100644 --- a/cbxp/control_block_explorer.cpp +++ b/cbxp/control_block_explorer.cpp @@ -13,6 +13,7 @@ #include "control_blocks/control_block.hpp" #include "control_blocks/cvt.hpp" #include "control_blocks/ecvt.hpp" +#include "control_blocks/ldax.hpp" #include "control_blocks/oucb.hpp" #include "control_blocks/psa.hpp" #include "logger.hpp" @@ -50,32 +51,72 @@ std::vector ControlBlockExplorer::createOptionsList( return options_list; } -void ControlBlockExplorer::exploreControlBlock( +void ControlBlockExplorer::extractControlBlock( const std::string& control_block_name, const std::string& includes_string, const std::string& filters_string) { - cbxp_options_t cbxp_options = { - ControlBlockExplorer::createOptionsList(includes_string), - ControlBlockExplorer::createOptionsList(filters_string)}; + cbxp_options_ = {ControlBlockExplorer::createOptionsList(includes_string), + ControlBlockExplorer::createOptionsList(filters_string), + true}; - Logger::getInstance().debug("Extracting '" + control_block_name + - "' control block data..."); + control_block_operation_ = "Extract"; + + ControlBlockExplorer::processControlBlock(control_block_name); + + return; +} + +void ControlBlockExplorer::formatControlBlock( + const std::string& control_block_name, const void* p_data, + const size_t data_length) { + if (p_data == nullptr) { + // C/C++ callers who invoke this directly could pass null pointer(s) in + // which should halt processing + p_result_->return_code = Error::NullDataPtr; + return; + } + p_control_block_ = p_data; + control_block_data_length_ = data_length; + + control_block_operation_ = "Format"; + + ControlBlockExplorer::processControlBlock(control_block_name); + + return; +} + +void ControlBlockExplorer::processControlBlock( + const std::string& control_block_name) { + Logger::getInstance().debug(control_block_operation_ + "ing '" + + control_block_name + "' control block data..."); + + nlohmann::json control_block_json = {}; - nlohmann::json control_block_json; try { if (control_block_name == "psa") { - control_block_json = PSA(cbxp_options).get(); + control_block_json = + PSA(cbxp_options_).get(p_control_block_, control_block_data_length_); } else if (control_block_name == "cvt") { - control_block_json = CVT(cbxp_options).get(); + control_block_json = + CVT(cbxp_options_).get(p_control_block_, control_block_data_length_); } else if (control_block_name == "ecvt") { - control_block_json = ECVT(cbxp_options).get(); + control_block_json = + ECVT(cbxp_options_).get(p_control_block_, control_block_data_length_); } else if (control_block_name == "ascb") { - control_block_json = ASCB(cbxp_options).get(); + control_block_json = + ASCB(cbxp_options_).get(p_control_block_, control_block_data_length_); } else if (control_block_name == "asvt") { - control_block_json = ASVT(cbxp_options).get(); + control_block_json = + ASVT(cbxp_options_).get(p_control_block_, control_block_data_length_); } else if (control_block_name == "assb") { - control_block_json = ASSB(cbxp_options).get(); + control_block_json = + ASSB(cbxp_options_).get(p_control_block_, control_block_data_length_); } else if (control_block_name == "oucb") { - control_block_json = OUCB(cbxp_options).get(); + control_block_json = + OUCB(cbxp_options_).get(p_control_block_, control_block_data_length_); + } else if (control_block_name == "ldax") { + control_block_json = + LDAX(cbxp_options_).get(p_control_block_, control_block_data_length_); + } else { throw ControlBlockError(); } @@ -84,10 +125,11 @@ void ControlBlockExplorer::exploreControlBlock( return; } - std::string control_block_json_string = control_block_json.dump(); + std::string control_block_json_string = control_block_json.dump( + -1, ' ', false, nlohmann::json::error_handler_t::replace); - Logger::getInstance().debug("'" + control_block_name + - "' control block data extracted"); + Logger::getInstance().debug(control_block_operation_ + "ed '" + + control_block_name + "' control block data"); Logger::getInstance().debug("Control Block JSON: " + control_block_json_string); diff --git a/cbxp/control_block_explorer.hpp b/cbxp/control_block_explorer.hpp index 01ae003..ba1cf8f 100644 --- a/cbxp/control_block_explorer.hpp +++ b/cbxp/control_block_explorer.hpp @@ -4,20 +4,29 @@ #include #include "cbxp.h" +#include "control_blocks/control_block.hpp" namespace CBXP { + class ControlBlockExplorer { private: cbxp_result_t* p_result_; + cbxp_options_t cbxp_options_ = {{}, {}, false}; + const void* p_control_block_ = nullptr; + size_t control_block_data_length_ = 0; + std::string control_block_operation_ = ""; static std::vector createOptionsList( const std::string& comma_separated_string); public: explicit ControlBlockExplorer(cbxp_result_t* p_result) : p_result_(p_result) {}; - void exploreControlBlock(const std::string& control_block_name, + void extractControlBlock(const std::string& control_block_name, const std::string& includes_string, const std::string& filters_string); + void formatControlBlock(const std::string& control_block_name, + const void* p_data, const size_t data_length); + void processControlBlock(const std::string& control_block_name); }; } // namespace CBXP diff --git a/cbxp/control_blocks/ascb.cpp b/cbxp/control_blocks/ascb.cpp index e95573a..cef0b7a 100644 --- a/cbxp/control_blocks/ascb.cpp +++ b/cbxp/control_blocks/ascb.cpp @@ -1,7 +1,6 @@ #include "ascb.hpp" #include -#include #include #include @@ -15,9 +14,11 @@ #include "oucb.hpp" namespace CBXP { -nlohmann::json ASCB::get(void* __ptr32 p_control_block) { +nlohmann::json ASCB::get(const void* p_control_block, + const size_t buffer_length) { + ASCB::checkDataLength(buffer_length); nlohmann::json ascb_json = {}; - const ascb* __ptr32 p_ascb; + const ascb* p_ascb; if (p_control_block == nullptr) { // PSA starts at address 0 @@ -26,7 +27,7 @@ nlohmann::json ASCB::get(void* __ptr32 p_control_block) { 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); + static_cast(p_psa->flccvt); asvt_t* __ptr32 p_asvt = static_cast(p_cvtmap->cvtasvt); ascb_json["ascbs"] = std::vector(); @@ -52,7 +53,7 @@ nlohmann::json ASCB::get(void* __ptr32 p_control_block) { } return ascbs; } else { - p_ascb = static_cast(p_control_block); + p_ascb = static_cast(p_control_block); } ascb_json["ascbassb"] = formatter_.getHex(&(p_ascb->ascbassb)); diff --git a/cbxp/control_blocks/ascb.hpp b/cbxp/control_blocks/ascb.hpp index c892d48..b858800 100644 --- a/cbxp/control_blocks/ascb.hpp +++ b/cbxp/control_blocks/ascb.hpp @@ -1,15 +1,19 @@ #ifndef __ASCB_H_ #define __ASCB_H_ +#include + #include "control_block.hpp" namespace CBXP { class ASCB : public ControlBlock { public: - nlohmann::json get(void* __ptr32 p_control_block = nullptr) override; + nlohmann::json get(const void* p_control_block = nullptr, + const size_t buffer_length = 0) override; explicit ASCB(const cbxp_options_t& cbxp_options) - : ControlBlock("ascb", {"assb", "oucb"}, cbxp_options) {} + : ControlBlock("ascb", {"assb", "oucb"}, cbxp_options, + sizeof(struct ascb)) {} }; } // namespace CBXP diff --git a/cbxp/control_blocks/assb.cpp b/cbxp/control_blocks/assb.cpp index c41eb3a..c261b73 100644 --- a/cbxp/control_blocks/assb.cpp +++ b/cbxp/control_blocks/assb.cpp @@ -1,5 +1,4 @@ #include -#include #include #include @@ -11,11 +10,14 @@ #include "ascb.hpp" #include "asvt.hpp" +#include "ldax.hpp" #include "logger.hpp" namespace CBXP { -nlohmann::json ASSB::get(void* __ptr32 p_control_block) { - const assb* __ptr32 p_assb; +nlohmann::json ASSB::get(const void* p_control_block, + const size_t buffer_length) { + ASSB::checkDataLength(buffer_length); + const assb* p_assb; nlohmann::json assb_json = {}; if (p_control_block == nullptr) { // PSA starts at address 0 @@ -24,7 +26,7 @@ nlohmann::json ASSB::get(void* __ptr32 p_control_block) { 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); + static_cast(p_psa->flccvt); const asvt_t* __ptr32 p_asvt = static_cast(p_cvtmap->cvtasvt); @@ -55,13 +57,26 @@ nlohmann::json ASSB::get(void* __ptr32 p_control_block) { } return assbs; } else { - p_assb = static_cast(p_control_block); + p_assb = static_cast(p_control_block); } Logger::getInstance().debug("assb hex dump:"); Logger::getInstance().hexDump(reinterpret_cast(p_assb), sizeof(struct assb)); + assb_json["assbldax"] = formatter_.getHex(&(p_assb->assbldax)); + + for (const auto& [include, cbxp_options] : options_map_) { + if (include == "ldax") { + assb_json["assbldax"] = + CBXP::LDAX(cbxp_options) + .get(*reinterpret_cast(p_assb->assbldax)); + if (assb_json["assbldax"].is_null()) { + return {}; + } + } + } + assb_json["assb_cms_lockinst_addr"] = formatter_.getHex(&(p_assb->assb_cms_lockinst_addr)); assb_json["assb_enqdeq_cms_lockinst_addr"] = @@ -77,7 +92,6 @@ nlohmann::json ASSB::get(void* __ptr32 p_control_block) { assb_json["assboasb"] = formatter_.getHex(&(p_assb->assboasb)); assb_json["assbtasb"] = formatter_.getHex(&(p_assb->assbtasb)); assb_json["assbvab"] = formatter_.getHex(&(p_assb->assbvab)); - assb_json["assbldax"] = formatter_.getHex(&(p_assb->assbldax)); assb_json["assbisqn"] = p_assb->assbisqn; assb_json["assbjbni"] = formatter_.getString(p_assb->assbjbni, 8); assb_json["assbjbns"] = formatter_.getString(p_assb->assbjbns, 8); diff --git a/cbxp/control_blocks/assb.hpp b/cbxp/control_blocks/assb.hpp index 5046d8f..9f0c720 100644 --- a/cbxp/control_blocks/assb.hpp +++ b/cbxp/control_blocks/assb.hpp @@ -1,15 +1,18 @@ #ifndef __ASSB_H_ #define __ASSB_H_ +#include + #include "control_block.hpp" namespace CBXP { class ASSB : public ControlBlock { public: - nlohmann::json get(void* __ptr32 p_control_block = nullptr) override; + nlohmann::json get(const void* p_control_block = nullptr, + const size_t buffer_length = 0) override; explicit ASSB(const cbxp_options_t& cbxp_options) - : ControlBlock("assb", {}, cbxp_options) {} + : ControlBlock("assb", {"ldax"}, cbxp_options, sizeof(struct assb)) {} }; } // namespace CBXP diff --git a/cbxp/control_blocks/asvt.cpp b/cbxp/control_blocks/asvt.cpp index a14d31c..0316177 100644 --- a/cbxp/control_blocks/asvt.cpp +++ b/cbxp/control_blocks/asvt.cpp @@ -12,8 +12,10 @@ #include "logger.hpp" namespace CBXP { -nlohmann::json ASVT::get(void* __ptr32 p_control_block) { - const asvt_t* __ptr32 p_asvt; +nlohmann::json ASVT::get(const void* p_control_block, + const size_t buffer_length) { + ASVT::checkDataLength(buffer_length); + const asvt_t* p_asvt; nlohmann::json asvt_json = {}; if (p_control_block == nullptr) { @@ -21,10 +23,10 @@ nlohmann::json ASVT::get(void* __ptr32 p_control_block) { 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); + static_cast(p_psa->flccvt); p_asvt = static_cast(p_cvtmap->cvtasvt); } else { - p_asvt = static_cast(p_control_block); + p_asvt = static_cast(p_control_block); } Logger::getInstance().debug("ASCB pointers:"); @@ -33,8 +35,8 @@ nlohmann::json ASVT::get(void* __ptr32 p_control_block) { std::vector ascbs; ascbs.reserve(p_asvt->asvtmaxu); - const uint32_t* __ptr32 p_ascb = const_cast( - reinterpret_cast(&p_asvt->asvtenty)); + const uint32_t* 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)); @@ -50,7 +52,7 @@ nlohmann::json ASVT::get(void* __ptr32 p_control_block) { if (include == "ascb") { nlohmann::json ascbs_json; CBXP::ASCB ascb(cbxp_options); - uint32_t* __ptr32 p_ascb_addr = const_cast( + uint32_t const* __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) { diff --git a/cbxp/control_blocks/asvt.hpp b/cbxp/control_blocks/asvt.hpp index 15268bb..16c15c0 100644 --- a/cbxp/control_blocks/asvt.hpp +++ b/cbxp/control_blocks/asvt.hpp @@ -32,9 +32,10 @@ namespace CBXP { class ASVT : public ControlBlock { public: - nlohmann::json get(void* __ptr32 p_control_block = nullptr) override; + nlohmann::json get(const void* p_control_block = nullptr, + const size_t buffer_length = 0) override; explicit ASVT(const cbxp_options_t& cbxp_options) - : ControlBlock("asvt", {"ascb"}, cbxp_options) {} + : ControlBlock("asvt", {"ascb"}, cbxp_options, sizeof(asvt_t)) {} }; } // namespace CBXP diff --git a/cbxp/control_blocks/control_block.cpp b/cbxp/control_blocks/control_block.cpp index 3b7a4d0..eaf2783 100644 --- a/cbxp/control_blocks/control_block.cpp +++ b/cbxp/control_blocks/control_block.cpp @@ -14,6 +14,10 @@ void ControlBlock::createOptionsMap(const std::vector& includes, // of the options_map_ structure and must be called after createIncludeLists ControlBlock::createIncludeLists(includes); ControlBlock::createFilterLists(filters); + for (auto it = options_map_.begin(); it != options_map_.end(); ++it) { + options_map_[it->first].skip_buffer_length_check = + skip_buffer_length_check_; + } } void ControlBlock::createIncludeLists( @@ -161,14 +165,14 @@ void ControlBlock::createFilterLists(const std::vector& filters) { } void ControlBlock::addCurrentFilter(const std::string& filter) { - std::string filter_key, filter_value; std::vector operations = {"<=", ">=", "<", ">", "="}; for (std::string operation : operations) { size_t operation_pos = filter.find(operation); if (operation_pos != std::string::npos) { // If there's a delimeter then separate include into the key and its value - filter_value = filter.substr(operation_pos + operation.length()); - filter_key = filter.substr(0, operation_pos); + std::string filter_value = + filter.substr(operation_pos + operation.length()); + std::string filter_key = filter.substr(0, operation_pos); cbxp_filter_t filter_data = {operation, filter_value}; Logger::getInstance().debug("Adding '" + filter_key + operation + filter_value + @@ -191,7 +195,7 @@ bool ControlBlock::compare(const nlohmann::json& json_value, uint64_t value_uint; if (json_value.is_number()) { - value_uint = json_value.get(); + value_uint = json_value.get(); } else { value_str = json_value.get(); value_is_string = true; @@ -277,5 +281,23 @@ bool ControlBlock::matchFilter(nlohmann::json& control_block_json) { "' control block matched"); return true; } + +void ControlBlock::checkDataLength(const size_t buffer_length) const { + if (skip_buffer_length_check_) { + // Data length check is only done when formatting + // user provided control block data. + // This check is skipped when extracting and formatting + // control block data from live memory. + return; + } + Logger::getInstance().debug( + "Checking if specified buffer (" + std::to_string(buffer_length) + + " bytes) too small to contain the '" + control_block_name_ + + "' control block (requires " + std::to_string(control_block_length_) + + " bytes)..."); + if (buffer_length < control_block_length_) { + throw DataLengthError(); + } +} } // namespace CBXP diff --git a/cbxp/control_blocks/control_block.hpp b/cbxp/control_blocks/control_block.hpp index 1ec9095..e7c8989 100644 --- a/cbxp/control_blocks/control_block.hpp +++ b/cbxp/control_blocks/control_block.hpp @@ -15,12 +15,14 @@ typedef struct { typedef struct { std::vector include_patterns; std::vector filters; + bool skip_buffer_length_check; } cbxp_options_t; class ControlBlock { private: const std::string control_block_name_; const std::vector includables_; + size_t control_block_length_ = 0; void createIncludeLists(const std::vector& includes); void processDoubleAsteriskInclude(); void processAsteriskInclude(); @@ -37,14 +39,22 @@ class ControlBlock { void createOptionsMap(const std::vector& includes, const std::vector& filters); bool matchFilter(nlohmann::json& control_block_json); + bool skip_buffer_length_check_ = false; public: - virtual nlohmann::json get(void* __ptr32 p_control_block = nullptr) = 0; + void checkDataLength(const size_t buffer_length) const; + virtual nlohmann::json get(const void* p_control_block = nullptr, + const size_t buffer_length = 0) = 0; explicit ControlBlock(const std::string& name, const std::vector& includables, - const cbxp_options_t& cbxp_options) - : control_block_name_(name), includables_(includables) { - createOptionsMap(cbxp_options.include_patterns, cbxp_options.filters); + const cbxp_options_t& cbxp_options, + size_t control_block_length) + : control_block_name_(name), + includables_(includables), + control_block_length_(control_block_length), + skip_buffer_length_check_(cbxp_options.skip_buffer_length_check) { + ControlBlock::createOptionsMap(cbxp_options.include_patterns, + cbxp_options.filters); } virtual ~ControlBlock() = default; }; diff --git a/cbxp/control_blocks/cvt.cpp b/cbxp/control_blocks/cvt.cpp index d679270..92daa9e 100644 --- a/cbxp/control_blocks/cvt.cpp +++ b/cbxp/control_blocks/cvt.cpp @@ -1,6 +1,5 @@ #include "cvt.hpp" -#include #include #include @@ -12,11 +11,13 @@ #include "logger.hpp" namespace CBXP { -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::get(const void* p_control_block, + const size_t buffer_length) { + CVT::checkDataLength(buffer_length); + const struct cvtmap* p_cvtmap; + const struct cvtfix* p_cvtfix; + const struct cvtxtnt2* p_cvtxtnt2; + const struct cvtvstgx* p_cvtvstgx; nlohmann::json cvt_json = {}; if (p_control_block == nullptr) { @@ -24,24 +25,24 @@ nlohmann::json CVT::get(void* __ptr32 p_control_block) { 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); + p_cvtmap = static_cast(p_psa->flccvt); } else { - p_cvtmap = static_cast(p_control_block); + 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)); + 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); + cvt_json["cvtasvt"] = formatter_.getHex(&(p_cvtmap->cvtasvt)); + cvt_json["cvtecvt"] = formatter_.getHex(&(p_cvtmap->cvtecvt)); for (const auto& [include, cbxp_options] : options_map_) { if (include == "asvt") { @@ -59,91 +60,124 @@ nlohmann::json CVT::get(void* __ptr32 p_control_block) { // Get fields - 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["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["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); + // Read bitfields directly to avoid read-modify-write corruption + cvt_json["cvtdcpa"] = formatter_.getBitmap( + reinterpret_cast(&p_cvtmap->cvtflags) + 3); cvt_json["cvtdfa"] = formatter_.getHex(p_cvtmap->cvtdfa); - cvt_json["cvtedat2"] = formatter_.getBitmap(p_cvtmap->cvtedat2); - cvt_json["cvteplps"] = formatter_.getHex(p_cvtvstgx->cvteplps); + cvt_json["cvtedat2"] = formatter_.getBitmap( + reinterpret_cast(&p_cvtmap->cvtflags) + 3); + cvt_json["cvteplps"] = formatter_.getHex(&(p_cvtvstgx->cvteplps)); cvt_json["cvtexit"] = formatter_.getHex(p_cvtmap->cvtexit); - cvt_json["cvtexp1"] = formatter_.getHex(p_cvtmap->cvtexp1); - cvt_json["cvtflag2"] = formatter_.getBitmap(p_cvtmap->cvtflag2); - cvt_json["cvtflag3"] = formatter_.getBitmap(p_cvtmap->cvtflag3); - cvt_json["cvtflag4"] = formatter_.getBitmap(p_cvtmap->cvtflag4); - cvt_json["cvtflag5"] = formatter_.getBitmap(p_cvtmap->cvtflag5); - cvt_json["cvtflag6"] = formatter_.getBitmap(p_cvtmap->cvtflag6); - cvt_json["cvtflag7"] = formatter_.getBitmap(p_cvtmap->cvtflag7); + cvt_json["cvtexp1"] = formatter_.getHex(&(p_cvtmap->cvtexp1)); + // Read bitfields directly to avoid read-modify-write corruption + cvt_json["cvtflag2"] = formatter_.getBitmap( + reinterpret_cast(&p_cvtmap->cvtflags) + 1); + cvt_json["cvtflag3"] = formatter_.getBitmap( + reinterpret_cast(&p_cvtmap->cvtflags) + 2); + cvt_json["cvtflag4"] = formatter_.getBitmap( + reinterpret_cast(&p_cvtmap->cvtflags) + 3); + cvt_json["cvtflag5"] = formatter_.getBitmap( + reinterpret_cast(&p_cvtmap->cvtaqavt) + 4); + cvt_json["cvtflag6"] = formatter_.getBitmap( + reinterpret_cast(&p_cvtmap->cvtaqavt) + 5); + cvt_json["cvtflag7"] = formatter_.getBitmap( + reinterpret_cast(&p_cvtmap->cvtaqavt) + 6); cvt_json["cvtflag9"] = formatter_.getBitmap( reinterpret_cast(&p_cvtmap->cvtflag9)); - cvt_json["cvtflgbt"] = formatter_.getBitmap(p_cvtxtnt2->cvtflgbt); - cvt_json["cvtgda"] = formatter_.getHex(p_cvtmap->cvtgda); - cvt_json["cvtgrsst"] = formatter_.getBitmap(p_cvtmap->cvtgrsst); - cvt_json["cvtgvt"] = formatter_.getHex(p_cvtmap->cvtgvt); - cvt_json["cvthid"] = formatter_.getHex(p_cvtmap->cvthid); - cvt_json["cvtixavl"] = formatter_.getHex(p_cvtmap->cvtixavl); - cvt_json["cvtjesct"] = formatter_.getHex(p_cvtmap->cvtjesct); - cvt_json["cvtlccat"] = formatter_.getHex(p_cvtmap->cvtlccat); + // Read cvtflgbt byte directly to avoid bitfield read-modify-write corruption + // cvtflgbt is at offset 5 in cvtxtnt2 (4 bytes cvt2r000 + 1 byte cvtnucls) + const unsigned char* p_cvtflgbt = + reinterpret_cast(p_cvtxtnt2) + 5; + cvt_json["cvtflgbt"] = formatter_.getBitmap(p_cvtflgbt); + cvt_json["cvtgda"] = formatter_.getHex(&(p_cvtmap->cvtgda)); + // Read bitfield directly to avoid read-modify-write corruption + cvt_json["cvtgrsst"] = formatter_.getBitmap( + reinterpret_cast(&p_cvtmap->cvtppgmx) + 4); + cvt_json["cvtgvt"] = formatter_.getHex(&(p_cvtmap->cvtgvt)); + cvt_json["cvthid"] = formatter_.getHex(&(p_cvtmap->cvthid)); + cvt_json["cvtixavl"] = formatter_.getHex(&(p_cvtmap->cvtixavl)); + cvt_json["cvtjesct"] = formatter_.getHex(&(p_cvtmap->cvtjesct)); + cvt_json["cvtlccat"] = formatter_.getHex(&(p_cvtmap->cvtlccat)); cvt_json["cvtldto"] = formatter_.getHex(p_cvtxtnt2->cvtldto); - cvt_json["cvtlink"] = formatter_.getHex(p_cvtmap->cvtlink); + cvt_json["cvtlink"] = formatter_.getHex(&(p_cvtmap->cvtlink)); cvt_json["cvtlso"] = formatter_.getHex(p_cvtxtnt2->cvtlso); cvt_json["cvtmaxmp"] = p_cvtmap->cvtmaxmp; cvt_json["cvtmdl"] = formatter_.getHex(p_cvtfix->cvtmdl); - cvt_json["cvtmser"] = formatter_.getHex(p_cvtmap->cvtmser); - cvt_json["cvtopctp"] = formatter_.getHex(p_cvtmap->cvtopctp); + cvt_json["cvtmser"] = formatter_.getHex(&(p_cvtmap->cvtmser)); + cvt_json["cvtopctp"] = formatter_.getHex(&(p_cvtmap->cvtopctp)); cvt_json["cvtoslvl"] = formatter_.getHex(p_cvtmap->cvtoslvl) + formatter_.getHex(p_cvtmap->cvtoslvl + 8); - cvt_json["cvtover"] = formatter_.getBitmap(p_cvtmap->cvtover); - cvt_json["cvtpccat"] = formatter_.getHex(p_cvtmap->cvtpccat); - cvt_json["cvtpcnvt"] = formatter_.getHex(p_cvtmap->cvtpcnvt); - cvt_json["cvtprltv"] = formatter_.getHex(p_cvtmap->cvtprltv); + // Read bitfield directly to avoid read-modify-write corruption + cvt_json["cvtover"] = formatter_.getBitmap( + reinterpret_cast(&p_cvtmap->cvtflags)); + cvt_json["cvtpccat"] = formatter_.getHex(&(p_cvtmap->cvtpccat)); + cvt_json["cvtpcnvt"] = formatter_.getHex(&(p_cvtmap->cvtpcnvt)); + cvt_json["cvtprltv"] = formatter_.getHex(&(p_cvtmap->cvtprltv)); cvt_json["cvtprod"] = formatter_.getHex(p_cvtfix->cvtprod) + formatter_.getHex(p_cvtfix->cvtprod + 8); - cvt_json["cvtpsxm"] = formatter_.getHex(p_cvtmap->cvtpsxm); - cvt_json["cvtpvtp"] = formatter_.getHex(p_cvtmap->cvtpvtp); - cvt_json["cvtqtd00"] = formatter_.getHex(p_cvtmap->cvtqtd00); - cvt_json["cvtqte00"] = formatter_.getHex(p_cvtmap->cvtqte00); - cvt_json["cvtrac"] = formatter_.getHex(p_cvtmap->cvtrac); - cvt_json["cvtrcep"] = formatter_.getHex(p_cvtmap->cvtrcep); - cvt_json["cvtrczrt"] = formatter_.getHex(p_cvtmap->cvtrczrt); + cvt_json["cvtpsxm"] = formatter_.getHex(&(p_cvtmap->cvtpsxm)); + cvt_json["cvtpvtp"] = formatter_.getHex(&(p_cvtmap->cvtpvtp)); + cvt_json["cvtqtd00"] = formatter_.getHex(&(p_cvtmap->cvtqtd00)); + cvt_json["cvtqte00"] = formatter_.getHex(&(p_cvtmap->cvtqte00)); + cvt_json["cvtrac"] = formatter_.getHex(&(p_cvtmap->cvtrac)); + cvt_json["cvtrcep"] = formatter_.getHex(&(p_cvtmap->cvtrcep)); + cvt_json["cvtrczrt"] = formatter_.getHex(&(p_cvtmap->cvtrczrt)); cvt_json["cvtrelno"] = formatter_.getHex(p_cvtfix->cvtrelno); - cvt_json["cvtri"] = formatter_.getBitmap(p_cvtmap->cvtri); - cvt_json["cvtrtmct"] = formatter_.getHex(p_cvtmap->cvtrtmct); - cvt_json["cvtsaf"] = formatter_.getHex(p_cvtmap->cvtsaf); - cvt_json["cvtscpin"] = formatter_.getHex(p_cvtmap->cvtscpin); + // Read bitfield directly to avoid read-modify-write corruption + cvt_json["cvtri"] = formatter_.getBitmap( + reinterpret_cast(&p_cvtmap->cvtflags) + 3); + cvt_json["cvtrtmct"] = formatter_.getHex(&(p_cvtmap->cvtrtmct)); + cvt_json["cvtsaf"] = formatter_.getHex(&(p_cvtmap->cvtsaf)); + cvt_json["cvtscpin"] = formatter_.getHex(&(p_cvtmap->cvtscpin)); // cvt_json["cvtsdbf"] = formatter_.getBitmap(p_cvtmap->cvtsdbf); - cvt_json["cvtsdump"] = formatter_.getBitmap(p_cvtmap->cvtsdump); - cvt_json["cvtsmca"] = formatter_.getHex(p_cvtmap->cvtsmca); + // Read bitfields directly to avoid read-modify-write corruption + cvt_json["cvtsdump"] = formatter_.getBitmap( + reinterpret_cast(&p_cvtmap->cvtdmsr)); + cvt_json["cvtsmca"] = formatter_.getHex(&(p_cvtmap->cvtsmca)); cvt_json["cvtsname"] = formatter_.getString(p_cvtmap->cvtsname, 8); - cvt_json["cvtsubsp"] = formatter_.getBitmap(p_cvtmap->cvtsubsp); - cvt_json["cvtsvt"] = formatter_.getHex(p_cvtmap->cvtsvt); - cvt_json["cvtsysad"] = formatter_.getHex(p_cvtmap->cvtsysad); - cvt_json["cvttpc"] = formatter_.getHex(p_cvtmap->cvttpc); - cvt_json["cvttvt"] = formatter_.getHex(p_cvtmap->cvttvt); - cvt_json["cvttx"] = formatter_.getBitmap(p_cvtmap->cvttx); - cvt_json["cvttxc"] = formatter_.getBitmap(p_cvtmap->cvttxc); - cvt_json["cvttxte"] = formatter_.getBitmap(p_cvtmap->cvttxte); + cvt_json["cvtsubsp"] = formatter_.getBitmap( + reinterpret_cast(&p_cvtmap->cvtflags)); + cvt_json["cvtsvt"] = formatter_.getHex(&(p_cvtmap->cvtsvt)); + cvt_json["cvtsysad"] = formatter_.getHex(&(p_cvtmap->cvtsysad)); + cvt_json["cvttpc"] = formatter_.getHex(&(p_cvtmap->cvttpc)); + cvt_json["cvttvt"] = formatter_.getHex(&(p_cvtmap->cvttvt)); + // Read bitfields directly to avoid read-modify-write corruption + cvt_json["cvttx"] = formatter_.getBitmap( + reinterpret_cast(&p_cvtmap->cvtflags) + 3); + cvt_json["cvttxc"] = formatter_.getBitmap( + reinterpret_cast(&p_cvtmap->cvtflags) + 3); + cvt_json["cvttxte"] = formatter_.getBitmap( + reinterpret_cast(&p_cvtmap->cvtctlfg)); + ; cvt_json["cvttz"] = p_cvtmap->cvttz; - cvt_json["cvtucbsc"] = formatter_.getHex(p_cvtmap->cvtucbsc); - cvt_json["cvtundvm"] = formatter_.getBitmap(p_cvtxtnt2->cvtundvm); - cvt_json["cvtuser"] = formatter_.getHex(p_cvtmap->cvtuser); + cvt_json["cvtucbsc"] = formatter_.getHex(&(p_cvtmap->cvtucbsc)); + // Read cvtundvm bit directly to avoid bitfield read-modify-write corruption + // cvtundvm is bit 3 of the byte at offset 5 in cvtxtnt2 (same byte as + // cvtflgbt) + const unsigned char* p_cvtundvm = + reinterpret_cast(p_cvtxtnt2) + 5; + cvt_json["cvtundvm"] = formatter_.getBitmap(p_cvtundvm); + cvt_json["cvtuser"] = formatter_.getHex(&(p_cvtmap->cvtuser)); cvt_json["cvtverid"] = formatter_.getHex(p_cvtfix->cvtverid); - cvt_json["cvtvfget"] = formatter_.getHex(p_cvtmap->cvtvfget); - cvt_json["cvtvfind"] = formatter_.getHex(p_cvtmap->cvtvfind); - cvt_json["cvtvpsib"] = formatter_.getHex(p_cvtmap->cvtvpsib); - cvt_json["cvtvwait"] = formatter_.getHex(p_cvtmap->cvtvwait); - cvt_json["cvt0ef00"] = formatter_.getHex(p_cvtmap->cvt0ef00); - cvt_json["cvt0pt0e"] = formatter_.getHex(p_cvtmap->cvt0pt0e); - cvt_json["cvt0pt02"] = formatter_.getHex(p_cvtmap->cvt0pt02); - cvt_json["cvt0pt03"] = formatter_.getHex(p_cvtmap->cvt0pt03); - cvt_json["cvt0scr1"] = formatter_.getHex(p_cvtmap->cvt0scr1); + cvt_json["cvtvfget"] = formatter_.getHex(&(p_cvtmap->cvtvfget)); + cvt_json["cvtvfind"] = formatter_.getHex(&(p_cvtmap->cvtvfind)); + cvt_json["cvtvpsib"] = formatter_.getHex(&(p_cvtmap->cvtvpsib)); + cvt_json["cvtvwait"] = formatter_.getHex(&(p_cvtmap->cvtvwait)); + cvt_json["cvt0ef00"] = formatter_.getHex(&(p_cvtmap->cvt0ef00)); + cvt_json["cvt0pt0e"] = formatter_.getHex(&(p_cvtmap->cvt0pt0e)); + cvt_json["cvt0pt02"] = formatter_.getHex(&(p_cvtmap->cvt0pt02)); + cvt_json["cvt0pt03"] = formatter_.getHex(&(p_cvtmap->cvt0pt03)); + cvt_json["cvt0scr1"] = formatter_.getHex(&(p_cvtmap->cvt0scr1)); if (CVT::matchFilter(cvt_json)) { return cvt_json; diff --git a/cbxp/control_blocks/cvt.hpp b/cbxp/control_blocks/cvt.hpp index e65e3c7..51ff892 100644 --- a/cbxp/control_blocks/cvt.hpp +++ b/cbxp/control_blocks/cvt.hpp @@ -1,15 +1,19 @@ #ifndef __CVT_H_ #define __CVT_H_ +#include + #include "control_block.hpp" namespace CBXP { class CVT : public ControlBlock { public: - nlohmann::json get(void* __ptr32 p_control_block = nullptr) override; + nlohmann::json get(const void* p_control_block = nullptr, + const size_t buffer_length = 0) override; explicit CVT(const cbxp_options_t& cbxp_options) - : ControlBlock("cvt", {"ecvt", "asvt"}, cbxp_options) {} + : ControlBlock("cvt", {"ecvt", "asvt"}, cbxp_options, + sizeof(struct cvtmap)) {} }; } // namespace CBXP diff --git a/cbxp/control_blocks/ecvt.cpp b/cbxp/control_blocks/ecvt.cpp index 0142304..9735ba3 100644 --- a/cbxp/control_blocks/ecvt.cpp +++ b/cbxp/control_blocks/ecvt.cpp @@ -1,7 +1,6 @@ #include "ecvt.hpp" #include -#include #include #include @@ -12,8 +11,10 @@ #include "logger.hpp" namespace CBXP { -nlohmann::json ECVT::get(void* __ptr32 p_control_block) { - const struct ecvt* __ptr32 p_ecvt; +nlohmann::json ECVT::get(const void* p_control_block, + const size_t buffer_length) { + ECVT::checkDataLength(buffer_length); + const struct ecvt* p_ecvt; nlohmann::json ecvt_json = {}; if (p_control_block == nullptr) { @@ -23,11 +24,11 @@ nlohmann::json ECVT::get(void* __ptr32 p_control_block) { 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); + static_cast(p_psa->flccvt); // Get the address of the EVCT from the CVT - p_ecvt = static_cast(p_cvt->cvtecvt); + p_ecvt = static_cast(p_cvt->cvtecvt); } else { - p_ecvt = static_cast(p_control_block); + p_ecvt = static_cast(p_control_block); } Logger::getInstance().debug("ECVT hex dump:"); diff --git a/cbxp/control_blocks/ecvt.hpp b/cbxp/control_blocks/ecvt.hpp index ff452b3..a8183a7 100644 --- a/cbxp/control_blocks/ecvt.hpp +++ b/cbxp/control_blocks/ecvt.hpp @@ -1,15 +1,18 @@ #ifndef __ECVT_H_ #define __ECVT_H_ +#include + #include "control_block.hpp" namespace CBXP { class ECVT : public ControlBlock { public: - nlohmann::json get(void* __ptr32 p_control_block = nullptr) override; + nlohmann::json get(const void* p_control_block = nullptr, + const size_t buffer_length = 0) override; explicit ECVT(const cbxp_options_t& cbxp_options) - : ControlBlock("ecvt", {}, cbxp_options) {} + : ControlBlock("ecvt", {}, cbxp_options, sizeof(struct ecvt)) {} }; } // namespace CBXP #endif diff --git a/cbxp/control_blocks/ldax.cpp b/cbxp/control_blocks/ldax.cpp new file mode 100644 index 0000000..45f30a9 --- /dev/null +++ b/cbxp/control_blocks/ldax.cpp @@ -0,0 +1,130 @@ +#include "ldax.hpp" + +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "asvt.hpp" +#include "logger.hpp" + +namespace CBXP { + +nlohmann::json LDAX::get(const void* p_control_block, + const size_t buffer_length) { + LDAX::checkDataLength(buffer_length); + const struct ldax* p_ldax; + nlohmann::json ldax_json = {}; + if (p_control_block == nullptr) { + // PSA starts at address 0 + const struct psa* __ptr32 p_psa = 0; + + const struct cvtmap* __ptr32 p_cvtmap = + // cppcheck-suppress nullPointer + static_cast(p_psa->flccvt); + + const asvt_t* __ptr32 p_asvt = + static_cast(p_cvtmap->cvtasvt); + + ldax_json["ldaxs"] = std::vector(); + std::vector& ldaxs = + ldax_json["ldaxs"].get_ref&>(); + + ldaxs.reserve(p_asvt->asvtmaxu); + + const 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; + } + + // Cast ASCB address into ASCB pointer + const struct ascb* __ptr32 p_ascb = + reinterpret_cast(*p_ascb_addr); + + // Get ASSB from ASCB + const struct assb* __ptr32 p_assb = + reinterpret_cast(p_ascb->ascbassb); + + nlohmann::json next_ldax = + LDAX::get(*reinterpret_cast(p_assb->assbldax)); + if (!next_ldax.is_null()) { + ldaxs.push_back(next_ldax); + } + + p_ascb_addr++; + } + + return ldaxs; + } else { + p_ldax = static_cast(p_control_block); + } + + Logger::getInstance().debug("ldax hex dump:"); + Logger::getInstance().hexDump(reinterpret_cast(p_ldax), + sizeof(struct ldax)); + + ldax_json["ldax_id"] = formatter_.getString(p_ldax->ldax_id, 4); + ldax_json["ldax_version"] = + formatter_.getBitmap(p_ldax->ldax_version); + ldax_json["ldax_ldaascb"] = + formatter_.getHex(&(p_ldax->ldax_ldaascb)); + ldax_json["ldax_ldastrta"] = + formatter_.getHex(&(p_ldax->ldax_ldastrta)); + ldax_json["ldax_ldasiza"] = p_ldax->ldax_ldasiza; + ldax_json["ldax_ldaestra"] = + formatter_.getHex(&(p_ldax->ldax_ldaestra)); + ldax_json["ldax_ldaesiza"] = p_ldax->ldax_ldaesiza; + ldax_json["ldax_ldacrgtp"] = + formatter_.getHex(&(p_ldax->ldax_ldacrgtp)); + ldax_json["ldax_ldaergtp"] = + formatter_.getHex(&(p_ldax->ldax_ldaergtp)); + ldax_json["ldax_ldalimit"] = + formatter_.getHex(&(p_ldax->ldax_ldalimit)); + ldax_json["ldax_ldavvrg"] = + formatter_.getHex(&(p_ldax->ldax_ldavvrg)); + ldax_json["ldax_ldaelim"] = + formatter_.getHex(&(p_ldax->ldax_ldaelim)); + ldax_json["ldax_ldaevvrg"] = + formatter_.getHex(&(p_ldax->ldax_ldaevvrg)); + ldax_json["ldax_ldaloal"] = + formatter_.getBitmap(p_ldax->ldax_ldaloal); + ldax_json["ldax_ldahial"] = + formatter_.getBitmap(p_ldax->ldax_ldahial); + ldax_json["ldax_ldaeloal"] = + formatter_.getBitmap(p_ldax->ldax_ldaeloal); + ldax_json["ldax_ldaehial"] = + formatter_.getBitmap(p_ldax->ldax_ldaehial); + ldax_json["ldax_tcthwm"] = p_ldax->ldax_tcthwm; + ldax_json["ldax_tctlwm"] = p_ldax->ldax_tctlwm; + ldax_json["ldax_tctehwm"] = p_ldax->ldax_tctehwm; + ldax_json["ldax_tctelwm"] = p_ldax->ldax_tctelwm; + ldax_json["ldax_curhighbot"] = + formatter_.getHex(&(p_ldax->ldax_curhighbot)); + ldax_json["ldax_curehighbot"] = + formatter_.getHex(&(p_ldax->ldax_curehighbot)); + ldax_json["ldax_ldasmad"] = + formatter_.getHex(&(p_ldax->ldax_ldasmad)); + ldax_json["ldax_ldasmsz"] = + formatter_.getHex(&(p_ldax->ldax_ldasmsz)); + ldax_json["ldax_obtainshomespace"] = p_ldax->ldax_obtainshomespace; + + if (LDAX::matchFilter(ldax_json)) { + return ldax_json; + } else { + return {}; + } +} + +} // namespace CBXP diff --git a/cbxp/control_blocks/ldax.hpp b/cbxp/control_blocks/ldax.hpp new file mode 100644 index 0000000..0878206 --- /dev/null +++ b/cbxp/control_blocks/ldax.hpp @@ -0,0 +1,20 @@ +#ifndef __LDAX_H_ +#define __LDAX_H_ + +#include + +#include "control_block.hpp" + +namespace CBXP { + +class LDAX : public ControlBlock { + public: + nlohmann::json get(const void* p_control_block = nullptr, + const size_t buffer_length = 0) override; + explicit LDAX(const cbxp_options_t& cbxp_options) + : ControlBlock("ldax", {}, cbxp_options, sizeof(struct ldax)) {} +}; + +} // namespace CBXP + +#endif diff --git a/cbxp/control_blocks/oucb.cpp b/cbxp/control_blocks/oucb.cpp index 99b752f..954a3e4 100644 --- a/cbxp/control_blocks/oucb.cpp +++ b/cbxp/control_blocks/oucb.cpp @@ -13,8 +13,10 @@ #include "logger.hpp" namespace CBXP { -nlohmann::json OUCB::get(void* __ptr32 p_control_block) { - const oucb_t* __ptr32 p_oucb; +nlohmann::json OUCB::get(const void* p_control_block, + const size_t buffer_length) { + OUCB::checkDataLength(buffer_length); + const oucb_t* p_oucb; nlohmann::json oucb_json = {}; if (p_control_block == nullptr) { // PSA starts at address 0 @@ -23,7 +25,7 @@ nlohmann::json OUCB::get(void* __ptr32 p_control_block) { const struct cvtmap* __ptr32 p_cvtmap = // 'nullPointer' is a false positive because the PSA starts at address // cppcheck-suppress nullPointer - static_cast(p_psa->flccvt); + static_cast(p_psa->flccvt); const asvt_t* __ptr32 p_asvt = static_cast(p_cvtmap->cvtasvt); @@ -54,7 +56,7 @@ nlohmann::json OUCB::get(void* __ptr32 p_control_block) { } return oucbs; } else { - p_oucb = static_cast(p_control_block); + p_oucb = static_cast(p_control_block); } Logger::getInstance().debug("oucb hex dump:"); diff --git a/cbxp/control_blocks/oucb.hpp b/cbxp/control_blocks/oucb.hpp index 160a4d0..a5cff84 100644 --- a/cbxp/control_blocks/oucb.hpp +++ b/cbxp/control_blocks/oucb.hpp @@ -109,9 +109,10 @@ namespace CBXP { class OUCB : public ControlBlock { public: - nlohmann::json get(void* __ptr32 p_control_block = nullptr) override; + nlohmann::json get(const void* p_control_block = nullptr, + const size_t buffer_length = 0) override; explicit OUCB(const cbxp_options_t& cbxp_options) - : ControlBlock("oucb", {}, cbxp_options) {} + : ControlBlock("oucb", {}, cbxp_options, sizeof(oucb_t)) {} }; } // namespace CBXP diff --git a/cbxp/control_blocks/psa.cpp b/cbxp/control_blocks/psa.cpp index ce13391..9824383 100644 --- a/cbxp/control_blocks/psa.cpp +++ b/cbxp/control_blocks/psa.cpp @@ -1,7 +1,5 @@ #include "psa.hpp" -#include - #include #include #include @@ -10,15 +8,17 @@ #include "logger.hpp" namespace CBXP { -nlohmann::json PSA::get(void* __ptr32 p_control_block) { - const struct psa* __ptr32 p_psa; +nlohmann::json PSA::get(const void* p_control_block, + const size_t buffer_length) { + PSA::checkDataLength(buffer_length); + const struct psa* 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); + p_psa = static_cast(p_control_block); } Logger::getInstance().debug("PSA hex dump:"); diff --git a/cbxp/control_blocks/psa.hpp b/cbxp/control_blocks/psa.hpp index d53b53d..92db78a 100644 --- a/cbxp/control_blocks/psa.hpp +++ b/cbxp/control_blocks/psa.hpp @@ -1,15 +1,18 @@ #ifndef __PSA_H_ #define __PSA_H_ +#include + #include "control_block.hpp" namespace CBXP { class PSA : public ControlBlock { public: - nlohmann::json get(void* __ptr32 p_control_block = nullptr) override; + nlohmann::json get(const void* p_control_block = nullptr, + const size_t buffer_length = 0) override; explicit PSA(const cbxp_options_t& cbxp_options) - : ControlBlock("psa", {"cvt"}, cbxp_options) {} + : ControlBlock("psa", {"cvt"}, cbxp_options, sizeof(struct psa) / 2) {} }; } // namespace CBXP #endif diff --git a/cbxp/logger.cpp b/cbxp/logger.cpp index b10112e..6fe7c9f 100644 --- a/cbxp/logger.cpp +++ b/cbxp/logger.cpp @@ -1,3 +1,5 @@ +#define _POSIX_SOURCE + #include "logger.hpp" #include diff --git a/cbxp/main.cpp b/cbxp/main.cpp deleted file mode 100644 index 4c4c3e5..0000000 --- a/cbxp/main.cpp +++ /dev/null @@ -1,147 +0,0 @@ -#define _UNIX03_SOURCE - -#include - -#include -#include -#include -#include -#include - -#include "cbxp.h" -#include "control_block_error.hpp" - -static void show_usage(const char* argv[]); - -enum CLIReturnCode { SUCCESS = 0, FAILURE = -1 }; - -static void show_usage(const char* argv[]) { - 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 - << " -f, --filter Filter repeated control " - "block data" - << std::endl - << " -v, --version Show version number" - << std::endl - << " -h, --help Show usage information" - << std::endl - << std::endl; -} - -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[]) { - bool debug = false; - std::string control_block_name = "", includes_string = "", - filters_string = ""; - - if (argc < 2) { - show_usage(argv); - return CLIReturnCode::FAILURE; - } - - if (argc == 2) { - if (std::strcmp(argv[1], "-v") == 0 || - std::strcmp(argv[1], "--version") == 0) { - std::cout << "CBXP " << VERSION << std::endl; - return CLIReturnCode::SUCCESS; - } - - if (std::strcmp(argv[1], "-h") == 0 || - std::strcmp(argv[1], "--help") == 0) { - show_usage(argv); - return CLIReturnCode::SUCCESS; - } - } - - for (int i = 1; i < argc; i++) { - std::string flag = argv[i]; - if (flag == "-d" || flag == "--debug") { - if (!debug) { - debug = true; - } else { - show_usage(argv); - return CLIReturnCode::FAILURE; - } - } else if (flag == "-i" || flag == "--include") { - if (i + 1 >= argc - 1) { - show_usage(argv); - return CLIReturnCode::FAILURE; - } - std::string include = std::string(argv[++i]); - if (check_for_comma(include)) { - std::cerr << "Include patterns cannot contain commas" << std::endl; - return CLIReturnCode::FAILURE; - } - if (includes_string == "") { - includes_string = include; - } else { - includes_string += "," + include; - } - } else if (flag == "-f" || flag == "--filter") { - if (i + 1 >= argc - 1) { - show_usage(argv); - return CLIReturnCode::FAILURE; - } - std::string filter = std::string(argv[++i]); - if (check_for_comma(filter)) { - std::cerr << "Filters cannot contain commas" << std::endl; - return CLIReturnCode::FAILURE; - } - if (filters_string == "") { - filters_string = filter; - } else { - filters_string += "," + filter; - } - } else { - if (i != argc - 1) { - show_usage(argv); - return CLIReturnCode::FAILURE; - } - control_block_name = std::string(argv[i]); - } - } - - if (control_block_name == "") { - show_usage(argv); - return CLIReturnCode::FAILURE; - } - - nlohmann::json control_block_json; - - cbxp_result_t* cbxp_result = - cbxp(control_block_name.c_str(), includes_string.c_str(), - filters_string.c_str(), debug); - - CLIReturnCode cli_return_code = CLIReturnCode::FAILURE; - - switch (cbxp_result->return_code) { - case CBXP::Error::BadControlBlock: - std::cerr << "Unknown control block '" << control_block_name - << "' was specified." << std::endl; - break; - case CBXP::Error::BadInclude: - std::cerr << "A bad include pattern was provided" << std::endl; - break; - case CBXP::Error::BadFilter: - std::cerr << "A bad filter was provided" << std::endl; - break; - default: - std::cout << cbxp_result->result_json << std::endl; - cli_return_code = CLIReturnCode::SUCCESS; - } - - cbxp_free(cbxp_result, debug); - - return cli_return_code; -} diff --git a/cbxp/python/_cbxp.c b/cbxp/python/_cbxp.c index a51d20f..f781084 100644 --- a/cbxp/python/_cbxp.c +++ b/cbxp/python/_cbxp.c @@ -6,32 +6,69 @@ #include "cbxp.h" -// Entry point to the call_cbxp() function -static PyObject* call_cbxp(PyObject* self, PyObject* args, PyObject* kwargs) { +// Entry point to the call_cbxp_extract() function +static PyObject* call_cbxp_extract(PyObject* self, PyObject* args, + PyObject* kwargs) { PyObject* result_dictionary; PyObject* debug_pyobj; - const char* p_control_block; + const char* p_control_block_name; const char* p_includes_string; const char* p_filters_string; - Py_ssize_t request_length; + Py_ssize_t control_block_name_length, includes_length, filters_length; bool debug = false; static char* kwlist[] = {"control_block", "includes_string", "filters_string", "debug", NULL}; - if (!PyArg_ParseTupleAndKeywords(args, kwargs, "sss|O", kwlist, - &p_control_block, &p_includes_string, - &p_filters_string, &debug_pyobj)) { + if (!PyArg_ParseTupleAndKeywords( + args, kwargs, "s#s#s#|O", kwlist, &p_control_block_name, + &control_block_name_length, &p_includes_string, &includes_length, + &p_filters_string, &filters_length, &debug_pyobj)) { + return NULL; + } + + debug = PyObject_IsTrue(debug_pyobj); + + cbxp_result_t* p_cbxp_result = cbxp_extract( + p_control_block_name, control_block_name_length, p_includes_string, + includes_length, p_filters_string, filters_length, debug); + + result_dictionary = + Py_BuildValue("{s:s#, s:I}", "result_json", p_cbxp_result->result_json, + p_cbxp_result->result_json_length, "return_code", + p_cbxp_result->return_code); + + cbxp_free(p_cbxp_result, debug); + + return result_dictionary; +} + +// Entry point to the call_cbxp_format() function +static PyObject* call_cbxp_format(PyObject* self, PyObject* args, + PyObject* kwargs) { + PyObject* result_dictionary; + PyObject* debug_pyobj; + const char* p_control_block_name; + const char* p_data; + Py_ssize_t data_length, control_block_name_length; + bool debug = false; + + static char* kwlist[] = {"control_block", "data", "debug", NULL}; + + if (!PyArg_ParseTupleAndKeywords( + args, kwargs, "s#y#|O", kwlist, &p_control_block_name, + &control_block_name_length, &p_data, &data_length, &debug_pyobj)) { return NULL; } debug = PyObject_IsTrue(debug_pyobj); cbxp_result_t* p_cbxp_result = - cbxp(p_control_block, p_includes_string, p_filters_string, debug); + cbxp_format(p_control_block_name, control_block_name_length, p_data, + data_length, debug); result_dictionary = - Py_BuildValue("{s:s#, s:i}", "result_json", p_cbxp_result->result_json, + Py_BuildValue("{s:s#, s:I}", "result_json", p_cbxp_result->result_json, p_cbxp_result->result_json_length, "return_code", p_cbxp_result->return_code); @@ -42,17 +79,19 @@ static PyObject* call_cbxp(PyObject* self, PyObject* args, PyObject* kwargs) { // Method definition static PyMethodDef _C_methods[] = { - {"call_cbxp", (PyCFunction)call_cbxp, METH_VARARGS | METH_KEYWORDS, - "A unified and standardized interface for extracting z/OS control block " - "data."}, + {"call_cbxp_extract", (PyCFunction)call_cbxp_extract, + METH_VARARGS | METH_KEYWORDS, + "Extract z/OS control block data from live memory."}, + {"call_cbxp_format", (PyCFunction)call_cbxp_format, + METH_VARARGS | METH_KEYWORDS, "Format user provided control block data."}, {NULL} }; // Module definition static struct PyModuleDef _C_module_def = { PyModuleDef_HEAD_INIT, "_C", - "A unified and standardized interface for extracting z/OS control block " - "data.", + "A unified and standardized interface for extracting and formatting " + "z/OS control block data.", -1, _C_methods}; // Module initialization function diff --git a/pyproject.toml b/pyproject.toml index 2a2321c..cddf5d1 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ [project] name="cbxp" - version="0.0.3" + version="0.0.4" requires-python = ">= 3.13" authors = [ { name = "Leonard J. Carcaramo Jr", email = "lcarcaramo@ibm.com" }, @@ -16,7 +16,7 @@ { name = "Elijah Swift", email = "elijah.swift@ibm.com" }, { name = "Varun Chennamadhava", email = "varunchennamadhava@gmail.com" }, ] - description="A unified and standardized interface for extracting z/OS control block data." + description="A unified and standardized interface for extracting and formatting z/OS control block data." readme = "README.md" license = "Apache-2.0" classifiers=[ diff --git a/python/cbxp/_C.pyi b/python/cbxp/_C.pyi index 1d518bd..195d8f9 100644 --- a/python/cbxp/_C.pyi +++ b/python/cbxp/_C.pyi @@ -1,6 +1,11 @@ -def call_cbxp( # noqa: N999 +def call_cbxp_extract( # noqa: N999 control_block: str, includes_string: str, filters_string: str, debug: bool = False, ) -> dict: ... +def call_cbxp_format( # noqa: N999 + control_block: str, + data: bytes, + debug: bool = False, +) -> dict: ... diff --git a/python/cbxp/__init__.py b/python/cbxp/__init__.py index 4627129..0889fbe 100644 --- a/python/cbxp/__init__.py +++ b/python/cbxp/__init__.py @@ -1,4 +1,3 @@ from .cbxp import CBXPError as CBXPError from .cbxp import CBXPFilter as CBXPFilter from .cbxp import CBXPFilterOperation as CBXPFilterOperation -from .cbxp import cbxp as cbxp diff --git a/python/cbxp/cbxp.py b/python/cbxp/cbxp.py index 1868db9..d01e768 100644 --- a/python/cbxp/cbxp.py +++ b/python/cbxp/cbxp.py @@ -1,7 +1,7 @@ import json from enum import Enum -from cbxp._C import call_cbxp +from cbxp._C import call_cbxp_extract, call_cbxp_format class CBXPFilterOperation(Enum): @@ -34,11 +34,14 @@ def __str__(self): class CBXPErrorCode(Enum): """An enum of error and return codes from the cbxp interface""" + # Negative Error Codes are from the Python interface COMMA_IN_INCLUDE = -1 COMMA_IN_FILTER = -2 - BAD_CONTROL_BLOCK = 1 + # Positive Error Codes are return codes from CBXP + UNKNOWN_CONTROL_BLOCK = 1 BAD_INCLUDE = 2 - BAD_CONTROL_BLOCK_FILTER = 3 + BAD_FILTER = 3 + DATA_TOO_SMALL = 4 class CBXPError(Exception): @@ -51,18 +54,23 @@ def __init__(self, return_code: int, control_block_name: str): 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.UNKNOWN_CONTROL_BLOCK.value: + message = f"Unknown control block: {control_block_name}" case CBXPErrorCode.BAD_INCLUDE.value: message = "A bad include pattern was provided" - case CBXPErrorCode.BAD_CONTROL_BLOCK_FILTER.value: + case CBXPErrorCode.BAD_FILTER.value: message = "A bad filter was provided" + case CBXPErrorCode.DATA_TOO_SMALL.value: + message = ( + "Data provided is not large enough for specified " + f"control block: {control_block_name}" + ) case _: - message = "an unknown error occurred" + message = "An unknown error occurred" super().__init__(message) -def cbxp( +def extract( control_block: str, includes: list[str] = None, filters: list[CBXPFilter] = None, @@ -86,7 +94,7 @@ def cbxp( raise CBXPError(CBXPErrorCode.COMMA_IN_FILTER.value, control_block) filters_string += str(filter_obj) - response = call_cbxp( + response = call_cbxp_extract( control_block.lower(), ",".join(includes), filters_string, @@ -97,3 +105,21 @@ def cbxp( if response["result_json"] == "null" or response["result_json"] == "[]": return None return json.loads(response["result_json"]) + + +def format( # noqa: A001 + control_block: str, + data: bytes, + debug: bool = False, +) -> dict: + response = call_cbxp_format( + control_block.lower(), + data, + debug=debug, + ) + + if response["return_code"]: + raise CBXPError(response["return_code"], control_block) + if response["result_json"] == "null" or response["result_json"] == "[]": + return None + return json.loads(response["result_json"]) diff --git a/setup.py b/setup.py index 81683d5..a342796 100644 --- a/setup.py +++ b/setup.py @@ -18,8 +18,12 @@ def main(): Extension( "cbxp._C", sources=( - glob("cbxp/**/*.cpp") - + [file for file in glob("cbxp/*.cpp") if file != "cbxp/main.cpp"] + [ + file + for file in glob("cbxp/**/*.cpp") + if file not in glob("cbxp/cli/*.cpp") + ] + + glob("cbxp/*.cpp") + ["cbxp/python/_cbxp.c"] ), include_dirs=( diff --git a/tests/samples/ascb.bin b/tests/samples/ascb.bin new file mode 100644 index 0000000..69f2a75 Binary files /dev/null and b/tests/samples/ascb.bin differ diff --git a/tests/samples/ascb_offset_0x40.bin b/tests/samples/ascb_offset_0x40.bin new file mode 100644 index 0000000..33e4ed5 Binary files /dev/null and b/tests/samples/ascb_offset_0x40.bin differ diff --git a/tests/samples/cvt.bin b/tests/samples/cvt.bin new file mode 100644 index 0000000..a4ffc52 Binary files /dev/null and b/tests/samples/cvt.bin differ diff --git a/tests/samples/oucb.bin b/tests/samples/oucb.bin new file mode 100644 index 0000000..dfaefd7 Binary files /dev/null and b/tests/samples/oucb.bin differ diff --git a/tests/samples/oucb_offset_0x03a8.bin b/tests/samples/oucb_offset_0x03a8.bin new file mode 100644 index 0000000..fd9670c Binary files /dev/null and b/tests/samples/oucb_offset_0x03a8.bin differ diff --git a/tests/test.py b/tests/test.py index 6e69f5a..08a776c 100644 --- a/tests/test.py +++ b/tests/test.py @@ -1,96 +1,160 @@ import unittest +from pathlib import Path from cbxp import CBXPError, CBXPFilter, CBXPFilterOperation, cbxp class TestCBXP(unittest.TestCase): + SAMPLE_DIR = Path(__file__).resolve().parent / "samples" + + # ============================================================================ + # Utility Functions + # ============================================================================ + @staticmethod + def read_sample(filename: str) -> bytes: + return (TestCBXP.SAMPLE_DIR / filename).read_bytes() + + @staticmethod + def get_cvtasmvt_values() -> tuple[int, str]: + cbdata = cbxp.extract("cvt") + cvtasmvt_hex = cbdata["cvtasmvt"] + return int(cvtasmvt_hex, 16), cvtasmvt_hex + # ============================================================================ - # Basic Usage + # Extract -- Basic Usage # ============================================================================ - def test_cbxp_can_extract_psa(self): - cbdata = cbxp("psa") + def test_cbxp_extract_psa(self): + cbdata = cbxp.extract("psa") self.assertIs(type(cbdata), dict) - def test_cbxp_can_extract_cvt(self): - cbdata = cbxp("cvt") + def test_cbxp_extract_cvt(self): + cbdata = cbxp.extract("cvt") self.assertIs(type(cbdata), dict) - def test_cbxp_can_extract_ecvt(self): - cbdata = cbxp("ecvt") + def test_cbxp_extract_ecvt(self): + cbdata = cbxp.extract("ecvt") self.assertIs(type(cbdata), dict) - def test_cbxp_can_extract_asvt(self): - cbdata = cbxp("asvt") + def test_cbxp_extract_asvt(self): + cbdata = cbxp.extract("asvt") self.assertIs(type(cbdata), dict) - def test_cbxp_can_extract_ascb(self): - cbdata = cbxp("ascb") + def test_cbxp_extract_ascb(self): + cbdata = cbxp.extract("ascb") + self.assertIs(type(cbdata), list) + for entry in cbdata: + self.assertIs(type(entry), dict) + + def test_cbxp_extract_assb(self): + cbdata = cbxp.extract("assb") self.assertIs(type(cbdata), list) for entry in cbdata: self.assertIs(type(entry), dict) - def test_cbxp_can_extract_assb(self): - cbdata = cbxp("assb") + def test_cbxp_extract_oucb(self): + cbdata = cbxp.extract("oucb") self.assertIs(type(cbdata), list) for entry in cbdata: self.assertIs(type(entry), dict) - def test_cbxp_can_extract_oucb(self): - cbdata = cbxp("oucb") + def test_cbxp_extract_ldax(self): + cbdata = cbxp.extract("ldax") self.assertIs(type(cbdata), list) for entry in cbdata: self.assertIs(type(entry), dict) # ============================================================================ - # Include Patterns + # Extract -- Debug Mode # ============================================================================ - def test_cbxp_can_extract_the_psa_and_include_the_cvt(self): - cbdata = cbxp("psa", includes=["cvt"]) + def test_cbxp_extract_runs_in_debug_mode(self): + cbdata = cbxp.extract("psa", debug=True) + self.assertIs(type(cbdata), dict) + + # ============================================================================ + # Extract -- Include Patterns + # ============================================================================ + def test_cbxp_extract_psa_and_include_cvt(self): + cbdata = cbxp.extract("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"]) + def test_cbxp_extract_cvt_and_include_ecvt(self): + cbdata = cbxp.extract("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"]) + def test_cbxp_extract_cvt_and_include_asvt(self): + cbdata = cbxp.extract("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"]) + def test_cbxp_extract_asvt_and_include_ascb(self): + cbdata = cbxp.extract("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_ascb_and_include_the_assb(self): - cbdata = cbxp("ascb", includes=["assb"]) + def test_cbxp_extract_ascb_and_include_assb(self): + cbdata = cbxp.extract("ascb", includes=["assb"]) self.assertIs(type(cbdata), list) for entry in cbdata: self.assertIs(type(entry), dict) self.assertIs(type(entry["ascbassb"]), dict) - def test_cbxp_can_extract_the_ascb_and_include_the_oucb(self): - cbdata = cbxp("ascb", includes=["oucb"]) + def test_cbxp_extract_ascb_and_include_oucb(self): + cbdata = cbxp.extract("ascb", includes=["oucb"]) self.assertIs(type(cbdata), list) for entry in cbdata: self.assertIs(type(entry), dict) self.assertIs(type(entry["ascboucb"]), dict) - def test_cbxp_can_extract_the_psa_and_include_the_cvt_ecvt(self): - cbdata = cbxp("psa", includes=["cvt.ecvt"]) + def test_cbxp_extract_asvt_and_include_ascb_assb_ldax(self): + cbdata = cbxp.extract("asvt", includes=["ascb.assb.ldax"]) + self.assertIs(type(cbdata), dict) + self.assertIs(type(cbdata["asvtenty"]), list) + for entry in cbdata["asvtenty"]: + self.assertIs(type(entry), dict) + self.assertIs(type(entry["ascbassb"]), dict) + self.assertIs(type(entry["ascbassb"]["assbldax"]), dict) + + def test_cbxp_extract_psa_and_include_cvt_asvt_ascb_assb_ldax(self): + cbdata = cbxp.extract("psa", includes=["cvt.asvt.ascb.assb.ldax"]) + 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) + self.assertIs(type(entry["ascbassb"]), dict) + self.assertIs(type(entry["ascbassb"]["assbldax"]), dict) + + def test_cbxp_extract_assb_and_include_ldax(self): + cbdata = cbxp.extract("assb", includes=["ldax"]) + self.assertIs(type(cbdata), list) + for entry in cbdata: + self.assertIs(type(entry), dict) + self.assertIs(type(entry["assbldax"]), dict) + + def test_cbxp_extract_ascb_and_include_assb_ldax(self): + cbdata = cbxp.extract("ascb", includes=["assb.ldax"]) + self.assertIs(type(cbdata), list) + for entry in cbdata: + self.assertIs(type(entry), dict) + self.assertIs(type(entry["ascbassb"]), dict) + self.assertIs(type(entry["ascbassb"]["assbldax"]), dict) + + def test_cbxp_extract_psa_and_include_cvt_ecvt(self): + cbdata = cbxp.extract("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"]) + def test_cbxp_extract_psa_and_include_cvt_ecvt_ascb(self): + cbdata = cbxp.extract("psa", includes=["cvt.asvt.ascb"]) self.assertIs(type(cbdata), dict) self.assertIs(type(cbdata["flccvt"]), dict) self.assertIs(type(cbdata["flccvt"]["cvtasvt"]), dict) @@ -98,16 +162,16 @@ def test_cbxp_can_extract_the_psa_and_include_the_cvt_ecvt_ascb(self): 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"]) + def test_cbxp_extract_cvt_and_include_asvt_ascb(self): + cbdata = cbxp.extract("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_ecvt_and_asvt(self): - cbdata = cbxp("cvt", includes=["ecvt", "asvt"]) + def test_cbxp_extract_cvt_and_include_ecvt_and_asvt(self): + cbdata = cbxp.extract("cvt", includes=["ecvt", "asvt"]) self.assertIs(type(cbdata), dict) self.assertIs(type(cbdata["cvtecvt"]), dict) self.assertIs(type(cbdata["cvtasvt"]), dict) @@ -115,10 +179,10 @@ def test_cbxp_include_can_extract_cvt_and_include_ecvt_and_asvt(self): for entry in cbdata["cvtasvt"]["asvtenty"]: self.assertIs(type(entry), str) - def test_cbxp_include_can_extract_psa_and_include_ecvt_asvt_and_cvt_asvt_ascb( + def test_cbxp_extract_psa_and_include_ecvt_asvt_and_cvt_asvt_ascb( self, ): - cbdata = cbxp("psa", includes=["cvt.ecvt", "cvt.asvt.ascb"]) + cbdata = cbxp.extract("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) @@ -127,10 +191,10 @@ def test_cbxp_include_can_extract_psa_and_include_ecvt_asvt_and_cvt_asvt_ascb( for entry in cbdata["flccvt"]["cvtasvt"]["asvtenty"]: self.assertIs(type(entry), dict) - def test_cbxp_include_can_extract_psa_and_include_ecvt_asvt_and_cvt_asvt_ascb_assb( + def test_cbxp_extract_psa_and_include_ecvt_asvt_and_cvt_asvt_ascb_assb( self, ): - cbdata = cbxp("psa", includes=["cvt.ecvt", "cvt.asvt.ascb.assb"]) + cbdata = cbxp.extract("psa", includes=["cvt.ecvt", "cvt.asvt.ascb.assb"]) self.assertIs(type(cbdata), dict) self.assertIs(type(cbdata["flccvt"]), dict) self.assertIs(type(cbdata["flccvt"]["cvtecvt"]), dict) @@ -140,10 +204,10 @@ def test_cbxp_include_can_extract_psa_and_include_ecvt_asvt_and_cvt_asvt_ascb_as self.assertIs(type(entry), dict) self.assertIs(type(entry["ascbassb"]), dict) - def test_cbxp_include_can_extract_psa_and_include_ecvt_asvt_and_cvt_asvt_ascb_oucb( + def test_cbxp_extract_psa_and_include_ecvt_asvt_and_cvt_asvt_ascb_oucb( self, ): - cbdata = cbxp("psa", includes=["cvt.ecvt", "cvt.asvt.ascb.oucb"]) + cbdata = cbxp.extract("psa", includes=["cvt.ecvt", "cvt.asvt.ascb.oucb"]) self.assertIs(type(cbdata), dict) self.assertIs(type(cbdata["flccvt"]), dict) self.assertIs(type(cbdata["flccvt"]["cvtecvt"]), dict) @@ -153,8 +217,8 @@ def test_cbxp_include_can_extract_psa_and_include_ecvt_asvt_and_cvt_asvt_ascb_ou self.assertIs(type(entry), dict) self.assertIs(type(entry["ascboucb"]), dict) - def test_cbxp_can_extract_psa_and_include_cvt_recursive_wildcard(self): - cbdata = cbxp("psa", includes=["cvt.**"]) + def test_cbxp_extract_psa_and_include_cvt_recursive_wildcard(self): + cbdata = cbxp.extract("psa", includes=["cvt.**"]) self.assertIs(type(cbdata), dict) self.assertIs(type(cbdata["flccvt"]), dict) self.assertIs(type(cbdata["flccvt"]["cvtecvt"]), dict) @@ -163,10 +227,11 @@ def test_cbxp_can_extract_psa_and_include_cvt_recursive_wildcard(self): for entry in cbdata["flccvt"]["cvtasvt"]["asvtenty"]: self.assertIs(type(entry), dict) self.assertIs(type(entry["ascbassb"]), dict) + self.assertIs(type(entry["ascbassb"]["assbldax"]), dict) self.assertIs(type(entry["ascboucb"]), dict) - def test_cbxp_can_extract_psa_and_include_cvt_wildcard(self): - cbdata = cbxp("psa", includes=["cvt.*"]) + def test_cbxp_extract_psa_and_include_cvt_wildcard(self): + cbdata = cbxp.extract("psa", includes=["cvt.*"]) self.assertIs(type(cbdata), dict) self.assertIs(type(cbdata["flccvt"]), dict) self.assertIs(type(cbdata["flccvt"]["cvtecvt"]), dict) @@ -175,8 +240,8 @@ def test_cbxp_can_extract_psa_and_include_cvt_wildcard(self): 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.*"]) + def test_cbxp_extract_cvt_and_include_wildcard_and_asvt_wildcard(self): + cbdata = cbxp.extract("cvt", includes=["*", "asvt.*"]) self.assertIs(type(cbdata), dict) self.assertIs(type(cbdata["cvtecvt"]), dict) self.assertIs(type(cbdata["cvtasvt"]), dict) @@ -184,10 +249,10 @@ def test_cbxp_can_extract_cvt_and_include_wildcard_and_asvt_wildcard(self): for entry in cbdata["cvtasvt"]["asvtenty"]: self.assertIs(type(entry), dict) - def test_cbxp_can_extract_cvt_and_include_wildcard_and_asvt_recursive_wildcard( + def test_cbxp_extract_cvt_and_include_wildcard_and_asvt_recursive_wildcard( self, ): - cbdata = cbxp("cvt", includes=["*", "asvt.**"]) + cbdata = cbxp.extract("cvt", includes=["*", "asvt.**"]) self.assertIs(type(cbdata), dict) self.assertIs(type(cbdata["cvtecvt"]), dict) self.assertIs(type(cbdata["cvtasvt"]), dict) @@ -197,18 +262,35 @@ def test_cbxp_can_extract_cvt_and_include_wildcard_and_asvt_recursive_wildcard( self.assertIs(type(entry["ascbassb"]), dict) self.assertIs(type(entry["ascboucb"]), dict) + def test_cbxp_extract_ascb_and_include_assb_recursive_wildcard(self): + cbdata = cbxp.extract("ascb", includes=["assb.**"]) + self.assertIs(type(cbdata), list) + for entry in cbdata: + self.assertIs(type(entry), dict) + self.assertIs(type(entry["ascbassb"]), dict) + self.assertIs(type(entry["ascbassb"]["assbldax"]), dict) + + def test_cbxp_extract_ascb_and_include_oucb_and_assb_recursive_wildcard(self): + cbdata = cbxp.extract("ascb", includes=["oucb", "assb.**"]) + self.assertIs(type(cbdata), list) + for entry in cbdata: + self.assertIs(type(entry), dict) + self.assertIs(type(entry["ascbassb"]), dict) + self.assertIs(type(entry["ascbassb"]["assbldax"]), dict) + self.assertIs(type(entry["ascboucb"]), dict) + # ============================================================================ - # Filters + # Extract -- Filters # ============================================================================ - def test_cbxp_can_use_filter(self): - cbdata = cbxp( + def test_cbxp_extract_psa_filter_eyecatcher(self): + cbdata = cbxp.extract( "psa", filters=[CBXPFilter("psapsa", CBXPFilterOperation.EQUAL, "PSA")], ) self.assertIs(type(cbdata), dict) - def test_cbxp_can_use_filter_with_wildcard_include(self): - cbdata = cbxp( + def test_cbxp_extract_psa_filter_wildcard_include(self): + cbdata = cbxp.extract( "psa", filters=[ CBXPFilter( @@ -221,8 +303,8 @@ def test_cbxp_can_use_filter_with_wildcard_include(self): ) self.assertIs(type(cbdata), dict) - def test_cbxp_can_use_filter_with_explicit_include(self): - cbdata = cbxp( + def test_cbxp_extract_filter_with_explicit_include(self): + cbdata = cbxp.extract( "psa", filters=[ CBXPFilter( @@ -235,8 +317,8 @@ def test_cbxp_can_use_filter_with_explicit_include(self): ) self.assertIs(type(cbdata), dict) - def test_cbxp_can_use_multiple_filters(self): - cbdata = cbxp( + def test_cbxp_extract_multiple_filters(self): + cbdata = cbxp.extract( "psa", filters=[ CBXPFilter( @@ -254,8 +336,8 @@ def test_cbxp_can_use_multiple_filters(self): ) self.assertIs(type(cbdata), dict) - def test_cbxp_can_use_wildcard_filter_with_string(self): - cbdata = cbxp( + def test_cbxp_extract_wildcard_filter_with_string(self): + cbdata = cbxp.extract( "psa", filters=[ CBXPFilter( @@ -268,8 +350,8 @@ def test_cbxp_can_use_wildcard_filter_with_string(self): ) self.assertIs(type(cbdata), dict) - def test_cbxp_can_use_int_filter_equal(self): - cbdata = cbxp( + def test_cbxp_extract_int_filter_equal(self): + cbdata = cbxp.extract( "psa", filters=[ CBXPFilter("cvt.asvt.ascb.ascbasid", CBXPFilterOperation.EQUAL, 1), @@ -278,8 +360,8 @@ def test_cbxp_can_use_int_filter_equal(self): ) self.assertIs(type(cbdata), dict) - def test_cbxp_can_use_int_filter_greater_than(self): - cbdata = cbxp( + def test_cbxp_extract_int_filter_greater_than(self): + cbdata = cbxp.extract( "psa", filters=[ CBXPFilter( @@ -292,8 +374,8 @@ def test_cbxp_can_use_int_filter_greater_than(self): ) self.assertIs(type(cbdata), dict) - def test_cbxp_can_use_int_filter_less_than(self): - cbdata = cbxp( + def test_cbxp_extract_int_filter_less_than(self): + cbdata = cbxp.extract( "psa", filters=[ CBXPFilter( @@ -306,8 +388,8 @@ def test_cbxp_can_use_int_filter_less_than(self): ) self.assertIs(type(cbdata), dict) - def test_cbxp_can_use_int_filter_greater_than_or_equal(self): - cbdata = cbxp( + def test_cbxp_extract_int_filter_greater_than_or_equal(self): + cbdata = cbxp.extract( "psa", filters=[ CBXPFilter( @@ -320,8 +402,8 @@ def test_cbxp_can_use_int_filter_greater_than_or_equal(self): ) self.assertIs(type(cbdata), dict) - def test_cbxp_can_use_int_filter_less_than_or_equal(self): - cbdata = cbxp( + def test_cbxp_extract_int_filter_less_than_or_equal(self): + cbdata = cbxp.extract( "psa", filters=[ CBXPFilter( @@ -334,79 +416,93 @@ def test_cbxp_can_use_int_filter_less_than_or_equal(self): ) self.assertIs(type(cbdata), dict) - def test_cbxp_can_use_int_filter_with_hex_field_equal(self): - cbdata = cbxp( + def test_cbxp_extract_int_filter_with_hex_field_equal(self): + cvtasmvt_int, _ = self.get_cvtasmvt_values() + cbdata = cbxp.extract( "cvt", - filters=[CBXPFilter("cvtasmvt", CBXPFilterOperation.EQUAL, 2281701376)], + filters=[CBXPFilter("cvtasmvt", CBXPFilterOperation.EQUAL, cvtasmvt_int)], ) self.assertIs(type(cbdata), dict) - def test_cbxp_can_use_hex_filter_with_equal(self): - cbdata = cbxp( + def test_cbxp_extract_hex_filter_with_equal(self): + _, cvtasmvt_hex = self.get_cvtasmvt_values() + cbdata = cbxp.extract( "cvt", - filters=[CBXPFilter("cvtasmvt", CBXPFilterOperation.EQUAL, "0x88000000")], + filters=[CBXPFilter("cvtasmvt", CBXPFilterOperation.EQUAL, cvtasmvt_hex)], ) self.assertIs(type(cbdata), dict) - def test_cbxp_can_use_hex_filter_with_greater_than(self): - cbdata = cbxp( + def test_cbxp_extract_hex_filter_with_greater_than(self): + cvtasmvt_int, _ = self.get_cvtasmvt_values() + cbdata = cbxp.extract( "cvt", filters=[ - CBXPFilter("cvtasmvt", CBXPFilterOperation.GREATER_THAN, "0x87FFFFFF"), + CBXPFilter( + "cvtasmvt", + CBXPFilterOperation.GREATER_THAN, + hex(cvtasmvt_int - 1), + ), ], ) self.assertIs(type(cbdata), dict) - def test_cbxp_can_use_hex_filter_with_less_than(self): - cbdata = cbxp( + def test_cbxp_extract_hex_filter_with_less_than(self): + cvtasmvt_int, _ = self.get_cvtasmvt_values() + cbdata = cbxp.extract( "cvt", filters=[ - CBXPFilter("cvtasmvt", CBXPFilterOperation.LESS_THAN, "0x88000001"), + CBXPFilter( + "cvtasmvt", + CBXPFilterOperation.LESS_THAN, + hex(cvtasmvt_int + 1), + ), ], ) self.assertIs(type(cbdata), dict) - def test_cbxp_can_use_hex_filter_with_greater_than_or_equal(self): - cbdata = cbxp( + def test_cbxp_extract_hex_filter_with_greater_than_or_equal(self): + cvtasmvt_int, _ = self.get_cvtasmvt_values() + cbdata = cbxp.extract( "cvt", filters=[ CBXPFilter( "cvtasmvt", CBXPFilterOperation.GREATER_THAN_OR_EQUAL, - "0x87FFFFFF", + hex(cvtasmvt_int), ), ], ) self.assertIs(type(cbdata), dict) - def test_cbxp_can_use_hex_filter_with_less_than_or_equal(self): - cbdata = cbxp( + def test_cbxp_extract_hex_filter_with_less_than_or_equal(self): + cvtasmvt_int, _ = self.get_cvtasmvt_values() + cbdata = cbxp.extract( "cvt", filters=[ CBXPFilter( "cvtasmvt", CBXPFilterOperation.LESS_THAN_OR_EQUAL, - "0x88000000", + hex(cvtasmvt_int), ), ], ) self.assertIs(type(cbdata), dict) - def test_cbxp_returns_none_if_no_filter_match( + def test_cbxp_extract_returns_none_if_no_filter_match( self, ): self.assertIsNone( - cbxp( + cbxp.extract( "psa", filters=[CBXPFilter("psapsa", CBXPFilterOperation.EQUAL, "PSB")], ), ) - def test_cbxp_returns_none_if_one_of_two_filters_fails( + def test_cbxp_extract_returns_none_if_one_of_two_filters_fails( self, ): self.assertIsNone( - cbxp( + cbxp.extract( "ascb", includes=["assb"], filters=[ @@ -424,8 +520,8 @@ def test_cbxp_returns_none_if_one_of_two_filters_fails( ), ) - def test_cbxp_can_use_filter_oucbtrxn_from_oucb(self): - cbdata = cbxp( + def test_cbxp_extract_filter_oucbtrxn_from_oucb(self): + cbdata = cbxp.extract( "oucb", filters=[CBXPFilter("oucbtrxn", CBXPFilterOperation.EQUAL, "OMVS")], ) @@ -434,8 +530,8 @@ def test_cbxp_can_use_filter_oucbtrxn_from_oucb(self): self.assertIs(type(entry), dict) self.assertEqual(entry["oucbtrxn"], "OMVS") - def test_cbxp_can_use_filter_on_ascb_oucb_oucbtrxn_with_explicit_include_oucb(self): - cbdata = cbxp( + def test_cbxp_extract_filter_on_ascb_oucb_oucbtrxn_with_explicit_include_oucb(self): + cbdata = cbxp.extract( "ascb", filters=[ CBXPFilter( @@ -452,10 +548,59 @@ def test_cbxp_can_use_filter_on_ascb_oucb_oucbtrxn_with_explicit_include_oucb(se self.assertIs(type(entry["ascboucb"]), dict) self.assertEqual(entry["ascboucb"]["oucbtrxn"], "OMVS") - def test_cbxp_can_use_null_filter_string( + def test_cbxp_extract_filter_on_ldax_tcthwm_equal(self): + cbdata = cbxp.extract( + "ldax", + filters=[ + CBXPFilter( + "ldax_tcthwm", + CBXPFilterOperation.EQUAL, + 0, + ), + ], + ) + self.assertIs(type(cbdata), list) + for entry in cbdata: + self.assertIs(type(entry), dict) + self.assertEqual(entry["ldax_tcthwm"], 0) + + def test_cbxp_extract_filter_on_psa_cvt_asvt_ascb_assb_ldax_tcthwm_with_include( self, ): - cbdata = cbxp( + cbdata = cbxp.extract( + "psa", + filters=[ + CBXPFilter( + "cvt.asvt.ascb.assb.ldax.ldax_tcthwm", + CBXPFilterOperation.EQUAL, + 0, + ), + ], + includes=["cvt.asvt.ascb.assb.ldax"], + ) + self.assertIs(type(cbdata), dict) + self.assertIn("flccvt", cbdata) + self.assertIs(type(cbdata["flccvt"]), dict) + self.assertIn("cvtasvt", cbdata["flccvt"]) + self.assertIs(type(cbdata["flccvt"]["cvtasvt"]), dict) + self.assertIn("asvtenty", cbdata["flccvt"]["cvtasvt"]) + self.assertIs(type(cbdata["flccvt"]["cvtasvt"]["asvtenty"]), list) + # Verify at least one entry matches the filter + self.assertGreater(len(cbdata["flccvt"]["cvtasvt"]["asvtenty"]), 0) + for entry in cbdata["flccvt"]["cvtasvt"]["asvtenty"]: + self.assertIs(type(entry), dict) + self.assertIn("ascbassb", entry) + self.assertIs(type(entry["ascbassb"]), dict) + self.assertIn("assbldax", entry["ascbassb"]) + self.assertIs(type(entry["ascbassb"]["assbldax"]), dict) + self.assertEqual( + entry["ascbassb"]["assbldax"]["ldax_tcthwm"], 0, + ) + + def test_cbxp_extract_null_filter_string( + self, + ): + cbdata = cbxp.extract( "assb", filters=[ CBXPFilter( @@ -473,80 +618,75 @@ def test_cbxp_can_use_null_filter_string( self.assertIs(type(cbdata), list) # ============================================================================ - # 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 + # Extract -- Testing Errors: Unknown Control Block # ============================================================================ - def test_cbxp_raises_cbxp_error_when_unknown_control_block_is_provided(self): + def test_cbxp_extract_raises_cbxp_error_if_unknown_control_block_is_provided(self): with self.assertRaises(CBXPError) as e: - cbxp("unknown") + cbxp.extract("unknown") self.assertEqual( - "Unknown control block 'unknown' was specified.", + "Unknown control block: unknown", str(e.exception), ) # ============================================================================ - # Errors: Bad Include Patterns + # Extract -- Testing Errors: Bad Include Patterns # ============================================================================ - def test_cbxp_raises_cbxp_error_if_asvt_ascb_is_included_with_the_psa( + def test_cbxp_extract_raises_cbxp_error_if_asvt_ascb_is_included_with_the_psa( self, ): with self.assertRaises(CBXPError) as e: - cbxp("psa", includes=["asvt.ascb"]) + cbxp.extract("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_with_the_psa(self): + def test_cbxp_extract_raises_cbxp_error_if_ascb_is_included_with_the_psa(self): with self.assertRaises(CBXPError) as e: - cbxp("psa", includes=["ascb"]) + cbxp.extract("psa", includes=["ascb"]) self.assertEqual("A bad include pattern was provided", str(e.exception)) - def test_cbxp_raises_cbxp_error_if_ecvt_is_included_with_ascb(self): + def test_cbxp_extract_raises_cbxp_error_if_ecvt_is_included_with_ascb(self): with self.assertRaises(CBXPError) as e: - cbxp("ascb", includes=["ecvt"]) + cbxp.extract("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_with_the_psa( + def test_cbxp_extract_raises_cbxp_error_if_cvt_ascb_is_included_with_the_psa( self, ): + # cvt.ecvt is also included but is not error source with self.assertRaises(CBXPError) as e: - cbxp("psa", includes=["cvt.ecvt", "cvt.ascb"]) + cbxp.extract("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_with_the_psa( + def test_cbxp_extract_raises_cbxp_error_if_ecvt_is_included_with_the_psa( self, ): + # cvt.asvt.ascb is also included but is not error source with self.assertRaises(CBXPError) as e: - cbxp("psa", includes=["ecvt", "cvt.asvt.ascb"]) + cbxp.extract("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_with_the_cvt(self): + def test_cbxp_extract_raises_cbxp_error_if_cvt_is_included_with_the_cvt(self): with self.assertRaises(CBXPError) as e: - cbxp("cvt", includes=["cvt"]) + cbxp.extract("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): + def test_cbxp_extract_raises_cbxp_error_when_pattern_cannot_contain_comma_1(self): with self.assertRaises(CBXPError) as e: - cbxp("cvt", includes=["asvt,ascb"]) + cbxp.extract("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): + def test_cbxp_extract_raises_cbxp_error_when_pattern_cannot_contain_comma_2(self): with self.assertRaises(CBXPError) as e: - cbxp("cvt", includes=["asvt,as"]) + cbxp.extract("cvt", includes=["asvt,as"]) self.assertEqual("Include patterns cannot contain commas", str(e.exception)) # ============================================================================ - # Errors: Bad Filters + # Extract -- Testing Errors: Bad Filters # ============================================================================ - def test_cbxp_raises_cbxp_error_if_filter_uses_non_included_control_block( + def test_cbxp_extract_raises_cbxp_error_if_filter_uses_non_included_control_block( self, ): with self.assertRaises(CBXPError) as e: - cbxp( + cbxp.extract( "psa", filters=[ CBXPFilter( @@ -558,11 +698,11 @@ 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_non_equality_filter_used_with_string( + def test_cbxp_extract_raises_cbxp_error_if_non_equality_filter_used_with_string( self, ): with self.assertRaises(CBXPError) as e: - cbxp( + cbxp.extract( "psa", includes=["**"], filters=[ @@ -575,56 +715,126 @@ def test_cbxp_raises_cbxp_error_if_non_equality_filter_used_with_string( ) self.assertEqual("A bad filter was provided", str(e.exception)) - def test_cbxp_raises_cbxp_error_if_filter_uses_unknown_key( + def test_cbxp_extract_raises_cbxp_error_if_filter_uses_unknown_key( self, ): with self.assertRaises(CBXPError) as e: - cbxp( + cbxp.extract( "psa", filters=["psapsb", CBXPFilterOperation.EQUAL, "PSA"], ) self.assertEqual("A bad filter was provided", str(e.exception)) - def test_cbxp_raises_cbxp_error_if_filter_passes_null_value_for_non_string( + def test_cbxp_extract_raises_cbxp_error_if_filter_passes_null_value_for_non_string( self, ): with self.assertRaises(CBXPError) as e: - cbxp( + cbxp.extract( "assb", filters=["assbasid", CBXPFilterOperation.EQUAL, ""], ) self.assertEqual("A bad filter was provided", str(e.exception)) - def test_cbxp_raises_cbxp_error_if_filter_uses_string_for_numeric_field( + def test_cbxp_extract_raises_cbxp_error_if_filter_uses_string_for_numeric_field( self, ): with self.assertRaises(CBXPError) as e: - cbxp( + cbxp.extract( "ascb", filters=["ascbasid", CBXPFilterOperation.LESS_THAN, "junk"], ) self.assertEqual("A bad filter was provided", str(e.exception)) - def test_cbxp_raises_cbxp_error_if_no_operation_provided( + def test_cbxp_extract_raises_cbxp_error_if_no_operation_provided( self, ): with self.assertRaises(CBXPError) as e: - cbxp( + cbxp.extract( "psa", filters=["junk", None, ""], ) self.assertEqual("A bad filter was provided", str(e.exception)) - def test_cbxp_raises_cbxp_error_if_filter_has_comma( + def test_cbxp_extract_raises_cbxp_error_if_filter_has_comma( self, ): with self.assertRaises(CBXPError) as e: - cbxp( + cbxp.extract( "psa", filters=["psapsa", CBXPFilterOperation.EQUAL, "PSA,PSB"], ) self.assertEqual("Filters cannot contain commas", str(e.exception)) + def test_cbxp_extract_raises_cbxp_error_if_ldax_included_with_psa(self): + with self.assertRaises(CBXPError) as e: + cbxp.extract("psa", includes=["ldax"]) + self.assertEqual("A bad include pattern was provided", str(e.exception)) + + def test_cbxp_extract_raises_cbxp_error_if_ldax_included_with_cvt(self): + with self.assertRaises(CBXPError) as e: + cbxp.extract("cvt", includes=["ldax"]) + self.assertEqual("A bad include pattern was provided", str(e.exception)) + + def test_cbxp_extract_raises_cbxp_error_if_ldax_included_with_ascb(self): + with self.assertRaises(CBXPError) as e: + cbxp.extract("ascb", includes=["ldax"]) + self.assertEqual("A bad include pattern was provided", str(e.exception)) + # ============================================================================ + # Format -- Basic Usage + # ============================================================================ + def test_cbxp_format_ascb(self): + cbdata = cbxp.format( + "ascb", + data=self.read_sample("ascb.bin"), + ) + self.assertIs(type(cbdata), dict) + + def test_cbxp_format_cvt(self): + cbdata = cbxp.format( + "cvt", + data=self.read_sample("cvt.bin"), + ) + self.assertIs(type(cbdata), dict) + + def test_cbxp_format_oucb(self): + cbdata = cbxp.format( + "oucb", + data=self.read_sample("oucb.bin"), + ) + self.assertIs(type(cbdata), dict) + + # ============================================================================ + # Format -- Debug Mode + # ============================================================================ + def test_cbxp_format_runs_in_debug_mode(self): + cbdata = cbxp.format("ascb", data=self.read_sample("ascb.bin"), debug=True) + self.assertIs(type(cbdata), dict) + + # ============================================================================ + # Format -- Testing Errors: Unknown Control Block + # ============================================================================ + def test_cbxp_format_raises_cbxp_error_if_unknown_control_block_is_provided(self): + with self.assertRaises(CBXPError) as e: + cbxp.format("unknown", data=self.read_sample("ascb.bin")) + self.assertEqual( + "Unknown control block: unknown", + str(e.exception), + ) + + # ============================================================================ + # Format -- Testing Errors: Bad Data + # ============================================================================ + def test_cbxp_format_raises_cbxp_error_if_data_is_too_small(self): + with self.assertRaises(CBXPError) as e: + cbxp.format( + "psa", + data=self.read_sample("ascb.bin"), + ) + self.assertEqual( + "Data provided is not large enough for specified control block: psa", + 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 8052adf..1d534c3 100755 --- a/tests/test.sh +++ b/tests/test.sh @@ -29,98 +29,160 @@ run_with_expected_null_response() { 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 -run_with_expected_exit_code 0 ./dist/cbxp assb -run_with_expected_exit_code 0 ./dist/cbxp oucb +## General Test Cases -# 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 -run_with_expected_exit_code 0 ./dist/cbxp -i assb ascb -run_with_expected_exit_code 0 ./dist/cbxp -i oucb ascb -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 -i cvt.ecvt -i cvt.asvt.ascb.oucb 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.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=*MASTER*" -f "cvt.asvt.ascb.ascbasid>0" -i "**" 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.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<2" -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 "cvtasmvt=2281701376" 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 -run_with_expected_exit_code 0 ./dist/cbxp -f "cvtasmvt<0x88000001" 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 'oucbtrxn=OMVS' oucb -run_with_expected_exit_code 0 ./dist/cbxp -i oucb -f 'oucb.oucbtrxn=OMVS' ascb -run_with_expected_null_response ./dist/cbxp -f psapsa=PSB psa -run_with_expected_null_response ./dist/cbxp -f "ascb.assb.assbjbns=*MASTER*" -f "ascb.ascbasid=2" -i ascb.assb asvt -run_with_expected_exit_code 0 ./dist/cbxp -f assbjbns="*MASTER*" -f assbjbni= assb -run_with_expected_exit_code 0 ./dist/cbxp -f assbjbns="*MASTER*" -f assbjbni="" assb -run_with_expected_exit_code 0 ./dist/cbxp -f assbjbns="*MASTER*" -f assbjbni='' assb - - -# Debug Mode -run_with_expected_exit_code 0 ./dist/cbxp -d psa -run_with_expected_exit_code 0 ./dist/cbxp --debug psa -# Show Usage +# General - Show Usage run_with_expected_exit_code 0 ./dist/cbxp -h run_with_expected_exit_code 0 ./dist/cbxp --help -# Show Version +# General - Show Version run_with_expected_exit_code 0 ./dist/cbxp -v run_with_expected_exit_code 0 ./dist/cbxp --version +# General - Errors: Bad Usage +run_with_expected_exit_code 255 ./dist/cbxp junk +run_with_expected_exit_code 255 ./dist/cbxp --junk + +## Extract Command + +# Extract - Show Usage +run_with_expected_exit_code 0 ./dist/cbxp extract --help +run_with_expected_exit_code 0 ./dist/cbxp extract -h +# Extract - Basic Usage +run_with_expected_exit_code 0 ./dist/cbxp extract psa +run_with_expected_exit_code 0 ./dist/cbxp extract cvt +run_with_expected_exit_code 0 ./dist/cbxp extract ecvt +run_with_expected_exit_code 0 ./dist/cbxp extract asvt +run_with_expected_exit_code 0 ./dist/cbxp extract ascb +run_with_expected_exit_code 0 ./dist/cbxp extract assb +run_with_expected_exit_code 0 ./dist/cbxp extract oucb +run_with_expected_exit_code 0 ./dist/cbxp extract ldax +# Extract - Debug Mode +run_with_expected_exit_code 0 ./dist/cbxp extract -d psa +run_with_expected_exit_code 0 ./dist/cbxp extract --debug psa +# Extract - Include Patterns +run_with_expected_exit_code 0 ./dist/cbxp extract -i cvt psa +run_with_expected_exit_code 0 ./dist/cbxp extract --include cvt psa +run_with_expected_exit_code 0 ./dist/cbxp extract -i ecvt cvt +run_with_expected_exit_code 0 ./dist/cbxp extract -i asvt cvt +run_with_expected_exit_code 0 ./dist/cbxp extract -i ascb asvt +run_with_expected_exit_code 0 ./dist/cbxp extract -i cvt.ecvt psa +run_with_expected_exit_code 0 ./dist/cbxp extract -i cvt.asvt.ascb psa +run_with_expected_exit_code 0 ./dist/cbxp extract -i asvt.ascb cvt +run_with_expected_exit_code 0 ./dist/cbxp extract -i ecvt -i asvt cvt +run_with_expected_exit_code 0 ./dist/cbxp extract -i cvt.ecvt -i cvt.asvt.ascb psa +run_with_expected_exit_code 0 ./dist/cbxp extract -i "cvt.**" psa +run_with_expected_exit_code 0 ./dist/cbxp extract -i "cvt.*" psa +run_with_expected_exit_code 0 ./dist/cbxp extract -i "asvt.*" -i "*" cvt +run_with_expected_exit_code 0 ./dist/cbxp extract -i assb ascb +run_with_expected_exit_code 0 ./dist/cbxp extract -i oucb ascb +run_with_expected_exit_code 0 ./dist/cbxp extract -i cvt.ecvt -i cvt.asvt.ascb.assb psa +run_with_expected_exit_code 0 ./dist/cbxp extract -i cvt.ecvt -i cvt.asvt.ascb.oucb psa +run_with_expected_exit_code 0 ./dist/cbxp extract -i "*" assb +run_with_expected_exit_code 0 ./dist/cbxp extract -i "**" assb +run_with_expected_exit_code 0 ./dist/cbxp extract -i "assb.**" ascb +run_with_expected_exit_code 0 ./dist/cbxp extract -i ldax assb +run_with_expected_exit_code 0 ./dist/cbxp extract -i assb.ldax ascb +run_with_expected_exit_code 0 ./dist/cbxp extract -i ascb.assb.ldax asvt +run_with_expected_exit_code 0 ./dist/cbxp extract -i cvt.asvt.ascb.assb.ldax psa +# Extract - Filters +run_with_expected_exit_code 0 ./dist/cbxp extract -f psapsa=PSA psa +run_with_expected_exit_code 0 ./dist/cbxp extract -f "cvt.asvt.ascb.assb.assbjbns=*MASTER*" -i "**" psa +run_with_expected_exit_code 0 ./dist/cbxp extract -f "cvt.asvt.ascb.assb.assbjbns=*MASTER*" -i cvt.asvt.ascb.assb psa +run_with_expected_exit_code 0 ./dist/cbxp extract -f "cvt.asvt.ascb.assb.assbjbns=*MASTER*" -f "cvt.asvt.ascb.ascbasid>0" -i "**" psa +run_with_expected_exit_code 0 ./dist/cbxp extract -f "cvt.asvt.ascb.assb.assbjbns=?MAS?ER?" -i cvt.asvt.ascb.assb psa +run_with_expected_exit_code 0 ./dist/cbxp extract -f "cvt.asvt.ascb.ascbasid=1" -i "**" psa +run_with_expected_exit_code 0 ./dist/cbxp extract -f "cvt.asvt.ascb.ascbasid>0" -i "**" psa +run_with_expected_exit_code 0 ./dist/cbxp extract -f "cvt.asvt.ascb.ascbasid<2" -i "**" psa +run_with_expected_exit_code 0 ./dist/cbxp extract -f "cvt.asvt.ascb.ascbasid>=1" -i "**" psa +run_with_expected_exit_code 0 ./dist/cbxp extract -f "cvt.asvt.ascb.ascbasid<=2" -i "**" psa +run_with_expected_exit_code 0 ./dist/cbxp extract -f "cvtasmvt=2281701376" cvt +run_with_expected_exit_code 0 ./dist/cbxp extract -f "cvtasmvt=0x88000000" cvt +run_with_expected_exit_code 0 ./dist/cbxp extract -f "cvtasmvt>0x87FFFFFF" cvt +run_with_expected_exit_code 0 ./dist/cbxp extract -f "cvtasmvt<0x88000001" cvt +run_with_expected_exit_code 0 ./dist/cbxp extract -f "cvtasmvt>=0x87FFFFFF" cvt +run_with_expected_exit_code 0 ./dist/cbxp extract -f "cvtasmvt<=0x88000000" cvt +run_with_expected_exit_code 0 ./dist/cbxp extract -f 'oucbtrxn=OMVS' oucb +run_with_expected_exit_code 0 ./dist/cbxp extract -i oucb -f 'oucb.oucbtrxn=OMVS' ascb +run_with_expected_null_response ./dist/cbxp extract -f psapsa=PSB psa +run_with_expected_null_response ./dist/cbxp extract -f "ascb.assb.assbjbns=*MASTER*" -f "ascb.ascbasid=2" -i ascb.assb asvt +run_with_expected_exit_code 0 ./dist/cbxp extract -f assbjbns="*MASTER*" -f assbjbni= assb +run_with_expected_exit_code 0 ./dist/cbxp extract -f assbjbns="*MASTER*" -f assbjbni="" assb +run_with_expected_exit_code 0 ./dist/cbxp extract -f assbjbns="*MASTER*" -f assbjbni='' assb +run_with_expected_exit_code 0 ./dist/cbxp extract -f "cvt.asvt.ascb.assb.ldax.ldax_tcthwm=0" -i cvt.asvt.ascb.assb.ldax psa +run_with_expected_exit_code 0 ./dist/cbxp extract -f "ldax_tcthwm=0" ldax +run_with_expected_null_response ./dist/cbxp extract -f "cvt.asvt.ascb.assb.ldax.ldax_id=INVALID" -i cvt.asvt.ascb.assb.ldax psa +# Extract - Errors: Bad Usage +run_with_expected_exit_code 255 ./dist/cbxp extract +run_with_expected_exit_code 255 ./dist/cbxp extract --junk +run_with_expected_exit_code 255 ./dist/cbxp extract --junk junk +run_with_expected_exit_code 255 ./dist/cbxp extract -x "unknown flag" cvt +run_with_expected_exit_code 255 ./dist/cbxp extract -i cvt +run_with_expected_exit_code 255 ./dist/cbxp extract -i -i cvt psa +run_with_expected_exit_code 255 ./dist/cbxp extract -f psa +run_with_expected_exit_code 255 ./dist/cbxp extract -f psapsa=psa +run_with_expected_exit_code 255 ./dist/cbxp extract -F tests/samples/ascb.bin psa +run_with_expected_exit_code 255 ./dist/cbxp extract -o 1 psa +# Extract - Errors: Unknown Control Block +run_with_expected_exit_code 255 ./dist/cbxp extract unknown +# Extract - Errors: Bad Include Patterns +run_with_expected_exit_code 255 ./dist/cbxp extract -i asvt,ascb cvt +run_with_expected_exit_code 255 ./dist/cbxp extract -i asvt,as cvt +run_with_expected_exit_code 255 ./dist/cbxp extract -i asvt.ascb psa +run_with_expected_exit_code 255 ./dist/cbxp extract -i ascb psa +run_with_expected_exit_code 255 ./dist/cbxp extract -i ecvt ascb +run_with_expected_exit_code 255 ./dist/cbxp extract -i cvt.ecvt -i cvt.ascb psa +run_with_expected_exit_code 255 ./dist/cbxp extract -i cvt.asvt.ascb -i ecvt psa +run_with_expected_exit_code 255 ./dist/cbxp extract -i cvt cvt +run_with_expected_exit_code 255 ./dist/cbxp extract -i ldax psa +run_with_expected_exit_code 255 ./dist/cbxp extract -i ldax cvt +run_with_expected_exit_code 255 ./dist/cbxp extract -i ldax ascb +# Extract - Errors: Bad Filters +run_with_expected_exit_code 255 ./dist/cbxp extract -f "cvt.asvt.ascb.assb.assbjbns=*master*" psa +run_with_expected_exit_code 255 ./dist/cbxp extract -f "cvt.asvt.ascb.assb.assbjbns<*master*" -i "**" psa +run_with_expected_exit_code 255 ./dist/cbxp extract -f psapsb=PSA psa +run_with_expected_exit_code 255 ./dist/cbxp extract -f assbasid= assb +run_with_expected_exit_code 255 ./dist/cbxp extract -f 'ascbasid<=junk' ascb +run_with_expected_exit_code 255 ./dist/cbxp extract -f "psapsa=psa,cvt.asvt.ascb.ascbasid<2" cvt +run_with_expected_exit_code 255 ./dist/cbxp extract -f junk psa + +## Format Command -# 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 -run_with_expected_exit_code 255 ./dist/cbxp -d -d psa -run_with_expected_exit_code 255 ./dist/cbxp -f psa -run_with_expected_exit_code 255 ./dist/cbxp -f psapsa=psa -run_with_expected_exit_code 255 ./dist/cbxp --debug -d psa -# Errors: Unknown Control Block -run_with_expected_exit_code 255 ./dist/cbxp unknown -# Errors: Bad Include Patterns -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 -# Errors: Bad Filters -run_with_expected_exit_code 255 ./dist/cbxp -f "cvt.asvt.ascb.assb.assbjbns=*master*" psa -run_with_expected_exit_code 255 ./dist/cbxp -f "cvt.asvt.ascb.assb.assbjbns<*master*" -i "**" psa -run_with_expected_exit_code 255 ./dist/cbxp -f psapsb=PSA psa -run_with_expected_exit_code 255 ./dist/cbxp -f assbasid= assb -run_with_expected_exit_code 255 ./dist/cbxp -f 'ascbasid<=junk' ascb -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 -f junk psa +# Format - Show Usage +run_with_expected_exit_code 0 ./dist/cbxp format -h +run_with_expected_exit_code 0 ./dist/cbxp format --help +# Format - Basic Usage +run_with_expected_exit_code 0 ./dist/cbxp format -F tests/samples/ascb.bin ascb +run_with_expected_exit_code 0 cat tests/samples/cvt.bin | ./dist/cbxp format cvt +run_with_expected_exit_code 0 ./dist/cbxp format -F tests/samples/oucb.bin oucb +# Format - Debug Mode +run_with_expected_exit_code 0 ./dist/cbxp format -F tests/samples/ascb.bin -d ascb +run_with_expected_exit_code 0 ./dist/cbxp format -F tests/samples/ascb.bin --debug ascb +# Format - Offset +run_with_expected_exit_code 0 ./dist/cbxp format -F tests/samples/ascb_offset_0x40.bin -o 0x40 ascb +run_with_expected_exit_code 0 ./dist/cbxp format -F tests/samples/ascb_offset_0x40.bin -o 64 ascb +run_with_expected_exit_code 0 cat tests/samples/oucb_offset_0x03a8.bin | ./dist/cbxp format -o 0x03a8 oucb +run_with_expected_exit_code 0 cat tests/samples/oucb_offset_0x03a8.bin | ./dist/cbxp format -o 936 oucb +# Format - Errors: Bad Usage +run_with_expected_exit_code 255 ./dist/cbxp format +run_with_expected_exit_code 255 ./dist/cbxp format --junk +run_with_expected_exit_code 255 ./dist/cbxp format --junk junk +run_with_expected_exit_code 255 ./dist/cbxp format psa +run_with_expected_exit_code 255 ./dist/cbxp format -i cvt psa +run_with_expected_exit_code 255 ./dist/cbxp format -f psapsa=PSA psa +# Format - Errors: Unknown Control Block +run_with_expected_exit_code 255 ./dist/cbxp format -F tests/samples/ascb.bin -d unknown +# Format - Errors: Bad Offset +run_with_expected_exit_code 255 ./dist/cbxp format -F tests/samples/ascb.bin -o 999999 ascb +run_with_expected_exit_code 255 ./dist/cbxp format -F tests/samples/ascb.bin -o -1 ascb +run_with_expected_exit_code 255 ./dist/cbxp format -F tests/samples/ascb.bin -o JUNK ascb +run_with_expected_exit_code 255 ./dist/cbxp format -F tests/samples/ascb.bin -o 5.5 ascb +# Format - Errors: Error Opening File +run_with_expected_exit_code 255 ./dist/cbxp format -F tests/samples/notreal.bin psa +# Format - Errors: No File/Pipe provided +run_with_expected_exit_code 255 ./dist/cbxp format psa +# Format - Errors: File and Pipe +run_with_expected_exit_code 255 sh -c 'cat tests/samples/cvt.bin | ./dist/cbxp format -F tests/samples/cvt.bin cvt' +# Format - Errors: Data Too Small +run_with_expected_exit_code 255 ./dist/cbxp format -F tests/samples/ascb.bin psa echo " -------------------------------- " echo " -------------------------------- "