diff --git a/.codespellrc b/.codespellrc index c7019c766a..7506fd03df 100644 --- a/.codespellrc +++ b/.codespellrc @@ -1,4 +1,4 @@ [codespell] -skip = *.dat,typos-config.toml,.git,.venv,./ci,./Dist,./mk,./Tests/ExamplesTest/expected_output,./Tests/ExamplesTest/pcap_examples,./Tests/Packet++Test/PacketExamples,./Tests/Pcap++Test/PcapExamples,./3rdParty,./Examples/PcapSearch/dirent-for-Visual-Studio +skip = *.dat,typos-config.toml,.git,.venv,venv,./out,./ci,./Dist,./mk,./Tests/ExamplesTest/expected_output,./Tests/ExamplesTest/pcap_examples,./Tests/Packet++Test/PacketExamples,./Tests/Pcap++Test/PcapExamples,./3rdParty,./Examples/PcapSearch/dirent-for-Visual-Studio ignore-words = codespell-ignore-list.txt count = diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 77ca6d1a3c..c59e90eeef 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -38,17 +38,17 @@ repos: - id: cppcheck args: ["--std=c++11", "--language=c++", "--suppressions-list=cppcheckSuppressions.txt", "--inline-suppr", "--force"] - repo: https://github.com/BlankSpruce/gersemi - rev: 0.17.1 + rev: 0.18.2 hooks: - id: gersemi args: ["-c"] - repo: https://github.com/codespell-project/codespell - rev: v2.3.0 + rev: v2.4.1 hooks: - id: codespell pass_filenames: false - repo: https://github.com/crate-ci/typos - rev: codespell-dict-v0.5.0 + rev: typos-dict-v0.12.4 hooks: - id: typos args: ['--config=typos-config.toml'] diff --git a/Common++/CMakeLists.txt b/Common++/CMakeLists.txt index 9170aea53e..8ef1ca1f79 100644 --- a/Common++/CMakeLists.txt +++ b/Common++/CMakeLists.txt @@ -22,6 +22,7 @@ set( header/Logger.h header/LRUList.h header/MacAddress.h + header/ObjectPool.h header/OUILookup.h header/PcapPlusPlusVersion.h header/PointerVector.h diff --git a/Common++/header/IpAddress.h b/Common++/header/IpAddress.h index 38936e8570..d93a341b70 100644 --- a/Common++/header/IpAddress.h +++ b/Common++/header/IpAddress.h @@ -876,5 +876,4 @@ namespace pcpp oss << network.toString(); return oss; } - } // namespace pcpp diff --git a/Common++/header/Logger.h b/Common++/header/Logger.h index 2b5ec3954f..df9ce372ee 100644 --- a/Common++/header/Logger.h +++ b/Common++/header/Logger.h @@ -1,10 +1,14 @@ #pragma once #include -#include -#include -#include #include +#include +#include +#include +#include +#include +#include "DeprecationUtils.h" +#include "ObjectPool.h" #ifndef LOG_MODULE # define LOG_MODULE UndefinedLogModule @@ -17,35 +21,29 @@ # define PCAPPP_FILENAME __FILE__ #endif -#define PCPP_LOG(level, message) \ - do \ - { \ - std::ostringstream* sstream = pcpp::Logger::getInstance().internalCreateLogStream(); \ - (*sstream) << message; \ - pcpp::Logger::getInstance().internalPrintLogMessage(sstream, level, PCAPPP_FILENAME, __FUNCTION__, __LINE__); \ - } while (0) - -#define PCPP_LOG_DEBUG(message) \ - do \ - { \ - if (pcpp::Logger::getInstance().logsEnabled() && pcpp::Logger::getInstance().isDebugEnabled(LOG_MODULE)) \ - { \ - PCPP_LOG(pcpp::Logger::Debug, message); \ - } \ - } while (0) +/// @file -#define PCPP_LOG_ERROR(message) \ - do \ - { \ - PCPP_LOG(pcpp::Logger::Error, message); \ - } while (0) +// Compile time log levels. +// Allows for conditional removal of unwanted log calls at compile time. +#define PCPP_LOG_LEVEL_OFF 0 +#define PCPP_LOG_LEVEL_ERROR 1 +#define PCPP_LOG_LEVEL_INFO 2 +#define PCPP_LOG_LEVEL_DEBUG 3 -/// @file +// All log messages built via a PCPP_LOG_* macro below the PCPP_ACTIVE_LOG_LEVEL will be removed at compile time. +// Uses the PCPP_ACTIVE_LOG_LEVEL if it is defined, otherwise defaults to PCAP_LOG_LEVEL_DEBUG +#ifndef PCPP_ACTIVE_LOG_LEVEL +# define PCPP_ACTIVE_LOG_LEVEL PCPP_LOG_LEVEL_DEBUG +#endif // !PCPP_ACTIVE_LOG_LEVEL /// @namespace pcpp /// @brief The main namespace for the PcapPlusPlus lib namespace pcpp { + /// Cross-platform and thread-safe version of strerror + /// @param errnum Value of errno + /// @return String representation of the error number + std::string getErrorString(int errnum); /// An enum representing all PcapPlusPlus modules enum LogModule : uint8_t @@ -75,6 +73,7 @@ namespace pcpp PacketLogModuleGreLayer, ///< GreLayer module (Packet++) PacketLogModuleSSLLayer, ///< SSLLayer module (Packet++) PacketLogModuleSllLayer, ///< SllLayer module (Packet++) + PacketLogModuleSll2Layer, ///< Sll2Layer module (Packet++) PacketLogModuleNflogLayer, ///< NflogLayer module (Packet++) PacketLogModuleDhcpLayer, ///< DhcpLayer module (Packet++) PacketLogModuleDhcpV6Layer, ///< DhcpV6Layer module (Packet++) @@ -109,14 +108,100 @@ namespace pcpp PcapLogModuleDpdkDevice, ///< DpdkDevice module (Pcap++) PcapLogModuleKniDevice, ///< KniDevice module (Pcap++) PcapLogModuleXdpDevice, ///< XdpDevice module (Pcap++) - NetworkUtils, ///< NetworkUtils module (Pcap++) + PcapLogModuleNetworkUtils, ///< Network Utils module (Pcap++) NumOfLogModules }; - /// Cross-platform and thread-safe version of strerror - /// @param errnum Value of errno - /// @return String representation of the error number - std::string getErrorString(int errnum); + /// @struct LogSource + /// Represents the source of a log message. + /// Contains information about the source file, function, line number, and the log module. + struct LogSource + { + /// Default constructor for LogSource. + constexpr LogSource() = default; + + /// Constructor for LogSource with only the log module. + /// @param logModule The log module. + explicit constexpr LogSource(LogModule logModule) : logModule(logModule) + {} + + /// Constructor for LogSource with all parameters. + /// @param logModule The log module. + /// @param file The source file. + /// @param function The source function. + /// @param line The line number. + constexpr LogSource(LogModule logModule, const char* file, const char* function, int line) + : file(file), function(function), line(line), logModule(logModule) + {} + + const char* file = nullptr; /**< The source file. */ + const char* function = nullptr; /**< The source function. */ + int line = 0; /**< The line number. */ + LogModule logModule = UndefinedLogModule; /**< The log module. */ + }; + + /// An enum representing the log level. Currently 4 log levels are supported: Off, Error, Info and Debug. Info is + /// the default log level + enum class LogLevel + { + Off = PCPP_LOG_LEVEL_OFF, ///< No log messages are emitted. + Error = PCPP_LOG_LEVEL_ERROR, ///< Error level logs are emitted. + Info = PCPP_LOG_LEVEL_INFO, ///< Info level logs and above are emitted. + Debug = PCPP_LOG_LEVEL_DEBUG ///< Debug level logs and above are emitted. + }; + + inline std::ostream& operator<<(std::ostream& s, LogLevel v) + { + return s << static_cast::type>(v); + } + + // Forward declaration + class Logger; + + namespace internal + { + /// @class LogContext + /// @brief A context encapsulating the details of a single log message to be passed to the Logger. + class LogContext + { + public: + friend class pcpp::Logger; + + /// @brief Creates a context with an empty message with Info level and no source. + LogContext() = default; + + /// @brief Creates a context with an empty message with the given level and source. + /// @param level The log level for this message. + /// @param source The log source. + explicit LogContext(LogLevel level, LogSource const& source = {}) : m_Source(source), m_Level(level) + {} + + /// @brief Initializes the context with an empty message and the given level and source. + /// @param level The log level for this message. + /// @param source The log source. + void init(LogLevel level, LogSource const& source) + { + m_Source = source; + m_Level = level; + m_Stream.clear(); + m_Stream.str({}); + } + + /// @brief Appends to the message. + /// @param value The value to append. + /// @return A reference to this context. + template inline LogContext& operator<<(T const& value) + { + m_Stream << value; + return *this; + } + + private: + std::ostringstream m_Stream; + LogSource m_Source; + LogLevel m_Level = LogLevel::Info; + }; + } // namespace internal /// @class Logger /// PcapPlusPlus logger manager. @@ -138,14 +223,14 @@ namespace pcpp class Logger { public: - /// An enum representing the log level. Currently 3 log levels are supported: Error, Info and Debug. Info is the - /// default log level - enum LogLevel : uint8_t - { - Error, ///< Error log level - Info, ///< Info log level - Debug ///< Debug log level - }; + // Deprecated, Use the LogLevel in the pcpp namespace instead. + using LogLevel = pcpp::LogLevel; + PCPP_DEPRECATED("Use the LogLevel in the pcpp namespace instead.") + static const LogLevel Error = LogLevel::Error; + PCPP_DEPRECATED("Use the LogLevel in the pcpp namespace instead.") + static const LogLevel Info = LogLevel::Info; + PCPP_DEPRECATED("Use the LogLevel in the pcpp namespace instead.") + static const LogLevel Debug = LogLevel::Debug; /// @typedef LogPrinter /// Log printer callback. Used for printing the logs in a custom way. @@ -154,7 +239,10 @@ namespace pcpp /// @param[in] file The source file in PcapPlusPlus code the log message is coming from /// @param[in] method The method in PcapPlusPlus code the log message is coming from /// @param[in] line The line in PcapPlusPlus code the log message is coming from - using LogPrinter = void (*)(LogLevel, const std::string&, const std::string&, const std::string&, const int); + /// @remarks The printer callback should support being called from multiple threads simultaneously. + using LogPrinter = + std::add_pointer::type; /// A static method for converting the log level enum to a string. /// @param[in] logLevel A log level enum @@ -182,7 +270,16 @@ namespace pcpp /// @return True if this module log level is "debug". False otherwise bool isDebugEnabled(LogModule module) const { - return m_LogModulesArray[module] == Debug; + return m_LogModulesArray[module] == LogLevel::Debug; + } + + /// @brief Check whether a log level should be emitted by the logger. + /// @param level The level of the log message. + /// @param module PcapPlusPlus module + /// @return True if the message should be emitted. False otherwise. + bool shouldLog(LogLevel level, LogModule module) const + { + return level != LogLevel::Off && m_LogModulesArray[module] >= level; } /// Set all PcapPlusPlus modules to a certain log level @@ -209,8 +306,9 @@ namespace pcpp } /// @return Get the last error message - std::string getLastError() + std::string getLastError() const { + std::lock_guard lock(m_LastErrorMtx); return m_LastError; } @@ -233,17 +331,32 @@ namespace pcpp return m_LogsEnabled; } - template Logger& operator<<(const T& msg) + /// @brief Controls if the logger should use a pool of LogContext objects. + /// + /// If enabled is set to false, preallocate and maxPoolSize are ignored. + /// @param enabled True to enable context pooling, false to disable. + /// @param preallocate The number of LogContext objects to preallocate in the pool. + /// @param maxPoolSize The maximum number of LogContext objects to keep in the pool. + /// @remarks Disabling the pooling clears the pool. + void useContextPooling(bool enabled, std::size_t preallocate = 2, std::size_t maxPoolSize = 10) { - (*m_LogStream) << msg; - return *this; - } + m_UseContextPooling = enabled; - static std::ostringstream* internalCreateLogStream(); + if (m_UseContextPooling) + { + m_LogContextPool.setMaxSize(maxPoolSize); - /// An internal method to print log messages. Shouldn't be used externally. - void internalPrintLogMessage(std::ostringstream* logStream, Logger::LogLevel logLevel, const char* file, - const char* method, int line); + if (preallocate > 0) + { + m_LogContextPool.preallocate(preallocate); + } + } + else + { + // Clear the pool if we're disabling pooling. + m_LogContextPool.clear(); + } + } /// Get access to Logger singleton /// @todo: make this singleton thread-safe/ @@ -254,12 +367,37 @@ namespace pcpp return instance; } + /// @brief Creates a new LogContext with Info level and no source. + /// @return A new LogContext. + std::unique_ptr createLogContext(); + + /// @brief Creates a new LogContext with the given level and source. + /// @param level The log level for this message. + /// @param source The log source. + /// @return A new LogContext. + std::unique_ptr createLogContext(LogLevel level, LogSource const& source = {}); + + /// @brief Directly emits a log message bypassing all level checks. + /// @param source The log source. + /// @param level The log level for this message. This is only used for the log printer. + /// @param message The log message. + void emit(LogSource const& source, LogLevel level, std::string const& message); + + /// @brief Directly emits a log message bypassing all level checks. + /// @param message The log message. + void emit(std::unique_ptr message); + private: bool m_LogsEnabled; - Logger::LogLevel m_LogModulesArray[NumOfLogModules]{}; + std::array m_LogModulesArray; LogPrinter m_LogPrinter; + + mutable std::mutex m_LastErrorMtx; std::string m_LastError; - std::ostringstream* m_LogStream{}; + + bool m_UseContextPooling = true; + // Keep a maximum of 10 LogContext objects in the pool. + internal::DynamicObjectPool m_LogContextPool{ 10, 2 }; // private c'tor - this class is a singleton Logger(); @@ -267,4 +405,36 @@ namespace pcpp static void defaultLogPrinter(LogLevel logLevel, const std::string& logMessage, const std::string& file, const std::string& method, int line); }; + } // namespace pcpp + +#define PCPP_LOG(level, message) \ + do \ + { \ + auto& logger = pcpp::Logger::getInstance(); \ + if (logger.shouldLog(level, LOG_MODULE)) \ + { \ + auto ctx = \ + logger.createLogContext(level, pcpp::LogSource(LOG_MODULE, PCAPPP_FILENAME, __FUNCTION__, __LINE__)); \ + (*ctx) << message; \ + logger.emit(std::move(ctx)); \ + } \ + } while (0) + +#if PCPP_ACTIVE_LOG_LEVEL >= PCPP_LOG_LEVEL_DEBUG +# define PCPP_LOG_DEBUG(message) PCPP_LOG(pcpp::LogLevel::Debug, message) +#else +# define PCPP_LOG_DEBUG(message) (void)0 +#endif + +#if PCPP_ACTIVE_LOG_LEVEL >= PCPP_LOG_LEVEL_INFO +# define PCPP_LOG_INFO(message) PCPP_LOG(pcpp::LogLevel::Info, message) +#else +# define PCPP_LOG_INFO(message) (void)0 +#endif + +#if PCPP_ACTIVE_LOG_LEVEL >= PCPP_LOG_LEVEL_ERROR +# define PCPP_LOG_ERROR(message) PCPP_LOG(pcpp::LogLevel::Error, message) +#else +# define PCPP_LOG_ERROR(message) (void)0 +#endif diff --git a/Common++/header/MacAddress.h b/Common++/header/MacAddress.h index e34c9d99a9..1cf5852e6b 100644 --- a/Common++/header/MacAddress.h +++ b/Common++/header/MacAddress.h @@ -5,8 +5,8 @@ #include #include #include -#include #include +#include /// @file @@ -28,11 +28,17 @@ namespace pcpp /// The byte array length should be 6 (as MAC address is 6-byte long), and the remaining bytes are ignored. /// If the byte array is invalid, the constructor throws an exception. /// @param[in] addr A pointer to the byte array containing 6 bytes representing the MAC address - explicit MacAddress(const uint8_t* addr) + explicit MacAddress(const uint8_t addr[6]) { - memcpy(m_Address, addr, sizeof(m_Address)); + std::copy(addr, addr + 6, m_Address.begin()); } + /// A constructor that creates an instance of the class out of a std::array. + /// The array length should be 6 (as MAC address is 6-byte long). + /// @param [in] addr A std::array containing 6 bytes representing the MAC address + explicit MacAddress(const std::array& addr) : m_Address(addr) + {} + /// A constructor that creates an instance of the class out of a std::string. /// If the string doesn't represent a valid MAC address, the constructor throws an exception. /// @param[in] addr the string representing the MAC address in format "00:00:00:00:00:00" @@ -54,14 +60,8 @@ namespace pcpp /// @param[in] sixthOctet Represent the sixth octet in the address MacAddress(uint8_t firstOctet, uint8_t secondOctet, uint8_t thirdOctet, uint8_t fourthOctet, uint8_t fifthOctet, uint8_t sixthOctet) - { - m_Address[0] = firstOctet; - m_Address[1] = secondOctet; - m_Address[2] = thirdOctet; - m_Address[3] = fourthOctet; - m_Address[4] = fifthOctet; - m_Address[5] = sixthOctet; - } + : m_Address{ firstOctet, secondOctet, thirdOctet, fourthOctet, fifthOctet, sixthOctet } + {} /// A constructor that creates an instance out of the initializer list. /// The byte list length should be 6 (as MAC address is 6-byte long). @@ -69,7 +69,7 @@ namespace pcpp /// @param[in] octets An initializer list containing the values of type uint8_t representing the MAC address MacAddress(std::initializer_list octets) { - if (octets.size() != sizeof(m_Address)) + if (octets.size() != m_Address.size()) { throw std::invalid_argument("Invalid initializer list size, should be 6"); } @@ -81,7 +81,7 @@ namespace pcpp /// @return True if addresses are equal, false otherwise bool operator==(const MacAddress& other) const { - return memcmp(m_Address, other.m_Address, sizeof(m_Address)) == 0; + return m_Address == other.m_Address; } /// Overload of the not-equal operator @@ -103,7 +103,7 @@ namespace pcpp throw std::invalid_argument("Invalid initializer list size, should be 6"); } - std::copy(octets.begin(), octets.end(), std::begin(m_Address)); + std::copy(octets.begin(), octets.end(), m_Address.begin()); return *this; } @@ -111,35 +111,41 @@ namespace pcpp /// @return The pointer to raw data const uint8_t* getRawData() const { - return m_Address; + return m_Address.data(); } /// Returns a std::string representation of the address /// @return A string representation of the address std::string toString() const; + /// @return A 6-byte integer representing the MAC address + std::array toByteArray() const + { + return m_Address; + } + /// Allocates a byte array of length 6 and copies address value into it. Array deallocation is user /// responsibility /// @param[in] arr A pointer to where array will be allocated void copyTo(uint8_t** arr) const { - *arr = new uint8_t[sizeof(m_Address)]; - memcpy(*arr, m_Address, sizeof(m_Address)); + *arr = new uint8_t[m_Address.size()]; + std::copy(m_Address.begin(), m_Address.end(), *arr); } /// Gets a pointer to an already allocated byte array and copies the address value to it. /// This method assumes array allocated size is at least 6 (the size of a MAC address) /// @param[in] arr A pointer to the array which address will be copied to - void copyTo(uint8_t* arr) const + void copyTo(uint8_t arr[6]) const { - memcpy(arr, m_Address, sizeof(m_Address)); + std::copy(m_Address.begin(), m_Address.end(), arr); } /// A static value representing a zero value of MAC address, meaning address of value "00:00:00:00:00:00" static MacAddress Zero; private: - uint8_t m_Address[6] = { 0 }; + std::array m_Address{}; }; inline std::ostream& operator<<(std::ostream& oss, const pcpp::MacAddress& macAddress) diff --git a/Common++/header/ObjectPool.h b/Common++/header/ObjectPool.h new file mode 100644 index 0000000000..15d479cb90 --- /dev/null +++ b/Common++/header/ObjectPool.h @@ -0,0 +1,185 @@ +#pragma once + +#include +#include +#include +#include +#include + +namespace pcpp +{ + namespace internal + { + /// @brief A generic object pool implementation. + /// + /// This class provides a generic object pool that can be used to efficiently manage and reuse objects of any + /// type. Objects can be acquired from the pool using the `acquireObject` method, and released back to the pool + /// using the `releaseObject` method. If the pool is empty when acquiring an object, a new object will be + /// created. If the pool is full when releasing an object, the object will be deleted. + /// + /// @tparam T The type of objects managed by the pool. Must be default constructable. + template ::value, bool>::type = true> + class DynamicObjectPool + { + public: + constexpr static std::size_t DEFAULT_POOL_SIZE = 100; +#pragma push_macro("max") // Undefine max to avoid conflict with std::numeric_limits::max() +#undef max + constexpr static std::size_t INFINITE_POOL_SIZE = std::numeric_limits::max(); +#pragma pop_macro("max") + + /// A constructor for this class that creates a pool of objects + /// @param[in] maxPoolSize The maximum number of objects in the pool + /// @param[in] initialSize The number of objects to preallocate in the pool + explicit DynamicObjectPool(std::size_t maxPoolSize = DEFAULT_POOL_SIZE, std::size_t initialSize = 0) + : m_MaxPoolSize(maxPoolSize) + { + if (initialSize > maxPoolSize) + throw std::invalid_argument("Preallocated objects cannot exceed the maximum pool size"); + + if (initialSize > 0) + this->preallocate(initialSize); + } + + // These don't strictly need to be deleted, but don't need to be implemented for now either. + DynamicObjectPool(const DynamicObjectPool&) = delete; + DynamicObjectPool(DynamicObjectPool&&) = delete; + DynamicObjectPool& operator=(const DynamicObjectPool&) = delete; + DynamicObjectPool& operator=(DynamicObjectPool&&) = delete; + + /// A destructor for this class that deletes all objects in the pool + ~DynamicObjectPool() + { + clear(); + } + + /// @brief Acquires a unique pointer to an object from the pool. + /// + /// This method acquires a unique pointer to an object from the pool. + /// If the pool is empty, a new object will be created. + /// + /// @return A unique pointer to an object from the pool. + std::unique_ptr acquireObject() + { + return std::unique_ptr(acquireObjectRaw()); + } + + /// @brief Acquires a raw pointer to an object from the pool. + /// + /// This method acquires a raw pointer to an object from the pool. + /// If the pool is empty, a new object will be created. + /// + /// @return A raw pointer to an object from the pool. + T* acquireObjectRaw() + { + std::unique_lock lock(m_Mutex); + + if (m_Pool.empty()) + { + // We don't need the lock anymore, so release it. + lock.unlock(); + return new T(); + } + + T* obj = m_Pool.top(); + m_Pool.pop(); + return obj; + } + + /// @brief Releases a unique pointer to an object back to the pool. + /// + /// This method releases a unique pointer to an object back to the pool. + /// If the pool is full, the object will be deleted. + /// + /// @param[in] obj The unique pointer to the object to release. + void releaseObject(std::unique_ptr obj) + { + releaseObjectRaw(obj.release()); + } + + /// @brief Releases a raw pointer to an object back to the pool. + /// + /// This method releases a raw pointer to an object back to the pool. + /// If the pool is full, the object will be deleted. + /// + /// @param[in] obj The raw pointer to the object to release. + void releaseObjectRaw(T* obj) + { + std::unique_lock lock(m_Mutex); + + if (m_MaxPoolSize == INFINITE_POOL_SIZE || m_Pool.size() < m_MaxPoolSize) + { + m_Pool.push(obj); + } + else + { + // We don't need the lock anymore, so release it. + lock.unlock(); + delete obj; + } + } + + /// @brief Gets the current number of objects in the pool. + std::size_t size() const + { + std::lock_guard lock(m_Mutex); + return m_Pool.size(); + } + + /// @brief Gets the maximum number of objects in the pool. + std::size_t maxSize() const + { + std::lock_guard lock(m_Mutex); + return m_MaxPoolSize; + } + + /// @brief Sets the maximum number of objects in the pool. + void setMaxSize(std::size_t maxSize) + { + std::lock_guard lock(m_Mutex); + m_MaxPoolSize = maxSize; + + // If the new max size is less than the current size, we need to remove some objects from the pool. + while (m_Pool.size() > m_MaxPoolSize) + { + delete m_Pool.top(); + m_Pool.pop(); + } + } + + /// @brief Pre-allocates up to a minimum number of objects in the pool. + /// @param count The number of objects to pre-allocate. + void preallocate(std::size_t count) + { + std::unique_lock lock(m_Mutex); + + if (m_MaxPoolSize < count) + { + throw std::invalid_argument("Preallocated objects cannot exceed the maximum pool size"); + } + + // If the pool is already larger than the requested count, we don't need to do anything. + for (std::size_t i = m_Pool.size(); i < count; i++) + { + m_Pool.push(new T()); + } + } + + /// @brief Deallocates and releases all objects currently held by the pool. + void clear() + { + std::unique_lock lock(m_Mutex); + while (!m_Pool.empty()) + { + delete m_Pool.top(); + m_Pool.pop(); + } + } + + private: + std::size_t m_MaxPoolSize; ///< The maximum number of objects in the pool + mutable std::mutex m_Mutex; ///< Mutex for thread safety + std::stack m_Pool; ///< The pool of objects + }; + } // namespace internal +} // namespace pcpp diff --git a/Common++/src/IpAddress.cpp b/Common++/src/IpAddress.cpp index 90ef1b59fe..f42ddbcf87 100644 --- a/Common++/src/IpAddress.cpp +++ b/Common++/src/IpAddress.cpp @@ -68,6 +68,7 @@ namespace pcpp } catch (const std::invalid_argument& e) { + (void)e; // Suppress the unreferenced local variable warning when PCPP_LOG_ERROR is disabled PCPP_LOG_ERROR(e.what()); return false; } @@ -130,6 +131,7 @@ namespace pcpp } catch (const std::invalid_argument& e) { + (void)e; // Suppress the unreferenced local variable warning when PCPP_LOG_ERROR is disabled PCPP_LOG_ERROR(e.what()); return false; } diff --git a/Common++/src/IpUtils.cpp b/Common++/src/IpUtils.cpp index 44bd7e1f90..e6fa1150b6 100644 --- a/Common++/src/IpUtils.cpp +++ b/Common++/src/IpUtils.cpp @@ -41,6 +41,7 @@ namespace pcpp } catch (const std::invalid_argument& e) { + (void)e; // Suppress the unreferenced local variable warning when PCPP_LOG_DEBUG is disabled PCPP_LOG_DEBUG("Extraction failed: " << e.what() << " Returning nullptr."); return nullptr; } @@ -69,6 +70,7 @@ namespace pcpp } catch (const std::invalid_argument& e) { + (void)e; // Suppress the unreferenced local variable warning when PCPP_LOG_DEBUG is disabled PCPP_LOG_DEBUG("Extraction failed: " << e.what() << " Returning nullptr."); return nullptr; } diff --git a/Common++/src/Logger.cpp b/Common++/src/Logger.cpp index e67388ea5a..39056120cc 100644 --- a/Common++/src/Logger.cpp +++ b/Common++/src/Logger.cpp @@ -1,9 +1,11 @@ -#include -#include -#include -#include #include "Logger.h" +#include +#include +#include +#include +#include + namespace pcpp { @@ -33,49 +35,76 @@ namespace pcpp Logger::Logger() : m_LogsEnabled(true), m_LogPrinter(&defaultLogPrinter) { m_LastError.reserve(200); - std::fill(m_LogModulesArray, m_LogModulesArray + NumOfLogModules, Info); + m_LogModulesArray.fill(LogLevel::Info); } std::string Logger::logLevelAsString(LogLevel logLevel) { switch (logLevel) { - case Logger::Error: + case LogLevel::Off: + return "OFF"; + case LogLevel::Error: return "ERROR"; - case Logger::Info: + case LogLevel::Info: return "INFO"; - default: + case LogLevel::Debug: return "DEBUG"; + default: + return "UNKNOWN"; } } - void Logger::defaultLogPrinter(LogLevel logLevel, const std::string& logMessage, const std::string& file, - const std::string& method, const int line) + std::unique_ptr Logger::createLogContext() { - std::ostringstream sstream; - sstream << file << ": " << method << ":" << line; - std::cerr << std::left << "[" << std::setw(5) << Logger::logLevelAsString(logLevel) << ": " << std::setw(45) - << sstream.str() << "] " << logMessage << '\n'; + return createLogContext(LogLevel::Info, {}); // call the other createLogContext method + } + std::unique_ptr Logger::createLogContext(LogLevel level, LogSource const& source) + { + if (m_UseContextPooling) + { + auto ctx = m_LogContextPool.acquireObject(); + ctx->init(level, source); + return ctx; + } + return std::unique_ptr(new internal::LogContext(level, source)); } - std::ostringstream* Logger::internalCreateLogStream() + void Logger::emit(std::unique_ptr message) { - return new std::ostringstream(); + emit(message->m_Source, message->m_Level, message->m_Stream.str()); + // Pushes the message back to the pool if pooling is enabled. Otherwise, the message is deleted. + if (m_UseContextPooling) + { + m_LogContextPool.releaseObject(std::move(message)); + } } - void Logger::internalPrintLogMessage(std::ostringstream* logStream, Logger::LogLevel logLevel, const char* file, - const char* method, int line) + void Logger::emit(LogSource const& source, LogLevel logLevel, std::string const& message) { - const std::string logMessage = logStream->str(); - delete logStream; - if (logLevel == Logger::Error) + // If the log level is an error, save the error to the last error message variable. + if (logLevel == LogLevel::Error) { - m_LastError = logMessage; + std::lock_guard lock(m_LastErrorMtx); + m_LastError = message; } if (m_LogsEnabled) { - m_LogPrinter(logLevel, logMessage, file, method, line); + m_LogPrinter(logLevel, message, source.file, source.function, source.line); } } + void Logger::defaultLogPrinter(LogLevel logLevel, const std::string& logMessage, const std::string& file, + const std::string& method, const int line) + { + // This mutex is used to prevent multiple threads from writing to the console at the same time. + static std::mutex logMutex; + + std::ostringstream sstream; + sstream << file << ": " << method << ":" << line; + + std::unique_lock lock(logMutex); + std::cerr << std::left << "[" << std::setw(5) << Logger::logLevelAsString(logLevel) << ": " << std::setw(45) + << sstream.str() << "] " << logMessage << std::endl; + } } // namespace pcpp diff --git a/Examples/KniPong/main.cpp b/Examples/KniPong/main.cpp index e77db3188a..beb5f89e97 100644 --- a/Examples/KniPong/main.cpp +++ b/Examples/KniPong/main.cpp @@ -5,6 +5,7 @@ #include #include #include +#include #include #include diff --git a/Examples/PfRingExample-FilterTraffic/main.cpp b/Examples/PfRingExample-FilterTraffic/main.cpp index f070fceb67..7f7583ac9c 100644 --- a/Examples/PfRingExample-FilterTraffic/main.cpp +++ b/Examples/PfRingExample-FilterTraffic/main.cpp @@ -40,6 +40,8 @@ #include #include #include +#include +#include #include // clang-format off diff --git a/Packet++/header/Layer.h b/Packet++/header/Layer.h index 6e04a8f189..a6c22be5f6 100644 --- a/Packet++/header/Layer.h +++ b/Packet++/header/Layer.h @@ -191,10 +191,9 @@ namespace pcpp virtual bool shortenLayer(int offsetInLayer, size_t numOfBytesToShorten); }; + inline std::ostream& operator<<(std::ostream& os, const pcpp::Layer& layer) + { + os << layer.toString(); + return os; + } } // namespace pcpp - -inline std::ostream& operator<<(std::ostream& os, const pcpp::Layer& layer) -{ - os << layer.toString(); - return os; -} diff --git a/Packet++/header/LdapLayer.h b/Packet++/header/LdapLayer.h index f4dfa13ff7..4485d17c28 100644 --- a/Packet++/header/LdapLayer.h +++ b/Packet++/header/LdapLayer.h @@ -1060,47 +1060,47 @@ namespace pcpp : LdapResponseLayer(std::move(asn1Record), data, dataLen, prevLayer, packet) {} }; -} // namespace pcpp - -inline std::ostream& operator<<(std::ostream& os, const pcpp::LdapControl& control) -{ - os << "{" << control.controlType << ", " << control.controlValue << "}"; - return os; -} -inline std::ostream& operator<<(std::ostream& os, const pcpp::LdapAttribute& attr) -{ - os << "{" << attr.type << ", {"; + inline std::ostream& operator<<(std::ostream& os, const pcpp::LdapControl& control) + { + os << "{" << control.controlType << ", " << control.controlValue << "}"; + return os; + } - std::string separator; - for (const auto& value : attr.values) + inline std::ostream& operator<<(std::ostream& os, const pcpp::LdapAttribute& attr) { - os << separator << value; - if (separator.empty()) + os << "{" << attr.type << ", {"; + + std::string separator; + for (const auto& value : attr.values) { - separator = ", "; + os << separator << value; + if (separator.empty()) + { + separator = ", "; + } } - } - os << "}}"; - return os; -} - -inline std::ostream& operator<<(std::ostream& os, - const pcpp::LdapBindRequestLayer::SaslAuthentication& saslAuthentication) -{ - os << "{" << saslAuthentication.mechanism << ", {"; + os << "}}"; + return os; + } - std::string separator; - for (const auto& value : saslAuthentication.credentials) + inline std::ostream& operator<<(std::ostream& os, + const pcpp::LdapBindRequestLayer::SaslAuthentication& saslAuthentication) { - os << separator << "0x" << std::hex << static_cast(value) << std::dec; - if (separator.empty()) + os << "{" << saslAuthentication.mechanism << ", {"; + + std::string separator; + for (const auto& value : saslAuthentication.credentials) { - separator = ", "; + os << separator << "0x" << std::hex << static_cast(value) << std::dec; + if (separator.empty()) + { + separator = ", "; + } } - } - os << "}}"; - return os; -} + os << "}}"; + return os; + } +} // namespace pcpp diff --git a/Packet++/header/Packet.h b/Packet++/header/Packet.h index f843f1b27b..b430ba6c8e 100644 --- a/Packet++/header/Packet.h +++ b/Packet++/header/Packet.h @@ -379,10 +379,9 @@ namespace pcpp return dynamic_cast(curLayer); } + inline std::ostream& operator<<(std::ostream& os, const pcpp::Packet& packet) + { + os << packet.toString(); + return os; + } } // namespace pcpp - -inline std::ostream& operator<<(std::ostream& os, const pcpp::Packet& packet) -{ - os << packet.toString(); - return os; -} diff --git a/Packet++/header/StpLayer.h b/Packet++/header/StpLayer.h index 78fcc4edce..19be58907c 100644 --- a/Packet++/header/StpLayer.h +++ b/Packet++/header/StpLayer.h @@ -2,6 +2,7 @@ #include "Layer.h" #include "MacAddress.h" +#include /// @file diff --git a/Packet++/src/CotpLayer.cpp b/Packet++/src/CotpLayer.cpp index 2a218f547a..1183df5f13 100644 --- a/Packet++/src/CotpLayer.cpp +++ b/Packet++/src/CotpLayer.cpp @@ -2,6 +2,8 @@ #include "S7CommLayer.h" #include +#include + namespace pcpp { diff --git a/Packet++/src/EthDot3Layer.cpp b/Packet++/src/EthDot3Layer.cpp index b7ad95f30c..35d6d48b14 100644 --- a/Packet++/src/EthDot3Layer.cpp +++ b/Packet++/src/EthDot3Layer.cpp @@ -3,6 +3,8 @@ #include "PayloadLayer.h" #include "LLCLayer.h" +#include + namespace pcpp { EthDot3Layer::EthDot3Layer(const MacAddress& sourceMac, const MacAddress& destMac, uint16_t length) : Layer() diff --git a/Packet++/src/LLCLayer.cpp b/Packet++/src/LLCLayer.cpp index fe5c3c2fe5..5cfe5d84da 100644 --- a/Packet++/src/LLCLayer.cpp +++ b/Packet++/src/LLCLayer.cpp @@ -4,6 +4,8 @@ #include "PayloadLayer.h" #include "StpLayer.h" +#include + namespace pcpp { diff --git a/Packet++/src/S7CommLayer.cpp b/Packet++/src/S7CommLayer.cpp index 33358d23bb..0affaa8145 100644 --- a/Packet++/src/S7CommLayer.cpp +++ b/Packet++/src/S7CommLayer.cpp @@ -2,6 +2,7 @@ #include "S7CommLayer.h" #include +#include #include namespace pcpp diff --git a/Packet++/src/TpktLayer.cpp b/Packet++/src/TpktLayer.cpp index 2d003cd5e7..c3fe3a9ff7 100644 --- a/Packet++/src/TpktLayer.cpp +++ b/Packet++/src/TpktLayer.cpp @@ -2,6 +2,7 @@ #include "EndianPortable.h" #include "CotpLayer.h" #include "PayloadLayer.h" +#include #include namespace pcpp diff --git a/Packet++/src/VxlanLayer.cpp b/Packet++/src/VxlanLayer.cpp index ec1acc1657..6001b4197b 100644 --- a/Packet++/src/VxlanLayer.cpp +++ b/Packet++/src/VxlanLayer.cpp @@ -2,6 +2,8 @@ #include "EthLayer.h" #include "EndianPortable.h" +#include + namespace pcpp { diff --git a/Pcap++/src/NetworkUtils.cpp b/Pcap++/src/NetworkUtils.cpp index 61269bc26c..75a19e5de8 100644 --- a/Pcap++/src/NetworkUtils.cpp +++ b/Pcap++/src/NetworkUtils.cpp @@ -1,4 +1,4 @@ -#define LOG_MODULE NetworkUtils +#define LOG_MODULE PcapLogModuleNetworkUtils #include #include diff --git a/Pcap++/src/PcapLiveDevice.cpp b/Pcap++/src/PcapLiveDevice.cpp index cff0fdd476..213295cee7 100644 --- a/Pcap++/src/PcapLiveDevice.cpp +++ b/Pcap++/src/PcapLiveDevice.cpp @@ -1046,6 +1046,7 @@ namespace pcpp } catch (const std::exception& e) { + (void)e; // Suppress the unreferenced local variable warning when PCPP_LOG_ERROR is disabled PCPP_LOG_ERROR("Error retrieving default gateway address: " << e.what()); } break; diff --git a/Pcap++/src/PcapLiveDeviceList.cpp b/Pcap++/src/PcapLiveDeviceList.cpp index 9fb46b4ce4..a68e65a406 100644 --- a/Pcap++/src/PcapLiveDeviceList.cpp +++ b/Pcap++/src/PcapLiveDeviceList.cpp @@ -51,6 +51,7 @@ namespace pcpp } catch (const std::exception& e) { + (void)e; // Suppress the unreferenced local variable warning when PCPP_LOG_ERROR is disabled PCPP_LOG_ERROR(e.what()); } diff --git a/Pcap++/src/PcapRemoteDeviceList.cpp b/Pcap++/src/PcapRemoteDeviceList.cpp index 01791d6dde..ca8629c8f3 100644 --- a/Pcap++/src/PcapRemoteDeviceList.cpp +++ b/Pcap++/src/PcapRemoteDeviceList.cpp @@ -94,6 +94,7 @@ namespace pcpp } catch (const std::exception& e) { + (void)e; // Suppress the unreferenced local variable warning when PCPP_LOG_ERROR is disabled PCPP_LOG_ERROR(e.what()); return nullptr; } @@ -118,6 +119,7 @@ namespace pcpp { delete device; } + (void)e; // Suppress the unreferenced local variable warning when PCPP_LOG_ERROR is disabled PCPP_LOG_ERROR("Error creating remote devices: " << e.what()); return nullptr; } diff --git a/Tests/Packet++Test/main.cpp b/Tests/Packet++Test/main.cpp index 4163780110..ebf1838620 100644 --- a/Tests/Packet++Test/main.cpp +++ b/Tests/Packet++Test/main.cpp @@ -85,7 +85,8 @@ int main(int argc, char* argv[]) #endif // The logger singleton looks like a memory leak. Invoke it before starting the memory check - pcpp::Logger::getInstance(); + // Disables context pooling to avoid false positives in the memory leak check, as the contexts persist in the pool. + pcpp::Logger::getInstance().useContextPooling(false); // cppcheck-suppress knownConditionTrueFalse if (skipMemLeakCheck) diff --git a/Tests/Pcap++Test/CMakeLists.txt b/Tests/Pcap++Test/CMakeLists.txt index 07262ffb81..1727a4d15b 100644 --- a/Tests/Pcap++Test/CMakeLists.txt +++ b/Tests/Pcap++Test/CMakeLists.txt @@ -10,6 +10,7 @@ add_executable( Tests/KniTests.cpp Tests/LiveDeviceTests.cpp Tests/LoggerTests.cpp + Tests/ObjectPoolTests.cpp Tests/PacketParsingTests.cpp Tests/PfRingTests.cpp Tests/RawSocketTests.cpp diff --git a/Tests/Pcap++Test/TestDefinition.h b/Tests/Pcap++Test/TestDefinition.h index ccaaec4756..74188ff885 100644 --- a/Tests/Pcap++Test/TestDefinition.h +++ b/Tests/Pcap++Test/TestDefinition.h @@ -12,6 +12,9 @@ PTF_TEST_CASE(TestIPv4Network); PTF_TEST_CASE(TestIPv6Network); PTF_TEST_CASE(TestIPNetwork); +// Implemented in ObjectPoolTests.cpp +PTF_TEST_CASE(TestObjectPool); + // Implemented in LoggerTests.cpp PTF_TEST_CASE(TestLogger); PTF_TEST_CASE(TestLoggerMultiThread); diff --git a/Tests/Pcap++Test/Tests/LoggerTests.cpp b/Tests/Pcap++Test/Tests/LoggerTests.cpp index 2d53a491e8..5579c350c5 100644 --- a/Tests/Pcap++Test/Tests/LoggerTests.cpp +++ b/Tests/Pcap++Test/Tests/LoggerTests.cpp @@ -136,7 +136,7 @@ class LoggerCleaner ~LoggerCleaner() { pcpp::Logger::getInstance().enableLogs(); - pcpp::Logger::getInstance().setAllModulesToLogLevel(pcpp::Logger::Info); + pcpp::Logger::getInstance().setAllModulesToLogLevel(pcpp::LogLevel::Info); pcpp::Logger::getInstance().resetLogPrinter(); std::cout.clear(); LogPrinter::clean(); @@ -190,24 +190,39 @@ PTF_TEST_CASE(TestLoggerMultiThread) PTF_TEST_CASE(TestLogger) { + using pcpp::Logger; + using pcpp::LogLevel; + using pcpp::LogModule; + + auto& logger = Logger::getInstance(); + // cppcheck-suppress unusedVariable LoggerCleaner loggerCleaner; // verify all modules are on info log level - for (int module = 1; module < pcpp::NumOfLogModules; module++) + for (int moduleInt = 1; moduleInt < LogModule::NumOfLogModules; moduleInt++) { - PTF_ASSERT_EQUAL(pcpp::Logger::getInstance().getLogLevel((pcpp::LogModule)module), pcpp::Logger::Info, enum); - PTF_ASSERT_FALSE(pcpp::Logger::getInstance().isDebugEnabled((pcpp::LogModule)module)); + const LogModule moduleEnum = static_cast(moduleInt); + + PTF_ASSERT_EQUAL(logger.getLogLevel(moduleEnum), LogLevel::Info, enum); + PTF_ASSERT_FALSE(logger.isDebugEnabled(moduleEnum)); + + PTF_ASSERT_TRUE(logger.shouldLog(LogLevel::Error, moduleEnum)); + PTF_ASSERT_TRUE(logger.shouldLog(LogLevel::Info, moduleEnum)); + PTF_ASSERT_FALSE(logger.shouldLog(LogLevel::Debug, moduleEnum)); + PTF_ASSERT_FALSE(logger.shouldLog(LogLevel::Off, moduleEnum)); } // invoke debug and error logs - expect to see only the error log - pcpp::Logger::getInstance().setLogPrinter(&LogPrinter::logPrinter); + logger.setLogPrinter(&LogPrinter::logPrinter); + pcpp::invokeDebugLog(); PTF_ASSERT_EQUAL(LogPrinter::lastLogLevelSeen, 999); PTF_ASSERT_EQUAL(LogPrinter::lastLineSeen, 99999); PTF_ASSERT_NULL(LogPrinter::lastLogMessageSeen); PTF_ASSERT_NULL(LogPrinter::lastFilenameSeen); PTF_ASSERT_NULL(LogPrinter::lastMethodSeen); + pcpp::invokeErrorLog(); PTF_ASSERT_EQUAL(LogPrinter::lastLogLevelSeen, (int)pcpp::Logger::Error); PTF_ASSERT_EQUAL(*LogPrinter::lastLogMessageSeen, "error log"); @@ -216,9 +231,9 @@ PTF_TEST_CASE(TestLogger) PTF_ASSERT_EQUAL(LogPrinter::lastLineSeen, 21); // change one module log level - pcpp::Logger::getInstance().setLogLevel(pcpp::PacketLogModuleArpLayer, pcpp::Logger::Debug); - PTF_ASSERT_EQUAL(pcpp::Logger::getInstance().getLogLevel(pcpp::PacketLogModuleArpLayer), pcpp::Logger::Debug, enum); - PTF_ASSERT_TRUE(pcpp::Logger::getInstance().isDebugEnabled(pcpp::PacketLogModuleArpLayer)); + logger.setLogLevel(pcpp::PacketLogModuleArpLayer, pcpp::Logger::Debug); + PTF_ASSERT_EQUAL(logger.getLogLevel(pcpp::PacketLogModuleArpLayer), pcpp::LogLevel::Debug, enum); + PTF_ASSERT_TRUE(logger.isDebugEnabled(pcpp::PacketLogModuleArpLayer)); // invoke debug and error logs - expect to see both pcpp::invokeDebugLog(); @@ -236,14 +251,21 @@ PTF_TEST_CASE(TestLogger) PTF_ASSERT_EQUAL(LogPrinter::lastLineSeen, 21); // verify the last error message - PTF_ASSERT_EQUAL(pcpp::Logger::getInstance().getLastError(), "error log"); + PTF_ASSERT_EQUAL(logger.getLastError(), "error log"); // change all modules log level - pcpp::Logger::getInstance().setAllModulesToLogLevel(pcpp::Logger::Debug); - for (int module = 1; module < pcpp::NumOfLogModules; module++) + logger.setAllModulesToLogLevel(LogLevel::Debug); + for (int moduleInt = 1; moduleInt < LogModule::NumOfLogModules; moduleInt++) { - PTF_ASSERT_EQUAL(pcpp::Logger::getInstance().getLogLevel((pcpp::LogModule)module), pcpp::Logger::Debug, enum); - PTF_ASSERT_TRUE(pcpp::Logger::getInstance().isDebugEnabled((pcpp::LogModule)module)); + auto const moduleEnum = static_cast(moduleInt); + + PTF_ASSERT_EQUAL(logger.getLogLevel(static_cast(moduleEnum)), pcpp::LogLevel::Debug, enum); + PTF_ASSERT_TRUE(logger.isDebugEnabled(static_cast(moduleEnum))); + + PTF_ASSERT_TRUE(logger.shouldLog(LogLevel::Error, moduleEnum)); + PTF_ASSERT_TRUE(logger.shouldLog(LogLevel::Info, moduleEnum)); + PTF_ASSERT_TRUE(logger.shouldLog(LogLevel::Debug, moduleEnum)); + PTF_ASSERT_FALSE(logger.shouldLog(LogLevel::Off, moduleEnum)); } // invoke debug log - expect to see it @@ -255,9 +277,9 @@ PTF_TEST_CASE(TestLogger) PTF_ASSERT_EQUAL(LogPrinter::lastLineSeen, 16); // suppress logs - PTF_ASSERT_TRUE(pcpp::Logger::getInstance().logsEnabled()) - pcpp::Logger::getInstance().suppressLogs(); - PTF_ASSERT_FALSE(pcpp::Logger::getInstance().logsEnabled()) + PTF_ASSERT_TRUE(logger.logsEnabled()) + logger.suppressLogs(); + PTF_ASSERT_FALSE(logger.logsEnabled()) // reset LogPrinter LogPrinter::clean(); @@ -270,32 +292,32 @@ PTF_TEST_CASE(TestLogger) // invoke another error log - expect to see it as the last error message although logs are suppressed pcpp::invokeErrorLog("2"); - PTF_ASSERT_EQUAL(pcpp::Logger::getInstance().getLastError(), "error log2"); + PTF_ASSERT_EQUAL(logger.getLastError(), "error log2"); // re-enable logs - pcpp::Logger::getInstance().enableLogs(); - PTF_ASSERT_TRUE(pcpp::Logger::getInstance().logsEnabled()) + logger.enableLogs(); + PTF_ASSERT_TRUE(logger.logsEnabled()) // invoke error log - expect to see it pcpp::invokeErrorLog(); - PTF_ASSERT_EQUAL(LogPrinter::lastLogLevelSeen, (int)pcpp::Logger::Error); + PTF_ASSERT_EQUAL(LogPrinter::lastLogLevelSeen, static_cast(pcpp::LogLevel::Error)); PTF_ASSERT_EQUAL(*LogPrinter::lastLogMessageSeen, "error log"); PTF_ASSERT_EQUAL(getLowerCaseFileName(*LogPrinter::lastFilenameSeen), "loggertests.cpp"); PTF_ASSERT_EQUAL(getMethodWithoutNamespace(*LogPrinter::lastMethodSeen), "invokeErrorLog"); - PTF_ASSERT_EQUAL(pcpp::Logger::getInstance().getLastError(), "error log"); + PTF_ASSERT_EQUAL(logger.getLastError(), "error log"); PTF_ASSERT_EQUAL(LogPrinter::lastLineSeen, 21); // reset LogPrinter LogPrinter::clean(); // reset the log printer - pcpp::Logger::getInstance().resetLogPrinter(); + logger.resetLogPrinter(); // disable std::cout for a bit std::cout.setstate(std::ios_base::failbit); // set debug log for a module, don't expect to see it in the custom log printer - pcpp::Logger::getInstance().setLogLevel(pcpp::PacketLogModuleArpLayer, pcpp::Logger::Debug); + logger.setLogLevel(pcpp::PacketLogModuleArpLayer, pcpp::LogLevel::Debug); pcpp::invokeDebugLog(); pcpp::invokeErrorLog(); PTF_ASSERT_EQUAL(LogPrinter::lastLogLevelSeen, 999); diff --git a/Tests/Pcap++Test/Tests/ObjectPoolTests.cpp b/Tests/Pcap++Test/Tests/ObjectPoolTests.cpp new file mode 100644 index 0000000000..3f79e0ef30 --- /dev/null +++ b/Tests/Pcap++Test/Tests/ObjectPoolTests.cpp @@ -0,0 +1,79 @@ + +#include "../TestDefinition.h" + +#include "ObjectPool.h" + +PTF_TEST_CASE(TestObjectPool) +{ + using pcpp::internal::DynamicObjectPool; + + { + DynamicObjectPool pool; + PTF_ASSERT_EQUAL(pool.size(), 0); + PTF_ASSERT_EQUAL(pool.maxSize(), 100); + + pool.preallocate(2); + PTF_ASSERT_EQUAL(pool.size(), 2); + + pool.setMaxSize(1); + PTF_ASSERT_EQUAL(pool.size(), 1); + PTF_ASSERT_EQUAL(pool.maxSize(), 1); + + PTF_ASSERT_RAISES(pool.preallocate(2), std::invalid_argument, + "Preallocated objects cannot exceed the maximum pool size"); + + pool.clear(); + PTF_ASSERT_EQUAL(pool.size(), 0); + PTF_ASSERT_EQUAL(pool.maxSize(), 1); + } + + { + DynamicObjectPool pool(10, 2); + PTF_ASSERT_EQUAL(pool.size(), 2); + PTF_ASSERT_EQUAL(pool.maxSize(), 10); + + PTF_ASSERT_RAISES(DynamicObjectPool(0, 2), std::invalid_argument, + "Preallocated objects cannot exceed the maximum pool size"); + } + + { + DynamicObjectPool pool; + PTF_ASSERT_EQUAL(pool.size(), 0); + + // Acquire an object, since the pool is empty, a new object will be created. + auto obj1 = pool.acquireObject(); + PTF_ASSERT_NOT_NULL(obj1); + + // Acquire a second object, since the pool is still empty, a new object will be created. + auto obj2 = pool.acquireObject(); + + // For the purposes of this test a value will be assigned to track the object. + *obj1 = 55; + *obj2 = 66; + + // Release the objects back to the pool. + pool.releaseObject(std::move(obj1)); + pool.releaseObject(std::move(obj2)); + + PTF_ASSERT_EQUAL(pool.size(), 2); + + // Acquire an object again, this time the object should be reused. + // Since the pool is a LIFO stack the object that was released last should be acquired first. + obj1 = pool.acquireObject(); + + // The value should be the same as the one assigned before releasing the object. + PTF_ASSERT_EQUAL(*obj1, 66); + + // Acquire the second object, this time the object that was released first should be acquired. + obj2 = pool.acquireObject(); + PTF_ASSERT_EQUAL(*obj2, 55); + + // Set the max size of the pool to zero to test the deletion of objects. + pool.setMaxSize(0); + + // Release the objects back to the pool, this time the objects should be deleted. + pool.releaseObject(std::move(obj1)); + pool.releaseObject(std::move(obj2)); + PTF_ASSERT_EQUAL(pool.size(), 0); + } +} diff --git a/Tests/Pcap++Test/main.cpp b/Tests/Pcap++Test/main.cpp index 784a9e0d6f..204e34fee7 100644 --- a/Tests/Pcap++Test/main.cpp +++ b/Tests/Pcap++Test/main.cpp @@ -143,8 +143,9 @@ int main(int argc, char* argv[]) << " https://github.com/cpputest/cpputest/issues/786#issuecomment-148921958" << std::endl; #endif - // The logger singleton looks like a memory leak. Invoke it before starting the memory check - pcpp::Logger::getInstance(); + // The logger singleton looks like a memory leak. Invoke it before starting the memory check. + // Disables context pooling to avoid false positives in the memory leak check, as the contexts persist in the pool. + pcpp::Logger::getInstance().useContextPooling(false); // cppcheck-suppress knownConditionTrueFalse if (skipMemLeakCheck) @@ -209,6 +210,8 @@ int main(int argc, char* argv[]) PTF_RUN_TEST(TestIPv6Network, "no_network;ip"); PTF_RUN_TEST(TestIPNetwork, "no_network;ip"); + PTF_RUN_TEST(TestObjectPool, "no_network"); + PTF_RUN_TEST(TestLogger, "no_network;logger"); PTF_RUN_TEST(TestLoggerMultiThread, "no_network;logger;skip_mem_leak_check"); diff --git a/Tests/PcppTestFramework/PcppTestFramework.h b/Tests/PcppTestFramework/PcppTestFramework.h index 9a1e60cad3..143e24332e 100644 --- a/Tests/PcppTestFramework/PcppTestFramework.h +++ b/Tests/PcppTestFramework/PcppTestFramework.h @@ -1,6 +1,7 @@ #pragma once #include +#include #include #include "memplumber.h" #include "PcppTestFrameworkCommon.h"