From 6f45e91db4cbb21a835d1f8a7f546dde007152d4 Mon Sep 17 00:00:00 2001 From: Elijah Swift Date: Fri, 17 Oct 2025 13:46:41 -0400 Subject: [PATCH 01/37] Feature/includeparameter (#14) * Add skeleton for inclusion list parameter Extend interface/skeleton for include_list * Input POinter Attempt to add input pointer parameter for control blocks * Definitions outside of if statements * Attempt to map the intended inclusion list * Try Virtual Function to keep Control Block in Explorer * No longer have get return * Attempt to add the meat of the function -Add methods and members to control blocks to get name of control block, specific control blocks within it, and all control blocks within it -add functions in control block explorer to parse inclusion "map" into actionable data name fix * Update psa.cpp Update psa.cpp * Update cvt.cpp * Update asvt.cpp * algorithm cleanup * better cleanup * Update control_block.cpp * Update main.cpp * Finish addressing merge issues and commit hooks -Minor code updates that were lost in merge commit -Bring new code up to standard for cppcheck -Format new code with clang-format * Update _cbxp.c * Streamline Control Block Explorer Class Update control_block_explorer.hpp * Massive refactor -Shave 2 step process down to one -Change serialized json inclusion map to use a vector of strings still split by "dot" operators * Error Handling Logic * BIG UPDATE PR COMMENTS -Switch pre-processing to one hash map function -use try/catch with custom errors rather than passing return codes everywhere -style and name changes -Enforce more rigid parm structure on entry -Fix some behavioral bugs and oversights in inclusion preprocessing -General streamlining and refactoring of functions, methods, classes, etc. * Another Big Refactor -PR comments (mostly style, but streamlining of error code as well) -Reworked base and derived classes to allow for includables to be defined to the base class and include_map to be defined to the base and derived classes * Update ascb.cpp * Update control_block.hpp * . * .. * ... * PR Comments -ASCB pointer deref in ASVT -Minor name changes -Remove double wildcard error -Add control_block_name_ private member and add initialization to constructor -move include_map_ to protected and remove private using statement * Update asvt.cpp * Update asvt.cpp * PR Comments Mostly renaming things streamlining some unnecessary text, parms and strings * Update control_block.cpp * Update main.cpp * Last round of PR comments string compare with == remove vestiges of old mechanisms for control block management name changes minor tweaks * Update cvt.cpp * Update cvt.cpp * Update cvt.cpp * Final comments Update control_block_explorer.cpp * comments * Last Comments * include changes * Last round of comments * debug --- .vscode/settings.json | 115 +++++++++++----------- cbxp/cbxp.cpp | 5 +- cbxp/cbxp.h | 3 +- cbxp/control_block_error.hpp | 27 ++++++ cbxp/control_block_explorer.cpp | 86 ++++++++++++----- cbxp/control_block_explorer.hpp | 7 +- cbxp/control_blocks/ascb.cpp | 134 ++++++++++++-------------- cbxp/control_blocks/ascb.hpp | 4 +- cbxp/control_blocks/asvt.cpp | 78 ++++++++++----- cbxp/control_blocks/asvt.hpp | 4 +- cbxp/control_blocks/control_block.cpp | 90 +++++++++++++++++ cbxp/control_blocks/control_block.hpp | 19 ++++ cbxp/control_blocks/cvt.cpp | 65 ++++++++----- cbxp/control_blocks/cvt.hpp | 4 +- cbxp/control_blocks/ecvt.cpp | 28 +++--- cbxp/control_blocks/ecvt.hpp | 4 +- cbxp/control_blocks/psa.cpp | 27 ++++-- cbxp/control_blocks/psa.hpp | 4 +- cbxp/main.cpp | 92 +++++++++++++----- cbxp/python/_cbxp.c | 9 +- python/cbxp/_C.pyi | 2 +- python/cbxp/cbxp.py | 31 ++++-- 22 files changed, 571 insertions(+), 267 deletions(-) create mode 100644 cbxp/control_block_error.hpp create mode 100644 cbxp/control_blocks/control_block.cpp diff --git a/.vscode/settings.json b/.vscode/settings.json index 68fdb84..5cd750a 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,61 +1,62 @@ { "files.associations": { - "*.py": "python", - "__bit_reference": "cpp", - "__hash_table": "cpp", - "__locale": "cpp", - "__node_handle": "cpp", - "__split_buffer": "cpp", - "__verbose_abort": "cpp", - "array": "cpp", - "bitset": "cpp", - "cctype": "cpp", - "charconv": "cpp", - "clocale": "cpp", - "cmath": "cpp", - "complex": "cpp", - "cstdarg": "cpp", - "cstdint": "cpp", - "cstdio": "cpp", - "cstdlib": "cpp", - "cstring": "cpp", - "ctime": "cpp", - "cwchar": "cpp", - "cwctype": "cpp", - "deque": "cpp", - "execution": "cpp", - "memory": "cpp", - "initializer_list": "cpp", - "iomanip": "cpp", - "ios": "cpp", - "iosfwd": "cpp", - "iostream": "cpp", - "istream": "cpp", - "limits": "cpp", - "locale": "cpp", - "mutex": "cpp", - "new": "cpp", - "optional": "cpp", - "print": "cpp", - "queue": "cpp", - "ratio": "cpp", - "sstream": "cpp", - "stack": "cpp", - "stdexcept": "cpp", - "streambuf": "cpp", - "string": "cpp", - "string_view": "cpp", - "typeinfo": "cpp", - "unordered_map": "cpp", - "variant": "cpp", - "vector": "cpp", - "algorithm": "cpp", - "__tree": "cpp", - "any": "cpp", - "forward_list": "cpp", - "map": "cpp", - "span": "cpp", - "valarray": "cpp", - "csignal": "cpp" + "*.py": "python", + "__bit_reference": "cpp", + "__hash_table": "cpp", + "__locale": "cpp", + "__node_handle": "cpp", + "__split_buffer": "cpp", + "__verbose_abort": "cpp", + "array": "cpp", + "bitset": "cpp", + "cctype": "cpp", + "charconv": "cpp", + "clocale": "cpp", + "cmath": "cpp", + "complex": "cpp", + "cstdarg": "cpp", + "cstdint": "cpp", + "cstdio": "cpp", + "cstdlib": "cpp", + "cstring": "cpp", + "ctime": "cpp", + "cwchar": "cpp", + "cwctype": "cpp", + "deque": "cpp", + "execution": "cpp", + "memory": "cpp", + "initializer_list": "cpp", + "iomanip": "cpp", + "ios": "cpp", + "iosfwd": "cpp", + "iostream": "cpp", + "istream": "cpp", + "limits": "cpp", + "locale": "cpp", + "mutex": "cpp", + "new": "cpp", + "optional": "cpp", + "print": "cpp", + "queue": "cpp", + "ratio": "cpp", + "sstream": "cpp", + "stack": "cpp", + "stdexcept": "cpp", + "streambuf": "cpp", + "string": "cpp", + "string_view": "cpp", + "typeinfo": "cpp", + "unordered_map": "cpp", + "variant": "cpp", + "vector": "cpp", + "algorithm": "cpp", + "__tree": "cpp", + "any": "cpp", + "forward_list": "cpp", + "map": "cpp", + "span": "cpp", + "valarray": "cpp", + "csignal": "cpp", + "cstddef": "cpp" } } diff --git a/cbxp/cbxp.cpp b/cbxp/cbxp.cpp index b1b4190..68c5f87 100644 --- a/cbxp/cbxp.cpp +++ b/cbxp/cbxp.cpp @@ -7,7 +7,8 @@ #include "cbxp_result.h" #include "control_block_explorer.hpp" -cbxp_result_t* cbxp(const char* control_block_name, bool debug) { +cbxp_result_t* cbxp(const char* control_block_name, const char* includes_string, + bool debug) { nlohmann::json control_block_json; std::string control_block = control_block_name; @@ -16,7 +17,7 @@ cbxp_result_t* cbxp(const char* control_block_name, bool debug) { CBXP::ControlBlockExplorer explorer = CBXP::ControlBlockExplorer(&cbxp_result, debug); - explorer.exploreControlBlock(control_block); + explorer.exploreControlBlock(control_block, includes_string); return &cbxp_result; } diff --git a/cbxp/cbxp.h b/cbxp/cbxp.h index 220548f..5ac1292 100644 --- a/cbxp/cbxp.h +++ b/cbxp/cbxp.h @@ -16,7 +16,8 @@ avoid memory leaks: result.result_json */ -cbxp_result_t *cbxp(const char *control_block_name, bool debug); +cbxp_result_t* cbxp(const char* control_block_name, const char* includes_string, + bool debug); #ifdef __cplusplus } diff --git a/cbxp/control_block_error.hpp b/cbxp/control_block_error.hpp new file mode 100644 index 0000000..65902a0 --- /dev/null +++ b/cbxp/control_block_error.hpp @@ -0,0 +1,27 @@ +#ifndef __CONTROL_BLOCK_ERROR_H_ +#define __CONTROL_BLOCK_ERROR_H_ + +namespace CBXP { +enum Error { BadControlBlock = 1, BadInclude }; +class CBXPError : public std::exception { + private: + Error error_code_; + + public: + explicit CBXPError(const Error& rc) : error_code_(rc) {} + const int getErrorCode() const { return error_code_; } +}; + +class ControlBlockError : public CBXPError { + public: + ControlBlockError() : CBXPError(Error::BadControlBlock) {} +}; + +class IncludeError : public CBXPError { + public: + IncludeError() : CBXPError(Error::BadInclude) {} +}; + +} // namespace CBXP + +#endif diff --git a/cbxp/control_block_explorer.cpp b/cbxp/control_block_explorer.cpp index b6fd3fe..ea1c95e 100644 --- a/cbxp/control_block_explorer.cpp +++ b/cbxp/control_block_explorer.cpp @@ -5,14 +5,48 @@ #include #include "cbxp.h" +#include "control_block_error.hpp" #include "control_blocks/ascb.hpp" #include "control_blocks/asvt.hpp" +#include "control_blocks/control_block.hpp" #include "control_blocks/cvt.hpp" #include "control_blocks/ecvt.hpp" #include "control_blocks/psa.hpp" #include "logger.hpp" namespace CBXP { + +std::vector ControlBlockExplorer::createIncludeList( + const std::string& includes_string) { + if (includes_string == "") { + return {}; + } + + std::vector includes = {}; + + Logger::getInstance().debug( + "Creating include list from the provided include list string: " + + includes_string); + + const std::string del = ","; + std::string entry; + size_t index = 0; + + auto pos = includes_string.find(del); + + while (pos != std::string::npos) { + entry = includes_string.substr(index, pos); + includes.push_back(entry); + index += pos + 1; + pos = includes_string.substr(index, std::string::npos).find(del); + } + entry = includes_string.substr(index, pos); + includes.push_back(entry); + Logger::getInstance().debug("Done."); + + return includes; +} + ControlBlockExplorer::ControlBlockExplorer(cbxp_result_t* p_result, bool debug) { Logger::getInstance().setDebug(debug); @@ -24,29 +58,36 @@ ControlBlockExplorer::ControlBlockExplorer(cbxp_result_t* p_result, p_result->result_json_length = 0; p_result->result_json = nullptr; - p_result->return_code = -1; + p_result->return_code = 0; - _p_result = p_result; + p_result_ = p_result; } void ControlBlockExplorer::exploreControlBlock( - const std::string& control_block_name) { - nlohmann::json control_block_json = {}; + const std::string& control_block_name, const std::string& includes_string) { + std::vector includes = + ControlBlockExplorer::createIncludeList(includes_string); Logger::getInstance().debug("Extracting '" + control_block_name + "' control block data..."); - if (control_block_name == "psa") { - control_block_json = CBXP::PSA().get(); - } else if (control_block_name == "cvt") { - control_block_json = CBXP::CVT().get(); - } else if (control_block_name == "ecvt") { - control_block_json = CBXP::ECVT().get(); - } else if (control_block_name == "ascb") { - control_block_json = CBXP::ASCB().get(); - } else if (control_block_name == "asvt") { - control_block_json = CBXP::ASVT().get(); - } else { + nlohmann::json control_block_json; + try { + if (control_block_name == "psa") { + control_block_json = PSA(includes).get(); + } else if (control_block_name == "cvt") { + control_block_json = CVT(includes).get(); + } else if (control_block_name == "ecvt") { + control_block_json = ECVT(includes).get(); + } else if (control_block_name == "ascb") { + control_block_json = ASCB(includes).get(); + } else if (control_block_name == "asvt") { + control_block_json = ASVT(includes).get(); + } else { + throw ControlBlockError(); + } + } catch (const CBXPError& e) { + p_result_->return_code = e.getErrorCode(); return; } @@ -57,17 +98,16 @@ void ControlBlockExplorer::exploreControlBlock( Logger::getInstance().debug("Control Block JSON: " + control_block_json_string); - _p_result->result_json_length = control_block_json_string.length(); - _p_result->result_json = new char[_p_result->result_json_length]; - _p_result->result_json[_p_result->result_json_length] = 0; + p_result_->result_json_length = control_block_json_string.length(); + p_result_->result_json = new char[p_result_->result_json_length]; + p_result_->result_json[p_result_->result_json_length] = 0; - Logger::getInstance().debugAllocate(_p_result->result_json, 64, - _p_result->result_json_length); + Logger::getInstance().debugAllocate(p_result_->result_json, 64, + p_result_->result_json_length); - std::strncpy(_p_result->result_json, control_block_json_string.c_str(), - _p_result->result_json_length); + std::strncpy(p_result_->result_json, control_block_json_string.c_str(), + p_result_->result_json_length); - _p_result->return_code = 0; return; } diff --git a/cbxp/control_block_explorer.hpp b/cbxp/control_block_explorer.hpp index f6f1aae..7542b17 100644 --- a/cbxp/control_block_explorer.hpp +++ b/cbxp/control_block_explorer.hpp @@ -8,11 +8,14 @@ namespace CBXP { class ControlBlockExplorer { private: - cbxp_result_t* _p_result; + cbxp_result_t* p_result_; + static std::vector createIncludeList( + const std::string& includes_string); public: ControlBlockExplorer(cbxp_result_t* p_result, bool debug); - void exploreControlBlock(const std::string& control_block_name); + void exploreControlBlock(const std::string& control_block_name, + const std::string& includes_string); }; } // namespace CBXP diff --git a/cbxp/control_blocks/ascb.cpp b/cbxp/control_blocks/ascb.cpp index 7f887a9..e8d7312 100644 --- a/cbxp/control_blocks/ascb.cpp +++ b/cbxp/control_blocks/ascb.cpp @@ -4,95 +4,83 @@ #include #include -#include #include #include #include #include +#include "asvt.hpp" #include "logger.hpp" namespace CBXP { -nlohmann::json ASCB::get() { - const struct psa* __ptr32 p_psa = 0; - - const struct cvtmap* __ptr32 p_cvtmap = - // 'nullPointer' is a false positive because the PSA starts at address 0 - // cppcheck-suppress nullPointer - static_cast(p_psa->flccvt); - const asvt_t* __ptr32 p_asvt = - static_cast(p_cvtmap->cvtasvt); - +nlohmann::json ASCB::get(void* __ptr32 p_control_block) { nlohmann::json ascb_json = {}; + const ascb* __ptr32 p_ascb; + + if (p_control_block == nullptr) { + // PSA starts at address 0 + const struct psa* __ptr32 p_psa = 0; - ascb_json["ascbs"] = std::vector(); - std::vector& ascbs = - ascb_json["ascbs"].get_ref&>(); - ascbs.reserve(p_asvt->asvtmaxu); + const struct cvtmap* __ptr32 p_cvtmap = + // 'nullPointer' is a false positive because the PSA starts at address 0 + // cppcheck-suppress nullPointer + static_cast(p_psa->flccvt); + asvt_t* __ptr32 p_asvt = static_cast(p_cvtmap->cvtasvt); - const uint32_t* __ptr32 p_ascb_addr = - reinterpret_cast(&p_asvt->asvtenty); + ascb_json["ascbs"] = std::vector(); + std::vector& ascbs = + ascb_json["ascbs"].get_ref&>(); + ascbs.reserve(p_asvt->asvtmaxu); - for (int i = 0; i < p_asvt->asvtmaxu; i++) { - if (0x80000000 & *p_ascb_addr) { - Logger::getInstance().debug(formatter_.getHex(p_ascb_addr) + - " is not a valid ASCB address"); - p_ascb_addr++; - continue; + uint32_t* __ptr32 p_ascb_addr = + reinterpret_cast(&p_asvt->asvtenty); + for (int i = 0; i < p_asvt->asvtmaxu; i++) { + if (0x80000000 & *p_ascb_addr) { + Logger::getInstance().debug(formatter_.getHex(p_ascb_addr) + + " is not a valid ASCB address"); + p_ascb_addr++; + continue; + } + ascbs.push_back(ASCB::get(reinterpret_cast(*p_ascb_addr))); + p_ascb_addr++; // This SHOULD increment the pointer by 4 bytes. } - nlohmann::json ascb_entry_json = {}; + return ascbs; + } else { + p_ascb = static_cast(p_control_block); + } - struct ascb* __ptr32 p_ascb = - reinterpret_cast(*p_ascb_addr); + ascb_json["ascbassb"] = formatter_.getHex(&(p_ascb->ascbassb)); + ascb_json["ascbasxb"] = formatter_.getHex(&(p_ascb->ascbasxb)); - Logger::getInstance().debug("ASCB hex dump:"); - Logger::getInstance().hexDump(reinterpret_cast(p_ascb), - sizeof(struct ascb)); + Logger::getInstance().debug("ASCB hex dump:"); + Logger::getInstance().hexDump(reinterpret_cast(p_ascb), + sizeof(struct ascb)); - ascb_entry_json["ascbasid"] = static_cast(p_ascb->ascbasid); - ascb_entry_json["ascbassb"] = - formatter_.getHex(&(p_ascb->ascbassb)); - ascb_entry_json["ascbasxb"] = - formatter_.getHex(&(p_ascb->ascbasxb)); - ascb_entry_json["ascbdcti"] = p_ascb->ascbdcti; - ascb_entry_json["ascbejst"] = formatter_.getBitmap( - reinterpret_cast(&p_ascb->ascbejst)); - ascb_entry_json["ascbflg3"] = - formatter_.getBitmap(p_ascb->ascbflg3); - ascb_entry_json["ascbfw3"] = formatter_.getBitmap( - reinterpret_cast(&p_ascb->ascbfw3)); - ascb_entry_json["ascbjbni"] = - formatter_.getHex(&(p_ascb->ascbjbni)); - ascb_entry_json["ascbjbns"] = - formatter_.getHex(&(p_ascb->ascbjbns)); - ascb_entry_json["ascblsqe"] = p_ascb->ascblsqe; - ascb_entry_json["ascblsqt"] = p_ascb->ascblsqt; - ascb_entry_json["ascbnoft"] = - formatter_.getBitmap(p_ascb->ascbnoft); - ascb_entry_json["ascboucb"] = - formatter_.getHex(&(p_ascb->ascboucb)); - ascb_entry_json["ascbouxb"] = - formatter_.getHex(&(p_ascb->ascbouxb)); - ascb_entry_json["ascbpo1m"] = - formatter_.getBitmap(p_ascb->ascbpo1m); - ascb_entry_json["ascbp1m0"] = - formatter_.getBitmap(p_ascb->ascbp1m0); - ascb_entry_json["ascbrsme"] = - formatter_.getHex(&(p_ascb->ascbrsme)); - ascb_entry_json["ascbsdbf"] = - formatter_.getBitmap(p_ascb->ascbsdbf); - ascb_entry_json["ascbsrbt"] = formatter_.getBitmap( - reinterpret_cast(&p_ascb->ascbsrbt)); - ascb_entry_json["ascbtcbe"] = - formatter_.getBitmap(p_ascb->ascbtcbe); - ascb_entry_json["ascbtcbs"] = p_ascb->ascbtcbs; - ascb_entry_json["ascbxtcb"] = - formatter_.getHex(&(p_ascb->ascbxtcb)); - ascb_entry_json["ascbzcx"] = - formatter_.getBitmap(p_ascb->ascbzcx); - ascbs.push_back(ascb_entry_json); - p_ascb_addr++; // This SHOULD increment the pointer by 4 bytes. - } + ascb_json["ascbascb"] = formatter_.getString(p_ascb->ascbascb, 4); + ascb_json["ascbasid"] = formatter_.getBitmap(p_ascb->ascbasid); + ascb_json["ascbdcti"] = p_ascb->ascbdcti; + ascb_json["ascbejst"] = formatter_.getBitmap( + reinterpret_cast(&p_ascb->ascbejst)); + ascb_json["ascbflg3"] = formatter_.getBitmap(p_ascb->ascbflg3); + ascb_json["ascbfw3"] = formatter_.getBitmap( + reinterpret_cast(&p_ascb->ascbfw3)); + ascb_json["ascbjbni"] = formatter_.getHex(&(p_ascb->ascbjbni)); + ascb_json["ascbjbns"] = formatter_.getHex(&(p_ascb->ascbjbns)); + ascb_json["ascblsqe"] = p_ascb->ascblsqe; + ascb_json["ascblsqt"] = p_ascb->ascblsqt; + ascb_json["ascbnoft"] = formatter_.getBitmap(p_ascb->ascbnoft); + ascb_json["ascboucb"] = formatter_.getHex(&(p_ascb->ascboucb)); + ascb_json["ascbouxb"] = formatter_.getHex(&(p_ascb->ascbouxb)); + ascb_json["ascbpo1m"] = formatter_.getBitmap(p_ascb->ascbpo1m); + ascb_json["ascbp1m0"] = formatter_.getBitmap(p_ascb->ascbp1m0); + ascb_json["ascbrsme"] = formatter_.getHex(&(p_ascb->ascbrsme)); + ascb_json["ascbsdbf"] = formatter_.getBitmap(p_ascb->ascbsdbf); + ascb_json["ascbsrbt"] = formatter_.getBitmap( + reinterpret_cast(&p_ascb->ascbsrbt)); + ascb_json["ascbtcbe"] = formatter_.getBitmap(p_ascb->ascbtcbe); + ascb_json["ascbtcbs"] = p_ascb->ascbtcbs; + ascb_json["ascbxtcb"] = formatter_.getHex(&(p_ascb->ascbxtcb)); + ascb_json["ascbzcx"] = formatter_.getBitmap(p_ascb->ascbzcx); return ascb_json; } diff --git a/cbxp/control_blocks/ascb.hpp b/cbxp/control_blocks/ascb.hpp index 4d7020b..f15b841 100644 --- a/cbxp/control_blocks/ascb.hpp +++ b/cbxp/control_blocks/ascb.hpp @@ -7,7 +7,9 @@ namespace CBXP { class ASCB : public ControlBlock { public: - nlohmann::json get(); + nlohmann::json get(void* __ptr32 p_control_block = nullptr) override; + explicit ASCB(const std::vector& includes) + : ControlBlock("ascb", {}, includes) {} }; } // namespace CBXP diff --git a/cbxp/control_blocks/asvt.cpp b/cbxp/control_blocks/asvt.cpp index c0e8521..eff61dd 100644 --- a/cbxp/control_blocks/asvt.cpp +++ b/cbxp/control_blocks/asvt.cpp @@ -3,30 +3,73 @@ #include #include -#include #include #include #include #include +#include "ascb.hpp" +#include "asvt.hpp" #include "logger.hpp" namespace CBXP { -nlohmann::json ASVT::get() { - struct psa* __ptr32 p_psa = 0; - struct cvtmap* __ptr32 p_cvtmap = - // 'nullPointer' is a false positive because the PSA starts at address 0 - // cppcheck-suppress nullPointer - static_cast(p_psa->flccvt); - const asvt_t* __ptr32 p_asvt = - static_cast(p_cvtmap->cvtasvt); +nlohmann::json ASVT::get(void* __ptr32 p_control_block) { + const asvt_t* __ptr32 p_asvt; + nlohmann::json asvt_json = {}; + + if (p_control_block == nullptr) { + const struct psa* __ptr32 p_psa = 0; + const struct cvtmap* __ptr32 p_cvtmap = + // 'nullPointer' is a false positive because the PSA starts at address 0 + // cppcheck-suppress nullPointer + static_cast(p_psa->flccvt); + p_asvt = static_cast(p_cvtmap->cvtasvt); + } else { + p_asvt = static_cast(p_control_block); + } + + Logger::getInstance().debug("ASCB pointers:"); + Logger::getInstance().hexDump( + reinterpret_cast(&p_asvt->asvtenty), p_asvt->asvtmaxu * 4); + + std::vector ascbs; + ascbs.reserve(p_asvt->asvtmaxu); + uint32_t* __ptr32 p_ascb = const_cast( + reinterpret_cast(&p_asvt->asvtenty)); + + for (int i = 0; i < p_asvt->asvtmaxu; i++) { + ascbs.push_back(formatter_.getHex(p_ascb)); + p_ascb++; // This SHOULD increment the pointer by 4 bytes. + } + + asvt_json["asvtenty"] = ascbs; Logger::getInstance().debug("ASVT hex dump:"); Logger::getInstance().hexDump(reinterpret_cast(p_asvt), sizeof(asvt_t)); + for (const auto& [include, include_includes] : include_map_) { + if (include == "ascb") { + nlohmann::json ascbs_json; + CBXP::ASCB ascb(include_includes); + uint32_t* __ptr32 p_ascb_addr = const_cast( + reinterpret_cast(&p_asvt->asvtenty)); + for (int i = 0; i < p_asvt->asvtmaxu; i++) { + if (0x80000000 & *p_ascb_addr) { + Logger::getInstance().debug(formatter_.getHex(p_ascb_addr) + + " is not a valid ASCB address"); + p_ascb_addr++; + continue; + } + nlohmann::json ascb_json = + ascb.get(reinterpret_cast(*p_ascb_addr)); + ascbs_json.push_back(ascb_json); + p_ascb_addr++; // This SHOULD increment the pointer by 4 bytes. + } + asvt_json["asvtenty"] = ascbs_json; + } + } // Get fields - nlohmann::json asvt_json = {}; asvt_json["asvthwmasid"] = p_asvt->asvthwmasid; asvt_json["asvtcurhighasid"] = p_asvt->asvtcurhighasid; asvt_json["asvtreua"] = formatter_.getHex(p_asvt->asvtreua); @@ -42,21 +85,6 @@ nlohmann::json ASVT::get() { asvt_json["asvtmdsc"] = p_asvt->asvtmdsc; asvt_json["asvtfrst"] = formatter_.getHex(p_asvt->asvtfrst); - Logger::getInstance().debug("ASCB pointers:"); - Logger::getInstance().hexDump( - reinterpret_cast(&p_asvt->asvtenty), p_asvt->asvtmaxu * 4); - - std::vector ascbs; - ascbs.reserve(p_asvt->asvtmaxu); - const uint32_t* __ptr32 p_ascb = - reinterpret_cast(&p_asvt->asvtenty); - - for (int i = 0; i < p_asvt->asvtmaxu; i++) { - ascbs.push_back(formatter_.getHex(p_ascb)); - p_ascb++; // This SHOULD increment the pointer by 4 bytes. - } - - asvt_json["asvtenty"] = ascbs; return asvt_json; } } // namespace CBXP diff --git a/cbxp/control_blocks/asvt.hpp b/cbxp/control_blocks/asvt.hpp index 188dee9..a6e91c9 100644 --- a/cbxp/control_blocks/asvt.hpp +++ b/cbxp/control_blocks/asvt.hpp @@ -32,7 +32,9 @@ namespace CBXP { class ASVT : public ControlBlock { public: - nlohmann::json get(); + nlohmann::json get(void* __ptr32 p_control_block = nullptr) override; + explicit ASVT(const std::vector& includes) + : ControlBlock("asvt", {"ascb"}, includes) {} }; } // namespace CBXP diff --git a/cbxp/control_blocks/control_block.cpp b/cbxp/control_blocks/control_block.cpp new file mode 100644 index 0000000..02616ab --- /dev/null +++ b/cbxp/control_blocks/control_block.cpp @@ -0,0 +1,90 @@ +#include "control_block.hpp" + +#include + +#include "control_block_error.hpp" +#include "logger.hpp" + +namespace CBXP { +void ControlBlock::createIncludeMap(const std::vector& includes) { + Logger::getInstance().debug("Creating include map for the '" + + control_block_name_ + "' control block..."); + for (std::string include : includes) { + if (include == "**") { + ControlBlock::processDoubleAsteriskInclude(); + return; + } else if (include == "*") { + ControlBlock::processAsteriskInclude(); + } else { + ControlBlock::processExplicitInclude(include); + } + } + Logger::getInstance().debug("Done"); +} + +void ControlBlock::processDoubleAsteriskInclude() { + // Any existing entries in the hash map are redundant, so clear them + include_map_.clear(); + for (const std::string& includable : includables_) { + // Build a map of all includables_ but with "**" at the next level + include_map_[includable] = {"**"}; + } +} + +void ControlBlock::processAsteriskInclude() { + if (include_map_.empty()) { + for (const std::string& includable : includables_) { + // Build a map of all includables_ + include_map_[includable] = {}; + } + } + for (const std::string& includable : includables_) { + if (include_map_.find(includable) != include_map_.end()) { + continue; + } + // Add all includables_ not already present to the map + include_map_[includable] = {}; + } +} + +void ControlBlock::processExplicitInclude(std::string& include) { + // Default case; have to validate against an includable + const std::string del = "."; + std::string include_includes = ""; + size_t del_pos = include.find(del); + if (del_pos != std::string::npos) { + // If there's a "." then separate include into the include and its + // includes + include_includes = include.substr(del_pos + 1); + include.resize(del_pos); + } + if (std::find(includables_.begin(), includables_.end(), include) == + includables_.end()) { + Logger::getInstance().debug("'" + include + + "' is not a known includable for the '" + + control_block_name_ + "' control block"); + throw IncludeError(); + } + if (include_map_.find(include) == include_map_.end()) { + // If we don't already have this include in our map, add it with its + // includes + if (include_includes == "") { + include_map_[include] = {}; + } else { + include_map_[include] = {include_includes}; + } + } else { + // If we DO already have this in our map, then we should add its + // includes if they are useful or new + if (std::find(include_map_[include].begin(), include_map_[include].end(), + include_includes) != include_map_[include].end()) { + return; + } + if (include_includes == "") { + return; + } + include_map_[include].push_back(include_includes); + } +} +} // namespace CBXP + diff --git a/cbxp/control_blocks/control_block.hpp b/cbxp/control_blocks/control_block.hpp index 36f05bb..dcbcb3b 100644 --- a/cbxp/control_blocks/control_block.hpp +++ b/cbxp/control_blocks/control_block.hpp @@ -8,8 +8,27 @@ namespace CBXP { class ControlBlock { + private: + const std::string control_block_name_; + const std::vector includables_; + void processDoubleAsteriskInclude(); + void processAsteriskInclude(); + void processExplicitInclude(std::string& include); + protected: ControlBlockFieldFormatter formatter_; + std::unordered_map> include_map_; + + public: + void createIncludeMap(const std::vector& includes); + virtual nlohmann::json get(void* __ptr32 p_control_block = nullptr) = 0; + explicit ControlBlock(const std::string& name, + const std::vector& includables, + const std::vector& includes) + : control_block_name_(name), includables_(includables) { + createIncludeMap(includes); + } + virtual ~ControlBlock() = default; }; } // namespace CBXP diff --git a/cbxp/control_blocks/cvt.cpp b/cbxp/control_blocks/cvt.cpp index 3c4e54a..12107ad 100644 --- a/cbxp/control_blocks/cvt.cpp +++ b/cbxp/control_blocks/cvt.cpp @@ -7,44 +7,64 @@ #include #include +#include "asvt.hpp" +#include "ecvt.hpp" #include "logger.hpp" namespace CBXP { -nlohmann::json CVT::get() { - const struct psa* __ptr32 p_psa = 0; - // 'nullPointer' is a false positive because the PSA starts at address 0 - // cppcheck-suppress-begin nullPointer - const struct cvtmap* __ptr32 p_cvtmap = - static_cast(p_psa->flccvt); - const struct cvtfix* __ptr32 p_cvtfix = - static_cast(p_psa->flccvt); - const struct cvtxtnt2* __ptr32 p_cvtxtnt2 = - static_cast(p_psa->flccvt); - const struct cvtvstgx* __ptr32 p_cvtvstgx = - static_cast(p_psa->flccvt); +nlohmann::json CVT::get(void* __ptr32 p_control_block) { + const struct cvtmap* __ptr32 p_cvtmap; + const struct cvtfix* __ptr32 p_cvtfix; + const struct cvtxtnt2* __ptr32 p_cvtxtnt2; + const struct cvtvstgx* __ptr32 p_cvtvstgx; + nlohmann::json cvt_json = {}; + + if (p_control_block == nullptr) { + // PSA starts at address 0 + const struct psa* __ptr32 p_psa = 0; + // 'nullPointer' is a false positive because the PSA starts at address 0 + // cppcheck-suppress-begin nullPointer + p_cvtmap = static_cast(p_psa->flccvt); + } else { + p_cvtmap = static_cast(p_control_block); + } + p_cvtfix = const_cast( + reinterpret_cast(p_cvtmap)); + p_cvtxtnt2 = const_cast( + reinterpret_cast(p_cvtmap)); + p_cvtvstgx = const_cast( + reinterpret_cast(p_cvtmap)); // cppcheck-suppress-end nullPointer Logger::getInstance().debug("CVT hex dump:"); Logger::getInstance().hexDump(reinterpret_cast(p_cvtmap), sizeof(struct cvtmap)); + cvt_json["cvtasvt"] = formatter_.getHex(&p_cvtmap->cvtasvt); + cvt_json["cvtecvt"] = formatter_.getHex(&p_cvtmap->cvtecvt); + + for (const auto& [include, include_includes] : include_map_) { + if (include == "asvt") { + cvt_json["cvtasvt"] = CBXP::ASVT(include_includes).get(p_cvtmap->cvtasvt); + } else if (include == "ecvt") { + cvt_json["cvtecvt"] = CBXP::ECVT(include_includes).get(p_cvtmap->cvtecvt); + } + } + // Get fields - nlohmann::json cvt_json = {}; - cvt_json["cvtabend"] = formatter_.getHex(p_cvtmap->cvtabend); - cvt_json["cvtamff"] = formatter_.getHex(p_cvtmap->cvtamff); - cvt_json["cvtasmvt"] = formatter_.getHex(p_cvtmap->cvtasmvt); - cvt_json["cvtasvt"] = formatter_.getHex(p_cvtmap->cvtasvt); - cvt_json["cvtbret"] = formatter_.getHex(p_cvtmap->cvtbret); - cvt_json["cvtbsm0f"] = formatter_.getHex(p_cvtmap->cvtbsm0f); - cvt_json["cvtcsd"] = formatter_.getHex(p_cvtmap->cvtcsd); - cvt_json["cvtctlfg"] = formatter_.getBitmap( + cvt_json["cvtabend"] = formatter_.getHex(p_cvtmap->cvtabend); + cvt_json["cvtamff"] = formatter_.getHex(p_cvtmap->cvtamff); + cvt_json["cvtasmvt"] = formatter_.getHex(p_cvtmap->cvtasmvt); + cvt_json["cvtbret"] = formatter_.getHex(p_cvtmap->cvtbret); + cvt_json["cvtbsm0f"] = formatter_.getHex(p_cvtmap->cvtbsm0f); + cvt_json["cvtcsd"] = formatter_.getHex(p_cvtmap->cvtcsd); + cvt_json["cvtctlfg"] = formatter_.getBitmap( reinterpret_cast(&p_cvtmap->cvtctlfg)); cvt_json["cvtdcb"] = formatter_.getBitmap( reinterpret_cast(&p_cvtmap->cvtdcb)); cvt_json["cvtdcpa"] = formatter_.getBitmap(p_cvtmap->cvtdcpa); cvt_json["cvtdfa"] = formatter_.getHex(p_cvtmap->cvtdfa); - cvt_json["cvtecvt"] = formatter_.getHex(p_cvtmap->cvtecvt); cvt_json["cvtedat2"] = formatter_.getBitmap(p_cvtmap->cvtedat2); cvt_json["cvteplps"] = formatter_.getHex(p_cvtvstgx->cvteplps); cvt_json["cvtexit"] = formatter_.getHex(p_cvtmap->cvtexit); @@ -122,4 +142,3 @@ nlohmann::json CVT::get() { return cvt_json; } } // namespace CBXP - diff --git a/cbxp/control_blocks/cvt.hpp b/cbxp/control_blocks/cvt.hpp index 0163a4a..3111cda 100644 --- a/cbxp/control_blocks/cvt.hpp +++ b/cbxp/control_blocks/cvt.hpp @@ -7,7 +7,9 @@ namespace CBXP { class CVT : public ControlBlock { public: - nlohmann::json get(); + nlohmann::json get(void* __ptr32 p_control_block = nullptr) override; + explicit CVT(const std::vector& includes) + : ControlBlock("cvt", {"ecvt", "asvt"}, includes) {} }; } // namespace CBXP diff --git a/cbxp/control_blocks/ecvt.cpp b/cbxp/control_blocks/ecvt.cpp index a3940ac..e577624 100644 --- a/cbxp/control_blocks/ecvt.cpp +++ b/cbxp/control_blocks/ecvt.cpp @@ -12,23 +12,29 @@ #include "logger.hpp" namespace CBXP { -nlohmann::json ECVT::get() { - const struct psa* __ptr32 p_psa = 0; - // Get the address of the CVT from the PSA - const struct cvtmap* __ptr32 p_cvt = - // 'nullPointer' is a false positive because the PSA starts at address 0 - // cppcheck-suppress nullPointer - static_cast(p_psa->flccvt); - // Get the address of the EVCT from the CVT - const struct ecvt* __ptr32 p_ecvt = - static_cast(p_cvt->cvtecvt); +nlohmann::json ECVT::get(void* __ptr32 p_control_block) { + const struct ecvt* __ptr32 p_ecvt; + nlohmann::json ecvt_json = {}; + + if (p_control_block == nullptr) { + // PSA starts at address 0 + const struct psa* __ptr32 p_psa = 0; + // Get the address of the CVT from the PSA + const struct cvtmap* __ptr32 p_cvt = + // 'nullPointer' is a false positive because the PSA starts at address 0 + // cppcheck-suppress nullPointer + static_cast(p_psa->flccvt); + // Get the address of the EVCT from the CVT + p_ecvt = static_cast(p_cvt->cvtecvt); + } else { + p_ecvt = static_cast(p_control_block); + } Logger::getInstance().debug("ECVT hex dump:"); Logger::getInstance().hexDump(reinterpret_cast(p_ecvt), sizeof(struct ecvt)); // Get Fields - nlohmann::json ecvt_json = {}; ecvt_json["ecvt_boostinfo"] = formatter_.getBitmap( diff --git a/cbxp/control_blocks/ecvt.hpp b/cbxp/control_blocks/ecvt.hpp index 6f12393..9cece25 100644 --- a/cbxp/control_blocks/ecvt.hpp +++ b/cbxp/control_blocks/ecvt.hpp @@ -7,7 +7,9 @@ namespace CBXP { class ECVT : public ControlBlock { public: - nlohmann::json get(); + nlohmann::json get(void* __ptr32 p_control_block = nullptr) override; + explicit ECVT(const std::vector& includes) + : ControlBlock("ecvt", {}, includes) {} }; } // namespace CBXP #endif diff --git a/cbxp/control_blocks/psa.cpp b/cbxp/control_blocks/psa.cpp index 2b00f88..8feec8c 100644 --- a/cbxp/control_blocks/psa.cpp +++ b/cbxp/control_blocks/psa.cpp @@ -6,28 +6,40 @@ #include #include +#include "cvt.hpp" #include "logger.hpp" namespace CBXP { -nlohmann::json PSA::get() { - // PSA starts at address 0 - const struct psa* __ptr32 p_psa = 0; +nlohmann::json PSA::get(void* __ptr32 p_control_block) { + const struct psa* __ptr32 p_psa; + nlohmann::json psa_json = {}; + + if (p_control_block == nullptr) { + // PSA starts at address 0 + p_psa = 0; + } else { + p_psa = static_cast(p_control_block); + } Logger::getInstance().debug("PSA hex dump:"); Logger::getInstance().hexDump( reinterpret_cast(p_psa), sizeof(struct psa) / 2, true); // Only the first "page" of the PSA is not fetch-protected + psa_json["flccvt"] = formatter_.getHex(&p_psa->flccvt); + + for (const auto& [include, include_includes] : include_map_) { + if (include == "cvt") { + psa_json["flccvt"] = CBXP::CVT(include_includes).get(p_psa->flccvt); + } + } + // Get fields - nlohmann::json psa_json = {}; - // 'nullPointer' is a false positive because the PSA starts at address 0 - // cppcheck-suppress-begin nullPointer psa_json["psapsa"] = formatter_.getString(p_psa->psapsa, 4); psa_json["flcsvilc"] = static_cast(p_psa->flcsvilc); psa_json["flcrnpsw_bitstring"] = formatter_.getBitmap(p_psa->flcrnpsw); psa_json["flcrnpsw_hex"] = formatter_.getHex(p_psa->flcrnpsw + 4); - psa_json["flccvt"] = formatter_.getHex(p_psa->flccvt); psa_json["flcsopsw"] = formatter_.getPswSmall(p_psa->flcsopsw); psa_json["flcarch"] = formatter_.getBitmap( reinterpret_cast(&p_psa->flcarch)); @@ -48,7 +60,6 @@ nlohmann::json PSA::get() { psa_json["psatrvt"] = formatter_.getHex(p_psa->psatrvt); psa_json["psaval"] = formatter_.getBitmap(p_psa->psaval); psa_json["psaxcvt"] = formatter_.getHex(p_psa->psaxcvt); - // cppcheck-suppress-end nullPointer return psa_json; } diff --git a/cbxp/control_blocks/psa.hpp b/cbxp/control_blocks/psa.hpp index 12f6485..7843b38 100644 --- a/cbxp/control_blocks/psa.hpp +++ b/cbxp/control_blocks/psa.hpp @@ -7,7 +7,9 @@ namespace CBXP { class PSA : public ControlBlock { public: - nlohmann::json get(); + nlohmann::json get(void* __ptr32 p_control_block = nullptr) override; + explicit PSA(const std::vector& includes) + : ControlBlock("psa", {"cvt"}, includes) {} }; } // namespace CBXP #endif diff --git a/cbxp/main.cpp b/cbxp/main.cpp index 8038d1a..9890459 100644 --- a/cbxp/main.cpp +++ b/cbxp/main.cpp @@ -4,46 +4,84 @@ #include #include "cbxp_result.h" +#include "control_block_error.hpp" #include "control_block_explorer.hpp" static void show_usage(const char* argv[]) { - std::cout << "Usage: " << std::endl; - std::cout << " " << argv[0] - << " [ENABLE_DEBUG_LOGGING: -d/--debug] (default false)" - << " [CONTROL_BLOCK]" << std::endl; - std::cout << " " << argv[0] << " [SHOW_VERSION: -v/--version]" << std::endl; - std::cout << " " << argv[0] << " [SHOW_USAGE]: -h/--help" << std::endl; + std::cout << "Usage: " << argv[0] << " [options] " << std::endl + << std::endl; + + std::cout << "Options:" << std::endl + << " -d, --debug Write debug messages" + << std::endl + << " -i, --include Include additional control " + "blocks based on a pattern" + << std::endl + << " -v, --version Show version number" + << std::endl + << " -h, --help Show usage information" + << std::endl + << std::endl; } int main(int argc, const char* argv[]) { - bool debug = false; + bool debug = false; + std::string control_block_name = "", includes_string = ""; - if (argc == 3) { - if ((std::strcmp(argv[1], "-d") == 0) || - (std::strcmp(argv[1], "--debug") == 0)) { - debug = true; - } else { - show_usage(argv); - return -1; - } - } else if (argc != 2) { + if (argc < 2) { show_usage(argv); return -1; } - if (std::strcmp(argv[1], "-v") == 0 || - std::strcmp(argv[1], "--version") == 0) { - std::cout << "CBXP " << VERSION << std::endl; - return 0; + if (argc == 2) { + if (std::strcmp(argv[1], "-v") == 0 || + std::strcmp(argv[1], "--version") == 0) { + std::cout << "CBXP " << VERSION << std::endl; + return 0; + } + + if (std::strcmp(argv[1], "-h") == 0 || + std::strcmp(argv[1], "--help") == 0) { + show_usage(argv); + return 0; + } + } + + for (int i = 1; i < argc; i++) { + std::string flag = argv[i]; + if (flag == "-d" || flag == "-debug") { + debug = true; + } else if (flag == "-i" || flag == "--include") { + if (i + 1 >= argc - 1) { + show_usage(argv); + return -1; + } + std::string include = std::string(argv[++i]); + bool has_comma = std::any_of(include.begin(), include.end(), + [](char c) { return c == ','; }); + if (has_comma) { + std::cout << "Include patterns cannot contain commas" << std::endl; + return -1; + } + if (includes_string == "") { + includes_string = include; + } else { + includes_string += "," + include; + } + } else { + if (i != argc - 1) { + show_usage(argv); + return -1; + } + control_block_name = std::string(argv[i]); + } } - if (std::strcmp(argv[1], "-h") == 0 || std::strcmp(argv[1], "--help") == 0) { + if (control_block_name == "") { show_usage(argv); - return 0; + return -1; } - std::string control_block_name = argv[argc - 1]; - nlohmann::json control_block_json; static cbxp_result_t cbxp_result = {nullptr, 0, -1}; @@ -51,11 +89,13 @@ int main(int argc, const char* argv[]) { CBXP::ControlBlockExplorer explorer = CBXP::ControlBlockExplorer(&cbxp_result, debug); - explorer.exploreControlBlock(control_block_name); + explorer.exploreControlBlock(control_block_name, includes_string); - if (cbxp_result.return_code == -1) { + if (cbxp_result.return_code == CBXP::Error::BadControlBlock) { std::cout << "Unknown control block '" << control_block_name << "' was specified." << std::endl; + } else if (cbxp_result.return_code == CBXP::Error::BadInclude) { + std::cout << "A bad include pattern was provided" << std::endl; } else { std::cout << cbxp_result.result_json << std::endl; } diff --git a/cbxp/python/_cbxp.c b/cbxp/python/_cbxp.c index dea4c98..90fe08c 100644 --- a/cbxp/python/_cbxp.c +++ b/cbxp/python/_cbxp.c @@ -15,13 +15,14 @@ static PyObject* call_cbxp(PyObject* self, PyObject* args, PyObject* kwargs) { PyObject* result_dictionary; PyObject* debug_pyobj; const char* control_block; + const char* includes_string; Py_ssize_t request_length; bool debug = false; - static char* kwlist[] = {"request", "debug", NULL}; + static char* kwlist[] = {"request", "include", "debug", NULL}; - if (!PyArg_ParseTupleAndKeywords(args, kwargs, "s|O", kwlist, &control_block, - &debug_pyobj)) { + if (!PyArg_ParseTupleAndKeywords(args, kwargs, "s|sO", kwlist, &control_block, + &includes_string, &debug_pyobj)) { return NULL; } @@ -33,7 +34,7 @@ static PyObject* call_cbxp(PyObject* self, PyObject* args, PyObject* kwargs) { // but we will set this up anyways to be safe. pthread_mutex_lock(&cbxp_mutex); - cbxp_result_t* result = cbxp(control_block, debug); + cbxp_result_t* result = cbxp(control_block, includes_string, debug); result_dictionary = Py_BuildValue( "{s:s#, s:i}", "result_json", result->result_json, diff --git a/python/cbxp/_C.pyi b/python/cbxp/_C.pyi index e830ecb..96e2bba 100644 --- a/python/cbxp/_C.pyi +++ b/python/cbxp/_C.pyi @@ -1 +1 @@ -def call_cbxp(self, control_block: str, debug: bool=False) -> dict: ... +def call_cbxp(self, control_block: str, includes_string: str, debug: bool=False) -> dict: ... diff --git a/python/cbxp/cbxp.py b/python/cbxp/cbxp.py index ea1d890..1741a33 100644 --- a/python/cbxp/cbxp.py +++ b/python/cbxp/cbxp.py @@ -1,14 +1,26 @@ import json +from typing import Union +from enum import Enum from cbxp._C import call_cbxp +class CBXPErrorCode(Enum): + """An enum of error and return codes from the cbxp interface""" + COMMA_IN_INCLUDE = -1 + BAD_CONTROL_BLOCK = 1 + BAD_INCLUDE = 2 + class CBXPError(Exception): """A class of errors for return codes from the cbxp interface""" - def __init__(self, return_code, control_block_name): + def __init__(self, return_code: int, control_block_name: str): self.rc = return_code match self.rc: - case -1: - message = f"control block '{control_block_name}' is unknown" + case CBXPErrorCode.COMMA_IN_INCLUDE: + message = "Include patterns cannot contain commas" + case CBXPErrorCode.BAD_CONTROL_BLOCK: + message = f"Unknown control block '{control_block_name}' was specified." + case CBXPErrorCode.BAD_INCLUDE: + message = "A bad include pattern was provided" case _: message = "an unknown error occurred" super().__init__(message) @@ -16,8 +28,15 @@ def __init__(self, return_code, control_block_name): -def cbxp(control_block_name: str, debug: bool = False) -> dict: - response = call_cbxp(control_block_name, debug=debug) +def cbxp( + control_block: str, + includes: list[str] = [], + debug: bool = False +) -> dict: + for include in includes: + if "," in include: + raise CBXPError(CBXPErrorCode.COMMA_IN_INCLUDE, control_block) + response = call_cbxp(control_block.lower(), ",".join(includes).lower(), debug=debug) if response['return_code']: - raise CBXPError(response['return_code'], control_block_name) + raise CBXPError(response['return_code'], control_block) return json.loads(response['result_json']) From cec942166eb3f555568db03aee9da8e297c3d508 Mon Sep 17 00:00:00 2001 From: Varun Chennamadhava Date: Wed, 5 Nov 2025 07:34:55 -0500 Subject: [PATCH 02/37] Unit testing (#17) * initial commit 1 * cleaned code before include test cases * wrote test cases, need to check with team now * added space after every function * added .value * shell script done * made changes proposed by leonard 1 * PR changes requested by team * added tests to check for ascb and asvt entries whether it be a string or dict * added tests to check for ascb and asvt entries whether it be a string or dict one more place * made minor tweaks * added updates provided by leonard * grouped failure test cases together * grouped error test cases together * removed extra lines * style changes --- CMakeLists.txt | 6 ++ cbxp/main.cpp | 10 ++- python/cbxp/__init__.py | 1 + python/cbxp/cbxp.py | 10 +-- tests/test.py | 170 ++++++++++++++++++++++++++++++++++++++++ tests/test.sh | 58 ++++++++++++++ 6 files changed, 245 insertions(+), 10 deletions(-) create mode 100644 tests/test.py create mode 100755 tests/test.sh diff --git a/CMakeLists.txt b/CMakeLists.txt index e2346f3..7ef9a50 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -112,3 +112,9 @@ add_custom_target( "-i" ${CBXP_SRC_ALL} ) + +add_custom_target( + test + COMMAND "./tests/test.sh" + DEPENDS cbxp +) diff --git a/cbxp/main.cpp b/cbxp/main.cpp index 9890459..3328d94 100644 --- a/cbxp/main.cpp +++ b/cbxp/main.cpp @@ -60,7 +60,7 @@ int main(int argc, const char* argv[]) { bool has_comma = std::any_of(include.begin(), include.end(), [](char c) { return c == ','; }); if (has_comma) { - std::cout << "Include patterns cannot contain commas" << std::endl; + std::cerr << "Include patterns cannot contain commas" << std::endl; return -1; } if (includes_string == "") { @@ -92,13 +92,15 @@ int main(int argc, const char* argv[]) { explorer.exploreControlBlock(control_block_name, includes_string); if (cbxp_result.return_code == CBXP::Error::BadControlBlock) { - std::cout << "Unknown control block '" << control_block_name + std::cerr << "Unknown control block '" << control_block_name << "' was specified." << std::endl; + return -1; } else if (cbxp_result.return_code == CBXP::Error::BadInclude) { - std::cout << "A bad include pattern was provided" << std::endl; + std::cerr << "A bad include pattern was provided" << std::endl; + return -1; } else { std::cout << cbxp_result.result_json << std::endl; } - return cbxp_result.return_code; + return 0; } diff --git a/python/cbxp/__init__.py b/python/cbxp/__init__.py index d64ce22..99af136 100644 --- a/python/cbxp/__init__.py +++ b/python/cbxp/__init__.py @@ -1 +1,2 @@ from .cbxp import cbxp as cbxp +from .cbxp import CBXPError as CBXPError diff --git a/python/cbxp/cbxp.py b/python/cbxp/cbxp.py index 1741a33..e43a450 100644 --- a/python/cbxp/cbxp.py +++ b/python/cbxp/cbxp.py @@ -15,19 +15,17 @@ class CBXPError(Exception): def __init__(self, return_code: int, control_block_name: str): self.rc = return_code match self.rc: - case CBXPErrorCode.COMMA_IN_INCLUDE: + case CBXPErrorCode.COMMA_IN_INCLUDE.value: message = "Include patterns cannot contain commas" - case CBXPErrorCode.BAD_CONTROL_BLOCK: + case CBXPErrorCode.BAD_CONTROL_BLOCK.value: message = f"Unknown control block '{control_block_name}' was specified." - case CBXPErrorCode.BAD_INCLUDE: + case CBXPErrorCode.BAD_INCLUDE.value: message = "A bad include pattern was provided" case _: message = "an unknown error occurred" super().__init__(message) - - def cbxp( control_block: str, includes: list[str] = [], @@ -35,7 +33,7 @@ def cbxp( ) -> dict: for include in includes: if "," in include: - raise CBXPError(CBXPErrorCode.COMMA_IN_INCLUDE, control_block) + raise CBXPError(CBXPErrorCode.COMMA_IN_INCLUDE.value, control_block) response = call_cbxp(control_block.lower(), ",".join(includes).lower(), debug=debug) if response['return_code']: raise CBXPError(response['return_code'], control_block) diff --git a/tests/test.py b/tests/test.py new file mode 100644 index 0000000..cb54888 --- /dev/null +++ b/tests/test.py @@ -0,0 +1,170 @@ +import unittest +from cbxp import cbxp, CBXPError + +class TestCBXP(unittest.TestCase): + def test_cbxp_can_extract_psa(self): + cbdata = cbxp("psa") + self.assertIs(type(cbdata), dict) + + def test_cbxp_can_extract_cvt(self): + cbdata = cbxp("cvt") + self.assertIs(type(cbdata), dict) + + def test_cbxp_can_extract_ecvt(self): + cbdata = cbxp("ecvt") + self.assertIs(type(cbdata), dict) + + def test_cbxp_can_extract_asvt(self): + cbdata = cbxp("asvt") + self.assertIs(type(cbdata), dict) + + def test_cbxp_can_extract_ascb(self): + cbdata = cbxp("ascb") + self.assertIs(type(cbdata), list) + for entry in cbdata: + self.assertIs(type(entry), dict) + + def test_cbxp_can_extract_the_psa_and_include_the_cvt(self): + cbdata = cbxp("psa", includes=["cvt"]) + self.assertIs(type(cbdata), dict) + self.assertIs(type(cbdata["flccvt"]), dict) + + def test_cbxp_can_extract_the_cvt_and_include_the_ecvt(self): + cbdata = cbxp("cvt", includes=["ecvt"]) + self.assertIs(type(cbdata), dict) + self.assertIs(type(cbdata["cvtecvt"]), dict) + + def test_cbxp_can_extract_the_cvt_and_include_the_asvt(self): + cbdata = cbxp("cvt", includes=["asvt"]) + self.assertIs(type(cbdata), dict) + self.assertIs(type(cbdata["cvtasvt"]), dict) + self.assertIs(type(cbdata["cvtasvt"]["asvtenty"]), list) + for entry in cbdata["cvtasvt"]["asvtenty"]: + self.assertIs(type(entry), str) + + def test_cbxp_can_extract_the_asvt_and_include_the_ascb(self): + cbdata = cbxp("asvt", includes=["ascb"]) + self.assertIs(type(cbdata), dict) + self.assertIs(type(cbdata["asvtenty"]), list) + for entry in cbdata["asvtenty"]: + self.assertIs(type(entry), dict) + + def test_cbxp_can_extract_the_psa_and_include_the_cvt_ecvt(self): + cbdata = cbxp("psa", includes=["cvt.ecvt"]) + self.assertIs(type(cbdata), dict) + self.assertIs(type(cbdata["flccvt"]), dict) + self.assertIs(type(cbdata["flccvt"]["cvtecvt"]), dict) + + def test_cbxp_can_extract_the_psa_and_include_the_cvt_ecvt_ascb(self): + cbdata = cbxp("psa", includes=["cvt.asvt.ascb"]) + self.assertIs(type(cbdata), dict) + self.assertIs(type(cbdata["flccvt"]), dict) + self.assertIs(type(cbdata["flccvt"]["cvtasvt"]), dict) + self.assertIs(type(cbdata["flccvt"]["cvtasvt"]["asvtenty"]), list) + for entry in cbdata["flccvt"]["cvtasvt"]["asvtenty"]: + self.assertIs(type(entry), dict) + + def test_cbxp_can_extract_the_cvt_and_include_the_asvt_ascb(self): + cbdata = cbxp("cvt", includes=["asvt.ascb"]) + self.assertIs(type(cbdata), dict) + self.assertIs(type(cbdata["cvtasvt"]), dict) + self.assertIs(type(cbdata["cvtasvt"]["asvtenty"]), list) + for entry in cbdata["cvtasvt"]["asvtenty"]: + self.assertIs(type(entry), dict) + + def test_cbxp_include_can_extract_cvt_and_include_pattern_ecvt_and_asvt(self): + cbdata = cbxp("cvt", includes=["ecvt", "asvt"]) + self.assertIs(type(cbdata), dict) + self.assertIs(type(cbdata["cvtecvt"]), dict) + self.assertIs(type(cbdata["cvtasvt"]), dict) + self.assertIs(type(cbdata["cvtasvt"]["asvtenty"]), list) + for entry in cbdata["cvtasvt"]["asvtenty"]: + self.assertIs(type(entry), str) + + def test_cbxp_include_can_extract_psa_and_include_pattern_ecvt_asvt_and_cvt_asvt_ascb(self): + cbdata = cbxp("psa", includes=["cvt.ecvt", "cvt.asvt.ascb"]) + self.assertIs(type(cbdata), dict) + self.assertIs(type(cbdata["flccvt"]), dict) + self.assertIs(type(cbdata["flccvt"]["cvtecvt"]), dict) + self.assertIs(type(cbdata["flccvt"]["cvtasvt"]), dict) + self.assertIs(type(cbdata["flccvt"]["cvtasvt"]["asvtenty"]), list) + for entry in cbdata["flccvt"]["cvtasvt"]["asvtenty"]: + self.assertIs(type(entry), dict) + + def test_cbxp_can_extract_psa_and_include_cvt_recursive_wildcard(self): + cbdata = cbxp("psa", includes=["cvt.**"]) + self.assertIs(type(cbdata), dict) + self.assertIs(type(cbdata["flccvt"]), dict) + self.assertIs(type(cbdata["flccvt"]["cvtecvt"]), dict) + self.assertIs(type(cbdata["flccvt"]["cvtasvt"]), dict) + self.assertIs(type(cbdata["flccvt"]["cvtasvt"]["asvtenty"]), list) + for entry in cbdata["flccvt"]["cvtasvt"]["asvtenty"]: + self.assertIs(type(entry), dict) + + def test_cbxp_can_extract_psa_and_include_cvt_wildcard(self): + cbdata = cbxp("psa", includes=["cvt.*"]) + self.assertIs(type(cbdata), dict) + self.assertIs(type(cbdata["flccvt"]), dict) + self.assertIs(type(cbdata["flccvt"]["cvtecvt"]), dict) + self.assertIs(type(cbdata["flccvt"]["cvtasvt"]), dict) + self.assertIs(type(cbdata["flccvt"]["cvtasvt"]["asvtenty"]), list) + for entry in cbdata["flccvt"]["cvtasvt"]["asvtenty"]: + self.assertIs(type(entry), str) + + def test_cbxp_can_extract_cvt_and_include_wildcard_and_asvt_wildcard(self): + cbdata = cbxp("cvt", includes=["*", "asvt.*"]) + self.assertIs(type(cbdata), dict) + self.assertIs(type(cbdata["cvtecvt"]), dict) + self.assertIs(type(cbdata["cvtasvt"]), dict) + self.assertIs(type(cbdata["cvtasvt"]["asvtenty"]), list) + for entry in cbdata["cvtasvt"]["asvtenty"]: + self.assertIs(type(entry), dict) + + def test_cbxp_raises_cbxp_error_when_unknown_control_block_is_provided(self): + with self.assertRaises(CBXPError) as e: + cbxp("unknown") + self.assertEqual("Unknown control block 'unknown' was specified.", str(e.exception)) + + def test_cbxp_raises_cbxp_error_if_asvt_ascb_is_included_when_extracting_the_psa(self): + with self.assertRaises(CBXPError) as e: + cbxp("psa", includes=["asvt.ascb"]) + self.assertEqual("A bad include pattern was provided", str(e.exception)) + + def test_cbxp_raises_cbxp_error_if_ascb_is_included_when_extracting_the_psa(self): + with self.assertRaises(CBXPError) as e: + cbxp("psa", includes=["ascb"]) + self.assertEqual("A bad include pattern was provided", str(e.exception)) + + def test_cbxp_raises_cbxp_error_if_ecvt_is_included_when_extracting_the_ascb(self): + with self.assertRaises(CBXPError) as e: + cbxp("ascb", includes=["ecvt"]) + self.assertEqual("A bad include pattern was provided", str(e.exception)) + + def test_cbxp_raises_cbxp_error_if_ect_ecvt_and_cvt_ascb_is_included_when_extracting_the_psa(self): + with self.assertRaises(CBXPError) as e: + cbdata = cbxp("psa", includes=["cvt.ecvt", "cvt.ascb"]) + self.assertEqual("A bad include pattern was provided", str(e.exception)) + + def test_cbxp_raises_cbxp_error_if_ecvt_and_cvt_asvt_ascb_is_included_when_extracting_the_psa(self): + with self.assertRaises(CBXPError) as e: + cbdata = cbxp("psa", includes=["ecvt", "cvt.asvt.ascb"]) + self.assertEqual("A bad include pattern was provided", str(e.exception)) + + def test_cbxp_raises_cbxp_error_if_cvt_is_included_when_extracting_the_cvt(self): + with self.assertRaises(CBXPError) as e: + cbdata = cbxp("cvt", includes=["cvt"]) + self.assertEqual("A bad include pattern was provided", str(e.exception)) + + def test_cbxp_raises_cbxp_error_when_pattern_cannot_contain_comma_1(self): + with self.assertRaises(CBXPError) as e: + cbdata = cbxp("cvt", includes=["asvt,ascb"]) + self.assertEqual("Include patterns cannot contain commas", str(e.exception)) + + def test_cbxp_raises_cbxp_error_when_pattern_cannot_contain_comma_2(self): + with self.assertRaises(CBXPError) as e: + cbdata = cbxp("cvt", includes=["asvt,as"]) + self.assertEqual("Include patterns cannot contain commas", str(e.exception)) + + +if __name__ == "__main__": + unittest.main(argv=["", "-v", "-f", "-b"]) diff --git a/tests/test.sh b/tests/test.sh new file mode 100755 index 0000000..3a6a7e9 --- /dev/null +++ b/tests/test.sh @@ -0,0 +1,58 @@ +#!/bin/bash + +run_with_expected_exit_code() { + expected_code=$1 + shift + echo "Running: $* (expected exit code: $expected_code)" + + "$@" > /dev/null + return_code=$? + + if [ $return_code -eq $expected_code ]; then + echo "Command exited with $return_code as expected." + else + echo "Unexpected exit code: got $return_code, expected $expected_code" + exit 1 + fi + echo +} + +# exit code 0 +run_with_expected_exit_code 0 ./dist/cbxp ascb +run_with_expected_exit_code 0 ./dist/cbxp asvt +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 psa +run_with_expected_exit_code 0 ./dist/cbxp -i 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 + +# exit code 255 +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 unknown +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 +run_with_expected_exit_code 255 ./dist/cbxp -i cvt +run_with_expected_exit_code 255 ./dist/cbxp -i -i cvt psa + +echo " -------------------------------- " +echo " -------------------------------- " +echo " All tests completed successfully" +echo " -------------------------------- " +echo " -------------------------------- " From 55fbda0eb7fa46b503253f664b92c04f9230b0d3 Mon Sep 17 00:00:00 2001 From: Leonard Carcaramo Date: Wed, 5 Nov 2025 13:38:14 -0500 Subject: [PATCH 03/37] Feat/oss housekeeping2 (#18) * Set explicit C/C++ standard and cleanup README. Signed-off-by: Leonard Carcaramo * Update contribution guidelines and functional tests. Signed-off-by: Leonard Carcaramo * Cleanup contribution guidelines and debug debug mode. Signed-off-by: Leonard Carcaramo * Cleanup. Signed-off-by: Leonard Carcaramo * Cleanup. Signed-off-by: Leonard Carcaramo * Fix sdist packaging and pyproject.toml metadata. Signed-off-by: Leonard Carcaramo --------- Signed-off-by: Leonard Carcaramo --- CMakeLists.txt | 3 ++ CONTRIBUTING.md | 77 ++++++++++++++++++++---------------- MANIFEST.in | 1 + README.md | 43 ++++---------------- cbxp/control_blocks/asvt.cpp | 2 +- cbxp/main.cpp | 2 +- pyproject.toml | 10 ++--- setup.py | 5 +++ tests/test.py | 24 ++++++++++- tests/test.sh | 69 +++++++++++++++++++------------- 10 files changed, 131 insertions(+), 105 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 7ef9a50..f27e724 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -6,6 +6,8 @@ execute_process( RESULT_VARIABLE uname_result ) +set(CXX_STANDARD 17) + if(uname_output STREQUAL "OS/390\n") set(CMAKE_CXX_COMPILER "ibm-clang++64") else() @@ -86,6 +88,7 @@ add_custom_target( "--suppress='missingIncludeSystem'" "--inline-suppr" "--language=c++" + "--std=c++${CXX_STANDARD}" "--enable=all" "--force" "--check-level=exhaustive" diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 55f5dbb..248998f 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -15,11 +15,15 @@ The following are a set of guidelines to help you contribute. * [Adding New Functionality](#adding-new-functionality) - * [Testing](#testing) + * [Branch Naming Conventions](#branch-naming-conventions) * [Fixing Bugs](#fixing-bugs) - * [Branch Naming Conventions](#branch-naming-conventions) + * [Testing](#testing) + + * [Python Interface Testing](#python-interface-testing) + + * [Shell Interface Testing](#shell-interface-testing) * [Style Guidelines](#style-guidelines) @@ -57,47 +61,54 @@ To ensure `clang-format` and `cppcheck` are always run against your code on **ev If you want to continube new functionality, open a GitHub pull request against the `dev` branch with your changes. In the PR, make sure to clearly document the new functionality including why it is valuable. -### Testing - -Testing is currently done in the form of manual funcitional testing. +### Branch Naming Conventions -* To test the **Python Interface**, build the CBXP sdist and wheel, and then install the sdist and wheel to verify that the Python binding works. - ```shell - # Build - python3 -m build - # Install sdist - python3 -m pip install dist/*.tar.gz - # Install wheel - python3 -m pip install dist/*.whl - ``` +Code branches should use the following naming conventions: - ```python - from cbxp import cbxp - psa = cbxp("psa") - ``` -* To test the **CLI**, build the `cbxp` binary using `cmake` and `gmake`, and then execute it with the appropriate arguments. - ```shell - cmake . - gmake - ./dist/cbxp psa - ``` +* `wip/name` *(Work in progress branch that likely won't be finished soon)* +* `feat/name` *(Branch where new functionality or enhancements are being developed)* +* `bug/name` *(Branch where one or more bugs are being fixed)* +* `junk/name` *(Throwaway branch created for experimentation)* ### Fixing Bugs If you fix a bug, open a GitHub pull request against the `dev` branch with the fix. In the PR, make sure to clearly describe the problem and the solution. -### Branch Naming Conventions +### Testing -Code branches should use the following naming conventions: +CBXP is tested using automated functional tests. Test cases for new functionality and bug fixes should be added to [`tests/test.py`](tests/test.py) and [`tests/test.sh`](tests/test.sh). -* `wip/name` *(Work in progress branch that likely won't be finished soon)* -* `feat/name` *(Branch where new functionality or enhancements are being developed)* -* `bug/name` *(Branch where one or more bugs are being fixed)* -* `junk/name` *(Throwaway branch created for experimentation)* +#### Python Interface Testing +1. Add new test cases to [`tests/test.py`](tests/test.py). +2. Build and install the CBXP Python package. Both the **wheel** and **sdist** distributions for all [Python versions supported by CBXP](README.md#minimum-zos--language-versions) should be tested. + + ```shell + # Build + python3 -m build + # Install sdist + python3 -m pip install dist/.tar.gz + # Install wheel + python3 -m pip install dist/.whl + ``` + +3. Run the test suite. + + ```shell + python3 ./tests/test.py + ``` + +#### Shell Interface Testing +1. Add new tests cases to [`tests/test.sh`](tests/test.sh). +2. Run the test suite using `cmake` and `gmake`. + + ```shell + cmake . + gmake test + ``` ## Style Guidelines -:bulb: _These steps can be done automatically using the [pre-commit Hooks](#pre-commit-hooks)._ +:bulb: _`clang-format` can be setup to run automatically using the [pre-commit Hooks](#pre-commit-hooks)._ The use of the `clang-format` code formatter is required. @@ -105,7 +116,7 @@ The following code style conventions should be followed: * Varible names should use snake case *(i.e., `my_variable`)*. * Pointer variables should start with `p_` *(i.e., `p_my_pointer`)*. * Class variables should end with an `_` to help differentiate between class variables and local function variables *(i.e., `my_class_variable_`)*. -* Class name should use pascal case *(i.e., `MyClass`)*. +* Class names should use pascal case *(i.e., `MyClass`)*. * Function names should use camel case *(i.e., `myFunction()`)*. * When calling a class function within the same class that function is a member of, the following syntax should be used to make it clear that a function within the same class is being called. @@ -124,7 +135,7 @@ The following code style conventions should be followed: ## Static Code Analysis -:bulb: _These steps can be done automatically using the [pre-commit Hooks](#pre-commit-hooks)._ +:bulb: _`cppcheck` can be setup to run automatically using the [pre-commit Hooks](#pre-commit-hooks)._ `cppcheck` will be run against all code contributions to ensure that contributions don't inadvertently introduce any vulnerabilities or other significant issues. All contributions must have no `cppcheck` complaints. False positives and minor complaints may be [suppressed](http://cppcheck.net/manual.html#inline-suppressions) when it makes sense to do so, but this should only be done sparingly. All `cppcheck` comlpaints should be evaluated and corrected when it is possible and makes sense to do so. You can run `cppcheck` by running `gmake check`. diff --git a/MANIFEST.in b/MANIFEST.in index c2b3b73..ef3744e 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,3 +1,4 @@ graft externals graft cbxp exclude externals/.clang-format +exclude tests/* diff --git a/README.md b/README.md index a60defe..82e14af 100644 --- a/README.md +++ b/README.md @@ -10,12 +10,10 @@ A unified and standardized interface for extracting z/OS control block data. ## Description -z/OS Control Blocks are in-memory data structures that describe and control countless process, operating system components, and subsystems. Control blocks are unbiquitous, but not very straight forward to access and extract information from. The mission of CBXP *(Control Block EXPlorer)* is to make extracting z/OS control block data straight forward and easy. CBXP accomplishes this by implementing a C/C++ XPLINK ASCII interface for extracting control blocks and post processing them into JSON. This makes it straight forward to integrate with industry standard programming languages and tools, which generally have well documented and understood foreign language intefaces for C/C++, and native and or third party JSON support that makes working with JSON data easy. +z/OS Control Blocks are in-memory data structures that describe and control countless process, operating system components, and subsystems. Control blocks are unbiquitous on z/OS, but not very straight forward to access and extract information from. The mission of CBXP *(Control Block EXPlorer)* is to make it easy to extract z/OS control block data using industry standard tools and methodologies. CBXP accomplishes this by implementing a **C/C++ XPLINK ASCII** interface for extracting control blocks and post processing them into **JSON**. This makes it straight forward to integrate with industry standard programming languages and tools, which generally have well documented and understood foreign language intefaces for C/C++, and native and or third party JSON support that makes working with JSON data easy. CBXP is the successor to the existing [cbxplorer](https://github.com/ambitus/cbexplorer) project. CBXP mainly improves upon this existing work by being implementing in C/C++ so that it is not limited to a specific programming language or tool. CBXP also focuses heavily on providing an interface that is simple and straight forward to use. -You can find information about system level control blocks in the [z/OS MVS Data Areas](https://www.ibm.com/docs/en/zos/3.1.0?topic=zos-mvs) documentation. - ## Getting Started ### Minimum z/OS & Language Versions @@ -29,39 +27,14 @@ All versions of the **IBM Open Enterprise SDK for Python** that are fully suppor * **z/OS Language Environment Runtime Support**: CBXP is compiled using the **IBM Open XL C/C++ 2.1** compiler, which is still fairly new and requires **z/OS Language Environment** service updates for runtime support. * More information can be found in section **5.2.2.2 Operational Requisites** on page **9** in the [Program Directory for IBM Open XL C/C++ 2.1 for z/OS](https://publibfp.dhe.ibm.com/epubs/pdf/i1357012.pdf). +### Interfaces +Currently, the following interfaces are provided for CBXP. Additional interfaces can be added in the future if there are use cases for them. +* [Python Interface](https://ambitus.github.io/cbxp/interfaces/python) +* [Shell Interface](https://ambitus.github.io/cbxp/interfaces/shell) + ### Supported Control Blocks -Currently, CBXP supports the following system level control blocks. CBXP also currently only supports extracting control blocks from memory *(storage)*. We plan on adding support for extracting control blocks from data sets/files and extracting control blocks based on a user defined JSON schema. We also plan on continuously expanding upon the following list of natively supported control blocks. The community is encouraged to assist in these efforts. See the [Contribution Guidelines](https://github.com/ambitus/cbxp/blob/main/CONTRIBUTING.md) for more details. - -* `psa` -* `cvt` -* `ecvt` -* `asvt` -* `ascb` - -### Python Interface -A python binding for CBXP is available for installation via [PyPi](https://pypi.org/project/cbxp/) or download via [GitHub](https://github.com/ambitus/cbxp/releases). - -#### Python Installation *(PyPi)* -```shell -python3 -m pip install cbxp -``` - -#### Python Usage -```python3 -from cbxp import cbxp -psa = cbxp("psa") -``` - -### Shell Interface -A shell-based CLI for CBXP is available for download via [GitHub](https://github.com/ambitus/cbxp/releases). - -#### Installation *(GitHub)* -Wherever you install/extract the CBXP pax file, make sure to add the path to the `bin` directory where the `cbxp` executable resides to `PATH` in `/etc/profile` *(global)* or `~/.profile`/`~/.bashrc` *(local/individual)*. - -#### Shell Usage -```shell -cbxp psa -``` + +Currently, CBXP only has support for extracting a handful of **System-Level Control Blocks** from **Live Memory** *(storage)*. See [Supported Control Blocks](https://ambitus.github.io/cbxp/supported_control_blocks) for more details. ## Help * [GitHub Discussions](https://github.com/ambitus/cbxp/discussions) diff --git a/cbxp/control_blocks/asvt.cpp b/cbxp/control_blocks/asvt.cpp index eff61dd..17ad32f 100644 --- a/cbxp/control_blocks/asvt.cpp +++ b/cbxp/control_blocks/asvt.cpp @@ -34,7 +34,7 @@ nlohmann::json ASVT::get(void* __ptr32 p_control_block) { std::vector ascbs; ascbs.reserve(p_asvt->asvtmaxu); - uint32_t* __ptr32 p_ascb = const_cast( + const uint32_t* __ptr32 p_ascb = const_cast( reinterpret_cast(&p_asvt->asvtenty)); for (int i = 0; i < p_asvt->asvtmaxu; i++) { diff --git a/cbxp/main.cpp b/cbxp/main.cpp index 3328d94..fabb910 100644 --- a/cbxp/main.cpp +++ b/cbxp/main.cpp @@ -49,7 +49,7 @@ int main(int argc, const char* argv[]) { for (int i = 1; i < argc; i++) { std::string flag = argv[i]; - if (flag == "-d" || flag == "-debug") { + if (flag == "-d" || flag == "--debug") { debug = true; } else if (flag == "-i" || flag == "--include") { if (i + 1 >= argc - 1) { diff --git a/pyproject.toml b/pyproject.toml index 0f8ea59..e1a81c4 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -38,11 +38,11 @@ build-backend = "setuptools.build_meta" ] [project.urls] - Homepage = "https://github.ibm.com/lcarcaramo/CBExplorer/" - Documentation = "https://github.ibm.com/lcarcaramo/CBExplorer/" - Source = "https://github.ibm.com/lcarcaramo/CBExplorer/" - Issues = "https://github.ibm.com/lcarcaramo/CBExplorer/issues" - "Release Notes" = "https://github.ibm.com/lcarcaramo/CBExplorer/milestones?state=closed" + Homepage = "https://ambitus.github.io/cbxp/" + Documentation = "https://ambitus.github.io/cbxp/interfaces/python/" + Source = "https://github.com/ambitus/cbxp" + Issues = "https://github.com/ambitus/cbxp/issues" + "Release Notes" = "https://github.com/ambitus/cbxp/milestones?state=closed" [tool.setuptools.packages.find] where = ["python"] diff --git a/setup.py b/setup.py index 32cdee0..5d5588f 100644 --- a/setup.py +++ b/setup.py @@ -1,5 +1,6 @@ """Build Python extension.""" +import os from glob import glob from setuptools import Extension, setup @@ -8,6 +9,10 @@ def main(): """Python extension build entrypoint.""" + os.environ["CC"] = "ibm-clang64" + os.environ["CFLAGS"] = "-std=c11" + os.environ["CXX"] = "ibm-clang++64" + os.environ["CXXFLAGS"] = "-std=c++17" setup_args = { "ext_modules": [ Extension( diff --git a/tests/test.py b/tests/test.py index cb54888..ac541ed 100644 --- a/tests/test.py +++ b/tests/test.py @@ -2,6 +2,10 @@ from cbxp import cbxp, CBXPError class TestCBXP(unittest.TestCase): + + # ============================================================================ + # Basic Usage + # ============================================================================ def test_cbxp_can_extract_psa(self): cbdata = cbxp("psa") self.assertIs(type(cbdata), dict) @@ -23,7 +27,10 @@ def test_cbxp_can_extract_ascb(self): self.assertIs(type(cbdata), list) for entry in cbdata: self.assertIs(type(entry), dict) - + + # ============================================================================ + # Include Patterns + # ============================================================================ def test_cbxp_can_extract_the_psa_and_include_the_cvt(self): cbdata = cbxp("psa", includes=["cvt"]) self.assertIs(type(cbdata), dict) @@ -120,11 +127,24 @@ def test_cbxp_can_extract_cvt_and_include_wildcard_and_asvt_wildcard(self): for entry in cbdata["cvtasvt"]["asvtenty"]: self.assertIs(type(entry), dict) + # ============================================================================ + # Debug Mode + # ============================================================================ + def test_cbxp_can_run_in_debug_mode(self): + cbdata = cbxp("psa", debug=True) + self.assertIs(type(cbdata), dict) + + # ============================================================================ + # Errors: Unknown Control Block + # ============================================================================ def test_cbxp_raises_cbxp_error_when_unknown_control_block_is_provided(self): with self.assertRaises(CBXPError) as e: cbxp("unknown") self.assertEqual("Unknown control block 'unknown' was specified.", str(e.exception)) + # ============================================================================ + # Errors: Bad Include Patterns + # ============================================================================ def test_cbxp_raises_cbxp_error_if_asvt_ascb_is_included_when_extracting_the_psa(self): with self.assertRaises(CBXPError) as e: cbxp("psa", includes=["asvt.ascb"]) @@ -167,4 +187,4 @@ def test_cbxp_raises_cbxp_error_when_pattern_cannot_contain_comma_2(self): if __name__ == "__main__": - unittest.main(argv=["", "-v", "-f", "-b"]) + unittest.main(verbosity=2, failfast=True, buffer=True) diff --git a/tests/test.sh b/tests/test.sh index 3a6a7e9..b05ee54 100755 --- a/tests/test.sh +++ b/tests/test.sh @@ -17,39 +17,52 @@ run_with_expected_exit_code() { echo } -# exit code 0 -run_with_expected_exit_code 0 ./dist/cbxp ascb -run_with_expected_exit_code 0 ./dist/cbxp asvt -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 psa -run_with_expected_exit_code 0 ./dist/cbxp -i 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 +# Basic Usage +run_with_expected_exit_code 0 ./dist/cbxp psa +run_with_expected_exit_code 0 ./dist/cbxp cvt +run_with_expected_exit_code 0 ./dist/cbxp ecvt +run_with_expected_exit_code 0 ./dist/cbxp asvt +run_with_expected_exit_code 0 ./dist/cbxp ascb +# Include Patterns +run_with_expected_exit_code 0 ./dist/cbxp -i cvt psa +run_with_expected_exit_code 0 ./dist/cbxp --include cvt psa +run_with_expected_exit_code 0 ./dist/cbxp -i ecvt cvt +run_with_expected_exit_code 0 ./dist/cbxp -i asvt cvt +run_with_expected_exit_code 0 ./dist/cbxp -i ascb asvt +run_with_expected_exit_code 0 ./dist/cbxp -i cvt.ecvt psa +run_with_expected_exit_code 0 ./dist/cbxp -i cvt.asvt.ascb psa +run_with_expected_exit_code 0 ./dist/cbxp -i asvt.ascb cvt +run_with_expected_exit_code 0 ./dist/cbxp -i ecvt -i asvt cvt +run_with_expected_exit_code 0 ./dist/cbxp -i cvt.ecvt -i cvt.asvt.ascb psa +run_with_expected_exit_code 0 ./dist/cbxp -i "cvt.**" psa +run_with_expected_exit_code 0 ./dist/cbxp -i "cvt.*" psa +run_with_expected_exit_code 0 ./dist/cbxp -i "asvt.*" -i "*" cvt +# Debug Mode +run_with_expected_exit_code 0 ./dist/cbxp -d psa +run_with_expected_exit_code 0 ./dist/cbxp --debug psa +# Show Usage +run_with_expected_exit_code 0 ./dist/cbxp -h +run_with_expected_exit_code 0 ./dist/cbxp --help +# Show Version +run_with_expected_exit_code 0 ./dist/cbxp -v +run_with_expected_exit_code 0 ./dist/cbxp --version -# exit code 255 +# 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 unknown -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 run_with_expected_exit_code 255 ./dist/cbxp -i cvt run_with_expected_exit_code 255 ./dist/cbxp -i -i cvt psa +# Errors: Unknown Control Block +run_with_expected_exit_code 255 ./dist/cbxp unknown +# Errors: Bad Include Patterns +run_with_expected_exit_code 255 ./dist/cbxp -i asvt,ascb cvt +run_with_expected_exit_code 255 ./dist/cbxp -i asvt,as cvt +run_with_expected_exit_code 255 ./dist/cbxp -i asvt.ascb psa +run_with_expected_exit_code 255 ./dist/cbxp -i ascb psa +run_with_expected_exit_code 255 ./dist/cbxp -i ecvt ascb +run_with_expected_exit_code 255 ./dist/cbxp -i cvt.ecvt -i cvt.ascb psa +run_with_expected_exit_code 255 ./dist/cbxp -i cvt.asvt.ascb -i ecvt psa +run_with_expected_exit_code 255 ./dist/cbxp -i cvt cvt echo " -------------------------------- " echo " -------------------------------- " From c87cccf5f8f352e1ab25e1ea881fed4dafeef3c8 Mon Sep 17 00:00:00 2001 From: Leonard Carcaramo Date: Thu, 6 Nov 2025 06:47:26 -0500 Subject: [PATCH 04/37] Fix _C.pyi and removed unused import from cbxp.py. Signed-off-by: Leonard Carcaramo --- python/cbxp/_C.pyi | 2 +- python/cbxp/cbxp.py | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/python/cbxp/_C.pyi b/python/cbxp/_C.pyi index 96e2bba..d82dd77 100644 --- a/python/cbxp/_C.pyi +++ b/python/cbxp/_C.pyi @@ -1 +1 @@ -def call_cbxp(self, control_block: str, includes_string: str, debug: bool=False) -> dict: ... +def call_cbxp(control_block: str, includes_string: str, debug: bool = False) -> dict: ... diff --git a/python/cbxp/cbxp.py b/python/cbxp/cbxp.py index e43a450..bd37b4b 100644 --- a/python/cbxp/cbxp.py +++ b/python/cbxp/cbxp.py @@ -1,5 +1,4 @@ import json -from typing import Union from enum import Enum from cbxp._C import call_cbxp From b61fb73574f4f2a67805656c8826e73a32aa7a3a Mon Sep 17 00:00:00 2001 From: Chris Brooker Date: Thu, 4 Dec 2025 12:44:00 -0500 Subject: [PATCH 05/37] use a published link for DCO description (#10) Signed-off-by: Chris Brooker --- CONTRIBUTING.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 248998f..51762aa 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -37,7 +37,7 @@ The following are a set of guidelines to help you contribute. > :warning: _All code contributed must be made under an Apache 2 license._ > -> :warning: _All contributions must be accompanied by a [Developer Certification of Origin (DCO) signoff](https://github.com/openmainframeproject/tsc/blob/master/process/contribution_guidelines.md#developer-certificate-of-origin)._ +> :warning: _All contributions must be accompanied by a [Developer Certification of Origin (DCO) signoff](https://tac.openmainframeproject.org/process/contribution_guidelines.html#contribution-sign-off)._ ## Ways to Contribute From e8d93721d30a982309568909f63dc66c79298ffa Mon Sep 17 00:00:00 2001 From: "Emma S." <64806882+EmmasBox@users.noreply.github.com> Date: Fri, 5 Dec 2025 17:16:48 +0100 Subject: [PATCH 06/37] fixed some spelling and grammar mistakes (#14) Signed-off-by: Emma S. <64806882+EmmasBox@users.noreply.github.com> --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 82e14af..4cd3abd 100644 --- a/README.md +++ b/README.md @@ -4,15 +4,15 @@ ![CBXP Logo](https://raw.githubusercontent.com/ambitus/cbxp/refs/heads/main/logo.svg) -# CBXP (Control Block EXPlorer) +# CBXP (Control Block EXPlorer) A unified and standardized interface for extracting z/OS control block data. ## Description -z/OS Control Blocks are in-memory data structures that describe and control countless process, operating system components, and subsystems. Control blocks are unbiquitous on z/OS, but not very straight forward to access and extract information from. The mission of CBXP *(Control Block EXPlorer)* is to make it easy to extract z/OS control block data using industry standard tools and methodologies. CBXP accomplishes this by implementing a **C/C++ XPLINK ASCII** interface for extracting control blocks and post processing them into **JSON**. This makes it straight forward to integrate with industry standard programming languages and tools, which generally have well documented and understood foreign language intefaces for C/C++, and native and or third party JSON support that makes working with JSON data easy. +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. -CBXP is the successor to the existing [cbxplorer](https://github.com/ambitus/cbexplorer) project. CBXP mainly improves upon this existing work by being implementing in C/C++ so that it is not limited to a specific programming language or tool. CBXP also focuses heavily on providing an interface that is simple and straight forward to use. +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 From 7e099de25185a7033ccf9be55b0697b2b1cb428b Mon Sep 17 00:00:00 2001 From: "Leonard J. Carcaramo Jr" <72973973+lcarcaramo@users.noreply.github.com> Date: Tue, 16 Dec 2025 11:37:45 -0500 Subject: [PATCH 07/37] Create separate /bin/cbxp executable and /lib/libcbxp.so library (#16) * Create separate library and executable for CLI. Signed-off-by: Leonard Carcaramo * Cleanup and debug cmake Signed-off-by: Leonard Carcaramo * Update version in pyproject.toml Signed-off-by: Leonard Carcaramo * Change 'cbxp_result_t*' to 'const cbxp_result_t*' Signed-off-by: Leonard Carcaramo --------- Signed-off-by: Leonard Carcaramo --- .gitignore | 3 -- .vscode/c_cpp_properties.json | 22 ++++++++++ .vscode/settings.json | 62 --------------------------- CMakeLists.txt | 81 ++++++++++++++++++++++++++--------- cbxp/cbxp.cpp | 4 +- cbxp/cbxp.h | 13 +----- cbxp/main.cpp | 77 ++++++++++++++++++++++++--------- cbxp/python/_cbxp.c | 4 +- pyproject.toml | 2 +- setup.py | 1 + tests/test.sh | 2 + 11 files changed, 150 insertions(+), 121 deletions(-) create mode 100644 .vscode/c_cpp_properties.json delete mode 100644 .vscode/settings.json diff --git a/.gitignore b/.gitignore index 5fcbc79..81c7fc6 100644 --- a/.gitignore +++ b/.gitignore @@ -1,9 +1,6 @@ # Prerequisites *.d -# Vscode settings -.vscode/ - # Compiled Object files *.slo *.lo diff --git a/.vscode/c_cpp_properties.json b/.vscode/c_cpp_properties.json new file mode 100644 index 0000000..4468274 --- /dev/null +++ b/.vscode/c_cpp_properties.json @@ -0,0 +1,22 @@ +{ + "configurations": [ + { + "name": "Mac", + "includePath": [ + "${workspaceFolder}/cbxp", + "${workspaceFolder}/externals", + "/opt/homebrew/opt/python@3.12/Frameworks/Python.framework/Versions/3.12/include/python3.12", + "/opt/homebrew/opt/python@3.13/Frameworks/Python.framework/Versions/3.13/include/python3.13" + ], + "defines": [ + "__ptr32=", + "VERSION=\"test\"" + ], + "compilerPath": "/usr/bin/clang", + "cStandard": "c11", + "cppStandard": "c++17", + "intelliSenseMode": "macos-clang-arm64" + } + ], + "version": 4 +} diff --git a/.vscode/settings.json b/.vscode/settings.json deleted file mode 100644 index 5cd750a..0000000 --- a/.vscode/settings.json +++ /dev/null @@ -1,62 +0,0 @@ -{ - "files.associations": { - "*.py": "python", - "__bit_reference": "cpp", - "__hash_table": "cpp", - "__locale": "cpp", - "__node_handle": "cpp", - "__split_buffer": "cpp", - "__verbose_abort": "cpp", - "array": "cpp", - "bitset": "cpp", - "cctype": "cpp", - "charconv": "cpp", - "clocale": "cpp", - "cmath": "cpp", - "complex": "cpp", - "cstdarg": "cpp", - "cstdint": "cpp", - "cstdio": "cpp", - "cstdlib": "cpp", - "cstring": "cpp", - "ctime": "cpp", - "cwchar": "cpp", - "cwctype": "cpp", - "deque": "cpp", - "execution": "cpp", - "memory": "cpp", - "initializer_list": "cpp", - "iomanip": "cpp", - "ios": "cpp", - "iosfwd": "cpp", - "iostream": "cpp", - "istream": "cpp", - "limits": "cpp", - "locale": "cpp", - "mutex": "cpp", - "new": "cpp", - "optional": "cpp", - "print": "cpp", - "queue": "cpp", - "ratio": "cpp", - "sstream": "cpp", - "stack": "cpp", - "stdexcept": "cpp", - "streambuf": "cpp", - "string": "cpp", - "string_view": "cpp", - "typeinfo": "cpp", - "unordered_map": "cpp", - "variant": "cpp", - "vector": "cpp", - "algorithm": "cpp", - "__tree": "cpp", - "any": "cpp", - "forward_list": "cpp", - "map": "cpp", - "span": "cpp", - "valarray": "cpp", - "csignal": "cpp", - "cstddef": "cpp" - } -} diff --git a/CMakeLists.txt b/CMakeLists.txt index f27e724..b44eb9f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,28 +1,40 @@ 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." + LANGUAGES C CXX) + +set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "./dist") +set(CMAKE_LIBRARY_OUTPUT_DIRECTORY "./dist") + execute_process( COMMAND uname OUTPUT_VARIABLE uname_output RESULT_VARIABLE uname_result ) +# ============================================================================ +# Compiler Info +# ============================================================================ set(CXX_STANDARD 17) +set(COMPILE_OPTIONS -O2 -fzos-le-char-mode=ascii -Wno-trigraphs) +set(LINK_OPTIONS -m64 -Wl,-b,edit=no) + if(uname_output STREQUAL "OS/390\n") set(CMAKE_CXX_COMPILER "ibm-clang++64") else() set(CMAKE_CXX_COMPILER "clang") endif() -set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "./dist") - -project(CBXP VERSION 0.0.2 - DESCRIPTION "A unified and standardized interface for extracting z/OS control block data." - LANGUAGES C CXX) - +# ============================================================================ +# Sources +# ============================================================================ file( - GLOB CBXP_SRC - "cbxp/*.cpp" + GLOB CBXP_SRC_LIB + "cbxp/cbxp.cpp" + "cbxp/control_block_explorer.cpp" + "cbxp/logger.cpp" "cbxp/control_blocks/*.cpp" ) @@ -38,33 +50,47 @@ file( "cbxp/**/*.h" ) -add_executable(cbxp ${CBXP_SRC}) +# ============================================================================ +# Build '/lib/libcbxp.so' +# ============================================================================ +add_library(libcbxp SHARED ${CBXP_SRC_LIB}) +set_target_properties(libcbxp PROPERTIES OUTPUT_NAME "cbxp") target_include_directories( - cbxp PUBLIC + libcbxp PUBLIC cbxp cbxp/control_blocks externals /usr/include/zos ) +target_compile_options(libcbxp PUBLIC ${COMPILE_OPTIONS}) +target_link_options(libcbxp PUBLIC ${LINK_OPTIONS}) + +# ============================================================================ +# Build '/bin/cbxp' +# ============================================================================ +add_executable(cbxp "cbxp/main.cpp") + +target_include_directories(cbxp PUBLIC cbxp externals) + target_compile_definitions( - cbxp - PUBLIC + cbxp PUBLIC VERSION="${CMAKE_PROJECT_VERSION}" ) -target_compile_options( - cbxp PUBLIC - -fzos-le-char-mode=ascii - -Wno-trigraphs -) +target_compile_options(cbxp PUBLIC ${COMPILE_OPTIONS}) +target_link_options(cbxp PUBLIC ${LINK_OPTIONS}) +# ============================================================================ +# Packaging +# ============================================================================ add_custom_target( package COMMAND "mkdir" "-p" "cbxp-${CMAKE_PROJECT_VERSION}/bin" + "cbxp-${CMAKE_PROJECT_VERSION}/lib" COMMAND "cp" "LICENSE" "cbxp-${CMAKE_PROJECT_VERSION}/" @@ -72,16 +98,22 @@ add_custom_target( "NOTICES" "cbxp-${CMAKE_PROJECT_VERSION}/" COMMAND "cp" - "${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/cbxp" + "./dist/libcbxp.so" + "cbxp-${CMAKE_PROJECT_VERSION}/lib/" + COMMAND "cp" + "./dist/cbxp" "cbxp-${CMAKE_PROJECT_VERSION}/bin/" COMMAND "pax" "-wzf" - "${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/cbxp-${CMAKE_PROJECT_VERSION}.pax.Z" + "./dist/cbxp-${CMAKE_PROJECT_VERSION}.pax.Z" "-o saveext" "cbxp-${CMAKE_PROJECT_VERSION}/*" - DEPENDS cbxp + DEPENDS libcbxp cbxp ) +# ============================================================================ +# Cppcheck +# ============================================================================ add_custom_target( check COMMAND "cppcheck" @@ -101,6 +133,9 @@ add_custom_target( "${CMAKE_CURRENT_SOURCE_DIR}/cbxp" ) +# ============================================================================ +# Lint +# ============================================================================ add_custom_target( lint COMMAND "clang-format" @@ -109,6 +144,9 @@ add_custom_target( ${CBXP_SRC_ALL} ) +# ============================================================================ +# Format +# ============================================================================ add_custom_target( format COMMAND "clang-format" @@ -116,6 +154,9 @@ add_custom_target( ${CBXP_SRC_ALL} ) +# ============================================================================ +# Test +# ============================================================================ add_custom_target( test COMMAND "./tests/test.sh" diff --git a/cbxp/cbxp.cpp b/cbxp/cbxp.cpp index 68c5f87..5e8618f 100644 --- a/cbxp/cbxp.cpp +++ b/cbxp/cbxp.cpp @@ -7,8 +7,8 @@ #include "cbxp_result.h" #include "control_block_explorer.hpp" -cbxp_result_t* cbxp(const char* control_block_name, const char* includes_string, - bool debug) { +const cbxp_result_t* cbxp(const char* control_block_name, + const char* includes_string, bool debug) { nlohmann::json control_block_json; std::string control_block = control_block_name; diff --git a/cbxp/cbxp.h b/cbxp/cbxp.h index 5ac1292..4e50bf7 100644 --- a/cbxp/cbxp.h +++ b/cbxp/cbxp.h @@ -7,17 +7,8 @@ extern "C" { #endif -/* -This is the main interface to CBXP. - -The following pointers must be freed after calling this interface to -avoid memory leaks: - - result.result_json - -*/ -cbxp_result_t* cbxp(const char* control_block_name, const char* includes_string, - bool debug); +const cbxp_result_t* cbxp(const char* control_block_name, + const char* includes_string, bool debug); #ifdef __cplusplus } diff --git a/cbxp/main.cpp b/cbxp/main.cpp index fabb910..19c6ea5 100644 --- a/cbxp/main.cpp +++ b/cbxp/main.cpp @@ -1,11 +1,22 @@ +#define _UNIX03_SOURCE + +#include + #include +#include #include #include #include #include "cbxp_result.h" #include "control_block_error.hpp" -#include "control_block_explorer.hpp" + +typedef const cbxp_result_t* (*cbxp_t)(const char* control_block_name, + const char* includes_string, bool debug); + +static void show_usage(const char* argv[]); +static void show_dll_errors(); +static void cleanup_and_exit(int exit_rc, void* lib_handle); static void show_usage(const char* argv[]) { std::cout << "Usage: " << argv[0] << " [options] " << std::endl @@ -24,26 +35,56 @@ static void show_usage(const char* argv[]) { << std::endl; } +static void show_dll_errors() { + const char* error_string = dlerror(); + if (error_string != nullptr) { + std::cerr << error_string << std::endl; + } +} + +static void cleanup_and_exit(int exit_rc, void* lib_handle) { + int rc = dlclose(lib_handle); + if (rc != 0) { + show_dll_errors(); + exit(-1); + } + exit(exit_rc); +} + int main(int argc, const char* argv[]) { + // Load 'libcbxp.so' + void* lib_handle = dlopen("libcbxp.so", RTLD_NOW); + if (lib_handle == nullptr) { + show_dll_errors(); + return -1; + } + + // Resolve symbol 'cbxp()' + cbxp_t cbxp = reinterpret_cast(dlsym(lib_handle, "cbxp")); + if (cbxp == nullptr) { + show_dll_errors(); + cleanup_and_exit(-1, lib_handle); + } + bool debug = false; std::string control_block_name = "", includes_string = ""; if (argc < 2) { show_usage(argv); - return -1; + cleanup_and_exit(-1, lib_handle); } if (argc == 2) { if (std::strcmp(argv[1], "-v") == 0 || std::strcmp(argv[1], "--version") == 0) { std::cout << "CBXP " << VERSION << std::endl; - return 0; + cleanup_and_exit(0, lib_handle); } if (std::strcmp(argv[1], "-h") == 0 || std::strcmp(argv[1], "--help") == 0) { show_usage(argv); - return 0; + cleanup_and_exit(0, lib_handle); } } @@ -54,14 +95,14 @@ int main(int argc, const char* argv[]) { } else if (flag == "-i" || flag == "--include") { if (i + 1 >= argc - 1) { show_usage(argv); - return -1; + cleanup_and_exit(-1, lib_handle); } std::string include = std::string(argv[++i]); bool has_comma = std::any_of(include.begin(), include.end(), [](char c) { return c == ','; }); if (has_comma) { std::cerr << "Include patterns cannot contain commas" << std::endl; - return -1; + cleanup_and_exit(-1, lib_handle); } if (includes_string == "") { includes_string = include; @@ -71,7 +112,7 @@ int main(int argc, const char* argv[]) { } else { if (i != argc - 1) { show_usage(argv); - return -1; + cleanup_and_exit(-1, lib_handle); } control_block_name = std::string(argv[i]); } @@ -79,28 +120,24 @@ int main(int argc, const char* argv[]) { if (control_block_name == "") { show_usage(argv); - return -1; + cleanup_and_exit(-1, lib_handle); } nlohmann::json control_block_json; - static cbxp_result_t cbxp_result = {nullptr, 0, -1}; - - CBXP::ControlBlockExplorer explorer = - CBXP::ControlBlockExplorer(&cbxp_result, debug); - - explorer.exploreControlBlock(control_block_name, includes_string); + const cbxp_result_t* cbxp_result = + cbxp(control_block_name.c_str(), includes_string.c_str(), debug); - if (cbxp_result.return_code == CBXP::Error::BadControlBlock) { + if (cbxp_result->return_code == CBXP::Error::BadControlBlock) { std::cerr << "Unknown control block '" << control_block_name << "' was specified." << std::endl; - return -1; - } else if (cbxp_result.return_code == CBXP::Error::BadInclude) { + cleanup_and_exit(-1, lib_handle); + } else if (cbxp_result->return_code == CBXP::Error::BadInclude) { std::cerr << "A bad include pattern was provided" << std::endl; - return -1; + cleanup_and_exit(-1, lib_handle); } else { - std::cout << cbxp_result.result_json << std::endl; + std::cout << cbxp_result->result_json << std::endl; } - return 0; + cleanup_and_exit(0, lib_handle); } diff --git a/cbxp/python/_cbxp.c b/cbxp/python/_cbxp.c index 90fe08c..9b167a0 100644 --- a/cbxp/python/_cbxp.c +++ b/cbxp/python/_cbxp.c @@ -34,9 +34,9 @@ static PyObject* call_cbxp(PyObject* self, PyObject* args, PyObject* kwargs) { // but we will set this up anyways to be safe. pthread_mutex_lock(&cbxp_mutex); - cbxp_result_t* result = cbxp(control_block, includes_string, debug); + const cbxp_result_t* result = cbxp(control_block, includes_string, debug); - result_dictionary = Py_BuildValue( + result_dictionary = Py_BuildValue( "{s:s#, s:i}", "result_json", result->result_json, result->result_json_length, "return_code", result->return_code); diff --git a/pyproject.toml b/pyproject.toml index e1a81c4..8d34536 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name="cbxp" - version="0.0.2" + version="0.0.3" requires-python = ">= 3.12" authors = [ { name = "Leonard J. Carcaramo Jr", email = "lcarcaramo@ibm.com" }, diff --git a/setup.py b/setup.py index 5d5588f..341d263 100644 --- a/setup.py +++ b/setup.py @@ -35,6 +35,7 @@ def main(): "-Wl,-b,edit=no", ], extra_compile_args=[ + "-O2", "-fzos-le-char-mode=ascii", "-Wno-trigraphs" ] diff --git a/tests/test.sh b/tests/test.sh index b05ee54..ee8c130 100755 --- a/tests/test.sh +++ b/tests/test.sh @@ -1,5 +1,7 @@ #!/bin/bash +export LIBPATH=${PWD}/dist:$LIBPATH + run_with_expected_exit_code() { expected_code=$1 shift From bf0cdcb025ec60f52d6dbd001a755883e9b86aee Mon Sep 17 00:00:00 2001 From: Varun R Chennamadhava <38167054+varunchennamadhava@users.noreply.github.com> Date: Tue, 16 Dec 2025 12:43:07 -0500 Subject: [PATCH 08/37] Feat/assb control block (#17) * Initial Alpha (#19) * Feature/includeparameter (#14) * Add skeleton for inclusion list parameter Extend interface/skeleton for include_list * Input POinter Attempt to add input pointer parameter for control blocks * Definitions outside of if statements * Attempt to map the intended inclusion list * Try Virtual Function to keep Control Block in Explorer * No longer have get return * Attempt to add the meat of the function -Add methods and members to control blocks to get name of control block, specific control blocks within it, and all control blocks within it -add functions in control block explorer to parse inclusion "map" into actionable data name fix * Update psa.cpp Update psa.cpp * Update cvt.cpp * Update asvt.cpp * algorithm cleanup * better cleanup * Update control_block.cpp * Update main.cpp * Finish addressing merge issues and commit hooks -Minor code updates that were lost in merge commit -Bring new code up to standard for cppcheck -Format new code with clang-format * Update _cbxp.c * Streamline Control Block Explorer Class Update control_block_explorer.hpp * Massive refactor -Shave 2 step process down to one -Change serialized json inclusion map to use a vector of strings still split by "dot" operators * Error Handling Logic * BIG UPDATE PR COMMENTS -Switch pre-processing to one hash map function -use try/catch with custom errors rather than passing return codes everywhere -style and name changes -Enforce more rigid parm structure on entry -Fix some behavioral bugs and oversights in inclusion preprocessing -General streamlining and refactoring of functions, methods, classes, etc. * Another Big Refactor -PR comments (mostly style, but streamlining of error code as well) -Reworked base and derived classes to allow for includables to be defined to the base class and include_map to be defined to the base and derived classes * Update ascb.cpp * Update control_block.hpp * . * .. * ... * PR Comments -ASCB pointer deref in ASVT -Minor name changes -Remove double wildcard error -Add control_block_name_ private member and add initialization to constructor -move include_map_ to protected and remove private using statement * Update asvt.cpp * Update asvt.cpp * PR Comments Mostly renaming things streamlining some unnecessary text, parms and strings * Update control_block.cpp * Update main.cpp * Last round of PR comments string compare with == remove vestiges of old mechanisms for control block management name changes minor tweaks * Update cvt.cpp * Update cvt.cpp * Update cvt.cpp * Final comments Update control_block_explorer.cpp * comments * Last Comments * include changes * Last round of comments * debug * Unit testing (#17) * initial commit 1 * cleaned code before include test cases * wrote test cases, need to check with team now * added space after every function * added .value * shell script done * made changes proposed by leonard 1 * PR changes requested by team * added tests to check for ascb and asvt entries whether it be a string or dict * added tests to check for ascb and asvt entries whether it be a string or dict one more place * made minor tweaks * added updates provided by leonard * grouped failure test cases together * grouped error test cases together * removed extra lines * style changes * Feat/oss housekeeping2 (#18) * Set explicit C/C++ standard and cleanup README. Signed-off-by: Leonard Carcaramo * Update contribution guidelines and functional tests. Signed-off-by: Leonard Carcaramo * Cleanup contribution guidelines and debug debug mode. Signed-off-by: Leonard Carcaramo * Cleanup. Signed-off-by: Leonard Carcaramo * Cleanup. Signed-off-by: Leonard Carcaramo * Fix sdist packaging and pyproject.toml metadata. Signed-off-by: Leonard Carcaramo --------- Signed-off-by: Leonard Carcaramo * Fix _C.pyi and removed unused import from cbxp.py. Signed-off-by: Leonard Carcaramo --------- Signed-off-by: Leonard Carcaramo Co-authored-by: Elijah Swift Co-authored-by: Varun Chennamadhava * test commit Signed-off-by: varunchennamadhava * error 1 Signed-off-by: varunchennamadhava * error 2 Signed-off-by: varunchennamadhava * fixed error Signed-off-by: varunchennamadhava * fixed error 1 Signed-off-by: varunchennamadhava * assb include Signed-off-by: varunchennamadhava * assb all fields draft 1 Signed-off-by: varunchennamadhava * Changed assbjbini and assbjbns to getString() Signed-off-by: varunchennamadhava * added test cases Signed-off-by: varunchennamadhava * made linting formatting changes Signed-off-by: varunchennamadhava * fixed an error with p_ascb_addr, made it const Signed-off-by: varunchennamadhava * fixed error with ascbasid Signed-off-by: varunchennamadhava * moved a function from private to public to private in control_block_field formatter and used that function to remove kruft Signed-off-by: varunchennamadhava * format and cleaning out 1 test case Signed-off-by: varunchennamadhava --------- Signed-off-by: Leonard Carcaramo Signed-off-by: varunchennamadhava Co-authored-by: Leonard Carcaramo Co-authored-by: Elijah Swift --- cbxp/control_block_explorer.cpp | 3 + cbxp/control_blocks/ascb.cpp | 10 +- cbxp/control_blocks/ascb.hpp | 2 +- cbxp/control_blocks/assb.cpp | 200 ++++++++++++++++++ cbxp/control_blocks/assb.hpp | 17 ++ .../control_block_field_formatter.hpp | 3 +- tests/test.py | 40 +++- tests/test.sh | 5 + 8 files changed, 273 insertions(+), 7 deletions(-) create mode 100644 cbxp/control_blocks/assb.cpp create mode 100644 cbxp/control_blocks/assb.hpp diff --git a/cbxp/control_block_explorer.cpp b/cbxp/control_block_explorer.cpp index ea1c95e..f1cfd65 100644 --- a/cbxp/control_block_explorer.cpp +++ b/cbxp/control_block_explorer.cpp @@ -7,6 +7,7 @@ #include "cbxp.h" #include "control_block_error.hpp" #include "control_blocks/ascb.hpp" +#include "control_blocks/assb.hpp" #include "control_blocks/asvt.hpp" #include "control_blocks/control_block.hpp" #include "control_blocks/cvt.hpp" @@ -83,6 +84,8 @@ void ControlBlockExplorer::exploreControlBlock( control_block_json = ASCB(includes).get(); } else if (control_block_name == "asvt") { control_block_json = ASVT(includes).get(); + } else if (control_block_name == "assb") { + control_block_json = ASSB(includes).get(); } else { throw ControlBlockError(); } diff --git a/cbxp/control_blocks/ascb.cpp b/cbxp/control_blocks/ascb.cpp index e8d7312..d2f5fa9 100644 --- a/cbxp/control_blocks/ascb.cpp +++ b/cbxp/control_blocks/ascb.cpp @@ -9,6 +9,7 @@ #include #include +#include "assb.hpp" #include "asvt.hpp" #include "logger.hpp" @@ -52,12 +53,19 @@ nlohmann::json ASCB::get(void* __ptr32 p_control_block) { ascb_json["ascbassb"] = formatter_.getHex(&(p_ascb->ascbassb)); ascb_json["ascbasxb"] = formatter_.getHex(&(p_ascb->ascbasxb)); + for (const auto& [include, include_includes] : include_map_) { + if (include == "assb") { + ascb_json["ascbassb"] = + CBXP::ASSB(include_includes).get(p_ascb->ascbassb); + } + } + Logger::getInstance().debug("ASCB hex dump:"); Logger::getInstance().hexDump(reinterpret_cast(p_ascb), sizeof(struct ascb)); ascb_json["ascbascb"] = formatter_.getString(p_ascb->ascbascb, 4); - ascb_json["ascbasid"] = formatter_.getBitmap(p_ascb->ascbasid); + ascb_json["ascbasid"] = p_ascb->ascbasn; ascb_json["ascbdcti"] = p_ascb->ascbdcti; ascb_json["ascbejst"] = formatter_.getBitmap( reinterpret_cast(&p_ascb->ascbejst)); diff --git a/cbxp/control_blocks/ascb.hpp b/cbxp/control_blocks/ascb.hpp index f15b841..e811530 100644 --- a/cbxp/control_blocks/ascb.hpp +++ b/cbxp/control_blocks/ascb.hpp @@ -9,7 +9,7 @@ class ASCB : public ControlBlock { public: nlohmann::json get(void* __ptr32 p_control_block = nullptr) override; explicit ASCB(const std::vector& includes) - : ControlBlock("ascb", {}, includes) {} + : ControlBlock("ascb", {"assb"}, includes) {} }; } // namespace CBXP diff --git a/cbxp/control_blocks/assb.cpp b/cbxp/control_blocks/assb.cpp new file mode 100644 index 0000000..3a12daf --- /dev/null +++ b/cbxp/control_blocks/assb.cpp @@ -0,0 +1,200 @@ +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "ascb.hpp" +#include "asvt.hpp" +#include "logger.hpp" + +namespace CBXP { +nlohmann::json ASSB::get(void* __ptr32 p_control_block) { + const assb* __ptr32 p_assb; + nlohmann::json assb_json = {}; + if (p_control_block == nullptr) { + // PSA starts at address 0 + const struct psa* __ptr32 p_psa = 0; + + const struct cvtmap* __ptr32 p_cvtmap = + // 'nullPointer' is a false positive because the PSA starts at address 0 + // cppcheck-suppress nullPointer + static_cast(p_psa->flccvt); + const asvt_t* __ptr32 p_asvt = + static_cast(p_cvtmap->cvtasvt); + + assb_json["assbs"] = std::vector(); + std::vector& assbs = + assb_json["assbs"].get_ref&>(); + assbs.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 addr into ascb pointer a + // + const struct ascb* __ptr32 p_ascb = + reinterpret_cast(*p_ascb_addr); + assbs.push_back( + ASSB::get(reinterpret_cast(p_ascb->ascbassb))); + p_ascb_addr++; // This SHOULD increment the pointer by 4 bytes. + } + return assbs; + } else { + 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["assb_cms_lockinst_addr"] = + formatter_.getHex(&(p_assb->assb_cms_lockinst_addr)); + assb_json["assb_enqdeq_cms_lockinst_addr"] = + formatter_.getHex(&(p_assb->assb_enqdeq_cms_lockinst_addr)); + assb_json["assb_latch_cms_lockinst_addr"] = + formatter_.getHex(&(p_assb->assb_latch_cms_lockinst_addr)); + assb_json["assb_local_lockinst_addr"] = + formatter_.getHex(&(p_assb->assb_local_lockinst_addr)); + assb_json["assb_smfcms_lockinst_addr"] = + formatter_.getHex(&(p_assb->assb_smfcms_lockinst_addr)); + assb_json["assbdlcb"] = formatter_.getHex(&(p_assb->assbdlcb)); + assb_json["assbmqma"] = formatter_.getHex(&(p_assb->assbmqma)); + 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); + assb_json["assb_asst_time_on_cp"] = + formatter_.uint(p_assb->assb_asst_time_on_cp); + assb_json["assb_asst_time_on_zcbp"] = + formatter_.uint(p_assb->assb_asst_time_on_zcbp); + assb_json["assb_enct"] = formatter_.uint(p_assb->assb_enct); + assb_json["assb_enct_prezos11"] = + formatter_.uint(p_assb->assb_enct_prezos11); + assb_json["assb_ifa_enct"] = formatter_.uint(p_assb->assb_ifa_enct); + assb_json["assb_ifa_enct_prezos11"] = + formatter_.uint(p_assb->assb_ifa_enct_prezos11); + assb_json["assb_ifa_on_cp_enct"] = + formatter_.uint(p_assb->assb_ifa_on_cp_enct); + assb_json["assb_ifa_phtm"] = formatter_.uint(p_assb->assb_ifa_phtm); + assb_json["assb_srb_time_on_cp"] = + formatter_.uint(p_assb->assb_srb_time_on_cp); + assb_json["assb_srb_time_on_zcbp"] = + formatter_.uint(p_assb->assb_srb_time_on_zcbp); + assb_json["assb_sup_on_cp_enct"] = + formatter_.uint(p_assb->assb_sup_on_cp_enct); + assb_json["assb_task_time_on_cp"] = + formatter_.uint(p_assb->assb_task_time_on_cp); + assb_json["assb_time_ifa_on_cp"] = + formatter_.uint(p_assb->assb_time_ifa_on_cp); + assb_json["assb_time_java_on_cp"] = + formatter_.uint(p_assb->assb_time_java_on_cp); + assb_json["assb_time_java_on_ziip"] = + formatter_.uint(p_assb->assb_time_java_on_ziip); + assb_json["assb_time_on_ifa"] = + formatter_.uint(p_assb->assb_time_on_ifa); + assb_json["assb_time_on_zaap"] = + formatter_.uint(p_assb->assb_time_on_zaap); + assb_json["assb_time_on_zcbp"] = + formatter_.uint(p_assb->assb_time_on_zcbp); + assb_json["assb_time_on_ziip"] = + formatter_.uint(p_assb->assb_time_on_ziip); + assb_json["assb_time_zcbp_on_cp"] = + formatter_.uint(p_assb->assb_time_zcbp_on_cp); + assb_json["assb_time_ziip_on_cp"] = + formatter_.uint(p_assb->assb_time_ziip_on_cp); + assb_json["assb_zaap_enct"] = + formatter_.uint(p_assb->assb_zaap_enct); + assb_json["assb_zaap_phtm"] = + formatter_.uint(p_assb->assb_zaap_phtm); + assb_json["assb_zcbp_base_phtm"] = + formatter_.uint(p_assb->assb_zcbp_base_phtm); + assb_json["assb_zcbp_enct"] = + formatter_.uint(p_assb->assb_zcbp_enct); + assb_json["assb_zcbp_on_cp_enct"] = + formatter_.uint(p_assb->assb_zcbp_on_cp_enct); + assb_json["assb_zcbp_phtm"] = + formatter_.uint(p_assb->assb_zcbp_phtm); + assb_json["assb_ziip_enct"] = + formatter_.uint(p_assb->assb_ziip_enct); + assb_json["assb_ziip_on_cp_enct"] = + formatter_.uint(p_assb->assb_ziip_on_cp_enct); + assb_json["assb_ziip_phtm"] = + formatter_.uint(p_assb->assb_ziip_phtm); + assb_json["assb_ziip_phtm_base"] = + formatter_.uint(p_assb->assb_ziip_phtm_base); + assb_json["assbasst"] = formatter_.uint(p_assb->assbasst); + assb_json["assbphtm"] = formatter_.uint(p_assb->assbphtm); + assb_json["assbphtm_base"] = formatter_.uint(p_assb->assbphtm_base); + assb_json["assbstkn"] = formatter_.uint(p_assb->assbstkn); + assb_json["assbhst"] = formatter_.uint(p_assb->assbhst); + assb_json["assbiipt"] = formatter_.uint(p_assb->assbiipt); + assb_json["assblasb"] = formatter_.uint(p_assb->assblasb); + assb_json["assb_time_on_cp"] = + formatter_.uint(p_assb->assb_time_on_cp); + assb_json["assb_base_phtm"] = + formatter_.uint(p_assb->assb_base_phtm); + assb_json["assb_ifa_base_phtm"] = + formatter_.uint(p_assb->assb_ifa_base_phtm); + assb_json["assb_base_enct"] = + formatter_.uint(p_assb->assb_base_enct); + assb_json["assb_zcbp_base_enct"] = + formatter_.uint(p_assb->assb_zcbp_base_enct); + assb_json["assb_ifa_base_enct"] = + formatter_.uint(p_assb->assb_ifa_base_enct); + assb_json["assb_time_on_sup"] = + formatter_.uint(p_assb->assb_time_on_sup); + assb_json["assb_time_sup_on_cp"] = + formatter_.uint(p_assb->assb_time_sup_on_cp); + assb_json["assb_sup_phtm"] = formatter_.uint(p_assb->assb_sup_phtm); + assb_json["assb_sup_enct"] = formatter_.uint(p_assb->assb_sup_enct); + assb_json["assb_zcbp_on_cp_base_enct"] = + formatter_.uint(p_assb->assb_zcbp_on_cp_base_enct); + assb_json["assb_ifa_on_cp_base_enct"] = + formatter_.uint(p_assb->assb_ifa_on_cp_base_enct); + assb_json["assb_time_at_pdp"] = + formatter_.uint(p_assb->assb_time_at_pdp); + assb_json["assb_srbt_base"] = + formatter_.uint(p_assb->assb_srbt_base); + assb_json["assb_switch_to_zaapziip_count"] = + formatter_.uint(p_assb->assb_switch_to_zaapziip_count); + assb_json["assbasab"] = formatter_.uint(p_assb->assbasab); + assb_json["assb_ziip_base_enct"] = + formatter_.uint(p_assb->assb_ziip_base_enct); + assb_json["assb_sup_base_enct"] = + formatter_.uint(p_assb->assb_sup_base_enct); + assb_json["assb_ziip_on_cp_base_enct"] = + formatter_.uint(p_assb->assb_ziip_on_cp_base_enct); + assb_json["assb_sup_on_cp_base_enct"] = + formatter_.uint(p_assb->assb_sup_on_cp_base_enct); + assb_json["assb_hdlockpromotion_time_at_pdp"] = + formatter_.uint(p_assb->assb_hdlockpromotion_time_at_pdp); + assb_json["assb_enct_hdlockpromote_time"] = + formatter_.uint(p_assb->assb_enct_hdlockpromote_time); + assb_json["assbsupc"] = formatter_.uint(p_assb->assbsupc); + assb_json["assb_vartime_at_pdp"] = + formatter_.uint(p_assb->assb_vartime_at_pdp); + assb_json["assb_varweighted_time_at_pdp"] = + formatter_.uint(p_assb->assb_varweighted_time_at_pdp); + assb_json["assb_scmbc"] = formatter_.uint(p_assb->assb_scmbc); + assb_json["assbinitiatorjobid"] = + formatter_.uint(p_assb->assbinitiatorjobid); + assb_json["assbend"] = formatter_.uint(p_assb->assbend); + + return assb_json; +} +} // namespace CBXP diff --git a/cbxp/control_blocks/assb.hpp b/cbxp/control_blocks/assb.hpp new file mode 100644 index 0000000..6692b2f --- /dev/null +++ b/cbxp/control_blocks/assb.hpp @@ -0,0 +1,17 @@ +#ifndef __ASSB_H_ +#define __ASSB_H_ + +#include "control_block.hpp" + +namespace CBXP { + +class ASSB : public ControlBlock { + public: + nlohmann::json get(void* __ptr32 p_control_block = nullptr) override; + explicit ASSB(const std::vector& includes) + : ControlBlock("assb", {}, includes) {} +}; + +} // namespace CBXP + +#endif diff --git a/cbxp/control_blocks/control_block_field_formatter.hpp b/cbxp/control_blocks/control_block_field_formatter.hpp index 09129f1..7cf499b 100644 --- a/cbxp/control_blocks/control_block_field_formatter.hpp +++ b/cbxp/control_blocks/control_block_field_formatter.hpp @@ -10,7 +10,7 @@ namespace CBXP { class ControlBlockFieldFormatter { - private: + public: template static T uint(const void* p_field) { T uint_field; @@ -18,7 +18,6 @@ class ControlBlockFieldFormatter { return uint_field; } - public: static const std::string getString(const void* p_field, int length) { auto ascii_field_unique_ptr = std::make_unique(length); std::memcpy(ascii_field_unique_ptr.get(), p_field, length); diff --git a/tests/test.py b/tests/test.py index ac541ed..cf7f2a7 100644 --- a/tests/test.py +++ b/tests/test.py @@ -28,6 +28,11 @@ def test_cbxp_can_extract_ascb(self): for entry in cbdata: self.assertIs(type(entry), dict) + def test_cbxp_can_extract_assb(self): + cbdata = cbxp("assb") + self.assertIs(type(cbdata), list) + for entry in cbdata: + self.assertIs(type(entry), dict) # ============================================================================ # Include Patterns # ============================================================================ @@ -55,6 +60,13 @@ def test_cbxp_can_extract_the_asvt_and_include_the_ascb(self): 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"]) + 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_psa_and_include_the_cvt_ecvt(self): cbdata = cbxp("psa", includes=["cvt.ecvt"]) @@ -70,7 +82,7 @@ def test_cbxp_can_extract_the_psa_and_include_the_cvt_ecvt_ascb(self): self.assertIs(type(cbdata["flccvt"]["cvtasvt"]["asvtenty"]), list) for entry in cbdata["flccvt"]["cvtasvt"]["asvtenty"]: self.assertIs(type(entry), dict) - + def test_cbxp_can_extract_the_cvt_and_include_the_asvt_ascb(self): cbdata = cbxp("cvt", includes=["asvt.ascb"]) self.assertIs(type(cbdata), dict) @@ -98,6 +110,17 @@ def test_cbxp_include_can_extract_psa_and_include_pattern_ecvt_asvt_and_cvt_asvt for entry in cbdata["flccvt"]["cvtasvt"]["asvtenty"]: self.assertIs(type(entry), dict) + def test_cbxp_include_can_extract_psa_and_include_pattern_ecvt_asvt_and_cvt_asvt_ascb_assb(self): + cbdata = cbxp("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) + 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) + def test_cbxp_can_extract_psa_and_include_cvt_recursive_wildcard(self): cbdata = cbxp("psa", includes=["cvt.**"]) self.assertIs(type(cbdata), dict) @@ -107,7 +130,8 @@ def test_cbxp_can_extract_psa_and_include_cvt_recursive_wildcard(self): 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) + def test_cbxp_can_extract_psa_and_include_cvt_wildcard(self): cbdata = cbxp("psa", includes=["cvt.*"]) self.assertIs(type(cbdata), dict) @@ -117,7 +141,7 @@ def test_cbxp_can_extract_psa_and_include_cvt_wildcard(self): self.assertIs(type(cbdata["flccvt"]["cvtasvt"]["asvtenty"]), list) for entry in cbdata["flccvt"]["cvtasvt"]["asvtenty"]: self.assertIs(type(entry), str) - + def test_cbxp_can_extract_cvt_and_include_wildcard_and_asvt_wildcard(self): cbdata = cbxp("cvt", includes=["*", "asvt.*"]) self.assertIs(type(cbdata), dict) @@ -126,6 +150,16 @@ def test_cbxp_can_extract_cvt_and_include_wildcard_and_asvt_wildcard(self): self.assertIs(type(cbdata["cvtasvt"]["asvtenty"]), list) for entry in cbdata["cvtasvt"]["asvtenty"]: self.assertIs(type(entry), dict) + + def test_cbxp_can_extract_cvt_and_include_wildcard_and_asvt_recursive_wildcard(self): + cbdata = cbxp("cvt", includes=["*", "asvt.**"]) + self.assertIs(type(cbdata), dict) + self.assertIs(type(cbdata["cvtecvt"]), dict) + self.assertIs(type(cbdata["cvtasvt"]), dict) + self.assertIs(type(cbdata["cvtasvt"]["asvtenty"]), list) + for entry in cbdata["cvtasvt"]["asvtenty"]: + self.assertIs(type(entry), dict) + self.assertIs(type(entry["ascbassb"]), dict) # ============================================================================ # Debug Mode diff --git a/tests/test.sh b/tests/test.sh index ee8c130..1706df0 100755 --- a/tests/test.sh +++ b/tests/test.sh @@ -25,6 +25,8 @@ 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 + # Include Patterns run_with_expected_exit_code 0 ./dist/cbxp -i cvt psa run_with_expected_exit_code 0 ./dist/cbxp --include cvt psa @@ -39,6 +41,9 @@ 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 cvt.ecvt -i cvt.asvt.ascb.assb psa + # Debug Mode run_with_expected_exit_code 0 ./dist/cbxp -d psa run_with_expected_exit_code 0 ./dist/cbxp --debug psa From 1ddbd2940f40cfd51034ccaa78b87bef1ee4d112 Mon Sep 17 00:00:00 2001 From: "Leonard J. Carcaramo Jr" <72973973+lcarcaramo@users.noreply.github.com> Date: Wed, 17 Dec 2025 12:57:11 -0500 Subject: [PATCH 09/37] CI/CD Infrastructure (#19) * Add Jenkinsfile, GitHub Actions workflows, and Ruff checks/formatting. Signed-off-by: Leonard Carcaramo * Debug clang-format workflow Signed-off-by: Leonard Carcaramo * Fix symbolic link. Signed-off-by: Leonard Carcaramo * Use clang-format-20 Signed-off-by: Leonard Carcaramo * Try adding clang-format-19 to PATH. Signed-off-by: Leonard Carcaramo * Add temporary format error. Signed-off-by: Leonard Carcaramo * Fix temporary format error. Signed-off-by: Leonard Carcaramo * Fix syntax error. Signed-off-by: Leonard Carcaramo * Fix syntax error. Signed-off-by: Leonard Carcaramo * Update version number. Signed-off-by: Leonard Carcaramo * Debug shell install test. Signed-off-by: Leonard Carcaramo * Debug publish stage. Signed-off-by: Leonard Carcaramo * Ruff and cmake test target fixes. Signed-off-by: Leonard Carcaramo * Show full directory structure in CLI/library install test. Signed-off-by: Leonard Carcaramo * Add ruff pre-commit hook. Signed-off-by: Leonard Carcaramo * Update contribution guidelines. Signed-off-by: Leonard Carcaramo * Update pre-commit Hooks section in the contribution guidelines. Signed-off-by: Leonard Carcaramo * Fix typo. Signed-off-by: Leonard Carcaramo * Cleanup. Signed-off-by: Leonard Carcaramo * Minor cleanup. Signed-off-by: Leonard Carcaramo --------- Signed-off-by: Leonard Carcaramo --- .github/workflows/clang-format.yml | 20 ++ .github/workflows/cppcheck.yml | 15 ++ .github/workflows/ruff.yml | 17 ++ .pre-commit-config.yaml | 4 + CMakeLists.txt | 2 +- CONTRIBUTING.md | 20 +- Jenkinsfile | 410 +++++++++++++++++++++++++++++ README.md | 5 +- pyproject.toml | 24 +- python/cbxp/_C.pyi | 6 +- python/cbxp/__init__.py | 2 +- python/cbxp/cbxp.py | 20 +- setup.py | 6 +- tests/test.py | 67 +++-- 14 files changed, 568 insertions(+), 50 deletions(-) create mode 100644 .github/workflows/clang-format.yml create mode 100644 .github/workflows/cppcheck.yml create mode 100644 .github/workflows/ruff.yml create mode 100644 Jenkinsfile diff --git a/.github/workflows/clang-format.yml b/.github/workflows/clang-format.yml new file mode 100644 index 0000000..dab77c7 --- /dev/null +++ b/.github/workflows/clang-format.yml @@ -0,0 +1,20 @@ +name: clang-format +on: [push, pull_request] +jobs: + build: + strategy: + fail-fast: false + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Install clang-format + run: sudo apt install -y cmake clang-format-19 + - name: clang-format + # Create symbolic link to clang-format-19 and + # add it to PATH so that 'clang-format' can + # be used to run 'clang-format-19'. + run: | + ln -s $(which clang-format-19) clang-format + export PATH=$PWD:$PATH + cmake . + make lint diff --git a/.github/workflows/cppcheck.yml b/.github/workflows/cppcheck.yml new file mode 100644 index 0000000..1f40e94 --- /dev/null +++ b/.github/workflows/cppcheck.yml @@ -0,0 +1,15 @@ +name: cppcheck +on: [push, pull_request] +jobs: + build: + strategy: + fail-fast: false + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Install cppcheck + run: sudo apt-get install -y cmake cppcheck + - name: Run cppcheck + run: | + cmake . + make check diff --git a/.github/workflows/ruff.yml b/.github/workflows/ruff.yml new file mode 100644 index 0000000..e4f7b77 --- /dev/null +++ b/.github/workflows/ruff.yml @@ -0,0 +1,17 @@ +name: ruff +on: [push, pull_request] +jobs: + build: + strategy: + fail-fast: false + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: "3.x" + - name: Install ruff + run: python3 -m pip install --upgrade pip ruff + - name: Run ruff + run: ruff check diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 57d5113..2fec720 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -14,3 +14,7 @@ repos: pass_filenames: false always_run: true verbose: true + - repo: https://github.com/astral-sh/ruff-pre-commit + rev: v0.14.9 + hooks: + - id: ruff-format diff --git a/CMakeLists.txt b/CMakeLists.txt index b44eb9f..3d92f7f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -160,5 +160,5 @@ add_custom_target( add_custom_target( test COMMAND "./tests/test.sh" - DEPENDS cbxp + DEPENDS libcbxp cbxp ) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 51762aa..f622852 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -23,7 +23,7 @@ The following are a set of guidelines to help you contribute. * [Python Interface Testing](#python-interface-testing) - * [Shell Interface Testing](#shell-interface-testing) + * [CLI Interface Testing](#cli-interface-testing) * [Style Guidelines](#style-guidelines) @@ -48,7 +48,7 @@ There are many ways to contribute to the project. You can write code, work on th If you want to write code, a good way to get started is by looking at the issues section of this repository. Look for the **Good First Issue** tag. Good First Issues are great as a first contribution. ### pre-commit Hooks -To ensure `clang-format` and `cppcheck` are always run against your code on **every commit**, set up the **pre-commit hooks**. +To ensure `clang-format`, `cppcheck`, and `ruff` are always run against your code on **every commit**, set up the **pre-commit hooks**. * Install [`pre-commit`](https://pre-commit.com/). * Setup **pre-commit Hooks**: @@ -97,7 +97,7 @@ CBXP is tested using automated functional tests. Test cases for new functionalit python3 ./tests/test.py ``` -#### Shell Interface Testing +#### CLI Interface Testing 1. Add new tests cases to [`tests/test.sh`](tests/test.sh). 2. Run the test suite using `cmake` and `gmake`. @@ -108,11 +108,11 @@ CBXP is tested using automated functional tests. Test cases for new functionalit ## Style Guidelines -:bulb: _`clang-format` can be setup to run automatically using the [pre-commit Hooks](#pre-commit-hooks)._ +:bulb: _`clang-format` and `ruff` can be setup to run automatically using the [pre-commit Hooks](#pre-commit-hooks)._ -The use of the `clang-format` code formatter is required. +All C/C++ code must be formatted using `clang-format`, and all Python code must be formatted using `ruff`. -The following code style conventions should be followed: +The following C/C++ code style conventions should be followed: * Varible names should use snake case *(i.e., `my_variable`)*. * Pointer variables should start with `p_` *(i.e., `p_my_pointer`)*. * Class variables should end with an `_` to help differentiate between class variables and local function variables *(i.e., `my_class_variable_`)*. @@ -145,10 +145,12 @@ When contributing to CBXP, make sure to complete all applicable tasks in the fol * Make any necessary updates to `pyproject.toml`. * Make any necessary updates to `README.md`. -* Ensure that you have **pre-commit Hooks** setup to ensure that `clang-format` and `cppcheck` are run against the code for every commit you make. +* Ensure that you have **pre-commit Hooks** setup to ensure that `clang-format`, `cppcheck`, and `ruff` are run against the code for every commit you make. +* Check for style violations in C/C++ code by running `gmake lint`. +* Check for style vilotations in Python code by running `ruff check`. +* Format C/C++ code by running `gmake format`. +* Format Python code by running `ruff format`. * Run `cppcheck` static code analysis by running `gmake check`. -* Check for style violations using `clang-format` by running `gmake lint`. -* Format the CBXP code using `clang-format` by running `gmake format`. ## Found a bug? diff --git a/Jenkinsfile b/Jenkinsfile new file mode 100644 index 0000000..ac9abd3 --- /dev/null +++ b/Jenkinsfile @@ -0,0 +1,410 @@ +def python_versions +def python_executables_and_wheels_map + +pipeline { + agent { + node { + label 'zOS_Ambitus' + } + } + + parameters { + // Note: Each Python version listed must be installed on the + // build agent and must be added to '$PATH' and '$LIBPATH'. + string ( + name: "pythonVersions", + defaultValue: "", + description: ( + "(Required Always) Comma separated list of Python versions to build " + + "wheels for (i.e., Use '12,13' for Python 3.12 and Python 3.13)." + ) + ) + booleanParam( + name: "createRelease", + defaultValue: false, + description: "Toggle whether or not to create a release from this revision." + ) + string( + name: "releaseTag", + defaultValue: "", + description: ( + "(Required When Creating Releases) This will be " + + "the git tag and version number of the release." + ) + ) + string( + name: "releaseTitle", + defaultValue: "", + description: ( + "(Required When Creating Releases) This will be " + + "the title of the release." + ) + ) + string( + name: "gitHubMilestoneLink", + defaultValue: "", + description: ( + "(Required When Creating Releases) This is the GitHub " + + "Milestone URL that coresponds to the release." + ) + ) + booleanParam( + name: "preRelease", + defaultValue: true, + description: "Toggle whether or not this is a pre-release." + ) + } + + options { + ansiColor('css') + } + + stages { + stage('Parameter Validation') { + steps { + script { + if (params.pythonVersions == "") { + error("'pythonVersions' is a required parameter.") + } + if (params.createRelease) { + if (params.releaseTag == "") { + error("'releaseTag' is a required parameter when creating a release.") + } + if (params.releaseTitle == "") { + error("'releaseTitle' is a required parameter when creating a release.") + } + if (params.gitHubMilestoneLink == "") { + error("'gitHubMilestoneLink' is a required parameter when creating a release.") + } + } + } + } + } + stage('Prepare') { + steps { + clean_python_environment() + sh "cmake ." + } + } + stage('Lint') { + steps { + echo "Linting with clang-format ..." + sh "gmake lint" + } + } + stage('Cppcheck') { + steps { + echo "Running cppcheck ..." + sh "gmake check" + } + } + stage('Create Python Distribution Metadata') { + steps { + script { + python_versions = params.pythonVersions.split(",") + python_executables_and_wheels_map = ( + create_python_executables_and_wheels_map(python_versions) + ) + } + } + } + stage('Install & Function Test') { + steps { + script { + // Python distribution + for (python in python_executables_and_wheels_map.keySet()) { + def wheel = python_executables_and_wheels_map[python]["wheelDefault"] + def tar = python_executables_and_wheels_map[python]["tarPublish"] + + echo "Building '${wheel}' and '${tar}' ..." + sh """ + ${python} -m pip install build>=1.3.0 + ${python} -m build + """ + + echo "Install testing '${wheel}' ..." + sh "${python} -m pip install ./dist/${wheel}" + + echo "Function testing '${wheel}' ..." + sh "${python} tests/test.py" + + clean_python_environment() + + echo "Install testing '${tar}' ..." + sh "${python} -m pip install ./dist/${tar}" + + echo "Function testing '${tar} ..." + sh "${python} tests/test.py" + + clean_python_environment() + clean_git_repo() + } + // CLI/Library distribution + def cbxp_version = get_cbxp_version() + def pax = "cbxp-${cbxp_version}.pax.Z" + echo "Building '${pax}' ..." + sh """ + cmake . + gmake package + """ + + echo "Install testing '${pax}' ..." + sh """ + mkdir install-test + cd install-test + pax -rf ../dist/${pax} + ls -alT cbxp-${cbxp_version}/* + """ + + echo "'Function testing './dist/cbxp' ..." + sh "./tests/test.sh" + + clean_git_repo() + } + } + } + stage('Publish') { + when { + expression { params.createRelease == true } + } + steps { + publish( + python_executables_and_wheels_map, + params.releaseTitle, + params.releaseTag, + env.BRANCH_NAME, + params.gitHubMilestoneLink, + params.preRelease + ) + } + } + } + post { + always { + echo "Cleaning up workspace ..." + cleanWs() + clean_python_environment() + } + } +} + +def create_python_executables_and_wheels_map(python_versions) { + def os = sh( + returnStdout: true, + script: "uname" + ).trim().replace("/", "").toLowerCase() + def zos_release = sh( + returnStdout: true, + script: "uname -r" + ).trim().replace(".", "_") + def processor = sh( + returnStdout: true, + script: "uname -m" + ).trim() + def cbxp_version = get_cbxp_version() + + python_executables_and_wheels_map = [:] + + for (python_version in python_versions) { + python_executables_and_wheels_map["python3.${python_version}"] = [ + "wheelDefault": ( + "cbxp-${cbxp_version}-cp3${python_version}-cp3${python_version}-${os}_${zos_release}_${processor}.whl" + ), + "wheelPublish": "cbxp-${cbxp_version}-cp3${python_version}-none-any.whl", + "tarPublish": "cbxp-${cbxp_version}.tar.gz" + ] + } + + return python_executables_and_wheels_map +} + +def get_cbxp_version() { + return sh( + returnStdout: true, + script: "cat pyproject.toml | grep version | cut -d'=' -f2 | cut -d'\"' -f2" + ).trim() +} + +def clean_python_environment() { + echo "Cleaning Python environment ..." + + sh """ + rm -rf ~/.cache + rm -rf ~/.local + """ +} + +def clean_git_repo() { + echo "Cleaning repo ..." + sh "git clean -fdx" +} + +def publish( + python_executables_and_wheels_map, + release_title, + release_tag, + git_branch, + milestone, + pre_release +) { + if (pre_release == true) { + pre_release = "true" + } + else { + pre_release = "false" + } + withCredentials( + [ + usernamePassword( + credentialsId: 'ambitus-github-access-token', + usernameVariable: 'github_user', + passwordVariable: 'github_access_token' + ) + ] + ) { + + // Creating GitHub releases: + // https://docs.github.com/en/rest/releases/releases?apiVersion=2022-11-28#create-a-release + // Uploading release assets: + // https://docs.github.com/en/rest/releases/assets?apiVersion=2022-11-28#upload-a-release-asset + + // Use single quotes for credentials since it is the most secure + // method for interpolating secrets according to the Jenkins docs: + // https://www.jenkins.io/doc/book/pipeline/jenkinsfile/#string-interpolation + + echo "Creating '${release_title}' GitHub release ..." + + def description = build_description(python_executables_and_wheels_map, release_tag, milestone) + + def release_id = sh( + returnStdout: true, + script: ( + 'curl -f -v -L ' + + '-X POST ' + + '-H "Accept: application/vnd.github+json" ' + + '-H "Authorization: Bearer ${github_access_token}" ' + + '-H "X-GitHub-Api-Version: 2022-11-28" ' + + "https://api.github.com/repos/ambitus/cbxp/releases " + + "-d '{" + + " \"tag_name\": \"${release_tag}\"," + + " \"target_commitish\": \"${git_branch}\"," + + " \"name\": \"${release_title}\"," + + " \"body\": \"${description}\"," + + " \"draft\": false," + + " \"prerelease\": ${pre_release}," + + " \"generate_release_notes\":false" + + "}' | grep '\"id\": ' | head -n1 | cut -d':' -f2 | cut -d',' -f1" + ) + ).trim() + + clean_git_repo() + + def checksums_file = "SHASUMS256.txt.asc" + sh """ + mkdir ./dist + touch ./dist/${checksums_file} + chtag -t -c ISO8859-1 ./dist/${checksums_file} + """ + + def tar_publish + def tar_built = false + + // Build and publish Python packages + for (python in python_executables_and_wheels_map.keySet()) { + def wheel_default = python_executables_and_wheels_map[python]["wheelDefault"] + def wheel_publish = python_executables_and_wheels_map[python]["wheelPublish"] + + echo "Building '${wheel_default}' ..." + + sh """ + ${python} -m pip install build>=1.2.2 + ${python} -m build -w + mv ./dist/${wheel_default} ./dist/${wheel_publish} + """ + + if (tar_built == false) { + tar_publish = python_executables_and_wheels_map[python]["tarPublish"] + echo "Building '${tar_publish}' ..." + sh "${python} -m build -s" + tar_built = true + } + + echo "Uploading '${wheel_default}' as '${wheel_publish}' to '${release_title}' GitHub release ..." + 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}" + } + + 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}" + + // Build and publish CLI/Library distribution + def cbxp_version = get_cbxp_version() + def pax = "cbxp-${cbxp_version}.pax.Z" + echo "Building '${pax}' ..." + sh """ + cmake . + gmake package + """ + + 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}" + + echo "Uploading '${checksums_file}' to '${release_title}' GitHub release ..." + upload_asset(release_id, checksums_file) + } +} + +def upload_asset(release_id, release_asset) { + sh( + 'curl -f -v -L ' + + '-X POST ' + + '-H "Accept: application/vnd.github+json" ' + + '-H "Authorization: Bearer ${github_access_token}" ' + + '-H "X-GitHub-Api-Version: 2022-11-28" ' + + '-H "Content-Type: application/octet-stream" ' + + "\"https://uploads.github.com/repos/ambitus/cbxp/releases/${release_id}/assets?name=${release_asset}\" " + + "--data-binary \"@dist/${release_asset}\"" + ) +} + +def build_description(python_executables_and_wheels_map, release_tag, milestone) { + def description = ( + "Release Notes: ${milestone}\\n" + + "## Python Interface Installation\\n" + ) + + for (python in python_executables_and_wheels_map.keySet()) { + def wheel = python_executables_and_wheels_map[python]["wheelPublish"] + def python_executable = python + def python_label = python.replace("python", "Python ") + description += ( + "Install From **${python_label} Wheel Distribution** *(pre-built)*:\\n" + + "```\\ncurl -O -L https://github.com/ambitus/cbxp/releases/download/${release_tag}/${wheel} " + + "&& ${python_executable} -m pip install ${wheel}\\n```\\n" + ) + } + + def python = python_executables_and_wheels_map.keySet()[-1] + def tar = python_executables_and_wheels_map[python]["tarPublish"] + def cbxp_version = get_cbxp_version() + def pax = "cbxp-${cbxp_version}.pax.Z" + description += ( + "Install From **Source Distribution** *(build on install)*:\\n" + + "> :warning: _Requires z/OS Open XL C/C++ 2.1 compiler._\\n" + + "```\\ncurl -O -L https://github.com/ambitus/cbxp/releases/download/${release_tag}/${tar} " + + "&& python3 -m pip install ${tar}\\n```\\n" + + "## CLI/Library Installation\\n" + + "```\\ncurl -O -L https://github.com/ambitus/cbxp/releases/download/${release_tag}/${pax} " + + "&& pax -rf ${pax}\\n```\\n" + ) + + return description +} diff --git a/README.md b/README.md index 4cd3abd..90a7799 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,6 @@ +[![clang-format](https://github.com/ambitus/cbxp/actions/workflows/clang-format.yml/badge.svg)](https://github.com/ambitus/cbxp/actions/workflows/clang-format.yml) +[![cppcheck](https://github.com/ambitus/cbxp/actions/workflows/cppcheck.yml/badge.svg)](https://github.com/ambitus/cbxp/actions/workflows/cppcheck.yml) +[![ruff](https://github.com/ambitus/cbxp/actions/workflows/ruff.yml/badge.svg)](https://github.com/ambitus/cbxp/actions/workflows/ruff.yml) [![Version](https://img.shields.io/pypi/v/cbxp?label=alpha)](https://pypi.org/project/cbxp/#history) [![Python Versions](https://img.shields.io/pypi/pyversions/cbxp)](https://pypi.org/project/cbxp/) [![Downloads](https://img.shields.io/pypi/dm/cbxp)](https://pypistats.org/packages/cbxp) @@ -30,7 +33,7 @@ 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) -* [Shell Interface](https://ambitus.github.io/cbxp/interfaces/shell) +* [CLI Interface](https://ambitus.github.io/cbxp/interfaces/shell) ### Supported Control Blocks diff --git a/pyproject.toml b/pyproject.toml index 8d34536..08210f7 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [build-system] -requires = ["setuptools >= 80.9.0"] -build-backend = "setuptools.build_meta" + requires = ["setuptools >= 80.9.0"] + build-backend = "setuptools.build_meta" [project] name="cbxp" @@ -50,3 +50,23 @@ build-backend = "setuptools.build_meta" [tool.setuptools.package-data] cbxp = ["LICENSE", "NOTICES"] + +[tool.ruff.lint] + # Ruff by default is not very aggressive, only enforcing a few rulesets like E and F. + # To ensure well organized code a bunch more rulesets have been enabled + select = [ + "ERA", # Checks for commented out code + "E", + "F", + "B", + "EM", # Error messages and exceptions + "SIM", # Comes with suggestions on simplifying things + "N", # Checks if code conforms to the PEP8 naming conventions + "PLE", + "UP", + "TRY", # Best practices for try-except + "FLY", # Checks for string joins + "I", # Comes with suggestions on organizing imports + "COM", # Checks for missing commas and unnecessary + "A", + ] diff --git a/python/cbxp/_C.pyi b/python/cbxp/_C.pyi index d82dd77..7ab645e 100644 --- a/python/cbxp/_C.pyi +++ b/python/cbxp/_C.pyi @@ -1 +1,5 @@ -def call_cbxp(control_block: str, includes_string: str, debug: bool = False) -> dict: ... +def call_cbxp( # noqa: N999 + control_block: str, + includes_string: str, + debug: bool = False, +) -> dict: ... diff --git a/python/cbxp/__init__.py b/python/cbxp/__init__.py index 99af136..448e3f8 100644 --- a/python/cbxp/__init__.py +++ b/python/cbxp/__init__.py @@ -1,2 +1,2 @@ -from .cbxp import cbxp as cbxp from .cbxp import CBXPError as CBXPError +from .cbxp import cbxp as cbxp diff --git a/python/cbxp/cbxp.py b/python/cbxp/cbxp.py index bd37b4b..49e35e7 100644 --- a/python/cbxp/cbxp.py +++ b/python/cbxp/cbxp.py @@ -3,14 +3,18 @@ from cbxp._C import call_cbxp + class CBXPErrorCode(Enum): """An enum of error and return codes from the cbxp interface""" + COMMA_IN_INCLUDE = -1 BAD_CONTROL_BLOCK = 1 BAD_INCLUDE = 2 + class CBXPError(Exception): """A class of errors for return codes from the cbxp interface""" + def __init__(self, return_code: int, control_block_name: str): self.rc = return_code match self.rc: @@ -24,16 +28,18 @@ def __init__(self, return_code: int, control_block_name: str): message = "an unknown error occurred" super().__init__(message) - + def cbxp( - control_block: str, - includes: list[str] = [], - debug: bool = False + control_block: str, + includes: list[str] = None, + debug: bool = False, ) -> dict: + if includes is None: + includes = [] for include in includes: if "," in include: raise CBXPError(CBXPErrorCode.COMMA_IN_INCLUDE.value, control_block) response = call_cbxp(control_block.lower(), ",".join(includes).lower(), debug=debug) - if response['return_code']: - raise CBXPError(response['return_code'], control_block) - return json.loads(response['result_json']) + if response["return_code"]: + raise CBXPError(response["return_code"], control_block) + return json.loads(response["result_json"]) diff --git a/setup.py b/setup.py index 341d263..d00bc23 100644 --- a/setup.py +++ b/setup.py @@ -27,7 +27,7 @@ def main(): + [ "cbxp", "externals", - "/usr/include/zos" + "/usr/include/zos", ] ), extra_link_args=[ @@ -37,8 +37,8 @@ def main(): extra_compile_args=[ "-O2", "-fzos-le-char-mode=ascii", - "-Wno-trigraphs" - ] + "-Wno-trigraphs", + ], ), ], "cmdclass": {"build_ext": build_ext}, diff --git a/tests/test.py b/tests/test.py index cf7f2a7..7d3351a 100644 --- a/tests/test.py +++ b/tests/test.py @@ -1,8 +1,9 @@ import unittest -from cbxp import cbxp, CBXPError -class TestCBXP(unittest.TestCase): +from cbxp import CBXPError, cbxp + +class TestCBXP(unittest.TestCase): # ============================================================================ # Basic Usage # ============================================================================ @@ -27,12 +28,13 @@ def test_cbxp_can_extract_ascb(self): self.assertIs(type(cbdata), list) for entry in cbdata: self.assertIs(type(entry), dict) - + def test_cbxp_can_extract_assb(self): cbdata = cbxp("assb") self.assertIs(type(cbdata), list) for entry in cbdata: self.assertIs(type(entry), dict) + # ============================================================================ # Include Patterns # ============================================================================ @@ -60,7 +62,7 @@ def test_cbxp_can_extract_the_asvt_and_include_the_ascb(self): 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"]) self.assertIs(type(cbdata), list) @@ -82,7 +84,7 @@ def test_cbxp_can_extract_the_psa_and_include_the_cvt_ecvt_ascb(self): self.assertIs(type(cbdata["flccvt"]["cvtasvt"]["asvtenty"]), list) for entry in cbdata["flccvt"]["cvtasvt"]["asvtenty"]: self.assertIs(type(entry), dict) - + def test_cbxp_can_extract_the_cvt_and_include_the_asvt_ascb(self): cbdata = cbxp("cvt", includes=["asvt.ascb"]) self.assertIs(type(cbdata), dict) @@ -91,7 +93,7 @@ def test_cbxp_can_extract_the_cvt_and_include_the_asvt_ascb(self): for entry in cbdata["cvtasvt"]["asvtenty"]: self.assertIs(type(entry), dict) - def test_cbxp_include_can_extract_cvt_and_include_pattern_ecvt_and_asvt(self): + def test_cbxp_include_can_extract_cvt_and_include_ecvt_and_asvt(self): cbdata = cbxp("cvt", includes=["ecvt", "asvt"]) self.assertIs(type(cbdata), dict) self.assertIs(type(cbdata["cvtecvt"]), dict) @@ -100,7 +102,9 @@ def test_cbxp_include_can_extract_cvt_and_include_pattern_ecvt_and_asvt(self): for entry in cbdata["cvtasvt"]["asvtenty"]: self.assertIs(type(entry), str) - def test_cbxp_include_can_extract_psa_and_include_pattern_ecvt_asvt_and_cvt_asvt_ascb(self): + def test_cbxp_include_can_extract_psa_and_include_ecvt_asvt_and_cvt_asvt_ascb( + self, + ): cbdata = cbxp("psa", includes=["cvt.ecvt", "cvt.asvt.ascb"]) self.assertIs(type(cbdata), dict) self.assertIs(type(cbdata["flccvt"]), dict) @@ -109,8 +113,10 @@ def test_cbxp_include_can_extract_psa_and_include_pattern_ecvt_asvt_and_cvt_asvt self.assertIs(type(cbdata["flccvt"]["cvtasvt"]["asvtenty"]), list) for entry in cbdata["flccvt"]["cvtasvt"]["asvtenty"]: self.assertIs(type(entry), dict) - - def test_cbxp_include_can_extract_psa_and_include_pattern_ecvt_asvt_and_cvt_asvt_ascb_assb(self): + + def test_cbxp_include_can_extract_psa_and_include_ecvt_asvt_and_cvt_asvt_ascb_assb( + self, + ): cbdata = cbxp("psa", includes=["cvt.ecvt", "cvt.asvt.ascb.assb"]) self.assertIs(type(cbdata), dict) self.assertIs(type(cbdata["flccvt"]), dict) @@ -141,7 +147,7 @@ def test_cbxp_can_extract_psa_and_include_cvt_wildcard(self): self.assertIs(type(cbdata["flccvt"]["cvtasvt"]["asvtenty"]), list) for entry in cbdata["flccvt"]["cvtasvt"]["asvtenty"]: self.assertIs(type(entry), str) - + def test_cbxp_can_extract_cvt_and_include_wildcard_and_asvt_wildcard(self): cbdata = cbxp("cvt", includes=["*", "asvt.*"]) self.assertIs(type(cbdata), dict) @@ -150,8 +156,10 @@ def test_cbxp_can_extract_cvt_and_include_wildcard_and_asvt_wildcard(self): self.assertIs(type(cbdata["cvtasvt"]["asvtenty"]), list) for entry in cbdata["cvtasvt"]["asvtenty"]: self.assertIs(type(entry), dict) - - def test_cbxp_can_extract_cvt_and_include_wildcard_and_asvt_recursive_wildcard(self): + + def test_cbxp_can_extract_cvt_and_include_wildcard_and_asvt_recursive_wildcard( + self, + ): cbdata = cbxp("cvt", includes=["*", "asvt.**"]) self.assertIs(type(cbdata), dict) self.assertIs(type(cbdata["cvtecvt"]), dict) @@ -174,49 +182,58 @@ def test_cbxp_can_run_in_debug_mode(self): def test_cbxp_raises_cbxp_error_when_unknown_control_block_is_provided(self): with self.assertRaises(CBXPError) as e: cbxp("unknown") - self.assertEqual("Unknown control block 'unknown' was specified.", str(e.exception)) + self.assertEqual( + "Unknown control block 'unknown' was specified.", + str(e.exception), + ) # ============================================================================ # Errors: Bad Include Patterns # ============================================================================ - def test_cbxp_raises_cbxp_error_if_asvt_ascb_is_included_when_extracting_the_psa(self): + def test_cbxp_raises_cbxp_error_if_asvt_ascb_is_included_with_the_psa( + self, + ): with self.assertRaises(CBXPError) as e: cbxp("psa", includes=["asvt.ascb"]) self.assertEqual("A bad include pattern was provided", str(e.exception)) - def test_cbxp_raises_cbxp_error_if_ascb_is_included_when_extracting_the_psa(self): + def test_cbxp_raises_cbxp_error_if_ascb_is_included_with_the_psa(self): with self.assertRaises(CBXPError) as e: cbxp("psa", includes=["ascb"]) self.assertEqual("A bad include pattern was provided", str(e.exception)) - def test_cbxp_raises_cbxp_error_if_ecvt_is_included_when_extracting_the_ascb(self): + def test_cbxp_raises_cbxp_error_if_ecvt_is_included_with_ascb(self): with self.assertRaises(CBXPError) as e: cbxp("ascb", includes=["ecvt"]) self.assertEqual("A bad include pattern was provided", str(e.exception)) - - def test_cbxp_raises_cbxp_error_if_ect_ecvt_and_cvt_ascb_is_included_when_extracting_the_psa(self): + + def test_cbxp_raises_cbxp_error_if_ect_ecvt_and_cvt_ascb_is_included_with_the_psa( + self, + ): with self.assertRaises(CBXPError) as e: - cbdata = cbxp("psa", includes=["cvt.ecvt", "cvt.ascb"]) + cbxp("psa", includes=["cvt.ecvt", "cvt.ascb"]) self.assertEqual("A bad include pattern was provided", str(e.exception)) - def test_cbxp_raises_cbxp_error_if_ecvt_and_cvt_asvt_ascb_is_included_when_extracting_the_psa(self): + def test_cbxp_raises_cbxp_error_if_ecvt_and_cvt_asvt_ascb_is_included_with_the_psa( + self, + ): with self.assertRaises(CBXPError) as e: - cbdata = cbxp("psa", includes=["ecvt", "cvt.asvt.ascb"]) + cbxp("psa", includes=["ecvt", "cvt.asvt.ascb"]) self.assertEqual("A bad include pattern was provided", str(e.exception)) - def test_cbxp_raises_cbxp_error_if_cvt_is_included_when_extracting_the_cvt(self): + def test_cbxp_raises_cbxp_error_if_cvt_is_included_with_the_cvt(self): with self.assertRaises(CBXPError) as e: - cbdata = cbxp("cvt", includes=["cvt"]) + cbxp("cvt", includes=["cvt"]) self.assertEqual("A bad include pattern was provided", str(e.exception)) def test_cbxp_raises_cbxp_error_when_pattern_cannot_contain_comma_1(self): with self.assertRaises(CBXPError) as e: - cbdata = cbxp("cvt", includes=["asvt,ascb"]) + cbxp("cvt", includes=["asvt,ascb"]) self.assertEqual("Include patterns cannot contain commas", str(e.exception)) def test_cbxp_raises_cbxp_error_when_pattern_cannot_contain_comma_2(self): with self.assertRaises(CBXPError) as e: - cbdata = cbxp("cvt", includes=["asvt,as"]) + cbxp("cvt", includes=["asvt,as"]) self.assertEqual("Include patterns cannot contain commas", str(e.exception)) From 4eb4ca948ec1876fa1bd9b91a824926da31fd971 Mon Sep 17 00:00:00 2001 From: Elijah Swift <86801927+ElijahSwiftIBM@users.noreply.github.com> Date: Tue, 17 Feb 2026 13:19:51 -0500 Subject: [PATCH 10/37] Feat/filter control blocks (#20) * Initial Alpha (#19) * Feature/includeparameter (#14) * Add skeleton for inclusion list parameter Extend interface/skeleton for include_list * Input POinter Attempt to add input pointer parameter for control blocks * Definitions outside of if statements * Attempt to map the intended inclusion list * Try Virtual Function to keep Control Block in Explorer * No longer have get return * Attempt to add the meat of the function -Add methods and members to control blocks to get name of control block, specific control blocks within it, and all control blocks within it -add functions in control block explorer to parse inclusion "map" into actionable data name fix * Update psa.cpp Update psa.cpp * Update cvt.cpp * Update asvt.cpp * algorithm cleanup * better cleanup * Update control_block.cpp * Update main.cpp * Finish addressing merge issues and commit hooks -Minor code updates that were lost in merge commit -Bring new code up to standard for cppcheck -Format new code with clang-format * Update _cbxp.c * Streamline Control Block Explorer Class Update control_block_explorer.hpp * Massive refactor -Shave 2 step process down to one -Change serialized json inclusion map to use a vector of strings still split by "dot" operators * Error Handling Logic * BIG UPDATE PR COMMENTS -Switch pre-processing to one hash map function -use try/catch with custom errors rather than passing return codes everywhere -style and name changes -Enforce more rigid parm structure on entry -Fix some behavioral bugs and oversights in inclusion preprocessing -General streamlining and refactoring of functions, methods, classes, etc. * Another Big Refactor -PR comments (mostly style, but streamlining of error code as well) -Reworked base and derived classes to allow for includables to be defined to the base class and include_map to be defined to the base and derived classes * Update ascb.cpp * Update control_block.hpp * . * .. * ... * PR Comments -ASCB pointer deref in ASVT -Minor name changes -Remove double wildcard error -Add control_block_name_ private member and add initialization to constructor -move include_map_ to protected and remove private using statement * Update asvt.cpp * Update asvt.cpp * PR Comments Mostly renaming things streamlining some unnecessary text, parms and strings * Update control_block.cpp * Update main.cpp * Last round of PR comments string compare with == remove vestiges of old mechanisms for control block management name changes minor tweaks * Update cvt.cpp * Update cvt.cpp * Update cvt.cpp * Final comments Update control_block_explorer.cpp * comments * Last Comments * include changes * Last round of comments * debug * Unit testing (#17) * initial commit 1 * cleaned code before include test cases * wrote test cases, need to check with team now * added space after every function * added .value * shell script done * made changes proposed by leonard 1 * PR changes requested by team * added tests to check for ascb and asvt entries whether it be a string or dict * added tests to check for ascb and asvt entries whether it be a string or dict one more place * made minor tweaks * added updates provided by leonard * grouped failure test cases together * grouped error test cases together * removed extra lines * style changes * Feat/oss housekeeping2 (#18) * Set explicit C/C++ standard and cleanup README. Signed-off-by: Leonard Carcaramo * Update contribution guidelines and functional tests. Signed-off-by: Leonard Carcaramo * Cleanup contribution guidelines and debug debug mode. Signed-off-by: Leonard Carcaramo * Cleanup. Signed-off-by: Leonard Carcaramo * Cleanup. Signed-off-by: Leonard Carcaramo * Fix sdist packaging and pyproject.toml metadata. Signed-off-by: Leonard Carcaramo --------- Signed-off-by: Leonard Carcaramo * Fix _C.pyi and removed unused import from cbxp.py. Signed-off-by: Leonard Carcaramo --------- Signed-off-by: Leonard Carcaramo Co-authored-by: Elijah Swift Co-authored-by: Varun Chennamadhava * Squashed Commit Signed-off-by: Elijah Swift * change to ensure filter check fails empty lists Signed-off-by: Elijah Swift * More robust unit testing Signed-off-by: Elijah Swift * trailing commas in python Signed-off-by: Elijah Swift * PR Comments Signed-off-by: Elijah Swift * Complete PR Comments Signed-off-by: Elijah Swift * Fix an error in tests Signed-off-by: Elijah Swift * Fix another error in tests Signed-off-by: Elijah Swift * Streamline control block parameters Signed-off-by: Elijah Swift * Streamline control block parameters part 2 Signed-off-by: Elijah Swift * Fix python tests Signed-off-by: Elijah Swift * Fix python tests to ruff standards Signed-off-by: Elijah Swift * round 3 PR comments Signed-off-by: Elijah Swift * Explicit enum declarations Signed-off-by: Elijah Swift * pre commit comments round 4 Signed-off-by: Elijah Swift * pre commit comments round 4.1 Signed-off-by: Elijah Swift * pre commit comments round 4.2 Signed-off-by: Elijah Swift * pre commit comments round 4.3 Signed-off-by: Elijah Swift * pre commit comments round 4.4 Signed-off-by: Elijah Swift * pre commit comments round 4.5 Signed-off-by: Elijah Swift * pre commit comments round 4.6 Signed-off-by: Elijah Swift * pre commit comments round 4.7 Signed-off-by: Elijah Swift * If statement format fix Signed-off-by: Elijah Swift * upgrading debug logging strings Signed-off-by: Elijah Swift * updated operation logging Signed-off-by: Elijah Swift * Include debug statements Signed-off-by: Elijah Swift * Fix string filter for int value bug Signed-off-by: Elijah Swift * thread safe mechanism changes Signed-off-by: Elijah Swift * thread safe mechanism changes 1.1 Signed-off-by: Elijah Swift * thread safe mechanism changes 1.2 Signed-off-by: Elijah Swift * thread safe mechanism changes 1.3 Signed-off-by: Elijah Swift * thread safe mechanism changes 1.4 Signed-off-by: Elijah Swift * thread safe mechanism changes 1.5 Signed-off-by: Elijah Swift * thread safe mechanism changes 1.6 Signed-off-by: Elijah Swift * thread safe mechanism changes 1.7 Signed-off-by: Elijah Swift * thread safe mechanism changes 2.0 Signed-off-by: Elijah Swift * thread safe mechanism changes 2.1 Signed-off-by: Elijah Swift * thread safe mechanism changes 2.2 Signed-off-by: Elijah Swift * thread safe mechanism changes 2.2 Signed-off-by: Elijah Swift * thread safe mechanism changes 2.3 Signed-off-by: Elijah Swift * thread safe mechanism changes 2.4 Signed-off-by: Elijah Swift * thread safe mechanism changes 2.5 Signed-off-by: Elijah Swift * thread safe mechanism changes 2.6 Signed-off-by: Elijah Swift * thread safe mechanism changes 2.7 Signed-off-by: Elijah Swift * thread safe mechanism changes 2.8 Signed-off-by: Elijah Swift * thread safe mechanism changes 2.9 Signed-off-by: Elijah Swift * thread safe mechanism changes 3.0 Signed-off-by: Elijah Swift * thread safe mechanism changes 3.1 Signed-off-by: Elijah Swift * thread safe mechanism changes 3.2 Signed-off-by: Elijah Swift --------- Signed-off-by: Leonard Carcaramo Signed-off-by: Elijah Swift Co-authored-by: Leonard Carcaramo Co-authored-by: Varun Chennamadhava --- cbxp/cbxp.cpp | 29 +- cbxp/cbxp.h | 7 +- cbxp/control_block_error.hpp | 7 +- cbxp/control_block_explorer.cpp | 73 ++--- cbxp/control_block_explorer.hpp | 10 +- cbxp/control_blocks/ascb.cpp | 20 +- cbxp/control_blocks/ascb.hpp | 4 +- cbxp/control_blocks/assb.cpp | 13 +- cbxp/control_blocks/assb.hpp | 4 +- cbxp/control_blocks/asvt.cpp | 17 +- cbxp/control_blocks/asvt.hpp | 4 +- cbxp/control_blocks/control_block.cpp | 230 +++++++++++-- cbxp/control_blocks/control_block.hpp | 26 +- .../control_block_field_formatter.hpp | 11 +- cbxp/control_blocks/cvt.cpp | 18 +- cbxp/control_blocks/cvt.hpp | 4 +- cbxp/control_blocks/ecvt.cpp | 6 +- cbxp/control_blocks/ecvt.hpp | 4 +- cbxp/control_blocks/psa.cpp | 13 +- cbxp/control_blocks/psa.hpp | 4 +- cbxp/main.cpp | 92 ++++-- cbxp/python/_cbxp.c | 34 +- python/cbxp/_C.pyi | 1 + python/cbxp/__init__.py | 2 + python/cbxp/cbxp.py | 56 +++- tests/test.py | 305 +++++++++++++++++- tests/test.sh | 43 +++ 27 files changed, 875 insertions(+), 162 deletions(-) diff --git a/cbxp/cbxp.cpp b/cbxp/cbxp.cpp index 5e8618f..32a0a4b 100644 --- a/cbxp/cbxp.cpp +++ b/cbxp/cbxp.cpp @@ -6,18 +6,33 @@ #include "cbxp_result.h" #include "control_block_explorer.hpp" +#include "logger.hpp" -const cbxp_result_t* cbxp(const char* control_block_name, - const char* includes_string, bool debug) { +cbxp_result_t* cbxp(const char* control_block, const char* includes_string, + const char* filters_string, bool debug) { nlohmann::json control_block_json; - std::string control_block = control_block_name; + std::string control_block_name = control_block; - static cbxp_result_t cbxp_result = {nullptr, 0, -1}; + 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(&cbxp_result, debug); + CBXP::ControlBlockExplorer(p_cbxp_result); + + explorer.exploreControlBlock(control_block_name, includes_string, + filters_string); + + return p_cbxp_result; +} - explorer.exploreControlBlock(control_block, includes_string); +void cbxp_free(cbxp_result_t* cbxp_result, bool debug) { + CBXP::Logger::getInstance().setDebug(debug); - return &cbxp_result; + CBXP::Logger::getInstance().debugFree(cbxp_result->result_json); + delete cbxp_result->result_json; + CBXP::Logger::getInstance().debugFree(cbxp_result); + delete cbxp_result; } diff --git a/cbxp/cbxp.h b/cbxp/cbxp.h index 4e50bf7..3c6ca55 100644 --- a/cbxp/cbxp.h +++ b/cbxp/cbxp.h @@ -7,13 +7,16 @@ extern "C" { #endif -const cbxp_result_t* cbxp(const char* control_block_name, - const char* includes_string, bool debug); +cbxp_result_t* cbxp(const char* control_block, const char* includes_string, + const char* filters_string, bool debug); + +void cbxp_free(cbxp_result_t* cbxp_result, bool debug); #ifdef __cplusplus } #endif #pragma export(cbxp) +#pragma export(cbxp_free) #endif diff --git a/cbxp/control_block_error.hpp b/cbxp/control_block_error.hpp index 65902a0..bbdeee4 100644 --- a/cbxp/control_block_error.hpp +++ b/cbxp/control_block_error.hpp @@ -2,7 +2,7 @@ #define __CONTROL_BLOCK_ERROR_H_ namespace CBXP { -enum Error { BadControlBlock = 1, BadInclude }; +enum Error { BadControlBlock = 1, BadInclude = 2, BadFilter = 3 }; class CBXPError : public std::exception { private: Error error_code_; @@ -22,6 +22,11 @@ class IncludeError : public CBXPError { IncludeError() : CBXPError(Error::BadInclude) {} }; +class FilterError : public CBXPError { + public: + FilterError() : CBXPError(Error::BadFilter) {} +}; + } // namespace CBXP #endif diff --git a/cbxp/control_block_explorer.cpp b/cbxp/control_block_explorer.cpp index f1cfd65..b8af653 100644 --- a/cbxp/control_block_explorer.cpp +++ b/cbxp/control_block_explorer.cpp @@ -17,57 +17,43 @@ namespace CBXP { -std::vector ControlBlockExplorer::createIncludeList( - const std::string& includes_string) { - if (includes_string == "") { +std::vector ControlBlockExplorer::createOptionsList( + const std::string& comma_separated_string) { + if (comma_separated_string == "") { return {}; } - std::vector includes = {}; + std::vector options_list = {}; Logger::getInstance().debug( - "Creating include list from the provided include list string: " + - includes_string); + "Creating options list from the provided comma-separated list string: " + + comma_separated_string); const std::string del = ","; std::string entry; size_t index = 0; - auto pos = includes_string.find(del); + auto pos = comma_separated_string.find(del); while (pos != std::string::npos) { - entry = includes_string.substr(index, pos); - includes.push_back(entry); + entry = comma_separated_string.substr(index, pos); + options_list.push_back(entry); index += pos + 1; - pos = includes_string.substr(index, std::string::npos).find(del); + pos = comma_separated_string.substr(index, std::string::npos).find(del); } - entry = includes_string.substr(index, pos); - includes.push_back(entry); - Logger::getInstance().debug("Done."); + entry = comma_separated_string.substr(index, pos); + options_list.push_back(entry); + Logger::getInstance().debug("Options list created"); - return includes; -} - -ControlBlockExplorer::ControlBlockExplorer(cbxp_result_t* p_result, - bool debug) { - Logger::getInstance().setDebug(debug); - - if (p_result->result_json != nullptr) { - Logger::getInstance().debugFree(p_result->result_json); - delete[] p_result->result_json; - } - - p_result->result_json_length = 0; - p_result->result_json = nullptr; - p_result->return_code = 0; - - p_result_ = p_result; + return options_list; } void ControlBlockExplorer::exploreControlBlock( - const std::string& control_block_name, const std::string& includes_string) { - std::vector includes = - ControlBlockExplorer::createIncludeList(includes_string); + 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)}; Logger::getInstance().debug("Extracting '" + control_block_name + "' control block data..."); @@ -75,17 +61,17 @@ void ControlBlockExplorer::exploreControlBlock( nlohmann::json control_block_json; try { if (control_block_name == "psa") { - control_block_json = PSA(includes).get(); + control_block_json = PSA(cbxp_options).get(); } else if (control_block_name == "cvt") { - control_block_json = CVT(includes).get(); + control_block_json = CVT(cbxp_options).get(); } else if (control_block_name == "ecvt") { - control_block_json = ECVT(includes).get(); + control_block_json = ECVT(cbxp_options).get(); } else if (control_block_name == "ascb") { - control_block_json = ASCB(includes).get(); + control_block_json = ASCB(cbxp_options).get(); } else if (control_block_name == "asvt") { - control_block_json = ASVT(includes).get(); + control_block_json = ASVT(cbxp_options).get(); } else if (control_block_name == "assb") { - control_block_json = ASSB(includes).get(); + control_block_json = ASSB(cbxp_options).get(); } else { throw ControlBlockError(); } @@ -96,17 +82,16 @@ void ControlBlockExplorer::exploreControlBlock( std::string control_block_json_string = control_block_json.dump(); - Logger::getInstance().debug("Done."); + Logger::getInstance().debug("'" + control_block_name + + "' control block data extracted"); Logger::getInstance().debug("Control Block JSON: " + control_block_json_string); p_result_->result_json_length = control_block_json_string.length(); - p_result_->result_json = new char[p_result_->result_json_length]; - p_result_->result_json[p_result_->result_json_length] = 0; - + p_result_->result_json = new char[p_result_->result_json_length + 1]{}; Logger::getInstance().debugAllocate(p_result_->result_json, 64, - p_result_->result_json_length); + p_result_->result_json_length + 1); std::strncpy(p_result_->result_json, control_block_json_string.c_str(), p_result_->result_json_length); diff --git a/cbxp/control_block_explorer.hpp b/cbxp/control_block_explorer.hpp index 7542b17..01ae003 100644 --- a/cbxp/control_block_explorer.hpp +++ b/cbxp/control_block_explorer.hpp @@ -9,13 +9,15 @@ namespace CBXP { class ControlBlockExplorer { private: cbxp_result_t* p_result_; - static std::vector createIncludeList( - const std::string& includes_string); + static std::vector createOptionsList( + const std::string& comma_separated_string); public: - ControlBlockExplorer(cbxp_result_t* p_result, bool debug); + explicit ControlBlockExplorer(cbxp_result_t* p_result) + : p_result_(p_result) {}; void exploreControlBlock(const std::string& control_block_name, - const std::string& includes_string); + const std::string& includes_string, + const std::string& filters_string); }; } // namespace CBXP diff --git a/cbxp/control_blocks/ascb.cpp b/cbxp/control_blocks/ascb.cpp index d2f5fa9..93f7c60 100644 --- a/cbxp/control_blocks/ascb.cpp +++ b/cbxp/control_blocks/ascb.cpp @@ -42,7 +42,11 @@ nlohmann::json ASCB::get(void* __ptr32 p_control_block) { p_ascb_addr++; continue; } - ascbs.push_back(ASCB::get(reinterpret_cast(*p_ascb_addr))); + nlohmann::json next_ascb = + ASCB::get(reinterpret_cast(*p_ascb_addr)); + if (!next_ascb.is_null()) { + ascbs.push_back(next_ascb); + } p_ascb_addr++; // This SHOULD increment the pointer by 4 bytes. } return ascbs; @@ -53,10 +57,12 @@ nlohmann::json ASCB::get(void* __ptr32 p_control_block) { ascb_json["ascbassb"] = formatter_.getHex(&(p_ascb->ascbassb)); ascb_json["ascbasxb"] = formatter_.getHex(&(p_ascb->ascbasxb)); - for (const auto& [include, include_includes] : include_map_) { + for (const auto& [include, cbxp_options] : options_map_) { if (include == "assb") { - ascb_json["ascbassb"] = - CBXP::ASSB(include_includes).get(p_ascb->ascbassb); + ascb_json["ascbassb"] = CBXP::ASSB(cbxp_options).get(p_ascb->ascbassb); + if (ascb_json["ascbassb"].is_null()) { + return {}; + } } } @@ -90,6 +96,10 @@ nlohmann::json ASCB::get(void* __ptr32 p_control_block) { ascb_json["ascbxtcb"] = formatter_.getHex(&(p_ascb->ascbxtcb)); ascb_json["ascbzcx"] = formatter_.getBitmap(p_ascb->ascbzcx); - return ascb_json; + if (ASCB::matchFilter(ascb_json)) { + return ascb_json; + } else { + return {}; + } } } // namespace CBXP diff --git a/cbxp/control_blocks/ascb.hpp b/cbxp/control_blocks/ascb.hpp index e811530..c658db9 100644 --- a/cbxp/control_blocks/ascb.hpp +++ b/cbxp/control_blocks/ascb.hpp @@ -8,8 +8,8 @@ namespace CBXP { class ASCB : public ControlBlock { public: nlohmann::json get(void* __ptr32 p_control_block = nullptr) override; - explicit ASCB(const std::vector& includes) - : ControlBlock("ascb", {"assb"}, includes) {} + explicit ASCB(const cbxp_options_t& cbxp_options) + : ControlBlock("ascb", {"assb"}, cbxp_options) {} }; } // namespace CBXP diff --git a/cbxp/control_blocks/assb.cpp b/cbxp/control_blocks/assb.cpp index 3a12daf..c41eb3a 100644 --- a/cbxp/control_blocks/assb.cpp +++ b/cbxp/control_blocks/assb.cpp @@ -46,8 +46,11 @@ nlohmann::json ASSB::get(void* __ptr32 p_control_block) { // const struct ascb* __ptr32 p_ascb = reinterpret_cast(*p_ascb_addr); - assbs.push_back( - ASSB::get(reinterpret_cast(p_ascb->ascbassb))); + nlohmann::json next_assb = + ASSB::get(reinterpret_cast(p_ascb->ascbassb)); + if (!next_assb.is_null()) { + assbs.push_back(next_assb); + } p_ascb_addr++; // This SHOULD increment the pointer by 4 bytes. } return assbs; @@ -195,6 +198,10 @@ nlohmann::json ASSB::get(void* __ptr32 p_control_block) { formatter_.uint(p_assb->assbinitiatorjobid); assb_json["assbend"] = formatter_.uint(p_assb->assbend); - return assb_json; + if (ASSB::matchFilter(assb_json)) { + return assb_json; + } else { + return {}; + } } } // namespace CBXP diff --git a/cbxp/control_blocks/assb.hpp b/cbxp/control_blocks/assb.hpp index 6692b2f..5046d8f 100644 --- a/cbxp/control_blocks/assb.hpp +++ b/cbxp/control_blocks/assb.hpp @@ -8,8 +8,8 @@ namespace CBXP { class ASSB : public ControlBlock { public: nlohmann::json get(void* __ptr32 p_control_block = nullptr) override; - explicit ASSB(const std::vector& includes) - : ControlBlock("assb", {}, includes) {} + explicit ASSB(const cbxp_options_t& cbxp_options) + : ControlBlock("assb", {}, cbxp_options) {} }; } // namespace CBXP diff --git a/cbxp/control_blocks/asvt.cpp b/cbxp/control_blocks/asvt.cpp index 17ad32f..8d77051 100644 --- a/cbxp/control_blocks/asvt.cpp +++ b/cbxp/control_blocks/asvt.cpp @@ -47,10 +47,10 @@ nlohmann::json ASVT::get(void* __ptr32 p_control_block) { Logger::getInstance().debug("ASVT hex dump:"); Logger::getInstance().hexDump(reinterpret_cast(p_asvt), sizeof(asvt_t)); - for (const auto& [include, include_includes] : include_map_) { + for (const auto& [include, cbxp_options] : options_map_) { if (include == "ascb") { nlohmann::json ascbs_json; - CBXP::ASCB ascb(include_includes); + CBXP::ASCB ascb(cbxp_options); uint32_t* __ptr32 p_ascb_addr = const_cast( reinterpret_cast(&p_asvt->asvtenty)); for (int i = 0; i < p_asvt->asvtmaxu; i++) { @@ -62,10 +62,15 @@ nlohmann::json ASVT::get(void* __ptr32 p_control_block) { } nlohmann::json ascb_json = ascb.get(reinterpret_cast(*p_ascb_addr)); - ascbs_json.push_back(ascb_json); + if (!ascb_json.is_null()) { + ascbs_json.push_back(ascb_json); + } p_ascb_addr++; // This SHOULD increment the pointer by 4 bytes. } asvt_json["asvtenty"] = ascbs_json; + if (asvt_json["asvtenty"].is_null()) { + return {}; + } } } @@ -85,6 +90,10 @@ nlohmann::json ASVT::get(void* __ptr32 p_control_block) { asvt_json["asvtmdsc"] = p_asvt->asvtmdsc; asvt_json["asvtfrst"] = formatter_.getHex(p_asvt->asvtfrst); - return asvt_json; + if (ASVT::matchFilter(asvt_json)) { + return asvt_json; + } else { + return {}; + } } } // namespace CBXP diff --git a/cbxp/control_blocks/asvt.hpp b/cbxp/control_blocks/asvt.hpp index a6e91c9..15268bb 100644 --- a/cbxp/control_blocks/asvt.hpp +++ b/cbxp/control_blocks/asvt.hpp @@ -33,8 +33,8 @@ namespace CBXP { class ASVT : public ControlBlock { public: nlohmann::json get(void* __ptr32 p_control_block = nullptr) override; - explicit ASVT(const std::vector& includes) - : ControlBlock("asvt", {"ascb"}, includes) {} + explicit ASVT(const cbxp_options_t& cbxp_options) + : ControlBlock("asvt", {"ascb"}, cbxp_options) {} }; } // namespace CBXP diff --git a/cbxp/control_blocks/control_block.cpp b/cbxp/control_blocks/control_block.cpp index 02616ab..43c397d 100644 --- a/cbxp/control_blocks/control_block.cpp +++ b/cbxp/control_blocks/control_block.cpp @@ -1,49 +1,75 @@ #include "control_block.hpp" +#include + #include #include "control_block_error.hpp" #include "logger.hpp" namespace CBXP { -void ControlBlock::createIncludeMap(const std::vector& includes) { - Logger::getInstance().debug("Creating include map for the '" + - control_block_name_ + "' control block..."); +void ControlBlock::createOptionsMap(const std::vector& includes, + const std::vector& filters) { + // createFilterLists depends on the construction of the "includes" portion + // of the options_map_ structure and must be called after createIncludeLists + ControlBlock::createIncludeLists(includes); + ControlBlock::createFilterLists(filters); +} + +void ControlBlock::createIncludeLists( + const std::vector& includes) { + Logger::getInstance().debug( + "Creating include lists for child control blocks to include with the '" + + control_block_name_ + "' control block..."); for (std::string include : includes) { if (include == "**") { + Logger::getInstance().debug("Processing '**' include..."); ControlBlock::processDoubleAsteriskInclude(); return; } else if (include == "*") { + Logger::getInstance().debug("Processing '*' include..."); ControlBlock::processAsteriskInclude(); } else { + Logger::getInstance().debug("Processing '" + include + "' include..."); ControlBlock::processExplicitInclude(include); } } - Logger::getInstance().debug("Done"); + Logger::getInstance().debug( + "Include lists for child control blocks to include with the '" + + control_block_name_ + "' control block have been created"); } void ControlBlock::processDoubleAsteriskInclude() { // Any existing entries in the hash map are redundant, so clear them - include_map_.clear(); + options_map_.clear(); for (const std::string& includable : includables_) { // Build a map of all includables_ but with "**" at the next level - include_map_[includable] = {"**"}; + Logger::getInstance().debug( + "Initializing and adding '**' to the include list for the '" + + includable + "' control block..."); + options_map_[includable].include_patterns = {"**"}; } } void ControlBlock::processAsteriskInclude() { - if (include_map_.empty()) { + if (options_map_.empty()) { for (const std::string& includable : includables_) { // Build a map of all includables_ - include_map_[includable] = {}; + Logger::getInstance().debug("Initializing include list for the '" + + includable + "' control block..."); + options_map_[includable].include_patterns = {}; } } for (const std::string& includable : includables_) { - if (include_map_.find(includable) != include_map_.end()) { + if (options_map_.find(includable) != options_map_.end()) { + Logger::getInstance().debug("Include list already exists for the '" + + includable + "' control block"); continue; } // Add all includables_ not already present to the map - include_map_[includable] = {}; + Logger::getInstance().debug("Initializing include list for the '" + + includable + "' control block..."); + options_map_[includable].include_patterns = {}; } } @@ -60,31 +86,197 @@ void ControlBlock::processExplicitInclude(std::string& include) { } if (std::find(includables_.begin(), includables_.end(), include) == includables_.end()) { - Logger::getInstance().debug("'" + include + - "' is not a known includable for the '" + - control_block_name_ + "' control block"); + Logger::getInstance().debug( + "'" + include + + "' is not a known child control block that can be included with the '" + + control_block_name_ + "' control block"); throw IncludeError(); } - if (include_map_.find(include) == include_map_.end()) { + if (options_map_.find(include) == options_map_.end()) { // If we don't already have this include in our map, add it with its // includes if (include_includes == "") { - include_map_[include] = {}; + Logger::getInstance().debug("Initializing include list for the '" + + include + "' control block..."); + options_map_[include].include_patterns = {}; } else { - include_map_[include] = {include_includes}; + Logger::getInstance().debug("Adding '" + include_includes + + "' to the include list for the '" + include + + "' control block..."); + options_map_[include].include_patterns = {include_includes}; } } else { // If we DO already have this in our map, then we should add its // includes if they are useful or new - if (std::find(include_map_[include].begin(), include_map_[include].end(), - include_includes) != include_map_[include].end()) { + if (std::find(options_map_[include].include_patterns.begin(), + options_map_[include].include_patterns.end(), + include_includes) != + options_map_[include].include_patterns.end()) { return; } if (include_includes == "") { return; } - include_map_[include].push_back(include_includes); + Logger::getInstance().debug("Adding '" + include_includes + + "' to the include list for the '" + include + + "' control block..."); + options_map_[include].include_patterns.push_back(include_includes); + } +} + +void ControlBlock::createFilterLists(const std::vector& filters) { + Logger::getInstance().debug( + "Creating filter lists for the '" + control_block_name_ + + "' control block and included child control blocks..."); + for (const std::string& filter : filters) { + // Only case; specific non-generic filter + const std::string del = "."; + size_t del_pos = filter.find(del); + if (del_pos != std::string::npos) { + // If there's a "." then separate filter into the control_block + // and its filter + std::string control_block_filter = filter.substr(del_pos + 1); + std::string control_block = filter.substr(0, del_pos); + + // Check to make sure we are including the specified control block + auto it = options_map_.find(control_block); + if (it == options_map_.end()) { + Logger::getInstance().debug( + "A filter that requires the '" + control_block + + "' control block was provided, but the '" + control_block + + "' control block was not included"); + throw FilterError(); + } + Logger::getInstance().debug("Adding '" + control_block_filter + + "' to the filter list for the '" + + control_block + "' control block..."); + options_map_[control_block].filters.push_back(control_block_filter); + } else { + ControlBlock::addCurrentFilter(filter); + } + } + Logger::getInstance().debug( + "Filter lists for the '" + control_block_name_ + + "' control block and included child control blocks have been created"); +} + +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); + if (filter_value == "") { + Logger::getInstance().debug("Filter values cannot be null"); + throw FilterError(); + } + cbxp_filter_t filter_data = {operation, filter_value}; + Logger::getInstance().debug("Adding '" + filter_key + operation + + filter_value + + "' to the current filters list for the '" + + control_block_name_ + "' control block..."); + current_filters_[filter_key].push_back(filter_data); + return; + } + } +} + +bool ControlBlock::compare(const nlohmann::json& json_value, + const std::string& filter_value, + const std::string& operation) { + std::string value_str = ""; + bool value_is_string = false; + uint64_t value_uint; + + if (json_value.is_number()) { + value_uint = json_value.get(); + } else { + value_str = json_value.get(); + value_is_string = true; + if (value_str.substr(0, 2) == "0x") { + value_uint = std::stoull(value_str, nullptr, 0); + value_is_string = false; + } + } + if (value_is_string) { + // Filter is testing strings + if (operation == "=") { + Logger::getInstance().debug("\"" + value_str + "\" = \"" + filter_value + + "\" ?"); + return (fnmatch(filter_value.c_str(), value_str.c_str(), 0) == 0); + } else { + Logger::getInstance().debug( + "<, <=, >, and >= cannot be used with string filter values"); + throw FilterError(); + } + } // Filter is testing non-strings + else { + uint64_t filter_uint; + try { + filter_uint = std::stoull(filter_value, nullptr, 0); + } catch (...) { + Logger::getInstance().debug("'" + filter_value + + "' cannot be compared to a numeric value"); + throw FilterError(); + } + Logger::getInstance().debug(std::to_string(value_uint) + " " + operation + + " " + std::to_string(filter_uint) + " ?"); + if (operation == "=") { + return value_uint == filter_uint; + } else if (operation == ">") { + return value_uint > filter_uint; + } else if (operation == "<") { + return value_uint < filter_uint; + } else if (operation == ">=") { + return value_uint >= filter_uint; + } else if (operation == "<=") { + return value_uint <= filter_uint; + } + } + // We should never get here, so it would be good to say "no match" just in + // case + return false; +} + +bool ControlBlock::matchFilter(nlohmann::json& control_block_json) { + Logger::getInstance().debug("Applying filters to the '" + + control_block_name_ + "' control block..."); + if (current_filters_.empty()) { + // If the filter map is empty then we want to return the control block + Logger::getInstance().debug("No filters were provided for the '" + + control_block_name_ + "' control block"); + return true; + } + for (const auto& [filter_key, filter_list] : current_filters_) { + if (!control_block_json.contains(filter_key)) { + Logger::getInstance().debug( + "The filter key '" + filter_key + + "' does not correspond to any control block field in the '" + + control_block_name_ + "' control block"); + throw FilterError(); + } + // cppcheck-suppress useStlAlgorithm + for (const cbxp_filter_t& filter_data : filter_list) { + // would require capturing structured bindings to use all_of or none_of + Logger::getInstance().debug("Applying filter '" + filter_key + + filter_data.operation + filter_data.value + + "'..."); + if (!ControlBlock::compare(control_block_json[filter_key], + filter_data.value, filter_data.operation)) { + Logger::getInstance().debug("The filter '" + filter_key + + filter_data.operation + filter_data.value + + "' did not match"); + return false; + } + } } + // If we didn't have a reason to return false, we return true + Logger::getInstance().debug("All filters for the '" + control_block_name_ + + "' control block matched"); + return true; } } // namespace CBXP diff --git a/cbxp/control_blocks/control_block.hpp b/cbxp/control_blocks/control_block.hpp index dcbcb3b..1ec9095 100644 --- a/cbxp/control_blocks/control_block.hpp +++ b/cbxp/control_blocks/control_block.hpp @@ -7,26 +7,44 @@ namespace CBXP { +typedef struct { + std::string operation; + std::string value; +} cbxp_filter_t; + +typedef struct { + std::vector include_patterns; + std::vector filters; +} cbxp_options_t; + class ControlBlock { private: const std::string control_block_name_; const std::vector includables_; + void createIncludeLists(const std::vector& includes); void processDoubleAsteriskInclude(); void processAsteriskInclude(); void processExplicitInclude(std::string& include); + void createFilterLists(const std::vector& filters); + void addCurrentFilter(const std::string& filter); + bool compare(const nlohmann::json& json_value, + const std::string& filter_value, const std::string& operation); protected: ControlBlockFieldFormatter formatter_; - std::unordered_map> include_map_; + std::unordered_map options_map_; + std::unordered_map> current_filters_; + void createOptionsMap(const std::vector& includes, + const std::vector& filters); + bool matchFilter(nlohmann::json& control_block_json); public: - void createIncludeMap(const std::vector& includes); virtual nlohmann::json get(void* __ptr32 p_control_block = nullptr) = 0; explicit ControlBlock(const std::string& name, const std::vector& includables, - const std::vector& includes) + const cbxp_options_t& cbxp_options) : control_block_name_(name), includables_(includables) { - createIncludeMap(includes); + createOptionsMap(cbxp_options.include_patterns, cbxp_options.filters); } virtual ~ControlBlock() = default; }; diff --git a/cbxp/control_blocks/control_block_field_formatter.hpp b/cbxp/control_blocks/control_block_field_formatter.hpp index 7cf499b..cc1d05a 100644 --- a/cbxp/control_blocks/control_block_field_formatter.hpp +++ b/cbxp/control_blocks/control_block_field_formatter.hpp @@ -19,10 +19,13 @@ class ControlBlockFieldFormatter { } static const std::string getString(const void* p_field, int length) { - auto ascii_field_unique_ptr = std::make_unique(length); - std::memcpy(ascii_field_unique_ptr.get(), p_field, length); - __e2a_l(ascii_field_unique_ptr.get(), length); - return ascii_field_unique_ptr.get(); + std::vector ascii_field_tmp(length, 0); + std::memcpy(ascii_field_tmp.data(), p_field, length); + __e2a_l(ascii_field_tmp.data(), length); + std::string ascii_field(ascii_field_tmp.begin(), ascii_field_tmp.end()); + size_t last_non_space = ascii_field.find_last_not_of(" \t\n\r\f\v"); + ascii_field.resize(last_non_space + 1); + return ascii_field; } template static const std::string getHex(const void* p_field) { diff --git a/cbxp/control_blocks/cvt.cpp b/cbxp/control_blocks/cvt.cpp index 12107ad..d679270 100644 --- a/cbxp/control_blocks/cvt.cpp +++ b/cbxp/control_blocks/cvt.cpp @@ -43,11 +43,17 @@ nlohmann::json CVT::get(void* __ptr32 p_control_block) { cvt_json["cvtasvt"] = formatter_.getHex(&p_cvtmap->cvtasvt); cvt_json["cvtecvt"] = formatter_.getHex(&p_cvtmap->cvtecvt); - for (const auto& [include, include_includes] : include_map_) { + for (const auto& [include, cbxp_options] : options_map_) { if (include == "asvt") { - cvt_json["cvtasvt"] = CBXP::ASVT(include_includes).get(p_cvtmap->cvtasvt); + cvt_json["cvtasvt"] = CBXP::ASVT(cbxp_options).get(p_cvtmap->cvtasvt); + if (cvt_json["cvtasvt"].is_null()) { + return {}; + } } else if (include == "ecvt") { - cvt_json["cvtecvt"] = CBXP::ECVT(include_includes).get(p_cvtmap->cvtecvt); + cvt_json["cvtecvt"] = CBXP::ECVT(cbxp_options).get(p_cvtmap->cvtecvt); + if (cvt_json["cvtecvt"].is_null()) { + return {}; + } } } @@ -139,6 +145,10 @@ nlohmann::json CVT::get(void* __ptr32 p_control_block) { cvt_json["cvt0pt03"] = formatter_.getHex(p_cvtmap->cvt0pt03); cvt_json["cvt0scr1"] = formatter_.getHex(p_cvtmap->cvt0scr1); - return cvt_json; + if (CVT::matchFilter(cvt_json)) { + return cvt_json; + } else { + return {}; + } } } // namespace CBXP diff --git a/cbxp/control_blocks/cvt.hpp b/cbxp/control_blocks/cvt.hpp index 3111cda..e65e3c7 100644 --- a/cbxp/control_blocks/cvt.hpp +++ b/cbxp/control_blocks/cvt.hpp @@ -8,8 +8,8 @@ namespace CBXP { class CVT : public ControlBlock { public: nlohmann::json get(void* __ptr32 p_control_block = nullptr) override; - explicit CVT(const std::vector& includes) - : ControlBlock("cvt", {"ecvt", "asvt"}, includes) {} + explicit CVT(const cbxp_options_t& cbxp_options) + : ControlBlock("cvt", {"ecvt", "asvt"}, cbxp_options) {} }; } // namespace CBXP diff --git a/cbxp/control_blocks/ecvt.cpp b/cbxp/control_blocks/ecvt.cpp index e577624..0142304 100644 --- a/cbxp/control_blocks/ecvt.cpp +++ b/cbxp/control_blocks/ecvt.cpp @@ -176,6 +176,10 @@ nlohmann::json ECVT::get(void* __ptr32 p_control_block) { reinterpret_cast(&p_ecvt->ecvtvser)); ecvt_json["ecvtxtsw"] = formatter_.getHex(p_ecvt->ecvtxtsw); - return ecvt_json; + if (ECVT::matchFilter(ecvt_json)) { + return ecvt_json; + } else { + return {}; + } } } // namespace CBXP diff --git a/cbxp/control_blocks/ecvt.hpp b/cbxp/control_blocks/ecvt.hpp index 9cece25..ff452b3 100644 --- a/cbxp/control_blocks/ecvt.hpp +++ b/cbxp/control_blocks/ecvt.hpp @@ -8,8 +8,8 @@ namespace CBXP { class ECVT : public ControlBlock { public: nlohmann::json get(void* __ptr32 p_control_block = nullptr) override; - explicit ECVT(const std::vector& includes) - : ControlBlock("ecvt", {}, includes) {} + explicit ECVT(const cbxp_options_t& cbxp_options) + : ControlBlock("ecvt", {}, cbxp_options) {} }; } // namespace CBXP #endif diff --git a/cbxp/control_blocks/psa.cpp b/cbxp/control_blocks/psa.cpp index 8feec8c..ce13391 100644 --- a/cbxp/control_blocks/psa.cpp +++ b/cbxp/control_blocks/psa.cpp @@ -28,9 +28,12 @@ nlohmann::json PSA::get(void* __ptr32 p_control_block) { psa_json["flccvt"] = formatter_.getHex(&p_psa->flccvt); - for (const auto& [include, include_includes] : include_map_) { + for (const auto& [include, cbxp_options] : options_map_) { if (include == "cvt") { - psa_json["flccvt"] = CBXP::CVT(include_includes).get(p_psa->flccvt); + psa_json["flccvt"] = CBXP::CVT(cbxp_options).get(p_psa->flccvt); + if (psa_json["flccvt"].is_null()) { + return {}; + } } } @@ -61,6 +64,10 @@ nlohmann::json PSA::get(void* __ptr32 p_control_block) { psa_json["psaval"] = formatter_.getBitmap(p_psa->psaval); psa_json["psaxcvt"] = formatter_.getHex(p_psa->psaxcvt); - return psa_json; + if (PSA::matchFilter(psa_json)) { + return psa_json; + } else { + return {}; + } } } // namespace CBXP diff --git a/cbxp/control_blocks/psa.hpp b/cbxp/control_blocks/psa.hpp index 7843b38..d53b53d 100644 --- a/cbxp/control_blocks/psa.hpp +++ b/cbxp/control_blocks/psa.hpp @@ -8,8 +8,8 @@ namespace CBXP { class PSA : public ControlBlock { public: nlohmann::json get(void* __ptr32 p_control_block = nullptr) override; - explicit PSA(const std::vector& includes) - : ControlBlock("psa", {"cvt"}, includes) {} + explicit PSA(const cbxp_options_t& cbxp_options) + : ControlBlock("psa", {"cvt"}, cbxp_options) {} }; } // namespace CBXP #endif diff --git a/cbxp/main.cpp b/cbxp/main.cpp index 19c6ea5..d9c4e9d 100644 --- a/cbxp/main.cpp +++ b/cbxp/main.cpp @@ -12,7 +12,10 @@ #include "control_block_error.hpp" typedef const cbxp_result_t* (*cbxp_t)(const char* control_block_name, - const char* includes_string, bool debug); + const char* includes_string, + const char* filters_string, bool debug); + +typedef const void (*cbxp_free_t)(const cbxp_result_t* cbxp_result, bool debug); static void show_usage(const char* argv[]); static void show_dll_errors(); @@ -28,6 +31,9 @@ static void show_usage(const char* argv[]) { << " -i, --include Include additional control " "blocks based on a pattern" << std::endl + << " -f, --filter Filter repeated control " + "block data" + << std::endl << " -v, --version Show version number" << std::endl << " -h, --help Show usage information" @@ -51,6 +57,11 @@ static void cleanup_and_exit(int exit_rc, void* lib_handle) { exit(exit_rc); } +bool check_for_comma(const std::string& string) { + return std::any_of(string.begin(), string.end(), + [](char c) { return c == ','; }); +} + int main(int argc, const char* argv[]) { // Load 'libcbxp.so' void* lib_handle = dlopen("libcbxp.so", RTLD_NOW); @@ -59,19 +70,27 @@ int main(int argc, const char* argv[]) { return -1; } + int exit_rc = -1; // Resolve symbol 'cbxp()' cbxp_t cbxp = reinterpret_cast(dlsym(lib_handle, "cbxp")); if (cbxp == nullptr) { show_dll_errors(); - cleanup_and_exit(-1, lib_handle); + cleanup_and_exit(exit_rc, lib_handle); + } + cbxp_free_t cbxp_free = + reinterpret_cast(dlsym(lib_handle, "cbxp_free")); + if (cbxp_free == nullptr) { + show_dll_errors(); + cleanup_and_exit(exit_rc, lib_handle); } bool debug = false; - std::string control_block_name = "", includes_string = ""; + std::string control_block_name = "", includes_string = "", + filters_string = ""; if (argc < 2) { show_usage(argv); - cleanup_and_exit(-1, lib_handle); + cleanup_and_exit(exit_rc, lib_handle); } if (argc == 2) { @@ -91,28 +110,46 @@ int main(int argc, const char* argv[]) { for (int i = 1; i < argc; i++) { std::string flag = argv[i]; if (flag == "-d" || flag == "--debug") { - debug = true; + if (!debug) { + debug = true; + } else { + show_usage(argv); + cleanup_and_exit(exit_rc, lib_handle); + } } else if (flag == "-i" || flag == "--include") { if (i + 1 >= argc - 1) { show_usage(argv); - cleanup_and_exit(-1, lib_handle); + cleanup_and_exit(exit_rc, lib_handle); } std::string include = std::string(argv[++i]); - bool has_comma = std::any_of(include.begin(), include.end(), - [](char c) { return c == ','; }); - if (has_comma) { + if (check_for_comma(include)) { std::cerr << "Include patterns cannot contain commas" << std::endl; - cleanup_and_exit(-1, lib_handle); + cleanup_and_exit(exit_rc, lib_handle); } if (includes_string == "") { includes_string = include; } else { includes_string += "," + include; } + } else if (flag == "-f" || flag == "--filter") { + if (i + 1 >= argc - 1) { + show_usage(argv); + cleanup_and_exit(exit_rc, lib_handle); + } + std::string filter = std::string(argv[++i]); + if (check_for_comma(filter)) { + std::cerr << "Filters cannot contain commas" << std::endl; + cleanup_and_exit(exit_rc, lib_handle); + } + if (filters_string == "") { + filters_string = filter; + } else { + filters_string += "," + filter; + } } else { if (i != argc - 1) { show_usage(argv); - cleanup_and_exit(-1, lib_handle); + cleanup_and_exit(exit_rc, lib_handle); } control_block_name = std::string(argv[i]); } @@ -120,24 +157,31 @@ int main(int argc, const char* argv[]) { if (control_block_name == "") { show_usage(argv); - cleanup_and_exit(-1, lib_handle); + cleanup_and_exit(exit_rc, lib_handle); } nlohmann::json control_block_json; const cbxp_result_t* cbxp_result = - cbxp(control_block_name.c_str(), includes_string.c_str(), debug); - - if (cbxp_result->return_code == CBXP::Error::BadControlBlock) { - std::cerr << "Unknown control block '" << control_block_name - << "' was specified." << std::endl; - cleanup_and_exit(-1, lib_handle); - } else if (cbxp_result->return_code == CBXP::Error::BadInclude) { - std::cerr << "A bad include pattern was provided" << std::endl; - cleanup_and_exit(-1, lib_handle); - } else { - std::cout << cbxp_result->result_json << std::endl; + cbxp(control_block_name.c_str(), includes_string.c_str(), + filters_string.c_str(), debug); + + 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; + exit_rc = 0; } - cleanup_and_exit(0, lib_handle); + cbxp_free(cbxp_result, debug); + cleanup_and_exit(exit_rc, lib_handle); } diff --git a/cbxp/python/_cbxp.c b/cbxp/python/_cbxp.c index 9b167a0..11f5d65 100644 --- a/cbxp/python/_cbxp.c +++ b/cbxp/python/_cbxp.c @@ -1,46 +1,42 @@ #define PY_SSIZE_T_CLEAN #include -#include #include #include #include "cbxp.h" #include "cbxp_result.h" -pthread_mutex_t cbxp_mutex = PTHREAD_MUTEX_INITIALIZER; - // Entry point to the call_cbxp() function static PyObject* call_cbxp(PyObject* self, PyObject* args, PyObject* kwargs) { PyObject* result_dictionary; PyObject* debug_pyobj; - const char* control_block; - const char* includes_string; + const char* p_control_block; + const char* p_includes_string; + const char* p_filters_string; Py_ssize_t request_length; bool debug = false; - static char* kwlist[] = {"request", "include", "debug", NULL}; + static char* kwlist[] = {"control_block", "includes_string", "filters_string", + "debug", NULL}; - if (!PyArg_ParseTupleAndKeywords(args, kwargs, "s|sO", kwlist, &control_block, - &includes_string, &debug_pyobj)) { + if (!PyArg_ParseTupleAndKeywords(args, kwargs, "sss|O", kwlist, + &p_control_block, &p_includes_string, + &p_filters_string, &debug_pyobj)) { return NULL; } debug = PyObject_IsTrue(debug_pyobj); - // Since cbxp manages cbxp_result_t as a static structure, - // we need to use a mutex to make this thread safe. - // Technically we shouldn't need this because the Python GIL, - // but we will set this up anyways to be safe. - pthread_mutex_lock(&cbxp_mutex); - - const cbxp_result_t* result = cbxp(control_block, includes_string, debug); + cbxp_result_t* p_cbxp_result = + cbxp(p_control_block, p_includes_string, p_filters_string, debug); - result_dictionary = Py_BuildValue( - "{s:s#, s:i}", "result_json", result->result_json, - result->result_json_length, "return_code", result->return_code); + 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); - pthread_mutex_unlock(&cbxp_mutex); + cbxp_free(p_cbxp_result, debug); return result_dictionary; } diff --git a/python/cbxp/_C.pyi b/python/cbxp/_C.pyi index 7ab645e..1d518bd 100644 --- a/python/cbxp/_C.pyi +++ b/python/cbxp/_C.pyi @@ -1,5 +1,6 @@ def call_cbxp( # noqa: N999 control_block: str, includes_string: str, + filters_string: str, debug: bool = False, ) -> dict: ... diff --git a/python/cbxp/__init__.py b/python/cbxp/__init__.py index 448e3f8..4627129 100644 --- a/python/cbxp/__init__.py +++ b/python/cbxp/__init__.py @@ -1,2 +1,4 @@ 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 49e35e7..1868db9 100644 --- a/python/cbxp/cbxp.py +++ b/python/cbxp/cbxp.py @@ -4,12 +4,41 @@ from cbxp._C import call_cbxp +class CBXPFilterOperation(Enum): + """An enum of possible filter operations for the cbxp interface""" + + EQUAL = "=" + LESS_THAN = "<" + GREATER_THAN = ">" + LESS_THAN_OR_EQUAL = "<=" + GREATER_THAN_OR_EQUAL = ">=" + + +class CBXPFilter: + """A class to represent a filter to limit cbxp output based on set conditions""" + + def __init__( + self, + key: str, + operation: CBXPFilterOperation, + value: str | int, + ): + self.key = key + self.operation = operation.value + self.value = value + + def __str__(self): + return str(self.key) + str(self.operation) + str(self.value) + + class CBXPErrorCode(Enum): """An enum of error and return codes from the cbxp interface""" COMMA_IN_INCLUDE = -1 + COMMA_IN_FILTER = -2 BAD_CONTROL_BLOCK = 1 BAD_INCLUDE = 2 + BAD_CONTROL_BLOCK_FILTER = 3 class CBXPError(Exception): @@ -20,10 +49,14 @@ def __init__(self, return_code: int, control_block_name: str): match self.rc: case CBXPErrorCode.COMMA_IN_INCLUDE.value: message = "Include patterns cannot contain commas" + case CBXPErrorCode.COMMA_IN_FILTER.value: + message = "Filters cannot contain commas" case CBXPErrorCode.BAD_CONTROL_BLOCK.value: message = f"Unknown control block '{control_block_name}' was specified." case CBXPErrorCode.BAD_INCLUDE.value: message = "A bad include pattern was provided" + case CBXPErrorCode.BAD_CONTROL_BLOCK_FILTER.value: + message = "A bad filter was provided" case _: message = "an unknown error occurred" super().__init__(message) @@ -32,14 +65,35 @@ def __init__(self, return_code: int, control_block_name: str): def cbxp( control_block: str, includes: list[str] = None, + filters: list[CBXPFilter] = None, debug: bool = False, ) -> dict: + # Includes processing if includes is None: includes = [] for include in includes: if "," in include: raise CBXPError(CBXPErrorCode.COMMA_IN_INCLUDE.value, control_block) - response = call_cbxp(control_block.lower(), ",".join(includes).lower(), debug=debug) + + # Filter Processing + if filters is None: + filters = [] + filters_string = "" + for filter_obj in filters: + if filters_string != "": + filters_string += "," + if "," in str(filter_obj): + raise CBXPError(CBXPErrorCode.COMMA_IN_FILTER.value, control_block) + filters_string += str(filter_obj) + + response = call_cbxp( + control_block.lower(), + ",".join(includes), + filters_string, + 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/tests/test.py b/tests/test.py index 7d3351a..c08f051 100644 --- a/tests/test.py +++ b/tests/test.py @@ -1,6 +1,6 @@ import unittest -from cbxp import CBXPError, cbxp +from cbxp import CBXPError, CBXPFilter, CBXPFilterOperation, cbxp class TestCBXP(unittest.TestCase): @@ -169,6 +169,233 @@ def test_cbxp_can_extract_cvt_and_include_wildcard_and_asvt_recursive_wildcard( self.assertIs(type(entry), dict) self.assertIs(type(entry["ascbassb"]), dict) + # ============================================================================ + # Filters + # ============================================================================ + def test_cbxp_can_use_filter(self): + cbdata = cbxp( + "psa", + filters=[CBXPFilter("psapsa", CBXPFilterOperation.EQUAL, "PSA")], + ) + self.assertIs(type(cbdata), dict) + + def test_cbxp_can_use_filter_with_wildcard_include(self): + cbdata = cbxp( + "psa", + filters=[ + CBXPFilter( + "cvt.asvt.ascb.assb.assbjbns", + CBXPFilterOperation.EQUAL, + "*MASTER*", + ), + ], + includes=["**"], + ) + self.assertIs(type(cbdata), dict) + + def test_cbxp_can_use_filter_with_explicit_include(self): + cbdata = cbxp( + "psa", + filters=[ + CBXPFilter( + "cvt.asvt.ascb.assb.assbjbns", + CBXPFilterOperation.EQUAL, + "*MASTER*", + ), + ], + includes=["cvt.asvt.ascb.assb"], + ) + self.assertIs(type(cbdata), dict) + + def test_cbxp_can_use_multiple_filters(self): + cbdata = cbxp( + "psa", + filters=[ + CBXPFilter( + "cvt.asvt.ascb.assb.assbjbns", + CBXPFilterOperation.EQUAL, + "*MASTER*", + ), + CBXPFilter( + "cvt.asvt.ascb.ascbasid", + CBXPFilterOperation.GREATER_THAN, + 0, + ), + ], + includes=["cvt.asvt.ascb.assb"], + ) + self.assertIs(type(cbdata), dict) + + def test_cbxp_can_use_wildcard_filter_with_string(self): + cbdata = cbxp( + "psa", + filters=[ + CBXPFilter( + "cvt.asvt.ascb.assb.assbjbns", + CBXPFilterOperation.EQUAL, + "?MAS?ER?", + ), + ], + includes=["cvt.asvt.ascb.assb"], + ) + self.assertIs(type(cbdata), dict) + + def test_cbxp_can_use_int_filter_equal(self): + cbdata = cbxp( + "psa", + filters=[ + CBXPFilter("cvt.asvt.ascb.ascbasid", CBXPFilterOperation.EQUAL, 1), + ], + includes=["cvt.asvt.ascb.assb"], + ) + self.assertIs(type(cbdata), dict) + + def test_cbxp_can_use_int_filter_greater_than(self): + cbdata = cbxp( + "psa", + filters=[ + CBXPFilter( + "cvt.asvt.ascb.ascbasid", + CBXPFilterOperation.GREATER_THAN, + 0, + ), + ], + includes=["cvt.asvt.ascb.assb"], + ) + self.assertIs(type(cbdata), dict) + + def test_cbxp_can_use_int_filter_less_than(self): + cbdata = cbxp( + "psa", + filters=[ + CBXPFilter( + "cvt.asvt.ascb.ascbasid", + CBXPFilterOperation.LESS_THAN, + 2, + ), + ], + includes=["cvt.asvt.ascb.assb"], + ) + self.assertIs(type(cbdata), dict) + + def test_cbxp_can_use_int_filter_greater_than_or_equal(self): + cbdata = cbxp( + "psa", + filters=[ + CBXPFilter( + "cvt.asvt.ascb.ascbasid", + CBXPFilterOperation.GREATER_THAN_OR_EQUAL, + 1, + ), + ], + includes=["cvt.asvt.ascb.assb"], + ) + self.assertIs(type(cbdata), dict) + + def test_cbxp_can_use_int_filter_less_than_or_equal(self): + cbdata = cbxp( + "psa", + filters=[ + CBXPFilter( + "cvt.asvt.ascb.ascbasid", + CBXPFilterOperation.LESS_THAN_OR_EQUAL, + 2, + ), + ], + includes=["cvt.asvt.ascb.assb"], + ) + self.assertIs(type(cbdata), dict) + + def test_cbxp_can_use_int_filter_with_hex_field_equal(self): + cbdata = cbxp( + "cvt", + filters=[CBXPFilter("cvtasmvt", CBXPFilterOperation.EQUAL, 2281701376)], + ) + self.assertIs(type(cbdata), dict) + + def test_cbxp_can_use_hex_filter_with_equal(self): + cbdata = cbxp( + "cvt", + filters=[CBXPFilter("cvtasmvt", CBXPFilterOperation.EQUAL, "0x88000000")], + ) + self.assertIs(type(cbdata), dict) + + def test_cbxp_can_use_hex_filter_with_greater_than(self): + cbdata = cbxp( + "cvt", + filters=[ + CBXPFilter("cvtasmvt", CBXPFilterOperation.GREATER_THAN, "0x87FFFFFF"), + ], + ) + self.assertIs(type(cbdata), dict) + + def test_cbxp_can_use_hex_filter_with_less_than(self): + cbdata = cbxp( + "cvt", + filters=[ + CBXPFilter("cvtasmvt", CBXPFilterOperation.LESS_THAN, "0x88000001"), + ], + ) + self.assertIs(type(cbdata), dict) + + def test_cbxp_can_use_hex_filter_with_greater_than_or_equal(self): + cbdata = cbxp( + "cvt", + filters=[ + CBXPFilter( + "cvtasmvt", + CBXPFilterOperation.GREATER_THAN_OR_EQUAL, + "0x87FFFFFF", + ), + ], + ) + self.assertIs(type(cbdata), dict) + + def test_cbxp_can_use_hex_filter_with_less_than_or_equal(self): + cbdata = cbxp( + "cvt", + filters=[ + CBXPFilter( + "cvtasmvt", + CBXPFilterOperation.LESS_THAN_OR_EQUAL, + "0x88000000", + ), + ], + ) + self.assertIs(type(cbdata), dict) + + def test_cbxp_returns_none_if_no_filter_match( + self, + ): + self.assertIsNone( + cbxp( + "psa", + filters=[CBXPFilter("psapsa", CBXPFilterOperation.EQUAL, "PSB")], + ), + ) + + def test_cbxp_returns_none_if_one_of_two_filters_fails( + self, + ): + self.assertIsNone( + cbxp( + "ascb", + includes=["assb"], + filters=[ + CBXPFilter( + "assb.assbjbns", + CBXPFilterOperation.EQUAL, + "*MASTER*", + ), + CBXPFilter( + "ascbasid", + CBXPFilterOperation.GREATER_THAN, + 2, + ), + ], + ), + ) + # ============================================================================ # Debug Mode # ============================================================================ @@ -236,6 +463,82 @@ def test_cbxp_raises_cbxp_error_when_pattern_cannot_contain_comma_2(self): cbxp("cvt", includes=["asvt,as"]) self.assertEqual("Include patterns cannot contain commas", str(e.exception)) + # ============================================================================ + # Errors: Bad Filters + # ============================================================================ + def test_cbxp_raises_cbxp_error_if_filter_uses_non_included_control_block( + self, + ): + with self.assertRaises(CBXPError) as e: + cbxp( + "psa", + filters=[ + CBXPFilter( + "cvt.asvt.ascb.assb.assbjbns", + CBXPFilterOperation.EQUAL, + "*MASTER*", + ), + ], + ) + self.assertEqual("A bad filter was provided", str(e.exception)) + + def test_cbxp_raises_cbxp_error_if_non_equality_filter_used_with_string( + self, + ): + with self.assertRaises(CBXPError) as e: + cbxp( + "psa", + includes=["**"], + filters=[ + CBXPFilter( + "cvt.asvt.ascb.assb.assbjbns", + CBXPFilterOperation.LESS_THAN_OR_EQUAL, + "*MASTER*", + ), + ], + ) + self.assertEqual("A bad filter was provided", str(e.exception)) + + def test_cbxp_raises_cbxp_error_if_filter_uses_unknown_key( + self, + ): + with self.assertRaises(CBXPError) as e: + cbxp( + "psa", + 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( + self, + ): + with self.assertRaises(CBXPError) as e: + cbxp( + "psa", + filters=["psapsa", 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( + self, + ): + with self.assertRaises(CBXPError) as e: + cbxp( + "ascb", + filters=["ascbasid", CBXPFilterOperation.LESS_THAN, "junk"], + ) + self.assertEqual("A bad filter was provided", str(e.exception)) + + def test_cbxp_raises_cbxp_error_if_filter_has_comma( + self, + ): + with self.assertRaises(CBXPError) as e: + cbxp( + "psa", + filters=["psapsa", CBXPFilterOperation.EQUAL, "PSA,PSB"], + ) + self.assertEqual("Filters cannot contain commas", str(e.exception)) + if __name__ == "__main__": unittest.main(verbosity=2, failfast=True, buffer=True) diff --git a/tests/test.sh b/tests/test.sh index 1706df0..7d30785 100755 --- a/tests/test.sh +++ b/tests/test.sh @@ -19,6 +19,18 @@ run_with_expected_exit_code() { echo } +run_with_expected_null_response() { + echo "Running: $* (expecting null response)" + + if "$@" | grep -q -x "null"; then + echo "Command exited with null response as expected." + else + echo "Unexpected non-null response" + exit 1 + fi + echo +} + # Basic Usage run_with_expected_exit_code 0 ./dist/cbxp psa run_with_expected_exit_code 0 ./dist/cbxp cvt @@ -44,6 +56,26 @@ run_with_expected_exit_code 0 ./dist/cbxp -i "asvt.*" -i "*" cvt run_with_expected_exit_code 0 ./dist/cbxp -i assb ascb run_with_expected_exit_code 0 ./dist/cbxp -i cvt.ecvt -i cvt.asvt.ascb.assb psa +# Filters +run_with_expected_exit_code 0 ./dist/cbxp -f psapsa=PSA psa +run_with_expected_exit_code 0 ./dist/cbxp -f "cvt.asvt.ascb.assb.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_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 + # Debug Mode run_with_expected_exit_code 0 ./dist/cbxp -d psa run_with_expected_exit_code 0 ./dist/cbxp --debug psa @@ -59,6 +91,10 @@ run_with_expected_exit_code 255 ./dist/cbxp run_with_expected_exit_code 255 ./dist/cbxp -x "unknown flag" cvt run_with_expected_exit_code 255 ./dist/cbxp -i cvt run_with_expected_exit_code 255 ./dist/cbxp -i -i cvt psa +run_with_expected_exit_code 255 ./dist/cbxp -d -d psa +run_with_expected_exit_code 255 ./dist/cbxp -f psa +run_with_expected_exit_code 255 ./dist/cbxp -f psapsa=psa +run_with_expected_exit_code 255 ./dist/cbxp --debug -d psa # Errors: Unknown Control Block run_with_expected_exit_code 255 ./dist/cbxp unknown # Errors: Bad Include Patterns @@ -70,6 +106,13 @@ 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 psapsa= psa +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 echo " -------------------------------- " echo " -------------------------------- " From eb16ecd74ce7e1e9094ced7008f0dfb385228305 Mon Sep 17 00:00:00 2001 From: varunchennamadhava Date: Tue, 17 Feb 2026 13:45:41 -0500 Subject: [PATCH 11/37] squashed commits for OUCB Signed-off-by: varunchennamadhava --- cbxp/cbxp.cpp | 29 +- cbxp/cbxp.h | 7 +- cbxp/control_block_error.hpp | 7 +- cbxp/control_block_explorer.cpp | 76 ++-- cbxp/control_block_explorer.hpp | 10 +- cbxp/control_blocks/ascb.cpp | 28 +- cbxp/control_blocks/ascb.hpp | 4 +- cbxp/control_blocks/assb.cpp | 13 +- cbxp/control_blocks/assb.hpp | 4 +- cbxp/control_blocks/asvt.cpp | 18 +- cbxp/control_blocks/asvt.hpp | 4 +- cbxp/control_blocks/control_block.cpp | 230 +++++++++++- cbxp/control_blocks/control_block.hpp | 26 +- .../control_block_field_formatter.hpp | 11 +- cbxp/control_blocks/cvt.cpp | 18 +- cbxp/control_blocks/cvt.hpp | 4 +- cbxp/control_blocks/ecvt.cpp | 6 +- cbxp/control_blocks/ecvt.hpp | 4 +- cbxp/control_blocks/oucb.cpp | 169 +++++++++ cbxp/control_blocks/oucb.hpp | 122 +++++++ cbxp/control_blocks/psa.cpp | 13 +- cbxp/control_blocks/psa.hpp | 4 +- cbxp/main.cpp | 92 +++-- cbxp/python/_cbxp.c | 34 +- python/cbxp/_C.pyi | 1 + python/cbxp/__init__.py | 2 + python/cbxp/cbxp.py | 56 ++- tests/test.py | 333 +++++++++++++++++- tests/test.sh | 46 +++ 29 files changed, 1207 insertions(+), 164 deletions(-) create mode 100644 cbxp/control_blocks/oucb.cpp create mode 100644 cbxp/control_blocks/oucb.hpp diff --git a/cbxp/cbxp.cpp b/cbxp/cbxp.cpp index 5e8618f..32a0a4b 100644 --- a/cbxp/cbxp.cpp +++ b/cbxp/cbxp.cpp @@ -6,18 +6,33 @@ #include "cbxp_result.h" #include "control_block_explorer.hpp" +#include "logger.hpp" -const cbxp_result_t* cbxp(const char* control_block_name, - const char* includes_string, bool debug) { +cbxp_result_t* cbxp(const char* control_block, const char* includes_string, + const char* filters_string, bool debug) { nlohmann::json control_block_json; - std::string control_block = control_block_name; + std::string control_block_name = control_block; - static cbxp_result_t cbxp_result = {nullptr, 0, -1}; + 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(&cbxp_result, debug); + CBXP::ControlBlockExplorer(p_cbxp_result); + + explorer.exploreControlBlock(control_block_name, includes_string, + filters_string); + + return p_cbxp_result; +} - explorer.exploreControlBlock(control_block, includes_string); +void cbxp_free(cbxp_result_t* cbxp_result, bool debug) { + CBXP::Logger::getInstance().setDebug(debug); - return &cbxp_result; + CBXP::Logger::getInstance().debugFree(cbxp_result->result_json); + delete cbxp_result->result_json; + CBXP::Logger::getInstance().debugFree(cbxp_result); + delete cbxp_result; } diff --git a/cbxp/cbxp.h b/cbxp/cbxp.h index 4e50bf7..3c6ca55 100644 --- a/cbxp/cbxp.h +++ b/cbxp/cbxp.h @@ -7,13 +7,16 @@ extern "C" { #endif -const cbxp_result_t* cbxp(const char* control_block_name, - const char* includes_string, bool debug); +cbxp_result_t* cbxp(const char* control_block, const char* includes_string, + const char* filters_string, bool debug); + +void cbxp_free(cbxp_result_t* cbxp_result, bool debug); #ifdef __cplusplus } #endif #pragma export(cbxp) +#pragma export(cbxp_free) #endif diff --git a/cbxp/control_block_error.hpp b/cbxp/control_block_error.hpp index 65902a0..bbdeee4 100644 --- a/cbxp/control_block_error.hpp +++ b/cbxp/control_block_error.hpp @@ -2,7 +2,7 @@ #define __CONTROL_BLOCK_ERROR_H_ namespace CBXP { -enum Error { BadControlBlock = 1, BadInclude }; +enum Error { BadControlBlock = 1, BadInclude = 2, BadFilter = 3 }; class CBXPError : public std::exception { private: Error error_code_; @@ -22,6 +22,11 @@ class IncludeError : public CBXPError { IncludeError() : CBXPError(Error::BadInclude) {} }; +class FilterError : public CBXPError { + public: + FilterError() : CBXPError(Error::BadFilter) {} +}; + } // namespace CBXP #endif diff --git a/cbxp/control_block_explorer.cpp b/cbxp/control_block_explorer.cpp index f1cfd65..b3f1204 100644 --- a/cbxp/control_block_explorer.cpp +++ b/cbxp/control_block_explorer.cpp @@ -12,62 +12,49 @@ #include "control_blocks/control_block.hpp" #include "control_blocks/cvt.hpp" #include "control_blocks/ecvt.hpp" +#include "control_blocks/oucb.hpp" #include "control_blocks/psa.hpp" #include "logger.hpp" namespace CBXP { -std::vector ControlBlockExplorer::createIncludeList( - const std::string& includes_string) { - if (includes_string == "") { +std::vector ControlBlockExplorer::createOptionsList( + const std::string& comma_separated_string) { + if (comma_separated_string == "") { return {}; } - std::vector includes = {}; + std::vector options_list = {}; Logger::getInstance().debug( - "Creating include list from the provided include list string: " + - includes_string); + "Creating options list from the provided comma-separated list string: " + + comma_separated_string); const std::string del = ","; std::string entry; size_t index = 0; - auto pos = includes_string.find(del); + auto pos = comma_separated_string.find(del); while (pos != std::string::npos) { - entry = includes_string.substr(index, pos); - includes.push_back(entry); + entry = comma_separated_string.substr(index, pos); + options_list.push_back(entry); index += pos + 1; - pos = includes_string.substr(index, std::string::npos).find(del); + pos = comma_separated_string.substr(index, std::string::npos).find(del); } - entry = includes_string.substr(index, pos); - includes.push_back(entry); - Logger::getInstance().debug("Done."); + entry = comma_separated_string.substr(index, pos); + options_list.push_back(entry); + Logger::getInstance().debug("Options list created"); - return includes; -} - -ControlBlockExplorer::ControlBlockExplorer(cbxp_result_t* p_result, - bool debug) { - Logger::getInstance().setDebug(debug); - - if (p_result->result_json != nullptr) { - Logger::getInstance().debugFree(p_result->result_json); - delete[] p_result->result_json; - } - - p_result->result_json_length = 0; - p_result->result_json = nullptr; - p_result->return_code = 0; - - p_result_ = p_result; + return options_list; } void ControlBlockExplorer::exploreControlBlock( - const std::string& control_block_name, const std::string& includes_string) { - std::vector includes = - ControlBlockExplorer::createIncludeList(includes_string); + 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)}; Logger::getInstance().debug("Extracting '" + control_block_name + "' control block data..."); @@ -75,17 +62,19 @@ void ControlBlockExplorer::exploreControlBlock( nlohmann::json control_block_json; try { if (control_block_name == "psa") { - control_block_json = PSA(includes).get(); + control_block_json = PSA(cbxp_options).get(); } else if (control_block_name == "cvt") { - control_block_json = CVT(includes).get(); + control_block_json = CVT(cbxp_options).get(); } else if (control_block_name == "ecvt") { - control_block_json = ECVT(includes).get(); + control_block_json = ECVT(cbxp_options).get(); } else if (control_block_name == "ascb") { - control_block_json = ASCB(includes).get(); + control_block_json = ASCB(cbxp_options).get(); } else if (control_block_name == "asvt") { - control_block_json = ASVT(includes).get(); + control_block_json = ASVT(cbxp_options).get(); } else if (control_block_name == "assb") { - control_block_json = ASSB(includes).get(); + control_block_json = ASSB(cbxp_options).get(); + } else if (control_block_name == "oucb") { + control_block_json = OUCB(cbxp_options).get(); } else { throw ControlBlockError(); } @@ -96,17 +85,16 @@ void ControlBlockExplorer::exploreControlBlock( std::string control_block_json_string = control_block_json.dump(); - Logger::getInstance().debug("Done."); + Logger::getInstance().debug("'" + control_block_name + + "' control block data extracted"); Logger::getInstance().debug("Control Block JSON: " + control_block_json_string); p_result_->result_json_length = control_block_json_string.length(); - p_result_->result_json = new char[p_result_->result_json_length]; - p_result_->result_json[p_result_->result_json_length] = 0; - + p_result_->result_json = new char[p_result_->result_json_length + 1]{}; Logger::getInstance().debugAllocate(p_result_->result_json, 64, - p_result_->result_json_length); + p_result_->result_json_length + 1); std::strncpy(p_result_->result_json, control_block_json_string.c_str(), p_result_->result_json_length); diff --git a/cbxp/control_block_explorer.hpp b/cbxp/control_block_explorer.hpp index 7542b17..01ae003 100644 --- a/cbxp/control_block_explorer.hpp +++ b/cbxp/control_block_explorer.hpp @@ -9,13 +9,15 @@ namespace CBXP { class ControlBlockExplorer { private: cbxp_result_t* p_result_; - static std::vector createIncludeList( - const std::string& includes_string); + static std::vector createOptionsList( + const std::string& comma_separated_string); public: - ControlBlockExplorer(cbxp_result_t* p_result, bool debug); + explicit ControlBlockExplorer(cbxp_result_t* p_result) + : p_result_(p_result) {}; void exploreControlBlock(const std::string& control_block_name, - const std::string& includes_string); + const std::string& includes_string, + const std::string& filters_string); }; } // namespace CBXP diff --git a/cbxp/control_blocks/ascb.cpp b/cbxp/control_blocks/ascb.cpp index d2f5fa9..840c9c3 100644 --- a/cbxp/control_blocks/ascb.cpp +++ b/cbxp/control_blocks/ascb.cpp @@ -12,6 +12,7 @@ #include "assb.hpp" #include "asvt.hpp" #include "logger.hpp" +#include "oucb.hpp" namespace CBXP { nlohmann::json ASCB::get(void* __ptr32 p_control_block) { @@ -42,7 +43,11 @@ nlohmann::json ASCB::get(void* __ptr32 p_control_block) { p_ascb_addr++; continue; } - ascbs.push_back(ASCB::get(reinterpret_cast(*p_ascb_addr))); + nlohmann::json next_ascb = + ASCB::get(reinterpret_cast(*p_ascb_addr)); + if (!next_ascb.is_null()) { + ascbs.push_back(next_ascb); + } p_ascb_addr++; // This SHOULD increment the pointer by 4 bytes. } return ascbs; @@ -52,11 +57,19 @@ nlohmann::json ASCB::get(void* __ptr32 p_control_block) { ascb_json["ascbassb"] = formatter_.getHex(&(p_ascb->ascbassb)); ascb_json["ascbasxb"] = formatter_.getHex(&(p_ascb->ascbasxb)); + ascb_json["ascboucb"] = formatter_.getHex(&(p_ascb->ascboucb)); - for (const auto& [include, include_includes] : include_map_) { + for (const auto& [include, cbxp_options] : options_map_) { if (include == "assb") { - ascb_json["ascbassb"] = - CBXP::ASSB(include_includes).get(p_ascb->ascbassb); + ascb_json["ascbassb"] = CBXP::ASSB(cbxp_options).get(p_ascb->ascbassb); + if (ascb_json["ascbassb"].is_null()) { + return {}; + } + } else if (include == "oucb") { + ascb_json["ascboucb"] = CBXP::OUCB(cbxp_options).get(p_ascb->ascboucb); + if (ascb_json["ascboucb"].is_null()) { + return {}; + } } } @@ -77,7 +90,6 @@ nlohmann::json ASCB::get(void* __ptr32 p_control_block) { ascb_json["ascblsqe"] = p_ascb->ascblsqe; ascb_json["ascblsqt"] = p_ascb->ascblsqt; ascb_json["ascbnoft"] = formatter_.getBitmap(p_ascb->ascbnoft); - ascb_json["ascboucb"] = formatter_.getHex(&(p_ascb->ascboucb)); ascb_json["ascbouxb"] = formatter_.getHex(&(p_ascb->ascbouxb)); ascb_json["ascbpo1m"] = formatter_.getBitmap(p_ascb->ascbpo1m); ascb_json["ascbp1m0"] = formatter_.getBitmap(p_ascb->ascbp1m0); @@ -90,6 +102,10 @@ nlohmann::json ASCB::get(void* __ptr32 p_control_block) { ascb_json["ascbxtcb"] = formatter_.getHex(&(p_ascb->ascbxtcb)); ascb_json["ascbzcx"] = formatter_.getBitmap(p_ascb->ascbzcx); - return ascb_json; + if (ASCB::matchFilter(ascb_json)) { + return ascb_json; + } else { + return {}; + } } } // namespace CBXP diff --git a/cbxp/control_blocks/ascb.hpp b/cbxp/control_blocks/ascb.hpp index e811530..c892d48 100644 --- a/cbxp/control_blocks/ascb.hpp +++ b/cbxp/control_blocks/ascb.hpp @@ -8,8 +8,8 @@ namespace CBXP { class ASCB : public ControlBlock { public: nlohmann::json get(void* __ptr32 p_control_block = nullptr) override; - explicit ASCB(const std::vector& includes) - : ControlBlock("ascb", {"assb"}, includes) {} + explicit ASCB(const cbxp_options_t& cbxp_options) + : ControlBlock("ascb", {"assb", "oucb"}, cbxp_options) {} }; } // namespace CBXP diff --git a/cbxp/control_blocks/assb.cpp b/cbxp/control_blocks/assb.cpp index 3a12daf..c41eb3a 100644 --- a/cbxp/control_blocks/assb.cpp +++ b/cbxp/control_blocks/assb.cpp @@ -46,8 +46,11 @@ nlohmann::json ASSB::get(void* __ptr32 p_control_block) { // const struct ascb* __ptr32 p_ascb = reinterpret_cast(*p_ascb_addr); - assbs.push_back( - ASSB::get(reinterpret_cast(p_ascb->ascbassb))); + nlohmann::json next_assb = + ASSB::get(reinterpret_cast(p_ascb->ascbassb)); + if (!next_assb.is_null()) { + assbs.push_back(next_assb); + } p_ascb_addr++; // This SHOULD increment the pointer by 4 bytes. } return assbs; @@ -195,6 +198,10 @@ nlohmann::json ASSB::get(void* __ptr32 p_control_block) { formatter_.uint(p_assb->assbinitiatorjobid); assb_json["assbend"] = formatter_.uint(p_assb->assbend); - return assb_json; + if (ASSB::matchFilter(assb_json)) { + return assb_json; + } else { + return {}; + } } } // namespace CBXP diff --git a/cbxp/control_blocks/assb.hpp b/cbxp/control_blocks/assb.hpp index 6692b2f..5046d8f 100644 --- a/cbxp/control_blocks/assb.hpp +++ b/cbxp/control_blocks/assb.hpp @@ -8,8 +8,8 @@ namespace CBXP { class ASSB : public ControlBlock { public: nlohmann::json get(void* __ptr32 p_control_block = nullptr) override; - explicit ASSB(const std::vector& includes) - : ControlBlock("assb", {}, includes) {} + explicit ASSB(const cbxp_options_t& cbxp_options) + : ControlBlock("assb", {}, cbxp_options) {} }; } // namespace CBXP diff --git a/cbxp/control_blocks/asvt.cpp b/cbxp/control_blocks/asvt.cpp index 17ad32f..a14d31c 100644 --- a/cbxp/control_blocks/asvt.cpp +++ b/cbxp/control_blocks/asvt.cpp @@ -9,7 +9,6 @@ #include #include "ascb.hpp" -#include "asvt.hpp" #include "logger.hpp" namespace CBXP { @@ -47,10 +46,10 @@ nlohmann::json ASVT::get(void* __ptr32 p_control_block) { Logger::getInstance().debug("ASVT hex dump:"); Logger::getInstance().hexDump(reinterpret_cast(p_asvt), sizeof(asvt_t)); - for (const auto& [include, include_includes] : include_map_) { + for (const auto& [include, cbxp_options] : options_map_) { if (include == "ascb") { nlohmann::json ascbs_json; - CBXP::ASCB ascb(include_includes); + CBXP::ASCB ascb(cbxp_options); uint32_t* __ptr32 p_ascb_addr = const_cast( reinterpret_cast(&p_asvt->asvtenty)); for (int i = 0; i < p_asvt->asvtmaxu; i++) { @@ -62,10 +61,15 @@ nlohmann::json ASVT::get(void* __ptr32 p_control_block) { } nlohmann::json ascb_json = ascb.get(reinterpret_cast(*p_ascb_addr)); - ascbs_json.push_back(ascb_json); + if (!ascb_json.is_null()) { + ascbs_json.push_back(ascb_json); + } p_ascb_addr++; // This SHOULD increment the pointer by 4 bytes. } asvt_json["asvtenty"] = ascbs_json; + if (asvt_json["asvtenty"].is_null()) { + return {}; + } } } @@ -85,6 +89,10 @@ nlohmann::json ASVT::get(void* __ptr32 p_control_block) { asvt_json["asvtmdsc"] = p_asvt->asvtmdsc; asvt_json["asvtfrst"] = formatter_.getHex(p_asvt->asvtfrst); - return asvt_json; + if (ASVT::matchFilter(asvt_json)) { + return asvt_json; + } else { + return {}; + } } } // namespace CBXP diff --git a/cbxp/control_blocks/asvt.hpp b/cbxp/control_blocks/asvt.hpp index a6e91c9..15268bb 100644 --- a/cbxp/control_blocks/asvt.hpp +++ b/cbxp/control_blocks/asvt.hpp @@ -33,8 +33,8 @@ namespace CBXP { class ASVT : public ControlBlock { public: nlohmann::json get(void* __ptr32 p_control_block = nullptr) override; - explicit ASVT(const std::vector& includes) - : ControlBlock("asvt", {"ascb"}, includes) {} + explicit ASVT(const cbxp_options_t& cbxp_options) + : ControlBlock("asvt", {"ascb"}, cbxp_options) {} }; } // namespace CBXP diff --git a/cbxp/control_blocks/control_block.cpp b/cbxp/control_blocks/control_block.cpp index 02616ab..43c397d 100644 --- a/cbxp/control_blocks/control_block.cpp +++ b/cbxp/control_blocks/control_block.cpp @@ -1,49 +1,75 @@ #include "control_block.hpp" +#include + #include #include "control_block_error.hpp" #include "logger.hpp" namespace CBXP { -void ControlBlock::createIncludeMap(const std::vector& includes) { - Logger::getInstance().debug("Creating include map for the '" + - control_block_name_ + "' control block..."); +void ControlBlock::createOptionsMap(const std::vector& includes, + const std::vector& filters) { + // createFilterLists depends on the construction of the "includes" portion + // of the options_map_ structure and must be called after createIncludeLists + ControlBlock::createIncludeLists(includes); + ControlBlock::createFilterLists(filters); +} + +void ControlBlock::createIncludeLists( + const std::vector& includes) { + Logger::getInstance().debug( + "Creating include lists for child control blocks to include with the '" + + control_block_name_ + "' control block..."); for (std::string include : includes) { if (include == "**") { + Logger::getInstance().debug("Processing '**' include..."); ControlBlock::processDoubleAsteriskInclude(); return; } else if (include == "*") { + Logger::getInstance().debug("Processing '*' include..."); ControlBlock::processAsteriskInclude(); } else { + Logger::getInstance().debug("Processing '" + include + "' include..."); ControlBlock::processExplicitInclude(include); } } - Logger::getInstance().debug("Done"); + Logger::getInstance().debug( + "Include lists for child control blocks to include with the '" + + control_block_name_ + "' control block have been created"); } void ControlBlock::processDoubleAsteriskInclude() { // Any existing entries in the hash map are redundant, so clear them - include_map_.clear(); + options_map_.clear(); for (const std::string& includable : includables_) { // Build a map of all includables_ but with "**" at the next level - include_map_[includable] = {"**"}; + Logger::getInstance().debug( + "Initializing and adding '**' to the include list for the '" + + includable + "' control block..."); + options_map_[includable].include_patterns = {"**"}; } } void ControlBlock::processAsteriskInclude() { - if (include_map_.empty()) { + if (options_map_.empty()) { for (const std::string& includable : includables_) { // Build a map of all includables_ - include_map_[includable] = {}; + Logger::getInstance().debug("Initializing include list for the '" + + includable + "' control block..."); + options_map_[includable].include_patterns = {}; } } for (const std::string& includable : includables_) { - if (include_map_.find(includable) != include_map_.end()) { + if (options_map_.find(includable) != options_map_.end()) { + Logger::getInstance().debug("Include list already exists for the '" + + includable + "' control block"); continue; } // Add all includables_ not already present to the map - include_map_[includable] = {}; + Logger::getInstance().debug("Initializing include list for the '" + + includable + "' control block..."); + options_map_[includable].include_patterns = {}; } } @@ -60,31 +86,197 @@ void ControlBlock::processExplicitInclude(std::string& include) { } if (std::find(includables_.begin(), includables_.end(), include) == includables_.end()) { - Logger::getInstance().debug("'" + include + - "' is not a known includable for the '" + - control_block_name_ + "' control block"); + Logger::getInstance().debug( + "'" + include + + "' is not a known child control block that can be included with the '" + + control_block_name_ + "' control block"); throw IncludeError(); } - if (include_map_.find(include) == include_map_.end()) { + if (options_map_.find(include) == options_map_.end()) { // If we don't already have this include in our map, add it with its // includes if (include_includes == "") { - include_map_[include] = {}; + Logger::getInstance().debug("Initializing include list for the '" + + include + "' control block..."); + options_map_[include].include_patterns = {}; } else { - include_map_[include] = {include_includes}; + Logger::getInstance().debug("Adding '" + include_includes + + "' to the include list for the '" + include + + "' control block..."); + options_map_[include].include_patterns = {include_includes}; } } else { // If we DO already have this in our map, then we should add its // includes if they are useful or new - if (std::find(include_map_[include].begin(), include_map_[include].end(), - include_includes) != include_map_[include].end()) { + if (std::find(options_map_[include].include_patterns.begin(), + options_map_[include].include_patterns.end(), + include_includes) != + options_map_[include].include_patterns.end()) { return; } if (include_includes == "") { return; } - include_map_[include].push_back(include_includes); + Logger::getInstance().debug("Adding '" + include_includes + + "' to the include list for the '" + include + + "' control block..."); + options_map_[include].include_patterns.push_back(include_includes); + } +} + +void ControlBlock::createFilterLists(const std::vector& filters) { + Logger::getInstance().debug( + "Creating filter lists for the '" + control_block_name_ + + "' control block and included child control blocks..."); + for (const std::string& filter : filters) { + // Only case; specific non-generic filter + const std::string del = "."; + size_t del_pos = filter.find(del); + if (del_pos != std::string::npos) { + // If there's a "." then separate filter into the control_block + // and its filter + std::string control_block_filter = filter.substr(del_pos + 1); + std::string control_block = filter.substr(0, del_pos); + + // Check to make sure we are including the specified control block + auto it = options_map_.find(control_block); + if (it == options_map_.end()) { + Logger::getInstance().debug( + "A filter that requires the '" + control_block + + "' control block was provided, but the '" + control_block + + "' control block was not included"); + throw FilterError(); + } + Logger::getInstance().debug("Adding '" + control_block_filter + + "' to the filter list for the '" + + control_block + "' control block..."); + options_map_[control_block].filters.push_back(control_block_filter); + } else { + ControlBlock::addCurrentFilter(filter); + } + } + Logger::getInstance().debug( + "Filter lists for the '" + control_block_name_ + + "' control block and included child control blocks have been created"); +} + +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); + if (filter_value == "") { + Logger::getInstance().debug("Filter values cannot be null"); + throw FilterError(); + } + cbxp_filter_t filter_data = {operation, filter_value}; + Logger::getInstance().debug("Adding '" + filter_key + operation + + filter_value + + "' to the current filters list for the '" + + control_block_name_ + "' control block..."); + current_filters_[filter_key].push_back(filter_data); + return; + } + } +} + +bool ControlBlock::compare(const nlohmann::json& json_value, + const std::string& filter_value, + const std::string& operation) { + std::string value_str = ""; + bool value_is_string = false; + uint64_t value_uint; + + if (json_value.is_number()) { + value_uint = json_value.get(); + } else { + value_str = json_value.get(); + value_is_string = true; + if (value_str.substr(0, 2) == "0x") { + value_uint = std::stoull(value_str, nullptr, 0); + value_is_string = false; + } + } + if (value_is_string) { + // Filter is testing strings + if (operation == "=") { + Logger::getInstance().debug("\"" + value_str + "\" = \"" + filter_value + + "\" ?"); + return (fnmatch(filter_value.c_str(), value_str.c_str(), 0) == 0); + } else { + Logger::getInstance().debug( + "<, <=, >, and >= cannot be used with string filter values"); + throw FilterError(); + } + } // Filter is testing non-strings + else { + uint64_t filter_uint; + try { + filter_uint = std::stoull(filter_value, nullptr, 0); + } catch (...) { + Logger::getInstance().debug("'" + filter_value + + "' cannot be compared to a numeric value"); + throw FilterError(); + } + Logger::getInstance().debug(std::to_string(value_uint) + " " + operation + + " " + std::to_string(filter_uint) + " ?"); + if (operation == "=") { + return value_uint == filter_uint; + } else if (operation == ">") { + return value_uint > filter_uint; + } else if (operation == "<") { + return value_uint < filter_uint; + } else if (operation == ">=") { + return value_uint >= filter_uint; + } else if (operation == "<=") { + return value_uint <= filter_uint; + } + } + // We should never get here, so it would be good to say "no match" just in + // case + return false; +} + +bool ControlBlock::matchFilter(nlohmann::json& control_block_json) { + Logger::getInstance().debug("Applying filters to the '" + + control_block_name_ + "' control block..."); + if (current_filters_.empty()) { + // If the filter map is empty then we want to return the control block + Logger::getInstance().debug("No filters were provided for the '" + + control_block_name_ + "' control block"); + return true; + } + for (const auto& [filter_key, filter_list] : current_filters_) { + if (!control_block_json.contains(filter_key)) { + Logger::getInstance().debug( + "The filter key '" + filter_key + + "' does not correspond to any control block field in the '" + + control_block_name_ + "' control block"); + throw FilterError(); + } + // cppcheck-suppress useStlAlgorithm + for (const cbxp_filter_t& filter_data : filter_list) { + // would require capturing structured bindings to use all_of or none_of + Logger::getInstance().debug("Applying filter '" + filter_key + + filter_data.operation + filter_data.value + + "'..."); + if (!ControlBlock::compare(control_block_json[filter_key], + filter_data.value, filter_data.operation)) { + Logger::getInstance().debug("The filter '" + filter_key + + filter_data.operation + filter_data.value + + "' did not match"); + return false; + } + } } + // If we didn't have a reason to return false, we return true + Logger::getInstance().debug("All filters for the '" + control_block_name_ + + "' control block matched"); + return true; } } // namespace CBXP diff --git a/cbxp/control_blocks/control_block.hpp b/cbxp/control_blocks/control_block.hpp index dcbcb3b..1ec9095 100644 --- a/cbxp/control_blocks/control_block.hpp +++ b/cbxp/control_blocks/control_block.hpp @@ -7,26 +7,44 @@ namespace CBXP { +typedef struct { + std::string operation; + std::string value; +} cbxp_filter_t; + +typedef struct { + std::vector include_patterns; + std::vector filters; +} cbxp_options_t; + class ControlBlock { private: const std::string control_block_name_; const std::vector includables_; + void createIncludeLists(const std::vector& includes); void processDoubleAsteriskInclude(); void processAsteriskInclude(); void processExplicitInclude(std::string& include); + void createFilterLists(const std::vector& filters); + void addCurrentFilter(const std::string& filter); + bool compare(const nlohmann::json& json_value, + const std::string& filter_value, const std::string& operation); protected: ControlBlockFieldFormatter formatter_; - std::unordered_map> include_map_; + std::unordered_map options_map_; + std::unordered_map> current_filters_; + void createOptionsMap(const std::vector& includes, + const std::vector& filters); + bool matchFilter(nlohmann::json& control_block_json); public: - void createIncludeMap(const std::vector& includes); virtual nlohmann::json get(void* __ptr32 p_control_block = nullptr) = 0; explicit ControlBlock(const std::string& name, const std::vector& includables, - const std::vector& includes) + const cbxp_options_t& cbxp_options) : control_block_name_(name), includables_(includables) { - createIncludeMap(includes); + createOptionsMap(cbxp_options.include_patterns, cbxp_options.filters); } virtual ~ControlBlock() = default; }; diff --git a/cbxp/control_blocks/control_block_field_formatter.hpp b/cbxp/control_blocks/control_block_field_formatter.hpp index 7cf499b..cc1d05a 100644 --- a/cbxp/control_blocks/control_block_field_formatter.hpp +++ b/cbxp/control_blocks/control_block_field_formatter.hpp @@ -19,10 +19,13 @@ class ControlBlockFieldFormatter { } static const std::string getString(const void* p_field, int length) { - auto ascii_field_unique_ptr = std::make_unique(length); - std::memcpy(ascii_field_unique_ptr.get(), p_field, length); - __e2a_l(ascii_field_unique_ptr.get(), length); - return ascii_field_unique_ptr.get(); + std::vector ascii_field_tmp(length, 0); + std::memcpy(ascii_field_tmp.data(), p_field, length); + __e2a_l(ascii_field_tmp.data(), length); + std::string ascii_field(ascii_field_tmp.begin(), ascii_field_tmp.end()); + size_t last_non_space = ascii_field.find_last_not_of(" \t\n\r\f\v"); + ascii_field.resize(last_non_space + 1); + return ascii_field; } template static const std::string getHex(const void* p_field) { diff --git a/cbxp/control_blocks/cvt.cpp b/cbxp/control_blocks/cvt.cpp index 12107ad..d679270 100644 --- a/cbxp/control_blocks/cvt.cpp +++ b/cbxp/control_blocks/cvt.cpp @@ -43,11 +43,17 @@ nlohmann::json CVT::get(void* __ptr32 p_control_block) { cvt_json["cvtasvt"] = formatter_.getHex(&p_cvtmap->cvtasvt); cvt_json["cvtecvt"] = formatter_.getHex(&p_cvtmap->cvtecvt); - for (const auto& [include, include_includes] : include_map_) { + for (const auto& [include, cbxp_options] : options_map_) { if (include == "asvt") { - cvt_json["cvtasvt"] = CBXP::ASVT(include_includes).get(p_cvtmap->cvtasvt); + cvt_json["cvtasvt"] = CBXP::ASVT(cbxp_options).get(p_cvtmap->cvtasvt); + if (cvt_json["cvtasvt"].is_null()) { + return {}; + } } else if (include == "ecvt") { - cvt_json["cvtecvt"] = CBXP::ECVT(include_includes).get(p_cvtmap->cvtecvt); + cvt_json["cvtecvt"] = CBXP::ECVT(cbxp_options).get(p_cvtmap->cvtecvt); + if (cvt_json["cvtecvt"].is_null()) { + return {}; + } } } @@ -139,6 +145,10 @@ nlohmann::json CVT::get(void* __ptr32 p_control_block) { cvt_json["cvt0pt03"] = formatter_.getHex(p_cvtmap->cvt0pt03); cvt_json["cvt0scr1"] = formatter_.getHex(p_cvtmap->cvt0scr1); - return cvt_json; + if (CVT::matchFilter(cvt_json)) { + return cvt_json; + } else { + return {}; + } } } // namespace CBXP diff --git a/cbxp/control_blocks/cvt.hpp b/cbxp/control_blocks/cvt.hpp index 3111cda..e65e3c7 100644 --- a/cbxp/control_blocks/cvt.hpp +++ b/cbxp/control_blocks/cvt.hpp @@ -8,8 +8,8 @@ namespace CBXP { class CVT : public ControlBlock { public: nlohmann::json get(void* __ptr32 p_control_block = nullptr) override; - explicit CVT(const std::vector& includes) - : ControlBlock("cvt", {"ecvt", "asvt"}, includes) {} + explicit CVT(const cbxp_options_t& cbxp_options) + : ControlBlock("cvt", {"ecvt", "asvt"}, cbxp_options) {} }; } // namespace CBXP diff --git a/cbxp/control_blocks/ecvt.cpp b/cbxp/control_blocks/ecvt.cpp index e577624..0142304 100644 --- a/cbxp/control_blocks/ecvt.cpp +++ b/cbxp/control_blocks/ecvt.cpp @@ -176,6 +176,10 @@ nlohmann::json ECVT::get(void* __ptr32 p_control_block) { reinterpret_cast(&p_ecvt->ecvtvser)); ecvt_json["ecvtxtsw"] = formatter_.getHex(p_ecvt->ecvtxtsw); - return ecvt_json; + if (ECVT::matchFilter(ecvt_json)) { + return ecvt_json; + } else { + return {}; + } } } // namespace CBXP diff --git a/cbxp/control_blocks/ecvt.hpp b/cbxp/control_blocks/ecvt.hpp index 9cece25..ff452b3 100644 --- a/cbxp/control_blocks/ecvt.hpp +++ b/cbxp/control_blocks/ecvt.hpp @@ -8,8 +8,8 @@ namespace CBXP { class ECVT : public ControlBlock { public: nlohmann::json get(void* __ptr32 p_control_block = nullptr) override; - explicit ECVT(const std::vector& includes) - : ControlBlock("ecvt", {}, includes) {} + explicit ECVT(const cbxp_options_t& cbxp_options) + : ControlBlock("ecvt", {}, cbxp_options) {} }; } // namespace CBXP #endif diff --git a/cbxp/control_blocks/oucb.cpp b/cbxp/control_blocks/oucb.cpp new file mode 100644 index 0000000..ed5e997 --- /dev/null +++ b/cbxp/control_blocks/oucb.cpp @@ -0,0 +1,169 @@ +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "asvt.hpp" +#include "logger.hpp" + +namespace CBXP { +nlohmann::json OUCB::get(void* __ptr32 p_control_block) { + const oucb_t* __ptr32 p_oucb; + nlohmann::json oucb_json = {}; + if (p_control_block == nullptr) { + // PSA starts at address 0 + const struct psa* __ptr32 p_psa = 0; + + 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); + const asvt_t* __ptr32 p_asvt = + static_cast(p_cvtmap->cvtasvt); + + oucb_json["oucbs"] = std::vector(); + std::vector& oucbs = + oucb_json["oucbs"].get_ref&>(); + oucbs.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 addr into ascb pointer a + // + const struct ascb* __ptr32 p_ascb = + reinterpret_cast(*p_ascb_addr); + nlohmann::json next_oucb = + OUCB::get(reinterpret_cast(p_ascb->ascboucb)); + if (!next_oucb.is_null()) { + oucbs.push_back(next_oucb); + } + p_ascb_addr++; // This SHOULD increment the pointer by 4 bytes. + } + return oucbs; + } else { + p_oucb = static_cast(p_control_block); + } + + Logger::getInstance().debug("oucb hex dump:"); + Logger::getInstance().hexDump(reinterpret_cast(p_oucb), + sizeof(oucb_t)); + + oucb_json["oucbname"] = formatter_.getString(p_oucb->oucbname, 4); + oucb_json["oucbfwd"] = formatter_.getHex(&(p_oucb->oucbfwd)); + oucb_json["oucbbck"] = formatter_.getHex(&(p_oucb->oucbbck)); + oucb_json["oucbtma"] = p_oucb->oucbtma; + oucb_json["oucbqfl"] = formatter_.getBitmap(p_oucb->oucbqfl); + oucb_json["oucbsfl"] = formatter_.getBitmap(p_oucb->oucbsfl); + oucb_json["oucbyfl"] = formatter_.getBitmap(p_oucb->oucbyfl); + oucb_json["oucbafl"] = formatter_.getBitmap(p_oucb->oucbafl); + oucb_json["oucbtfl"] = formatter_.getBitmap(p_oucb->oucbtfl); + oucb_json["oucbefl"] = formatter_.getBitmap(p_oucb->oucbefl); + oucb_json["oucbasstatus"] = + formatter_.getBitmap(p_oucb->oucbasstatus); + oucb_json["oucbufl"] = formatter_.getBitmap(p_oucb->oucbufl); + oucb_json["oucblfl"] = formatter_.getBitmap(p_oucb->oucblfl); + oucb_json["oucbrfl"] = formatter_.getBitmap(p_oucb->oucbrfl); + oucb_json["oucbndp"] = formatter_.getBitmap(p_oucb->oucbndp); + oucb_json["oucbtndp"] = formatter_.getBitmap(p_oucb->oucbtndp); + oucb_json["oucbmfl"] = formatter_.getBitmap(p_oucb->oucbmfl); + oucb_json["oucbiac"] = p_oucb->oucbiac; + oucb_json["oucbrsv1"] = p_oucb->oucbrsv1; + oucb_json["oucbpgp"] = p_oucb->oucbpgp; + oucb_json["oucbwsci"] = p_oucb->oucbwsci; + oucb_json["oucbwrci"] = p_oucb->oucbwrci; + oucb_json["oucbmfl2"] = formatter_.getBitmap(p_oucb->oucbmfl2); + oucb_json["oucbmfl3"] = formatter_.getBitmap(p_oucb->oucbmfl3); + oucb_json["oucbdmo"] = p_oucb->oucbdmo; + oucb_json["oucbdmn"] = p_oucb->oucbdmn; + oucb_json["oucbsrc"] = p_oucb->oucbsrc; + oucb_json["oucbswc"] = p_oucb->oucbswc; + oucb_json["oucbascb"] = formatter_.getHex(&(p_oucb->oucbascb)); + oucb_json["oucbpagp"] = formatter_.getHex(&(p_oucb->oucbpagp)); + oucb_json["oucbtmw"] = p_oucb->oucbtmw; + oucb_json["oucbwms"] = p_oucb->oucbwms; + oucb_json["oucbcpu"] = p_oucb->oucbcpu; + oucb_json["oucbioc"] = p_oucb->oucbioc; + oucb_json["oucbmso"] = p_oucb->oucbmso; + oucb_json["oucbtms"] = p_oucb->oucbtms; + oucb_json["oucbtmo"] = p_oucb->oucbtmo; + oucb_json["oucbdrfr"] = p_oucb->oucbdrfr; + oucb_json["oucbact"] = formatter_.getHex(&(p_oucb->oucbact)); + // Union + oucb_json["oucbcsw"]["word"] = p_oucb->oucbcsw.word; + oucb_json["oucbcsw"]["oucbacn"] = + formatter_.getBitmap(p_oucb->oucbcsw.fields.oucbacn); + oucb_json["oucbcsw"]["oucbcfl"] = + formatter_.getBitmap(p_oucb->oucbcsw.fields.oucbcfl); + oucb_json["oucbcsw"]["oucbcsbt"] = + formatter_.getBitmap(p_oucb->oucbcsw.fields.oucbcsbt); + + oucb_json["oucbcmrv"] = p_oucb->oucbcmrv; + oucb_json["oucbwmrl"] = p_oucb->oucbwmrl; + oucb_json["oucbval"] = p_oucb->oucbval; + oucb_json["oucbpfl"] = formatter_.getBitmap(p_oucb->oucbpfl); + oucb_json["oucbactl"] = p_oucb->oucbactl; + oucb_json["oucbiocl"] = p_oucb->oucbiocl; + oucb_json["oucbdspc"] = formatter_.getBitmap(p_oucb->oucbdspc); + oucb_json["oucbdspn"] = formatter_.getBitmap(p_oucb->oucbdspn); + oucb_json["oucbntsp"] = p_oucb->oucbntsp; + oucb_json["oucbps1"] = p_oucb->oucbps1; + oucb_json["oucbps2"] = p_oucb->oucbps2; + oucb_json["oucbpst"] = p_oucb->oucbpst; + oucb_json["oucbrct"] = p_oucb->oucbrct; + oucb_json["oucbiit"] = p_oucb->oucbiit; + oucb_json["oucbnds"] = p_oucb->oucbnds; + oucb_json["oucbntsg"] = formatter_.getBitmap(p_oucb->oucbntsg); + oucb_json["oucbrsv2"] = p_oucb->oucbrsv2; + oucb_json["oucbtme"] = p_oucb->oucbtme; + oucb_json["oucbtml"] = p_oucb->oucbtml; + oucb_json["oucbdwms"] = p_oucb->oucbdwms; + oucb_json["oucbsrb"] = p_oucb->oucbsrb; + oucb_json["oucbtwss"] = p_oucb->oucbtwss; + oucb_json["oucbtmp"] = p_oucb->oucbtmp; + oucb_json["oucbdlyt"] = p_oucb->oucbdlyt; + oucb_json["oucbhst"] = p_oucb->oucbhst; + oucb_json["oucbcfs"] = p_oucb->oucbcfs; + oucb_json["oucbsubn"] = formatter_.getString(p_oucb->oucbsubn, 4); + oucb_json["oucbrpg"] = p_oucb->oucbrpg; + oucb_json["oucbspg"] = p_oucb->oucbspg; + oucb_json["oucbnpg"] = p_oucb->oucbnpg; + oucb_json["oucbsrpg"] = p_oucb->oucbsrpg; + oucb_json["oucbnrpg"] = p_oucb->oucbnrpg; + oucb_json["oucburpg"] = p_oucb->oucburpg; + oucb_json["oucbcrpg"] = p_oucb->oucbcrpg; + oucb_json["oucbarpg"] = p_oucb->oucbarpg; + oucb_json["oucbdrfp"] = p_oucb->oucbdrfp; + oucb_json["oucbtrxn"] = formatter_.getString(p_oucb->oucbtrxn, 8); + oucb_json["oucbusrd"] = formatter_.getString(p_oucb->oucbusrd, 8); + oucb_json["oucbcls"] = formatter_.getString(p_oucb->oucbcls, 8); + oucb_json["oucbtrs"] = p_oucb->oucbtrs; + oucb_json["oucbtrr"] = p_oucb->oucbtrr; + oucb_json["oucbactp"] = p_oucb->oucbactp; + oucb_json["oucbswss"] = p_oucb->oucbswss; + oucb_json["oucbpsum"] = p_oucb->oucbpsum; + oucb_json["oucbfixb"] = p_oucb->oucbfixb; + oucb_json["oucbaplv"] = formatter_.getBitmap(p_oucb->oucbaplv); + oucb_json["oucbesap"] = formatter_.getBitmap(p_oucb->oucbesap); + oucb_json["oucbrst1"] = p_oucb->oucbrst1; + oucb_json["oucbrst2"] = p_oucb->oucbrst2; + oucb_json["oucbx1_0"] = p_oucb->oucbx1_0; + + if (OUCB::matchFilter(oucb_json)) { + return oucb_json; + } else { + return {}; + } +} +} // namespace CBXP diff --git a/cbxp/control_blocks/oucb.hpp b/cbxp/control_blocks/oucb.hpp new file mode 100644 index 0000000..48c383c --- /dev/null +++ b/cbxp/control_blocks/oucb.hpp @@ -0,0 +1,122 @@ +#ifndef __OUCB_H_ +#define __OUCB_H_ + +#include "control_block.hpp" + +#pragma pack(push, 1) // Don't byte align structure members. +typedef struct { + char oucbname[4]; + char* __ptr32 oucbfwd; + char* __ptr32 oucbbck; + int32_t oucbtma; + uint8_t oucbqfl; + uint8_t oucbsfl; + uint8_t oucbyfl; + uint8_t oucbafl; + uint8_t oucbtfl; + uint8_t oucbefl; + uint8_t oucbasstatus; + uint8_t oucbufl; + uint8_t oucblfl; + uint8_t oucbrfl; + uint8_t oucbndp; + uint8_t oucbtndp; + uint8_t oucbmfl; + int8_t oucbiac; + int8_t oucbrsv1; + int8_t oucbpgp; + // int8_t oucbwmg; //need to bring up with team + int16_t oucbwsci; + int16_t oucbwrci; + uint8_t oucbmfl2; + uint8_t oucbmfl3; + int16_t oucbdmo; + int8_t oucbdmn; + int8_t oucbsrc; + int16_t oucbswc; + char* __ptr32 oucbascb; + char* __ptr32 oucbpagp; + int32_t oucbtmw; + int32_t oucbwms; + int32_t oucbcpu; + int32_t oucbioc; + int32_t oucbmso; + int32_t oucbtms; + int32_t oucbtmo; + int32_t oucbdrfr; + char* __ptr32 oucbact; + // int32_t oucbcsw; // speak to team + union { + uint32_t word; + struct { + uint16_t oucbacn; + uint8_t oucbcfl; + uint8_t oucbcsbt; + } fields; + } oucbcsw; + int32_t oucbcmrv; + int32_t oucbwmrl; + int16_t oucbval; + uint8_t oucbpfl; + int8_t oucbactl; + int64_t oucbiocl; + uint8_t oucbdspc; + uint8_t oucbdspn; + int16_t oucbntsp; + int32_t oucbps1; + int32_t oucbps2; + int32_t oucbpst; + // OUCBCHE2 + int32_t oucbrct; + int32_t oucbiit; + int16_t oucbnds; + uint8_t oucbntsg; + int8_t oucbrsv2; + int32_t oucbtme; + int32_t oucbtml; + int32_t oucbdwms; + int32_t oucbsrb; + int32_t oucbtwss; + int32_t oucbtmp; + int32_t oucbdlyt; + int32_t oucbhst; + int32_t oucbcfs; + char oucbsubn[4]; + int16_t oucbrpg; + int16_t oucbspg; + int16_t oucbnpg; + int16_t oucbsrpg; + int16_t oucbnrpg; + int16_t oucburpg; + int16_t oucbcrpg; + int16_t oucbarpg; + int32_t oucbdrfp; + char oucbtrxn[8]; + char oucbusrd[8]; + char oucbcls[8]; + int32_t oucbtrs; + int32_t oucbtrr; + int32_t oucbactp; + int32_t oucbswss; + int32_t oucbpsum; + int16_t oucbfixb; + uint8_t oucbaplv; + uint8_t oucbesap; + int32_t oucbrst1; + int32_t oucbrst2; + int32_t oucbx1_0; +} oucb_t; +#pragma pack(pop) // Restore default structure packing options. + +namespace CBXP { + +class OUCB : public ControlBlock { + public: + nlohmann::json get(void* __ptr32 p_control_block = nullptr) override; + explicit OUCB(const cbxp_options_t& cbxp_options) + : ControlBlock("oucb", {}, cbxp_options) {} +}; + +} // namespace CBXP + +#endif diff --git a/cbxp/control_blocks/psa.cpp b/cbxp/control_blocks/psa.cpp index 8feec8c..ce13391 100644 --- a/cbxp/control_blocks/psa.cpp +++ b/cbxp/control_blocks/psa.cpp @@ -28,9 +28,12 @@ nlohmann::json PSA::get(void* __ptr32 p_control_block) { psa_json["flccvt"] = formatter_.getHex(&p_psa->flccvt); - for (const auto& [include, include_includes] : include_map_) { + for (const auto& [include, cbxp_options] : options_map_) { if (include == "cvt") { - psa_json["flccvt"] = CBXP::CVT(include_includes).get(p_psa->flccvt); + psa_json["flccvt"] = CBXP::CVT(cbxp_options).get(p_psa->flccvt); + if (psa_json["flccvt"].is_null()) { + return {}; + } } } @@ -61,6 +64,10 @@ nlohmann::json PSA::get(void* __ptr32 p_control_block) { psa_json["psaval"] = formatter_.getBitmap(p_psa->psaval); psa_json["psaxcvt"] = formatter_.getHex(p_psa->psaxcvt); - return psa_json; + if (PSA::matchFilter(psa_json)) { + return psa_json; + } else { + return {}; + } } } // namespace CBXP diff --git a/cbxp/control_blocks/psa.hpp b/cbxp/control_blocks/psa.hpp index 7843b38..d53b53d 100644 --- a/cbxp/control_blocks/psa.hpp +++ b/cbxp/control_blocks/psa.hpp @@ -8,8 +8,8 @@ namespace CBXP { class PSA : public ControlBlock { public: nlohmann::json get(void* __ptr32 p_control_block = nullptr) override; - explicit PSA(const std::vector& includes) - : ControlBlock("psa", {"cvt"}, includes) {} + explicit PSA(const cbxp_options_t& cbxp_options) + : ControlBlock("psa", {"cvt"}, cbxp_options) {} }; } // namespace CBXP #endif diff --git a/cbxp/main.cpp b/cbxp/main.cpp index 19c6ea5..d9c4e9d 100644 --- a/cbxp/main.cpp +++ b/cbxp/main.cpp @@ -12,7 +12,10 @@ #include "control_block_error.hpp" typedef const cbxp_result_t* (*cbxp_t)(const char* control_block_name, - const char* includes_string, bool debug); + const char* includes_string, + const char* filters_string, bool debug); + +typedef const void (*cbxp_free_t)(const cbxp_result_t* cbxp_result, bool debug); static void show_usage(const char* argv[]); static void show_dll_errors(); @@ -28,6 +31,9 @@ static void show_usage(const char* argv[]) { << " -i, --include Include additional control " "blocks based on a pattern" << std::endl + << " -f, --filter Filter repeated control " + "block data" + << std::endl << " -v, --version Show version number" << std::endl << " -h, --help Show usage information" @@ -51,6 +57,11 @@ static void cleanup_and_exit(int exit_rc, void* lib_handle) { exit(exit_rc); } +bool check_for_comma(const std::string& string) { + return std::any_of(string.begin(), string.end(), + [](char c) { return c == ','; }); +} + int main(int argc, const char* argv[]) { // Load 'libcbxp.so' void* lib_handle = dlopen("libcbxp.so", RTLD_NOW); @@ -59,19 +70,27 @@ int main(int argc, const char* argv[]) { return -1; } + int exit_rc = -1; // Resolve symbol 'cbxp()' cbxp_t cbxp = reinterpret_cast(dlsym(lib_handle, "cbxp")); if (cbxp == nullptr) { show_dll_errors(); - cleanup_and_exit(-1, lib_handle); + cleanup_and_exit(exit_rc, lib_handle); + } + cbxp_free_t cbxp_free = + reinterpret_cast(dlsym(lib_handle, "cbxp_free")); + if (cbxp_free == nullptr) { + show_dll_errors(); + cleanup_and_exit(exit_rc, lib_handle); } bool debug = false; - std::string control_block_name = "", includes_string = ""; + std::string control_block_name = "", includes_string = "", + filters_string = ""; if (argc < 2) { show_usage(argv); - cleanup_and_exit(-1, lib_handle); + cleanup_and_exit(exit_rc, lib_handle); } if (argc == 2) { @@ -91,28 +110,46 @@ int main(int argc, const char* argv[]) { for (int i = 1; i < argc; i++) { std::string flag = argv[i]; if (flag == "-d" || flag == "--debug") { - debug = true; + if (!debug) { + debug = true; + } else { + show_usage(argv); + cleanup_and_exit(exit_rc, lib_handle); + } } else if (flag == "-i" || flag == "--include") { if (i + 1 >= argc - 1) { show_usage(argv); - cleanup_and_exit(-1, lib_handle); + cleanup_and_exit(exit_rc, lib_handle); } std::string include = std::string(argv[++i]); - bool has_comma = std::any_of(include.begin(), include.end(), - [](char c) { return c == ','; }); - if (has_comma) { + if (check_for_comma(include)) { std::cerr << "Include patterns cannot contain commas" << std::endl; - cleanup_and_exit(-1, lib_handle); + cleanup_and_exit(exit_rc, lib_handle); } if (includes_string == "") { includes_string = include; } else { includes_string += "," + include; } + } else if (flag == "-f" || flag == "--filter") { + if (i + 1 >= argc - 1) { + show_usage(argv); + cleanup_and_exit(exit_rc, lib_handle); + } + std::string filter = std::string(argv[++i]); + if (check_for_comma(filter)) { + std::cerr << "Filters cannot contain commas" << std::endl; + cleanup_and_exit(exit_rc, lib_handle); + } + if (filters_string == "") { + filters_string = filter; + } else { + filters_string += "," + filter; + } } else { if (i != argc - 1) { show_usage(argv); - cleanup_and_exit(-1, lib_handle); + cleanup_and_exit(exit_rc, lib_handle); } control_block_name = std::string(argv[i]); } @@ -120,24 +157,31 @@ int main(int argc, const char* argv[]) { if (control_block_name == "") { show_usage(argv); - cleanup_and_exit(-1, lib_handle); + cleanup_and_exit(exit_rc, lib_handle); } nlohmann::json control_block_json; const cbxp_result_t* cbxp_result = - cbxp(control_block_name.c_str(), includes_string.c_str(), debug); - - if (cbxp_result->return_code == CBXP::Error::BadControlBlock) { - std::cerr << "Unknown control block '" << control_block_name - << "' was specified." << std::endl; - cleanup_and_exit(-1, lib_handle); - } else if (cbxp_result->return_code == CBXP::Error::BadInclude) { - std::cerr << "A bad include pattern was provided" << std::endl; - cleanup_and_exit(-1, lib_handle); - } else { - std::cout << cbxp_result->result_json << std::endl; + cbxp(control_block_name.c_str(), includes_string.c_str(), + filters_string.c_str(), debug); + + 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; + exit_rc = 0; } - cleanup_and_exit(0, lib_handle); + cbxp_free(cbxp_result, debug); + cleanup_and_exit(exit_rc, lib_handle); } diff --git a/cbxp/python/_cbxp.c b/cbxp/python/_cbxp.c index 9b167a0..11f5d65 100644 --- a/cbxp/python/_cbxp.c +++ b/cbxp/python/_cbxp.c @@ -1,46 +1,42 @@ #define PY_SSIZE_T_CLEAN #include -#include #include #include #include "cbxp.h" #include "cbxp_result.h" -pthread_mutex_t cbxp_mutex = PTHREAD_MUTEX_INITIALIZER; - // Entry point to the call_cbxp() function static PyObject* call_cbxp(PyObject* self, PyObject* args, PyObject* kwargs) { PyObject* result_dictionary; PyObject* debug_pyobj; - const char* control_block; - const char* includes_string; + const char* p_control_block; + const char* p_includes_string; + const char* p_filters_string; Py_ssize_t request_length; bool debug = false; - static char* kwlist[] = {"request", "include", "debug", NULL}; + static char* kwlist[] = {"control_block", "includes_string", "filters_string", + "debug", NULL}; - if (!PyArg_ParseTupleAndKeywords(args, kwargs, "s|sO", kwlist, &control_block, - &includes_string, &debug_pyobj)) { + if (!PyArg_ParseTupleAndKeywords(args, kwargs, "sss|O", kwlist, + &p_control_block, &p_includes_string, + &p_filters_string, &debug_pyobj)) { return NULL; } debug = PyObject_IsTrue(debug_pyobj); - // Since cbxp manages cbxp_result_t as a static structure, - // we need to use a mutex to make this thread safe. - // Technically we shouldn't need this because the Python GIL, - // but we will set this up anyways to be safe. - pthread_mutex_lock(&cbxp_mutex); - - const cbxp_result_t* result = cbxp(control_block, includes_string, debug); + cbxp_result_t* p_cbxp_result = + cbxp(p_control_block, p_includes_string, p_filters_string, debug); - result_dictionary = Py_BuildValue( - "{s:s#, s:i}", "result_json", result->result_json, - result->result_json_length, "return_code", result->return_code); + 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); - pthread_mutex_unlock(&cbxp_mutex); + cbxp_free(p_cbxp_result, debug); return result_dictionary; } diff --git a/python/cbxp/_C.pyi b/python/cbxp/_C.pyi index 7ab645e..1d518bd 100644 --- a/python/cbxp/_C.pyi +++ b/python/cbxp/_C.pyi @@ -1,5 +1,6 @@ def call_cbxp( # noqa: N999 control_block: str, includes_string: str, + filters_string: str, debug: bool = False, ) -> dict: ... diff --git a/python/cbxp/__init__.py b/python/cbxp/__init__.py index 448e3f8..4627129 100644 --- a/python/cbxp/__init__.py +++ b/python/cbxp/__init__.py @@ -1,2 +1,4 @@ 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 49e35e7..1868db9 100644 --- a/python/cbxp/cbxp.py +++ b/python/cbxp/cbxp.py @@ -4,12 +4,41 @@ from cbxp._C import call_cbxp +class CBXPFilterOperation(Enum): + """An enum of possible filter operations for the cbxp interface""" + + EQUAL = "=" + LESS_THAN = "<" + GREATER_THAN = ">" + LESS_THAN_OR_EQUAL = "<=" + GREATER_THAN_OR_EQUAL = ">=" + + +class CBXPFilter: + """A class to represent a filter to limit cbxp output based on set conditions""" + + def __init__( + self, + key: str, + operation: CBXPFilterOperation, + value: str | int, + ): + self.key = key + self.operation = operation.value + self.value = value + + def __str__(self): + return str(self.key) + str(self.operation) + str(self.value) + + class CBXPErrorCode(Enum): """An enum of error and return codes from the cbxp interface""" COMMA_IN_INCLUDE = -1 + COMMA_IN_FILTER = -2 BAD_CONTROL_BLOCK = 1 BAD_INCLUDE = 2 + BAD_CONTROL_BLOCK_FILTER = 3 class CBXPError(Exception): @@ -20,10 +49,14 @@ def __init__(self, return_code: int, control_block_name: str): match self.rc: case CBXPErrorCode.COMMA_IN_INCLUDE.value: message = "Include patterns cannot contain commas" + case CBXPErrorCode.COMMA_IN_FILTER.value: + message = "Filters cannot contain commas" case CBXPErrorCode.BAD_CONTROL_BLOCK.value: message = f"Unknown control block '{control_block_name}' was specified." case CBXPErrorCode.BAD_INCLUDE.value: message = "A bad include pattern was provided" + case CBXPErrorCode.BAD_CONTROL_BLOCK_FILTER.value: + message = "A bad filter was provided" case _: message = "an unknown error occurred" super().__init__(message) @@ -32,14 +65,35 @@ def __init__(self, return_code: int, control_block_name: str): def cbxp( control_block: str, includes: list[str] = None, + filters: list[CBXPFilter] = None, debug: bool = False, ) -> dict: + # Includes processing if includes is None: includes = [] for include in includes: if "," in include: raise CBXPError(CBXPErrorCode.COMMA_IN_INCLUDE.value, control_block) - response = call_cbxp(control_block.lower(), ",".join(includes).lower(), debug=debug) + + # Filter Processing + if filters is None: + filters = [] + filters_string = "" + for filter_obj in filters: + if filters_string != "": + filters_string += "," + if "," in str(filter_obj): + raise CBXPError(CBXPErrorCode.COMMA_IN_FILTER.value, control_block) + filters_string += str(filter_obj) + + response = call_cbxp( + control_block.lower(), + ",".join(includes), + filters_string, + 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/tests/test.py b/tests/test.py index 7d3351a..3114c17 100644 --- a/tests/test.py +++ b/tests/test.py @@ -1,6 +1,6 @@ import unittest -from cbxp import CBXPError, cbxp +from cbxp import CBXPError, CBXPFilter, CBXPFilterOperation, cbxp class TestCBXP(unittest.TestCase): @@ -34,6 +34,12 @@ def test_cbxp_can_extract_assb(self): self.assertIs(type(cbdata), list) for entry in cbdata: self.assertIs(type(entry), dict) + + def test_cbxp_can_extract_oucb(self): + cbdata = cbxp("oucb") + self.assertIs(type(cbdata), list) + for entry in cbdata: + self.assertIs(type(entry), dict) # ============================================================================ # Include Patterns @@ -69,6 +75,13 @@ def test_cbxp_can_extract_the_ascb_and_include_the_assb(self): 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"]) + 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"]) @@ -126,6 +139,19 @@ def test_cbxp_include_can_extract_psa_and_include_ecvt_asvt_and_cvt_asvt_ascb_as for entry in cbdata["flccvt"]["cvtasvt"]["asvtenty"]: 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( + self, + ): + cbdata = cbxp("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) + 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["ascboucb"]), dict) def test_cbxp_can_extract_psa_and_include_cvt_recursive_wildcard(self): cbdata = cbxp("psa", includes=["cvt.**"]) @@ -137,6 +163,7 @@ 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["ascboucb"]), dict) def test_cbxp_can_extract_psa_and_include_cvt_wildcard(self): cbdata = cbxp("psa", includes=["cvt.*"]) @@ -168,6 +195,234 @@ def test_cbxp_can_extract_cvt_and_include_wildcard_and_asvt_recursive_wildcard( for entry in cbdata["cvtasvt"]["asvtenty"]: self.assertIs(type(entry), dict) self.assertIs(type(entry["ascbassb"]), dict) + self.assertIs(type(entry["ascboucb"]), dict) + + # ============================================================================ + # Filters + # ============================================================================ + def test_cbxp_can_use_filter(self): + cbdata = cbxp( + "psa", + filters=[CBXPFilter("psapsa", CBXPFilterOperation.EQUAL, "PSA")], + ) + self.assertIs(type(cbdata), dict) + + def test_cbxp_can_use_filter_with_wildcard_include(self): + cbdata = cbxp( + "psa", + filters=[ + CBXPFilter( + "cvt.asvt.ascb.assb.assbjbns", + CBXPFilterOperation.EQUAL, + "*MASTER*", + ), + ], + includes=["**"], + ) + self.assertIs(type(cbdata), dict) + + def test_cbxp_can_use_filter_with_explicit_include(self): + cbdata = cbxp( + "psa", + filters=[ + CBXPFilter( + "cvt.asvt.ascb.assb.assbjbns", + CBXPFilterOperation.EQUAL, + "*MASTER*", + ), + ], + includes=["cvt.asvt.ascb.assb"], + ) + self.assertIs(type(cbdata), dict) + + def test_cbxp_can_use_multiple_filters(self): + cbdata = cbxp( + "psa", + filters=[ + CBXPFilter( + "cvt.asvt.ascb.assb.assbjbns", + CBXPFilterOperation.EQUAL, + "*MASTER*", + ), + CBXPFilter( + "cvt.asvt.ascb.ascbasid", + CBXPFilterOperation.GREATER_THAN, + 0, + ), + ], + includes=["cvt.asvt.ascb.assb"], + ) + self.assertIs(type(cbdata), dict) + + def test_cbxp_can_use_wildcard_filter_with_string(self): + cbdata = cbxp( + "psa", + filters=[ + CBXPFilter( + "cvt.asvt.ascb.assb.assbjbns", + CBXPFilterOperation.EQUAL, + "?MAS?ER?", + ), + ], + includes=["cvt.asvt.ascb.assb"], + ) + self.assertIs(type(cbdata), dict) + + def test_cbxp_can_use_int_filter_equal(self): + cbdata = cbxp( + "psa", + filters=[ + CBXPFilter("cvt.asvt.ascb.ascbasid", CBXPFilterOperation.EQUAL, 1), + ], + includes=["cvt.asvt.ascb.assb"], + ) + self.assertIs(type(cbdata), dict) + + def test_cbxp_can_use_int_filter_greater_than(self): + cbdata = cbxp( + "psa", + filters=[ + CBXPFilter( + "cvt.asvt.ascb.ascbasid", + CBXPFilterOperation.GREATER_THAN, + 0, + ), + ], + includes=["cvt.asvt.ascb.assb"], + ) + self.assertIs(type(cbdata), dict) + + def test_cbxp_can_use_int_filter_less_than(self): + cbdata = cbxp( + "psa", + filters=[ + CBXPFilter( + "cvt.asvt.ascb.ascbasid", + CBXPFilterOperation.LESS_THAN, + 2, + ), + ], + includes=["cvt.asvt.ascb.assb"], + ) + self.assertIs(type(cbdata), dict) + + def test_cbxp_can_use_int_filter_greater_than_or_equal(self): + cbdata = cbxp( + "psa", + filters=[ + CBXPFilter( + "cvt.asvt.ascb.ascbasid", + CBXPFilterOperation.GREATER_THAN_OR_EQUAL, + 1, + ), + ], + includes=["cvt.asvt.ascb.assb"], + ) + self.assertIs(type(cbdata), dict) + + def test_cbxp_can_use_int_filter_less_than_or_equal(self): + cbdata = cbxp( + "psa", + filters=[ + CBXPFilter( + "cvt.asvt.ascb.ascbasid", + CBXPFilterOperation.LESS_THAN_OR_EQUAL, + 2, + ), + ], + includes=["cvt.asvt.ascb.assb"], + ) + self.assertIs(type(cbdata), dict) + + def test_cbxp_can_use_int_filter_with_hex_field_equal(self): + cbdata = cbxp( + "cvt", + filters=[CBXPFilter("cvtasmvt", CBXPFilterOperation.EQUAL, 2281701376)], + ) + self.assertIs(type(cbdata), dict) + + def test_cbxp_can_use_hex_filter_with_equal(self): + cbdata = cbxp( + "cvt", + filters=[CBXPFilter("cvtasmvt", CBXPFilterOperation.EQUAL, "0x88000000")], + ) + self.assertIs(type(cbdata), dict) + + def test_cbxp_can_use_hex_filter_with_greater_than(self): + cbdata = cbxp( + "cvt", + filters=[ + CBXPFilter("cvtasmvt", CBXPFilterOperation.GREATER_THAN, "0x87FFFFFF"), + ], + ) + self.assertIs(type(cbdata), dict) + + def test_cbxp_can_use_hex_filter_with_less_than(self): + cbdata = cbxp( + "cvt", + filters=[ + CBXPFilter("cvtasmvt", CBXPFilterOperation.LESS_THAN, "0x88000001"), + ], + ) + self.assertIs(type(cbdata), dict) + + def test_cbxp_can_use_hex_filter_with_greater_than_or_equal(self): + cbdata = cbxp( + "cvt", + filters=[ + CBXPFilter( + "cvtasmvt", + CBXPFilterOperation.GREATER_THAN_OR_EQUAL, + "0x87FFFFFF", + ), + ], + ) + self.assertIs(type(cbdata), dict) + + def test_cbxp_can_use_hex_filter_with_less_than_or_equal(self): + cbdata = cbxp( + "cvt", + filters=[ + CBXPFilter( + "cvtasmvt", + CBXPFilterOperation.LESS_THAN_OR_EQUAL, + "0x88000000", + ), + ], + ) + self.assertIs(type(cbdata), dict) + + def test_cbxp_returns_none_if_no_filter_match( + self, + ): + self.assertIsNone( + cbxp( + "psa", + filters=[CBXPFilter("psapsa", CBXPFilterOperation.EQUAL, "PSB")], + ), + ) + + def test_cbxp_returns_none_if_one_of_two_filters_fails( + self, + ): + self.assertIsNone( + cbxp( + "ascb", + includes=["assb"], + filters=[ + CBXPFilter( + "assb.assbjbns", + CBXPFilterOperation.EQUAL, + "*MASTER*", + ), + CBXPFilter( + "ascbasid", + CBXPFilterOperation.GREATER_THAN, + 2, + ), + ], + ), + ) # ============================================================================ # Debug Mode @@ -236,6 +491,82 @@ def test_cbxp_raises_cbxp_error_when_pattern_cannot_contain_comma_2(self): cbxp("cvt", includes=["asvt,as"]) self.assertEqual("Include patterns cannot contain commas", str(e.exception)) + # ============================================================================ + # Errors: Bad Filters + # ============================================================================ + def test_cbxp_raises_cbxp_error_if_filter_uses_non_included_control_block( + self, + ): + with self.assertRaises(CBXPError) as e: + cbxp( + "psa", + filters=[ + CBXPFilter( + "cvt.asvt.ascb.assb.assbjbns", + CBXPFilterOperation.EQUAL, + "*MASTER*", + ), + ], + ) + self.assertEqual("A bad filter was provided", str(e.exception)) + + def test_cbxp_raises_cbxp_error_if_non_equality_filter_used_with_string( + self, + ): + with self.assertRaises(CBXPError) as e: + cbxp( + "psa", + includes=["**"], + filters=[ + CBXPFilter( + "cvt.asvt.ascb.assb.assbjbns", + CBXPFilterOperation.LESS_THAN_OR_EQUAL, + "*MASTER*", + ), + ], + ) + self.assertEqual("A bad filter was provided", str(e.exception)) + + def test_cbxp_raises_cbxp_error_if_filter_uses_unknown_key( + self, + ): + with self.assertRaises(CBXPError) as e: + cbxp( + "psa", + 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( + self, + ): + with self.assertRaises(CBXPError) as e: + cbxp( + "psa", + filters=["psapsa", 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( + self, + ): + with self.assertRaises(CBXPError) as e: + cbxp( + "ascb", + filters=["ascbasid", CBXPFilterOperation.LESS_THAN, "junk"], + ) + self.assertEqual("A bad filter was provided", str(e.exception)) + + def test_cbxp_raises_cbxp_error_if_filter_has_comma( + self, + ): + with self.assertRaises(CBXPError) as e: + cbxp( + "psa", + filters=["psapsa", CBXPFilterOperation.EQUAL, "PSA,PSB"], + ) + self.assertEqual("Filters cannot contain commas", str(e.exception)) + if __name__ == "__main__": unittest.main(verbosity=2, failfast=True, buffer=True) diff --git a/tests/test.sh b/tests/test.sh index 1706df0..ee0c96f 100755 --- a/tests/test.sh +++ b/tests/test.sh @@ -19,6 +19,18 @@ run_with_expected_exit_code() { echo } +run_with_expected_null_response() { + echo "Running: $* (expecting null response)" + + if "$@" | grep -q -x "null"; then + echo "Command exited with null response as expected." + else + echo "Unexpected non-null response" + exit 1 + fi + echo +} + # Basic Usage run_with_expected_exit_code 0 ./dist/cbxp psa run_with_expected_exit_code 0 ./dist/cbxp cvt @@ -26,6 +38,7 @@ 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 # Include Patterns run_with_expected_exit_code 0 ./dist/cbxp -i cvt psa @@ -42,7 +55,29 @@ 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_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 # Debug Mode run_with_expected_exit_code 0 ./dist/cbxp -d psa @@ -59,6 +94,10 @@ run_with_expected_exit_code 255 ./dist/cbxp run_with_expected_exit_code 255 ./dist/cbxp -x "unknown flag" cvt run_with_expected_exit_code 255 ./dist/cbxp -i cvt run_with_expected_exit_code 255 ./dist/cbxp -i -i cvt psa +run_with_expected_exit_code 255 ./dist/cbxp -d -d psa +run_with_expected_exit_code 255 ./dist/cbxp -f psa +run_with_expected_exit_code 255 ./dist/cbxp -f psapsa=psa +run_with_expected_exit_code 255 ./dist/cbxp --debug -d psa # Errors: Unknown Control Block run_with_expected_exit_code 255 ./dist/cbxp unknown # Errors: Bad Include Patterns @@ -70,6 +109,13 @@ 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 psapsa= psa +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 echo " -------------------------------- " echo " -------------------------------- " From e537189a3264ba336b072a296105b6f4a75039e6 Mon Sep 17 00:00:00 2001 From: varunchennamadhava Date: Tue, 24 Feb 2026 10:43:12 -0500 Subject: [PATCH 12/37] test cases files modified Signed-off-by: varunchennamadhava --- tests/test.py | 227 -------------------------------------------------- tests/test.sh | 20 ----- 2 files changed, 247 deletions(-) diff --git a/tests/test.py b/tests/test.py index 9d5b9e9..3114c17 100644 --- a/tests/test.py +++ b/tests/test.py @@ -424,233 +424,6 @@ def test_cbxp_returns_none_if_one_of_two_filters_fails( ), ) - # ============================================================================ - # Filters - # ============================================================================ - def test_cbxp_can_use_filter(self): - cbdata = cbxp( - "psa", - filters=[CBXPFilter("psapsa", CBXPFilterOperation.EQUAL, "PSA")], - ) - self.assertIs(type(cbdata), dict) - - def test_cbxp_can_use_filter_with_wildcard_include(self): - cbdata = cbxp( - "psa", - filters=[ - CBXPFilter( - "cvt.asvt.ascb.assb.assbjbns", - CBXPFilterOperation.EQUAL, - "*MASTER*", - ), - ], - includes=["**"], - ) - self.assertIs(type(cbdata), dict) - - def test_cbxp_can_use_filter_with_explicit_include(self): - cbdata = cbxp( - "psa", - filters=[ - CBXPFilter( - "cvt.asvt.ascb.assb.assbjbns", - CBXPFilterOperation.EQUAL, - "*MASTER*", - ), - ], - includes=["cvt.asvt.ascb.assb"], - ) - self.assertIs(type(cbdata), dict) - - def test_cbxp_can_use_multiple_filters(self): - cbdata = cbxp( - "psa", - filters=[ - CBXPFilter( - "cvt.asvt.ascb.assb.assbjbns", - CBXPFilterOperation.EQUAL, - "*MASTER*", - ), - CBXPFilter( - "cvt.asvt.ascb.ascbasid", - CBXPFilterOperation.GREATER_THAN, - 0, - ), - ], - includes=["cvt.asvt.ascb.assb"], - ) - self.assertIs(type(cbdata), dict) - - def test_cbxp_can_use_wildcard_filter_with_string(self): - cbdata = cbxp( - "psa", - filters=[ - CBXPFilter( - "cvt.asvt.ascb.assb.assbjbns", - CBXPFilterOperation.EQUAL, - "?MAS?ER?", - ), - ], - includes=["cvt.asvt.ascb.assb"], - ) - self.assertIs(type(cbdata), dict) - - def test_cbxp_can_use_int_filter_equal(self): - cbdata = cbxp( - "psa", - filters=[ - CBXPFilter("cvt.asvt.ascb.ascbasid", CBXPFilterOperation.EQUAL, 1), - ], - includes=["cvt.asvt.ascb.assb"], - ) - self.assertIs(type(cbdata), dict) - - def test_cbxp_can_use_int_filter_greater_than(self): - cbdata = cbxp( - "psa", - filters=[ - CBXPFilter( - "cvt.asvt.ascb.ascbasid", - CBXPFilterOperation.GREATER_THAN, - 0, - ), - ], - includes=["cvt.asvt.ascb.assb"], - ) - self.assertIs(type(cbdata), dict) - - def test_cbxp_can_use_int_filter_less_than(self): - cbdata = cbxp( - "psa", - filters=[ - CBXPFilter( - "cvt.asvt.ascb.ascbasid", - CBXPFilterOperation.LESS_THAN, - 2, - ), - ], - includes=["cvt.asvt.ascb.assb"], - ) - self.assertIs(type(cbdata), dict) - - def test_cbxp_can_use_int_filter_greater_than_or_equal(self): - cbdata = cbxp( - "psa", - filters=[ - CBXPFilter( - "cvt.asvt.ascb.ascbasid", - CBXPFilterOperation.GREATER_THAN_OR_EQUAL, - 1, - ), - ], - includes=["cvt.asvt.ascb.assb"], - ) - self.assertIs(type(cbdata), dict) - - def test_cbxp_can_use_int_filter_less_than_or_equal(self): - cbdata = cbxp( - "psa", - filters=[ - CBXPFilter( - "cvt.asvt.ascb.ascbasid", - CBXPFilterOperation.LESS_THAN_OR_EQUAL, - 2, - ), - ], - includes=["cvt.asvt.ascb.assb"], - ) - self.assertIs(type(cbdata), dict) - - def test_cbxp_can_use_int_filter_with_hex_field_equal(self): - cbdata = cbxp( - "cvt", - filters=[CBXPFilter("cvtasmvt", CBXPFilterOperation.EQUAL, 2281701376)], - ) - self.assertIs(type(cbdata), dict) - - def test_cbxp_can_use_hex_filter_with_equal(self): - cbdata = cbxp( - "cvt", - filters=[CBXPFilter("cvtasmvt", CBXPFilterOperation.EQUAL, "0x88000000")], - ) - self.assertIs(type(cbdata), dict) - - def test_cbxp_can_use_hex_filter_with_greater_than(self): - cbdata = cbxp( - "cvt", - filters=[ - CBXPFilter("cvtasmvt", CBXPFilterOperation.GREATER_THAN, "0x87FFFFFF"), - ], - ) - self.assertIs(type(cbdata), dict) - - def test_cbxp_can_use_hex_filter_with_less_than(self): - cbdata = cbxp( - "cvt", - filters=[ - CBXPFilter("cvtasmvt", CBXPFilterOperation.LESS_THAN, "0x88000001"), - ], - ) - self.assertIs(type(cbdata), dict) - - def test_cbxp_can_use_hex_filter_with_greater_than_or_equal(self): - cbdata = cbxp( - "cvt", - filters=[ - CBXPFilter( - "cvtasmvt", - CBXPFilterOperation.GREATER_THAN_OR_EQUAL, - "0x87FFFFFF", - ), - ], - ) - self.assertIs(type(cbdata), dict) - - def test_cbxp_can_use_hex_filter_with_less_than_or_equal(self): - cbdata = cbxp( - "cvt", - filters=[ - CBXPFilter( - "cvtasmvt", - CBXPFilterOperation.LESS_THAN_OR_EQUAL, - "0x88000000", - ), - ], - ) - self.assertIs(type(cbdata), dict) - - def test_cbxp_returns_none_if_no_filter_match( - self, - ): - self.assertIsNone( - cbxp( - "psa", - filters=[CBXPFilter("psapsa", CBXPFilterOperation.EQUAL, "PSB")], - ), - ) - - def test_cbxp_returns_none_if_one_of_two_filters_fails( - self, - ): - self.assertIsNone( - cbxp( - "ascb", - includes=["assb"], - filters=[ - CBXPFilter( - "assb.assbjbns", - CBXPFilterOperation.EQUAL, - "*MASTER*", - ), - CBXPFilter( - "ascbasid", - CBXPFilterOperation.GREATER_THAN, - 2, - ), - ], - ), - ) - # ============================================================================ # Debug Mode # ============================================================================ diff --git a/tests/test.sh b/tests/test.sh index f557ebf..ee0c96f 100755 --- a/tests/test.sh +++ b/tests/test.sh @@ -79,26 +79,6 @@ run_with_expected_exit_code 0 ./dist/cbxp -f "cvtasmvt<=0x88000000" cvt 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 -# 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_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 - # Debug Mode run_with_expected_exit_code 0 ./dist/cbxp -d psa run_with_expected_exit_code 0 ./dist/cbxp --debug psa From 8f936c7cd07c7f0af559be80d5708d68d51a2bd9 Mon Sep 17 00:00:00 2001 From: varunchennamadhava Date: Tue, 24 Feb 2026 12:56:35 -0500 Subject: [PATCH 13/37] fixed some comments on PR made by Leonard Signed-off-by: varunchennamadhava --- cbxp/control_blocks/oucb.cpp | 8 ++++---- cbxp/control_blocks/oucb.hpp | 9 +++------ 2 files changed, 7 insertions(+), 10 deletions(-) diff --git a/cbxp/control_blocks/oucb.cpp b/cbxp/control_blocks/oucb.cpp index ed5e997..5e57145 100644 --- a/cbxp/control_blocks/oucb.cpp +++ b/cbxp/control_blocks/oucb.cpp @@ -101,13 +101,13 @@ nlohmann::json OUCB::get(void* __ptr32 p_control_block) { oucb_json["oucbdrfr"] = p_oucb->oucbdrfr; oucb_json["oucbact"] = formatter_.getHex(&(p_oucb->oucbact)); // Union - oucb_json["oucbcsw"]["word"] = p_oucb->oucbcsw.word; + oucb_json["oucbcsw"]["word"] = p_oucb->oucbcsw; oucb_json["oucbcsw"]["oucbacn"] = - formatter_.getBitmap(p_oucb->oucbcsw.fields.oucbacn); + formatter_.getBitmap(p_oucb->oucbacn); oucb_json["oucbcsw"]["oucbcfl"] = - formatter_.getBitmap(p_oucb->oucbcsw.fields.oucbcfl); + formatter_.getBitmap(p_oucb->oucbcfl); oucb_json["oucbcsw"]["oucbcsbt"] = - formatter_.getBitmap(p_oucb->oucbcsw.fields.oucbcsbt); + formatter_.getBitmap(p_oucb->oucbcsbt); oucb_json["oucbcmrv"] = p_oucb->oucbcmrv; oucb_json["oucbwmrl"] = p_oucb->oucbwmrl; diff --git a/cbxp/control_blocks/oucb.hpp b/cbxp/control_blocks/oucb.hpp index 48c383c..160a4d0 100644 --- a/cbxp/control_blocks/oucb.hpp +++ b/cbxp/control_blocks/oucb.hpp @@ -25,7 +25,6 @@ typedef struct { int8_t oucbiac; int8_t oucbrsv1; int8_t oucbpgp; - // int8_t oucbwmg; //need to bring up with team int16_t oucbwsci; int16_t oucbwrci; uint8_t oucbmfl2; @@ -45,15 +44,14 @@ typedef struct { int32_t oucbtmo; int32_t oucbdrfr; char* __ptr32 oucbact; - // int32_t oucbcsw; // speak to team union { - uint32_t word; + uint32_t oucbcsw; struct { uint16_t oucbacn; uint8_t oucbcfl; uint8_t oucbcsbt; - } fields; - } oucbcsw; + }; + }; int32_t oucbcmrv; int32_t oucbwmrl; int16_t oucbval; @@ -66,7 +64,6 @@ typedef struct { int32_t oucbps1; int32_t oucbps2; int32_t oucbpst; - // OUCBCHE2 int32_t oucbrct; int32_t oucbiit; int16_t oucbnds; From e21393ba99f1c989d833d2fb49aa7d9b99f30e84 Mon Sep 17 00:00:00 2001 From: varunchennamadhava Date: Tue, 24 Feb 2026 13:05:14 -0500 Subject: [PATCH 14/37] fixed some comments on PR made by Leonard 1 Signed-off-by: varunchennamadhava --- cbxp/control_blocks/oucb.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/cbxp/control_blocks/oucb.cpp b/cbxp/control_blocks/oucb.cpp index 5e57145..4a13694 100644 --- a/cbxp/control_blocks/oucb.cpp +++ b/cbxp/control_blocks/oucb.cpp @@ -101,12 +101,12 @@ nlohmann::json OUCB::get(void* __ptr32 p_control_block) { oucb_json["oucbdrfr"] = p_oucb->oucbdrfr; oucb_json["oucbact"] = formatter_.getHex(&(p_oucb->oucbact)); // Union - oucb_json["oucbcsw"]["word"] = p_oucb->oucbcsw; - oucb_json["oucbcsw"]["oucbacn"] = + oucb_json["oucbcsw"] = p_oucb->oucbcsw; + oucb_json["oucbacn"] = formatter_.getBitmap(p_oucb->oucbacn); - oucb_json["oucbcsw"]["oucbcfl"] = + oucb_json["oucbcfl"] = formatter_.getBitmap(p_oucb->oucbcfl); - oucb_json["oucbcsw"]["oucbcsbt"] = + oucb_json["oucbcsbt"] = formatter_.getBitmap(p_oucb->oucbcsbt); oucb_json["oucbcmrv"] = p_oucb->oucbcmrv; From 70a75c89e8af861c4fca2018cc7a450aafc0fad3 Mon Sep 17 00:00:00 2001 From: varunchennamadhava Date: Tue, 24 Feb 2026 14:49:41 -0500 Subject: [PATCH 15/37] removed not needed prpgramming interfaces and added python test cases Signed-off-by: varunchennamadhava --- cbxp/control_blocks/oucb.cpp | 5 ----- tests/test.py | 32 +++++++++++++++++++++++++++++++- tests/test.sh | 2 ++ 3 files changed, 33 insertions(+), 6 deletions(-) diff --git a/cbxp/control_blocks/oucb.cpp b/cbxp/control_blocks/oucb.cpp index 4a13694..b4ce348 100644 --- a/cbxp/control_blocks/oucb.cpp +++ b/cbxp/control_blocks/oucb.cpp @@ -89,8 +89,6 @@ nlohmann::json OUCB::get(void* __ptr32 p_control_block) { oucb_json["oucbdmn"] = p_oucb->oucbdmn; oucb_json["oucbsrc"] = p_oucb->oucbsrc; oucb_json["oucbswc"] = p_oucb->oucbswc; - oucb_json["oucbascb"] = formatter_.getHex(&(p_oucb->oucbascb)); - oucb_json["oucbpagp"] = formatter_.getHex(&(p_oucb->oucbpagp)); oucb_json["oucbtmw"] = p_oucb->oucbtmw; oucb_json["oucbwms"] = p_oucb->oucbwms; oucb_json["oucbcpu"] = p_oucb->oucbcpu; @@ -113,7 +111,6 @@ nlohmann::json OUCB::get(void* __ptr32 p_control_block) { oucb_json["oucbwmrl"] = p_oucb->oucbwmrl; oucb_json["oucbval"] = p_oucb->oucbval; oucb_json["oucbpfl"] = formatter_.getBitmap(p_oucb->oucbpfl); - oucb_json["oucbactl"] = p_oucb->oucbactl; oucb_json["oucbiocl"] = p_oucb->oucbiocl; oucb_json["oucbdspc"] = formatter_.getBitmap(p_oucb->oucbdspc); oucb_json["oucbdspn"] = formatter_.getBitmap(p_oucb->oucbdspn); @@ -135,7 +132,6 @@ nlohmann::json OUCB::get(void* __ptr32 p_control_block) { oucb_json["oucbdlyt"] = p_oucb->oucbdlyt; oucb_json["oucbhst"] = p_oucb->oucbhst; oucb_json["oucbcfs"] = p_oucb->oucbcfs; - oucb_json["oucbsubn"] = formatter_.getString(p_oucb->oucbsubn, 4); oucb_json["oucbrpg"] = p_oucb->oucbrpg; oucb_json["oucbspg"] = p_oucb->oucbspg; oucb_json["oucbnpg"] = p_oucb->oucbnpg; @@ -150,7 +146,6 @@ nlohmann::json OUCB::get(void* __ptr32 p_control_block) { oucb_json["oucbcls"] = formatter_.getString(p_oucb->oucbcls, 8); oucb_json["oucbtrs"] = p_oucb->oucbtrs; oucb_json["oucbtrr"] = p_oucb->oucbtrr; - oucb_json["oucbactp"] = p_oucb->oucbactp; oucb_json["oucbswss"] = p_oucb->oucbswss; oucb_json["oucbpsum"] = p_oucb->oucbpsum; oucb_json["oucbfixb"] = p_oucb->oucbfixb; diff --git a/tests/test.py b/tests/test.py index 3114c17..8d68411 100644 --- a/tests/test.py +++ b/tests/test.py @@ -197,6 +197,7 @@ 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) + # ============================================================================ # Filters # ============================================================================ @@ -206,7 +207,7 @@ def test_cbxp_can_use_filter(self): filters=[CBXPFilter("psapsa", CBXPFilterOperation.EQUAL, "PSA")], ) self.assertIs(type(cbdata), dict) - + def test_cbxp_can_use_filter_with_wildcard_include(self): cbdata = cbxp( "psa", @@ -424,6 +425,35 @@ def test_cbxp_returns_none_if_one_of_two_filters_fails( ), ) + def test_cbxp_can_use_filter_oucbsubn_from_oucb(self): + cbdata = cbxp( + "oucb", + filters=[CBXPFilter("oucbsubn", CBXPFilterOperation.EQUAL, "OMVS")], + ) + self.assertIs(type(cbdata), list) + for entry in cbdata: + self.assertIs(type(entry), dict) + self.assertEqual(entry["oucbsubn"], "OMVS") + + + def test_cbxp_can_use_filter_on_ascb_oucb_oucbsubn_with_explicit_include_oucb(self): + cbdata = cbxp( + "ascb", + filters=[ + CBXPFilter( + "oucb.oucbsubn", + CBXPFilterOperation.EQUAL, + "OMVS", + ), + ], + includes=["oucb"], + ) + self.assertIs(type(cbdata), list) + for entry in cbdata: + self.assertIs(type(entry), dict) + self.assertEqual(entry["ascboucb"]["oucbsubn"], "OMVS") + + # ============================================================================ # Debug Mode # ============================================================================ diff --git a/tests/test.sh b/tests/test.sh index ee0c96f..cb1d13c 100755 --- a/tests/test.sh +++ b/tests/test.sh @@ -76,6 +76,8 @@ 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 'oucbsubn=OMVS' oucb +run_with_expected_exit_code 0 ./dist/cbxp -i oucb -f 'oucb.oucbsubn=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 From 71de873358660bb66b8bf38ab468f831fa4d8b01 Mon Sep 17 00:00:00 2001 From: varunchennamadhava Date: Tue, 24 Feb 2026 14:55:55 -0500 Subject: [PATCH 16/37] make format run Signed-off-by: varunchennamadhava --- cbxp/control_blocks/oucb.cpp | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/cbxp/control_blocks/oucb.cpp b/cbxp/control_blocks/oucb.cpp index b4ce348..cad5ae9 100644 --- a/cbxp/control_blocks/oucb.cpp +++ b/cbxp/control_blocks/oucb.cpp @@ -99,14 +99,10 @@ nlohmann::json OUCB::get(void* __ptr32 p_control_block) { oucb_json["oucbdrfr"] = p_oucb->oucbdrfr; oucb_json["oucbact"] = formatter_.getHex(&(p_oucb->oucbact)); // Union - oucb_json["oucbcsw"] = p_oucb->oucbcsw; - oucb_json["oucbacn"] = - formatter_.getBitmap(p_oucb->oucbacn); - oucb_json["oucbcfl"] = - formatter_.getBitmap(p_oucb->oucbcfl); - oucb_json["oucbcsbt"] = - formatter_.getBitmap(p_oucb->oucbcsbt); - + oucb_json["oucbcsw"] = p_oucb->oucbcsw; + oucb_json["oucbacn"] = formatter_.getBitmap(p_oucb->oucbacn); + oucb_json["oucbcfl"] = formatter_.getBitmap(p_oucb->oucbcfl); + oucb_json["oucbcsbt"] = formatter_.getBitmap(p_oucb->oucbcsbt); oucb_json["oucbcmrv"] = p_oucb->oucbcmrv; oucb_json["oucbwmrl"] = p_oucb->oucbwmrl; oucb_json["oucbval"] = p_oucb->oucbval; From ef212677cd4622585c60644962eb3c470a2d3924 Mon Sep 17 00:00:00 2001 From: varunchennamadhava Date: Thu, 26 Feb 2026 11:48:27 -0500 Subject: [PATCH 17/37] PR Review comments on feb 26 Signed-off-by: varunchennamadhava --- cbxp/control_blocks/ascb.cpp | 2 +- cbxp/control_blocks/oucb.cpp | 1 - tests/test.py | 10 +++++----- tests/test.sh | 4 ++-- 4 files changed, 8 insertions(+), 9 deletions(-) diff --git a/cbxp/control_blocks/ascb.cpp b/cbxp/control_blocks/ascb.cpp index 840c9c3..e95573a 100644 --- a/cbxp/control_blocks/ascb.cpp +++ b/cbxp/control_blocks/ascb.cpp @@ -56,7 +56,6 @@ nlohmann::json ASCB::get(void* __ptr32 p_control_block) { } ascb_json["ascbassb"] = formatter_.getHex(&(p_ascb->ascbassb)); - ascb_json["ascbasxb"] = formatter_.getHex(&(p_ascb->ascbasxb)); ascb_json["ascboucb"] = formatter_.getHex(&(p_ascb->ascboucb)); for (const auto& [include, cbxp_options] : options_map_) { @@ -79,6 +78,7 @@ nlohmann::json ASCB::get(void* __ptr32 p_control_block) { ascb_json["ascbascb"] = formatter_.getString(p_ascb->ascbascb, 4); ascb_json["ascbasid"] = p_ascb->ascbasn; + ascb_json["ascbasxb"] = formatter_.getHex(&(p_ascb->ascbasxb)); ascb_json["ascbdcti"] = p_ascb->ascbdcti; ascb_json["ascbejst"] = formatter_.getBitmap( reinterpret_cast(&p_ascb->ascbejst)); diff --git a/cbxp/control_blocks/oucb.cpp b/cbxp/control_blocks/oucb.cpp index cad5ae9..c95cdac 100644 --- a/cbxp/control_blocks/oucb.cpp +++ b/cbxp/control_blocks/oucb.cpp @@ -97,7 +97,6 @@ nlohmann::json OUCB::get(void* __ptr32 p_control_block) { oucb_json["oucbtms"] = p_oucb->oucbtms; oucb_json["oucbtmo"] = p_oucb->oucbtmo; oucb_json["oucbdrfr"] = p_oucb->oucbdrfr; - oucb_json["oucbact"] = formatter_.getHex(&(p_oucb->oucbact)); // Union oucb_json["oucbcsw"] = p_oucb->oucbcsw; oucb_json["oucbacn"] = formatter_.getBitmap(p_oucb->oucbacn); diff --git a/tests/test.py b/tests/test.py index 8d68411..be2ffd8 100644 --- a/tests/test.py +++ b/tests/test.py @@ -425,23 +425,23 @@ def test_cbxp_returns_none_if_one_of_two_filters_fails( ), ) - def test_cbxp_can_use_filter_oucbsubn_from_oucb(self): + def test_cbxp_can_use_filter_oucbtrxn_from_oucb(self): cbdata = cbxp( "oucb", - filters=[CBXPFilter("oucbsubn", CBXPFilterOperation.EQUAL, "OMVS")], + filters=[CBXPFilter("oucbtrxn", CBXPFilterOperation.EQUAL, "OMVS")], ) self.assertIs(type(cbdata), list) for entry in cbdata: self.assertIs(type(entry), dict) - self.assertEqual(entry["oucbsubn"], "OMVS") + self.assertEqual(entry["oucbtrxn"], "OMVS") - def test_cbxp_can_use_filter_on_ascb_oucb_oucbsubn_with_explicit_include_oucb(self): + def test_cbxp_can_use_filter_on_ascb_oucb_oucbtrxn_with_explicit_include_oucb(self): cbdata = cbxp( "ascb", filters=[ CBXPFilter( - "oucb.oucbsubn", + "oucb.oucbtrxn", CBXPFilterOperation.EQUAL, "OMVS", ), diff --git a/tests/test.sh b/tests/test.sh index cb1d13c..e350fed 100755 --- a/tests/test.sh +++ b/tests/test.sh @@ -76,8 +76,8 @@ 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 'oucbsubn=OMVS' oucb -run_with_expected_exit_code 0 ./dist/cbxp -i oucb -f 'oucb.oucbsubn=OMVS' ascb +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 From 4983a3a4d32f1ad26655d585737e9671457465b4 Mon Sep 17 00:00:00 2001 From: varunchennamadhava Date: Thu, 26 Feb 2026 11:54:27 -0500 Subject: [PATCH 18/37] issue with test.py testcase Signed-off-by: varunchennamadhava --- tests/test.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test.py b/tests/test.py index be2ffd8..02d9b22 100644 --- a/tests/test.py +++ b/tests/test.py @@ -451,7 +451,7 @@ def test_cbxp_can_use_filter_on_ascb_oucb_oucbtrxn_with_explicit_include_oucb(se self.assertIs(type(cbdata), list) for entry in cbdata: self.assertIs(type(entry), dict) - self.assertEqual(entry["ascboucb"]["oucbsubn"], "OMVS") + self.assertEqual(entry["ascboucb"]["oucbtrxn"], "OMVS") # ============================================================================ From 389f2468e278f988ca8c8055633ffed7f7ca2766 Mon Sep 17 00:00:00 2001 From: varunchennamadhava Date: Fri, 27 Feb 2026 13:11:00 -0500 Subject: [PATCH 19/37] worked on PR comments Signed-off-by: varunchennamadhava --- cbxp/control_blocks/oucb.cpp | 3 ++- tests/test.py | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/cbxp/control_blocks/oucb.cpp b/cbxp/control_blocks/oucb.cpp index c95cdac..ca58ba5 100644 --- a/cbxp/control_blocks/oucb.cpp +++ b/cbxp/control_blocks/oucb.cpp @@ -4,7 +4,7 @@ #include #include -#include +#include "oucb.hpp" #include #include @@ -106,6 +106,7 @@ nlohmann::json OUCB::get(void* __ptr32 p_control_block) { oucb_json["oucbwmrl"] = p_oucb->oucbwmrl; oucb_json["oucbval"] = p_oucb->oucbval; oucb_json["oucbpfl"] = formatter_.getBitmap(p_oucb->oucbpfl); + oucb_json["oucbactl"] = p_oucb->oucbactl; oucb_json["oucbiocl"] = p_oucb->oucbiocl; oucb_json["oucbdspc"] = formatter_.getBitmap(p_oucb->oucbdspc); oucb_json["oucbdspn"] = formatter_.getBitmap(p_oucb->oucbdspn); diff --git a/tests/test.py b/tests/test.py index 02d9b22..d185504 100644 --- a/tests/test.py +++ b/tests/test.py @@ -451,6 +451,7 @@ def test_cbxp_can_use_filter_on_ascb_oucb_oucbtrxn_with_explicit_include_oucb(se self.assertIs(type(cbdata), list) for entry in cbdata: self.assertIs(type(entry), dict) + self.asserIs(type(entry["ascboucb"]), dict) self.assertEqual(entry["ascboucb"]["oucbtrxn"], "OMVS") From 7ce709e21d086660836277dcd1071d6e9c352627 Mon Sep 17 00:00:00 2001 From: varunchennamadhava Date: Fri, 27 Feb 2026 16:59:09 -0500 Subject: [PATCH 20/37] make format ran Signed-off-by: varunchennamadhava --- cbxp/control_blocks/oucb.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/cbxp/control_blocks/oucb.cpp b/cbxp/control_blocks/oucb.cpp index ca58ba5..99b752f 100644 --- a/cbxp/control_blocks/oucb.cpp +++ b/cbxp/control_blocks/oucb.cpp @@ -1,10 +1,11 @@ +#include "oucb.hpp" + #include #include #include #include #include -#include "oucb.hpp" #include #include From 74ddeeba7b3d33e1a733d8c24809d38ae5016392 Mon Sep 17 00:00:00 2001 From: varunchennamadhava Date: Tue, 3 Mar 2026 10:31:45 -0500 Subject: [PATCH 21/37] typo fix Signed-off-by: varunchennamadhava --- tests/test.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test.py b/tests/test.py index d185504..9f5dc59 100644 --- a/tests/test.py +++ b/tests/test.py @@ -451,7 +451,7 @@ def test_cbxp_can_use_filter_on_ascb_oucb_oucbtrxn_with_explicit_include_oucb(se self.assertIs(type(cbdata), list) for entry in cbdata: self.assertIs(type(entry), dict) - self.asserIs(type(entry["ascboucb"]), dict) + self.assertIs(type(entry["ascboucb"]), dict) self.assertEqual(entry["ascboucb"]["oucbtrxn"], "OMVS") From 0c0d788db7f4a9a084e2cfe81cd12e06b296bb57 Mon Sep 17 00:00:00 2001 From: Elijah Swift Date: Mon, 9 Mar 2026 11:40:31 -0400 Subject: [PATCH 22/37] remove null check and add empty string translation Signed-off-by: Elijah Swift --- cbxp/control_blocks/control_block.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/cbxp/control_blocks/control_block.cpp b/cbxp/control_blocks/control_block.cpp index 43c397d..e13688d 100644 --- a/cbxp/control_blocks/control_block.cpp +++ b/cbxp/control_blocks/control_block.cpp @@ -169,9 +169,9 @@ void ControlBlock::addCurrentFilter(const std::string& filter) { // 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); - if (filter_value == "") { - Logger::getInstance().debug("Filter values cannot be null"); - throw FilterError(); + if (filter_value == "\"\"" || filter_value == "\'\'") { + Logger::getInstance().debug("Filter value converted to null"); + filter_value = ""; } cbxp_filter_t filter_data = {operation, filter_value}; Logger::getInstance().debug("Adding '" + filter_key + operation + From 3b0718ec7c8c341ebac2ad754385d0ae7fc5a28c Mon Sep 17 00:00:00 2001 From: Elijah Swift Date: Mon, 9 Mar 2026 12:20:43 -0400 Subject: [PATCH 23/37] add test cases Signed-off-by: Elijah Swift --- .../control_block_field_formatter.hpp | 3 + tests/test.py | 79 ++++++++++++++++--- tests/test.sh | 6 +- 3 files changed, 76 insertions(+), 12 deletions(-) diff --git a/cbxp/control_blocks/control_block_field_formatter.hpp b/cbxp/control_blocks/control_block_field_formatter.hpp index cc1d05a..1b9a8c1 100644 --- a/cbxp/control_blocks/control_block_field_formatter.hpp +++ b/cbxp/control_blocks/control_block_field_formatter.hpp @@ -24,6 +24,9 @@ class ControlBlockFieldFormatter { __e2a_l(ascii_field_tmp.data(), length); std::string ascii_field(ascii_field_tmp.begin(), ascii_field_tmp.end()); size_t last_non_space = ascii_field.find_last_not_of(" \t\n\r\f\v"); + if (last_non_space == std::string::npos) { + return ""; + } ascii_field.resize(last_non_space + 1); return ascii_field; } diff --git a/tests/test.py b/tests/test.py index 9f5dc59..7793012 100644 --- a/tests/test.py +++ b/tests/test.py @@ -34,7 +34,7 @@ def test_cbxp_can_extract_assb(self): self.assertIs(type(cbdata), list) for entry in cbdata: self.assertIs(type(entry), dict) - + def test_cbxp_can_extract_oucb(self): cbdata = cbxp("oucb") self.assertIs(type(cbdata), list) @@ -75,7 +75,7 @@ def test_cbxp_can_extract_the_ascb_and_include_the_assb(self): 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"]) self.assertIs(type(cbdata), list) @@ -139,7 +139,7 @@ def test_cbxp_include_can_extract_psa_and_include_ecvt_asvt_and_cvt_asvt_ascb_as for entry in cbdata["flccvt"]["cvtasvt"]["asvtenty"]: 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( self, ): @@ -197,7 +197,6 @@ 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) - # ============================================================================ # Filters # ============================================================================ @@ -207,7 +206,7 @@ def test_cbxp_can_use_filter(self): filters=[CBXPFilter("psapsa", CBXPFilterOperation.EQUAL, "PSA")], ) self.assertIs(type(cbdata), dict) - + def test_cbxp_can_use_filter_with_wildcard_include(self): cbdata = cbxp( "psa", @@ -434,8 +433,7 @@ def test_cbxp_can_use_filter_oucbtrxn_from_oucb(self): for entry in cbdata: 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( "ascb", @@ -453,7 +451,66 @@ def test_cbxp_can_use_filter_on_ascb_oucb_oucbtrxn_with_explicit_include_oucb(se self.assertIs(type(entry), dict) self.assertIs(type(entry["ascboucb"]), dict) self.assertEqual(entry["ascboucb"]["oucbtrxn"], "OMVS") - + + def test_cbxp_can_use_empty_quotes_as_null_filter_string( + self, + ): + cbdata = cbxp( + "assb", + filters=[ + CBXPFilter( + "assbjbns", + CBXPFilterOperation.EQUAL, + "*MASTER*", + ), + CBXPFilter( + "ascbjbni", + CBXPFilterOperation.EQUAL, + '""', + ), + ], + ) + self.assertIs(type(cbdata), dict) + + def test_cbxp_can_use_empty_single_quotes_as_null_filter_string( + self, + ): + cbdata = cbxp( + "assb", + filters=[ + CBXPFilter( + "assbjbns", + CBXPFilterOperation.EQUAL, + "*MASTER*", + ), + CBXPFilter( + "ascbjbni", + CBXPFilterOperation.EQUAL, + "''", + ), + ], + ) + self.assertIs(type(cbdata), dict) + + def test_cbxp_can_use_null_filter_string( + self, + ): + cbdata = cbxp( + "assb", + filters=[ + CBXPFilter( + "assbjbns", + CBXPFilterOperation.EQUAL, + "*MASTER*", + ), + CBXPFilter( + "ascbjbni", + CBXPFilterOperation.EQUAL, + "", + ), + ], + ) + self.assertIs(type(cbdata), dict) # ============================================================================ # Debug Mode @@ -568,13 +625,13 @@ def test_cbxp_raises_cbxp_error_if_filter_uses_unknown_key( ) self.assertEqual("A bad filter was provided", str(e.exception)) - def test_cbxp_raises_cbxp_error_if_filter_passes_null_value( + def test_cbxp_raises_cbxp_error_if_filter_passes_null_value_for_non_string( self, ): with self.assertRaises(CBXPError) as e: cbxp( - "psa", - filters=["psapsa", CBXPFilterOperation.EQUAL, ""], + "assb", + filters=["assbasid", CBXPFilterOperation.EQUAL, ""], ) self.assertEqual("A bad filter was provided", str(e.exception)) diff --git a/tests/test.sh b/tests/test.sh index e350fed..f94bed0 100755 --- a/tests/test.sh +++ b/tests/test.sh @@ -80,6 +80,10 @@ 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 @@ -115,7 +119,7 @@ run_with_expected_exit_code 255 ./dist/cbxp -i cvt cvt 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 psapsa= 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 From 102da79f3bfa3ea5b02c18d5af4976f9f688b887 Mon Sep 17 00:00:00 2001 From: Elijah Swift Date: Mon, 9 Mar 2026 12:23:12 -0400 Subject: [PATCH 24/37] remove null fix Signed-off-by: Elijah Swift --- cbxp/control_blocks/control_block_field_formatter.hpp | 3 --- 1 file changed, 3 deletions(-) diff --git a/cbxp/control_blocks/control_block_field_formatter.hpp b/cbxp/control_blocks/control_block_field_formatter.hpp index 1b9a8c1..cc1d05a 100644 --- a/cbxp/control_blocks/control_block_field_formatter.hpp +++ b/cbxp/control_blocks/control_block_field_formatter.hpp @@ -24,9 +24,6 @@ class ControlBlockFieldFormatter { __e2a_l(ascii_field_tmp.data(), length); std::string ascii_field(ascii_field_tmp.begin(), ascii_field_tmp.end()); size_t last_non_space = ascii_field.find_last_not_of(" \t\n\r\f\v"); - if (last_non_space == std::string::npos) { - return ""; - } ascii_field.resize(last_non_space + 1); return ascii_field; } From 7f5db409105cd0a1406d0e7219495f33354cebfb Mon Sep 17 00:00:00 2001 From: Elijah Swift Date: Mon, 9 Mar 2026 12:30:25 -0400 Subject: [PATCH 25/37] fixed python tests Signed-off-by: Elijah Swift --- tests/test.py | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/tests/test.py b/tests/test.py index 7793012..72fbd4d 100644 --- a/tests/test.py +++ b/tests/test.py @@ -452,7 +452,7 @@ 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_empty_quotes_as_null_filter_string( + def test_cbxp_can_use_null_filter_string( self, ): cbdata = cbxp( @@ -464,15 +464,15 @@ def test_cbxp_can_use_empty_quotes_as_null_filter_string( "*MASTER*", ), CBXPFilter( - "ascbjbni", + "assbjbni", CBXPFilterOperation.EQUAL, - '""', + "", ), ], ) - self.assertIs(type(cbdata), dict) + self.assertIs(type(cbdata), list) - def test_cbxp_can_use_empty_single_quotes_as_null_filter_string( + def test_cbxp_can_use_empty_quotes_as_null_filter_string( self, ): cbdata = cbxp( @@ -484,15 +484,15 @@ def test_cbxp_can_use_empty_single_quotes_as_null_filter_string( "*MASTER*", ), CBXPFilter( - "ascbjbni", + "assbjbni", CBXPFilterOperation.EQUAL, - "''", + '""', ), ], ) - self.assertIs(type(cbdata), dict) + self.assertIs(type(cbdata), list) - def test_cbxp_can_use_null_filter_string( + def test_cbxp_can_use_empty_single_quotes_as_null_filter_string( self, ): cbdata = cbxp( @@ -504,13 +504,13 @@ def test_cbxp_can_use_null_filter_string( "*MASTER*", ), CBXPFilter( - "ascbjbni", + "assbjbni", CBXPFilterOperation.EQUAL, - "", + "''", ), ], ) - self.assertIs(type(cbdata), dict) + self.assertIs(type(cbdata), list) # ============================================================================ # Debug Mode From 1e0cbe352f2e83eff68877fa7a6009906ca96087 Mon Sep 17 00:00:00 2001 From: Elijah Swift Date: Mon, 9 Mar 2026 13:21:21 -0400 Subject: [PATCH 26/37] fixed extraneous null issue Signed-off-by: Elijah Swift --- cbxp/control_blocks/control_block_field_formatter.hpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/cbxp/control_blocks/control_block_field_formatter.hpp b/cbxp/control_blocks/control_block_field_formatter.hpp index cc1d05a..e57632c 100644 --- a/cbxp/control_blocks/control_block_field_formatter.hpp +++ b/cbxp/control_blocks/control_block_field_formatter.hpp @@ -25,6 +25,8 @@ class ControlBlockFieldFormatter { std::string ascii_field(ascii_field_tmp.begin(), ascii_field_tmp.end()); size_t last_non_space = ascii_field.find_last_not_of(" \t\n\r\f\v"); ascii_field.resize(last_non_space + 1); + last_non_space = ascii_field.find_last_not_of('\0'); + ascii_field.resize(last_non_space + 1); return ascii_field; } template From 1b24005d89d7dcc245c4019319383ee2a91d5618 Mon Sep 17 00:00:00 2001 From: Elijah Swift Date: Mon, 9 Mar 2026 13:26:38 -0400 Subject: [PATCH 27/37] fixed extraneous null issue better Signed-off-by: Elijah Swift --- cbxp/control_blocks/control_block_field_formatter.hpp | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/cbxp/control_blocks/control_block_field_formatter.hpp b/cbxp/control_blocks/control_block_field_formatter.hpp index e57632c..f8eac6a 100644 --- a/cbxp/control_blocks/control_block_field_formatter.hpp +++ b/cbxp/control_blocks/control_block_field_formatter.hpp @@ -23,9 +23,8 @@ class ControlBlockFieldFormatter { std::memcpy(ascii_field_tmp.data(), p_field, length); __e2a_l(ascii_field_tmp.data(), length); std::string ascii_field(ascii_field_tmp.begin(), ascii_field_tmp.end()); - size_t last_non_space = ascii_field.find_last_not_of(" \t\n\r\f\v"); - ascii_field.resize(last_non_space + 1); - last_non_space = ascii_field.find_last_not_of('\0'); + size_t last_non_space = + ascii_field.find_last_not_of({'\0', ' ', '\t', '\n', '\r', '\f', '\v'}); ascii_field.resize(last_non_space + 1); return ascii_field; } From 019dc904f15285dd677458cdd829aa70fb684718 Mon Sep 17 00:00:00 2001 From: Elijah Swift Date: Mon, 9 Mar 2026 16:55:52 -0400 Subject: [PATCH 28/37] git comments and bug fixes Signed-off-by: Elijah Swift --- cbxp/control_blocks/control_block.cpp | 9 ++++++--- tests/test.py | 22 +--------------------- tests/test.sh | 4 ++-- 3 files changed, 9 insertions(+), 26 deletions(-) diff --git a/cbxp/control_blocks/control_block.cpp b/cbxp/control_blocks/control_block.cpp index e13688d..622d055 100644 --- a/cbxp/control_blocks/control_block.cpp +++ b/cbxp/control_blocks/control_block.cpp @@ -169,9 +169,8 @@ void ControlBlock::addCurrentFilter(const std::string& filter) { // 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); - if (filter_value == "\"\"" || filter_value == "\'\'") { - Logger::getInstance().debug("Filter value converted to null"); - filter_value = ""; + if (filter_value == "") { + Logger::getInstance().debug("Filter value is empty string"); } cbxp_filter_t filter_data = {operation, filter_value}; Logger::getInstance().debug("Adding '" + filter_key + operation + @@ -180,6 +179,10 @@ void ControlBlock::addCurrentFilter(const std::string& filter) { control_block_name_ + "' control block..."); current_filters_[filter_key].push_back(filter_data); return; + } else { + // If there's not a delimeter then this is not a valid include + Logger::getInstance().debug("Filter values must have an operation"); + throw FilterError(); } } } diff --git a/tests/test.py b/tests/test.py index 72fbd4d..c83ad33 100644 --- a/tests/test.py +++ b/tests/test.py @@ -472,26 +472,6 @@ def test_cbxp_can_use_null_filter_string( ) self.assertIs(type(cbdata), list) - def test_cbxp_can_use_empty_quotes_as_null_filter_string( - self, - ): - cbdata = cbxp( - "assb", - filters=[ - CBXPFilter( - "assbjbns", - CBXPFilterOperation.EQUAL, - "*MASTER*", - ), - CBXPFilter( - "assbjbni", - CBXPFilterOperation.EQUAL, - '""', - ), - ], - ) - self.assertIs(type(cbdata), list) - def test_cbxp_can_use_empty_single_quotes_as_null_filter_string( self, ): @@ -506,7 +486,7 @@ def test_cbxp_can_use_empty_single_quotes_as_null_filter_string( CBXPFilter( "assbjbni", CBXPFilterOperation.EQUAL, - "''", + "", ), ], ) diff --git a/tests/test.sh b/tests/test.sh index f94bed0..61d1f8d 100755 --- a/tests/test.sh +++ b/tests/test.sh @@ -81,8 +81,8 @@ 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 +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 From 194c215296e22d6409303ccf4d022f88aff78dfa Mon Sep 17 00:00:00 2001 From: Elijah Swift Date: Mon, 9 Mar 2026 17:00:14 -0400 Subject: [PATCH 29/37] git comments and bug fixes 2 Signed-off-by: Elijah Swift --- cbxp/control_blocks/control_block.cpp | 7 +++---- tests/test.py | 10 ++++++++++ tests/test.sh | 1 + 3 files changed, 14 insertions(+), 4 deletions(-) diff --git a/cbxp/control_blocks/control_block.cpp b/cbxp/control_blocks/control_block.cpp index 622d055..a30048a 100644 --- a/cbxp/control_blocks/control_block.cpp +++ b/cbxp/control_blocks/control_block.cpp @@ -179,12 +179,11 @@ void ControlBlock::addCurrentFilter(const std::string& filter) { control_block_name_ + "' control block..."); current_filters_[filter_key].push_back(filter_data); return; - } else { - // If there's not a delimeter then this is not a valid include - Logger::getInstance().debug("Filter values must have an operation"); - throw FilterError(); } } + // If no delimeter is found then this is not a valid include + Logger::getInstance().debug("Filter values must have an operation"); + throw FilterError(); } bool ControlBlock::compare(const nlohmann::json& json_value, diff --git a/tests/test.py b/tests/test.py index c83ad33..664e064 100644 --- a/tests/test.py +++ b/tests/test.py @@ -625,6 +625,16 @@ def test_cbxp_raises_cbxp_error_if_filter_uses_string_for_numeric_field( ) self.assertEqual("A bad filter was provided", str(e.exception)) + def test_cbxp_raises_cbxp_error_if_no_operation_provided( + self, + ): + with self.assertRaises(CBXPError) as e: + cbxp( + "psa", + filters=["junk", None, ""], + ) + self.assertEqual("A bad filter was provided", str(e.exception)) + def test_cbxp_raises_cbxp_error_if_filter_has_comma( self, ): diff --git a/tests/test.sh b/tests/test.sh index 61d1f8d..7e9e26a 100755 --- a/tests/test.sh +++ b/tests/test.sh @@ -122,6 +122,7 @@ 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 echo " -------------------------------- " echo " -------------------------------- " From 87362ed5118b9bb60b11d0b5bd8e13cbe253e3ae Mon Sep 17 00:00:00 2001 From: Elijah Swift Date: Tue, 10 Mar 2026 09:37:38 -0400 Subject: [PATCH 30/37] git comments and bug fixes 3 Signed-off-by: Elijah Swift --- cbxp/control_blocks/control_block.cpp | 6 ++---- tests/test.py | 20 -------------------- 2 files changed, 2 insertions(+), 24 deletions(-) diff --git a/cbxp/control_blocks/control_block.cpp b/cbxp/control_blocks/control_block.cpp index a30048a..e12acd6 100644 --- a/cbxp/control_blocks/control_block.cpp +++ b/cbxp/control_blocks/control_block.cpp @@ -169,9 +169,6 @@ void ControlBlock::addCurrentFilter(const std::string& filter) { // 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); - if (filter_value == "") { - Logger::getInstance().debug("Filter value is empty string"); - } cbxp_filter_t filter_data = {operation, filter_value}; Logger::getInstance().debug("Adding '" + filter_key + operation + filter_value + @@ -182,7 +179,8 @@ void ControlBlock::addCurrentFilter(const std::string& filter) { } } // If no delimeter is found then this is not a valid include - Logger::getInstance().debug("Filter values must have an operation"); + Logger::getInstance().debug( + "Filters must be key-value pairs (e.g., 'key=value')"); throw FilterError(); } diff --git a/tests/test.py b/tests/test.py index 664e064..6e69f5a 100644 --- a/tests/test.py +++ b/tests/test.py @@ -472,26 +472,6 @@ def test_cbxp_can_use_null_filter_string( ) self.assertIs(type(cbdata), list) - def test_cbxp_can_use_empty_single_quotes_as_null_filter_string( - self, - ): - cbdata = cbxp( - "assb", - filters=[ - CBXPFilter( - "assbjbns", - CBXPFilterOperation.EQUAL, - "*MASTER*", - ), - CBXPFilter( - "assbjbni", - CBXPFilterOperation.EQUAL, - "", - ), - ], - ) - self.assertIs(type(cbdata), list) - # ============================================================================ # Debug Mode # ============================================================================ From 23705c43b5acbf0293a4376a4709f483470cf5a8 Mon Sep 17 00:00:00 2001 From: Elijah Swift Date: Tue, 10 Mar 2026 13:34:32 -0400 Subject: [PATCH 31/37] remove comment Signed-off-by: Elijah Swift --- cbxp/control_blocks/control_block.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/cbxp/control_blocks/control_block.cpp b/cbxp/control_blocks/control_block.cpp index e12acd6..3b7a4d0 100644 --- a/cbxp/control_blocks/control_block.cpp +++ b/cbxp/control_blocks/control_block.cpp @@ -178,7 +178,6 @@ void ControlBlock::addCurrentFilter(const std::string& filter) { return; } } - // If no delimeter is found then this is not a valid include Logger::getInstance().debug( "Filters must be key-value pairs (e.g., 'key=value')"); throw FilterError(); From a04b3a78e94982a4986dfd2d2513302641a636ce Mon Sep 17 00:00:00 2001 From: "Leonard J. Carcaramo Jr" <72973973+lcarcaramo@users.noreply.github.com> Date: Tue, 10 Mar 2026 14:18:51 -0400 Subject: [PATCH 32/37] Static Library/Cleanup (#29) * Static library changes/cleanup Signed-off-by: Leonard Carcaramo * Cleanup Signed-off-by: Leonard Carcaramo * Cleanup Signed-off-by: Leonard Carcaramo * Debug shell unit tests. Signed-off-by: Leonard Carcaramo * Debug unit tests Signed-off-by: Leonard Carcaramo * Cleanup Jenkinsfile Signed-off-by: Leonard Carcaramo * Cleanup Jenkinsfile Signed-off-by: Leonard Carcaramo * Use 'pax interchange format' Signed-off-by: Leonard Carcaramo * Debug 'gmake package' Signed-off-by: Leonard Carcaramo * '-o saveext' not necessary when using '-x pax' Signed-off-by: Leonard Carcaramo * Update copyright year. Signed-off-by: Leonard Carcaramo * Add back eyecatcher as exception to the programming interfaces only rule. Signed-off-by: Leonard Carcaramo --------- Signed-off-by: Leonard Carcaramo --- CMakeLists.txt | 31 +++++++++---- Jenkinsfile | 28 ++++++------ LICENSE | 2 +- README.md | 1 + cbxp/cbxp.cpp | 13 +++--- cbxp/cbxp.h | 13 +++--- cbxp/cbxp_result.h | 10 ----- cbxp/control_block_explorer.cpp | 9 ++-- cbxp/main.cpp | 78 ++++++++------------------------- cbxp/python/_cbxp.c | 1 - setup.py | 1 + tests/test.sh | 2 - 12 files changed, 80 insertions(+), 109 deletions(-) delete mode 100644 cbxp/cbxp_result.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 3d92f7f..282dbd0 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -5,7 +5,7 @@ project(CBXP VERSION 0.0.3 LANGUAGES C CXX) set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "./dist") -set(CMAKE_LIBRARY_OUTPUT_DIRECTORY "./dist") +set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY "./dist") execute_process( COMMAND uname @@ -18,7 +18,14 @@ execute_process( # ============================================================================ set(CXX_STANDARD 17) -set(COMPILE_OPTIONS -O2 -fzos-le-char-mode=ascii -Wno-trigraphs) +set( + COMPILE_OPTIONS + -O2 + -fzos-le-char-mode=ascii + -fstack-protector-strong + -Wno-trigraphs +) + set(LINK_OPTIONS -m64 -Wl,-b,edit=no) if(uname_output STREQUAL "OS/390\n") @@ -51,9 +58,9 @@ file( ) # ============================================================================ -# Build '/lib/libcbxp.so' +# Build '/lib/libcbxp.a' # ============================================================================ -add_library(libcbxp SHARED ${CBXP_SRC_LIB}) +add_library(libcbxp STATIC ${CBXP_SRC_LIB}) set_target_properties(libcbxp PROPERTIES OUTPUT_NAME "cbxp") target_include_directories( @@ -72,6 +79,8 @@ target_link_options(libcbxp PUBLIC ${LINK_OPTIONS}) # ============================================================================ add_executable(cbxp "cbxp/main.cpp") +target_link_libraries(cbxp libcbxp) + target_include_directories(cbxp PUBLIC cbxp externals) target_compile_definitions( @@ -91,22 +100,26 @@ add_custom_target( "-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/libcbxp.so" - "cbxp-${CMAKE_PROJECT_VERSION}/lib/" 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" - "-wzf" + "-x" "pax" + "-wzvf" "./dist/cbxp-${CMAKE_PROJECT_VERSION}.pax.Z" - "-o saveext" "cbxp-${CMAKE_PROJECT_VERSION}/*" DEPENDS libcbxp cbxp ) diff --git a/Jenkinsfile b/Jenkinsfile index ac9abd3..e09ae5f 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -41,11 +41,11 @@ pipeline { ) ) string( - name: "gitHubMilestoneLink", + name: "releaseNotesLink", defaultValue: "", description: ( - "(Required When Creating Releases) This is the GitHub " - + "Milestone URL that coresponds to the release." + "(Required When Creating Releases) This is the URL to " + + "the release notes for the release." ) ) booleanParam( @@ -73,8 +73,8 @@ pipeline { if (params.releaseTitle == "") { error("'releaseTitle' is a required parameter when creating a release.") } - if (params.gitHubMilestoneLink == "") { - error("'gitHubMilestoneLink' is a required parameter when creating a release.") + if (params.releaseNotesLink == "") { + error("'releaseNotesLink' is a required parameter when creating a release.") } } } @@ -139,7 +139,7 @@ pipeline { clean_python_environment() clean_git_repo() } - // CLI/Library distribution + // Shell/C/C++ pax distribution def cbxp_version = get_cbxp_version() def pax = "cbxp-${cbxp_version}.pax.Z" echo "Building '${pax}' ..." @@ -157,7 +157,7 @@ pipeline { """ echo "'Function testing './dist/cbxp' ..." - sh "./tests/test.sh" + sh "gmake test" clean_git_repo() } @@ -173,7 +173,7 @@ pipeline { params.releaseTitle, params.releaseTag, env.BRANCH_NAME, - params.gitHubMilestoneLink, + params.releaseNotesLink, params.preRelease ) } @@ -244,7 +244,7 @@ def publish( release_title, release_tag, git_branch, - milestone, + release_notes_link, pre_release ) { if (pre_release == true) { @@ -274,7 +274,7 @@ def publish( echo "Creating '${release_title}' GitHub release ..." - def description = build_description(python_executables_and_wheels_map, release_tag, milestone) + def description = build_description(python_executables_and_wheels_map, release_tag, release_notes_link) def release_id = sh( returnStdout: true, @@ -342,7 +342,7 @@ def publish( echo "Adding sha256 checksum for '${tar_publish}' to ${checksums_file}..." sh "cd dist && sha256sum -t ${tar_publish} >> ${checksums_file}" - // Build and publish CLI/Library distribution + // Build and publish Shell/C/C++ interface pax def cbxp_version = get_cbxp_version() def pax = "cbxp-${cbxp_version}.pax.Z" echo "Building '${pax}' ..." @@ -375,9 +375,9 @@ def upload_asset(release_id, release_asset) { ) } -def build_description(python_executables_and_wheels_map, release_tag, milestone) { +def build_description(python_executables_and_wheels_map, release_tag, release_notes_link) { def description = ( - "Release Notes: ${milestone}\\n" + "Release Notes: ${release_notes_link}\\n" + "## Python Interface Installation\\n" ) @@ -401,7 +401,7 @@ def build_description(python_executables_and_wheels_map, release_tag, milestone) + "> :warning: _Requires z/OS Open XL C/C++ 2.1 compiler._\\n" + "```\\ncurl -O -L https://github.com/ambitus/cbxp/releases/download/${release_tag}/${tar} " + "&& python3 -m pip install ${tar}\\n```\\n" - + "## CLI/Library Installation\\n" + + "## Shell/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/LICENSE b/LICENSE index 987ab26..bff8284 100644 --- a/LICENSE +++ b/LICENSE @@ -186,7 +186,7 @@ same "printed page" as the copyright notice for easier identification within third-party archives. - Copyright 2025 Open Mainframe Project and Linux Foundation + Copyright 2025-2026 Open Mainframe Project and Linux Foundation Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/README.md b/README.md index 90a7799..c013f62 100644 --- a/README.md +++ b/README.md @@ -34,6 +34,7 @@ All versions of the **IBM Open Enterprise SDK for Python** that are fully suppor 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) +* [C/C++ Interface](https://ambitus.github.io/cbxp/interfaces/c_cpp) ### Supported Control Blocks diff --git a/cbxp/cbxp.cpp b/cbxp/cbxp.cpp index 32a0a4b..7677eeb 100644 --- a/cbxp/cbxp.cpp +++ b/cbxp/cbxp.cpp @@ -4,7 +4,6 @@ #include #include -#include "cbxp_result.h" #include "control_block_explorer.hpp" #include "logger.hpp" @@ -31,8 +30,12 @@ cbxp_result_t* cbxp(const char* control_block, const char* includes_string, void cbxp_free(cbxp_result_t* cbxp_result, bool debug) { CBXP::Logger::getInstance().setDebug(debug); - CBXP::Logger::getInstance().debugFree(cbxp_result->result_json); - delete cbxp_result->result_json; - CBXP::Logger::getInstance().debugFree(cbxp_result); - delete cbxp_result; + if (cbxp_result != nullptr) { + if (cbxp_result->result_json != nullptr) { + CBXP::Logger::getInstance().debugFree(cbxp_result->result_json); + delete cbxp_result->result_json; + } + CBXP::Logger::getInstance().debugFree(cbxp_result); + delete cbxp_result; + } } diff --git a/cbxp/cbxp.h b/cbxp/cbxp.h index 3c6ca55..6ce0e1b 100644 --- a/cbxp/cbxp.h +++ b/cbxp/cbxp.h @@ -1,12 +1,18 @@ #ifndef __CBXP_H_ #define __CBXP_H_ -#include "cbxp_result.h" - #ifdef __cplusplus extern "C" { +#else +#include #endif +typedef struct { + char* result_json; + int result_json_length; + int return_code; +} cbxp_result_t; + cbxp_result_t* cbxp(const char* control_block, const char* includes_string, const char* filters_string, bool debug); @@ -16,7 +22,4 @@ void cbxp_free(cbxp_result_t* cbxp_result, bool debug); } #endif -#pragma export(cbxp) -#pragma export(cbxp_free) - #endif diff --git a/cbxp/cbxp_result.h b/cbxp/cbxp_result.h deleted file mode 100644 index 76a406d..0000000 --- a/cbxp/cbxp_result.h +++ /dev/null @@ -1,10 +0,0 @@ -#ifndef __CBXP_RESULT_H_ -#define __CBXP_RESULT_H_ - -typedef struct { - char* result_json; - int result_json_length; - int return_code; -} cbxp_result_t; - -#endif diff --git a/cbxp/control_block_explorer.cpp b/cbxp/control_block_explorer.cpp index b3f1204..0748756 100644 --- a/cbxp/control_block_explorer.cpp +++ b/cbxp/control_block_explorer.cpp @@ -1,6 +1,7 @@ #include "control_block_explorer.hpp" #include +#include #include #include @@ -92,12 +93,14 @@ void ControlBlockExplorer::exploreControlBlock( control_block_json_string); p_result_->result_json_length = control_block_json_string.length(); - p_result_->result_json = new char[p_result_->result_json_length + 1]{}; + p_result_->result_json = new char[p_result_->result_json_length + 1]; Logger::getInstance().debugAllocate(p_result_->result_json, 64, p_result_->result_json_length + 1); - std::strncpy(p_result_->result_json, control_block_json_string.c_str(), - p_result_->result_json_length); + std::memcpy(p_result_->result_json, control_block_json_string.c_str(), + p_result_->result_json_length); + + p_result_->result_json[p_result_->result_json_length] = 0; return; } diff --git a/cbxp/main.cpp b/cbxp/main.cpp index d9c4e9d..4c4c3e5 100644 --- a/cbxp/main.cpp +++ b/cbxp/main.cpp @@ -8,18 +8,12 @@ #include #include -#include "cbxp_result.h" +#include "cbxp.h" #include "control_block_error.hpp" -typedef const cbxp_result_t* (*cbxp_t)(const char* control_block_name, - const char* includes_string, - const char* filters_string, bool debug); - -typedef const void (*cbxp_free_t)(const cbxp_result_t* cbxp_result, bool debug); - static void show_usage(const char* argv[]); -static void show_dll_errors(); -static void cleanup_and_exit(int exit_rc, void* lib_handle); + +enum CLIReturnCode { SUCCESS = 0, FAILURE = -1 }; static void show_usage(const char* argv[]) { std::cout << "Usage: " << argv[0] << " [options] " << std::endl @@ -41,69 +35,32 @@ static void show_usage(const char* argv[]) { << std::endl; } -static void show_dll_errors() { - const char* error_string = dlerror(); - if (error_string != nullptr) { - std::cerr << error_string << std::endl; - } -} - -static void cleanup_and_exit(int exit_rc, void* lib_handle) { - int rc = dlclose(lib_handle); - if (rc != 0) { - show_dll_errors(); - exit(-1); - } - exit(exit_rc); -} - bool check_for_comma(const std::string& string) { return std::any_of(string.begin(), string.end(), [](char c) { return c == ','; }); } int main(int argc, const char* argv[]) { - // Load 'libcbxp.so' - void* lib_handle = dlopen("libcbxp.so", RTLD_NOW); - if (lib_handle == nullptr) { - show_dll_errors(); - return -1; - } - - int exit_rc = -1; - // Resolve symbol 'cbxp()' - cbxp_t cbxp = reinterpret_cast(dlsym(lib_handle, "cbxp")); - if (cbxp == nullptr) { - show_dll_errors(); - cleanup_and_exit(exit_rc, lib_handle); - } - cbxp_free_t cbxp_free = - reinterpret_cast(dlsym(lib_handle, "cbxp_free")); - if (cbxp_free == nullptr) { - show_dll_errors(); - cleanup_and_exit(exit_rc, lib_handle); - } - bool debug = false; std::string control_block_name = "", includes_string = "", filters_string = ""; if (argc < 2) { show_usage(argv); - cleanup_and_exit(exit_rc, lib_handle); + 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; - cleanup_and_exit(0, lib_handle); + return CLIReturnCode::SUCCESS; } if (std::strcmp(argv[1], "-h") == 0 || std::strcmp(argv[1], "--help") == 0) { show_usage(argv); - cleanup_and_exit(0, lib_handle); + return CLIReturnCode::SUCCESS; } } @@ -114,17 +71,17 @@ int main(int argc, const char* argv[]) { debug = true; } else { show_usage(argv); - cleanup_and_exit(exit_rc, lib_handle); + return CLIReturnCode::FAILURE; } } else if (flag == "-i" || flag == "--include") { if (i + 1 >= argc - 1) { show_usage(argv); - cleanup_and_exit(exit_rc, lib_handle); + return CLIReturnCode::FAILURE; } std::string include = std::string(argv[++i]); if (check_for_comma(include)) { std::cerr << "Include patterns cannot contain commas" << std::endl; - cleanup_and_exit(exit_rc, lib_handle); + return CLIReturnCode::FAILURE; } if (includes_string == "") { includes_string = include; @@ -134,12 +91,12 @@ int main(int argc, const char* argv[]) { } else if (flag == "-f" || flag == "--filter") { if (i + 1 >= argc - 1) { show_usage(argv); - cleanup_and_exit(exit_rc, lib_handle); + return CLIReturnCode::FAILURE; } std::string filter = std::string(argv[++i]); if (check_for_comma(filter)) { std::cerr << "Filters cannot contain commas" << std::endl; - cleanup_and_exit(exit_rc, lib_handle); + return CLIReturnCode::FAILURE; } if (filters_string == "") { filters_string = filter; @@ -149,7 +106,7 @@ int main(int argc, const char* argv[]) { } else { if (i != argc - 1) { show_usage(argv); - cleanup_and_exit(exit_rc, lib_handle); + return CLIReturnCode::FAILURE; } control_block_name = std::string(argv[i]); } @@ -157,15 +114,17 @@ int main(int argc, const char* argv[]) { if (control_block_name == "") { show_usage(argv); - cleanup_and_exit(exit_rc, lib_handle); + return CLIReturnCode::FAILURE; } nlohmann::json control_block_json; - const cbxp_result_t* cbxp_result = + 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 @@ -179,9 +138,10 @@ int main(int argc, const char* argv[]) { break; default: std::cout << cbxp_result->result_json << std::endl; - exit_rc = 0; + cli_return_code = CLIReturnCode::SUCCESS; } cbxp_free(cbxp_result, debug); - cleanup_and_exit(exit_rc, lib_handle); + + return cli_return_code; } diff --git a/cbxp/python/_cbxp.c b/cbxp/python/_cbxp.c index 11f5d65..a51d20f 100644 --- a/cbxp/python/_cbxp.c +++ b/cbxp/python/_cbxp.c @@ -5,7 +5,6 @@ #include #include "cbxp.h" -#include "cbxp_result.h" // Entry point to the call_cbxp() function static PyObject* call_cbxp(PyObject* self, PyObject* args, PyObject* kwargs) { diff --git a/setup.py b/setup.py index d00bc23..81683d5 100644 --- a/setup.py +++ b/setup.py @@ -37,6 +37,7 @@ def main(): extra_compile_args=[ "-O2", "-fzos-le-char-mode=ascii", + "-fstack-protector-strong", "-Wno-trigraphs", ], ), diff --git a/tests/test.sh b/tests/test.sh index 7e9e26a..8052adf 100755 --- a/tests/test.sh +++ b/tests/test.sh @@ -1,7 +1,5 @@ #!/bin/bash -export LIBPATH=${PWD}/dist:$LIBPATH - run_with_expected_exit_code() { expected_code=$1 shift From 1439563d4d3f93f17156217724c66734d1ca0584 Mon Sep 17 00:00:00 2001 From: "Leonard J. Carcaramo Jr" <72973973+lcarcaramo@users.noreply.github.com> Date: Wed, 18 Mar 2026 14:21:59 -0400 Subject: [PATCH 33/37] OpenXL 2.2/Python3.14 (#33) * Initial Alpha (#19) * Feature/includeparameter (#14) * Add skeleton for inclusion list parameter Extend interface/skeleton for include_list * Input POinter Attempt to add input pointer parameter for control blocks * Definitions outside of if statements * Attempt to map the intended inclusion list * Try Virtual Function to keep Control Block in Explorer * No longer have get return * Attempt to add the meat of the function -Add methods and members to control blocks to get name of control block, specific control blocks within it, and all control blocks within it -add functions in control block explorer to parse inclusion "map" into actionable data name fix * Update psa.cpp Update psa.cpp * Update cvt.cpp * Update asvt.cpp * algorithm cleanup * better cleanup * Update control_block.cpp * Update main.cpp * Finish addressing merge issues and commit hooks -Minor code updates that were lost in merge commit -Bring new code up to standard for cppcheck -Format new code with clang-format * Update _cbxp.c * Streamline Control Block Explorer Class Update control_block_explorer.hpp * Massive refactor -Shave 2 step process down to one -Change serialized json inclusion map to use a vector of strings still split by "dot" operators * Error Handling Logic * BIG UPDATE PR COMMENTS -Switch pre-processing to one hash map function -use try/catch with custom errors rather than passing return codes everywhere -style and name changes -Enforce more rigid parm structure on entry -Fix some behavioral bugs and oversights in inclusion preprocessing -General streamlining and refactoring of functions, methods, classes, etc. * Another Big Refactor -PR comments (mostly style, but streamlining of error code as well) -Reworked base and derived classes to allow for includables to be defined to the base class and include_map to be defined to the base and derived classes * Update ascb.cpp * Update control_block.hpp * . * .. * ... * PR Comments -ASCB pointer deref in ASVT -Minor name changes -Remove double wildcard error -Add control_block_name_ private member and add initialization to constructor -move include_map_ to protected and remove private using statement * Update asvt.cpp * Update asvt.cpp * PR Comments Mostly renaming things streamlining some unnecessary text, parms and strings * Update control_block.cpp * Update main.cpp * Last round of PR comments string compare with == remove vestiges of old mechanisms for control block management name changes minor tweaks * Update cvt.cpp * Update cvt.cpp * Update cvt.cpp * Final comments Update control_block_explorer.cpp * comments * Last Comments * include changes * Last round of comments * debug * Unit testing (#17) * initial commit 1 * cleaned code before include test cases * wrote test cases, need to check with team now * added space after every function * added .value * shell script done * made changes proposed by leonard 1 * PR changes requested by team * added tests to check for ascb and asvt entries whether it be a string or dict * added tests to check for ascb and asvt entries whether it be a string or dict one more place * made minor tweaks * added updates provided by leonard * grouped failure test cases together * grouped error test cases together * removed extra lines * style changes * Feat/oss housekeeping2 (#18) * Set explicit C/C++ standard and cleanup README. Signed-off-by: Leonard Carcaramo * Update contribution guidelines and functional tests. Signed-off-by: Leonard Carcaramo * Cleanup contribution guidelines and debug debug mode. Signed-off-by: Leonard Carcaramo * Cleanup. Signed-off-by: Leonard Carcaramo * Cleanup. Signed-off-by: Leonard Carcaramo * Fix sdist packaging and pyproject.toml metadata. Signed-off-by: Leonard Carcaramo --------- Signed-off-by: Leonard Carcaramo * Fix _C.pyi and removed unused import from cbxp.py. Signed-off-by: Leonard Carcaramo --------- Signed-off-by: Leonard Carcaramo Co-authored-by: Elijah Swift Co-authored-by: Varun Chennamadhava * Python support/OpenXL 2.2 updates. Signed-off-by: Leonard Carcaramo * Updates for Python wheel naming convention changes in Python 3.14 Signed-off-by: Leonard Carcaramo * Fix Python wheel file name. Signed-off-by: Leonard Carcaramo * Debug publish Signed-off-by: Leonard Carcaramo * Update Varun's email Signed-off-by: Leonard Carcaramo * Update Varun's email Signed-off-by: Leonard Carcaramo --------- Signed-off-by: Leonard Carcaramo Co-authored-by: Elijah Swift Co-authored-by: Varun Chennamadhava --- Jenkinsfile | 39 ++++++++++++++++++++++++++++++--------- README.md | 8 ++++---- cbxp/cbxp.cpp | 20 +++++++++++++++++--- pyproject.toml | 12 ++++++------ 4 files changed, 57 insertions(+), 22 deletions(-) diff --git a/Jenkinsfile b/Jenkinsfile index e09ae5f..281a168 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -206,13 +206,30 @@ def create_python_executables_and_wheels_map(python_versions) { python_executables_and_wheels_map = [:] for (python_version in python_versions) { - python_executables_and_wheels_map["python3.${python_version}"] = [ - "wheelDefault": ( - "cbxp-${cbxp_version}-cp3${python_version}-cp3${python_version}-${os}_${zos_release}_${processor}.whl" - ), - "wheelPublish": "cbxp-${cbxp_version}-cp3${python_version}-none-any.whl", - "tarPublish": "cbxp-${cbxp_version}.tar.gz" - ] + // New wheel naming convention in Python 3.14 + if (python_version > "13") { + python_executables_and_wheels_map["python3.${python_version}"] = [ + "wheelDefault": ( + "cbxp-${cbxp_version}-cp3${python_version}-cp3${python_version}-zos.whl" + ), + "wheelPublish": ( + "cbxp-${cbxp_version}-cp3${python_version}-cp3${python_version}-zos.whl" + ), + "tarPublish": "cbxp-${cbxp_version}.tar.gz" + ] + } + // Old wheel naming convention + else { + python_executables_and_wheels_map["python3.${python_version}"] = [ + "wheelDefault": ( + "cbxp-${cbxp_version}-cp3${python_version}-cp3${python_version}-${os}_${zos_release}_${processor}.whl" + ), + "wheelPublish": ( + "cbxp-${cbxp_version}-cp3${python_version}-none-any.whl" + ), + "tarPublish": "cbxp-${cbxp_version}.tar.gz" + ] + } } return python_executables_and_wheels_map @@ -319,9 +336,13 @@ def publish( sh """ ${python} -m pip install build>=1.2.2 ${python} -m build -w - mv ./dist/${wheel_default} ./dist/${wheel_publish} """ + // Rename wheel file if the old naming convention is being used + if (wheel_default != wheel_publish) { + sh "mv ./dist/${wheel_default} ./dist/${wheel_publish}" + } + if (tar_built == false) { tar_publish = python_executables_and_wheels_map[python]["tarPublish"] echo "Building '${tar_publish}' ..." @@ -398,7 +419,7 @@ def build_description(python_executables_and_wheels_map, release_tag, release_no def pax = "cbxp-${cbxp_version}.pax.Z" description += ( "Install From **Source Distribution** *(build on install)*:\\n" - + "> :warning: _Requires z/OS Open XL C/C++ 2.1 compiler._\\n" + + "> :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" diff --git a/README.md b/README.md index c013f62..8066e67 100644 --- a/README.md +++ b/README.md @@ -27,8 +27,8 @@ All versions of the **IBM Open Enterprise SDK for Python** that are fully suppor * [IBM Open Enterprise SDK for Python Product Lifecycle](https://www.ibm.com/support/pages/lifecycle/search?q=5655-PYT) ### Dependencies -* **z/OS Language Environment Runtime Support**: CBXP is compiled using the **IBM Open XL C/C++ 2.1** compiler, which is still fairly new and requires **z/OS Language Environment** service updates for runtime support. - * More information can be found in section **5.2.2.2 Operational Requisites** on page **9** in the [Program Directory for IBM Open XL C/C++ 2.1 for z/OS](https://publibfp.dhe.ibm.com/epubs/pdf/i1357012.pdf). +* **z/OS Language Environment Runtime Support**: CBXP is compiled using the [IBM Open XL C/C++ 2.2](https://www.ibm.com/docs/en/open-xl-c-cpp-zos/2.2.0) compiler, which is still fairly new and requires **z/OS Language Environment** service updates for runtime support. + * More information can be found in section **5.2.2.2 Operational Requisites** on page **8** in the [Program Directory for IBM Open XL C/C++ 2.2 for z/OS](https://publibfp.dhe.ibm.com/epubs/pdf/i1357013.pdf). ### Interfaces Currently, the following interfaces are provided for CBXP. Additional interfaces can be added in the future if there are use cases for them. @@ -46,9 +46,9 @@ Currently, CBXP only has support for extracting a handful of **System-Level Cont ## Authors * Leonard J. Carcaramo Jr: lcarcaramo@ibm.com * Elijah Swift: elijah.swift@ibm.com -* Varun Chennamadhava: varunchennamadhava@ibm.com +* Varun Chennamadhava: varunchennamadhava@gmail.com ## Maintainers * Leonard J. Carcaramo Jr: lcarcaramo@ibm.com * Elijah Swift: elijah.swift@ibm.com -* Varun Chennamadhava: varunchennamadhava@ibm.com +* Varun Chennamadhava: varunchennamadhava@gmail.com diff --git a/cbxp/cbxp.cpp b/cbxp/cbxp.cpp index 7677eeb..4aa2e0d 100644 --- a/cbxp/cbxp.cpp +++ b/cbxp/cbxp.cpp @@ -10,7 +10,20 @@ cbxp_result_t* cbxp(const char* control_block, const char* includes_string, const char* filters_string, bool debug) { nlohmann::json control_block_json; - std::string control_block_name = control_block; + + 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 (includes_string != nullptr) { + includes_string_cpp_string = includes_string; + } + if (filters_string != nullptr) { + filters_string_cpp_string = filters_string; + } CBXP::Logger::getInstance().setDebug(debug); @@ -21,8 +34,9 @@ cbxp_result_t* cbxp(const char* control_block, const char* includes_string, CBXP::ControlBlockExplorer explorer = CBXP::ControlBlockExplorer(p_cbxp_result); - explorer.exploreControlBlock(control_block_name, includes_string, - filters_string); + explorer.exploreControlBlock(control_block_cpp_string, + includes_string_cpp_string, + filters_string_cpp_string); return p_cbxp_result; } diff --git a/pyproject.toml b/pyproject.toml index 08210f7..2a2321c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,20 +1,20 @@ [build-system] - requires = ["setuptools >= 80.9.0"] + requires = ["setuptools >= 82.0.1"] build-backend = "setuptools.build_meta" [project] name="cbxp" version="0.0.3" - requires-python = ">= 3.12" + requires-python = ">= 3.13" authors = [ { name = "Leonard J. Carcaramo Jr", email = "lcarcaramo@ibm.com" }, { name = "Elijah Swift", email = "elijah.swift@ibm.com" }, - { name = "Varun Chennamadhava", email = "varunchennamadhava@ibm.com" }, + { name = "Varun Chennamadhava", email = "varunchennamadhava@gmail.com" }, ] maintainers = [ { name = "Leonard J. Carcaramo Jr", email = "lcarcaramo@ibm.com" }, { name = "Elijah Swift", email = "elijah.swift@ibm.com" }, - { name = "Varun Chennamadhava", email = "varunchennamadhava@ibm.com" }, + { name = "Varun Chennamadhava", email = "varunchennamadhava@gmail.com" }, ] description="A unified and standardized interface for extracting z/OS control block data." readme = "README.md" @@ -28,8 +28,8 @@ "Programming Language :: C++", 'Programming Language :: Python', "Programming Language :: Python :: 3", - "Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3.13", + "Programming Language :: Python :: 3.14", 'Programming Language :: Python :: 3 :: Only', "Programming Language :: Python :: Implementation :: CPython", "Programming Language :: Python :: Implementation :: PyPy", @@ -42,7 +42,7 @@ Documentation = "https://ambitus.github.io/cbxp/interfaces/python/" Source = "https://github.com/ambitus/cbxp" Issues = "https://github.com/ambitus/cbxp/issues" - "Release Notes" = "https://github.com/ambitus/cbxp/milestones?state=closed" + "Release Notes" = "https://ambitus.github.io/cbxp/release_notes/" [tool.setuptools.packages.find] where = ["python"] From 84825ede00f832718b9c3933bec928f308e5115d Mon Sep 17 00:00:00 2001 From: Elijah Swift <86801927+ElijahSwiftIBM@users.noreply.github.com> Date: Thu, 21 May 2026 12:44:31 -0400 Subject: [PATCH 34/37] Feat/format (#37) * preliminary changes to separate format and extract; still needs CLI file parsing, data piping support all work for python interface and tests Signed-off-by: Elijah Swift * Added untested file logic and stdin logic to CLI; still needs ptyhon interface and lots of testing Signed-off-by: Elijah Swift * Added python interface; still needs dataset logic in CLI and lots of testing Signed-off-by: Elijah Swift * Added dataset logic to c++ along with file/dataset read errors; all code is in base, just needs lots of testing Signed-off-by: Elijah Swift * Fixed testing to adopt operation parameter, made python interface default to 'explore' operation Signed-off-by: Elijah Swift * Minor fixes to address compiler errors; the cli is very broken right now Signed-off-by: Elijah Swift * Minor fixes to address compiler errors; the cli is very broken right now Signed-off-by: Elijah Swift * Minor fixes to address compiler errors; the cli may be fixed Signed-off-by: Elijah Swift * Scrap file parsing in python interface, just a cli feature, python takes bytes only (still needs testing) Signed-off-by: Elijah Swift * Minor fix (still needs testing) Signed-off-by: Elijah Swift * scrub lots of ptr32's Signed-off-by: Elijah Swift * scrub lots of ptr32's again and add debug statements Signed-off-by: Elijah Swift * getHexPtr Signed-off-by: Elijah Swift * moving on? Signed-off-by: Elijah Swift * bitfield patch Signed-off-by: Elijah Swift * bitfield patch 2 Signed-off-by: Elijah Swift * bitfield patch 3 Signed-off-by: Elijah Swift * bitfield patch 4 Signed-off-by: Elijah Swift * bitfield patch 5 Signed-off-by: Elijah Swift * bitfield patch 6 Signed-off-by: Elijah Swift * ascb bins Signed-off-by: Elijah Swift * oucb bins Signed-off-by: Elijah Swift * minor fix Signed-off-by: Elijah Swift * main file buffer fix Signed-off-by: Elijah Swift * main file buffer fix 2 Signed-off-by: Elijah Swift * more overt buffer error Signed-off-by: Elijah Swift * attempted testing Signed-off-by: Elijah Swift * attempted testing 2 Signed-off-by: Elijah Swift * attempted testing 3 Signed-off-by: Elijah Swift * attempted testing 4 Signed-off-by: Elijah Swift * attempted testing 5 Signed-off-by: Elijah Swift * attempted testing 6 Signed-off-by: Elijah Swift * attempted testing 7 Signed-off-by: Elijah Swift * attempted testing 8 Signed-off-by: Elijah Swift * attempted testing 9 Signed-off-by: Elijah Swift * PR Comments #1 (still needs testing) Signed-off-by: Elijah Swift * PR Comments #2 (still needs testing) Signed-off-by: Elijah Swift * PR Comments #1.3 (still needs testing) Signed-off-by: Elijah Swift * PR Comments #1.4 (still needs testing) Signed-off-by: Elijah Swift * PR Comments #1.5 (still needs testing) Signed-off-by: Elijah Swift * PR Comments #1.6 (still needs testing) Signed-off-by: Elijah Swift * PR Comments #1.7 (still needs testing) Signed-off-by: Elijah Swift * PR Comments #1.8 (still needs testing) Signed-off-by: Elijah Swift * PR Comments #1.9 (still needs testing) Signed-off-by: Elijah Swift * PR Comments #1.10 (still needs testing) Signed-off-by: Elijah Swift * PR Comments #1.11 (still needs testing) Signed-off-by: Elijah Swift * PR Comments #2.0 (still needs testing) Signed-off-by: Elijah Swift * PR Comments #2.1 (still needs testing) Signed-off-by: Elijah Swift * PR Comments #3.0 (still needs testing) Signed-off-by: Elijah Swift * PR Comments #3.1 (still needs testing) Signed-off-by: Elijah Swift * PR Comments #3.2 (still needs testing) Signed-off-by: Elijah Swift * PR Comments #3.3 (still needs testing) Signed-off-by: Elijah Swift * PR Comments #3.4 (still needs testing) Signed-off-by: Elijah Swift * PR Comments #4.0 (still needs testing) Signed-off-by: Elijah Swift * PR Comments #4.1 Signed-off-by: Elijah Swift * PR Comments #5.0 (still needs testing) Signed-off-by: Elijah Swift * PR Comments #5.1 (still needs testing) Signed-off-by: Elijah Swift * PR Comments #5.2 (still needs testing) Signed-off-by: Elijah Swift * PR Comments #6.0 (still needs testing) Signed-off-by: Elijah Swift * PR Comments #6.1 (still needs testing) Signed-off-by: Elijah Swift * PR Comments #6.2 (still needs testing) Signed-off-by: Elijah Swift * PR Comments #6.3 (still needs testing) Signed-off-by: Elijah Swift * PR Comments #6.4 (still needs testing) Signed-off-by: Elijah Swift * PR Comments #6.5 Signed-off-by: Elijah Swift * PR Comments #6.6 Signed-off-by: Elijah Swift * PR Comments #7.0 (Still needs testing) Signed-off-by: Elijah Swift * PR Comments #7.1 (Still needs testing) Signed-off-by: Elijah Swift * PR Comments #7.2 Signed-off-by: Elijah Swift * PR Comments #8.1 (Still needs testing) Signed-off-by: Elijah Swift * PR Comments #8.2 Signed-off-by: Elijah Swift * PR Comments #8.3 Signed-off-by: Elijah Swift * PR Comments #9.0 (still needs testing) Signed-off-by: Elijah Swift * PR Comments #9.1 (still needs testing) Signed-off-by: Elijah Swift * PR Comments #9.2 (still needs testing) Signed-off-by: Elijah Swift * PR Comments #9.3 (still needs testing) Signed-off-by: Elijah Swift * PR Comments #10.0 (still needs testing) Signed-off-by: Elijah Swift * PR Comments #11.0 (still needs testing) Signed-off-by: Elijah Swift * PR Comments #11.1 (still needs testing) Signed-off-by: Elijah Swift * PR Comments #11.2 (still needs testing) Signed-off-by: Elijah Swift * PR Comments #11.3 Signed-off-by: Elijah Swift * PR Comments #12.0 Signed-off-by: Elijah Swift * PR Comments #12.1 Signed-off-by: Elijah Swift * PR Comments #13.0 Signed-off-by: Elijah Swift * PR Comments #13.1 Signed-off-by: Elijah Swift * PR Comments #13.2 Signed-off-by: Elijah Swift --------- Signed-off-by: Elijah Swift --- CMakeLists.txt | 15 +- README.md | 4 +- cbxp/cbxp.cpp | 60 +++- cbxp/cbxp.h | 16 +- cbxp/cli/command_processor.cpp | 417 +++++++++++++++++++++++ cbxp/cli/command_processor.hpp | 95 ++++++ cbxp/cli/main.cpp | 15 + cbxp/control_block_error.hpp | 16 +- cbxp/control_block_explorer.cpp | 71 +++- cbxp/control_block_explorer.hpp | 11 +- cbxp/control_blocks/ascb.cpp | 11 +- cbxp/control_blocks/ascb.hpp | 8 +- cbxp/control_blocks/assb.cpp | 11 +- cbxp/control_blocks/assb.hpp | 7 +- cbxp/control_blocks/asvt.cpp | 16 +- cbxp/control_blocks/asvt.hpp | 5 +- cbxp/control_blocks/control_block.cpp | 22 ++ cbxp/control_blocks/control_block.hpp | 18 +- cbxp/control_blocks/cvt.cpp | 190 ++++++----- cbxp/control_blocks/cvt.hpp | 8 +- cbxp/control_blocks/ecvt.cpp | 13 +- cbxp/control_blocks/ecvt.hpp | 7 +- cbxp/control_blocks/oucb.cpp | 10 +- cbxp/control_blocks/oucb.hpp | 5 +- cbxp/control_blocks/psa.cpp | 10 +- cbxp/control_blocks/psa.hpp | 7 +- cbxp/main.cpp | 147 --------- cbxp/python/_cbxp.c | 81 ++++- pyproject.toml | 4 +- python/cbxp/_C.pyi | 8 +- python/cbxp/__init__.py | 1 - python/cbxp/cbxp.py | 64 +++- setup.py | 8 +- tests/samples/ascb.bin | Bin 0 -> 432 bytes tests/samples/ascboffset40.bin | Bin 0 -> 496 bytes tests/samples/cvt.bin | Bin 0 -> 1312 bytes tests/samples/oucb.bin | Bin 0 -> 2344 bytes tests/samples/oucboffset3A8.bin | Bin 0 -> 3280 bytes tests/test.py | 458 ++++++++++++++++++-------- tests/test.sh | 220 ++++++++----- 40 files changed, 1478 insertions(+), 581 deletions(-) create mode 100644 cbxp/cli/command_processor.cpp create mode 100644 cbxp/cli/command_processor.hpp create mode 100644 cbxp/cli/main.cpp delete mode 100644 cbxp/main.cpp create mode 100644 tests/samples/ascb.bin create mode 100644 tests/samples/ascboffset40.bin create mode 100644 tests/samples/cvt.bin create mode 100644 tests/samples/oucb.bin create mode 100644 tests/samples/oucboffset3A8.bin diff --git a/CMakeLists.txt b/CMakeLists.txt index 282dbd0..7a5751e 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) diff --git a/README.md b/README.md index 8066e67..8233267 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@ # 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 @@ -38,7 +38,7 @@ Currently, the following interfaces are provided for CBXP. Additional interfaces ### 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..94d6aa0 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* p_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, p_data, data_length); return p_cbxp_result; } diff --git a/cbxp/cbxp.h b/cbxp/cbxp.h index 6ce0e1b..c55690b 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* p_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..7e35f9f --- /dev/null +++ b/cbxp/cli/command_processor.cpp @@ -0,0 +1,417 @@ +#include "command_processor.hpp" + +#include + +#include +#include +#include +#include + +#include "cbxp.h" +#include "control_block_error.hpp" + +namespace CBXP { + +void CommandProcessor::showGeneralUsage() const { + std::cout << "Full CLI documentation is available at: " + "https://ambitus.github.io/cbxp/interfaces/shell/" + << 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(); + } + } + if (isatty(STDIN_FILENO) == 0) { + 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..4142030 --- /dev/null +++ b/cbxp/cli/command_processor.hpp @@ -0,0 +1,95 @@ +#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_; + 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..21b352f 100644 --- a/cbxp/control_block_explorer.cpp +++ b/cbxp/control_block_explorer.cpp @@ -50,32 +50,68 @@ 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 { throw ControlBlockError(); } @@ -84,10 +120,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..179358b 100644 --- a/cbxp/control_blocks/assb.cpp +++ b/cbxp/control_blocks/assb.cpp @@ -1,5 +1,4 @@ #include -#include #include #include @@ -14,8 +13,10 @@ #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 +25,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,7 +56,7 @@ 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:"); diff --git a/cbxp/control_blocks/assb.hpp b/cbxp/control_blocks/assb.hpp index 5046d8f..5f6b0d2 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", {}, 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..b3ffe1b 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( @@ -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/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/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..9e26d00 100644 --- a/cbxp/python/_cbxp.c +++ b/cbxp/python/_cbxp.c @@ -6,32 +6,83 @@ #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, offset = 0; + bool debug = false; + + static char* kwlist[] = {"control_block", "data", "offset", "debug", NULL}; + + if (!PyArg_ParseTupleAndKeywords(args, kwargs, "s#y#|nO", kwlist, + &p_control_block_name, + &control_block_name_length, &p_data, + &data_length, &offset, &debug_pyobj)) { return NULL; } debug = PyObject_IsTrue(debug_pyobj); + if (offset < 0) { + PyErr_SetString(PyExc_ValueError, "Offset cannot be negative"); + return NULL; + } + + if (offset > data_length) { + PyErr_SetString(PyExc_ArithmeticError, "Offset exceeds buffer length"); + return NULL; + } + + p_data += offset; + data_length -= offset; + 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 +93,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..356182e 100644 --- a/python/cbxp/_C.pyi +++ b/python/cbxp/_C.pyi @@ -1,6 +1,12 @@ -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, + offset: int = 0, + 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..d3189b0 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,16 @@ 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 + OFFSET_TOO_LARGE = -3 + OFFSET_NOT_POSITIVE_INT = -4 + # 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 +56,27 @@ 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.OFFSET_TOO_LARGE.value: + message = "Offset is too large for data provided" + case CBXPErrorCode.OFFSET_NOT_POSITIVE_INT.value: + message = "Offset must be a positive integer" + 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 +100,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 +111,35 @@ 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, + offset: int = None, + debug: bool = False, +) -> dict: + if offset is None: + offset = 0 + if not isinstance(offset, int): + raise CBXPError(CBXPErrorCode.OFFSET_NOT_POSITIVE_INT.value, control_block) + try: + response = call_cbxp_format( + control_block.lower(), + data, + offset=offset, + debug=debug, + ) + except ValueError as error: + raise CBXPError( + CBXPErrorCode.OFFSET_NOT_POSITIVE_INT.value, + control_block, + ) from error + except ArithmeticError as error: + raise CBXPError(CBXPErrorCode.OFFSET_TOO_LARGE.value, control_block) from error + + 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 0000000000000000000000000000000000000000..69f2a7541916d31663c8edb884057778e45b56eb GIT binary patch literal 432 zcmX^3=H3eg4;Ta(7_?5^g{o$(5#T&K z=TZ4?8`}?8+ZY%f?=|%Q{Q)CKP$F+VAh5AnO5XVPJ6e0*QGlRs!{*g8Kix0w4bW{{rW8%i79lp8teUeI)1C3{hJNsq58k|) z_kQ!{&F}3jp!p!v_rgB?8SGbeIHJN`P~I)DfBzEp!jo*y_6dDp2=e=s*4P?_tQcc~ z4OhXw=hNP zitcddb~9)CcT?I&fgfT2oP-^#cl<*D$Le1Z*wKF6e8h!v8rkgn2rhX68H6w%55hxU z^;?!gls}esj8YQ~j&*CIn9zp;P+Phw*P!EfBxC)pJHm&{yQt1(s1NJ;H%VxxHm24P zK&Sen2eMMXwPyNE3y?(NYT+v6^(n`H?EQV5Dy2ic0ii$V!Or5lVIS_&_SQlhj)U@d z5N}bY->@JbOC7D7@R9GxgS3V0n|QfcBl}W9SqyTA7t)x5+!=wa^+Bax5qdr(-gv8u z>=DRc3kciDjCFo<+XWx&(LS>#M4W7>VY78LfM-QJK4@wCt2t~cYB2=lnke6{EaYyF zFdj(pk9+dSwYVJP>@yKII}OMg;x6nb4NzHhm$IOEqTR)?@ST@k9}VK|uXVHfLSE<> zDMuUeU(Us$wm#OpGR=%l7S-DVS$CNk)tofX3@T3U3BW$z0{c|||Hav%!_?TdZ;dt2 z4Lfr_+3MlrBb<+C2AwrlvR>lzPtu!^Js#v*>ye8`AU_|1+Dclv2dXN@C$2@!vWr&1 zeOVmcHIRn-AtYvc16&<1rFd2t+XtbZDiB@w`?UDvpqBR2drE~#X@4q+OD(vajuS>G z>l{nmfR8<#HJ|pRsrPctXXaf?18e-YgBdScn|CK+<~yUu66M6j)lWJXqF@c$Sr;cu z=?#{?KpGZ1is&8{H7vV0=EhV>e9flXsmD!_rq5mHwpHTBqw`RU)YH*CYXcO(_D3T3nO6(;Y}B!? c literal 0 HcmV?d00001 diff --git a/tests/samples/oucb.bin b/tests/samples/oucb.bin new file mode 100644 index 0000000000000000000000000000000000000000..dfaefd72641be40d28946d2ea0e3e65be9f5678b GIT binary patch literal 2344 zcmb_dO=wd=5T3XBc`Z?^DCnWsAgy?4p{F9)V5}gPKm`vPK~YPQYQckwMm!j(29IE@GJ zaSm-0c)pHGK8Lm8-l!F9=|`LZ$O6=LxD6jp64rC(Xv_VViDbl`bIi}lxi?eulVSJ# zi^S`huv-kXO7mV1 zap=em!x#18RAGJiN@ccXHn601#7^hNAck(;yy}UUQrL%k*Ho4&Pfb0w*f9Dm_C)3T z)nH6giGAknPw-qTt=qeZEGm9=y~Ro5W)?!gS*16red`ls)GH@C9B6c+cVl%Nbgj{nyQ>@&1y z<}V|lw1oGhR+Ml|YQn_fu(#`W4_f5d!JMCaYIR}9nq_V|?8H7!>uvZSqGDW=E>_vm k4@TUx1_KvEERF18oRq89tYA_eS}}aZI3(tfvb-Mn3)m#%PXGV_ literal 0 HcmV?d00001 diff --git a/tests/samples/oucboffset3A8.bin b/tests/samples/oucboffset3A8.bin new file mode 100644 index 0000000000000000000000000000000000000000..fd9670c194e8b0b0afa7c6751e68b65e12916d01 GIT binary patch literal 3280 zcmebG9|fZ!Fd71bJp`^jIeduSVsiz%it`Wd3#*SaC^&dDGb~_WVEFK#fe{GJfJ_!3 z27&MQz!dk{N9!1vk~|rh5+8sCfCNYkEY84C0TTn#stn$**C6p>OwL0Y=U5n6->roS zFfjaQVBmLhK=2tdL2Rg*5Mky;M;<*s>;U1Ql1DFHIri-6A%|xNA6-6q$N>&eRe^b| zP%&1)PA35n8|-hWAPU7&@rHqc*$*zp@+fGvfVVq{Lz7qzf5fKIkMmC_j2oQ#<#>Hj@Gux*a&QJzfFI}{u(>b=@WcjXz{HX0b4Y9`ld<|s+1(u=4szT*D&N%!61#rw z(v!!Be|!V6Q30c$qs`P~KsP{X2Ava2fd-Y!iSLHaA8W&K^DWuRsfdcAbB%j9l{RO wrwUcf&J=$Kp>LZ!f{l&E^bRN*1H>S+I8jwFA(;b`LGT$7i3C*(QM?oe0P4~NMF0Q* literal 0 HcmV?d00001 diff --git a/tests/test.py b/tests/test.py index 6e69f5a..84ed572 100644 --- a/tests/test.py +++ b/tests/test.py @@ -1,96 +1,119 @@ 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 # ============================================================================ - # Basic Usage + @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 + # ============================================================================ - def test_cbxp_can_extract_psa(self): - cbdata = cbxp("psa") + # Extract -- Basic Usage + # ============================================================================ + 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_can_extract_assb(self): - cbdata = cbxp("assb") + 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_oucb(self): - cbdata = cbxp("oucb") + def test_cbxp_extract_oucb(self): + cbdata = cbxp.extract("oucb") 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_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 +121,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 +138,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 +150,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 +163,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 +176,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) @@ -165,8 +188,8 @@ def test_cbxp_can_extract_psa_and_include_cvt_recursive_wildcard(self): self.assertIs(type(entry["ascbassb"]), 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 +198,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 +207,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) @@ -198,17 +221,17 @@ def test_cbxp_can_extract_cvt_and_include_wildcard_and_asvt_recursive_wildcard( 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 +244,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 +258,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 +277,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 +291,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 +301,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 +315,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 +329,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 +343,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 +357,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 +461,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 +471,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 +489,10 @@ 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_null_filter_string( self, ): - cbdata = cbxp( + cbdata = cbxp.extract( "assb", filters=[ CBXPFilter( @@ -473,80 +510,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) - + # Extract -- Testing Errors: Unknown Control Block # ============================================================================ - # 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 +590,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 +607,198 @@ 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)) + # ============================================================================ + # 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 -- Offset + # ============================================================================ + def test_cbxp_format_ascb_with_hex_offset(self): + cbdata = cbxp.format( + "ascb", + data=self.read_sample("ascboffset40.bin"), + offset=0x40, + ) + self.assertIs(type(cbdata), dict) + + def test_cbxp_format_ascb_with_decimal_offset(self): + cbdata = cbxp.format( + "ascb", + data=self.read_sample("ascboffset40.bin"), + offset=64, + ) + self.assertIs(type(cbdata), dict) + + def test_cbxp_format_oucb_with_hex_offset(self): + cbdata = cbxp.format( + "oucb", + data=self.read_sample("oucboffset3A8.bin"), + offset=0x3A8, + ) + self.assertIs(type(cbdata), dict) + + def test_cbxp_format_oucb_with_decimal_offset(self): + cbdata = cbxp.format( + "oucb", + data=self.read_sample("oucboffset3A8.bin"), + offset=936, + ) + 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 Offset + # ============================================================================ + def test_cbxp_format_raises_cbxp_error_if_offset_is_too_large(self): + with self.assertRaises(CBXPError) as e: + cbxp.format( + "ascb", + data=self.read_sample("ascb.bin"), + offset=999999, + ) + self.assertEqual( + "Offset is too large for data provided", + str(e.exception), + ) + + def test_cbxp_format_raises_cbxp_error_if_offset_is_negative(self): + with self.assertRaises(CBXPError) as e: + cbxp.format( + "ascb", + data=self.read_sample("ascb.bin"), + offset=-1, + ) + self.assertEqual( + "Offset must be a positive integer", + str(e.exception), + ) + + def test_cbxp_format_raises_cbxp_error_if_offset_is_alpha(self): + with self.assertRaises(CBXPError) as e: + cbxp.format( + "ascb", + data=self.read_sample("ascb.bin"), + offset="JUNK", + ) + self.assertEqual( + "Offset must be a positive integer", + str(e.exception), + ) + + def test_cbxp_format_raises_cbxp_error_if_offset_is_float(self): + with self.assertRaises(CBXPError) as e: + cbxp.format( + "ascb", + data=self.read_sample("ascb.bin"), + offset=5.5, + ) + self.assertEqual( + "Offset must be a positive integer", + 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..43d8593 100755 --- a/tests/test.sh +++ b/tests/test.sh @@ -29,98 +29,146 @@ 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 +# 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 +# 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 +# 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 +# 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/ascboffset40.bin -o 0x40 ascb +run_with_expected_exit_code 0 ./dist/cbxp format -F tests/samples/ascboffset40.bin -o 64 ascb +run_with_expected_exit_code 0 cat tests/samples/oucboffset3A8.bin | ./dist/cbxp format -o 0x3A8 oucb +run_with_expected_exit_code 0 cat tests/samples/oucboffset3A8.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 " -------------------------------- " From 7870dde40363a354c28a0ef6cd14c39a5a77197b Mon Sep 17 00:00:00 2001 From: Varun R Chennamadhava <38167054+varunchennamadhava@users.noreply.github.com> Date: Wed, 10 Jun 2026 15:07:18 -0400 Subject: [PATCH 35/37] Feat/ldax control block (#38) * rebased with dev Signed-off-by: varunchennamadhava * ruff check Signed-off-by: varunchennamadhava * commit for testing PR comments 1 Signed-off-by: varunchennamadhava * leonard call 1 Signed-off-by: varunchennamadhava * clang and casting done correctly Signed-off-by: varunchennamadhava * removed test cases Signed-off-by: varunchennamadhava * made changes leonard suggested 2 Signed-off-by: varunchennamadhava * removed one test case 1 Signed-off-by: varunchennamadhava * removed p_next_ldax since its not being used anywhere Signed-off-by: varunchennamadhava --------- Signed-off-by: varunchennamadhava --- cbxp/control_block_explorer.cpp | 5 + cbxp/control_blocks/assb.cpp | 15 ++- cbxp/control_blocks/assb.hpp | 2 +- cbxp/control_blocks/control_block.cpp | 2 +- cbxp/control_blocks/ldax.cpp | 130 ++++++++++++++++++++++++++ cbxp/control_blocks/ldax.hpp | 20 ++++ tests/test.py | 122 ++++++++++++++++++++++++ tests/test.sh | 14 +++ 8 files changed, 307 insertions(+), 3 deletions(-) create mode 100644 cbxp/control_blocks/ldax.cpp create mode 100644 cbxp/control_blocks/ldax.hpp diff --git a/cbxp/control_block_explorer.cpp b/cbxp/control_block_explorer.cpp index 21b352f..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" @@ -112,6 +113,10 @@ void ControlBlockExplorer::processControlBlock( } else if (control_block_name == "oucb") { 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(); } diff --git a/cbxp/control_blocks/assb.cpp b/cbxp/control_blocks/assb.cpp index 179358b..c261b73 100644 --- a/cbxp/control_blocks/assb.cpp +++ b/cbxp/control_blocks/assb.cpp @@ -10,6 +10,7 @@ #include "ascb.hpp" #include "asvt.hpp" +#include "ldax.hpp" #include "logger.hpp" namespace CBXP { @@ -63,6 +64,19 @@ nlohmann::json ASSB::get(const void* p_control_block, 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"] = @@ -78,7 +92,6 @@ nlohmann::json ASSB::get(const void* 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 5f6b0d2..9f0c720 100644 --- a/cbxp/control_blocks/assb.hpp +++ b/cbxp/control_blocks/assb.hpp @@ -12,7 +12,7 @@ class ASSB : public ControlBlock { 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, sizeof(struct assb)) {} + : ControlBlock("assb", {"ldax"}, cbxp_options, sizeof(struct assb)) {} }; } // namespace CBXP diff --git a/cbxp/control_blocks/control_block.cpp b/cbxp/control_blocks/control_block.cpp index b3ffe1b..cdc771f 100644 --- a/cbxp/control_blocks/control_block.cpp +++ b/cbxp/control_blocks/control_block.cpp @@ -195,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; 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/tests/test.py b/tests/test.py index 84ed572..c8cf970 100644 --- a/tests/test.py +++ b/tests/test.py @@ -57,6 +57,12 @@ def test_cbxp_extract_oucb(self): for entry in cbdata: self.assertIs(type(entry), dict) + def test_cbxp_extract_ldax(self): + cbdata = cbxp.extract("ldax") + self.assertIs(type(cbdata), list) + for entry in cbdata: + self.assertIs(type(entry), dict) + # ============================================================================ # Extract -- Debug Mode # ============================================================================ @@ -106,6 +112,41 @@ def test_cbxp_extract_ascb_and_include_oucb(self): self.assertIs(type(entry), dict) self.assertIs(type(entry["ascboucb"]), dict) + 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) @@ -186,6 +227,7 @@ def test_cbxp_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_extract_psa_and_include_cvt_wildcard(self): @@ -220,6 +262,23 @@ def test_cbxp_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) + # ============================================================================ # Extract -- Filters # ============================================================================ @@ -489,6 +548,55 @@ def test_cbxp_extract_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_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.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, ): @@ -657,6 +765,20 @@ def test_cbxp_extract_raises_cbxp_error_if_filter_has_comma( ) 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 # ============================================================================ diff --git a/tests/test.sh b/tests/test.sh index 43d8593..8ff24c3 100755 --- a/tests/test.sh +++ b/tests/test.sh @@ -54,6 +54,7 @@ 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 @@ -75,6 +76,13 @@ 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 @@ -99,6 +107,9 @@ run_with_expected_null_response ./dist/cbxp extract -f "ascb.assb.assbjbns=*MAST 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 @@ -121,6 +132,9 @@ 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 From f3104f2bf3b180cdb4aca09e28717f9f5eee7b13 Mon Sep 17 00:00:00 2001 From: "Leonard J. Carcaramo Jr" <72973973+lcarcaramo@users.noreply.github.com> Date: Thu, 11 Jun 2026 14:14:25 -0400 Subject: [PATCH 36/37] Feat/0.0.4 housekeeping (#39) * 0.0.4 cleanup Signed-off-by: Leonard Carcaramo * Add ASCII art, remove package target, and debug clang-format action. Signed-off-by: Leonard Carcaramo * Debug Jenkinsfile, fix ascii art, and debug clang-format check Signed-off-by: Leonard Carcaramo * Cleanup clang-format and debug. Signed-off-by: Leonard Carcaramo * Cleanup and fix pipe check. Signed-off-by: Leonard Carcaramo * Debug Jenkinsfile Signed-off-by: Leonard Carcaramo * Update ASCII art and add _POSIX_SOURCE feature test macro for isatty()/fileno() Signed-off-by: Leonard Carcaramo * Fix sha256 checksum issue. Signed-off-by: Leonard Carcaramo --------- Signed-off-by: Leonard Carcaramo --- .github/workflows/clang-format.yml | 8 +- CMakeLists.txt | 54 +++++------ Jenkinsfile | 53 +++++------ README.md | 6 +- cbxp/cbxp.cpp | 4 +- cbxp/cbxp.h | 2 +- cbxp/cli/command_processor.cpp | 47 +++++++++- cbxp/cli/command_processor.hpp | 1 + cbxp/control_blocks/control_block.cpp | 6 +- cbxp/logger.cpp | 2 + cbxp/python/_cbxp.c | 24 +---- python/cbxp/_C.pyi | 1 - python/cbxp/cbxp.py | 30 +----- ...{ascboffset40.bin => ascb_offset_0x40.bin} | Bin ...cboffset3A8.bin => oucb_offset_0x03a8.bin} | Bin tests/test.py | 86 ------------------ tests/test.sh | 8 +- 17 files changed, 123 insertions(+), 209 deletions(-) rename tests/samples/{ascboffset40.bin => ascb_offset_0x40.bin} (100%) rename tests/samples/{oucboffset3A8.bin => oucb_offset_0x03a8.bin} (100%) 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 7a5751e..6bd2eba 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -95,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 8233267..b9ac010 100644 --- a/README.md +++ b/README.md @@ -13,14 +13,14 @@ A unified and standardized interface for extracting and formatting z/OS control ## 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,7 +33,7 @@ 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 diff --git a/cbxp/cbxp.cpp b/cbxp/cbxp.cpp index 94d6aa0..2ae70e0 100644 --- a/cbxp/cbxp.cpp +++ b/cbxp/cbxp.cpp @@ -42,7 +42,7 @@ cbxp_result_t* cbxp_extract(const char* control_block_name, cbxp_result_t* cbxp_format(const char* control_block_name, const size_t control_block_name_length, - const void* p_data, const size_t data_length, + const void* data, const size_t data_length, bool debug) { std::string control_block_name_string; @@ -60,7 +60,7 @@ cbxp_result_t* cbxp_format(const char* control_block_name, CBXP::ControlBlockExplorer explorer = CBXP::ControlBlockExplorer(p_cbxp_result); - explorer.formatControlBlock(control_block_name_string, p_data, data_length); + explorer.formatControlBlock(control_block_name_string, data, data_length); return p_cbxp_result; } diff --git a/cbxp/cbxp.h b/cbxp/cbxp.h index c55690b..359c16b 100644 --- a/cbxp/cbxp.h +++ b/cbxp/cbxp.h @@ -23,7 +23,7 @@ cbxp_result_t* cbxp_extract(const char* control_block_name, cbxp_result_t* cbxp_format(const char* control_block_name, const size_t control_block_name_length, - const void* p_data, const size_t data_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 index 7e35f9f..718bb7f 100644 --- a/cbxp/cli/command_processor.cpp +++ b/cbxp/cli/command_processor.cpp @@ -1,5 +1,9 @@ +#define _POSIX_SOURCE +#define _XOPEN_SOURCE_EXTENDED 1 + #include "command_processor.hpp" +#include #include #include @@ -12,9 +16,47 @@ 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/shell/" + "https://ambitus.github.io/cbxp/interfaces/cli/" << std::endl << std::endl; @@ -301,7 +343,8 @@ void CommandProcessor::processFormatFlags() { throw CLIExitFailure(); } } - if (isatty(STDIN_FILENO) == 0) { + struct pollfd pfd = {STDIN_FILENO, POLLIN, 0}; + if (poll(&pfd, 1, 0) > 0 && (pfd.revents & POLLIN)) { CommandProcessor::readFormatDataFromPipe(); } if (format_options_.data_buffer.empty()) { diff --git a/cbxp/cli/command_processor.hpp b/cbxp/cli/command_processor.hpp index 4142030..32ba26e 100644 --- a/cbxp/cli/command_processor.hpp +++ b/cbxp/cli/command_processor.hpp @@ -67,6 +67,7 @@ class CommandProcessor { 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; diff --git a/cbxp/control_blocks/control_block.cpp b/cbxp/control_blocks/control_block.cpp index cdc771f..eaf2783 100644 --- a/cbxp/control_blocks/control_block.cpp +++ b/cbxp/control_blocks/control_block.cpp @@ -165,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 + 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/python/_cbxp.c b/cbxp/python/_cbxp.c index 9e26d00..f781084 100644 --- a/cbxp/python/_cbxp.c +++ b/cbxp/python/_cbxp.c @@ -50,33 +50,19 @@ static PyObject* call_cbxp_format(PyObject* self, PyObject* args, PyObject* debug_pyobj; const char* p_control_block_name; const char* p_data; - Py_ssize_t data_length, control_block_name_length, offset = 0; + Py_ssize_t data_length, control_block_name_length; bool debug = false; - static char* kwlist[] = {"control_block", "data", "offset", "debug", NULL}; + static char* kwlist[] = {"control_block", "data", "debug", NULL}; - if (!PyArg_ParseTupleAndKeywords(args, kwargs, "s#y#|nO", kwlist, - &p_control_block_name, - &control_block_name_length, &p_data, - &data_length, &offset, &debug_pyobj)) { + 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); - if (offset < 0) { - PyErr_SetString(PyExc_ValueError, "Offset cannot be negative"); - return NULL; - } - - if (offset > data_length) { - PyErr_SetString(PyExc_ArithmeticError, "Offset exceeds buffer length"); - return NULL; - } - - p_data += offset; - data_length -= offset; - cbxp_result_t* p_cbxp_result = cbxp_format(p_control_block_name, control_block_name_length, p_data, data_length, debug); diff --git a/python/cbxp/_C.pyi b/python/cbxp/_C.pyi index 356182e..195d8f9 100644 --- a/python/cbxp/_C.pyi +++ b/python/cbxp/_C.pyi @@ -7,6 +7,5 @@ def call_cbxp_extract( # noqa: N999 def call_cbxp_format( # noqa: N999 control_block: str, data: bytes, - offset: int = 0, debug: bool = False, ) -> dict: ... diff --git a/python/cbxp/cbxp.py b/python/cbxp/cbxp.py index d3189b0..d01e768 100644 --- a/python/cbxp/cbxp.py +++ b/python/cbxp/cbxp.py @@ -37,8 +37,6 @@ class CBXPErrorCode(Enum): # Negative Error Codes are from the Python interface COMMA_IN_INCLUDE = -1 COMMA_IN_FILTER = -2 - OFFSET_TOO_LARGE = -3 - OFFSET_NOT_POSITIVE_INT = -4 # Positive Error Codes are return codes from CBXP UNKNOWN_CONTROL_BLOCK = 1 BAD_INCLUDE = 2 @@ -56,10 +54,6 @@ 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.OFFSET_TOO_LARGE.value: - message = "Offset is too large for data provided" - case CBXPErrorCode.OFFSET_NOT_POSITIVE_INT.value: - message = "Offset must be a positive integer" case CBXPErrorCode.UNKNOWN_CONTROL_BLOCK.value: message = f"Unknown control block: {control_block_name}" case CBXPErrorCode.BAD_INCLUDE.value: @@ -116,27 +110,13 @@ def extract( def format( # noqa: A001 control_block: str, data: bytes, - offset: int = None, debug: bool = False, ) -> dict: - if offset is None: - offset = 0 - if not isinstance(offset, int): - raise CBXPError(CBXPErrorCode.OFFSET_NOT_POSITIVE_INT.value, control_block) - try: - response = call_cbxp_format( - control_block.lower(), - data, - offset=offset, - debug=debug, - ) - except ValueError as error: - raise CBXPError( - CBXPErrorCode.OFFSET_NOT_POSITIVE_INT.value, - control_block, - ) from error - except ArithmeticError as error: - raise CBXPError(CBXPErrorCode.OFFSET_TOO_LARGE.value, control_block) from error + response = call_cbxp_format( + control_block.lower(), + data, + debug=debug, + ) if response["return_code"]: raise CBXPError(response["return_code"], control_block) diff --git a/tests/samples/ascboffset40.bin b/tests/samples/ascb_offset_0x40.bin similarity index 100% rename from tests/samples/ascboffset40.bin rename to tests/samples/ascb_offset_0x40.bin diff --git a/tests/samples/oucboffset3A8.bin b/tests/samples/oucb_offset_0x03a8.bin similarity index 100% rename from tests/samples/oucboffset3A8.bin rename to tests/samples/oucb_offset_0x03a8.bin diff --git a/tests/test.py b/tests/test.py index c8cf970..08a776c 100644 --- a/tests/test.py +++ b/tests/test.py @@ -810,41 +810,6 @@ 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 -- Offset - # ============================================================================ - def test_cbxp_format_ascb_with_hex_offset(self): - cbdata = cbxp.format( - "ascb", - data=self.read_sample("ascboffset40.bin"), - offset=0x40, - ) - self.assertIs(type(cbdata), dict) - - def test_cbxp_format_ascb_with_decimal_offset(self): - cbdata = cbxp.format( - "ascb", - data=self.read_sample("ascboffset40.bin"), - offset=64, - ) - self.assertIs(type(cbdata), dict) - - def test_cbxp_format_oucb_with_hex_offset(self): - cbdata = cbxp.format( - "oucb", - data=self.read_sample("oucboffset3A8.bin"), - offset=0x3A8, - ) - self.assertIs(type(cbdata), dict) - - def test_cbxp_format_oucb_with_decimal_offset(self): - cbdata = cbxp.format( - "oucb", - data=self.read_sample("oucboffset3A8.bin"), - offset=936, - ) - self.assertIs(type(cbdata), dict) - # ============================================================================ # Format -- Testing Errors: Unknown Control Block # ============================================================================ @@ -856,57 +821,6 @@ def test_cbxp_format_raises_cbxp_error_if_unknown_control_block_is_provided(self str(e.exception), ) - # ============================================================================ - # Format -- Testing Errors: Bad Offset - # ============================================================================ - def test_cbxp_format_raises_cbxp_error_if_offset_is_too_large(self): - with self.assertRaises(CBXPError) as e: - cbxp.format( - "ascb", - data=self.read_sample("ascb.bin"), - offset=999999, - ) - self.assertEqual( - "Offset is too large for data provided", - str(e.exception), - ) - - def test_cbxp_format_raises_cbxp_error_if_offset_is_negative(self): - with self.assertRaises(CBXPError) as e: - cbxp.format( - "ascb", - data=self.read_sample("ascb.bin"), - offset=-1, - ) - self.assertEqual( - "Offset must be a positive integer", - str(e.exception), - ) - - def test_cbxp_format_raises_cbxp_error_if_offset_is_alpha(self): - with self.assertRaises(CBXPError) as e: - cbxp.format( - "ascb", - data=self.read_sample("ascb.bin"), - offset="JUNK", - ) - self.assertEqual( - "Offset must be a positive integer", - str(e.exception), - ) - - def test_cbxp_format_raises_cbxp_error_if_offset_is_float(self): - with self.assertRaises(CBXPError) as e: - cbxp.format( - "ascb", - data=self.read_sample("ascb.bin"), - offset=5.5, - ) - self.assertEqual( - "Offset must be a positive integer", - str(e.exception), - ) - # ============================================================================ # Format -- Testing Errors: Bad Data # ============================================================================ diff --git a/tests/test.sh b/tests/test.sh index 8ff24c3..1d534c3 100755 --- a/tests/test.sh +++ b/tests/test.sh @@ -157,10 +157,10 @@ run_with_expected_exit_code 0 ./dist/cbxp format -F tests/samples/oucb.bin oucb 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/ascboffset40.bin -o 0x40 ascb -run_with_expected_exit_code 0 ./dist/cbxp format -F tests/samples/ascboffset40.bin -o 64 ascb -run_with_expected_exit_code 0 cat tests/samples/oucboffset3A8.bin | ./dist/cbxp format -o 0x3A8 oucb -run_with_expected_exit_code 0 cat tests/samples/oucboffset3A8.bin | ./dist/cbxp format -o 936 oucb +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 From 8bd8d71cbdc940d572b4a32985b091daa375efa7 Mon Sep 17 00:00:00 2001 From: Leonard Carcaramo Date: Fri, 12 Jun 2026 07:59:57 -0400 Subject: [PATCH 37/37] Cleanup CBXP ASCII art logo Signed-off-by: Leonard Carcaramo --- cbxp/cli/command_processor.cpp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/cbxp/cli/command_processor.cpp b/cbxp/cli/command_processor.cpp index 718bb7f..75725f4 100644 --- a/cbxp/cli/command_processor.cpp +++ b/cbxp/cli/command_processor.cpp @@ -23,12 +23,12 @@ void CommandProcessor::showASCIIArt() { // clang-format off const std::vector logo_ascii_art = { - " ____________ ", - " | |", - " | 1010 |", - " | |", - " | {} |", - " |____________|" + " __________ ", + " | |", + " | 1010 |", + " | |", + " | {} |", + " |__________|" }; const std::vector cbxp_ascii_art = {