Skip to content

Conversation

@ElijahSwiftIBM
Copy link
Collaborator

💡 Issue Reference

Issue: #5

💻 What does this address?

Control block filtering is a valuable enhancement. In basic cases it allows for simple validation that a control block containing the right data is collected, but in more complex cases, it allows users to grab information on just a singular relevant control block or collection of them in a large table.

📟 Implementation Details

The core control block class now has a new unordered map for filtration that is built on object creation. The filter is passed from block to block along with inclusion patterns and validated along the way. At the "end" of the line, the control block's value is validated against that of the filter. In the event of a match, the json data is returned, otherwise an empty json comes back. Large lists of control blocks like ASCB's and ASSB's were edited to filter out these null values, allowing for a true filter.

📋 Is there a test case?

I added the following tests in both the python and the shell test suites (the names in python are fare more descriptive)

test_cbxp_raises_cbxp_error_if_filter_uses_non_included_control_block
test_cbxp_raises_cbxp_error_if_filter_uses_unknown_key
test_cbxp_raises_cbxp_error_if_filter_dict_wrong_size (python only)
test_cbxp_raises_cbxp_error_if_filter_value_not_string (python only)
test_cbxp_raises_cbxp_error_if_no_filter_match
test_cbxp_can_use_basic_filter
test_cbxp_can_use_include_filter_with_generic_include
test_cbxp_can_use_include_filter_with_discrete_include

lcarcaramo and others added 2 commits November 6, 2025 07:34
* 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 <lcarcaramo@ibm.com>

* Update contribution guidelines and functional tests.

Signed-off-by: Leonard Carcaramo <lcarcaramo@ibm.com>

* Cleanup contribution guidelines and debug debug mode.

Signed-off-by: Leonard Carcaramo <lcarcaramo@ibm.com>

* Cleanup.

Signed-off-by: Leonard Carcaramo <lcarcaramo@ibm.com>

* Cleanup.

Signed-off-by: Leonard Carcaramo <lcarcaramo@ibm.com>

* Fix sdist packaging and pyproject.toml metadata.

Signed-off-by: Leonard Carcaramo <lcarcaramo@ibm.com>

---------

Signed-off-by: Leonard Carcaramo <lcarcaramo@ibm.com>

* Fix _C.pyi and removed unused import from cbxp.py.

Signed-off-by: Leonard Carcaramo <lcarcaramo@ibm.com>

---------

Signed-off-by: Leonard Carcaramo <lcarcaramo@ibm.com>
Co-authored-by: Elijah Swift <Elijah.Swift@ibm.com>
Co-authored-by: Varun Chennamadhava <varunchennamadhava@ibm.com>
@lcarcaramo
Copy link
Member

DCO signoff seems to be missing on all commits. Maybe consider squashing all of these commits into one commit that has a DCO signoff?

Signed-off-by: Elijah Swift <Elijah.Swift@IBM.com>
@ElijahSwiftIBM ElijahSwiftIBM force-pushed the feat/filter-control-blocks branch from 0d354a7 to 954478d Compare January 6, 2026 20:46
Signed-off-by: Elijah Swift <Elijah.Swift@IBM.com>
Signed-off-by: Elijah Swift <Elijah.Swift@IBM.com>
Signed-off-by: Elijah Swift <Elijah.Swift@IBM.com>
Signed-off-by: Elijah Swift <Elijah.Swift@IBM.com>
Signed-off-by: Elijah Swift <Elijah.Swift@IBM.com>
Signed-off-by: Elijah Swift <Elijah.Swift@IBM.com>
Signed-off-by: Elijah Swift <Elijah.Swift@IBM.com>
Signed-off-by: Elijah Swift <Elijah.Swift@IBM.com>
Signed-off-by: Elijah Swift <Elijah.Swift@IBM.com>
cbxp/cbxp.cpp Outdated
std::string control_block = control_block_name;
std::string control_block_string = control_block;

static cbxp_result_t cbxp_result = {nullptr, 0, -1};
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A static buffer stack allocated buffer should not be used here. This should be allocated in the heap using new.

https://www.geeksforgeeks.org/cpp/new-and-delete-operators-in-cpp-for-dynamic-memory/

cbxp/cbxp.cpp Outdated

