From d2ec8ca3055d758b8df3f6e4d485302bafcb3665 Mon Sep 17 00:00:00 2001 From: Stefan Machmeier Date: Tue, 24 Mar 2026 07:54:34 +0100 Subject: [PATCH 1/7] Fix save image function to store all images. --- heiFIP/extractor.cpp | 47 ++++++++++++++++++++++++++---------------- heiFIP/layers/init.cpp | 3 ++- 2 files changed, 31 insertions(+), 19 deletions(-) diff --git a/heiFIP/extractor.cpp b/heiFIP/extractor.cpp index 00003c0..2eed923 100644 --- a/heiFIP/extractor.cpp +++ b/heiFIP/extractor.cpp @@ -494,7 +494,7 @@ class FIPExtractor { } /** - * @brief Write the first 2D image in a UInt8Matrix vector to disk as a PNG file. + * @brief Write 2D image in a UInt8Matrix vector to disk as a PNG file. * * @param img A vector of 2D matrices. Only `img[0]` is used (grayscale). * @param output_path The desired file path (without extension). A ".png" is appended. @@ -514,28 +514,39 @@ class FIPExtractor { return; } - // Work with the first image slice (assuming grayscale) - const auto& grayscale_image = img[0]; - int height = static_cast(grayscale_image.size()); - int width = static_cast(grayscale_image[0].size()); + // Ensure parent directory exists + std::filesystem::path outp(output_path); + std::filesystem::create_directories(outp.parent_path()); + + // Save each image slice (assuming grayscale) + for (size_t k = 0; k < img.size(); ++k) { + const auto& grayscale_image = img[k]; + if (grayscale_image.empty() || grayscale_image[0].empty()) continue; - // Create an OpenCV Mat of the correct size and type (8‐bit unsigned, single channel) - cv::Mat mat(height, width, CV_8UC1); + int height = static_cast(grayscale_image.size()); + int width = static_cast(grayscale_image[0].size()); - // Copy pixel values row by row - for (int i = 0; i < height; ++i) { - uint8_t* row_ptr = mat.ptr(i); - for (int j = 0; j < width; ++j) { - row_ptr[j] = grayscale_image[i][j]; + // Create an OpenCV Mat of the correct size and type (8-bit unsigned, single channel) + cv::Mat mat(height, width, CV_8UC1); + + // Copy pixel values row by row + for (int i = 0; i < height; ++i) { + uint8_t* row_ptr = mat.ptr(i); + for (int j = 0; j < width; ++j) { + row_ptr[j] = grayscale_image[i][j]; + } } - } - // Append .png extension and ensure parent directory exists - std::filesystem::path outp(output_path + ".png"); - std::filesystem::create_directories(outp.parent_path()); + std::string final_path; + if (img.size() == 1) { + final_path = output_path + ".png"; + } else { + final_path = output_path + "_" + std::to_string(k) + ".png"; + } - // Write the PNG file to disk - cv::imwrite(outp.string(), mat); + // Write the PNG file to disk + cv::imwrite(final_path, mat); + } } private: diff --git a/heiFIP/layers/init.cpp b/heiFIP/layers/init.cpp index 1f80a4a..5074520 100644 --- a/heiFIP/layers/init.cpp +++ b/heiFIP/layers/init.cpp @@ -89,8 +89,9 @@ class PacketProcessor { pcpp::RawPacket rawPacket; std::unique_ptr rawPacketPt; size_t count = 0; + size_t limit = (maxCount == 0) ? 64 : maxCount; - while ((maxCount == 0 || count < maxCount) && reader.getNextPacket(rawPacket)) { + while (count < limit && reader.getNextPacket(rawPacket)) { rawPacketPt = std::make_unique(rawPacket); std::unique_ptr fippkt = preprocess(rawPacketPt, type); From 6954da9e4e2afec0c9375611f12bd677ee824c4b Mon Sep 17 00:00:00 2001 From: Stefan Machmeier Date: Tue, 24 Mar 2026 09:26:04 +0100 Subject: [PATCH 2/7] Update documentation --- README.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index e4cdb72..1a5671e 100644 --- a/README.md +++ b/README.md @@ -83,10 +83,10 @@ The idea to create heiFIP came from working with Deep Learning approaches to cla | Image Type | Description | Example | |------------|-------------|---------| -| Packet | Converts a single packet into a square image. Size depends on the total length | ![SMB Connection](https://raw.githubusercontent.com/stefanDeveloper/heiFIP/heiFIP-cpp/examples/packet.png?raw=true) | -| Flow | Converts a flow packet into a square image | ![SMB Connection](https://raw.githubusercontent.com/stefanDeveloper/heiFIP/heiFIP-cpp/examples/flow-tiled.png?raw=true) | -| Markov Transition Matrix Packet | Converts a packet into a Markov Transition Matrix. Size is fixed to 16x16. | ![SMB Connection](https://raw.githubusercontent.com/stefanDeveloper/heiFIP/heiFIP-cpp/examples/markov-packet.png?raw=true) | -| Markov Transition Matrix Flow | Converts a flow into a Markov Transition Matrix. It squares the image based on the number of packets | ![SMB Connection](https://raw.githubusercontent.com/stefanDeveloper/heiFIP/heiFIP-cpp/examples/markov-flow.png?raw=true) | +| Packet | Converts a single packet into a square image. Size depends on the total length | ![SMB Connection](https://raw.githubusercontent.com/stefanDeveloper/heiFIP/main/examples/packet.png?raw=true) | +| Flow | Converts a flow packet into a square image | ![SMB Connection](https://raw.githubusercontent.com/stefanDeveloper/heiFIP/main/examples/flow-tiled.png?raw=true) | +| Markov Transition Matrix Packet | Converts a packet into a Markov Transition Matrix. Size is fixed to 16x16. | ![SMB Connection](https://raw.githubusercontent.com/stefanDeveloper/heiFIP/main/examples/markov-packet.png?raw=true) | +| Markov Transition Matrix Flow | Converts a flow into a Markov Transition Matrix. It squares the image based on the number of packets | ![SMB Connection](https://raw.githubusercontent.com/stefanDeveloper/heiFIP/main/examples/markov-flow.png?raw=true) | ## Requirements @@ -131,7 +131,7 @@ cmake --build build -j$(nproc) After installation the command line interface can be used to extract images from pcap files witht he following command ```bash -./heiFIPCpp \ +./heiFIP \ --name HelloHeiFIP --input /path/to/capture.pcap \ --output /path/to/outdir \ From fd4455583f5d003bc647eb081f9c3f24b715d8ae Mon Sep 17 00:00:00 2001 From: Stefan Machmeier Date: Thu, 26 Mar 2026 08:53:40 +0100 Subject: [PATCH 3/7] fix: bug in max-pkgs results in 64 packets --- heiFIP/layers/init.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/heiFIP/layers/init.cpp b/heiFIP/layers/init.cpp index 5074520..1f80a4a 100644 --- a/heiFIP/layers/init.cpp +++ b/heiFIP/layers/init.cpp @@ -89,9 +89,8 @@ class PacketProcessor { pcpp::RawPacket rawPacket; std::unique_ptr rawPacketPt; size_t count = 0; - size_t limit = (maxCount == 0) ? 64 : maxCount; - while (count < limit && reader.getNextPacket(rawPacket)) { + while ((maxCount == 0 || count < maxCount) && reader.getNextPacket(rawPacket)) { rawPacketPt = std::make_unique(rawPacket); std::unique_ptr fippkt = preprocess(rawPacketPt, type); From 644bd899ad9e28379fe61e7a24d31e8cf89a9e01 Mon Sep 17 00:00:00 2001 From: Stefan Machmeier Date: Thu, 26 Mar 2026 14:22:20 +0100 Subject: [PATCH 4/7] feat: Refactor code and tests to avoid bugs in the future --- .github/workflows/build_test_linux.yml | 5 + .github/workflows/build_test_macos.yml | 5 + heiFIP/CMakeLists.txt | 56 ++-- ...PPacketImage.cpp => heiFIPPacketImage.hpp} | 37 +-- heiFIP/cli.cpp | 4 +- heiFIP/{extractor.cpp => extractor.hpp} | 27 +- heiFIP/images/{flow.cpp => flow.hpp} | 16 +- heiFIP/images/flow_tiled_auto.cpp | 256 ----------------- heiFIP/images/flow_tiled_auto.hpp | 87 ++++++ heiFIP/images/flow_tiled_fixed.cpp | 237 --------------- heiFIP/images/flow_tiled_fixed.hpp | 77 +++++ heiFIP/images/markov_chain.cpp | 270 ------------------ heiFIP/images/markov_chain.hpp | 101 +++++++ heiFIP/images/tile_utils.hpp | 92 ++++++ heiFIP/layers/{dns.cpp => dns.hpp} | 5 +- heiFIP/layers/{http.cpp => http.hpp} | 2 +- heiFIP/layers/{init.cpp => init.hpp} | 12 +- heiFIP/layers/{ip.cpp => ip.hpp} | 4 +- heiFIP/layers/{packet.cpp => packet.hpp} | 18 +- heiFIP/layers/{ssh.cpp => ssh.hpp} | 2 +- .../layers/{transport.cpp => transport.hpp} | 25 +- heiFIP/main.cpp | 6 +- heiFIP/plugins/{header.cpp => header.hpp} | 0 heiFIP/{runner.cpp => runner.hpp} | 2 +- heiFIP/tests/test_cli.cpp | 87 ++++++ heiFIP/tests/test_extractor.cpp | 61 ++++ heiFIP/tests/test_flow_image.cpp | 32 +++ heiFIP/tests/test_layers.cpp | 156 ++++++++++ heiFIP/tests/test_markov.cpp | 41 +++ heiFIP/tests/test_packet_image.cpp | 55 ++++ heiFIP/tests/test_tile_utils.cpp | 57 ++++ heiFIP/tests/test_tiled_images.cpp | 32 +++ heiFIP/vcpkg.json | 3 +- 33 files changed, 1025 insertions(+), 845 deletions(-) rename heiFIP/assets/{heiFIPPacketImage.cpp => heiFIPPacketImage.hpp} (92%) rename heiFIP/{extractor.cpp => extractor.hpp} (97%) rename heiFIP/images/{flow.cpp => flow.hpp} (92%) delete mode 100644 heiFIP/images/flow_tiled_auto.cpp create mode 100644 heiFIP/images/flow_tiled_auto.hpp delete mode 100644 heiFIP/images/flow_tiled_fixed.cpp create mode 100644 heiFIP/images/flow_tiled_fixed.hpp delete mode 100644 heiFIP/images/markov_chain.cpp create mode 100644 heiFIP/images/markov_chain.hpp create mode 100644 heiFIP/images/tile_utils.hpp rename heiFIP/layers/{dns.cpp => dns.hpp} (98%) rename heiFIP/layers/{http.cpp => http.hpp} (99%) rename heiFIP/layers/{init.cpp => init.hpp} (98%) rename heiFIP/layers/{ip.cpp => ip.hpp} (99%) rename heiFIP/layers/{packet.cpp => packet.hpp} (96%) rename heiFIP/layers/{ssh.cpp => ssh.hpp} (99%) rename heiFIP/layers/{transport.cpp => transport.hpp} (93%) rename heiFIP/plugins/{header.cpp => header.hpp} (100%) rename heiFIP/{runner.cpp => runner.hpp} (99%) create mode 100644 heiFIP/tests/test_cli.cpp create mode 100644 heiFIP/tests/test_extractor.cpp create mode 100644 heiFIP/tests/test_flow_image.cpp create mode 100644 heiFIP/tests/test_layers.cpp create mode 100644 heiFIP/tests/test_markov.cpp create mode 100644 heiFIP/tests/test_packet_image.cpp create mode 100644 heiFIP/tests/test_tile_utils.cpp create mode 100644 heiFIP/tests/test_tiled_images.cpp diff --git a/.github/workflows/build_test_linux.yml b/.github/workflows/build_test_linux.yml index 02b6ba1..3093ef7 100644 --- a/.github/workflows/build_test_linux.yml +++ b/.github/workflows/build_test_linux.yml @@ -49,3 +49,8 @@ jobs: -DCMAKE_BUILD_TYPE=Release \ -DCMAKE_TOOLCHAIN_FILE="$VCPKG_INSTALLATION_ROOT/scripts/buildsystems/vcpkg.cmake" cmake --build build -j$(nproc) + + - name: Run Tests + run: | + cd build + ctest --output-on-failure \ No newline at end of file diff --git a/.github/workflows/build_test_macos.yml b/.github/workflows/build_test_macos.yml index 6362922..bdcd9ec 100644 --- a/.github/workflows/build_test_macos.yml +++ b/.github/workflows/build_test_macos.yml @@ -45,3 +45,8 @@ jobs: -DCMAKE_BUILD_TYPE=Release \ -DCMAKE_TOOLCHAIN_FILE="$VCPKG_INSTALLATION_ROOT/scripts/buildsystems/vcpkg.cmake" cmake --build build -j$(sysctl -n hw.ncpu) + + - name: Run Tests + run: | + cd build + ctest --output-on-failure diff --git a/heiFIP/CMakeLists.txt b/heiFIP/CMakeLists.txt index 53d71d5..9c705d3 100644 --- a/heiFIP/CMakeLists.txt +++ b/heiFIP/CMakeLists.txt @@ -17,38 +17,62 @@ set(CMAKE_C_FLAGS_RELEASE "-O3 -march=native -funroll-loops -flto=auto -DNDEBU find_package(OpenSSL REQUIRED) find_package(OpenCV REQUIRED) find_package(PcapPlusPlus CONFIG REQUIRED) +find_package(GTest CONFIG REQUIRED) # === 4. User project headers === include_directories( + ${CMAKE_SOURCE_DIR} ${CMAKE_SOURCE_DIR}/assets ${CMAKE_SOURCE_DIR}/plugins ${CMAKE_SOURCE_DIR}/images ${CMAKE_SOURCE_DIR}/layers ) -# === 5. Executables === +# === 5. Enable Testing === +enable_testing() + +# === 6. Common Libraries === +set(HEIFIP_LIBS + OpenSSL::Crypto + OpenSSL::SSL + PcapPlusPlus::Common++ + PcapPlusPlus::Packet++ + PcapPlusPlus::Pcap++ + ${OpenCV_LIBS} +) + +# === 7. Executables === add_executable(heiFIP cli.cpp) add_executable(main main.cpp) -# main2.cpp removed - -# === 6. Apply libraries === -foreach(_target IN ITEMS heiFIP main) - target_link_libraries(${_target} PUBLIC - OpenSSL::Crypto - OpenSSL::SSL - PcapPlusPlus::Common++ - PcapPlusPlus::Packet++ - PcapPlusPlus::Pcap++ - ${OpenCV_LIBS} - ) + +# === 8. Tests === +add_executable(heiFIP_tests + tests/test_packet_image.cpp + tests/test_flow_image.cpp + tests/test_tiled_images.cpp + tests/test_markov.cpp + tests/test_tile_utils.cpp + tests/test_extractor.cpp + tests/test_layers.cpp + tests/test_cli.cpp +) + +# === 9. Apply libraries === +foreach(_target IN ITEMS heiFIP main heiFIP_tests) + target_link_libraries(${_target} PUBLIC ${HEIFIP_LIBS}) endforeach() -# === 7. Optimization and LTO in Release builds === -foreach(_target IN ITEMS heiFIP main) +target_link_libraries(heiFIP_tests PUBLIC GTest::gtest_main) + +# === 10. Optimization and LTO in Release builds === +foreach(_target IN ITEMS heiFIP main heiFIP_tests) target_compile_options(${_target} PUBLIC $<$:-O3 -march=native -funroll-loops -flto=auto -DNDEBUG> ) target_link_options(${_target} PUBLIC $<$:-flto=auto> ) -endforeach() \ No newline at end of file +endforeach() + +include(GoogleTest) +gtest_discover_tests(heiFIP_tests) \ No newline at end of file diff --git a/heiFIP/assets/heiFIPPacketImage.cpp b/heiFIP/assets/heiFIPPacketImage.hpp similarity index 92% rename from heiFIP/assets/heiFIPPacketImage.cpp rename to heiFIP/assets/heiFIPPacketImage.hpp index 28f579e..069418d 100644 --- a/heiFIP/assets/heiFIPPacketImage.cpp +++ b/heiFIP/assets/heiFIPPacketImage.hpp @@ -58,11 +58,8 @@ class heiFIPPacketImage { * callers need only supply the byte array; the header’s caplen is fetched internally. */ heiFIPPacketImage(std::vector data) - : _data(std::move(data)) - { - PcapPacketHeader packetHeader; - _cap_length = packetHeader.caplen; - } + : _data(std::move(data)), _cap_length(static_cast(_data.size())) + {} /** * @brief Constructor: initialize with raw byte data and immediately build a tiled image matrix. @@ -86,15 +83,12 @@ class heiFIPPacketImage { * so this constructor does that in one step, storing both the matrix and raw-binary copy. */ heiFIPPacketImage(std::vector data, int dim, int fill, bool auto_dim) - : _data(std::move(data)) + : _data(std::move(data)), _cap_length(static_cast(_data.size())) { - PcapPacketHeader packetHeader; - _cap_length = packetHeader.caplen; - // Build the tiled matrix and binaries representation in one call. - auto result = heiFIPPacketImage::get_matrix_tiled(fill, dim, auto_dim); - heiFIPPacketImage::matrix = std::move(result.first); - heiFIPPacketImage::binaries = std::move(result.second); + auto result = get_matrix_tiled(fill, dim, auto_dim); + matrix = std::move(result.first); + binaries = std::move(result.second); } ~heiFIPPacketImage() = default; @@ -128,12 +122,7 @@ class heiFIPPacketImage { * - Ensures callers cannot modify the original _data member. */ std::vector getHexData() const { - std::vector hexData; - hexData.reserve(_data.size()); - for (size_t i = 0; i < _data.size(); ++i) { - hexData.push_back(_data[i]); - } - return hexData; + return _data; } /** @@ -152,17 +141,11 @@ class heiFIPPacketImage { * - Converting each byte into two 4-bit values allows constructing those images. */ std::vector bit_array() const { - // 1) Copy bytes so as not to modify _data - std::vector data; - data.reserve(_data.size()); - for (uint8_t byte : _data) { - data.push_back(byte); - } - // 2) Build a concatenated string of bits, 8 bits per byte + // Build a concatenated string of bits, 8 bits per byte std::string bytes_as_bits; - bytes_as_bits.reserve(data.size() * 8); - for (unsigned char byte : data) { + bytes_as_bits.reserve(_data.size() * 8); + for (unsigned char byte : _data) { bytes_as_bits += std::bitset<8>(byte).to_string(); } diff --git a/heiFIP/cli.cpp b/heiFIP/cli.cpp index a975eff..09eeb8b 100644 --- a/heiFIP/cli.cpp +++ b/heiFIP/cli.cpp @@ -3,8 +3,8 @@ #include #include -#include "extractor.cpp" -#include "runner.cpp" +#include "extractor.hpp" +#include "runner.hpp" /// @brief Prints usage/help information for the CLI tool. void print_usage(const char* progName) { diff --git a/heiFIP/extractor.cpp b/heiFIP/extractor.hpp similarity index 97% rename from heiFIP/extractor.cpp rename to heiFIP/extractor.hpp index 2eed923..b23b806 100644 --- a/heiFIP/extractor.cpp +++ b/heiFIP/extractor.hpp @@ -7,12 +7,12 @@ #include #include -#include "init.cpp" -#include "flow.cpp" -#include "flow_tiled_auto.cpp" -#include "flow_tiled_fixed.cpp" -#include "markov_chain.cpp" -#include "heiFIPPacketImage.cpp" +#include "init.hpp" +#include "flow.hpp" +#include "flow_tiled_auto.hpp" +#include "flow_tiled_fixed.hpp" +#include "markov_chain.hpp" +#include "heiFIPPacketImage.hpp" /** @@ -264,6 +264,21 @@ class FIPExtractor { args ); } + + /** + * @brief Public access to packet reading functionality. + */ + std::vector> getPackets( + const std::string& input_file, + PacketProcessorType preprocessing_type = PacketProcessorType::NONE, + bool remove_duplicates = false, + size_t maxCount = 64 + ) { + if (!std::filesystem::exists(input_file)) { + return {}; + } + return processor.readPacketsFile(input_file, preprocessing_type, remove_duplicates, maxCount); + } /** * @brief Convert an in-memory list of RawPacket pointers to image(s). diff --git a/heiFIP/images/flow.cpp b/heiFIP/images/flow.hpp similarity index 92% rename from heiFIP/images/flow.cpp rename to heiFIP/images/flow.hpp index 05b0e18..41953b9 100644 --- a/heiFIP/images/flow.cpp +++ b/heiFIP/images/flow.hpp @@ -11,7 +11,7 @@ #include #include "NetworkTrafficImage.hpp" -#include "heiFIPPacketImage.cpp" +#include "heiFIPPacketImage.hpp" /** * @class FlowImage @@ -139,18 +139,20 @@ class FlowImage : public NetworkTrafficImage { } // If placing each packet’s bytes on its own row else { - // a) Determine maximum packet length - size_t maxLength = 0; - for (const auto& binary : binaries) { - maxLength = std::max(maxLength, binary.size()); + // a) Use dim as width (if dim > 0), otherwise use maximum packet length + size_t width = (dim > 0) ? static_cast(dim) : 0; + if (width == 0) { + for (const auto& binary : binaries) { + width = std::max(width, binary.size()); + } } - // b) Build one row per packet, padding each to maxLength with `fill` + // b) Build one row per packet, padding or truncating each to width with `fill` std::vector> reshaped; reshaped.reserve(binaries.size()); for (const auto& binary : binaries) { std::vector row = binary; // Copy raw bytes - row.resize(maxLength, static_cast(fill)); // Pad to uniform length + row.resize(width, static_cast(fill)); // Pad or truncate to fixed width reshaped.push_back(std::move(row)); } diff --git a/heiFIP/images/flow_tiled_auto.cpp b/heiFIP/images/flow_tiled_auto.cpp deleted file mode 100644 index cf9e79f..0000000 --- a/heiFIP/images/flow_tiled_auto.cpp +++ /dev/null @@ -1,256 +0,0 @@ -#pragma once - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "heiFIPPacketImage.cpp" -#include "NetworkTrafficImage.hpp" - -/** - * @class FlowImageTiledAuto - * @brief Builds a square, tiled image from a sequence of packet images, automatically determining tile dimensions. - * - * Inherits from NetworkTrafficImage, which stores a default fill value and base dimension. - * Responsibilities: - * - Convert each packet’s raw bytes into its own dim×dim tile, padding/truncating as needed. - * - Arrange all those tiles into a larger square grid (dim_total×dim_total), where dim_total = ceil(sqrt(numTiles)). - * - Provide getters for the final tiled matrix and the original per-packet binaries. - */ -class FlowImageTiledAuto : public NetworkTrafficImage { -public: - /** - * @brief Constructor: prepare tiled flow image using automatic dimension calculation if requested. - * - * @param packets Vector of heiFIPPacketImage, each containing raw bytes for one packet. - * @param dim Base dimension for each packet’s tile (width = height = dim) if auto_dim=false. - * If auto_dim=true, each tile’s dim is recalculated as ceil(sqrt(maxPacketLength)). - * @param fill Byte value (0–255) used to pad shorter packet byte arrays when building each tile. - * @param auto_dim If true, automatically set each tile’s dim = ceil(sqrt(max length among all packets)). - * - * Workflow: - * 1. Call NetworkTrafficImage(fill, dim) to store base fill and dim. - * 2. Store given `packets` and `auto_dim` flag in members. - * 3. Call get_matrix_tiled(fill, dim, auto_dim, packets), which: - * a. Extracts raw bytes from each packet image. - * b. Finds max packet length; if auto_dim, compute dim = ceil(sqrt(maxLength)). - * c. For each packet, reshape its bytes into a dim×dim tile (row-major), padding with `fill`. - * d. Compute dim_total = ceil(sqrt(numPackets)). - * e. Arrange all packet tiles into a dim_total×dim_total grid by: - * • Placing tiles row by row, concatenating horizontally via npconcatenate(). - * • Padding with zero tiles (via npzero()) if fewer than dim_total² packets. - * f. Return {tiledMatrix, binaries}, where binaries is the vector of each packet’s raw byte vector. - * 4. Store the returned tiled matrix and binaries in member variables. - */ - FlowImageTiledAuto(const std::vector& packets, int dim = 16, int fill = 0, bool auto_dim = false) - : NetworkTrafficImage(fill, dim), packets(packets), auto_dim(auto_dim) - { - auto result = get_matrix_tiled(fill, dim, auto_dim, packets); - matrix = std::move(result.first); - binaries = std::move(result.second); - } - - /** - * @brief Get the final tiled image matrix (square of tiles stacked). - * @return Reference to a 2D vector of size [dim_total*dim][dim_total*dim]. - */ - const std::vector>& get_matrix() const { - return matrix; - } - - /** - * @brief Get the raw byte vectors for each packet (binaries used to build tiles). - * @return Reference to a vector of vectors, one per packet. - */ - std::vector>& get_binaries() { - return binaries; - } - -private: - std::vector packets; ///< Input packet images - bool auto_dim; ///< Whether to recalc tile dim = ceil(sqrt(maxPacketLength)) - std::vector> matrix; ///< Final tiled flow image - std::vector> binaries; ///< Raw byte vectors for each packet - - /** - * @brief Build per-packet tiles and assemble them into one large square matrix. - * - * @param fill Byte value to use when padding individual packet tiles. - * @param dim Base dimension for each packet tile (unless overridden by auto_dim). - * @param auto_dim If true, recompute dim = ceil(sqrt(max packet length)). - * @param packets Vector of heiFIPPacketImage, each containing raw bytes for one packet. - * @return pair: - * - first: 2D tiled image (size = dim_total*dim × dim_total*dim). - * - second: Original raw byte vectors (for reference). - * - * Workflow: - * 1. Extract raw bytes from each packet (packet.getHexData()) into `binaries`. - * 2. Determine max packet length across all binaries. - * 3. If auto_dim=true, set dim = ceil(sqrt(maxLength)). - * 4. For each packet’s byte vector `x`: - * a. Allocate a dim×dim tile, initialized to `fill`. - * b. Copy x[k] into tile[i][j] for k from 0 to x.size()-1, filling row-major: - * • i = k / dim, j = k % dim; stop when k ≥ x.size() or out of bounds. - * c. Store that tile in a temporary list `result` (vector of 2D arrays). - * 5. Compute dim_total = ceil(sqrt(numPackets)) → number of tiles per row/column. - * 6. Call tile_images(result, dim_total, dim) to arrange tiles into one big matrix: - * a. Build rows of concatenated tiles horizontally: each row has dim_total tiles side by side. - * Use npzero(dim) to fill missing tiles if numPackets < dim_total². - * Use npconcatenate() to join tiles horizontally (rows must have same height=dim). - * b. After building each row (dim rows high, width = dim_total*dim), stack all rows vertically. - * 7. Return {tiledMatrix, binaries}. - */ - std::pair>, std::vector>> - get_matrix_tiled(int fill, int dim, bool auto_dim, const std::vector& packets) { - // 1) Extract raw bytes from each packet and push into binaries - std::vector> binaries; - for (const heiFIPPacketImage& packet : packets) { - binaries.push_back(packet.getHexData()); - } - - // 2) Determine the maximum length among all packet byte vectors - size_t length = 0; - for (const auto& b : binaries) { - length = std::max(length, b.size()); - } - - // 3) If auto_dim=true, set each tile’s dim = ceil(sqrt(length)) - if (auto_dim) { - dim = static_cast(std::ceil(std::sqrt(static_cast(length)))); - } - - // 4) Build a 3D list of per-packet dim×dim tiles - std::vector>> result; - for (const auto& x : binaries) { - // a) Initialize a dim×dim tile with `fill` - std::vector> reshaped(dim, std::vector(dim, static_cast(fill))); - - // b) Copy x[k] into reshaped row-major until x is exhausted or tile is filled - size_t k = 0; - for (int i = 0; i < dim && k < x.size(); ++i) { - for (int j = 0; j < dim && k < x.size(); ++j) { - reshaped[i][j] = x[k++]; - } - } - result.push_back(std::move(reshaped)); - } - - // 5) Compute dim_total = ceil(sqrt(number of tiles)) → grid is dim_total×dim_total tiles - size_t length_total = result.size(); - uint dim_total = static_cast(std::ceil(std::sqrt(static_cast(length_total)))); - - // 6) Arrange all tiles into a large tiled image - std::vector> fh = tile_images(result, dim_total, dim); - return { fh, binaries }; - } - - /** - * @brief Create a dim×dim tile filled with zeros. - * - * @param dim Dimension for both width and height. - * @return 2D vector of size [dim][dim], all elements = 0. - * - * Why: - * - Used to fill grid slots when numPackets < dim_total², ensuring the final image remains square. - */ - std::vector> npzero(size_t dim) { - return std::vector>(dim, std::vector(dim, static_cast(0))); - } - - /** - * @brief Horizontally concatenate two same-height images (2D arrays). - * - * @param img1 First image: vector of rows, each row is a vector. - * @param img2 Second image: must have same number of rows as img1. - * @return Concatenated image: each row is img1[row] followed by img2[row]. - * - * Throws: - * - std::invalid_argument if img1 and img2 have different heights. - * - * Why: - * - Used in tile_images() to join tiles side by side when building each row of the grid. - */ - std::vector> npconcatenate(const std::vector>& img1, - const std::vector>& img2) - { - if (img1.empty()) return img2; - if (img2.empty()) return img1; - - if (img1.size() != img2.size()) { - throw std::invalid_argument("Images must have the same number of rows to concatenate horizontally."); - } - - std::vector> result = img1; - for (size_t i = 0; i < result.size(); ++i) { - result[i].insert(result[i].end(), img2[i].begin(), img2[i].end()); - } - return result; - } - - /** - * @brief Arrange a list of per-packet tiles into a single large square image. - * - * @param images 3D vector: [numTiles][dim][dim], each is a dim×dim tile. - * @param cols Number of tiles per row/column in the final grid (dim_total). - * @param dim Dimension of each tile (width = height = dim). - * @return 2D vector of size [dim_total*dim][dim_total*dim], the tiled image. - * - * Workflow: - * 1. For each row i in [0..cols−1]: - * a. Initialize an empty 2D array `row` (to accumulate tile rows). - * b. For each column j in [0..cols−1]: - * - If k < images.size(), let im = images[k], else let im = npzero(dim). - * - If `row` is empty, set row = im; else row = npconcatenate(row, im). - * - Increment k. - * c. Append `row` to `rows` (vector of row-blocks). - * 2. Initialize `tiled` = rows[0]. - * 3. For i in [1..rows.size()−1], append rows[i] to the bottom of `tiled` using vector::insert. - * 4. Return `tiled`, which now has height = cols*dim and width = cols*dim. - * - * Why: - * - Ensures that if there are fewer tiles than cols², the missing slots are zero-filled, maintaining a square. - * - Maintains row-major order: first fill the top-left tile, then the next tile to its right, etc. - */ - std::vector> tile_images(const std::vector>>& images, - const uint cols, const uint dim) - { - std::vector>> rows; - size_t k = 0; // Tracks which tile we’re on - - // 1) Build each tile row (concatenate tiles horizontally) - for (size_t i = 0; i < cols; ++i) { - std::vector> row; // Start with an empty row-block - for (size_t j = 0; j < cols; ++j) { - std::vector> im; - if (k < images.size()) { - im = images[k]; // Use actual tile - } else { - im = npzero(dim); // Use zero tile if no more packets - } - - if (row.empty()) { - row = std::move(im); - } else { - row = npconcatenate(row, im); - } - ++k; - } - rows.push_back(std::move(row)); - } - - // 2) Stack all rows vertically to form the final tiled image - std::vector> tiled = std::move(rows[0]); - for (size_t i = 1; i < rows.size(); ++i) { - tiled.insert(tiled.end(), rows[i].begin(), rows[i].end()); - } - return tiled; - } -}; \ No newline at end of file diff --git a/heiFIP/images/flow_tiled_auto.hpp b/heiFIP/images/flow_tiled_auto.hpp new file mode 100644 index 0000000..6985277 --- /dev/null +++ b/heiFIP/images/flow_tiled_auto.hpp @@ -0,0 +1,87 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "heiFIPPacketImage.hpp" +#include "NetworkTrafficImage.hpp" +#include "tile_utils.hpp" + +/** + * @class FlowImageTiledAuto + * @brief Builds a square, tiled image from a sequence of packet images, automatically determining tile dimensions. + * + * Inherits from NetworkTrafficImage, which stores a default fill value and base dimension. + * Responsibilities: + * - Convert each packet's raw bytes into its own dim×dim tile, padding/truncating as needed. + * - Arrange all those tiles into a larger square grid (dim_total×dim_total), where dim_total = ceil(sqrt(numTiles)). + * - Provide getters for the final tiled matrix and the original per-packet binaries. + */ +class FlowImageTiledAuto : public NetworkTrafficImage { +public: + FlowImageTiledAuto(const std::vector& packets, int dim = 16, int fill = 0, bool auto_dim = false) + : NetworkTrafficImage(fill, dim), packets(packets), auto_dim(auto_dim) + { + auto result = get_matrix_tiled(fill, dim, auto_dim, packets); + matrix = std::move(result.first); + binaries = std::move(result.second); + } + + const std::vector>& get_matrix() const { + return matrix; + } + + std::vector>& get_binaries() { + return binaries; + } + +private: + std::vector packets; + bool auto_dim; + std::vector> matrix; + std::vector> binaries; + + std::pair>, std::vector>> + get_matrix_tiled(int fill, int dim, bool auto_dim, const std::vector& packets) { + std::vector> binaries; + for (const heiFIPPacketImage& packet : packets) { + binaries.push_back(packet.getHexData()); + } + + size_t length = 0; + for (const auto& b : binaries) { + length = std::max(length, b.size()); + } + + if (auto_dim) { + dim = static_cast(std::ceil(std::sqrt(static_cast(length)))); + } + + std::vector>> result; + for (const auto& x : binaries) { + std::vector> reshaped(dim, std::vector(dim, static_cast(fill))); + size_t k = 0; + for (int i = 0; i < dim && k < x.size(); ++i) { + for (int j = 0; j < dim && k < x.size(); ++j) { + reshaped[i][j] = x[k++]; + } + } + result.push_back(std::move(reshaped)); + } + + size_t length_total = result.size(); + unsigned int dim_total = static_cast(std::ceil(std::sqrt(static_cast(length_total)))); + + std::vector> fh = tile_utils::tile_images(result, dim_total, static_cast(dim)); + return { fh, binaries }; + } +}; \ No newline at end of file diff --git a/heiFIP/images/flow_tiled_fixed.cpp b/heiFIP/images/flow_tiled_fixed.cpp deleted file mode 100644 index cff317d..0000000 --- a/heiFIP/images/flow_tiled_fixed.cpp +++ /dev/null @@ -1,237 +0,0 @@ -#pragma once - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "heiFIPPacketImage.cpp" -#include "NetworkTrafficImage.hpp" - -/** - * @class FlowImageTiledFixed - * @brief Builds a fixed-grid tiled image from a sequence of packet images. - * - * Inherits from NetworkTrafficImage, which provides base logic for traffic-based images. - * Responsibilities: - * - Convert each packet’s raw bytes into its own dim×dim tile, padding/truncating as needed. - * - Arrange all those tiles into a fixed-size grid with `cols` tiles per row and per column. - * - Provide getters for both the tiled matrix and the original per-packet binaries. - */ -class FlowImageTiledFixed : public NetworkTrafficImage { -public: - /** - * @brief Constructor: prepare tiled flow image using a fixed number of columns. - * - * @param packets Vector of heiFIPPacketImage, each containing raw bytes for one packet. - * @param dim Dimension for each packet’s tile (width = height = dim). - * @param fill Byte value (0–255) used to pad shorter packet byte arrays when building each tile. - * @param cols Number of tiles per row (and column) in the final grid. Grid is cols×cols tiles. - * - * Workflow: - * 1. Call NetworkTrafficImage(fill, dim) to store base fill and dim. - * 2. Store input `packets` and `cols` in member variables. - * 3. Call get_matrix_tiled(fill, dim, packets), which: - * a. Extracts raw bytes from each packet image into `binaries`. - * b. For each packet’s byte vector `x`: - * i. Allocate a dim×dim tile, initialized to `fill`. - * ii. Copy x[k] into tile[i][j] in row-major until x is exhausted or tile is filled. - * iii. Append that tile to a local list `result` (vector of 2D arrays). - * c. Call tile_images(result, cols, dim) to arrange exactly cols×cols tiles: - * i. Place tiles row by row, concatenating horizontally with npconcatenate(). - * ii. If there are fewer than cols² tiles, use npzero(dim) to fill missing slots. - * iii. Stack all rows vertically to form the final matrix. - * 4. Store the returned matrix and binaries in member variables. - */ - FlowImageTiledFixed(const std::vector& packets, int dim = 16, int fill = 0, int cols = 3) - : NetworkTrafficImage(fill, dim), packets(packets), cols(cols) - { - auto result = get_matrix_tiled(fill, dim, packets); - matrix = std::move(result.first); - binaries = std::move(result.second); - } - - /** - * @brief Get the final tiled image matrix (fixed size: cols*dim by cols*dim). - * @return Reference to a 2D vector representing the tiled image. - */ - const std::vector>& get_matrix() const { - return matrix; - } - - /** - * @brief Get the raw byte vectors for each packet (binaries used to build tiles). - * @return Reference to a vector of vectors, one per packet’s bytes. - */ - std::vector>& get_binaries() { - return binaries; - } - -private: - std::vector packets; ///< Input packet images - int cols; ///< Number of tiles per row/column - std::vector> matrix; ///< Final tiled flow image - std::vector> binaries; ///< Raw byte vectors for each packet - - /** - * @brief Build per-packet tiles and assemble them into a fixed-size grid. - * - * @param fill Byte value to use when padding individual packet tiles. - * @param dim Dimension for each packet tile (width = height = dim). - * @param packets Vector of heiFIPPacketImage, each containing raw bytes for one packet. - * @return pair: - * - first: 2D tiled image (size = cols*dim × cols*dim). - * - second: Original raw byte vectors (for reference). - * - * Workflow: - * 1. Extract raw bytes from each packet (packet.getHexData()) into `binaries`. - * 2. For each packet’s byte vector `x`: - * a. Allocate a dim×dim tile, initialized to `fill`. - * b. Copy x[k] into tile[i][j] in row-major until x is exhausted or tile is filled. - * c. Append that tile to a local list `result` (vector of 2D tiles). - * 3. Call tile_images(result, cols, dim) to arrange exactly cols×cols tiles: - * a. Iterate over `cols` rows; for each row, iterate `cols` columns: - * • If a tile is available (k < result.size()), use it; else use npzero(dim). - * • Concatenate horizontally onto `row` via npconcatenate(). - * b. Append each completed `row` to `rows`. - * c. Stack all `rows` vertically into one matrix: first row, then subsequent rows appended. - * 4. Return {tiledMatrix, binaries}. - */ - std::pair>, std::vector>> - get_matrix_tiled(int fill, int dim, const std::vector& packets) { - std::vector> binaries; - - // 1) Extract raw bytes from each heiFIPPacketImage - for (const heiFIPPacketImage& packet : packets) { - binaries.push_back(packet.getHexData()); - } - - // 2) Build a dim×dim tile for each packet’s bytes - std::vector>> result; - for (const auto& x : binaries) { - // a) Initialize a dim×dim tile filled with `fill` - std::vector> reshaped(dim, std::vector(dim, static_cast(fill))); - // b) Copy bytes into reshaped row-major - size_t k = 0; - for (size_t i = 0; i < static_cast(dim) && k < x.size(); ++i) { - for (size_t j = 0; j < static_cast(dim) && k < x.size(); ++j) { - reshaped[i][j] = x[k++]; - } - } - result.push_back(std::move(reshaped)); - } - - // 3) Arrange the tiles into a fixed cols×cols grid - std::vector> fh = tile_images(result, static_cast(cols), static_cast(dim)); - return { fh, binaries }; - } - - /** - * @brief Create a dim×dim tile filled entirely with zeros. - * - * @param dim Dimension for both width and height. - * @return 2D vector of size [dim][dim], all elements = 0. - * - * Why: - * - Used in tile_images() to fill missing slots when fewer than cols² packets are available. - */ - std::vector> npzero(size_t dim) { - return std::vector>(dim, std::vector(dim, static_cast(0))); - } - - /** - * @brief Horizontally concatenate two same-height images (2D arrays). - * - * @param img1 First image: vector of rows, each row is a vector. - * @param img2 Second image: must have same number of rows as img1. - * @return Concatenated image: each row is img1[row] followed by img2[row]. - * - * Throws: - * - std::invalid_argument if img1 and img2 have different heights. - * - * Why: - * - Used in tile_images() to join tiles side by side when building each row of the grid. - */ - std::vector> npconcatenate(const std::vector>& img1, - const std::vector>& img2) - { - if (img1.empty()) return img2; - if (img2.empty()) return img1; - - if (img1.size() != img2.size()) { - throw std::invalid_argument("Images must have the same number of rows to concatenate horizontally."); - } - - std::vector> result = img1; - for (size_t i = 0; i < result.size(); ++i) { - result[i].insert(result[i].end(), img2[i].begin(), img2[i].end()); - } - return result; - } - - /** - * @brief Arrange a list of per-packet tiles into one large fixed-grid image. - * - * @param images 3D vector: [numTiles][dim][dim], each is a dim×dim tile. - * @param cols Number of tiles per row/column in the final grid (fixed). - * @param dim Dimension of each tile (width = height = dim). - * @return 2D vector of size [cols*dim][cols*dim], the tiled image. - * - * Workflow: - * 1. For each row i in [0..cols−1]: - * a. Initialize an empty 2D array `row`. - * b. For each column j in [0..cols−1]: - * - If k < images.size(), let im = images[k]; else im = npzero(dim). - * - If `row` is empty, set row = im; else row = npconcatenate(row, im). - * - Increment k. - * c. Append this completed `row` (size = dim rows, width = cols*dim) into `rows`. - * 2. Initialize `tiled` = rows[0]. - * 3. For i in [1..rows.size()−1], append rows[i] to the bottom of `tiled`. - * 4. Return `tiled`, which now has height = cols*dim and width = cols*dim. - * - * Why: - * - Having a fixed number of columns ensures a consistent final image size even if the number - * of packets < cols² (missing slots become zero-filled tiles). - */ - std::vector> tile_images(const std::vector>>& images, - const uint cols, const uint dim) - { - std::vector>> rows; - size_t k = 0; // Tracks which tile we’re on - - // 1) Build each tile row (concatenate tiles horizontally) - for (size_t i = 0; i < cols; ++i) { - std::vector> row; // Start with an empty row-block - for (size_t j = 0; j < cols; ++j) { - std::vector> im; - if (k < images.size()) { - im = images[k]; // Use actual tile - } else { - im = npzero(dim); // Zero tile if fewer than cols² packets - } - - if (row.empty()) { - row = std::move(im); - } else { - row = npconcatenate(row, im); - } - ++k; - } - rows.push_back(std::move(row)); - } - - // 2) Stack all rows vertically to form the final tiled image - std::vector> tiled = std::move(rows[0]); - for (size_t i = 1; i < rows.size(); ++i) { - tiled.insert(tiled.end(), rows[i].begin(), rows[i].end()); - } - return tiled; - } -}; \ No newline at end of file diff --git a/heiFIP/images/flow_tiled_fixed.hpp b/heiFIP/images/flow_tiled_fixed.hpp new file mode 100644 index 0000000..b11c04f --- /dev/null +++ b/heiFIP/images/flow_tiled_fixed.hpp @@ -0,0 +1,77 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "heiFIPPacketImage.hpp" +#include "NetworkTrafficImage.hpp" +#include "tile_utils.hpp" + +/** + * @class FlowImageTiledFixed + * @brief Builds a fixed-grid tiled image from a sequence of packet images. + * + * Inherits from NetworkTrafficImage, which provides base logic for traffic-based images. + * Responsibilities: + * - Convert each packet's raw bytes into its own dim×dim tile, padding/truncating as needed. + * - Arrange all those tiles into a fixed-size grid with `cols` tiles per row and per column. + * - Provide getters for both the tiled matrix and the original per-packet binaries. + */ +class FlowImageTiledFixed : public NetworkTrafficImage { +public: + FlowImageTiledFixed(const std::vector& packets, int dim = 16, int fill = 0, int cols = 3) + : NetworkTrafficImage(fill, dim), packets(packets), cols(cols) + { + auto result = get_matrix_tiled(fill, dim, packets); + matrix = std::move(result.first); + binaries = std::move(result.second); + } + + const std::vector>& get_matrix() const { + return matrix; + } + + std::vector>& get_binaries() { + return binaries; + } + +private: + std::vector packets; + int cols; + std::vector> matrix; + std::vector> binaries; + + std::pair>, std::vector>> + get_matrix_tiled(int fill, int dim, const std::vector& packets) { + std::vector> binaries; + + for (const heiFIPPacketImage& packet : packets) { + binaries.push_back(packet.getHexData()); + } + + std::vector>> result; + for (const auto& x : binaries) { + std::vector> reshaped(dim, std::vector(dim, static_cast(fill))); + size_t k = 0; + for (size_t i = 0; i < static_cast(dim) && k < x.size(); ++i) { + for (size_t j = 0; j < static_cast(dim) && k < x.size(); ++j) { + reshaped[i][j] = x[k++]; + } + } + result.push_back(std::move(reshaped)); + } + + std::vector> fh = tile_utils::tile_images( + result, static_cast(cols), static_cast(dim)); + return { fh, binaries }; + } +}; \ No newline at end of file diff --git a/heiFIP/images/markov_chain.cpp b/heiFIP/images/markov_chain.cpp deleted file mode 100644 index 047a410..0000000 --- a/heiFIP/images/markov_chain.cpp +++ /dev/null @@ -1,270 +0,0 @@ -#pragma once - -#include -#include -#include -#include -#include "heiFIPPacketImage.cpp" -#include "NetworkTrafficImage.hpp" - -/** - * @class MarkovTransitionMatrix - * @brief Base class for computing a normalized, grayscale Markov transition matrix from a sequence of symbols. - * - * Responsibilities: - * - Given a 1D vector of “transitions” (values in [0..15]), count the transitions between consecutive symbols. - * - Normalize each row of the count matrix so that probabilities sum to 1, then scale to [0..255]. - * - Return the resulting 16×16 matrix of uint8_t intensities. - */ -class MarkovTransitionMatrix : public NetworkTrafficImage { -public: - /** - * @brief Compute a 16×16 Markov transition matrix from a sequence of 4-bit symbols. - * - * @param transitions Vector of length L containing values 0..15. Each adjacent pair - * (transitions[k], transitions[k+1]) contributes to the count at [i][j]. - * @return 2D vector of size [16][16], where each cell holds a normalized probability - * scaled to [0..255]. Rows with zero total count remain all zeros. - * - * Workflow: - * 1. Allocate a 16×16 uintMatrix initialized to zero (counts of each transition). - * 2. For k in [0..L-2], let i = transitions[k], j = transitions[k+1]; increment uintMatrix[i][j]. - * 3. For each row i in uintMatrix: - * a. Compute sum = Σ_j uintMatrix[i][j]. - * b. If sum > 0, for each j: compute probability = uintMatrix[i][j] / sum. - * Then multiply by 255, clamp to [0..255], and store back as uint8_t. - * 4. Return the resulting 16×16 grayscale matrix. - * - * Why: - * - Captures the first-order Markov distribution between successive 4-bit values in a packet’s bit array. - * - Scaling to 0–255 yields a grayscale image representation suitable for CNNs or other image-based analysis. - */ - std::vector> transition_matrix(const std::vector& transitions) { - const size_t n = 16; - // 1) Initialize a 16×16 count matrix to zero - std::vector> uintMatrix(n, std::vector(n, 0)); - - // 2) Count transitions between consecutive symbols - for (size_t k = 0; k + 1 < transitions.size(); ++k) { - size_t i = transitions[k]; - size_t j = transitions[k + 1]; - uintMatrix[i][j] += 1; - } - - // 3) Normalize each row to probabilities and scale to [0..255] - for (auto& row : uintMatrix) { - double sum = 0.0; - // Compute total count for this row - for (double value : row) { - sum += value; - } - if (sum > 0.0) { - // Convert each count to a probability, multiply by 255, clamp, and cast to uint8_t - for (auto& value : row) { - double prob = static_cast(value) / sum; - double scaled = prob * 255.0; - // clamp to [0..255] - value = static_cast(std::clamp(scaled, 0.0, 255.0)); - } - } - // If sum == 0, leave row as all zeros - } - - return uintMatrix; - } -}; - -/** - * @class MarkovTransitionMatrixFlow - * @brief Builds a larger image by computing a Markov transition matrix for each packet in a flow, - * then arranging all 16×16 matrices into a fixed grid of tiles. - * - * Inherits from MarkovTransitionMatrix to leverage the transition_matrix() method. - * Responsibilities: - * - For each heiFIPPacketImage in `packets`, extract its 4-bit bit array and compute a 16×16 matrix. - * - Tile all per-packet matrices into a grid with `cols` tiles per row and column. - * - Store the final tiled matrix as a single 2D vector, accessible via get_matrix(). - */ -class MarkovTransitionMatrixFlow : public MarkovTransitionMatrix { -public: - /** - * @brief Constructor: compute and tile per-packet Markov matrices. - * - * @param packets Vector of heiFIPPacketImage, each representing one packet in the flow. - * @param cols Number of tiles per row and per column in the final grid (grid is cols×cols). - * - * Workflow: - * 1. Store `packets` and `cols`. - * 2. For each packet in `packets`: - * a. Call packet.bit_array() to get a vector of 4-bit values. - * b. Pass that vector to transition_matrix() to get a 16×16 grayscale matrix. - * c. Append that 16×16 matrix to a local list `result`. - * 3. Call tile_images(result, cols, 16) to arrange all 16×16 matrices into one large image: - * - Creates a cols×cols grid of 16×16 tiles. - * - If fewer than cols² matrices, fill missing spots with zero tiles (npzero). - * - Concatenate horizontally then vertically as necessary. - * 4. Store the final tiled image in member `matrix`. - */ - MarkovTransitionMatrixFlow(const std::vector& packets, uint cols = 4) - : packets(packets), cols(cols) - { - std::vector>> result; - // 2) Compute a 16×16 Markov matrix for each packet - for (const heiFIPPacketImage& packet : packets) { - std::vector transition = packet.bit_array(); - std::vector> m = transition_matrix(transition); - result.push_back(std::move(m)); - } - // 3) Tile all 16×16 matrices into a cols×cols grid - matrix = tile_images(result, cols, 16); - } - - /// Accessor for the final tiled flow image - const std::vector>& get_matrix() const { - return matrix; - } - -private: - std::vector packets; ///< Each packet in the flow - uint cols; ///< Number of tiles per row/column - MarkovTransitionMatrix transitionMatrix; ///< Base class instance (not strictly necessary) - std::vector> matrix; ///< Final tiled image composed of 16×16 tiles - - /** - * @brief Create a 16×16 tile filled with zeros (if a packet’s matrix is missing). - * - * @param dim Tile dimension (16 for Markov matrices). - * @return 2D vector of size [dim][dim], all zeros. - */ - std::vector> npzero(size_t dim) { - return std::vector>(dim, std::vector(dim, 0)); - } - - /** - * @brief Horizontally concatenate two same-height images (2D arrays). - * - * @param img1 First image: vector of rows, each row is a vector. - * @param img2 Second image: must have the same number of rows as img1. - * @return Concatenated image: each row is img1[row] followed by img2[row]. - * - * Throws: - * - std::invalid_argument if img1 and img2 have different heights. - * - * Why: - * - Used in tile_images() to join 16×16 tiles side by side when constructing each grid row. - */ - std::vector> npconcatenate(const std::vector>& img1, - const std::vector>& img2) - { - if (img1.empty()) return img2; - if (img2.empty()) return img1; - - if (img1.size() != img2.size()) { - throw std::invalid_argument("Images must have the same number of rows to concatenate horizontally."); - } - - std::vector> result = img1; - for (size_t i = 0; i < result.size(); ++i) { - result[i].insert(result[i].end(), img2[i].begin(), img2[i].end()); - } - return result; - } - - /** - * @brief Arrange a list of 16×16 tiles into one large square image of size [cols*dim][cols*dim]. - * - * @param images 3D vector: [numTiles][16][16], each a 16×16 grayscale matrix. - * @param cols Number of tiles per row/column in the final grid. - * @param dim Dimension of each tile (16). - * @return 2D vector of size [cols*dim][cols*dim], the tiled image. - * - * Workflow: - * 1. Initialize an empty vector `rows` to hold each combined grid-row. - * 2. Set k = 0 to track current tile index. - * 3. For each row i in [0..cols−1]: - * a. Initialize an empty 16×0 “row” block. - * b. For j in [0..cols−1]: - * - If k < images.size(), let im = images[k]; else use a zero tile npzero(dim). - * - If row is empty, set row = im; else row = npconcatenate(row, im). - * - Increment k. - * c. Append the completed row block (size = dim rows, width = cols*dim) to `rows`. - * 4. Initialize `tiled` = rows[0]. - * 5. For each subsequent row i in [1..rows.size()−1], append rows[i] to the bottom of `tiled`. - * 6. Return `tiled`. - * - * Why: - * - Ensures that if there are fewer than cols² packets, the missing grid slots are zero-filled tiles, - * preserving a square final image of consistent size. - */ - std::vector> tile_images(const std::vector>>& images, - const uint cols, const uint dim) - { - std::vector>> rows; - size_t k = 0; // Tracks which tile index we’re on - - // 1) Build each row of the tile grid - for (size_t i = 0; i < cols; ++i) { - std::vector> row; // Combined row of tiles - for (size_t j = 0; j < cols; ++j) { - std::vector> im; - if (k < images.size()) { - im = images[k]; // Use actual 16×16 tile - } else { - im = npzero(dim); // Use zero tile if no more images - } - - if (row.empty()) { - row = std::move(im); - } else { - row = npconcatenate(row, im); - } - ++k; - } - rows.push_back(std::move(row)); - } - - // 2) Stack all rows vertically to form final tiled image - std::vector> tiled = std::move(rows[0]); - for (size_t i = 1; i < rows.size(); ++i) { - tiled.insert(tiled.end(), rows[i].begin(), rows[i].end()); - } - return tiled; - } -}; - -/** - * @class MarkovTransitionMatrixPacket - * @brief Computes a single 16×16 Markov transition matrix for one packet and exposes it as an image. - * - * Inherits from MarkovTransitionMatrix to reuse transition_matrix(). - * Responsibilities: - * - Given one heiFIPPacketImage, extract its 4-bit bit array. - * - Compute the 16×16 transition matrix and store it as `matrix`. - * - Provide get_matrix() to retrieve that single matrix. - */ -class MarkovTransitionMatrixPacket : public MarkovTransitionMatrix { -public: - /** - * @brief Constructor: compute the Markov transition matrix for a single packet. - * - * @param packet heiFIPPacketImage containing raw packet bytes. - * - * Workflow: - * 1. Call packet.bit_array() to get a vector of 4-bit values. - * 2. Call transition_matrix(transition) to produce a 16×16 grayscale matrix. - * 3. Store the resulting matrix in member `matrix`. - */ - MarkovTransitionMatrixPacket(const heiFIPPacketImage packet) : packet(packet) { - std::vector transition = packet.bit_array(); - matrix = transition_matrix(transition); - } - - /// Accessor for the computed 16×16 matrix - const std::vector>& get_matrix() const { - return matrix; - } - -private: - heiFIPPacketImage packet; ///< The raw packet image to process - std::vector> matrix; ///< Resulting 16×16 transition matrix -}; \ No newline at end of file diff --git a/heiFIP/images/markov_chain.hpp b/heiFIP/images/markov_chain.hpp new file mode 100644 index 0000000..3c1dbc3 --- /dev/null +++ b/heiFIP/images/markov_chain.hpp @@ -0,0 +1,101 @@ +#pragma once + +#include +#include +#include +#include +#include "heiFIPPacketImage.hpp" +#include "NetworkTrafficImage.hpp" +#include "tile_utils.hpp" + +/** + * @class MarkovTransitionMatrix + * @brief Base class for computing a normalized, grayscale Markov transition matrix from a sequence of symbols. + * + * Responsibilities: + * - Given a 1D vector of "transitions" (values in [0..15]), count the transitions between consecutive symbols. + * - Normalize each row of the count matrix so that probabilities sum to 1, then scale to [0..255]. + * - Return the resulting 16×16 matrix of uint8_t intensities. + */ +class MarkovTransitionMatrix : public NetworkTrafficImage { +public: + /** + * @brief Compute a 16×16 Markov transition matrix from a sequence of 4-bit symbols. + */ + std::vector> transition_matrix(const std::vector& transitions) { + const size_t n = 16; + std::vector> uintMatrix(n, std::vector(n, 0)); + + for (size_t k = 0; k + 1 < transitions.size(); ++k) { + size_t i = transitions[k]; + size_t j = transitions[k + 1]; + uintMatrix[i][j] += 1; + } + + for (auto& row : uintMatrix) { + double sum = 0.0; + for (double value : row) { + sum += value; + } + if (sum > 0.0) { + for (auto& value : row) { + double prob = static_cast(value) / sum; + double scaled = prob * 255.0; + value = static_cast(std::clamp(scaled, 0.0, 255.0)); + } + } + } + + return uintMatrix; + } +}; + +/** + * @class MarkovTransitionMatrixFlow + * @brief Builds a larger image by computing a Markov transition matrix for each packet in a flow, + * then arranging all 16×16 matrices into a fixed grid of tiles. + */ +class MarkovTransitionMatrixFlow : public MarkovTransitionMatrix { +public: + MarkovTransitionMatrixFlow(const std::vector& packets, unsigned int cols = 4) + : packets(packets), cols(cols) + { + std::vector>> result; + for (const heiFIPPacketImage& packet : packets) { + std::vector transition = packet.bit_array(); + std::vector> m = transition_matrix(transition); + result.push_back(std::move(m)); + } + matrix = tile_utils::tile_images(result, cols, 16); + } + + const std::vector>& get_matrix() const { + return matrix; + } + +private: + std::vector packets; + unsigned int cols; + MarkovTransitionMatrix transitionMatrix; + std::vector> matrix; +}; + +/** + * @class MarkovTransitionMatrixPacket + * @brief Computes a single 16×16 Markov transition matrix for one packet and exposes it as an image. + */ +class MarkovTransitionMatrixPacket : public MarkovTransitionMatrix { +public: + MarkovTransitionMatrixPacket(const heiFIPPacketImage packet) : packet(packet) { + std::vector transition = packet.bit_array(); + matrix = transition_matrix(transition); + } + + const std::vector>& get_matrix() const { + return matrix; + } + +private: + heiFIPPacketImage packet; + std::vector> matrix; +}; \ No newline at end of file diff --git a/heiFIP/images/tile_utils.hpp b/heiFIP/images/tile_utils.hpp new file mode 100644 index 0000000..d8de62c --- /dev/null +++ b/heiFIP/images/tile_utils.hpp @@ -0,0 +1,92 @@ +#pragma once + +#include +#include +#include + +/** + * @file tile_utils.hpp + * @brief Shared utility functions for tiling 2D image matrices into grids. + * + * Used by FlowImageTiledAuto, FlowImageTiledFixed, and MarkovTransitionMatrixFlow + * to avoid code duplication. + */ +namespace tile_utils { + +/** + * @brief Create a dim×dim tile filled with zeros. + * @param dim Dimension for both width and height. + * @return 2D vector of size [dim][dim], all elements = 0. + */ +inline std::vector> npzero(size_t dim) { + return std::vector>(dim, std::vector(dim, 0)); +} + +/** + * @brief Horizontally concatenate two same-height images (2D arrays). + * @param img1 First image: vector of rows. + * @param img2 Second image: must have same number of rows as img1. + * @return Concatenated image: each row is img1[row] followed by img2[row]. + * @throws std::invalid_argument if img1 and img2 have different heights. + */ +inline std::vector> npconcatenate( + const std::vector>& img1, + const std::vector>& img2) +{ + if (img1.empty()) return img2; + if (img2.empty()) return img1; + + if (img1.size() != img2.size()) { + throw std::invalid_argument( + "Images must have the same number of rows to concatenate horizontally."); + } + + std::vector> result = img1; + for (size_t i = 0; i < result.size(); ++i) { + result[i].insert(result[i].end(), img2[i].begin(), img2[i].end()); + } + return result; +} + +/** + * @brief Arrange a list of tiles into a single large square image. + * @param images 3D vector: [numTiles][dim][dim], each is a dim×dim tile. + * @param cols Number of tiles per row/column in the final grid. + * @param dim Dimension of each tile (width = height = dim). + * @return 2D vector of size [cols*dim][cols*dim], the tiled image. + */ +inline std::vector> tile_images( + const std::vector>>& images, + unsigned int cols, unsigned int dim) +{ + std::vector>> rows; + size_t k = 0; + + for (size_t i = 0; i < cols; ++i) { + std::vector> row; + for (size_t j = 0; j < cols; ++j) { + std::vector> im; + if (k < images.size()) { + im = images[k]; + } else { + im = npzero(dim); + } + + if (row.empty()) { + row = std::move(im); + } else { + row = npconcatenate(row, im); + } + ++k; + } + rows.push_back(std::move(row)); + } + + std::vector> tiled = std::move(rows[0]); + for (size_t i = 1; i < rows.size(); ++i) { + tiled.insert(tiled.end(), rows[i].begin(), rows[i].end()); + } + return tiled; +} + +} // namespace tile_utils diff --git a/heiFIP/layers/dns.cpp b/heiFIP/layers/dns.hpp similarity index 98% rename from heiFIP/layers/dns.cpp rename to heiFIP/layers/dns.hpp index 5ca48fc..3bf18ca 100644 --- a/heiFIP/layers/dns.cpp +++ b/heiFIP/layers/dns.hpp @@ -4,7 +4,7 @@ #include #include -#include "transport.cpp" +#include "transport.hpp" /** * @class DNSPacket @@ -62,6 +62,9 @@ class DNSPacket : public TransportPacket { * f. Call Packet.computeCalculateFields() to recalculate lengths and checksums upstream of the new DNS header. */ void header_preprocessing() override { + // First, perform any transport-layer and IP/Ethernet substitutions + TransportPacket::header_preprocessing(); + // 1) Find the existing DnsLayer for preprocessing pcpp::DnsLayer* oldDNSForMessageProcessing = Packet.getLayerOfType(); if (!oldDNSForMessageProcessing) { diff --git a/heiFIP/layers/http.cpp b/heiFIP/layers/http.hpp similarity index 99% rename from heiFIP/layers/http.cpp rename to heiFIP/layers/http.hpp index 654bef3..94f5964 100644 --- a/heiFIP/layers/http.cpp +++ b/heiFIP/layers/http.hpp @@ -5,7 +5,7 @@ #include #include -#include "transport.cpp" +#include "transport.hpp" #include "PcapPlusPlusVersion.h" #include "HttpLayer.h" diff --git a/heiFIP/layers/init.cpp b/heiFIP/layers/init.hpp similarity index 98% rename from heiFIP/layers/init.cpp rename to heiFIP/layers/init.hpp index 1f80a4a..0bc0028 100644 --- a/heiFIP/layers/init.cpp +++ b/heiFIP/layers/init.hpp @@ -7,12 +7,12 @@ #include #include -#include "packet.cpp" -#include "dns.cpp" -#include "http.cpp" -#include "ip.cpp" -#include "ssh.cpp" -#include "transport.cpp" +#include "packet.hpp" +#include "dns.hpp" +#include "http.hpp" +#include "ip.hpp" +#include "ssh.hpp" +#include "transport.hpp" enum class SupportedHeaderType { IP, diff --git a/heiFIP/layers/ip.cpp b/heiFIP/layers/ip.hpp similarity index 99% rename from heiFIP/layers/ip.cpp rename to heiFIP/layers/ip.hpp index 5f47b62..4448059 100644 --- a/heiFIP/layers/ip.cpp +++ b/heiFIP/layers/ip.hpp @@ -15,8 +15,8 @@ #include #include // For SHA-256 hashing of header fields -#include "packet.cpp" -#include "header.cpp" +#include "packet.hpp" +#include "header.hpp" /** * @class IPPacket diff --git a/heiFIP/layers/packet.cpp b/heiFIP/layers/packet.hpp similarity index 96% rename from heiFIP/layers/packet.cpp rename to heiFIP/layers/packet.hpp index cd85cac..6a675fe 100644 --- a/heiFIP/layers/packet.cpp +++ b/heiFIP/layers/packet.hpp @@ -213,11 +213,11 @@ class FIPPacket { } /** - * @brief Access the underlying RawPacket pointer for additional, low-level operations. - * @return pcpp::RawPacket* Raw pointer to the owned RawPacket. + * @brief Access the underlying RawPacket pointer. + * @return pcpp::RawPacket* Pointer to the RawPacket, correctly reflected if modified by pcpp::Packet. */ pcpp::RawPacket* getRawPacket() { - return rawPtr.get(); + return Packet.getRawPacket(); } /** @@ -225,7 +225,15 @@ class FIPPacket { * @return const pcpp::RawPacket* Const raw pointer to the owned RawPacket. */ const pcpp::RawPacket* getRawPacket() const { - return rawPtr.get(); + return Packet.getRawPacket(); + } + + /** + * @brief Access the internally parsed pcpp::Packet object. + * @return const pcpp::Packet& Reference to the internal Packet member. + */ + const pcpp::Packet& getPacket() const { + return Packet; } }; @@ -271,7 +279,7 @@ class UnknownPacket : public FIPPacket { * * @return std::string A randomly generated 6-byte MAC address. */ -std::string generate_random_mac() { +static inline std::string generate_random_mac() { std::stringstream mac; mac << std::hex << std::uppercase << std::setw(2) << std::setfill('0') << (rand() % 256); for (int i = 0; i < 5; ++i) { diff --git a/heiFIP/layers/ssh.cpp b/heiFIP/layers/ssh.hpp similarity index 99% rename from heiFIP/layers/ssh.cpp rename to heiFIP/layers/ssh.hpp index ffd83da..26be647 100644 --- a/heiFIP/layers/ssh.cpp +++ b/heiFIP/layers/ssh.hpp @@ -1,6 +1,6 @@ #pragma once -#include "transport.cpp" +#include "transport.hpp" /** * @class SSHPacketProcessor diff --git a/heiFIP/layers/transport.cpp b/heiFIP/layers/transport.hpp similarity index 93% rename from heiFIP/layers/transport.cpp rename to heiFIP/layers/transport.hpp index debb87d..2293991 100644 --- a/heiFIP/layers/transport.cpp +++ b/heiFIP/layers/transport.hpp @@ -10,9 +10,9 @@ #include #include // For SHA-256 hashing -#include "header.cpp" -#include "ip.cpp" -#include "packet.cpp" +#include "header.hpp" +#include "ip.hpp" +#include "packet.hpp" /** * @class TransportPacket @@ -146,6 +146,25 @@ class TransportPacket : public IPPacket { } } } + + // Populate address_mapping with ports + if (layerMap["TCP"]) { + auto* tcp = Packet.getLayerOfType(); + if (tcp) { + std::string srcP = std::to_string(ntohs(tcp->getTcpHeader()->portSrc)); + std::string dstP = std::to_string(ntohs(tcp->getTcpHeader()->portDst)); + address_mapping[srcP] = srcP; + address_mapping[dstP] = dstP; + } + } else if (layerMap["UDP"]) { + auto* udp = Packet.getLayerOfType(); + if (udp) { + std::string srcP = std::to_string(ntohs(udp->getUdpHeader()->portSrc)); + std::string dstP = std::to_string(ntohs(udp->getUdpHeader()->portDst)); + address_mapping[srcP] = srcP; + address_mapping[dstP] = dstP; + } + } } /** diff --git a/heiFIP/main.cpp b/heiFIP/main.cpp index 32ec4d1..3a6e01f 100644 --- a/heiFIP/main.cpp +++ b/heiFIP/main.cpp @@ -1,4 +1,4 @@ -#include "runner.cpp" +#include "runner.hpp" #include #include @@ -40,8 +40,8 @@ std::string filenameWithoutExtension(const std::string& fullPath) { /// Demonstrates loading `.pcap` files and generating images using a Runner object. int main() { // Paths to input `.pcap` directory and output image directory - std::string output_dir = "/Users/henrirebitzky/Documents/BachelorDerInformatikAnDerUniversitätHeidelberg/IFPGit/heiFIP/build/"; - std::string input_dir = "/Users/henrirebitzky/Documents/BachelorDerInformatikAnDerUniversitätHeidelberg/IFPGit/tests/pcaps/http"; + std::string output_dir = "./"; + std::string input_dir = "../tests/pcaps/http"; // Retrieve all `.pcap` files from the input directory std::vector files = listPcapFilePathsInDir(input_dir); diff --git a/heiFIP/plugins/header.cpp b/heiFIP/plugins/header.hpp similarity index 100% rename from heiFIP/plugins/header.cpp rename to heiFIP/plugins/header.hpp diff --git a/heiFIP/runner.cpp b/heiFIP/runner.hpp similarity index 99% rename from heiFIP/runner.cpp rename to heiFIP/runner.hpp index f2af68a..f6f5601 100644 --- a/heiFIP/runner.cpp +++ b/heiFIP/runner.hpp @@ -3,7 +3,7 @@ #include #include -#include "extractor.cpp" +#include "extractor.hpp" // Runner class orchestrates multithreaded image generation using FIPExtractor class Runner { diff --git a/heiFIP/tests/test_cli.cpp b/heiFIP/tests/test_cli.cpp new file mode 100644 index 0000000..735fb9b --- /dev/null +++ b/heiFIP/tests/test_cli.cpp @@ -0,0 +1,87 @@ +#include +#include +#include +#include +#include + +namespace fs = std::filesystem; + +class CLITest : public ::testing::Test { +protected: + void SetUp() override { + // Create an output directory for CLI tests + testOutputDir = "cli_test_output"; + fs::create_directories(testOutputDir); + } + + void TearDown() override { + // Clean up output directory after tests + if (fs::exists(testOutputDir)) { + fs::remove_all(testOutputDir); + } + } + + std::string testOutputDir; + std::string binaryPath = "./heiFIP"; // Assumes running from build directory +}; + +TEST_F(CLITest, BasicExecution) { + std::string inputPcap = "../../tests/pcaps/dns53.pcap"; + if (!fs::exists(inputPcap)) { + GTEST_SKIP() << "Input PCAP not found: " << inputPcap; + } + + std::string cmd = binaryPath + " --name basic_test --input " + inputPcap + + " --output " + testOutputDir + " --mode PacketImage --dim 16"; + + int ret = std::system(cmd.c_str()); + EXPECT_EQ(ret, 0); + EXPECT_TRUE(fs::exists(testOutputDir + "/basic_test.png")); +} + +TEST_F(CLITest, ProcessorHeaderVsNone) { + std::string inputPcap = "../../tests/pcaps/dns53.pcap"; + if (!fs::exists(inputPcap)) { + GTEST_SKIP() << "Input PCAP not found: " << inputPcap; + } + + // Run in NONE mode + std::string cmdNone = binaryPath + " --name test_none --input " + inputPcap + + " --output " + testOutputDir + " --mode PacketImage --processor NONE --dim 16"; + ASSERT_EQ(std::system(cmdNone.c_str()), 0); + std::string pathNone = testOutputDir + "/test_none.png"; + ASSERT_TRUE(fs::exists(pathNone)); + auto sizeNone = fs::file_size(pathNone); + + // Run in HEADER mode + std::string cmdHeader = binaryPath + " --name test_header --input " + inputPcap + + " --output " + testOutputDir + " --mode PacketImage --processor HEADER --dim 16"; + ASSERT_EQ(std::system(cmdHeader.c_str()), 0); + std::string pathHeader = testOutputDir + "/test_header.png"; + ASSERT_TRUE(fs::exists(pathHeader)); + auto sizeHeader = fs::file_size(pathHeader); + + // HEADER mode should result in smaller image data (shorter packets) + // Note: PNG compression might vary, but for PacketImage with fixed dim 16, + // the amount of "filled" pixels (0 or 255) vs "data" pixels changes. + // In PacketImage mode, we pad to dim*dim. + // With 475 bytes (NONE) vs 459 bytes (HEADER - guessed reduction), the difference might be subtle. + // However, the bytes ARE different. + EXPECT_NE(sizeNone, 0); + EXPECT_NE(sizeHeader, 0); + + // We already verified manually that file sizes differ for dns-binds.pcap. + // For small packets in dns53.pcap, we just check they both succeed. +} + +TEST_F(CLITest, InvalidInput) { + std::string cmd = binaryPath + " --input non_existent.pcap --output " + testOutputDir; + int ret = std::system((cmd + " 2>/dev/null").c_str()); + EXPECT_NE(ret, 0); +} + +TEST_F(CLITest, HelpFlag) { + std::string cmd = binaryPath + " --help"; + int ret = std::system((cmd + " >/dev/null").c_str()); + EXPECT_EQ(ret, 0); +} diff --git a/heiFIP/tests/test_extractor.cpp b/heiFIP/tests/test_extractor.cpp new file mode 100644 index 0000000..7f68a14 --- /dev/null +++ b/heiFIP/tests/test_extractor.cpp @@ -0,0 +1,61 @@ +#include +#include "extractor.hpp" +#include + +TEST(ExtractorTest, LoadPcap) { + // dns53.pcap exists in tests/pcaps/ + std::string pcapPath = "../../tests/pcaps/dns53.pcap"; + if (!std::filesystem::exists(pcapPath)) { + GTEST_SKIP() << "Test PCAP file not found at " << pcapPath; + } + + FIPExtractor extractor; + // Test direct extraction + PacketProcessorType procType = PacketProcessorType::HEADER; + // dns53.pcap has 2 packets + auto packets = extractor.getPackets(pcapPath, procType, false, 0); + + EXPECT_FALSE(packets.empty()); + EXPECT_EQ(packets.size(), 1); +} + +TEST(ExtractorTest, CreateMatrix) { + std::string pcapPath = "../../tests/pcaps/dns53.pcap"; + if (!std::filesystem::exists(pcapPath)) { + GTEST_SKIP() << "Test PCAP file not found"; + } + + FIPExtractor extractor; + PacketProcessorType procType = PacketProcessorType::HEADER; + + // PacketImageArgs {dim, fill, auto_dim} + PacketImageArgs args{16, 0, true}; + + auto images = extractor.createImageFromFile( + pcapPath, + args, + procType, + ImageType::PacketImage, + 1, 100, 1, 100, false + ); + + EXPECT_FALSE(images.empty()); + // Check first image dimensions + EXPECT_GE(images[0].size(), 16); + EXPECT_GE(images[0][0].size(), 16); +} + +TEST(ExtractorTest, InvalidFileThrows) { + FIPExtractor extractor; + PacketImageArgs args{16, 0, true}; + + EXPECT_THROW({ + extractor.createImageFromFile( + "non_existent.pcap", + args, + PacketProcessorType::HEADER, + ImageType::PacketImage, + 1, 100, 1, 100, false + ); + }, std::runtime_error); +} diff --git a/heiFIP/tests/test_flow_image.cpp b/heiFIP/tests/test_flow_image.cpp new file mode 100644 index 0000000..e95f1e0 --- /dev/null +++ b/heiFIP/tests/test_flow_image.cpp @@ -0,0 +1,32 @@ +#include +#include "images/flow.hpp" +#include "assets/heiFIPPacketImage.hpp" + +TEST(FlowImageTest, NonAppendMode) { + std::vector packets; + packets.emplace_back(std::vector{1, 2, 3}); + packets.emplace_back(std::vector{4, 5}); + + // dim 4, fill 0, append false + FlowImage flow(packets, 4, 0, false); + + auto matrix = flow.get_matrix(); + ASSERT_EQ(matrix.size(), 2); + EXPECT_EQ(matrix[0], (std::vector{1, 2, 3, 0})); + EXPECT_EQ(matrix[1], (std::vector{4, 5, 0, 0})); +} + +TEST(FlowImageTest, AppendMode) { + std::vector packets; + packets.emplace_back(std::vector{1, 2}); + packets.emplace_back(std::vector{3, 4}); + + // dim 2, fill 0, append true + // [1, 2, 3, 4] reshaped to 2x2 + FlowImage flow(packets, 2, 0, true); + + auto matrix = flow.get_matrix(); + ASSERT_EQ(matrix.size(), 2); + EXPECT_EQ(matrix[0], (std::vector{1, 2})); + EXPECT_EQ(matrix[1], (std::vector{3, 4})); +} diff --git a/heiFIP/tests/test_layers.cpp b/heiFIP/tests/test_layers.cpp new file mode 100644 index 0000000..745da4f --- /dev/null +++ b/heiFIP/tests/test_layers.cpp @@ -0,0 +1,156 @@ +#include +#include "layers/init.hpp" +#include +#include + +TEST(LayerTest, IPPacketAddressMapping) { + std::string pcapPath = "../../tests/pcaps/dns53.pcap"; + if (!std::filesystem::exists(pcapPath)) { + GTEST_SKIP() << "Test PCAP file not found"; + } + + PacketProcessor processor; + auto packets = processor.readPacketsFile(pcapPath, PacketProcessorType::NONE); + ASSERT_FALSE(packets.empty()); + + auto& pkt = packets[0]; + auto mapping = pkt->getAdressMapping(); + + // dns53.pcap: 207.158.192.40 > 10.20.1.31 + // The keys are the ORIGINAL addresses + EXPECT_TRUE(mapping.count("207.158.192.40")); + EXPECT_TRUE(mapping.count("10.20.1.31")); + + // The values should be DIFFERENT (anonymized) + EXPECT_NE(mapping["207.158.192.40"], "207.158.192.40"); + EXPECT_NE(mapping["10.20.1.31"], "10.20.1.31"); +} + +TEST(LayerTest, TransportPacketPorts) { + std::string pcapPath = "../../tests/pcaps/dns53.pcap"; + if (!std::filesystem::exists(pcapPath)) { + GTEST_SKIP() << "Test PCAP file not found"; + } + + PacketProcessor processor; + auto packets = processor.readPacketsFile(pcapPath, PacketProcessorType::NONE); + ASSERT_FALSE(packets.empty()); + + auto* transPkt = dynamic_cast(packets[0].get()); + ASSERT_NE(transPkt, nullptr); + + auto mapping = transPkt->getAdressMapping(); + + // DNS uses port 53. TransportPacket maps "53" -> "53" (currently no port anonymization) + EXPECT_TRUE(mapping.count("53")); + EXPECT_EQ(mapping["53"], "53"); +} + +TEST(LayerTest, DNSPacketFields) { + std::string pcapPath = "../../tests/pcaps/dns53.pcap"; + if (!std::filesystem::exists(pcapPath)) { + GTEST_SKIP() << "Test PCAP file not found"; + } + + PacketProcessor processor; + auto packets = processor.readPacketsFile(pcapPath, PacketProcessorType::NONE); + ASSERT_FALSE(packets.empty()); + + auto* dnsPkt = dynamic_cast(packets[0].get()); + ASSERT_NE(dnsPkt, nullptr); + + auto layerMap = dnsPkt->getLayerMap(); + EXPECT_TRUE(layerMap["DNS"]); +} + +TEST(LayerTest, HeaderPreprocessingMasking) { + std::string pcapPath = "../../tests/pcaps/dns53.pcap"; + if (!std::filesystem::exists(pcapPath)) { + GTEST_SKIP() << "Test PCAP file not found"; + } + + PacketProcessor processor; + auto pktsNone = processor.readPacketsFile(pcapPath, PacketProcessorType::NONE); + auto pktsHeader = processor.readPacketsFile(pcapPath, PacketProcessorType::HEADER); + + ASSERT_EQ(pktsNone.size(), pktsHeader.size()); + + size_t lenNone = pktsNone[0]->getRawPacket()->getRawDataLen(); + // Use the raw packet from the parsed Packet object to ensure we see modifications + size_t lenHeader = pktsHeader[0]->getPacket().getRawPacket()->getRawDataLen(); + + bool foundCustom = false; + for (pcpp::Layer* l = pktsHeader[0]->getPacket().getFirstLayer(); l; l = l->getNextLayer()) { + if (l->toString().find("Custom") != std::string::npos) { + foundCustom = true; + } + } + + EXPECT_TRUE(foundCustom) << "No 'Custom' layer found in preprocessed packet"; + + // If it fails, report the layers for debugging + if (lenHeader >= lenNone) { + std::cout << "DEBUG: lenNone=" << lenNone << " lenHeader=" << lenHeader << std::endl; + for (pcpp::Layer* l = pktsHeader[0]->getPacket().getFirstLayer(); l; l = l->getNextLayer()) { + std::cout << " Layer: " << l->toString() << " HeaderLen: " << l->getHeaderLen() << std::endl; + } + } + + EXPECT_LT(lenHeader, lenNone) << "Packet length did not decrease after header preprocessing"; +} + +TEST(LayerTest, HTTPRequestPreprocessing) { + std::string pcapPath = "../../tests/pcaps/http/206_example_b.pcap"; + if (!std::filesystem::exists(pcapPath)) { + GTEST_SKIP() << "Test PCAP file not found"; + } + + PacketProcessor processor; + // Load with HEADER preprocessing + auto packets = processor.readPacketsFile(pcapPath, PacketProcessorType::HEADER); + ASSERT_FALSE(packets.empty()); + + // Look for CustomHTTPRequest layer + bool foundCustomHTTP = false; + for (auto& p : packets) { + const pcpp::Packet& pkt = p->getPacket(); + for (pcpp::Layer* layer = pkt.getFirstLayer(); layer; layer = layer->getNextLayer()) { + if (layer->toString().find("HTTP Request Layer") != std::string::npos) { + foundCustomHTTP = true; + break; + } + } + if (foundCustomHTTP) break; + } + + EXPECT_TRUE(foundCustomHTTP) << "CustomHTTPRequest layer not found in preprocessed HTTP packets"; +} + +TEST(LayerTest, HTTPResponsePreprocessing) { + std::string pcapPath = "../../tests/pcaps/http/206_example_b.pcap"; + if (!std::filesystem::exists(pcapPath)) { + GTEST_SKIP() << "Test PCAP file not found"; + } + + PacketProcessor processor; + // Load with HEADER preprocessing + auto packets = processor.readPacketsFile(pcapPath, PacketProcessorType::HEADER); + ASSERT_FALSE(packets.empty()); + + // Look for CustomHTTPResponse layer + bool foundCustomHTTP = false; + for (auto& p : packets) { + const pcpp::Packet& pkt = p->getPacket(); + for (pcpp::Layer* layer = pkt.getFirstLayer(); layer; layer = layer->getNextLayer()) { + if (layer->toString().find("HTTP Response Layer") != std::string::npos) { + foundCustomHTTP = true; + break; + } + } + if (foundCustomHTTP) break; + } + + EXPECT_TRUE(foundCustomHTTP) << "CustomHTTPResponse layer not found in preprocessed HTTP packets"; +} + + diff --git a/heiFIP/tests/test_markov.cpp b/heiFIP/tests/test_markov.cpp new file mode 100644 index 0000000..4d4a3ad --- /dev/null +++ b/heiFIP/tests/test_markov.cpp @@ -0,0 +1,41 @@ +#include +#include "images/markov_chain.hpp" +#include "assets/heiFIPPacketImage.hpp" + +TEST(MarkovTest, TransitionMatrixComputation) { + MarkovTransitionMatrix mtm; + // Transitions: 0->1, 1->0, 0->1 + std::vector transitions = {0, 1, 0, 1}; + + auto matrix = mtm.transition_matrix(transitions); + ASSERT_EQ(matrix.size(), 16); + + // Row 0: two 0->1 transitions. Sum = 2. 0->1 prob = 1.0 (255) + EXPECT_EQ(matrix[0][1], 255); + // Row 1: one 1->0 transition. Sum = 1. 1->0 prob = 1.0 (255) + EXPECT_EQ(matrix[1][0], 255); + // Others 0 + EXPECT_EQ(matrix[0][0], 0); +} + +TEST(MarkovTest, PacketMarkov) { + std::vector data = {0xAB}; // 1010 1011 -> nibbles 10, 11 + heiFIPPacketImage pkt(data); + + MarkovTransitionMatrixPacket mtmp(pkt); + auto matrix = mtmp.get_matrix(); + ASSERT_EQ(matrix.size(), 16); + // One transition 10->11 + EXPECT_EQ(matrix[10][11], 255); +} + +TEST(MarkovTest, FlowMarkov) { + std::vector packets; + packets.emplace_back(std::vector{0xAB}); + packets.emplace_back(std::vector{0xCD}); + + // 2 packets, grid 2x2, tile dim 16 -> final 32x32 + MarkovTransitionMatrixFlow mtmf(packets, 2); + auto matrix = mtmf.get_matrix(); + ASSERT_EQ(matrix.size(), 32); +} diff --git a/heiFIP/tests/test_packet_image.cpp b/heiFIP/tests/test_packet_image.cpp new file mode 100644 index 0000000..32ec0c6 --- /dev/null +++ b/heiFIP/tests/test_packet_image.cpp @@ -0,0 +1,55 @@ +#include +#include "assets/heiFIPPacketImage.hpp" + +TEST(PacketImageTest, Construction) { + std::vector data = {0xDE, 0xAD, 0xBE, 0xEF}; + heiFIPPacketImage pkt(data); + + EXPECT_EQ(pkt.get_cap_length(), 4); + EXPECT_EQ(pkt.getHexData(), data); +} + +TEST(PacketImageTest, BitArray) { + // 0xAB = 1010 1011 + // Bit array should give 10 and 11 + std::vector data = {0xAB}; + heiFIPPacketImage pkt(data); + + auto bits = pkt.bit_array(); + ASSERT_EQ(bits.size(), 2); + EXPECT_EQ(bits[0], 10); // 1010 + EXPECT_EQ(bits[1], 11); // 1011 +} + +TEST(PacketImageTest, TiledMatrix) { + // 4 bytes, dim 2 -> should be 2x2 matrix + std::vector data = {1, 2, 3, 4}; + heiFIPPacketImage pkt(data, 2, 0, false); + + auto matrix = pkt.get_matrix(); + ASSERT_EQ(matrix.size(), 2); + EXPECT_EQ(matrix[0], (std::vector{1, 2})); + EXPECT_EQ(matrix[1], (std::vector{3, 4})); +} + +TEST(PacketImageTest, TiledMatrixPadding) { + // 2 bytes, dim 2 -> should be 2x2 matrix padded with 255 + std::vector data = {1, 2}; + heiFIPPacketImage pkt(data, 2, 255, false); + + auto matrix = pkt.get_matrix(); + ASSERT_EQ(matrix.size(), 2); + EXPECT_EQ(matrix[0], (std::vector{1, 2})); + EXPECT_EQ(matrix[1], (std::vector{255, 255})); +} + +TEST(PacketImageTest, AutoDim) { + // 10 bytes -> ceil(sqrt(10)) = 4 + std::vector data(10, 1); + heiFIPPacketImage pkt(data, 0, 0, true); + + auto matrix = pkt.get_matrix(); + ASSERT_EQ(matrix.size(), 4); + EXPECT_EQ(matrix[0].size(), 4); + EXPECT_EQ(matrix[0][0], 1); +} diff --git a/heiFIP/tests/test_tile_utils.cpp b/heiFIP/tests/test_tile_utils.cpp new file mode 100644 index 0000000..bab30f0 --- /dev/null +++ b/heiFIP/tests/test_tile_utils.cpp @@ -0,0 +1,57 @@ +#include +#include "images/tile_utils.hpp" + +using namespace tile_utils; + +TEST(TileUtilsTest, NpZero) { + size_t dim = 4; + auto zero = npzero(dim); + EXPECT_EQ(zero.size(), dim); + for (const auto& row : zero) { + EXPECT_EQ(row.size(), dim); + for (uint8_t val : row) { + EXPECT_EQ(val, 0); + } + } +} + +TEST(TileUtilsTest, NpConcatenate) { + std::vector> img1 = {{1, 2}, {3, 4}}; + std::vector> img2 = {{5, 6}, {7, 8}}; + + auto concat = npconcatenate(img1, img2); + + ASSERT_EQ(concat.size(), 2); + EXPECT_EQ(concat[0], (std::vector{1, 2, 5, 6})); + EXPECT_EQ(concat[1], (std::vector{3, 4, 7, 8})); +} + +TEST(TileUtilsTest, NpConcatenateEmpty) { + std::vector> img1 = {{1, 2}}; + std::vector> empty; + + EXPECT_EQ(npconcatenate(img1, empty), img1); + EXPECT_EQ(npconcatenate(empty, img1), img1); +} + +TEST(TileUtilsTest, NpConcatenateMismatchedHeight) { + std::vector> img1 = {{1, 2}}; + std::vector> img2 = {{5, 6}, {7, 8}}; + + EXPECT_THROW(npconcatenate(img1, img2), std::invalid_argument); +} + +TEST(TileUtilsTest, TileImages) { + std::vector> t1 = {{1, 1}, {1, 1}}; + std::vector> t2 = {{2, 2}, {2, 2}}; + std::vector>> tiles = {t1, t2}; + + // Grid 2x2, tile dim 2 + auto tiled = tile_images(tiles, 2, 2); + + ASSERT_EQ(tiled.size(), 4); + EXPECT_EQ(tiled[0], (std::vector{1, 1, 2, 2})); + EXPECT_EQ(tiled[1], (std::vector{1, 1, 2, 2})); + EXPECT_EQ(tiled[2], (std::vector{0, 0, 0, 0})); + EXPECT_EQ(tiled[3], (std::vector{0, 0, 0, 0})); +} diff --git a/heiFIP/tests/test_tiled_images.cpp b/heiFIP/tests/test_tiled_images.cpp new file mode 100644 index 0000000..ffd9991 --- /dev/null +++ b/heiFIP/tests/test_tiled_images.cpp @@ -0,0 +1,32 @@ +#include +#include "images/flow_tiled_fixed.hpp" +#include "images/flow_tiled_auto.hpp" +#include "assets/heiFIPPacketImage.hpp" + +TEST(TiledImagesTest, FixedGrid) { + std::vector packets; + // 1 packet, dim 2, grid 2x2 + packets.emplace_back(std::vector{1, 2, 3, 4}); + + FlowImageTiledFixed tiled(packets, 2, 0, 2); + + auto matrix = tiled.get_matrix(); + // Grid 2x2, tile dim 2 -> final 4x4 + ASSERT_EQ(matrix.size(), 4); + EXPECT_EQ(matrix[0], (std::vector{1, 2, 0, 0})); + EXPECT_EQ(matrix[2], (std::vector{0, 0, 0, 0})); +} + +TEST(TiledImagesTest, AutoGrid) { + std::vector packets; + // 2 packets, dim 2 -> grid 2x2 + packets.emplace_back(std::vector{1, 2, 3, 4}); + packets.emplace_back(std::vector{5, 6, 7, 8}); + + FlowImageTiledAuto tiled(packets, 2, 0, false); + + auto matrix = tiled.get_matrix(); + ASSERT_EQ(matrix.size(), 4); + EXPECT_EQ(matrix[0], (std::vector{1, 2, 5, 6})); + EXPECT_EQ(matrix[2], (std::vector{0, 0, 0, 0})); +} diff --git a/heiFIP/vcpkg.json b/heiFIP/vcpkg.json index 1e0d704..6f3c98d 100644 --- a/heiFIP/vcpkg.json +++ b/heiFIP/vcpkg.json @@ -8,6 +8,7 @@ "features": ["jpeg", "png"] }, "openssl", - "pcapplusplus" + "pcapplusplus", + "gtest" ] } From 65520f773e74f199401fce80df7458ac23de27ba Mon Sep 17 00:00:00 2001 From: Stefan Machmeier Date: Thu, 26 Mar 2026 14:26:53 +0100 Subject: [PATCH 5/7] fix: Wrong path in CI --- .github/workflows/build_test_linux.yml | 4 ++-- .github/workflows/build_test_macos.yml | 3 ++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/.github/workflows/build_test_linux.yml b/.github/workflows/build_test_linux.yml index 3093ef7..4ecac0a 100644 --- a/.github/workflows/build_test_linux.yml +++ b/.github/workflows/build_test_linux.yml @@ -43,14 +43,14 @@ jobs: git - name: Configure and Build + working-directory: heiFIP run: | - cd heiFIP/ cmake -B build -S . \ -DCMAKE_BUILD_TYPE=Release \ -DCMAKE_TOOLCHAIN_FILE="$VCPKG_INSTALLATION_ROOT/scripts/buildsystems/vcpkg.cmake" cmake --build build -j$(nproc) - name: Run Tests + working-directory: heiFIP run: | - cd build ctest --output-on-failure \ No newline at end of file diff --git a/.github/workflows/build_test_macos.yml b/.github/workflows/build_test_macos.yml index bdcd9ec..47e220e 100644 --- a/.github/workflows/build_test_macos.yml +++ b/.github/workflows/build_test_macos.yml @@ -39,14 +39,15 @@ jobs: brew install pkg-config cmake git - name: Configure and Build + working-directory: heiFIP run: | - cd heiFIP/ cmake -B build -S . \ -DCMAKE_BUILD_TYPE=Release \ -DCMAKE_TOOLCHAIN_FILE="$VCPKG_INSTALLATION_ROOT/scripts/buildsystems/vcpkg.cmake" cmake --build build -j$(sysctl -n hw.ncpu) - name: Run Tests + working-directory: heiFIP run: | cd build ctest --output-on-failure From 5b61ce204ae146dce33a5c5c3c2fae5adccf63c0 Mon Sep 17 00:00:00 2001 From: Stefan Machmeier Date: Thu, 26 Mar 2026 14:36:12 +0100 Subject: [PATCH 6/7] feat: Set dim to 16 by default --- README.md | 180 ++++++++++++++++++++++++------------------------- heiFIP/cli.cpp | 13 ++-- 2 files changed, 97 insertions(+), 96 deletions(-) diff --git a/README.md b/README.md index 1a5671e..e7d5806 100644 --- a/README.md +++ b/README.md @@ -89,96 +89,96 @@ The idea to create heiFIP came from working with Deep Learning approaches to cla | Markov Transition Matrix Flow | Converts a flow into a Markov Transition Matrix. It squares the image based on the number of packets | ![SMB Connection](https://raw.githubusercontent.com/stefanDeveloper/heiFIP/main/examples/markov-flow.png?raw=true) | ## Requirements - -* **C++ Compiler**: GCC ≥ 9.0, Clang ≥ 10, or MSVC 2019 with C++17 support. -* **CMake**: Version ≥ 3.14 -* **vcpkg**: A C++ package manager to automatically download and build dependencies. - -Dependencies managed automatically by `vcpkg`: -* **PcapPlusPlus** -* **OpenSSL** -* **OpenCV** -* **libpcap** (Linux/macOS) / `pthread` - -## Building from source - -We use `vcpkg` to manage all C++ dependencies for heiFIP smoothly. If you don't have `vcpkg` installed, follow their [official instructions](https://github.com/microsoft/vcpkg#quick-start-windows-linux-macos). - -Ensure the `VCPKG_ROOT` environment variable is set to your `vcpkg` installation path (e.g., `export VCPKG_ROOT=~/vcpkg`). - -```bash -# Clone this repo -git clone https://github.com/stefanDeveloper/heiFIP.git -cd heiFIP/heiFIP/ - -# Set up vcpkg -git clone https://github.com/microsoft/vcpkg.git -./vcpkg/bootstrap-vcpkg.sh -export VCPKG_ROOT=$(pwd)/vcpkg - -# Create build directory and run CMake using the vcpkg toolchain -# The toolchain will automatically read vcpkg.json and install dependencies! -cmake -B build -S . -DCMAKE_BUILD_TYPE=Release -DCMAKE_TOOLCHAIN_FILE="$VCPKG_ROOT/scripts/buildsystems/vcpkg.cmake" - -# Compile the project -cmake --build build -j$(nproc) - -# The executables 'heiFIP' and 'main' will be produced in build/ -``` - - -## Getting Started - -After installation the command line interface can be used to extract images from pcap files witht he following command -```bash -./heiFIP \ - --name HelloHeiFIP - --input /path/to/capture.pcap \ - --output /path/to/outdir \ - --threads 4 \ - --processor HEADER \ - --mode FlowImageTiledAuto \ - --dim 16 \ - --apppend \ - --fill 0 \ - --min-dim 10 \ - --max-dim 2000 \ - --min-pkts 10 \ - --max-pkts 100 \ - --remove-dup -``` - -### Options -| Flag | Description | -| ------------------- | -------------------------------------------------------------- | -| `-i`, `--input` | Input PCAP file path | -| `-o`, `--output` | Output directory | -| `-t`, `--threads` | Number of worker threads (default: 1) | -| `-p`, `--processor` | Preprocessing: `NONE` or `HEADER` | -| `-m`, `--mode` | Image type: `PacketImage`, `FlowImage`, `FlowImageTiledFixed`, | -| | `FlowImageTiledAuto`, `MarkovTransitionMatrixFlow`, | -| | `MarkovTransitionMatrixPacket` | -| `--dim` | Base dimension for image (e.g. width/height in pixels) | -| `--fill` | Fill or padding value (0–255) | -| `--cols` | Number of columns (for tiled/fixed or Markov flow) | -| `--auto-dim` | Enable auto‑dimension selection (bool) | -| `--append` | Enable auto‑dimension selection (bool) | -| `--min-dim` | Minimum allowed image dimension | -| `--max-dim` | Maximum allowed image dimension | -| `--min-pkts` | Minimum packets per flow (for tiled/flow modes) | -| `--max-pkts` | Maximum packets per flow | -| `--remove-dup` | Remove duplicate flows/packets by hash | -| `--name` | Filname of processed image | -| `-h`, `--help` | Show this help message | - -## Extending - -To add a new image type: - -1. Define a new `ImageArgs` struct in `extractor.cpp`. -2. Extend the `ImageType` enum. -3. Implement the conversion in `PacketProcessor::createImageFromPacket()`. -4. Update the CLI `--mode` parser to include your new type. + + * **C++ Compiler**: GCC ≥ 9.0, Clang ≥ 10, or MSVC 2019 with C++20 support. + * **CMake**: Version ≥ 3.14 + * **vcpkg**: A C++ package manager to automatically download and build dependencies. + + Dependencies managed automatically by `vcpkg`: + * **PcapPlusPlus** + * **OpenSSL** + * **OpenCV** + * **GTest** (GoogleTest) + * **libpcap** (Linux/macOS) / `pthread` + + ## Building from source + + We use `vcpkg` to manage all C++ dependencies for heiFIP smoothly. If you don't have `vcpkg` installed, follow their [official instructions](https://github.com/microsoft/vcpkg#quick-start-windows-linux-macos). + + Ensure the `VCPKG_ROOT` environment variable is set to your `vcpkg` installation path (e.g., `export VCPKG_ROOT=~/vcpkg`). + + ```bash + # Clone this repo + git clone https://github.com/stefanDeveloper/heiFIP.git + cd heiFIP/heiFIP/ + + # Create build directory and run CMake using the vcpkg toolchain + # The toolchain will automatically read vcpkg.json and install dependencies! + cmake -B build -S . -DCMAKE_BUILD_TYPE=Release -DCMAKE_TOOLCHAIN_FILE="$VCPKG_ROOT/scripts/buildsystems/vcpkg.cmake" + + # Compile the project + cmake --build build -j$(nproc) + + # The executables 'heiFIP' and 'main' will be produced in build/ + ``` + + ## Testing + + heiFIP includes a comprehensive test suite covering protocol layers, image generation, and CLI functionality. + + ```bash + # Run all tests using CTest + cd build + ctest --output-on-failure + ``` + + ## Getting Started + + After installation the command line interface can be used to extract images from pcap files with the following command (Note: `--dim` defaults to 16 and `--processor` to `NONE`). + ```bash + ./build/heiFIP \ + --name HelloHeiFIP \ + --input /path/to/capture.pcap \ + --output /path/to/outdir \ + --threads 4 \ + --processor HEADER \ + --mode FlowImageTiledAuto \ + --max-pkts 100 \ + --remove-dup + ``` + + ### Options + | Flag | Description | + | ------------------- | -------------------------------------------------------------- | + | `-i`, `--input` | Input PCAP file path | + | `-o`, `--output` | Output directory | + | `-t`, `--threads` | Number of worker threads (default: 1) | + | `-p`, `--processor` | Preprocessing: `NONE` or `HEADER` (default: `NONE`) | + | `-m`, `--mode` | Image type: `PacketImage`, `FlowImage`, `FlowImageTiledFixed`, | + | | `FlowImageTiledAuto`, `MarkovTransitionMatrixFlow`, | + | | `MarkovTransitionMatrixPacket` | + | `--dim` | Base dimension for image (default: 16) | + | `--fill` | Fill or padding value (0–255) | + | `--cols` | Number of columns (for tiled/fixed or Markov flow) | + | `--auto-dim` | Enable auto‑dimension selection | + | `--append` | Enable append mode for FlowImage | + | `--min-dim` | Minimum allowed image dimension | + | `--max-dim` | Maximum allowed image dimension | + | `--min-pkts` | Minimum packets per flow (for tiled/flow modes) | + | `--max-pkts` | Maximum packets per flow | + | `--remove-dup` | Remove duplicate flows/packets by hash | + | `--name` | Filename of processed image | + | `-h`, `--help` | Show this help message | + + ## Extending + + To add a new image type: + + 1. Define a new `ImageArgs` struct in `extractor.hpp`. + 2. Extend the `ImageType` enum and the `ImageArgsVariant` in `extractor.hpp`. + 3. Implement the image logic (following the pattern of `FlowImage` or `heiFIPPacketImage`). + 4. Add the image creation case in `FIPExtractor::createMatrix()`. + 5. Update the CLI `--mode` parser in `cli.cpp` to include your new type. --- diff --git a/heiFIP/cli.cpp b/heiFIP/cli.cpp index 09eeb8b..f6b3985 100644 --- a/heiFIP/cli.cpp +++ b/heiFIP/cli.cpp @@ -11,12 +11,13 @@ void print_usage(const char* progName) { std::cout << "Usage: " << progName << " [options]\n" << " -i, --input FILE input pcap file path\n" << " -o, --output DIR output directory\n" - << " -t, --threads N number of threads (default 1)\n" - << " -p, --processor TYPE preprocessing type: NONE or HEADER\n" + << " -t, --threads N number of threads (default: 1)\n" + << " -p, --processor TYPE preprocessing type: NONE or HEADER (default: NONE)\n" << " -m, --mode MODE image type: FlowImage, FlowImageTiledFixed, FlowImageTiledAuto,\n" << " MarkovTransitionMatrixFlow, MarkovTransitionMatrixPacket, PacketImage\n" - << " --dim N image dimension\n" - << " --fill N fill value for missing data\n" + << " (default: PacketImage)\n" + << " --dim N image dimension (default: 16)\n" + << " --fill N fill value for missing data (default: 0)\n" << " --cols N number of columns (used in some modes)\n" << " --auto-dim enable auto-dimension (FlowImageTiledAuto, etc.)\n" << " --append append mode for FlowImage\n" @@ -25,7 +26,7 @@ void print_usage(const char* progName) { << " --min-pkts N minimum packets per flow\n" << " --max-pkts N maximum packets per flow\n" << " --remove-dup remove duplicate packets/flows\n" - << " --name name of processed image\n " + << " --name NAME name of processed image (default: heiFIPGeneratedImage)\n" << " -h, --help display this help and exit\n"; } @@ -39,7 +40,7 @@ int main(int argc, char* argv[]) { // Optional parameters with defaults std::string image_name = "heiFIPGeneratedImage"; - size_t dim = 0, fill = 0, cols = 0; + size_t dim = 16, fill = 0, cols = 0; bool auto_dim = false, append = false; size_t min_dim = 0, max_dim = 0; size_t min_pkts = 0, max_pkts = 0; From 47e122fe2a61f6dc751ddeba143f112880b0717b Mon Sep 17 00:00:00 2001 From: Stefan Machmeier Date: Thu, 26 Mar 2026 14:44:25 +0100 Subject: [PATCH 7/7] feat: Improve logging for better traceability --- heiFIP/assets/heiFIPPacketImage.hpp | 16 +++--- heiFIP/cli.cpp | 18 +++++- heiFIP/extractor.hpp | 20 ++++--- heiFIP/layers/dns.hpp | 21 ++++--- heiFIP/layers/http.hpp | 6 +- heiFIP/layers/init.hpp | 62 ++++++++++---------- heiFIP/layers/ip.hpp | 4 ++ heiFIP/layers/transport.hpp | 3 + heiFIP/logging.hpp | 88 +++++++++++++++++++++++++++++ 9 files changed, 182 insertions(+), 56 deletions(-) create mode 100644 heiFIP/logging.hpp diff --git a/heiFIP/assets/heiFIPPacketImage.hpp b/heiFIP/assets/heiFIPPacketImage.hpp index 069418d..56e5e07 100644 --- a/heiFIP/assets/heiFIPPacketImage.hpp +++ b/heiFIP/assets/heiFIPPacketImage.hpp @@ -5,6 +5,7 @@ #include "PcapHeaders.h" // Provides PcapPacketHeader for captured length #include #include +#include "logging.hpp" #include #include #include @@ -101,15 +102,16 @@ class heiFIPPacketImage { * Then each byte printed in “HH ” (two-digit hex, space-separated). */ void printHexData() const { - std::cout << std::dec - << "Packet has size" - << " (Size: " << get_cap_length() << " bytes):\n"; + std::stringstream ss; + ss << std::dec + << "Packet has size" + << " (Size: " << get_cap_length() << " bytes):\n"; for (size_t i = 0; i < _data.size(); ++i) { - std::cout << std::hex - << std::setw(2) << std::setfill('0') - << static_cast(_data[i]) << " "; + ss << std::hex + << std::setw(2) << std::setfill('0') + << static_cast(_data[i]) << " "; } - std::cout << std::endl; + LDEBUG(ss.str()); } /** diff --git a/heiFIP/cli.cpp b/heiFIP/cli.cpp index f6b3985..260b19d 100644 --- a/heiFIP/cli.cpp +++ b/heiFIP/cli.cpp @@ -5,6 +5,7 @@ #include "extractor.hpp" #include "runner.hpp" +#include "logging.hpp" /// @brief Prints usage/help information for the CLI tool. void print_usage(const char* progName) { @@ -27,6 +28,8 @@ void print_usage(const char* progName) { << " --max-pkts N maximum packets per flow\n" << " --remove-dup remove duplicate packets/flows\n" << " --name NAME name of processed image (default: heiFIPGeneratedImage)\n" + << " -v, --verbose enable debug logging (same as --log-level DEBUG)\n" + << " --log-level LEVEL set logging level: DEBUG, INFO, WARN, ERROR (default: INFO)\n" << " -h, --help display this help and exit\n"; } @@ -64,6 +67,8 @@ int main(int argc, char* argv[]) { {"min-pkts", required_argument, 0, 0 }, {"max-pkts", required_argument, 0, 0 }, {"remove-dup", no_argument, 0, 0 }, + {"verbose", no_argument, 0, 'v'}, + {"log-level", required_argument, 0, 0 }, {"help", no_argument, 0, 'h'}, {0, 0, 0, 0} }; @@ -71,7 +76,7 @@ int main(int argc, char* argv[]) { // Parse command-line arguments int opt; int long_index = 0; - while ((opt = getopt_long(argc, argv, "i:o:t:p:m:h", long_opts, &long_index)) != -1) { + while ((opt = getopt_long(argc, argv, "i:o:t:p:m:vh", long_opts, &long_index)) != -1) { switch (opt) { case 'i': input_file = optarg; break; case 'o': output_dir = optarg; break; @@ -102,6 +107,17 @@ int main(int argc, char* argv[]) { else if (strcmp(long_opts[long_index].name, "max-pkts") == 0) max_pkts = std::stoi(optarg); else if (strcmp(long_opts[long_index].name, "remove-dup") == 0) remove_dup = true; else if (strcmp(long_opts[long_index].name, "name") == 0) image_name = optarg; + else if (strcmp(long_opts[long_index].name, "log-level") == 0) { + std::string lvl = optarg; + if (lvl == "DEBUG") Logger::getInstance().setLevel(LogLevel::DEBUG); + else if (lvl == "INFO") Logger::getInstance().setLevel(LogLevel::INFO); + else if (lvl == "WARN") Logger::getInstance().setLevel(LogLevel::WARNING); + else if (lvl == "ERROR") Logger::getInstance().setLevel(LogLevel::ERROR); + else { LWARN("Unknown log level: " << lvl << ". Defaulting to INFO."); } + } + break; + case 'v': + Logger::getInstance().setLevel(LogLevel::DEBUG); break; case 'h': print_usage(argv[0]); return 0; default: print_usage(argv[0]); return 1; diff --git a/heiFIP/extractor.hpp b/heiFIP/extractor.hpp index b23b806..a126e15 100644 --- a/heiFIP/extractor.hpp +++ b/heiFIP/extractor.hpp @@ -13,6 +13,7 @@ #include "flow_tiled_fixed.hpp" #include "markov_chain.hpp" #include "heiFIPPacketImage.hpp" +#include "logging.hpp" /** @@ -169,7 +170,7 @@ class FIPExtractor { bool removeDuplicates) { if (image.get_matrix().empty() || image.get_matrix()[0].empty()) { - std::cout << "[!] Image not created: empty matrix.\n"; + LWARN("Image not created: empty matrix."); return false; } @@ -178,22 +179,20 @@ class FIPExtractor { // Enforce minimum dimension constraint: if (height < minImageDim || width < minImageDim) { - std::cout << "[!] Image not created: dimensions smaller than minimum (" - << minImageDim << ").\n"; + LWARN("Image not created: dimensions smaller than minimum (" << minImageDim << ")."); return false; } // Enforce maximum dimension constraint (if nonzero): if (maxImageDim != 0 && (height > maxImageDim || width > maxImageDim)) { - std::cout << "[!] Image not created: dimensions exceed maximum (" - << maxImageDim << ").\n"; + LWARN("Image not created: dimensions exceed maximum (" << maxImageDim << ")."); return false; } if (removeDuplicates) { std::vector> matrix = image.get_matrix(); if (imagesCreatedSet.count(matrix)) { - std::cout << "[!] Image not created: duplicate detected.\n"; + LDEBUG("Image not created: duplicate detected."); return false; } imagesCreatedSet.insert({matrix, true}); @@ -237,9 +236,12 @@ class FIPExtractor { ) { // Verify existence of the pcap file before proceeding: if (!std::filesystem::exists(input_file)) { + LERROR("Input file does not exist: " << input_file); throw std::runtime_error("Input file does not exist: " + input_file); } + LINFO("Processing PCAP file: " << input_file); + // Read and preprocess packets from the file: // - If remove_duplicates is true, duplicates are dropped here. // - If max_packets_per_flow > 0, stop reading after that many packets. @@ -251,6 +253,8 @@ class FIPExtractor { max_packets_per_flow ); + LINFO("Read " << processed_packets.size() << " packets from file."); + // Delegate to createMatrix, passing along preprocessing/filtering criteria return createMatrix( processed_packets, @@ -356,6 +360,7 @@ class FIPExtractor { // If we have a maximum packet‐per‐flow limit, cut the packet list down now: if (max_packets_per_flow && packets.size() > max_packets_per_flow) { + LDEBUG("Truncating packet list from " << packets.size() << " to " << max_packets_per_flow); packets.resize(max_packets_per_flow); } @@ -525,7 +530,7 @@ class FIPExtractor { void save_image(const UInt8Matrix& img, const std::string& output_path) { // Quick sanity check: must have at least one image, and that image must be non-empty if (img.empty() || img[0].empty() || img[0][0].empty()) { - std::cerr << "[!] Empty image, cannot save: " << output_path << "\n"; + LWARN("Empty image, cannot save: " << output_path); return; } @@ -561,6 +566,7 @@ class FIPExtractor { // Write the PNG file to disk cv::imwrite(final_path, mat); + LINFO("Image saved to: " << final_path); } } diff --git a/heiFIP/layers/dns.hpp b/heiFIP/layers/dns.hpp index 3bf18ca..2471e4f 100644 --- a/heiFIP/layers/dns.hpp +++ b/heiFIP/layers/dns.hpp @@ -5,6 +5,8 @@ #include #include "transport.hpp" +#include "logging.hpp" +#include "header.hpp" /** * @class DNSPacket @@ -87,8 +89,9 @@ class DNSPacket : public TransportPacket { } // 3) After processing individual records, replace the DNS header itself - pcpp::DnsLayer* oldDNS = Packet.getLayerOfType(); - pcpp::dnshdr* dnsHeader = oldDNS->getDnsHeader(); + LDEBUG("DNSPacket::header_preprocessing() - Substituting DNS layer with CustomDNSLayer"); + pcpp::DnsLayer* oldDns = Packet.getLayerOfType(); + pcpp::dnshdr* dnsHeader = oldDns->getDnsHeader(); // Build a new CustomDNS header using fields from the old DNS header std::unique_ptr customDns = std::make_unique(); @@ -102,18 +105,18 @@ class DNSPacket : public TransportPacket { customDns->ad = dnsHeader->authenticData; customDns->cd = dnsHeader->checkingDisabled; customDns->rcode = static_cast(dnsHeader->responseCode); - customDns->qdCount = oldDNS->getQueryCount(); - customDns->anCount = oldDNS->getAnswerCount(); - customDns->nsCount = oldDNS->getAuthorityCount(); - customDns->arCount = oldDNS->getAdditionalRecordCount(); + customDns->qdCount = oldDns->getQueryCount(); + customDns->anCount = oldDns->getAnswerCount(); + customDns->nsCount = oldDns->getAuthorityCount(); + customDns->arCount = oldDns->getAdditionalRecordCount(); // Insert the new CustomDNS layer immediately before the old DNS layer - pcpp::Layer* prev = oldDNS->getPrevLayer(); + pcpp::Layer* prev = oldDns->getPrevLayer(); Packet.insertLayer(prev, customDns.release()); // Detach and delete the old DNS layer - Packet.detachLayer(oldDNS); - delete oldDNS; + Packet.detachLayer(oldDns); + delete oldDns; // Recompute checksums and lengths for all layers upstream of the new DNS header Packet.computeCalculateFields(); diff --git a/heiFIP/layers/http.hpp b/heiFIP/layers/http.hpp index 94f5964..a41eaee 100644 --- a/heiFIP/layers/http.hpp +++ b/heiFIP/layers/http.hpp @@ -6,6 +6,8 @@ #include #include "transport.hpp" +#include "logging.hpp" +#include "header.hpp" #include "PcapPlusPlusVersion.h" #include "HttpLayer.h" @@ -116,6 +118,7 @@ class HTTPRequestPacket : public HTTPPacket { * 7. Recompute checksums/lengths (Packet.computeCalculateFields()). */ void header_preprocessing() override { + LDEBUG("HTTPRequestPacket::header_preprocessing() - Substituting HTTPRequest trace"); // First, perform any transport-layer substitutions HTTPPacket::header_preprocessing(); @@ -224,7 +227,7 @@ class HTTPRequestPacket : public HTTPPacket { * strip all layers that follow the HttpRequestLayer. * * Workflow: - * 1. Check if "Raw" is present in layer_map. + * 1. Check if "Raw" is in layer_map. * 2. Locate the HttpRequestLayer. * 3. Call Packet.removeAllLayersAfter(httpRequestLayer) to drop downstream payload layers. * 4. Recompute checksums/lengths. @@ -301,6 +304,7 @@ class HTTPResponsePacket : public HTTPPacket { * 6. Add the custom layer (Packet.addLayer(customResp)), then recompute checksums/lengths. */ void header_preprocessing() override { + LDEBUG("HTTPResponsePacket::header_preprocessing() - Substituting HTTPResponse trace"); // First, perform any transport-layer and IP/Ethernet substitutions HTTPPacket::header_preprocessing(); diff --git a/heiFIP/layers/init.hpp b/heiFIP/layers/init.hpp index 0bc0028..28f6a8c 100644 --- a/heiFIP/layers/init.hpp +++ b/heiFIP/layers/init.hpp @@ -9,6 +9,7 @@ #include "packet.hpp" #include "dns.hpp" +#include "logging.hpp" #include "http.hpp" #include "ip.hpp" #include "ssh.hpp" @@ -83,6 +84,7 @@ class PacketProcessor { pcpp::PcapFileReaderDevice reader(filename); if (!reader.open()) { + LERROR("Failed to open PCAP file for reading: " << filename); return result; } @@ -99,12 +101,10 @@ class PacketProcessor { if (res.second) { // was inserted, new result.push_back(std::move(fippkt)); } else { - // This case occurs if two packets are the same (have the same hash value) - // which results in the packet not being used if remove_duplicates is set if (!removeDuplicates) { result.push_back(std::move(fippkt)); } else { - std::cout << "[-] Warning: Duplicate packet with hash value " << fippkt->getHash() << " removed" << std::endl; + LDEBUG("Duplicate packet with hash " << fippkt->getHash() << " removed"); } } } else if (fippkt) { @@ -127,14 +127,12 @@ class PacketProcessor { if (!fippkt->getHash().empty()) { auto res = hashDict.insert(fippkt->getHash()); if (res.second) { - result.push_back(std::move(fippkt)); + result.push_back(std::move(fippkt)); } else { - // This case occurs if two packets are the same (have the same hash value) - // which results in the packet not being used if remove_duplicates is set if (!removeDuplicates) { result.push_back(std::move(fippkt)); } else { - std::cout << "[-] Warning: Duplicate packet with hash value " << fippkt->getHash() << " removed" << std::endl; + LDEBUG("Duplicate packet with hash " << fippkt->getHash() << " removed"); } } } else { @@ -144,8 +142,6 @@ class PacketProcessor { return result; } - // TODO: Add methods to process packets by type - private: std::string fileExtension; std::unordered_set hashDict; @@ -156,34 +152,38 @@ class PacketProcessor { * Optionally invoke header preprocessing. */ std::unique_ptr preprocess(std::unique_ptr& packet, PacketProcessorType type) { - std::unique_ptr fippacket = std::make_unique(std::make_unique(*packet)); - std::unordered_map address_mapping = fippacket->getAdressMapping(); - std::unordered_map layer_map = fippacket->getLayerMap(); - // HTTP handling - if (layer_map.count("HTTP")) { - fippacket = std::make_unique(std::move(packet), address_mapping, layer_map); - } - else if (layer_map.count("HTTPRequest")) { + // Build a temporary wrapper to detect layers + std::unique_ptr detector = std::make_unique(std::make_unique(*packet)); + auto address_mapping = detector->getAdressMapping(); + auto layer_map = detector->getLayerMap(); + + std::unique_ptr fippacket; + + // Classification Priority: HTTP -> DNS -> SSH -> Transport -> IP -> Ether -> Unknown + if (layer_map.count("HTTPRequest")) { fippacket = std::make_unique(std::move(packet), address_mapping, layer_map); - } - else if (layer_map.count("HTTPResponse")) { + LDEBUG("Classified packet as HTTPRequest"); + } else if (layer_map.count("HTTPResponse")) { fippacket = std::make_unique(std::move(packet), address_mapping, layer_map); - } - // DNS handling - else if (layer_map.count("DNS")) { + LDEBUG("Classified packet as HTTPResponse"); + } else if (layer_map.count("HTTP")) { + fippacket = std::make_unique(std::move(packet), address_mapping, layer_map); + LDEBUG("Classified packet as HTTP"); + } else if (layer_map.count("DNS")) { fippacket = std::make_unique(std::move(packet), address_mapping, layer_map); - } - // Transport layer (TCP/UDP) - else if (layer_map.count("TCP") || layer_map.count("UDP")) { + LDEBUG("Classified packet as DNS"); + } else if (layer_map.count("TCP") || layer_map.count("UDP")) { fippacket = std::make_unique(std::move(packet), address_mapping, layer_map); - } - // Network layer (IPv4/IPv6) - else if (layer_map.count("IPv4") || layer_map.count("IPv6")) { + LDEBUG("Classified packet as Transport (TCP/UDP)"); + } else if (layer_map.count("IPv4") || layer_map.count("IPv6")) { fippacket = std::make_unique(std::move(packet), address_mapping, layer_map); - } - // Data link layer (Ethernet) - else if (layer_map.count("Ethernet")) { + LDEBUG("Classified packet as IP"); + } else if (layer_map.count("Ethernet")) { fippacket = std::make_unique(std::move(packet), address_mapping, layer_map); + LDEBUG("Classified packet as Ethernet"); + } else { + fippacket = std::make_unique(std::move(packet), address_mapping, layer_map); + LDEBUG("Classified packet as Unknown/Generic"); } // Header preprocessing if requested diff --git a/heiFIP/layers/ip.hpp b/heiFIP/layers/ip.hpp index 4448059..a0236ef 100644 --- a/heiFIP/layers/ip.hpp +++ b/heiFIP/layers/ip.hpp @@ -16,6 +16,7 @@ #include // For SHA-256 hashing of header fields #include "packet.hpp" +#include "logging.hpp" #include "header.hpp" /** @@ -129,6 +130,7 @@ class IPPacket : public EtherPacket { void header_preprocessing() override { // IPv4 replacement logic if (layer_map.count("IPv4")) { + LDEBUG("IPPacket::header_preprocessing() - Substituting IPv4 layer"); pcpp::IPv4Layer* oldIp = Packet.getLayerOfType(); if (!oldIp) return; // No IPv4 layer found—nothing to replace @@ -150,6 +152,7 @@ class IPPacket : public EtherPacket { // IPv6 replacement logic if (layer_map.count("IPv6")) { + LDEBUG("IPPacket::header_preprocessing() - Substituting IPv6 layer"); pcpp::IPv6Layer* oldIp = Packet.getLayerOfType(); if (!oldIp) return; // No IPv6 layer found @@ -323,6 +326,7 @@ class IPPacket : public EtherPacket { return address_mapping[oldAddr]; } std::string newAddr = isIPv6 ? generateRandomIPv6() : generateRandomIPv4(); + LDEBUG("Mapping address " << oldAddr << " -> " << newAddr); address_mapping[oldAddr] = newAddr; return newAddr; } diff --git a/heiFIP/layers/transport.hpp b/heiFIP/layers/transport.hpp index 2293991..ed8ffb6 100644 --- a/heiFIP/layers/transport.hpp +++ b/heiFIP/layers/transport.hpp @@ -12,6 +12,7 @@ #include "header.hpp" #include "ip.hpp" +#include "logging.hpp" #include "packet.hpp" /** @@ -189,6 +190,7 @@ class TransportPacket : public IPPacket { void header_preprocessing() override { // Replace TCP layer if present if (layer_map["TCP"]) { + LDEBUG("TransportPacket::header_preprocessing() - Substituting TCP layer"); pcpp::TcpLayer* oldTcp = Packet.getLayerOfType(); if (!oldTcp) { // No TCP layer found; skip @@ -212,6 +214,7 @@ class TransportPacket : public IPPacket { // Replace UDP layer if present if (layer_map["UDP"]) { + LDEBUG("TransportPacket::header_preprocessing() - Substituting UDP layer"); pcpp::UdpLayer* oldUdp = Packet.getLayerOfType(); if (!oldUdp) { // No UDP layer found; skip diff --git a/heiFIP/logging.hpp b/heiFIP/logging.hpp new file mode 100644 index 0000000..48233a5 --- /dev/null +++ b/heiFIP/logging.hpp @@ -0,0 +1,88 @@ +#pragma once + +#include +#include +#include + +/** + * @enum LogLevel + * @brief Severity levels for logging. + */ +enum class LogLevel { + DEBUG = 0, + INFO = 1, + WARNING = 2, + ERROR = 3, + NONE = 4 +}; + +/** + * @class Logger + * @brief Simple thread-safe logger for heiFIP. + */ +class Logger { +public: + static Logger& getInstance() { + static Logger instance; + return instance; + } + + void setLevel(LogLevel level) { + std::lock_guard lock(mutex_); + currentLevel_ = level; + } + + LogLevel getLevel() const { + return currentLevel_; + } + + void log(LogLevel level, const std::string& message) { + if (level < currentLevel_) return; + + std::lock_guard lock(mutex_); + std::ostream& os = (level >= LogLevel::WARNING) ? std::cerr : std::cout; + + os << "[" << levelToString(level) << "] " << message << std::endl; + } + +private: + Logger() : currentLevel_(LogLevel::INFO) {} + ~Logger() = default; + + Logger(const Logger&) = delete; + Logger& operator=(const Logger&) = delete; + + std::string levelToString(LogLevel level) { + switch (level) { + case LogLevel::DEBUG: return "DEBUG"; + case LogLevel::INFO: return "INFO"; + case LogLevel::WARNING: return "WARNING"; + case LogLevel::ERROR: return "ERROR"; + default: return "UNKNOWN"; + } + } + + LogLevel currentLevel_; + std::mutex mutex_; +}; + +// Convenience macros for logging +#define LOG_DEBUG(msg) Logger::getInstance().log(LogLevel::DEBUG, msg) +#define LOG_INFO(msg) Logger::getInstance().log(LogLevel::INFO, msg) +#define LOG_WARN(msg) Logger::getInstance().log(LogLevel::WARNING, msg) +#define LOG_ERROR(msg) Logger::getInstance().log(LogLevel::ERROR, msg) + +// Helper for stream-style logging (optional but nice) +#include +#define LOG_STREAM(level, msg) { \ + if (level >= Logger::getInstance().getLevel()) { \ + std::stringstream ss; \ + ss << msg; \ + Logger::getInstance().log(level, ss.str()); \ + } \ +} + +#define LDEBUG(msg) LOG_STREAM(LogLevel::DEBUG, msg) +#define LINFO(msg) LOG_STREAM(LogLevel::INFO, msg) +#define LWARN(msg) LOG_STREAM(LogLevel::WARNING, msg) +#define LERROR(msg) LOG_STREAM(LogLevel::ERROR, msg)