void cbxp_free(cbxp_result_t* cbxp_result) {
free(cbxp_result->result_json);
free(cbxp_result);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use delete instead of free(). It is undefined behavior to free memory allocated with new using free().

filter_uint = std::stoul(filter_value, nullptr, 0);
} catch (...) {
Logger::getInstance().debug("'" + filter_value +
"' cannot be compared to an numeric value");
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Change an to a.

if (include_map_.find(includable) != include_map_.end()) {
if (options_map_.find(includable) != options_map_.end()) {
Logger::getInstance().debug("'" + includable +
"' already in inclusion list.");
Copy link
Member

@lcarcaramo lcarcaramo Feb 10, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"Include list already exists for the '" + includable + "' control block"

// Build a map of all includables_
include_map_[includable] = {};
Logger::getInstance().debug(
"Creating an entry in the include list for the '" + includable +
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"Initializing include list for the '"

Make the same changes for similar messages.


void ControlBlock::createIncludeList(const std::vector<std::string>& includes) {
Logger::getInstance().debug("Creating include list for the '" +
control_block_name_ + "' control block...");
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"Creating include lists for child control blocks to include with the '" + control_block_name_ + "' control block..."

return true;
}

void ControlBlock::createIncludeList(const std::vector<std::string>& includes) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ControlBlock::createIncludeLists()

}
Logger::getInstance().debug("Done");
Logger::getInstance().debug("'" + control_block_name_ +
"' include list has been created");
Copy link
Member

@lcarcaramo lcarcaramo Feb 10, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"Include lists for child control blocks to include with the '" + control_block_name_ + "' control block have been created"

ControlBlock::createFilterList(filters);
}

void ControlBlock::createFilterList(const std::vector<std::string>& filters) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ControlBlock::createFilterLists()


void ControlBlock::createFilterList(const std::vector<std::string>& filters) {
Logger::getInstance().debug("Creating filter list for the '" +
control_block_name_ + "' control block...");
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"Creating filter lists for the '" + control_block_name_ "' control block and included child control blocks..."

}
}
Logger::getInstance().debug("'" + control_block_name_ +
"' filter list has been created");
Copy link
Member

@lcarcaramo lcarcaramo Feb 10, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"Filter lists for the '" + control_block_name_ "' control block and included child control blocks have been created"

return true;
}

void ControlBlock::createIncludeList(const std::vector<std::string>& includes) {
Copy link
Member

@lcarcaramo lcarcaramo Feb 10, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Move Include processing functions before filter processing functions just so the definition of these function follow the same order in which the processing of include patterns and filters is done? Also make sure the order of the functions matches the prototypes defined in the corresponding header file.

"{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", result_json,
result_json_length, "return_code", rc);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looking at the Python C API reference more closely, I actually don't think you need to worry about keeping a copy of the result json string. Py_BuildValue() should copy and convert the C string into a Python str object. In this case, I don't think you need to worry about create a copy of the buffer and using a mutex for serialization. After calling Py_BuildValue(), I think you should be good to call cbxp_free() and then return control to the caller. There is no use after free here because Py_BuildValue() creates a copy of the C string. No need for any static data in this case either since we no longer need static data, which means we don't need a mutex and everything should therefore fully reentrent and thread safe.

https://docs.python.org/3/c-api/arg.html#c.Py_BuildValue

tests/test.py Outdated
)
self.assertIs(type(cbdata), dict)

def test_cbxp_can_use_decimal_filter_for_hex_field(self):
Copy link
Member

@lcarcaramo lcarcaramo Feb 10, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can_use_hex_filter_with_equal

tests/test.py Outdated
)
self.assertIs(type(cbdata), dict)

def test_cbxp_can_use_hex_filter_with_equal(self):
Copy link
Member

@lcarcaramo lcarcaramo Feb 10, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

test_cbxp_can_use_int_filter_with_hex_field_equal

self.assertIs(type(entry), dict)
self.assertIs(type(entry["ascbassb"]), dict)

# ============================================================================
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Double check that test function names are consistent and follow the same naming conventions.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Made a slight change but I think it looks good.

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

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Double check that the shell error tests match the Python error tests. I understand that some of these tests are specific to the shell interface.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

After some double checking, I believe this is now the case. There were a couple of tests that were merely performative, but I added a new testing function to test them effectively in this interface.

Signed-off-by: Elijah Swift <Elijah.Swift@IBM.com>
Signed-off-by: Elijah Swift <Elijah.Swift@IBM.com>
Signed-off-by: Elijah Swift <Elijah.Swift@IBM.com>
Signed-off-by: Elijah Swift <Elijah.Swift@IBM.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants