]> git.ipfire.org Git - thirdparty/snort3.git/commitdiff
Merge pull request #2974 in SNORT/snort3 from ~YVELYKOZ/snort3:ips_byte_options_updat...
authorMike Stepanek (mstepane) <mstepane@cisco.com>
Wed, 21 Jul 2021 15:51:19 +0000 (15:51 +0000)
committerMike Stepanek (mstepane) <mstepane@cisco.com>
Wed, 21 Jul 2021 15:51:19 +0000 (15:51 +0000)
Squashed commit of the following:

commit acf28ee21eba548ff0eae5119e57eb70683c52d7
Author: VytalyGorbatov <vytalygorbatovwork@gmail.com>
Date:   Mon May 17 09:14:21 2021 +0300

    ips_options: refactor byte_extract, byte_test, byte_math, byte_jump and related tests

    Move common logic to extract.cc.

commit 65523a0c2b8c6c8ac7dc6629ed7ab697776642d5
Author: Crowy-o <egor1velikogon@gmail.com>
Date:   Thu May 13 14:22:34 2021 +0300

    ips_options: add catch tests for byte_test, byte_jump, byte_math, byte_extract

src/framework/ips_option.cc
src/ips_options/CMakeLists.txt
src/ips_options/extract.cc
src/ips_options/extract.h
src/ips_options/ips_byte_extract.cc
src/ips_options/ips_byte_jump.cc
src/ips_options/ips_byte_math.cc
src/ips_options/ips_byte_test.cc

index 8c8d9eb5d21f3ec36202237c4274e27e56393ba3..6d68c7dad44a2ecfbe41bb4a45c48a82b27384b9 100644 (file)
@@ -65,3 +65,82 @@ bool IpsOption::operator==(const IpsOption& ips) const
         !strcmp(get_buffer(), ips.get_buffer());
 }
 
+//-------------------------------------------------------------------------
+// UNIT TESTS
+//-------------------------------------------------------------------------
+#ifdef UNIT_TEST
+#include "catch/snort_catch.h"
+
+class StubIpsOption : public IpsOption
+{
+public:
+    StubIpsOption(const char* name, option_type_t option_type) :
+        IpsOption(name, option_type)
+    { }
+};
+
+TEST_CASE("IpsOption test", "[ips_option]")
+{
+    StubIpsOption main_ips("ips_test",
+        option_type_t::RULE_OPTION_TYPE_OTHER);
+
+    SECTION("IpsOperator == test")
+    {
+        StubIpsOption case_diff_name("not_hello_world",
+            option_type_t::RULE_OPTION_TYPE_BUFFER_USE);
+        REQUIRE((main_ips == case_diff_name) == false);
+
+        StubIpsOption case_diff_option("hello_world",
+            option_type_t::RULE_OPTION_TYPE_CONTENT);
+        REQUIRE((main_ips == case_diff_option) == false);
+
+        StubIpsOption case_option_na("hello_world",
+            option_type_t::RULE_OPTION_TYPE_OTHER);
+        REQUIRE((main_ips == case_option_na) == false);
+    }
+
+    SECTION("hash test")
+    {
+        StubIpsOption main_ips("ips_test",
+            option_type_t::RULE_OPTION_TYPE_OTHER);
+
+        SECTION("hash test with short string")
+        {
+            StubIpsOption main_ips_short("ips_test",
+                option_type_t::RULE_OPTION_TYPE_OTHER);
+            REQUIRE((main_ips.hash() == main_ips_short.hash()) == true);
+
+            StubIpsOption main_ips_short_diff("not_ips_test",
+                option_type_t::RULE_OPTION_TYPE_OTHER);
+            REQUIRE((main_ips.hash() == main_ips_short_diff.hash()) == false);
+        }
+
+        SECTION("hash test with long string")
+        {
+            std::string really_long_string =
+                "101010101010101010101010101010101010101010101010101010101010101" \
+                "101010101010101010101010101010101010101010101010101010101010101" \
+                "101010101010101010101010101010101010101010101010101010101010101" \
+                "101010101010101010101010101010101010101010101010101010101010101" \
+                "101010101010101010101010101010101010101010101010101010101010101" \
+                "101010101010101010101010101010101010101010101010101010101010101" \
+                "101010101010101010101010101010101010101010101010101010101010101" \
+                "101010101010101010101010101010101010101010101010101010101010101" \
+                "101010101010101010101010101010101010101010101010101010101010101" \
+                "101010101010101010101010101010101010101010101010101010101010101" \
+                "101010101010101010101010101010101010101010101010101010101010101" \
+                "101010101010101010101010101010101010101010101010101010101010101" \
+                "101010101010101010101010101010101010101010101010101010101010101";
+
+            StubIpsOption main_ips_long_first(really_long_string.c_str(),
+                option_type_t::RULE_OPTION_TYPE_OTHER);
+            StubIpsOption main_ips_long_second(really_long_string.c_str(),
+                option_type_t::RULE_OPTION_TYPE_OTHER);
+            REQUIRE(main_ips_long_first.hash() == main_ips_long_second.hash());
+
+            REQUIRE(main_ips_long_first.hash() != main_ips.hash());
+        }
+    }
+}
+
+#endif
index 1db3c18d69e81ecd1d4bbd3a9729aca3218eec76..bc482d8b0e0f1c6b394c3ed5f48482b6063b97f6 100644 (file)
@@ -101,10 +101,10 @@ else (STATIC_IPS_OPTIONS)
     add_dynamic_module(ips_ber_data ips_options ips_ber_data.cc)
     add_dynamic_module(ips_ber_skip ips_options ips_ber_skip.cc)
     add_dynamic_module(ips_bufferlen ips_options ips_bufferlen.cc)
-    add_dynamic_module(ips_byte_extract ips_options ips_byte_extract.cc)
-    add_dynamic_module(ips_byte_jump ips_options ips_byte_jump.cc)
-    add_dynamic_module(ips_byte_math ips_options ips_byte_math.cc)
-    add_dynamic_module(ips_byte_test ips_options ips_byte_test.cc)
+    add_dynamic_module(ips_byte_extract ips_options extract.cc ips_byte_extract.cc)
+    add_dynamic_module(ips_byte_jump ips_options extract.cc ips_byte_jump.cc)
+    add_dynamic_module(ips_byte_math ips_options extract.cc ips_byte_math.cc)
+    add_dynamic_module(ips_byte_test ips_options extract.cc ips_byte_test.cc)
     add_dynamic_module(ips_cvs ips_options ips_cvs.cc)
     add_dynamic_module(ips_enable ips_options ips_enable.cc)
     add_dynamic_module(ips_file_type ips_options ips_file_type.cc)
index c5c1ec4184650ed3f53911ad02b3c184e203c144..73195a43131187401cbc00dc7cfdd63510eda0e7 100644 (file)
@@ -27,6 +27,7 @@
 
 #include "extract.h"
 
+#include "framework/ips_option.h"
 #include "log/messages.h"
 #include "utils/snort_bounds.h"
 #include "utils/util_cstring.h"
@@ -34,6 +35,7 @@
 
 #ifdef UNIT_TEST
 #include "catch/snort_catch.h"
+#include "service_inspectors/dce_rpc/dce_common.h"
 #endif
 
 using namespace snort;
@@ -46,6 +48,7 @@ static THREAD_LOCAL uint8_t extracted_values_cnt = 0;
 
 namespace snort
 {
+
 /* Given a variable name, retrieve its index.*/
 int8_t GetVarByName(const char* name)
 {
@@ -96,7 +99,7 @@ void ClearIpsOptionsVars()
   used at this point */
 int GetVarValueByIndex(uint32_t* dst, uint8_t var_number)
 {
-    if (dst == nullptr || var_number >= NUM_IPS_OPTIONS_VARS)
+    if (dst == nullptr or var_number >= NUM_IPS_OPTIONS_VARS)
         return IPS_OPTIONS_NO_VAR;
 
     *dst = extracted_values[var_number];
@@ -116,7 +119,7 @@ int SetVarValueByIndex(uint32_t value, uint8_t var_number)
 
 void set_byte_order(uint8_t& order, uint8_t flag, const char* opt)
 {
-    if ( order )
+    if (order)
         ParseWarning(WARN_RULES, "%s specifies multiple byte orders, using last", opt);
 
     order = flag;
@@ -143,22 +146,15 @@ int byte_extract(int endianness, int bytes_to_grab, const uint8_t* ptr,
     const uint8_t* start, const uint8_t* end,
     uint32_t* value)
 {
-    if (endianness != ENDIAN_LITTLE && endianness != ENDIAN_BIG)
-    {
-        /* we only support 2 byte formats */
-        return -2;
-    }
+    if (endianness != ENDIAN_LITTLE and endianness != ENDIAN_BIG)
+        return -2; /* we only support 2 byte formats */
 
     /* make sure the data to grab stays in bounds */
     if (!inBounds(start,end,ptr + (bytes_to_grab - 1)))
-    {
         return -3;
-    }
 
     if (!inBounds(start,end,ptr))
-    {
         return -3;
-    }
 
     /*
      * We only support grabbing 1, 2, or 4 bytes of binary data.
@@ -232,33 +228,24 @@ int byte_extract(int endianness, int bytes_to_grab, const uint8_t* ptr,
  * @returns 0 on success, otherwise failure
  */
 int string_extract(int bytes_to_grab, int base, const uint8_t* ptr,
-    const uint8_t* start, const uint8_t* end,
-    uint32_t* value)
+    const uint8_t* start, const uint8_t* end, uint32_t* value)
 {
     char byte_array[TEXTLEN];
     char* parse_helper;
     int x; /* counter */
 
-    if (bytes_to_grab > (TEXTLEN - 1) || bytes_to_grab <= 0)
-    {
+    if (bytes_to_grab > (TEXTLEN - 1) or bytes_to_grab <= 0)
         return -1;
-    }
 
     /* make sure the data to grab stays in bounds */
     if (!inBounds(start,end,ptr + (bytes_to_grab - 1)))
-    {
         return -3;
-    }
 
     if (!inBounds(start,end,ptr))
-    {
         return -3;
-    }
 
     for (x=0; x<bytes_to_grab; x++)
-    {
         byte_array[x] = *(ptr+x);
-    }
 
     byte_array[bytes_to_grab] = '\0';
 
@@ -267,13 +254,86 @@ int string_extract(int bytes_to_grab, int base, const uint8_t* ptr,
 
 #ifdef TEST_BYTE_EXTRACT
     printf("[----]\n");
-    for (x=0; (x<TEXTLEN) && (byte_array[x] != '\0'); x++)
+    for (x=0; (x<TEXTLEN) and (byte_array[x] != '\0'); x++)
         printf("%c", byte_array[x]);
     printf("\n");
 
     printf("converted value: 0x%08X (%u) %s\n", *value, *value, (char*)byte_array);
 #endif /* TEST_BYTE_EXTRACT */
-    return(parse_helper - byte_array);  /* Return the number of bytes actually extracted */
+    /* Return the number of bytes actually extracted */
+    return(parse_helper - byte_array);
+}
+
+void set_cursor_bounds(const ByteData& settings, const Cursor& c,
+    const uint8_t*& start, const uint8_t*& ptr, const uint8_t*& end)
+{
+    start = c.buffer();
+    end = start + c.size();
+
+    ptr = settings.relative_flag ? c.start() : c.buffer();
+    ptr += settings.offset;
+}
+
+int32_t data_extraction(const ByteData& settings, Packet* p,
+    uint32_t& result_var, const uint8_t* start,
+    const uint8_t* ptr, const uint8_t* end)
+{
+    if (p == nullptr)
+        return IpsOption::NO_MATCH;
+
+    // check bounds
+    if (ptr < start or ptr >= end)
+        return IpsOption::NO_MATCH;
+
+    uint8_t endian = settings.endianness;
+    if (settings.endianness == ENDIAN_FUNC)
+    {
+        if (!p->endianness or
+            !p->endianness->get_offset_endianness(ptr - p->data, endian))
+            return IpsOption::NO_MATCH;
+    }
+
+    // do the extraction
+    int32_t bytes_read = 0;
+    uint32_t value = 0;
+    if (!settings.string_convert_flag)
+    {
+        int ret = 0;
+        ret = byte_extract(endian, settings.bytes_to_extract, ptr, start, end, &value);
+        if (ret < 0)
+            return IpsOption::NO_MATCH;
+
+        bytes_read = settings.bytes_to_extract;
+    }
+    else
+    {
+        bytes_read = string_extract(settings.bytes_to_extract, settings.base,
+            ptr, start, end, &value);
+        if (bytes_read < 0)
+            return IpsOption::NO_MATCH;
+    }
+
+    if (settings.bitmask_val != 0)
+    {
+        uint32_t num_tailing_zeros_bitmask =
+            getNumberTailingZerosInBitmask(settings.bitmask_val);
+        value = value & settings.bitmask_val;
+        if (value and num_tailing_zeros_bitmask)
+            value = value >> num_tailing_zeros_bitmask;
+    }
+
+    result_var = value;
+    return bytes_read;
+}
+
+int32_t extract_data(const ByteData& settings, const Cursor& c, Packet* p,
+    uint32_t& result_var)
+{
+    const uint8_t* start = nullptr;
+    const uint8_t* ptr = nullptr;
+    const uint8_t* end = nullptr;
+    set_cursor_bounds(settings, c, start, ptr, end);
+    return data_extraction(settings, p, result_var, start, ptr, end);
 }
 
 uint32_t getNumberTailingZerosInBitmask(uint32_t bitmask)
@@ -287,11 +347,11 @@ uint32_t getNumberTailingZerosInBitmask(uint32_t bitmask)
 uint8_t numBytesInBitmask(uint32_t bitmask_value)
 {
     uint8_t num_bytes;
-    if ( bitmask_value <= 0xFF )
+    if (bitmask_value <= 0xFF)
         num_bytes = 1;
-    else if ( bitmask_value <= 0xFFFF )
+    else if (bitmask_value <= 0xFFFF)
         num_bytes = 2;
-    else if ( bitmask_value <= 0xFFFFFF )
+    else if (bitmask_value <= 0xFFFFFF)
         num_bytes = 3;
     else
         num_bytes = 4;
@@ -301,7 +361,21 @@ uint8_t numBytesInBitmask(uint32_t bitmask_value)
 
 } // namespace snort
 
+//-------------------------------------------------------------------------
+// UNIT TESTS
+//-------------------------------------------------------------------------
 #ifdef UNIT_TEST
+
+#define INITIALIZE(obj, bytes_to_extract_value, offset_value, relative_flag_value, \
+    string_convert_flag_value, base_value, endianness_value, bitmask_val_value) \
+    obj.base = base_value; \
+    obj.bitmask_val = bitmask_val_value; \
+    obj.bytes_to_extract = bytes_to_extract_value; \
+    obj.offset = offset_value; \
+    obj.endianness = endianness_value; \
+    obj.relative_flag = relative_flag_value; \
+    obj.string_convert_flag = string_convert_flag_value;
+
 TEST_CASE("ips options bitmask utils")
 {
     // numBytesInBitmask tests
@@ -326,8 +400,10 @@ TEST_CASE("ips options vars")
     // Fill up array
     int8_t ind1 = AddVarNameToList("OFFSET");
     REQUIRE((ind1 == 0));
+
     int8_t ind2 = AddVarNameToList("VALUE");
     REQUIRE((ind2 == 1));
+
     int8_t ind3 = AddVarNameToList("VAR3");
     REQUIRE((ind3 == IPS_OPTIONS_NO_VAR));
 
@@ -347,5 +423,273 @@ TEST_CASE("ips options vars")
     REQUIRE((GetVarValueByIndex(&dst, NUM_IPS_OPTIONS_VARS) == IPS_OPTIONS_NO_VAR));
     REQUIRE((SetVarValueByIndex(0, NUM_IPS_OPTIONS_VARS) == IPS_OPTIONS_NO_VAR));
 }
-#endif
 
+TEST_CASE("set_cursor_bounds", "[byte_extraction_tests]")
+{
+    Packet p;
+    p.data = (const uint8_t*)"Lorem 010 12345 0x75";
+    p.dsize = 21;
+    Cursor c(&p);
+    const uint8_t* start = nullptr;
+    const uint8_t* ptr = nullptr;
+    const uint8_t* end = nullptr;
+
+    SECTION("4 bytes read, no offset")
+    {
+        ByteData settings;
+        INITIALIZE(settings, 4, 0, 0, 0, 0, ENDIAN_BIG, 0);
+        set_cursor_bounds(settings, c, start, ptr, end);
+        CHECK(start == p.data);
+        CHECK(ptr == p.data);
+        CHECK(end == p.data + 21);
+    }
+    SECTION("4 byte read, offset = 4")
+    {
+        ByteData settings;
+        INITIALIZE(settings, 4, 4, 0, 0, 0, ENDIAN_BIG, 0);
+        set_cursor_bounds(settings, c, start, ptr, end);
+        CHECK(start == p.data);
+        CHECK(ptr == p.data + 4);
+        CHECK(end == p.data + 21);
+    }
+    SECTION("4 bytes read, cursor move without relative flag")
+    {
+        c.set_pos(3);
+        ByteData settings;
+        INITIALIZE(settings, 4, 0, 0, 0, 0, ENDIAN_BIG, 0);
+        set_cursor_bounds(settings, c, start, ptr, end);
+        CHECK(start == p.data);
+        CHECK(ptr == p.data);
+        CHECK(end == p.data + 21);
+    }
+    SECTION("4 bytes read, cursor move with relative flag")
+    {
+        c.set_pos(3);
+        ByteData settings;
+        INITIALIZE(settings, 4, 0, true, 0, 0, ENDIAN_BIG, 0);
+        set_cursor_bounds(settings, c, start, ptr, end);
+        CHECK(start == p.data);
+        CHECK(ptr == p.data + 3);
+        CHECK(end == p.data + 21);
+    }
+}
+
+TEST_CASE("extract_data valid", "[byte_extraction_tests]")
+{
+    Packet p;
+    p.data = (const uint8_t*)"Lorem 010 12345 0x75";
+    p.dsize = 21;
+    Cursor c(&p);
+    uint32_t res = 0;
+
+    SECTION("1 byte read, all - off")
+    {
+        ByteData settings;
+        INITIALIZE(settings, 1, 0, 0, 0, 0, ENDIAN_BIG, 0);
+        CHECK(extract_data(settings, c, &p, res) == 1);
+        CHECK(res == 76);
+    }
+    SECTION("2 bytes read, all - off")
+    {
+        ByteData settings;
+        INITIALIZE(settings, 2, 0, 0, 0, 0, ENDIAN_BIG, 0);
+        CHECK(extract_data(settings, c, &p, res) == 2);
+        CHECK(res == 19567);
+    }
+    SECTION("3 bytes read, all - off")
+    {
+        ByteData settings;
+        INITIALIZE(settings, 3, 0, 0, 0, 0, ENDIAN_BIG, 0);
+        CHECK(extract_data(settings, c, &p, res) == 3);
+        CHECK(res == 5009266);
+    }
+    SECTION("4 bytes read, all - off")
+    {
+        ByteData settings;
+        INITIALIZE(settings, 4, 0, 0, 0, 0, ENDIAN_BIG, 0);
+        CHECK(extract_data(settings, c, &p, res) == 4);
+        CHECK(res == 1282372197);
+    }
+    SECTION("1 byte read, offset 3")
+    {
+        ByteData settings;
+        INITIALIZE(settings, 1, 3, 0, 0, 0, ENDIAN_BIG, 0);
+        CHECK(extract_data(settings, c, &p, res) == 1);
+        CHECK(res == 101);
+    }
+    SECTION("1 byte read, offset 3, relative")
+    {
+        ByteData settings;
+        INITIALIZE(settings, 1, 3, 1, 0, 0, ENDIAN_BIG, 0);
+        c.set_pos(3);
+        CHECK(extract_data(settings, c, &p, res) == 1);
+        CHECK(res == 48);
+    }
+    SECTION("cursor 3, 1 byte read, offset -3, relative")
+    {
+        ByteData settings;
+        INITIALIZE(settings, 1, -3, 1, 0, 0, ENDIAN_BIG, 0);
+        c.set_pos(3);
+        CHECK(extract_data(settings, c, &p, res) == 1);
+        CHECK(res == 76);
+    }
+    SECTION("1 byte read, offset 6, string conversion, base 10")
+    {
+        ByteData settings;
+        INITIALIZE(settings, 1, 10, 0, 1, 10, ENDIAN_BIG, 0);
+        CHECK(extract_data(settings, c, &p, res) == 1);
+        CHECK(res == 1);
+    }
+    SECTION("2 bytes read, offset 6, string conversion, base 8 without prefix")
+    {
+        ByteData settings;
+        INITIALIZE(settings, 2, 10, 0, 1, 8, ENDIAN_BIG, 0);
+        CHECK(extract_data(settings, c, &p, res) == 2);
+        CHECK(res == 10);
+    }
+    SECTION("2 bytes read, offset 6, string conversion, base 10")
+    {
+        ByteData settings;
+        INITIALIZE(settings, 2, 10, 0, 1, 10, ENDIAN_BIG, 0);
+        CHECK(extract_data(settings, c, &p, res) == 2);
+        CHECK(res == 12);
+    }
+    SECTION("2 bytes read, offset 6, string conversion, base 16 without prefix")
+    {
+        ByteData settings;
+        INITIALIZE(settings, 2, 10, 0, 1, 16, ENDIAN_BIG, 0);
+        CHECK(extract_data(settings, c, &p, res) == 2);
+        CHECK(res == 18);
+    }
+    SECTION("3 bytes read, offset 6, string conversion, base 8 with prefix")
+    {
+        ByteData settings;
+        INITIALIZE(settings, 3, 6, 0, 1, 8, ENDIAN_BIG, 0);
+        CHECK(extract_data(settings, c, &p, res) == 3);
+        CHECK(res == 8);
+    }
+    SECTION("4 bytes read, offset 6, string conversion, base 16 with prefix")
+    {
+        ByteData settings;
+        INITIALIZE(settings, 4, 16, 0, 1, 16, ENDIAN_BIG, 0);
+        CHECK(extract_data(settings, c, &p, res) == 4);
+        CHECK(res == 117);
+    }
+    SECTION("2 byte read, bitmask 1100110011100011")
+    {
+        ByteData settings;
+        INITIALIZE(settings, 2, 0, 0, 0, 0, ENDIAN_BIG, 52451);
+        CHECK(extract_data(settings, c, &p, res) == 2);
+        CHECK(res == 19555);
+    }
+    SECTION("2 byte read, bitmask 1100110011100000")
+    {
+        ByteData settings;
+        INITIALIZE(settings, 2, 0, 0, 0, 0, ENDIAN_BIG, 52448);
+        CHECK(extract_data(settings, c, &p, res) == 2);
+        CHECK(res == 611);
+    }
+    SECTION("4 bytes read, ENDIAN_LITTLE")
+    {
+        ByteData settings;
+        INITIALIZE(settings, 4, 0, 0, 0, 0, ENDIAN_LITTLE, 0);
+        CHECK(extract_data(settings, c, &p, res) == 4);
+        CHECK(res == 1701998412);
+    }
+    SECTION("4 bytes read, ENDIAN_FUNC, packet.endianness " \
+        "= DCERPC_BO_FLAG__LITTLE_ENDIAN")
+    {
+        DceEndianness* auto_endian = new DceEndianness();
+        auto_endian->hdr_byte_order = DCERPC_BO_FLAG__LITTLE_ENDIAN;
+        auto_endian->data_byte_order = DCERPC_BO_FLAG__LITTLE_ENDIAN;
+        p.endianness = auto_endian;
+        ByteData settings;
+        INITIALIZE(settings, 4, 0, 0, 0, 0, ENDIAN_FUNC, 0);
+        CHECK(extract_data(settings, c, &p, res) == 4);
+        CHECK(res == 1701998412);
+    }
+}
+
+TEST_CASE("extract_data invalid", "[byte_extraction_tests]")
+{
+    Packet p;
+    p.data = (const uint8_t*)"Lorem 9876";
+    p.dsize = 11;
+    Cursor c(&p);
+    uint32_t res = 0;
+
+    SECTION("packet = nullptr")
+    {
+        ByteData settings;
+        INITIALIZE(settings, 1, 0, 0, 0, 0, ENDIAN_BIG, 0);
+        CHECK(extract_data(settings, c, nullptr, res) == IpsOption::NO_MATCH);
+    }
+    SECTION("read more than 4 bytes")
+    {
+        ByteData settings;
+        INITIALIZE(settings, 6, 0, 0, 0, 0, ENDIAN_BIG, 0);
+        CHECK(extract_data(settings, c, &p, res) == IpsOption::NO_MATCH);
+    }
+    SECTION("check bounds of packet, offset > packet size")
+    {
+        ByteData settings;
+        INITIALIZE(settings, 1, 20, 0, 0, 0, ENDIAN_BIG, 0);
+        CHECK(extract_data(settings, c, &p, res) == IpsOption::NO_MATCH);
+    }
+    SECTION("negative offset, without relative flag")
+    {
+        ByteData settings;
+        INITIALIZE(settings, 1, -20, 0, 0, 0, ENDIAN_BIG, 0);
+        CHECK(extract_data(settings, c, &p, res) == IpsOption::NO_MATCH);
+    }
+    SECTION("negative offset, out of bounds")
+    {
+        ByteData settings;
+        INITIALIZE(settings, 1, -20, 1, 0, 0, ENDIAN_BIG, 0);
+        CHECK(extract_data(settings, c, &p, res) == IpsOption::NO_MATCH);
+    }
+    SECTION("check bounds of packet, offset > packet size, empty packet")
+    {
+        p.data = (const uint8_t*)"";
+        p.dsize = 0;
+        Cursor c2(&p);
+        ByteData settings;
+        INITIALIZE(settings, 1, 20, 0, 0, 0, ENDIAN_BIG, 0);
+        CHECK(extract_data(settings, c2, &p, res) == IpsOption::NO_MATCH);
+    }
+    SECTION("check bounds of packet, read 2 bytes, empty packet")
+    {
+        p.data = (const uint8_t*)"";
+        p.dsize = 0;
+        Cursor c2(&p);
+        ByteData settings;
+        INITIALIZE(settings, 2, 0, 0, 0, 0, ENDIAN_BIG, 0);
+        CHECK(extract_data(settings, c2, &p, res) == IpsOption::NO_MATCH);
+    }
+    SECTION("ENDIAN_FUNC, without definition of endianness in packet")
+    {
+        ByteData settings;
+        INITIALIZE(settings, 3, 0, 0, 0, 0, ENDIAN_FUNC, 0);
+        CHECK(extract_data(settings, c, &p, res) == IpsOption::NO_MATCH);
+    }
+    SECTION("conversion from string, decimal number, base = 8")
+    {
+        ByteData settings;
+        INITIALIZE(settings, 3, 6, 0, 1, 8, ENDIAN_BIG, 0);
+        CHECK(extract_data(settings, c, &p, res) == IpsOption::NO_MATCH);
+    }
+    SECTION("conversion from string but the input is symbol")
+    {
+        ByteData settings;
+        INITIALIZE(settings, 1, 0, 0, 1, 10, ENDIAN_BIG, 0);
+        CHECK(extract_data(settings, c, &p, res) == IpsOption::NO_MATCH);
+    }
+    SECTION("cursor behind the packet size")
+    {
+        c.set_pos(15);
+        ByteData settings;
+        INITIALIZE(settings, 1, 0, 0, 1, 10, ENDIAN_BIG, 0);
+        CHECK(extract_data(settings, c, &p, res) == IpsOption::NO_MATCH);
+    }
+}
+#endif
index 19ed8562743c69a01d244d5961aad3568960a7c1..918399ffb59465643ac690b282e382bf78a41999 100644 (file)
 #ifndef EXTRACT_H
 #define EXTRACT_H
 
-#include "main/thread.h"
+#include "framework/cursor.h"
+#include "framework/endianness.h"
 #include "main/snort_types.h"
+#include "main/thread.h"
+#include "protocols/packet.h"
 
 #define ENDIAN_BIG    0x1
 #define ENDIAN_LITTLE 0x2
 
 namespace snort
 {
+
+struct ByteData
+{
+    uint32_t base;
+    uint32_t bitmask_val;
+    uint32_t bytes_to_extract;
+    int32_t offset;
+    uint8_t endianness;
+    bool relative_flag;
+    bool string_convert_flag;
+};
+
 SO_PUBLIC int string_extract(
     int bytes_to_grab, int base, const uint8_t* ptr,
     const uint8_t* start, const uint8_t* end, uint32_t* value);
@@ -44,6 +59,16 @@ SO_PUBLIC int byte_extract(
     int endianness, int bytes_to_grab, const uint8_t* ptr,
     const uint8_t* start, const uint8_t* end, uint32_t* value);
 
+void set_cursor_bounds(const ByteData& settings, const Cursor& c,
+    const uint8_t*& start, const uint8_t*& ptr, const uint8_t*& end);
+
+int32_t data_extraction(const ByteData& settings, Packet* p,
+    uint32_t& result_var, const uint8_t* start,
+    const uint8_t* ptr, const uint8_t* end);
+
+int32_t extract_data(const ByteData& settings, const Cursor& c, Packet* p,
+    uint32_t& result_var);
+
 SO_PUBLIC void set_byte_order(uint8_t& order, uint8_t flag, const char* opt);
 
 SO_PUBLIC uint32_t getNumberTailingZerosInBitmask(uint32_t);
@@ -59,4 +84,3 @@ SO_PUBLIC int GetVarValueByIndex(uint32_t* dst, uint8_t var_number);
 SO_PUBLIC int SetVarValueByIndex(uint32_t value, uint8_t var_number);
 }
 #endif
-
index 26c2668e895292c91f7799e40f3efb75b925f3c8..353d30e4d776a1823ca7ef5a7762b3adc9e9c55f 100644 (file)
 #include "framework/module.h"
 #include "hash/hash_key_operations.h"
 #include "log/messages.h"
-#include "protocols/packet.h"
 #include "profiler/profiler.h"
+#include "protocols/packet.h"
 #include "utils/util.h"
 
 #include "extract.h"
 
+#ifdef UNIT_TEST
+#include <catch/snort_catch.h>
+#include "service_inspectors/dce_rpc/dce_common.h"
+#endif
+
 using namespace snort;
 
 static THREAD_LOCAL ProfileStats byteExtractPerfStats;
@@ -44,17 +49,10 @@ static THREAD_LOCAL ProfileStats byteExtractPerfStats;
 #define s_help \
     "rule option to convert data to an integer variable"
 
-struct ByteExtractData
+struct ByteExtractData : public ByteData
 {
-    uint32_t bytes_to_grab;
-    int32_t offset;
-    uint8_t relative_flag;
-    uint8_t data_string_convert_flag;
-    uint8_t align;
-    uint8_t endianness;
-    uint32_t base;
     uint32_t multiplier;
-    uint32_t bitmask_val;
+    uint8_t align;
     int8_t var_number;
     char* name;
 };
@@ -62,8 +60,9 @@ struct ByteExtractData
 class ByteExtractOption : public IpsOption
 {
 public:
-    ByteExtractOption(const ByteExtractData& c) : IpsOption(s_name, RULE_OPTION_TYPE_BUFFER_USE)
-    { config = c; }
+    ByteExtractOption(const ByteExtractData& c) :
+        IpsOption(s_name, RULE_OPTION_TYPE_BUFFER_USE), config(c)
+    { }
 
     ~ByteExtractOption() override
     { snort_free(config.name); }
@@ -81,6 +80,7 @@ public:
 
 private:
     ByteExtractData config;
+    void apply_alignment(uint32_t& value);
 };
 
 //-------------------------------------------------------------------------
@@ -89,14 +89,14 @@ private:
 
 uint32_t ByteExtractOption::hash() const
 {
-    uint32_t a = config.bytes_to_grab;
+    uint32_t a = config.bytes_to_extract;
     uint32_t b = config.offset;
     uint32_t c = config.base;
 
     mix(a,b,c);
 
     a += (config.relative_flag << 24 |
-        config.data_string_convert_flag << 16 |
+        config.string_convert_flag << 16 |
         config.align << 8 |
         config.endianness);
     b += config.multiplier;
@@ -115,22 +115,22 @@ uint32_t ByteExtractOption::hash() const
 
 bool ByteExtractOption::operator==(const IpsOption& ips) const
 {
-    if ( !IpsOption::operator==(ips) )
+    if (!IpsOption::operator==(ips))
         return false;
 
     const ByteExtractOption& rhs = (const ByteExtractOption&)ips;
     const ByteExtractData* left = &config;
     const ByteExtractData* right = &rhs.config;
 
-    if ((left->bytes_to_grab == right->bytes_to_grab) &&
-        (left->offset == right->offset) &&
-        (left->relative_flag == right->relative_flag) &&
-        (left->data_string_convert_flag == right->data_string_convert_flag) &&
-        (left->align == right->align) &&
-        (left->endianness == right->endianness) &&
-        (left->base == right->base) &&
-        (left->multiplier == right->multiplier) &&
-        (left->var_number == right->var_number) &&
+    if ((left->bytes_to_extract == right->bytes_to_extract) and
+        (left->offset == right->offset) and
+        (left->relative_flag == right->relative_flag) and
+        (left->string_convert_flag == right->string_convert_flag) and
+        (left->align == right->align) and
+        (left->endianness == right->endianness) and
+        (left->base == right->base) and
+        (left->multiplier == right->multiplier) and
+        (left->var_number == right->var_number) and
         (left->bitmask_val == right->bitmask_val))
     {
         return true;
@@ -143,84 +143,36 @@ IpsOption::EvalStatus ByteExtractOption::eval(Cursor& c, Packet* p)
 {
     RuleProfile profile(byteExtractPerfStats);
 
-    ByteExtractData* data = &config;
+    uint32_t value = 0;
+    int bytes_read = extract_data(config, c, p, value);
 
-    if (data == nullptr || p == nullptr)
+    if (bytes_read == NO_MATCH)
         return NO_MATCH;
 
-    const uint8_t* start = c.buffer();
-    int dsize = c.size();
+    value *= config.multiplier;
 
-    const uint8_t* ptr = data->relative_flag ? c.start() : c.buffer();
-    ptr += data->offset;
+    apply_alignment(value);
 
-    const uint8_t* end = start + dsize;
+    SetVarValueByIndex(value, config.var_number);
 
-    // check bounds
-    if (ptr < start || ptr >= end)
-        return NO_MATCH;
+    c.add_pos(config.offset + bytes_read);
 
-    uint8_t endian = data->endianness;
-    if (data->endianness == ENDIAN_FUNC)
-    {
-        if (!p->endianness ||
-            !p->endianness->get_offset_endianness(ptr - p->data, endian))
-            return NO_MATCH;
-    }
-
-    // do the extraction
-    int ret = 0;
-    int bytes_read = 0;
-    uint32_t value;
-    if (data->data_string_convert_flag == 0)
-    {
-        ret = byte_extract(endian, data->bytes_to_grab, ptr, start, end, &value);
-        if (ret < 0)
-            return NO_MATCH;
-
-        bytes_read = data->bytes_to_grab;
-    }
-    else
-    {
-        ret = string_extract(data->bytes_to_grab, data->base, ptr, start, end, &value);
-        if (ret < 0)
-            return NO_MATCH;
-
-        bytes_read = ret;
-    }
-
-    if (data->bitmask_val != 0 )
-    {
-        uint32_t num_tailing_zeros_bitmask = getNumberTailingZerosInBitmask(data->bitmask_val);
-        value = value & data->bitmask_val;
-        if ( value && num_tailing_zeros_bitmask )
-        {
-            value = value >> num_tailing_zeros_bitmask;
-        }
-    }
-
-    /* multiply */
-    value *= data->multiplier;
+    return MATCH;
+}
 
-    /* align to next 32-bit or 16-bit boundary */
-    if ((data->align == 4) && (value % 4))
+void ByteExtractOption::apply_alignment(uint32_t& value)
+{
+    if ((config.align == 4) and (value % 4))
     {
         value = value + 4 - (value % 4);
     }
-    else if ((data->align == 2) && (value % 2))
+    else if ((config.align == 2) and (value % 2))
     {
         value = value + 2 - (value % 2);
     }
-
-    SetVarValueByIndex(value, data->var_number);
-
-    /* advance cursor */
-    c.add_pos(data->offset + bytes_read);
-
-    /* this rule option always "matches" if the read is performed correctly */
-    return MATCH;
 }
 
+
 //-------------------------------------------------------------------------
 // api
 //-------------------------------------------------------------------------
@@ -228,28 +180,28 @@ IpsOption::EvalStatus ByteExtractOption::eval(Cursor& c, Packet* p)
 /* Checks a ByteExtractData instance for errors. */
 static bool ByteExtractVerify(ByteExtractData* data)
 {
-    if (data->bytes_to_grab > MAX_BYTES_TO_GRAB && data->data_string_convert_flag == 0)
+    if (data->bytes_to_extract > MAX_BYTES_TO_GRAB and data->string_convert_flag == 0)
     {
         ParseError("byte_extract rule option cannot extract more than %d bytes.",
             MAX_BYTES_TO_GRAB);
         return false;
     }
 
-    if (data->bytes_to_grab > PARSELEN && data->data_string_convert_flag == 1)
+    if (data->bytes_to_extract > PARSELEN and data->string_convert_flag == 1)
     {
         ParseError("byte_extract rule cannot process more than %d bytes for "
             "string extraction.",  PARSELEN);
         return false;
     }
 
-    if (data->align != 0 && data->align != 2 && data->align != 4)
+    if (data->align != 0 and data->align != 2 and data->align != 4)
     {
         ParseError("byte_extract rule option has an invalid argument "
             "to 'align'. Valid arguments are '2' and '4'.");
         return false;
     }
 
-    if (data->offset < 0 && data->relative_flag == 0)
+    if (data->offset < 0 and data->relative_flag == 0)
     {
         ParseError("byte_extract rule option has a negative offset, but does "
             "not use the 'relative' option.");
@@ -269,7 +221,7 @@ static bool ByteExtractVerify(ByteExtractData* data)
         return false;
     }
 
-    if (data->base && !data->data_string_convert_flag)
+    if (data->base and !data->string_convert_flag)
     {
         ParseError("byte_extract rule option has a string conversion type "
             "(dec, hex, or oct) without the \"string\" "
@@ -277,7 +229,7 @@ static bool ByteExtractVerify(ByteExtractData* data)
         return false;
     }
 
-    if (numBytesInBitmask(data->bitmask_val) > data->bytes_to_grab)
+    if (numBytesInBitmask(data->bitmask_val) > data->bytes_to_extract)
     {
         ParseError("Number of bytes in \"bitmask\" value is greater than bytes to extract.");
         return false;
@@ -353,7 +305,7 @@ public:
     { return DETECT; }
 
 public:
-    ByteExtractData data = {};
+    ByteExtractData data{};
 };
 
 bool ExtractModule::begin(const char*, int, SnortConfig*)
@@ -365,55 +317,55 @@ bool ExtractModule::begin(const char*, int, SnortConfig*)
 
 bool ExtractModule::end(const char*, int, SnortConfig*)
 {
-    if ( !data.endianness )
+    if (!data.endianness)
         data.endianness = ENDIAN_BIG;
     return ByteExtractVerify(&data);
 }
 
 bool ExtractModule::set(const char*, Value& v, SnortConfig*)
 {
-    if ( v.is("~count") )
-        data.bytes_to_grab = v.get_uint8();
+    if (v.is("~count"))
+        data.bytes_to_extract = v.get_uint8();
 
-    else if ( v.is("~offset") )
+    else if (v.is("~offset"))
         data.offset = v.get_int32();
 
-    else if ( v.is("~name") )
+    else if (v.is("~name"))
         data.name = snort_strdup(v.get_string());
 
-    else if ( v.is("relative") )
+    else if (v.is("relative"))
         data.relative_flag = 1;
 
-    else if ( v.is("align") )
+    else if (v.is("align"))
         data.align = v.get_uint8();
 
-    else if ( v.is("multiplier") )
+    else if (v.is("multiplier"))
         data.multiplier = v.get_uint16();
 
-    else if ( v.is("big") )
+    else if (v.is("big"))
         set_byte_order(data.endianness, ENDIAN_BIG, "byte_extract");
 
-    else if ( v.is("little") )
+    else if (v.is("little"))
         set_byte_order(data.endianness, ENDIAN_LITTLE, "byte_extract");
 
-    else if ( v.is("dce") )
+    else if (v.is("dce"))
         set_byte_order(data.endianness, ENDIAN_FUNC, "byte_extract");
 
-    else if ( v.is("string") )
+    else if (v.is("string"))
     {
-        data.data_string_convert_flag = 1;
+        data.string_convert_flag = 1;
         data.base = 10;
     }
-    else if ( v.is("dec") )
+    else if (v.is("dec"))
         data.base = 10;
 
-    else if ( v.is("hex") )
+    else if (v.is("hex"))
         data.base = 16;
 
-    else if ( v.is("oct") )
+    else if (v.is("oct"))
         data.base = 8;
 
-    else if ( v.is("bitmask") )
+    else if (v.is("bitmask"))
         data.bitmask_val = v.get_uint32();
 
     else
@@ -491,3 +443,694 @@ const BaseApi* ips_byte_extract[] =
     &byte_extract_api.base,
     nullptr
 };
+
+//-------------------------------------------------------------------------
+// UNIT TESTS
+//-------------------------------------------------------------------------
+#ifdef UNIT_TEST
+
+//-------------------------------------------------------------------------
+// helpers
+//-------------------------------------------------------------------------
+#define INITIALIZE(obj, bytes_to_extract_value, offset_value, relative_flag_value, \
+    string_convert_flag_value, align_value, endianness_value, base_value,\
+    multiplier_value, bitmask_val_value, var_number_value, name_value) \
+    obj.base = base_value; \
+    obj.bitmask_val = bitmask_val_value; \
+    obj.bytes_to_extract = bytes_to_extract_value; \
+    obj.offset = offset_value; \
+    obj.endianness = endianness_value; \
+    obj.relative_flag = relative_flag_value; \
+    obj.string_convert_flag = string_convert_flag_value; \
+    obj.multiplier = multiplier_value; \
+    obj.align = align_value; \
+    obj.var_number = var_number_value; \
+    obj.name = name_value;
+
+class ByteExtractDataMatcher
+    : public Catch::Matchers::Impl::MatcherBase<ByteExtractData>
+{
+public:
+    ByteExtractDataMatcher(const ByteExtractData& value) : m_value(value) {}
+
+    bool match(ByteExtractData const& rhs) const override
+    {
+        return ((m_value.bytes_to_extract == rhs.bytes_to_extract) and
+            (m_value.offset == rhs.offset) and
+            (m_value.relative_flag == rhs.relative_flag) and
+            (m_value.string_convert_flag == rhs.string_convert_flag) and
+            (m_value.align == rhs.align) and
+            (m_value.endianness == rhs.endianness) and
+            (m_value.base == rhs.base) and
+            (m_value.multiplier == rhs.multiplier) and
+            (m_value.var_number == rhs.var_number) and
+            (m_value.bitmask_val == rhs.bitmask_val));
+    }
+
+    std::string describe() const override
+    {
+        std::ostringstream ss;
+        ss << "settings is equals to:\n";
+        ss << "bytes_to_extract : " << m_value.bytes_to_extract << ";\n";
+        ss << "offset : " << m_value.offset << ";\n";
+        ss << "relative_flag : " << m_value.relative_flag << ";\n";
+        ss << "string_convert_flag : " << m_value.string_convert_flag << ";\n";
+        ss << "align : " << m_value.align << ";\n";
+        ss << "endianness : " << m_value.endianness << ";\n";
+        ss << "base : " << m_value.base << ";\n";
+        ss << "multiplier : " << m_value.multiplier << ";\n";
+        ss << "bitmask_val : " << m_value.bitmask_val << ";\n";
+        ss << "var_number : " << m_value.var_number << ";\n";
+        return ss.str();
+    }
+
+private:
+    ByteExtractData m_value;
+};
+
+ByteExtractDataMatcher ByteExtractDataEquals(const ByteExtractData& value)
+{
+    return {value};
+}
+
+class SetBufferOptionHelper : public IpsOption
+{
+public:
+    SetBufferOptionHelper(const char* value)
+        : IpsOption(value, RULE_OPTION_TYPE_BUFFER_SET)
+    { }
+};
+
+//-------------------------------------------------------------------------
+// option tests
+//-------------------------------------------------------------------------
+
+TEST_CASE("ByteExtractOption::operator== valid", "[ips_byte_extract]")
+{
+    SetBufferOptionHelper set_buf("test");
+
+    char* lhs_name = new char[9];
+    strcpy(lhs_name, "test_lhs");
+    ByteExtractData data_lhs;
+    INITIALIZE(data_lhs, 0, 0, 0, 0, 0, 0, 8, 1, 0, 0, lhs_name);
+    ByteExtractOption lhs(data_lhs);
+
+    char* rhs_name = new char[9];
+    strcpy(rhs_name, "test_rhs");
+    ByteExtractData data_rhs;
+    INITIALIZE(data_rhs, 0, 0, 0, 0, 0, 0, 8, 1, 0, 0, rhs_name);
+    ByteExtractOption rhs(data_rhs);
+
+    CHECK(lhs == rhs);
+}
+
+TEST_CASE("ByteExtractOption::operator== invalid", "[ips_byte_extract]")
+{
+    SetBufferOptionHelper set_buf("test");
+
+    char* lhs_name = new char[5];
+    strcpy(lhs_name, "test");
+    ByteExtractData data_lhs;
+    INITIALIZE(data_lhs, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, nullptr);
+    ByteExtractOption lhs(data_lhs);
+
+    SECTION("not equal to IpsOption object")
+    {
+        CHECK(lhs != set_buf);
+        delete[] lhs_name;
+    }
+    SECTION("all fields is different")
+    {
+        ByteExtractData data_rhs;
+        INITIALIZE(data_rhs, 1, 4, true, false, 2, ENDIAN_FUNC, 0, 1, 0x1, 0, lhs_name);
+        ByteExtractOption rhs(data_rhs);
+        CHECK(lhs != rhs);
+    }
+    SECTION("bytes_to_extract is different")
+    {
+        ByteExtractData data_rhs;
+        INITIALIZE(data_rhs, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, lhs_name);
+        ByteExtractOption rhs(data_rhs);
+        CHECK(lhs != rhs);
+    }
+    SECTION("offset is different")
+    {
+        ByteExtractData data_rhs;
+        INITIALIZE(data_rhs, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, lhs_name);
+        ByteExtractOption rhs(data_rhs);
+        CHECK(lhs != rhs);
+    }
+    SECTION("relative_flag is different")
+    {
+        ByteExtractData data_rhs;
+        INITIALIZE(data_rhs, 0, 0, true, 0, 0, 0, 0, 1, 0, 0, lhs_name);
+        ByteExtractOption rhs(data_rhs);
+        CHECK(lhs != rhs);
+    }
+    SECTION("string_convert_flag is different")
+    {
+        ByteExtractData data_rhs;
+        INITIALIZE(data_rhs, 0, 0, 0, true, 0, 0, 0, 1, 0, 0, lhs_name);
+        ByteExtractOption rhs(data_rhs);
+        CHECK(lhs != rhs);
+    }
+    SECTION("align is different")
+    {
+        ByteExtractData data_rhs;
+        INITIALIZE(data_rhs, 0, 0, 0, 0, 2, 0, 0, 1, 0, 0, lhs_name);
+        ByteExtractOption rhs(data_rhs);
+        CHECK(lhs != rhs);
+    }
+    SECTION("endianness is different")
+    {
+        ByteExtractData data_rhs;
+        INITIALIZE(data_rhs, 0, 0, 0, 0, 0, ENDIAN_FUNC, 0, 1, 0, 0, lhs_name);
+        ByteExtractOption rhs(data_rhs);
+        CHECK(lhs != rhs);
+    }
+    SECTION("base is different")
+    {
+        ByteExtractData data_rhs;
+        INITIALIZE(data_rhs, 0, 0, 0, 0, 0, 0, 16, 1, 0, 0, lhs_name);
+        ByteExtractOption rhs(data_rhs);
+        CHECK(lhs != rhs);
+    }
+    SECTION("multiplier is different")
+    {
+        ByteExtractData data_rhs;
+        INITIALIZE(data_rhs, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, lhs_name);
+        ByteExtractOption rhs(data_rhs);
+        CHECK(lhs != rhs);
+    }
+    SECTION("bitmask is different")
+    {
+        ByteExtractData data_rhs;
+        INITIALIZE(data_rhs, 0, 0, 0, 0, 0, 0, 0, 1, 0xFFFF, 0, lhs_name);
+        ByteExtractOption rhs(data_rhs);
+        CHECK(lhs != rhs);
+    }
+    SECTION("var_number is different")
+    {
+        ByteExtractData data_rhs;
+        INITIALIZE(data_rhs, 0, 0, 0, 0, 0, 0, 0, 1, 0, 3, lhs_name);
+        ByteExtractOption rhs(data_rhs);
+        CHECK(lhs != rhs);
+    }
+    SECTION("name is different")
+    {
+        delete[] lhs_name;
+        char* rhs_name = new char[5];
+        strcpy(rhs_name, "unix");
+        ByteExtractData data_rhs;
+        INITIALIZE(data_rhs, 0, 0, 0, 0, 0, 0, 0, 1, 0, 3, rhs_name);
+        ByteExtractOption rhs(data_rhs);
+        CHECK(lhs != rhs);
+    }
+}
+
+TEST_CASE("ByteExtractOption::hash", "[ips_byte_extract]")
+{
+    SetBufferOptionHelper set_buf("test");
+
+    ByteExtractData data_lhs;
+    INITIALIZE(data_lhs, 0, 0, 0, 0, 0, 0, 8, 1, 0, 0, nullptr);
+    ByteExtractOption lhs(data_lhs);
+
+    SECTION("hash codes of any two equal objects are equal")
+    {
+        ByteExtractData data_rhs;
+        INITIALIZE(data_rhs, 0, 0, 0, 0, 0, 0, 8, 1, 0, 0, nullptr);
+        ByteExtractOption rhs(data_rhs);
+
+        CHECK(lhs.hash() == rhs.hash());
+    }
+}
+
+TEST_CASE("ByteExtractOption::eval valid", "[ips_byte_extract]")
+{
+    Packet p;
+    p.data = (const uint8_t*)"Lorem 12345";
+    p.dsize = 11;
+    Cursor c(&p);
+
+    for (unsigned i = 0; i < NUM_IPS_OPTIONS_VARS; ++i)
+    {
+        SetVarValueByIndex(0, i);
+    }
+    ClearIpsOptionsVars();
+
+    char* name = new char[5];
+    strcpy(name, "test");
+
+    SECTION("1 byte read, offset 6, string conversion, base 10, align 2")
+    {
+        ByteExtractData data;
+        INITIALIZE(data, 1, 6, 0, 1, 2, ENDIAN_BIG, 10, 1, 0, 0, name);
+        ByteExtractOption opt(data);
+        CHECK(opt.eval(c, &p) == IpsOption::MATCH);
+        uint32_t res = 0;
+        GetVarValueByIndex(&res, 0);
+        CHECK(res == 2);
+        CHECK(c.get_pos() == 7);
+    }
+    SECTION("3 byte read, offset 6, string conversion, base 10, align 4")
+    {
+        ByteExtractData data;
+        INITIALIZE(data, 3, 6, 0, 1, 4, ENDIAN_BIG, 10, 1, 0, 0, name);
+        ByteExtractOption opt(data);
+        CHECK(opt.eval(c, &p) == IpsOption::MATCH);
+        uint32_t res = 0;
+        GetVarValueByIndex(&res, 0);
+        CHECK(res == 124);
+        CHECK(c.get_pos() == 9);
+    }
+    SECTION("1 byte read, offset 1, no string conversion, align 2, multiply 3")
+    {
+        ByteExtractData data;
+        INITIALIZE(data, 1, 1, 0, 0, 2, ENDIAN_BIG, 0, 3, 0, 0, name);
+        ByteExtractOption opt(data);
+        CHECK(opt.eval(c, &p) == IpsOption::MATCH);
+        uint32_t res = 0;
+        GetVarValueByIndex(&res, 0);
+        CHECK(res == 334);
+        CHECK(c.get_pos() == 2);
+    }
+    SECTION("1 byte read, offset 3, no string conversion, align 4, multiply 5")
+    {
+        ByteExtractData data;
+        INITIALIZE(data, 1, 3, 0, 0, 4, ENDIAN_BIG, 0, 5, 0, 0, name);
+        ByteExtractOption opt(data);
+        CHECK(opt.eval(c, &p) == IpsOption::MATCH);
+        uint32_t res = 0;
+        GetVarValueByIndex(&res, 0);
+        CHECK(res == 508);
+        CHECK(c.get_pos() == 4);
+    }
+}
+
+TEST_CASE("ByteExtractOption::eval invalid", "[ips_byte_extract]")
+{
+    Packet p;
+    p.data = (const uint8_t*)"Lorem 9876";
+    p.dsize = 11;
+    Cursor c(&p);
+
+    for (unsigned i = 0; i < NUM_IPS_OPTIONS_VARS; ++i)
+    {
+        SetVarValueByIndex(0, i);
+    }
+    ClearIpsOptionsVars();
+
+    char* name = new char[5];
+    strcpy(name, "test");
+
+    SECTION("align value to 1")
+    {
+        ByteExtractData data;
+        INITIALIZE(data, 1, 0, 0, 0, 1, ENDIAN_BIG, 0, 1, 0, 0, name);
+        ByteExtractOption opt(data);
+        CHECK(opt.eval(c, &p) == IpsOption::MATCH);
+        uint32_t res = 0;
+        GetVarValueByIndex(&res, 0);
+        CHECK(res == 76);
+        CHECK(c.get_pos() == 1);
+    }
+    SECTION("align value to 6")
+    {
+        ByteExtractData data;
+        INITIALIZE(data, 1, 0, 0, 0, 6, ENDIAN_BIG, 0, 1, 0, 0, name);
+        ByteExtractOption opt(data);
+        CHECK(opt.eval(c, &p) == IpsOption::MATCH);
+        uint32_t res = 0;
+        GetVarValueByIndex(&res, 0);
+        CHECK(res == 76);
+        CHECK(c.get_pos() == 1);
+    }
+}
+
+//-------------------------------------------------------------------------
+// module tests
+//-------------------------------------------------------------------------
+
+TEST_CASE("ExtractModule lifecycle", "[ips_byte_extract]")
+{
+    ExtractModule obj;
+
+    SECTION("test of constructor")
+    {
+        CHECK(obj.data.multiplier == 1);
+    }
+    SECTION("test of \"begin\" method")
+    {
+        CHECK(obj.begin(nullptr, 0, nullptr));
+        ByteExtractData expected;
+        INITIALIZE(expected, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, nullptr);
+
+        CHECK_THAT(obj.data, ByteExtractDataEquals(expected));
+    }
+    SECTION("test of \"end\" method")
+    {
+        obj.begin(nullptr, 0, nullptr);
+
+        Value v_name("test");
+        Parameter p_name{
+            "~name", Parameter::PT_STRING, nullptr, nullptr,
+            "name of the variable that will be used in other rule options"};
+        v_name.set(&p_name);
+        obj.set(nullptr, v_name, nullptr);
+
+        Value v_bytes(4.0);
+        Parameter p_bytes{
+            "~count", Parameter::PT_INT, "1:10", nullptr,
+            "number of bytes to pick up from the buffer"};
+        v_bytes.set(&p_bytes);
+        obj.set(nullptr, v_bytes, nullptr);
+
+        CHECK(obj.end(nullptr, 0, nullptr));
+
+        char* name = new char[5];
+        strcpy(name, "test");
+        ByteExtractData expected;
+        INITIALIZE(expected, 4, 0, 0, 0, 0, ENDIAN_BIG, 0, 1, 0, 0, name);
+
+        CHECK_THAT(obj.data, ByteExtractDataEquals(expected));
+
+        delete[] name;
+        delete[] obj.data.name;
+    }
+}
+
+TEST_CASE("Test of byte_extract_ctor", "[ips_byte_extract]")
+{
+    ClearIpsOptionsVars();
+
+    std::string name = "test";
+    for (unsigned i = 0; i <= NUM_IPS_OPTIONS_VARS; ++i)
+    {
+        ExtractModule obj;
+        obj.begin(nullptr, 0, nullptr);
+        Value v((name + std::to_string(i)).c_str());
+        Parameter p{
+            "~name", Parameter::PT_STRING, nullptr, nullptr,
+            "name of the variable that will be used in other rule options"};
+        v.set(&p);
+        obj.set(nullptr, v, nullptr);
+
+        if (i < NUM_IPS_OPTIONS_VARS)
+        {
+            IpsOption* res = byte_extract_ctor(&obj, nullptr);
+            delete res;
+        }
+        else
+        {
+            IpsOption* res_null = byte_extract_ctor(&obj, nullptr);
+            CHECK(res_null == nullptr);
+            delete[] obj.data.name;
+        }
+    }
+}
+
+TEST_CASE("ExtractModule::set", "[ips_byte_extract]")
+{
+    ExtractModule obj;
+    obj.begin(nullptr, 0, nullptr);
+
+    SECTION("set bytes_to_extract")
+    {
+        Value v(4.0);
+        Parameter p{
+            "~count", Parameter::PT_INT, "1:10", nullptr,
+            "number of bytes to pick up from the buffer"};
+        v.set(&p);
+        ByteExtractData expected;
+        INITIALIZE(expected, 4, 0, 0, 0, 0, 0, 0, 1, 0, 0, nullptr);
+
+        CHECK(obj.set(nullptr, v, nullptr));
+        CHECK_THAT(obj.data, ByteExtractDataEquals(expected));
+    }
+    SECTION("set offset")
+    {
+        Value v(7.0);
+        Parameter p{
+            "~offset", Parameter::PT_INT, "-65535:65535", nullptr,
+            "number of bytes into the buffer to start processing"};
+        v.set(&p);
+        ByteExtractData expected;
+        INITIALIZE(expected, 0, 7, 0, 0, 0, 0, 0, 1, 0, 0, nullptr);
+
+        CHECK(obj.set(nullptr, v, nullptr));
+        CHECK_THAT(obj.data, ByteExtractDataEquals(expected));
+    }
+    SECTION("set name")
+    {
+        Value v("test_name");
+        Parameter p{
+            "~name", Parameter::PT_STRING, nullptr, nullptr,
+            "name of the variable that will be used in other rule options"};
+        v.set(&p);
+
+        CHECK(obj.set(nullptr, v, nullptr));
+        CHECK_THAT(obj.data.name, Catch::Matchers::Equals("test_name"));
+    }
+    SECTION("set relative")
+    {
+        Value v(true);
+        Parameter p{
+            "relative", Parameter::PT_IMPLIED, nullptr, nullptr,
+            "offset from cursor instead of start of buffer"};
+        v.set(&p);
+        obj.set(nullptr, v, nullptr);
+        ByteExtractData expected;
+        INITIALIZE(expected, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, nullptr);
+
+        CHECK(obj.set(nullptr, v, nullptr));
+        CHECK_THAT(obj.data, ByteExtractDataEquals(expected));
+    }
+    SECTION("set multiplier")
+    {
+        Value v(6.0);
+        Parameter p{
+            "multiplier", Parameter::PT_INT, "1:65535", "1",
+            "scale extracted value by given amount"};
+        v.set(&p);
+        ByteExtractData expected;
+        INITIALIZE(expected, 0, 0, 0, 0, 0, 0, 0, 6, 0, 0, nullptr);
+
+        CHECK(obj.set(nullptr, v, nullptr));
+        CHECK_THAT(obj.data, ByteExtractDataEquals(expected));
+    }
+    SECTION("set align")
+    {
+        Value v(2.0);
+        Parameter p{
+            "align", Parameter::PT_INT, "0:4", "0",
+            "round the number of converted bytes up to the next 2- "
+            "or 4-byte boundary"};
+        v.set(&p);
+        ByteExtractData expected;
+        INITIALIZE(expected, 0, 0, 0, 0, 2, 0, 0, 1, 0, 0, nullptr);
+
+        CHECK(obj.set(nullptr, v, nullptr));
+        CHECK_THAT(obj.data, ByteExtractDataEquals(expected));
+    }
+    SECTION("set endianness")
+    {
+        Value v_big((double)ENDIAN_BIG);
+        Parameter p_big{"big", Parameter::PT_IMPLIED, nullptr, nullptr, "big endian"};
+        v_big.set(&p_big);
+        obj.set(nullptr, v_big, nullptr);
+        ByteExtractData expected_big;
+        INITIALIZE(expected_big, 0, 0, 0, 0, 0, ENDIAN_BIG, 0, 1, 0, 0, nullptr);
+
+        CHECK(obj.set(nullptr, v_big, nullptr));
+        CHECK_THAT(obj.data, ByteExtractDataEquals(expected_big));
+
+        Value v_lit((double)ENDIAN_LITTLE);
+        Parameter p_lit{"little", Parameter::PT_IMPLIED, nullptr, nullptr, "little endian"};
+        v_lit.set(&p_lit);
+        ByteExtractData expected_lit;
+        INITIALIZE(expected_lit, 0, 0, 0, 0, 0, ENDIAN_LITTLE, 0, 1, 0, 0, nullptr);
+
+        CHECK(obj.set(nullptr, v_lit, nullptr));
+        CHECK_THAT(obj.data, ByteExtractDataEquals(expected_lit));
+
+        Value v_dce((double)ENDIAN_FUNC);
+        Parameter p_dce{"dce", Parameter::PT_IMPLIED, nullptr, nullptr,
+            "dcerpc2 determines endianness"};
+        v_dce.set(&p_dce);
+        ByteExtractData expected_dce;
+        INITIALIZE(expected_dce, 0, 0, 0, 0, 0, ENDIAN_FUNC, 0, 1, 0, 0, nullptr);
+
+        CHECK(obj.set(nullptr, v_dce, nullptr));
+        CHECK_THAT(obj.data, ByteExtractDataEquals(expected_dce));
+    }
+    SECTION("set string")
+    {
+        Value v(true);
+        Parameter p{
+            "string", Parameter::PT_IMPLIED, nullptr, nullptr,
+            "convert from string"};
+        v.set(&p);
+        ByteExtractData expected;
+        INITIALIZE(expected, 0, 0, 0, 1, 0, 0, 10, 1, 0, 0, nullptr);
+
+        CHECK(obj.set(nullptr, v, nullptr));
+        CHECK_THAT(obj.data, ByteExtractDataEquals(expected));
+    }
+    SECTION("set hex")
+    {
+        Value v(true);
+        Parameter p{
+            "hex", Parameter::PT_IMPLIED, nullptr, nullptr,
+            "convert from hex string"};
+        v.set(&p);
+        ByteExtractData expected;
+        INITIALIZE(expected, 0, 0, 0, 0, 0, 0, 16, 1, 0, 0, nullptr);
+
+        CHECK(obj.set(nullptr, v, nullptr));
+        CHECK_THAT(obj.data, ByteExtractDataEquals(expected));
+    }
+    SECTION("set oct")
+    {
+        Value v(true);
+        Parameter p{
+            "oct", Parameter::PT_IMPLIED, nullptr, nullptr,
+            "convert from octal string"};
+        v.set(&p);
+        ByteExtractData expected;
+        INITIALIZE(expected, 0, 0, 0, 0, 0, 0, 8, 1, 0, 0, nullptr);
+
+        CHECK(obj.set(nullptr, v, nullptr));
+        CHECK_THAT(obj.data, ByteExtractDataEquals(expected));
+    }
+    SECTION("set dec")
+    {
+        Value v(true);
+        Parameter p{
+            "dec", Parameter::PT_IMPLIED, nullptr, nullptr,
+            "convert from decimal string"};
+        v.set(&p);
+        ByteExtractData expected;
+        INITIALIZE(expected, 0, 0, 0, 0, 0, 0, 10, 1, 0, 0, nullptr);
+
+        CHECK(obj.set(nullptr, v, nullptr));
+        CHECK_THAT(obj.data, ByteExtractDataEquals(expected));
+    }
+    SECTION("set bitmask")
+    {
+        Value v(1023.0);
+        Parameter p{
+            "bitmask", Parameter::PT_INT, "0x1:0xFFFFFFFF", nullptr,
+            "applies as an AND to the extracted value before "
+            "storage in 'name'"};
+        v.set(&p);
+        ByteExtractData expected;
+        INITIALIZE(expected, 0, 0, 0, 0, 0, 0, 0, 1, 1023, 0, nullptr);
+
+        CHECK(obj.set(nullptr, v, nullptr));
+        CHECK_THAT(obj.data, ByteExtractDataEquals(expected));
+    }
+    SECTION("invalid set")
+    {
+        Value v(1023.0);
+        Parameter p{
+            "error", Parameter::PT_INT, "nan", nullptr,
+            "not an option"};
+        v.set(&p);
+        ByteExtractData expected;
+        INITIALIZE(expected, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, nullptr);
+
+        CHECK(!obj.set(nullptr, v, nullptr));
+        CHECK_THAT(obj.data, ByteExtractDataEquals(expected));
+    }
+
+    delete[] obj.data.name;
+}
+
+//-------------------------------------------------------------------------
+// api tests
+//-------------------------------------------------------------------------
+
+TEST_CASE("ByteExtractVerify_valid", "[ips_byte_extract]")
+{
+    ByteExtractData obj;
+    char name[] = "test";
+
+    SECTION("Minimum values, no string conversion")
+    {
+        INITIALIZE(obj, 1, -65535, 1, 0, 0, ENDIAN_FUNC, 0, 1, 0x1, 0, name);
+        CHECK(ByteExtractVerify(&obj));
+    }
+    SECTION("Maximum values, no string conversion")
+    {
+        INITIALIZE(obj, MAX_BYTES_TO_GRAB, 65535, 1, 0, 4, ENDIAN_FUNC,
+            0, 65535, 0xFFFFFFFF, 0, name);
+        CHECK(ByteExtractVerify(&obj));
+    }
+    SECTION("Minimum values, with string conversion")
+    {
+        INITIALIZE(obj, 1, -65535, 1, 1, 0, ENDIAN_FUNC, 8, 1, 0x1, 0, name);
+        CHECK(ByteExtractVerify(&obj));
+    }
+    SECTION("Maximum values, with string conversion")
+    {
+        INITIALIZE(obj, PARSELEN, 65535, 1, 1, 4, ENDIAN_FUNC, 16, 65535, 0xFFFFFFFF, 0, name);
+        CHECK(ByteExtractVerify(&obj));
+    }
+}
+
+TEST_CASE("ByteExtractVerify_invalid", "[ips_byte_extract]")
+{
+    char* name = new char[5];
+    strcpy(name, "test");
+    ByteExtractData obj;
+    INITIALIZE(obj, 2, 7, 0, 0, 2, ENDIAN_FUNC, 0, 1, 0, 0, name);
+
+    SECTION("bytes_to_extract checks")
+    {
+        obj.bytes_to_extract = MAX_BYTES_TO_GRAB + 1;
+        CHECK((!ByteExtractVerify(&obj)));
+
+        obj.string_convert_flag = true;
+        obj.bytes_to_extract = PARSELEN + 1;
+        CHECK((!ByteExtractVerify(&obj)));
+    }
+    SECTION("align checks")
+    {
+        obj.align = 1;
+        CHECK((!ByteExtractVerify(&obj)));
+
+        obj.align = 6;
+        CHECK((!ByteExtractVerify(&obj)));
+    }
+    SECTION("offset checks")
+    {
+        obj.offset = -5;
+        CHECK((!ByteExtractVerify(&obj)));
+    }
+    SECTION("name checks")
+    {
+        delete[] name;
+        obj.name = nullptr;
+        CHECK((!ByteExtractVerify(&obj)));
+
+        name = new char[6];
+        strcpy(name, "64bit");
+        obj.name = name;
+        CHECK((!ByteExtractVerify(&obj)));
+    }
+    SECTION("base checks")
+    {
+        obj.base = 16;
+        CHECK((!ByteExtractVerify(&obj)));
+    }
+    SECTION("bitmask checks")
+    {
+        obj.bytes_to_extract = 2;
+        obj.bitmask_val = 1048575;
+        CHECK((!ByteExtractVerify(&obj)));
+    }
+    delete[] name;
+}
+
+#endif
index 003ae7900634ce1939c2b544702ff6d4cecbaa60..8c70ce59cc521cabfc0a2de15c3f756f42b3db53 100644 (file)
@@ -28,7 +28,7 @@
  *
  * Arguments:
  *      Required:
- *      <bytes_to_grab>: number of bytes to pick up from the packet
+ *      <bytes_to_extract>: number of bytes to pick up from the packet
  *      <offset>: number of bytes into the payload to grab the bytes
  *      Optional:
  *      ["relative"]: offset relative to last pattern match
@@ -95,30 +95,23 @@ static THREAD_LOCAL ProfileStats byteJumpPerfStats;
 
 #define s_name "byte_jump"
 
-typedef struct _ByteJumpData
+struct ByteJumpData : public ByteData
 {
-    uint32_t bytes_to_grab;
-    int32_t offset;
-    uint8_t relative_flag;
-    uint8_t data_string_convert_flag;
-    uint8_t from_beginning_flag;
-    uint8_t align_flag;
-    uint8_t endianness;
-    uint32_t base;
     uint32_t multiplier;
     int32_t post_offset;
-    uint32_t bitmask_val;
     int8_t offset_var;
-    uint8_t from_end_flag;
     int8_t post_offset_var;
-} ByteJumpData;
+    bool align_flag;
+    bool from_beginning_flag;
+    bool from_end_flag;
+};
 
 class ByteJumpOption : public IpsOption
 {
 public:
-    ByteJumpOption(const ByteJumpData& c) : IpsOption(s_name, RULE_OPTION_TYPE_BUFFER_USE)
-    { config = c; }
-
+    ByteJumpOption(const ByteJumpData& c) : IpsOption(s_name,
+        RULE_OPTION_TYPE_BUFFER_USE), config(c)
+    { }
 
     uint32_t hash() const override;
     bool operator==(const IpsOption&) const override;
@@ -141,14 +134,14 @@ private:
 
 uint32_t ByteJumpOption::hash() const
 {
-    uint32_t a = config.bytes_to_grab;
+    uint32_t a = config.bytes_to_extract;
     uint32_t b = config.offset;
     uint32_t c = config.base;
 
     mix(a,b,c);
 
     a += (config.relative_flag << 24 |
-        config.data_string_convert_flag << 16 |
+        config.string_convert_flag << 16 |
         config.from_beginning_flag << 8 |
         config.align_flag);
     b += config.endianness;
@@ -157,7 +150,9 @@ uint32_t ByteJumpOption::hash() const
     mix(a,b,c);
 
     a += config.post_offset;
-    b += config.from_end_flag << 16 | (uint32_t) config.offset_var << 8 | config.post_offset_var;
+    b += config.from_end_flag << 16 |
+        (uint32_t) config.offset_var << 8 |
+        config.post_offset_var;
     c += config.bitmask_val;
 
     mix(a,b,c);
@@ -169,26 +164,26 @@ uint32_t ByteJumpOption::hash() const
 
 bool ByteJumpOption::operator==(const IpsOption& ips) const
 {
-    if ( !IpsOption::operator==(ips) )
+    if (!IpsOption::operator==(ips))
         return false;
 
     const ByteJumpOption& rhs = (const ByteJumpOption&)ips;
     const ByteJumpData* left = &config;
     const ByteJumpData* right = &rhs.config;
 
-    if (( left->bytes_to_grab == right->bytes_to_grab) &&
-        ( left->offset == right->offset) &&
-        ( left->offset_var == right->offset_var) &&
-        ( left->relative_flag == right->relative_flag) &&
-        ( left->data_string_convert_flag == right->data_string_convert_flag) &&
-        ( left->from_beginning_flag == right->from_beginning_flag) &&
-        ( left->align_flag == right->align_flag) &&
-        ( left->endianness == right->endianness) &&
-        ( left->base == right->base) &&
-        ( left->multiplier == right->multiplier) &&
-        ( left->post_offset == right->post_offset) &&
-        ( left->bitmask_val == right->bitmask_val) &&
-        ( left->from_end_flag == right->from_end_flag) &&
+    if (( left->bytes_to_extract == right->bytes_to_extract) and
+        ( left->offset == right->offset) and
+        ( left->offset_var == right->offset_var) and
+        ( left->relative_flag == right->relative_flag) and
+        ( left->string_convert_flag == right->string_convert_flag) and
+        ( left->from_beginning_flag == right->from_beginning_flag) and
+        ( left->align_flag == right->align_flag) and
+        ( left->endianness == right->endianness) and
+        ( left->base == right->base) and
+        ( left->multiplier == right->multiplier) and
+        ( left->post_offset == right->post_offset) and
+        ( left->bitmask_val == right->bitmask_val) and
+        ( left->from_end_flag == right->from_end_flag) and
         ( left->post_offset_var == right->post_offset_var))
     {
         return true;
@@ -207,7 +202,7 @@ IpsOption::EvalStatus ByteJumpOption::eval(Cursor& c, Packet* p)
     int32_t post_offset = 0;
 
     // Get values from byte_extract variables, if present.
-    if (bjd->offset_var >= 0 && bjd->offset_var < NUM_IPS_OPTIONS_VARS)
+    if (bjd->offset_var >= 0 and bjd->offset_var < NUM_IPS_OPTIONS_VARS)
     {
         uint32_t extract_offset;
         GetVarValueByIndex(&extract_offset, bjd->offset_var);
@@ -217,7 +212,8 @@ IpsOption::EvalStatus ByteJumpOption::eval(Cursor& c, Packet* p)
     {
         offset = bjd->offset;
     }
-    if (bjd->post_offset_var >= 0 && bjd->post_offset_var < NUM_IPS_OPTIONS_VARS)
+    if (bjd->post_offset_var >= 0 and
+            bjd->post_offset_var < NUM_IPS_OPTIONS_VARS)
     {
         uint32_t extract_post_offset;
         GetVarValueByIndex(&extract_post_offset, bjd->post_offset_var);
@@ -227,60 +223,22 @@ IpsOption::EvalStatus ByteJumpOption::eval(Cursor& c, Packet* p)
     {
         post_offset = bjd->post_offset;
     }
-
-    const uint8_t* const start_ptr = c.buffer();
-    const uint8_t* const end_ptr = start_ptr + c.size();
-    const uint8_t* const base_ptr = offset +
-        ((bjd->relative_flag) ? c.start() : start_ptr);
+    ByteJumpData extract_config = config;
+    extract_config.offset = offset;
+    const uint8_t *start_ptr, *end_ptr, *base_ptr;
+    set_cursor_bounds(extract_config, c, start_ptr, base_ptr, end_ptr);
 
     uint32_t jump = 0;
-    uint32_t payload_bytes_grabbed = 0;
-    uint8_t endian = bjd->endianness;
-
-    if (endian == ENDIAN_FUNC)
-    {
-        if (!p->endianness ||
-            !p->endianness->get_offset_endianness(base_ptr - p->data, endian))
-            return NO_MATCH;
-    }
+    int32_t payload_bytes_grabbed = 0;
 
-    // Both of the extraction functions contain checks to ensure the data
-    // is inbounds and will return no match if it isn't
-    if (bjd->bytes_to_grab)
+    if (bjd->bytes_to_extract)
     {
-        if ( !bjd->data_string_convert_flag )
-        {
-            if ( byte_extract(
-                endian, bjd->bytes_to_grab,
-                base_ptr, start_ptr, end_ptr, &jump) )
-                return NO_MATCH;
-
-            payload_bytes_grabbed = bjd->bytes_to_grab;
-        }
-        else
-        {
-            int32_t tmp = string_extract(
-                bjd->bytes_to_grab, bjd->base,
-                base_ptr, start_ptr, end_ptr, &jump);
-
-            if (tmp < 0)
-                return NO_MATCH;
-
-            payload_bytes_grabbed = tmp;
-        }
-
-        // Negative offsets that put us outside the buffer should have been caught
-        // in the extraction routines
-        assert(base_ptr >= c.buffer());
+        payload_bytes_grabbed = data_extraction(extract_config, p,
+            jump, start_ptr, base_ptr, end_ptr);
 
-        if (bjd->bitmask_val != 0 )
+        if (NO_MATCH == payload_bytes_grabbed)
         {
-            uint32_t num_tailing_zeros_bitmask = getNumberTailingZerosInBitmask(bjd->bitmask_val);
-            jump = jump & bjd->bitmask_val;
-            if (jump && num_tailing_zeros_bitmask )
-            {
-                jump = jump >> num_tailing_zeros_bitmask;
-            }
+            return NO_MATCH;
         }
 
         if (bjd->multiplier)
@@ -296,12 +254,12 @@ IpsOption::EvalStatus ByteJumpOption::eval(Cursor& c, Packet* p)
         }
     }
 
-    uint32_t pos;
+    uint32_t pos = 0;
 
-    if ( bjd->from_beginning_flag )
+    if (bjd->from_beginning_flag)
         pos = 0;
 
-    else if ( bjd->from_end_flag )
+    else if (bjd->from_end_flag)
         pos = c.size();
 
     else
@@ -309,7 +267,7 @@ IpsOption::EvalStatus ByteJumpOption::eval(Cursor& c, Packet* p)
 
     pos += jump + post_offset;
 
-    if ( !c.set_pos(pos) )
+    if (!c.set_pos(pos))
         return NO_MATCH;
 
     return MATCH;
@@ -343,7 +301,8 @@ static const Parameter s_params[] =
       "round the number of converted bytes up to the next 2- or 4-byte boundary" },
 
     { "post_offset", Parameter::PT_STRING, nullptr, nullptr,
-      "skip forward or backward (positive or negative value) by variable name or number of " \
+      "skip forward or backward (positive or negative value) " \
+      "by variable name or number of " \
       "bytes after the other jump options have been applied" },
 
     { "big", Parameter::PT_IMPLIED, nullptr, nullptr,
@@ -379,7 +338,8 @@ static const Parameter s_params[] =
 class ByteJumpModule : public Module
 {
 public:
-    ByteJumpModule() : Module(s_name, s_help, s_params) { data.multiplier = 1; }
+    ByteJumpModule() : Module(s_name, s_help, s_params)
+    { data.multiplier = 1; }
 
     bool begin(const char*, int, SnortConfig*) override;
     bool end(const char*, int, SnortConfig*) override;
@@ -408,7 +368,7 @@ bool ByteJumpModule::begin(const char*, int, SnortConfig*)
 
 bool ByteJumpModule::end(const char*, int, SnortConfig*)
 {
-    if ( var.empty() )
+    if (var.empty())
         data.offset_var = IPS_OPTIONS_NO_VAR;
     else
     {
@@ -420,7 +380,7 @@ bool ByteJumpModule::end(const char*, int, SnortConfig*)
             return false;
         }
     }
-    if ( post_var.empty() )
+    if (post_var.empty())
         data.post_offset_var = IPS_OPTIONS_NO_VAR;
     else
     {
@@ -432,26 +392,29 @@ bool ByteJumpModule::end(const char*, int, SnortConfig*)
             return false;
         }
     }
-    if ( !data.endianness )
+    if (!data.endianness)
         data.endianness = ENDIAN_BIG;
 
-    if (data.from_beginning_flag && data.from_end_flag)
+    if (data.from_beginning_flag and data.from_end_flag)
     {
         ParseError("from_beginning and from_end options together in a rule\n");
         return false;
     }
 
-    if ( data.bitmask_val && (numBytesInBitmask(data.bitmask_val) > data.bytes_to_grab))
+    if (data.bitmask_val and
+        (numBytesInBitmask(data.bitmask_val) > data.bytes_to_extract))
     {
-        ParseError("Number of bytes in \"bitmask\" value is greater than bytes to extract.");
+        ParseError("Number of bytes in \"bitmask\" value " \
+            "is greater than bytes to extract.");
         return false;
     }
 
-    if ((data.bytes_to_grab > MAX_BYTES_TO_GRAB) && !data.data_string_convert_flag)
+    if ((data.bytes_to_extract > MAX_BYTES_TO_GRAB) and
+        !data.string_convert_flag)
     {
         ParseError(
-            "byte_jump rule option cannot extract more than %d bytes without valid string prefix.",
-            MAX_BYTES_TO_GRAB);
+            "byte_jump rule option cannot extract more than %d bytes without "\
+            "valid string prefix.", MAX_BYTES_TO_GRAB);
         return false;
     }
 
@@ -460,64 +423,64 @@ bool ByteJumpModule::end(const char*, int, SnortConfig*)
 
 bool ByteJumpModule::set(const char*, Value& v, SnortConfig*)
 {
-    if ( v.is("~count") )
-        data.bytes_to_grab = v.get_uint8();
+    if (v.is("~count"))
+        data.bytes_to_extract = v.get_uint8();
 
-    else if ( v.is("~offset") )
+    else if (v.is("~offset"))
     {
         long n;
-        if ( v.strtol(n) )
+        if (v.strtol(n))
             data.offset = n;
         else
             var = v.get_string();
     }
-    else if ( v.is("relative") )
+    else if (v.is("relative"))
         data.relative_flag = 1;
 
-    else if ( v.is("from_beginning") )
+    else if (v.is("from_beginning"))
         data.from_beginning_flag = 1;
 
-    else if ( v.is("align") )
+    else if (v.is("align"))
         data.align_flag = 1;
 
-    else if ( v.is("multiplier") )
+    else if (v.is("multiplier"))
         data.multiplier = v.get_uint16();
 
-    else if ( v.is("post_offset") )
+    else if (v.is("post_offset"))
     {
         long n;
-        if ( v.strtol(n) )
+        if (v.strtol(n))
             data.post_offset = n;
         else
             post_var = v.get_string();
     }
-    else if ( v.is("big") )
+    else if (v.is("big"))
         set_byte_order(data.endianness, ENDIAN_BIG, "byte_jump");
 
-    else if ( v.is("little") )
+    else if (v.is("little"))
         set_byte_order(data.endianness, ENDIAN_LITTLE, "byte_jump");
 
-    else if ( v.is("dce") )
+    else if (v.is("dce"))
         set_byte_order(data.endianness, ENDIAN_FUNC, "byte_jump");
 
-    else if ( v.is("string") )
+    else if (v.is("string"))
     {
-        data.data_string_convert_flag = 1;
+        data.string_convert_flag = 1;
         data.base = 10;
     }
-    else if ( v.is("dec") )
+    else if (v.is("dec"))
         data.base = 10;
 
-    else if ( v.is("hex") )
+    else if (v.is("hex"))
         data.base = 16;
 
-    else if ( v.is("oct") )
+    else if (v.is("oct"))
         data.base = 8;
 
-    else if ( v.is("from_end") )
+    else if (v.is("from_end"))
         data.from_end_flag = 1;
 
-    else if ( v.is("bitmask") )
+    else if (v.is("bitmask"))
         data.bitmask_val = v.get_uint32();
 
     else
@@ -586,3 +549,513 @@ const BaseApi* ips_byte_jump[] =
     nullptr
 };
 
+//-------------------------------------------------------------------------
+// UNIT TESTS
+//-------------------------------------------------------------------------
+#ifdef UNIT_TEST
+#include <iostream>
+#include <climits>
+
+#include "framework/value.h"
+#include "framework/parameter.h"
+
+#include "catch/snort_catch.h"
+
+#define NO_MATCH snort::IpsOption::EvalStatus::NO_MATCH
+#define MATCH snort::IpsOption::EvalStatus::MATCH
+
+void SetByteJumpData(ByteJumpData &byte_jump, int value)
+{
+    byte_jump.bytes_to_extract = value;
+    byte_jump.offset = value;
+    byte_jump.relative_flag = value;
+    byte_jump.string_convert_flag = value;
+    byte_jump.from_beginning_flag = value;
+    byte_jump.align_flag = value;
+    byte_jump.endianness = value;
+    byte_jump.base = value;
+    byte_jump.multiplier = value;
+    byte_jump.post_offset = value;
+    byte_jump.bitmask_val = value;
+    byte_jump.offset_var = value;
+    byte_jump.from_end_flag = value;
+    byte_jump.post_offset_var = value;
+}
+
+void SetByteJumpMaxValue(ByteJumpData &byte_jump)
+{
+    byte_jump.bytes_to_extract = UINT_MAX;
+    byte_jump.offset = INT_MAX;
+    byte_jump.relative_flag = UCHAR_MAX;
+    byte_jump.string_convert_flag = UCHAR_MAX;
+    byte_jump.from_beginning_flag = UCHAR_MAX;
+    byte_jump.align_flag = UCHAR_MAX;
+    byte_jump.endianness = UCHAR_MAX;
+    byte_jump.base = UINT_MAX;
+    byte_jump.multiplier = UINT_MAX;
+    byte_jump.post_offset = INT_MAX;
+    byte_jump.bitmask_val = UINT_MAX;
+    byte_jump.offset_var = SCHAR_MAX;
+    byte_jump.from_end_flag = UCHAR_MAX;
+    byte_jump.post_offset_var = SCHAR_MAX;
+}
+
+class StubIpsOption : public IpsOption
+{
+public:
+    StubIpsOption(const char* name, option_type_t option_type) :
+        IpsOption(name, option_type)
+    { }
+};
+
+class StubEndianness : public Endianness
+{
+public:
+    StubEndianness() = default;
+    virtual bool get_offset_endianness(int32_t, uint8_t&) override
+    { return false; }
+};
+
+TEST_CASE("ByteJumpOption test", "[ips_byte_jump]")
+{
+    ByteJumpData byte_jump;
+    SetByteJumpData(byte_jump, 1);
+    snort::IpsOption::set_buffer("hello_world");
+
+    SECTION("method hash")
+    {
+        ByteJumpOption hash_test(byte_jump);
+        ByteJumpOption hash_test_equal(byte_jump);
+
+        SECTION("Testing hash with very low values")
+        {
+            SECTION("Hash has same source")
+            {
+                REQUIRE(hash_test.hash() == hash_test_equal.hash());
+            }
+
+            SECTION("Compare hash from different source")
+            {
+                SetByteJumpData(byte_jump, 4);
+                ByteJumpOption hash_test_diff(byte_jump);
+                CHECK(hash_test.hash() != hash_test_diff.hash());
+            }
+        }
+
+        SECTION("Testing hash with maximum values")
+        {
+            SetByteJumpMaxValue(byte_jump);
+            ByteJumpOption hash_test_max(byte_jump);
+            ByteJumpOption hash_test_equal_max(byte_jump);
+
+            SECTION("Hash has same source")
+            {
+                CHECK(hash_test_max.hash() == hash_test_equal_max.hash());
+            }
+
+            SECTION("Compare hash from different source")
+            {
+                SetByteJumpMaxValue(byte_jump);
+                ByteJumpOption hash_test_max(byte_jump);
+                CHECK(hash_test.hash() != hash_test_max.hash());
+            }
+        }
+    }
+
+    SECTION("operator ==")
+    {
+        ByteJumpOption jump(byte_jump);
+
+        SECTION("Compare IpsOptions with different names")
+        {
+            StubIpsOption case_diff_name("not_hello_world",
+                option_type_t::RULE_OPTION_TYPE_BUFFER_USE);
+            REQUIRE(jump != case_diff_name);
+        }
+
+        ByteJumpData byte_jump2;
+        SetByteJumpData(byte_jump2, 1);
+
+        SECTION("Compare between equals objects")
+        {
+            ByteJumpOption jump_1(byte_jump);
+            REQUIRE(jump == jump_1);
+        }
+
+        SECTION("bytes_to_extract is different")
+        {
+            byte_jump2.bytes_to_extract = 2;
+            ByteJumpOption jump_2_1(byte_jump2);
+            REQUIRE(jump != jump_2_1);
+        }
+
+        SECTION("offset is different")
+        {
+            byte_jump2.offset = 2;
+            ByteJumpOption jump_2_2(byte_jump2);
+            REQUIRE(jump != jump_2_2);
+        }
+
+        SECTION("relative_flag is different")
+        {
+            byte_jump2.relative_flag = 0;
+            ByteJumpOption jump_2_3(byte_jump2);
+            REQUIRE(jump != jump_2_3);
+        }
+
+        SECTION("string_convert_flag is different")
+        {
+            byte_jump2.string_convert_flag = 0;
+            ByteJumpOption jump_2_4(byte_jump2);
+            REQUIRE(jump != jump_2_4);
+        }
+
+        SECTION("from_beginning_flag is different")
+        {
+            byte_jump2.from_beginning_flag = 0;
+            ByteJumpOption jump_2_5(byte_jump2);
+            REQUIRE(jump != jump_2_5);
+        }
+
+        SECTION("align_flag is different")
+        {
+            byte_jump2.align_flag = 0;
+            ByteJumpOption jump_2_6(byte_jump2);
+            REQUIRE(jump != jump_2_6);
+        }
+
+        SECTION("endianness is different")
+        {
+            byte_jump2.endianness = 0;
+            ByteJumpOption jump_2_7(byte_jump2);
+            REQUIRE(jump != jump_2_7);
+        }
+
+        SECTION("base is different")
+        {
+            byte_jump2.base = 2;
+            ByteJumpOption jump_2_8(byte_jump2);
+            REQUIRE(jump != jump_2_8);
+        }
+
+        SECTION("multiplier is different")
+        {
+            byte_jump2.multiplier = 2;
+            ByteJumpOption jump_2_9(byte_jump2);
+            REQUIRE(jump != jump_2_9);
+        }
+
+        SECTION("post_offset is different")
+        {
+            byte_jump2.post_offset = 2;
+            ByteJumpOption jump_2_10(byte_jump2);
+            REQUIRE(jump != jump_2_10);
+        }
+
+        SECTION("bitmask_val is different")
+        {
+            byte_jump2.bitmask_val = 2;
+            ByteJumpOption jump_2_11(byte_jump2);
+            REQUIRE(jump != jump_2_11);
+        }
+
+        SECTION("offset_var is different")
+        {
+            byte_jump2.offset_var = 0;
+            ByteJumpOption jump_2_12(byte_jump2);
+            REQUIRE(jump != jump_2_12);
+        }
+
+        SECTION("from_end_flag is different")
+        {
+            byte_jump2.from_end_flag = 0;
+            ByteJumpOption jump_2_13(byte_jump2);
+            REQUIRE(jump != jump_2_13);
+        }
+
+        SECTION("post_offset_var is different")
+        {
+            byte_jump2.post_offset_var = 0;
+            ByteJumpOption jump_2_14(byte_jump2);
+            REQUIRE(jump != jump_2_14);
+        }
+    }
+
+    SECTION("method eval")
+    {
+        Packet test_packet;
+        Cursor current_cursor;
+        SetByteJumpData(byte_jump, 1);
+
+        SECTION("Cursor not set correct for string_extract")
+        {
+            ByteJumpOption test_2(byte_jump);
+            REQUIRE((test_2.eval(current_cursor, &test_packet)) == NO_MATCH);
+        }
+
+        SECTION("Extract too much (1000000) bytes from in byte_extract")
+        {
+            uint8_t buff = 0;
+            byte_jump.string_convert_flag = 0;
+            byte_jump.bytes_to_extract = 1000000;
+            current_cursor.set("hello_world_long_name", &buff, 50);
+            ByteJumpOption test_3(byte_jump);
+            REQUIRE((test_3.eval(current_cursor, &test_packet)) == NO_MATCH);
+        }
+
+        SECTION("Cursor not set correct")
+        {
+            uint8_t buff = 0;
+            current_cursor.set("hello_world_long_name", &buff, 1);
+            byte_jump.string_convert_flag = 0;
+            byte_jump.from_beginning_flag = 0;
+            byte_jump.from_end_flag = 1;
+            byte_jump.bytes_to_extract = 1;
+            byte_jump.post_offset_var = 12;
+            ByteJumpOption test_4(byte_jump);
+            REQUIRE((test_4.eval(current_cursor, &test_packet)) == NO_MATCH);
+        }
+
+        SECTION("Match")
+        {
+            uint8_t buff = 0;
+            byte_jump.string_convert_flag = 0;
+            byte_jump.from_beginning_flag = 0;
+            byte_jump.from_end_flag = 0;
+            current_cursor.set("hello_world_long_name", &buff, 50);
+            ByteJumpOption test_5(byte_jump);
+            REQUIRE((test_5.eval(current_cursor, &test_packet)) == MATCH);
+
+            current_cursor.set("hello_world_long_name", (const uint8_t*)"hello", 5);
+            byte_jump.from_beginning_flag = 1;
+            byte_jump.bitmask_val = 2;
+            ByteJumpOption test_5_1(byte_jump);
+            REQUIRE((test_5_1.eval(current_cursor, &test_packet)) == MATCH);
+        }
+    }
+}
+
+TEST_CASE("ByteJumpModule test", "[ips_byte_jump]")
+{
+    ByteJumpModule module_jump;
+    ByteJumpData byte_jump;
+    SetByteJumpData(byte_jump, 1);
+
+    SECTION("method end")
+    {
+        std::string buff = "tmp";
+
+        SECTION("Undefined rule option for var")
+        {
+            module_jump.var = buff;
+            byte_jump.offset_var = -1;
+            module_jump.data = byte_jump;
+            REQUIRE(module_jump.end("tmp", 0, nullptr) == false);
+        }
+
+        SECTION("Undefined rule option for offset_var")
+        {
+            module_jump.var.clear();
+            module_jump.post_var = buff;
+            byte_jump.post_offset_var = -1;
+            module_jump.data = byte_jump;
+            REQUIRE(module_jump.end("tmp", 0, nullptr) == false);
+        }
+
+        SECTION("From_beginning and from_end options together")
+        {
+            byte_jump.endianness = 0;
+            module_jump.data = byte_jump;
+            REQUIRE(module_jump.end("tmp", 0, nullptr) == false);
+        }
+
+        SECTION("Number of bytes in \"bitmask\" value is greater than bytes to extract")
+        {
+            byte_jump.from_beginning_flag = 0;
+            byte_jump.bytes_to_extract = 0;
+            module_jump.data = byte_jump;
+            REQUIRE(module_jump.end("tmp", 0, nullptr) == false);
+        }
+
+        SECTION("byte_jump rule option cannot extract more \
+            than %d bytes without valid string prefix")
+        {
+            byte_jump.from_beginning_flag = 0;
+            byte_jump.bytes_to_extract = 5;
+            byte_jump.string_convert_flag = 0;
+            module_jump.data = byte_jump;
+            REQUIRE(module_jump.end("tmp", 0, nullptr) == false);
+        }
+
+        SECTION("Case with returned value true")
+        {
+            byte_jump.from_beginning_flag = 0;
+            module_jump.data = byte_jump;
+            REQUIRE(module_jump.end("tmp", 0, nullptr) == true);
+        }
+    }
+
+    SECTION("method set")
+    {
+        Value value(false);
+
+        SECTION("All params incorrect")
+        {
+            REQUIRE(module_jump.set(nullptr, value, nullptr) == false);
+        }
+
+        SECTION("Case param \"~count\"")
+        {
+            Parameter param("~count", snort::Parameter::Type::PT_BOOL,
+                nullptr, "default", "help");
+            value.set(&param);
+            REQUIRE(module_jump.set(nullptr, value, nullptr) == true);
+        }
+
+        SECTION("Case param \"~offset\"")
+        {
+            SECTION("Value doesn't have a str")
+            {
+                Parameter param("~offset", snort::Parameter::Type::PT_BOOL,
+                    nullptr, "default", "help");
+                value.set(&param);
+                REQUIRE(module_jump.set(nullptr, value, nullptr) == true);
+            }
+
+            SECTION("When value has a str")
+            {
+                Value value_tmp("123");
+                Parameter param("~offset", snort::Parameter::Type::PT_BOOL,
+                    nullptr, "default", "help");
+                value_tmp.set(&param);
+                REQUIRE(module_jump.set(nullptr, value_tmp, nullptr) == true);
+            }
+        }
+
+        SECTION("Case param \"relative\"")
+        {
+            Parameter param("relative", snort::Parameter::Type::PT_BOOL,
+                nullptr, "default", "help");
+            value.set(&param);
+            REQUIRE(module_jump.set(nullptr, value, nullptr) == true);
+        }
+
+        SECTION("Param \"from_beginning\" correct")
+        {
+            Parameter param("from_beginning", snort::Parameter::Type::PT_BOOL,
+                nullptr, "default", "help");
+            value.set(&param);
+            REQUIRE(module_jump.set(nullptr, value, nullptr) == true);
+        }
+
+        SECTION("Case param \"from_end\"")
+        {
+            Parameter param("from_end", snort::Parameter::Type::PT_BOOL,
+                nullptr, "default", "help");
+            value.set(&param);
+            REQUIRE(module_jump.set(nullptr, value, nullptr) == true);
+        }
+
+        SECTION("Case param \"align\"")
+        {
+            Parameter param("align", snort::Parameter::Type::PT_BOOL,
+                nullptr, "default", "help");
+            value.set(&param);
+            REQUIRE(module_jump.set(nullptr, value, nullptr) == true);
+        }
+
+        SECTION("Case param \"multiplier\"")
+        {
+            Parameter param("multiplier", snort::Parameter::Type::PT_BOOL,
+                nullptr, "default", "help");
+            value.set(&param);
+            REQUIRE(module_jump.set(nullptr, value, nullptr) == true);
+        }
+
+        SECTION("Case param \"post_offset\"")
+        {
+            SECTION("Value doesn't have a str")
+            {
+                Parameter param("post_offset", snort::Parameter::Type::PT_BOOL,
+                    nullptr, "default", "help");
+                value.set(&param);
+                REQUIRE(module_jump.set(nullptr, value, nullptr) == true);
+            }
+
+            SECTION("When value has a str")
+            {
+                Value value_tmp("123");
+                Parameter param("post_offset", snort::Parameter::Type::PT_BOOL,
+                    nullptr, "default", "help");
+                value_tmp.set(&param);
+                REQUIRE(module_jump.set(nullptr, value_tmp, nullptr) == true);
+            }
+        }
+
+        SECTION("Case param \"big\"")
+        {
+            Parameter param("big", snort::Parameter::Type::PT_BOOL,
+                nullptr, "default", "help");
+            value.set(&param);
+            REQUIRE(module_jump.set(nullptr, value, nullptr) == true);
+        }
+
+        SECTION("Case param \"little\"")
+        {
+            Parameter param("little", snort::Parameter::Type::PT_BOOL,
+                nullptr, "default", "help");
+            value.set(&param);
+            REQUIRE(module_jump.set(nullptr, value, nullptr) == true);
+        }
+
+        SECTION("Case param \"dce\"")
+        {
+            Parameter param("dce", snort::Parameter::Type::PT_BOOL,
+                nullptr, "default", "help");
+            value.set(&param);
+            REQUIRE(module_jump.set(nullptr, value, nullptr) == true);
+        }
+
+        SECTION("Case param \"string\"")
+        {
+            Parameter param("string", snort::Parameter::Type::PT_BOOL,
+                nullptr, "default", "help");
+            value.set(&param);
+            REQUIRE(module_jump.set(nullptr, value, nullptr) == true);
+        }
+
+        SECTION("Case param \"dec\"")
+        {
+            Parameter param("dec", snort::Parameter::Type::PT_BOOL,
+                nullptr, "default", "help");
+            value.set(&param);
+            REQUIRE(module_jump.set(nullptr, value, nullptr) == true);
+        }
+
+        SECTION("Case param \"hex\"")
+        {
+            Parameter param("hex", snort::Parameter::Type::PT_BOOL,
+                nullptr, "default", "help");
+            value.set(&param);
+            REQUIRE(module_jump.set(nullptr, value, nullptr) == true);
+        }
+
+        SECTION("Case param \"oct\"")
+        {
+            Parameter param("oct", snort::Parameter::Type::PT_BOOL,
+                nullptr, "default", "help");
+            value.set(&param);
+            REQUIRE(module_jump.set(nullptr, value, nullptr) == true);
+        }
+
+        SECTION("Case param \"bitmask\"")
+        {
+            Parameter param("bitmask", snort::Parameter::Type::PT_BOOL,
+                nullptr, "default", "help");
+            value.set(&param);
+            REQUIRE(module_jump.set(nullptr, value, nullptr) == true);
+        }
+    }
+}
+
+#endif
index ffa54bbb669e8a319a09554880a32d5aa7361d0f..a50cb000306965b9fa3cf0868566a5b5d05664ce 100644 (file)
 #include "log/messages.h"
 #include "profiler/profiler.h"
 #include "protocols/packet.h"
-#include "profiler/profiler.h"
 #include "utils/util.h"
 
 #include "extract.h"
 
+#ifdef UNIT_TEST
+#include "catch/snort_catch.h"
+#include "service_inspectors/dce_rpc/dce_common.h"
+#endif
+
 using namespace snort;
 using namespace std;
 
@@ -57,28 +61,22 @@ enum BM_Oper // must match the exact order in Parameter table - i.e "+|-|*|/|<<|
 
 static THREAD_LOCAL ProfileStats byteMathPerfStats;
 
-struct ByteMathData
+struct ByteMathData : public ByteData
 {
-    uint32_t bytes_to_extract;
     uint32_t rvalue;
-    int32_t offset;
-    uint32_t bitmask_val;
-    char* result_name;
     BM_Oper oper;
-    bool relative_flag;
-    bool string_convert_flag;
-    uint8_t base;
-    uint8_t endianess;
+    int8_t offset_var;
     int8_t result_var;
     int8_t rvalue_var;
-    int8_t offset_var;
+    char* result_name;
 };
 
 class ByteMathOption : public IpsOption
 {
 public:
-    ByteMathOption(const ByteMathData& c) : IpsOption(s_name, RULE_OPTION_TYPE_BUFFER_USE),
-        config(c) { }
+    ByteMathOption(const ByteMathData& c) :
+        IpsOption(s_name, RULE_OPTION_TYPE_BUFFER_USE), config(c)
+    { }
 
     ~ByteMathOption() override
     { snort_free(config.result_name); }
@@ -96,6 +94,7 @@ public:
 
 private:
     const ByteMathData config;
+    int calc(uint32_t& value, const uint32_t rvalue);
 };
 
 uint32_t ByteMathOption::hash() const
@@ -110,7 +109,7 @@ uint32_t ByteMathOption::hash() const
     b += ((uint32_t) config.rvalue_var << 24 |
         (uint32_t) config.offset_var << 16 |
         (uint32_t) config.result_var << 8 |
-        config.endianess);
+        config.endianness);
     c += config.base;
 
     mix(a,b,c);
@@ -129,24 +128,24 @@ uint32_t ByteMathOption::hash() const
 
 bool ByteMathOption::operator==(const IpsOption& ips) const
 {
-    if ( !IpsOption::operator==(ips) )
+    if (!IpsOption::operator==(ips))
         return false;
 
     const ByteMathOption& rhs = (const ByteMathOption&)ips;
     const ByteMathData* left = &config;
     const ByteMathData* right = &rhs.config;
 
-    if (( left->bytes_to_extract == right->bytes_to_extract) &&
-        ( left->rvalue == right->rvalue) &&
-        ( left->oper == right->oper) &&
-        ( left->offset == right->offset) &&
-        ( left->relative_flag == right->relative_flag) &&
-        ( left->string_convert_flag == right->string_convert_flag) &&
-        ( left->endianess == right->endianess) &&
-        ( left->base == right->base) &&
-        ( left->bitmask_val == right->bitmask_val) &&
-        ( left->rvalue_var == right->rvalue_var) &&
-        ( left->offset_var == right->offset_var) &&
+    if (( left->bytes_to_extract == right->bytes_to_extract) and
+        ( left->rvalue == right->rvalue) and
+        ( left->oper == right->oper) and
+        ( left->offset == right->offset) and
+        ( left->relative_flag == right->relative_flag) and
+        ( left->string_convert_flag == right->string_convert_flag) and
+        ( left->endianness == right->endianness) and
+        ( left->base == right->base) and
+        ( left->bitmask_val == right->bitmask_val) and
+        ( left->rvalue_var == right->rvalue_var) and
+        ( left->offset_var == right->offset_var) and
         ( left->result_var == right->result_var))
     {
         return true;
@@ -163,18 +162,9 @@ IpsOption::EvalStatus ByteMathOption::eval(Cursor& c, Packet* p)
 {
     RuleProfile profile(byteMathPerfStats);
 
-    if (p == nullptr)
-        return NO_MATCH;
-
-    const uint8_t* start = c.buffer();
-    int dsize = c.size();
-
-    const uint8_t* ptr = config.relative_flag ? c.start() : c.buffer();
-    const uint8_t* end = start + dsize;
-
     /* Get values from ips options variables, if present. */
     uint32_t rvalue;
-    if (config.rvalue_var >= 0 && config.rvalue_var < NUM_IPS_OPTIONS_VARS)
+    if (config.rvalue_var >= 0 and config.rvalue_var < NUM_IPS_OPTIONS_VARS)
     {
         GetVarValueByIndex(&rvalue, config.rvalue_var);
         if (rvalue == 0 and config.oper == BM_DIVIDE)
@@ -184,13 +174,13 @@ IpsOption::EvalStatus ByteMathOption::eval(Cursor& c, Packet* p)
         rvalue = config.rvalue;
 
     int32_t offset;
-    if (config.offset_var >= 0 && config.offset_var < NUM_IPS_OPTIONS_VARS)
+    if (config.offset_var >= 0 and config.offset_var < NUM_IPS_OPTIONS_VARS)
     {
         // Rule options variables are kept as uint32_t,
         // in order to support full range for unsigned options.
         // Signed options do a cast to int32_t after getting the value.
-        // The range limitation should be taken into consideration when writing a rule
-        // with an option that is read from a variable.
+        // The range limitation should be taken into consideration when writing
+        // a rule with an option that is read from a variable.
         uint32_t extract_offset;
         GetVarValueByIndex(&extract_offset, config.offset_var);
         offset = (int32_t)extract_offset;
@@ -198,50 +188,31 @@ IpsOption::EvalStatus ByteMathOption::eval(Cursor& c, Packet* p)
     else
         offset = config.offset;
 
-    ptr += offset;
+    ByteMathData extract_config = config;
+    extract_config.offset = offset;
 
-    // check bounds
-    if (ptr < start || ptr >= end)
-        return NO_MATCH;
+    uint32_t value = 0;
+    int bytes_read = extract_data(extract_config, c, p, value);
 
-    uint8_t endian = config.endianess;
-    if (config.endianess == ENDIAN_FUNC)
-    {
-        if (!p->endianness ||
-            !p->endianness->get_offset_endianness(ptr - p->data, endian))
-            return NO_MATCH;
-    }
+    if (bytes_read == NO_MATCH)
+        return NO_MATCH;
 
-    // do the extraction
-    uint32_t value;
+    if (calc(value, rvalue) == NO_MATCH)
+        return NO_MATCH;
 
-    if (!config.string_convert_flag)
-    {
-        if (byte_extract(endian, config.bytes_to_extract, ptr, start, end, &value) < 0)
-            return NO_MATCH;
-    }
-    else
-    {
-        if (string_extract(config.bytes_to_extract, config.base, ptr, start, end, &value) < 0)
-            return NO_MATCH;
-    }
+    SetVarValueByIndex(value, config.result_var);
 
-    if (config.bitmask_val != 0)
-    {
-        uint32_t num_tailing_zeros_bitmask = getNumberTailingZerosInBitmask(config.bitmask_val);
-        value = value & config.bitmask_val;
-        if ( value && num_tailing_zeros_bitmask )
-        {
-            value = value >> num_tailing_zeros_bitmask;
-        }
-    }
+    return MATCH;
+}
 
+int ByteMathOption::calc(uint32_t& value, const uint32_t rvalue)
+{
     // Note: all of the operations are done on uint32_t.
     // If the rule isn't written correctly, there is a risk for wrap around.
     switch (config.oper)
     {
     case BM_PLUS:
-        if( value + rvalue < value )
+        if (value + rvalue < value)
         {
             return NO_MATCH;
         }
@@ -251,7 +222,7 @@ IpsOption::EvalStatus ByteMathOption::eval(Cursor& c, Packet* p)
             break;
         }
     case BM_MINUS:
-        if( value < rvalue )
+        if (value < rvalue)
         {
             return NO_MATCH;
         }
@@ -261,7 +232,8 @@ IpsOption::EvalStatus ByteMathOption::eval(Cursor& c, Packet* p)
             break;
         }
     case BM_MULTIPLY:
-        if ( value != 0 and rvalue != 0 and (((value * rvalue) / rvalue) != value) )
+        if (value != 0 and rvalue != 0 and
+            (((value * rvalue) / rvalue) != value))
         {
             return NO_MATCH;
         }
@@ -270,18 +242,18 @@ IpsOption::EvalStatus ByteMathOption::eval(Cursor& c, Packet* p)
             value *= rvalue;
             break;
         }
-    case BM_DIVIDE: value /= rvalue;
-            break;
+    case BM_DIVIDE:
+        value /= rvalue;
+        break;
 
-    case BM_LEFT_SHIFT: value <<= rvalue;
-            break;
+    case BM_LEFT_SHIFT:
+        value <<= rvalue;
+        break;
 
-    case BM_RIGHT_SHIFT: value >>= rvalue;
-            break;
+    case BM_RIGHT_SHIFT:
+        value >>= rvalue;
+        break;
     }
-
-    SetVarValueByIndex(value, config.result_var);
-
     return MATCH;
 }
 
@@ -300,7 +272,7 @@ static void parse_endian(uint8_t value, ByteMathData& idx)
 {
     assert(value <= 1);
     int endian[] = { ENDIAN_BIG, ENDIAN_LITTLE };
-    set_byte_order(idx.endianess, endian[value], "byte_math");
+    set_byte_order(idx.endianness, endian[value], "byte_math");
 }
 
 //-------------------------------------------------------------------------
@@ -358,7 +330,7 @@ public:
     { return DETECT; }
 
 public:
-    ByteMathData data = {};
+    ByteMathData data{};
     string rvalue_var;
     string off_var;
 };
@@ -374,16 +346,16 @@ bool ByteMathModule::begin(const char*, int, SnortConfig*)
 
 bool ByteMathModule::set(const char*, Value& v, SnortConfig*)
 {
-    if ( v.is("bytes") )
+    if (v.is("bytes"))
         data.bytes_to_extract = v.get_uint8();
 
-    else if ( v.is("oper") )
+    else if (v.is("oper"))
         data.oper = (BM_Oper)v.get_uint8();
 
-    else if ( v.is("rvalue") )
+    else if (v.is("rvalue"))
     {
         long n;
-        if ( v.strtol(n) )
+        if (v.strtol(n))
         {
             if (n == 0)
                 return false;
@@ -392,32 +364,32 @@ bool ByteMathModule::set(const char*, Value& v, SnortConfig*)
         else
             rvalue_var = v.get_string();
     }
-    else if ( v.is("offset") )
+    else if (v.is("offset"))
     {
         long n;
-        if ( v.strtol(n) )
+        if (v.strtol(n))
             data.offset = n;
         else
             off_var = v.get_string();
     }
-    else if ( v.is("relative") )
+    else if (v.is("relative"))
         data.relative_flag = true;
 
-    else if ( v.is("dce") )
-        set_byte_order(data.endianess, ENDIAN_FUNC, "byte_math");
+    else if (v.is("dce"))
+        set_byte_order(data.endianness, ENDIAN_FUNC, "byte_math");
 
-    else if ( v.is("string") )
+    else if (v.is("string"))
     {
         data.string_convert_flag = true;
         parse_base(v.get_uint8(), data);
     }
-    else if ( v.is("endian") )
+    else if (v.is("endian"))
         parse_endian(v.get_uint8(), data);
 
-    else if ( v.is("bitmask") )
+    else if (v.is("bitmask"))
         data.bitmask_val = v.get_uint32();
 
-    else if ( v.is("result") )
+    else if (v.is("result"))
         data.result_name = snort_strdup(v.get_string());
 
     else
@@ -442,7 +414,7 @@ static bool ByteMathVerify(ByteMathData* data)
         return false;
     }
 
-    if ( ((data->oper == BM_LEFT_SHIFT) || (data->oper == BM_RIGHT_SHIFT)) &&
+    if (((data->oper == BM_LEFT_SHIFT) or (data->oper == BM_RIGHT_SHIFT)) and
         (data->rvalue > 32))
     {
         ParseError("Number of bits in rvalue input [%u] should be less than 32 "
@@ -450,7 +422,7 @@ static bool ByteMathVerify(ByteMathData* data)
         return false;
     }
 
-    if (((data->oper == BM_LEFT_SHIFT) || (data->oper == BM_RIGHT_SHIFT)) &&
+    if (((data->oper == BM_LEFT_SHIFT) or (data->oper == BM_RIGHT_SHIFT)) and
         (data->bytes_to_extract > 4))
     {
         ParseError("for operators << and  >> valid bytes_to_extract input range is"
@@ -458,7 +430,7 @@ static bool ByteMathVerify(ByteMathData* data)
         return false;
     }
 
-    if (data->bytes_to_extract > MAX_BYTES_TO_GRAB && !data->string_convert_flag)
+    if (data->bytes_to_extract > MAX_BYTES_TO_GRAB and !data->string_convert_flag)
     {
         ParseError("byte_math rule option cannot extract more than %d bytes without valid"
             " string prefix.", MAX_BYTES_TO_GRAB);
@@ -476,7 +448,7 @@ static bool ByteMathVerify(ByteMathData* data)
 
 bool ByteMathModule::end(const char*, int, SnortConfig*)
 {
-    if ( rvalue_var.empty() )
+    if (rvalue_var.empty())
         data.rvalue_var = IPS_OPTIONS_NO_VAR;
     else
     {
@@ -489,7 +461,7 @@ bool ByteMathModule::end(const char*, int, SnortConfig*)
         }
     }
 
-    if ( off_var.empty() )
+    if (off_var.empty())
         data.offset_var = IPS_OPTIONS_NO_VAR;
     else
     {
@@ -502,8 +474,8 @@ bool ByteMathModule::end(const char*, int, SnortConfig*)
         }
     }
 
-    if ( !data.endianess )
-        data.endianess = ENDIAN_BIG;
+    if (!data.endianness )
+        data.endianness = ENDIAN_BIG;
 
     return ByteMathVerify(&data);
 }
@@ -575,3 +547,1007 @@ const BaseApi* ips_byte_math[] =
     &byte_math_api.base,
     nullptr
 };
+
+//-------------------------------------------------------------------------
+// UNIT TESTS
+//-------------------------------------------------------------------------
+#ifdef UNIT_TEST
+
+//-------------------------------------------------------------------------
+// helpers
+//-------------------------------------------------------------------------
+#define INITIALIZE(obj, bytes_to_extract_value, rvalue_value, offset_value, bitmask_val_value, \
+    result_name_value, oper_value, relative_flag_value, string_convert_flag_value, base_value, \
+    endianness_value, result_var_value, rvalue_var_value, offset_var_value) \
+    obj.base = base_value; \
+    obj.bitmask_val = bitmask_val_value; \
+    obj.bytes_to_extract = bytes_to_extract_value; \
+    obj.offset = offset_value; \
+    obj.endianness = endianness_value; \
+    obj.relative_flag = relative_flag_value; \
+    obj.string_convert_flag = string_convert_flag_value; \
+    obj.rvalue = rvalue_value; \
+    obj.oper = oper_value; \
+    obj.offset_var = offset_var_value; \
+    obj.result_var = result_var_value; \
+    obj.rvalue_var = rvalue_var_value; \
+    obj.result_name = result_name_value;
+
+class ByteMathDataMatcher
+    : public Catch::Matchers::Impl::MatcherBase<ByteMathData>
+{
+public:
+    ByteMathDataMatcher(const ByteMathData& value) : m_value(value) {}
+
+    bool match(ByteMathData const& rhs) const override
+    {
+        return ((m_value.bytes_to_extract == rhs.bytes_to_extract) and
+            (m_value.rvalue == rhs.rvalue) and (m_value.oper == rhs.oper) and
+            (m_value.offset == rhs.offset) and
+            (m_value.relative_flag == rhs.relative_flag) and
+            (m_value.string_convert_flag == rhs.string_convert_flag) and
+            (m_value.endianness == rhs.endianness) and
+            (m_value.base == rhs.base) and
+            (m_value.bitmask_val == rhs.bitmask_val) and
+            (m_value.rvalue_var == rhs.rvalue_var) and
+            (m_value.offset_var == rhs.offset_var) and
+            (m_value.result_var == rhs.result_var));
+    }
+
+    std::string describe() const override
+    {
+        std::ostringstream ss;
+        ss << "settings is equals to:\n";
+        ss << "bytes_to_extract : " << m_value.bytes_to_extract << ";\n";
+        ss << "rvalue : " << m_value.rvalue << ";\n";
+        ss << "oper : " << m_value.oper << ";\n";
+        ss << "offset : " << m_value.offset << ";\n";
+        ss << "relative_flag : " << m_value.relative_flag << ";\n";
+        ss << "string_convert_flag : " << m_value.string_convert_flag << ";\n";
+        ss << "endianness : " << m_value.endianness << ";\n";
+        ss << "base : " << m_value.base << ";\n";
+        ss << "bitmask_val : " << m_value.bitmask_val << ";\n";
+        ss << "rvalue_var : " << m_value.rvalue_var << ";\n";
+        ss << "offset_var : " << m_value.offset_var << ";\n";
+        ss << "result_var : " << m_value.result_var << ";\n";
+        return ss.str();
+    }
+
+private:
+    ByteMathData m_value;
+};
+
+ByteMathDataMatcher ByteMathDataEquals(const ByteMathData& value)
+{
+    return {value};
+}
+
+class SetBufferOptionHelper : public IpsOption
+{
+public:
+    SetBufferOptionHelper(const char* value)
+        : IpsOption(value, RULE_OPTION_TYPE_BUFFER_SET)
+    { }
+};
+
+//-------------------------------------------------------------------------
+// option tests
+//-------------------------------------------------------------------------
+
+TEST_CASE("ByteMathOption::operator== valid", "[ips_byte_math]")
+{
+    SetBufferOptionHelper set_buf("test");
+
+    char* lhs_name = new char[9];
+    strcpy(lhs_name, "test_lhs");
+    ByteMathData data_lhs;
+    INITIALIZE(data_lhs, 0, 25, 0, 0, lhs_name, BM_PLUS, 0, 0, 0, ENDIAN_BIG,
+        IPS_OPTIONS_NO_VAR, IPS_OPTIONS_NO_VAR, IPS_OPTIONS_NO_VAR);
+    ByteMathOption lhs(data_lhs);
+
+    char* rhs_name = new char[9];
+    strcpy(rhs_name, "test_rhs");
+    ByteMathData data_rhs;
+    INITIALIZE(data_rhs, 0, 25, 0, 0, rhs_name, BM_PLUS, 0, 0, 0, ENDIAN_BIG,
+        IPS_OPTIONS_NO_VAR, IPS_OPTIONS_NO_VAR, IPS_OPTIONS_NO_VAR);
+    ByteMathOption rhs(data_rhs);
+
+    CHECK(lhs == rhs);
+}
+
+TEST_CASE("ByteMathOption::operator== invalid", "[ips_byte_math]")
+{
+    SetBufferOptionHelper set_buf("test");
+
+    char* lhs_name = new char[5];
+    strcpy(lhs_name, "test");
+    ByteMathData data_lhs;
+    INITIALIZE(data_lhs, 0, 25, 0, 0, lhs_name, BM_PLUS, 0, 0, 0, ENDIAN_BIG,
+        IPS_OPTIONS_NO_VAR, IPS_OPTIONS_NO_VAR, IPS_OPTIONS_NO_VAR);
+    ByteMathOption lhs(data_lhs);
+
+    char* rhs_name = new char[5];
+    strcpy(rhs_name, "test");
+
+    SECTION("not equal to IpsOption object")
+    {
+        CHECK(lhs != set_buf);
+        delete[] rhs_name;
+    }
+    SECTION("all fields is different")
+    {
+        delete[] rhs_name;
+        rhs_name = new char[5];
+        strcpy(rhs_name, "unix");
+        ByteMathData data_rhs;
+        INITIALIZE(data_rhs, 2, 25, 2, 255, rhs_name, BM_MULTIPLY, 1,
+            1, 8, ENDIAN_LITTLE, 1, 1, 1);
+        ByteMathOption rhs(data_rhs);
+        CHECK(lhs != rhs);
+    }
+    SECTION("bytes_to_grab is different")
+    {
+        ByteMathData data_rhs;
+        INITIALIZE(data_rhs, 2, 25, 0, 0, rhs_name, BM_PLUS, 0, 0, 0, ENDIAN_BIG,
+            IPS_OPTIONS_NO_VAR, IPS_OPTIONS_NO_VAR, IPS_OPTIONS_NO_VAR);
+        ByteMathOption rhs(data_rhs);
+        CHECK(lhs != rhs);
+    }
+    SECTION("rvalue is different")
+    {
+        ByteMathData data_rhs;
+        INITIALIZE(data_rhs, 0, 15, 0, 0, rhs_name, BM_PLUS, 0, 0, 0, ENDIAN_BIG,
+            IPS_OPTIONS_NO_VAR, IPS_OPTIONS_NO_VAR, IPS_OPTIONS_NO_VAR);
+        ByteMathOption rhs(data_rhs);
+        CHECK(lhs != rhs);
+    }
+    SECTION("offset is different")
+    {
+        ByteMathData data_rhs;
+        INITIALIZE(data_rhs, 0, 25, 3, 0, rhs_name, BM_PLUS, 0, 0, 0, ENDIAN_BIG,
+            IPS_OPTIONS_NO_VAR, IPS_OPTIONS_NO_VAR, IPS_OPTIONS_NO_VAR);
+        ByteMathOption rhs(data_rhs);
+        CHECK(lhs != rhs);
+    }
+    SECTION("bitmask is different")
+    {
+        ByteMathData data_rhs;
+        INITIALIZE(data_rhs, 0, 25, 0, 255, rhs_name, BM_PLUS, 0, 0, 0, ENDIAN_BIG,
+            IPS_OPTIONS_NO_VAR, IPS_OPTIONS_NO_VAR, IPS_OPTIONS_NO_VAR);
+        ByteMathOption rhs(data_rhs);
+        CHECK(lhs != rhs);
+    }
+    SECTION("result_name is different")
+    {
+        delete[] rhs_name;
+        rhs_name = new char[5];
+        strcpy(rhs_name, "unix");
+        ByteMathData data_rhs;
+        INITIALIZE(data_rhs, 0, 25, 0, 255, rhs_name, BM_PLUS, 0, 0, 0, ENDIAN_BIG,
+            IPS_OPTIONS_NO_VAR, IPS_OPTIONS_NO_VAR, IPS_OPTIONS_NO_VAR);
+        ByteMathOption rhs(data_rhs);
+        CHECK(lhs != rhs);
+    }
+    SECTION("operation is different")
+    {
+        ByteMathData data_rhs;
+        INITIALIZE(data_rhs, 0, 25, 0, 0, rhs_name, BM_DIVIDE, 0, 0, 0, ENDIAN_BIG,
+            IPS_OPTIONS_NO_VAR, IPS_OPTIONS_NO_VAR, IPS_OPTIONS_NO_VAR);
+        ByteMathOption rhs(data_rhs);
+        CHECK(lhs != rhs);
+    }
+    SECTION("relative_flag is different")
+    {
+        ByteMathData data_rhs;
+        INITIALIZE(data_rhs, 0, 25, 0, 0, rhs_name, BM_PLUS, 1, 0, 0, ENDIAN_BIG,
+            IPS_OPTIONS_NO_VAR, IPS_OPTIONS_NO_VAR, IPS_OPTIONS_NO_VAR);
+        ByteMathOption rhs(data_rhs);
+        CHECK(lhs != rhs);
+    }
+    SECTION("string_convert_flag is different")
+    {
+        ByteMathData data_rhs;
+        INITIALIZE(data_rhs, 0, 25, 0, 0, rhs_name, BM_PLUS, 0, 1, 0, ENDIAN_BIG,
+            IPS_OPTIONS_NO_VAR, IPS_OPTIONS_NO_VAR, IPS_OPTIONS_NO_VAR);
+        ByteMathOption rhs(data_rhs);
+        CHECK(lhs != rhs);
+    }
+    SECTION("base is different")
+    {
+        ByteMathData data_rhs;
+        INITIALIZE(data_rhs, 0, 25, 0, 0, rhs_name, BM_PLUS, 0, 0, 8, ENDIAN_BIG,
+            IPS_OPTIONS_NO_VAR, IPS_OPTIONS_NO_VAR, IPS_OPTIONS_NO_VAR);
+        ByteMathOption rhs(data_rhs);
+        CHECK(lhs != rhs);
+    }
+    SECTION("endianness is different")
+    {
+        ByteMathData data_rhs;
+        INITIALIZE(data_rhs, 0, 25, 0, 0, rhs_name, BM_PLUS, 0, 0, 0, ENDIAN_LITTLE,
+            IPS_OPTIONS_NO_VAR, IPS_OPTIONS_NO_VAR, IPS_OPTIONS_NO_VAR);
+        ByteMathOption rhs(data_rhs);
+        CHECK(lhs != rhs);
+    }
+    SECTION("result_var is different")
+    {
+        ByteMathData data_rhs;
+        INITIALIZE(data_rhs, 0, 25, 0, 0, rhs_name, BM_PLUS, 0, 0, 0,  ENDIAN_BIG,
+            0, IPS_OPTIONS_NO_VAR, IPS_OPTIONS_NO_VAR);
+        ByteMathOption rhs(data_rhs);
+        CHECK(lhs != rhs);
+    }
+    SECTION("rvalue_var is different")
+    {
+        ByteMathData data_rhs;
+        INITIALIZE(data_rhs, 0, 25, 0, 0, rhs_name, BM_PLUS, 0, 0, 0, ENDIAN_BIG,
+            IPS_OPTIONS_NO_VAR, 1, IPS_OPTIONS_NO_VAR);
+        ByteMathOption rhs(data_rhs);
+        CHECK(lhs != rhs);
+    }
+    SECTION("offset_var is different")
+    {
+        ByteMathData data_rhs;
+        INITIALIZE(data_rhs, 0, 25, 0, 0, rhs_name, BM_PLUS, 0, 0,  0, ENDIAN_BIG,
+            IPS_OPTIONS_NO_VAR, IPS_OPTIONS_NO_VAR, 0);
+        ByteMathOption rhs(data_rhs);
+        CHECK(lhs != rhs);
+    }
+}
+
+TEST_CASE("ByteMathOption::hash", "[ips_byte_math]")
+{
+    SetBufferOptionHelper set_buf("test");
+
+    char* lhs_name = new char[5];
+    strcpy(lhs_name, "test");
+    ByteMathData data_lhs;
+    INITIALIZE(data_lhs, 2, 25, 2, 255, lhs_name, BM_MULTIPLY, 1, 1, 8,
+        ENDIAN_LITTLE, 1, 1, 1);
+    ByteMathOption lhs(data_lhs);
+
+    SECTION("hash codes of any two equal objects are equal")
+    {
+        char* rhs_name = new char[5];
+        strcpy(rhs_name, "test");
+        ByteMathData data_rhs;
+        INITIALIZE(data_rhs, 2, 25, 2, 255, rhs_name, BM_MULTIPLY, 1, 1, 8,
+            ENDIAN_LITTLE, 1, 1, 1);
+        ByteMathOption rhs(data_rhs);
+
+        CHECK(lhs.hash() == rhs.hash());
+    }
+}
+
+TEST_CASE("ByteMathOption::eval valid", "[ips_byte_math]")
+{
+    Packet p;
+    p.data = (const uint8_t*)"Lorem 12345";
+    p.dsize = 11;
+    Cursor c(&p);
+
+    for (unsigned i = 0; i < NUM_IPS_OPTIONS_VARS; ++i)
+    {
+        SetVarValueByIndex(0, i);
+    }
+    ClearIpsOptionsVars();
+
+    char* name = new char[5];
+    strcpy(name, "test");
+
+    SECTION("1 byte read, all off, operation \"+\", rvalue 1")
+    {
+        ByteMathData data;
+        INITIALIZE(data, 1, 1, 0, 0, name, BM_PLUS, 0, 0, 0,
+            ENDIAN_BIG, 0, IPS_OPTIONS_NO_VAR, IPS_OPTIONS_NO_VAR);
+        ByteMathOption opt(data);
+        CHECK(opt.eval(c, &p) == IpsOption::MATCH);
+        uint32_t res = 0;
+        GetVarValueByIndex(&res, 0);
+        CHECK(res == 77);
+    }
+    SECTION("1 byte read, offset 3, operation \"*\", rvalue 2")
+    {
+        ByteMathData data;
+        INITIALIZE(data, 1, 2, 3, 0, name, BM_MULTIPLY, 0, 0, 0,
+            ENDIAN_BIG, 0, IPS_OPTIONS_NO_VAR, IPS_OPTIONS_NO_VAR);
+        ByteMathOption opt(data);
+        CHECK(opt.eval(c, &p) == IpsOption::MATCH);
+        uint32_t res = 0;
+        GetVarValueByIndex(&res, 0);
+        CHECK(res == 202);
+    }
+    SECTION("1 byte read, offset 3, relative, cursor 3, operation \"-\", rvalue 3")
+    {
+        ByteMathData data;
+        INITIALIZE(data, 1, 3, 3, 0, name, BM_MINUS, 1, 0, 0,
+            ENDIAN_BIG, 0, IPS_OPTIONS_NO_VAR, IPS_OPTIONS_NO_VAR);
+        ByteMathOption opt(data);
+        c.set_pos(3);
+        CHECK(opt.eval(c, &p) == IpsOption::MATCH);
+        uint32_t res = 0;
+        GetVarValueByIndex(&res, 0);
+        CHECK(res == 46);
+    }
+    SECTION("cursor 3, 1 byte read, offset -3, relative, operation \"/\", rvalue 4")
+    {
+        ByteMathData data;
+        INITIALIZE(data, 1, 4, -3, 0, name, BM_DIVIDE, 1, 0, 0,
+            ENDIAN_BIG, 0, IPS_OPTIONS_NO_VAR, IPS_OPTIONS_NO_VAR);
+        ByteMathOption opt(data);
+        c.set_pos(3);
+        CHECK(opt.eval(c, &p) == IpsOption::MATCH);
+        uint32_t res = 0;
+        GetVarValueByIndex(&res, 0);
+        CHECK(res == 19);
+    }
+    SECTION("1 byte read, offset 6, string conversion, base 10, operation \"+\", rvalue 4")
+    {
+        ByteMathData data;
+        INITIALIZE(data, 1, 4, 6, 0, name, BM_PLUS, 0, 1, 10,
+            ENDIAN_BIG, 0, IPS_OPTIONS_NO_VAR, IPS_OPTIONS_NO_VAR);
+        ByteMathOption opt(data);
+        CHECK(opt.eval(c, &p) == IpsOption::MATCH);
+        uint32_t res = 0;
+        GetVarValueByIndex(&res, 0);
+        CHECK(res == 5);
+    }
+    SECTION("1 byte read, offset 6, string conversion, base 10, operation \"<<\", rvalue 2")
+    {
+        ByteMathData data;
+        INITIALIZE(data, 1, 2, 6, 0, name, BM_LEFT_SHIFT, 0, 1, 10,
+            ENDIAN_BIG, 0, IPS_OPTIONS_NO_VAR, IPS_OPTIONS_NO_VAR);
+        ByteMathOption opt(data);
+        CHECK(opt.eval(c, &p) == IpsOption::MATCH);
+        uint32_t res = 0;
+        GetVarValueByIndex(&res, 0);
+        CHECK(res == 4);
+    }
+    SECTION("3 byte read, offset 6, string conversion, base 10, operation \">>\", rvalue 1")
+    {
+        ByteMathData data;
+        INITIALIZE(data, 3, 1, 6, 0, name, BM_RIGHT_SHIFT, 0, 1, 10,
+            ENDIAN_BIG, 0, IPS_OPTIONS_NO_VAR, IPS_OPTIONS_NO_VAR);
+        ByteMathOption opt(data);
+        CHECK(opt.eval(c, &p) == IpsOption::MATCH);
+        uint32_t res = 0;
+        GetVarValueByIndex(&res, 0);
+        CHECK(res == 61);
+    }
+    SECTION("2 bytes read, operation \">>\", result_var = 0, rvalue_var = 1")
+    {
+        SetVarValueByIndex(3, 1);
+        ByteMathData data;
+        INITIALIZE(data, 2, 0, 0, 0, name, BM_RIGHT_SHIFT, 0,
+            0, 0, ENDIAN_BIG, 0, 1, IPS_OPTIONS_NO_VAR);
+        ByteMathOption opt(data);
+        CHECK(opt.eval(c, &p) == IpsOption::MATCH);
+        uint32_t res = 0;
+        GetVarValueByIndex(&res, 0);
+        CHECK(res == 2445);
+    }
+    SECTION("1 byte read, operation \"<<\", offset_var = 0, result_var = 1")
+    {
+        SetVarValueByIndex(1, 0);
+        ByteMathData data;
+        INITIALIZE(data, 1, 1, 0, 0, name, BM_LEFT_SHIFT,
+            0, 0, 0, ENDIAN_BIG, 1, IPS_OPTIONS_NO_VAR, 0);
+        ByteMathOption opt(data);
+        CHECK(opt.eval(c, &p) == IpsOption::MATCH);
+        uint32_t res = 0;
+        GetVarValueByIndex(&res, 1);
+        CHECK(res == 222);
+    }
+}
+
+TEST_CASE("ByteMathOption::eval invalid", "[ips_byte_math]")
+{
+    Packet p;
+    p.data = (const uint8_t*)"Lorem 9876";
+    p.dsize = 11;
+    Cursor c(&p);
+
+    for (unsigned i = 0; i < NUM_IPS_OPTIONS_VARS; ++i)
+    {
+        SetVarValueByIndex(0, i);
+    }
+    ClearIpsOptionsVars();
+
+    char* name = new char[5];
+    strcpy(name, "test");
+
+    SECTION("rvalue_variable didn't exist")
+    {
+        ByteMathData data;
+        INITIALIZE(data, 1, 1, 0, 0, name, BM_PLUS, 0,
+            0, 0, ENDIAN_BIG, 0, 1, IPS_OPTIONS_NO_VAR);
+        ByteMathOption opt(data);
+        CHECK(opt.eval(c, &p) == IpsOption::MATCH);
+        uint32_t res = 0;
+        GetVarValueByIndex(&res, 0);
+        CHECK(res == 76);
+    }
+    SECTION("offset_variable didn't exist")
+    {
+        ByteMathData data;
+        INITIALIZE(data, 1, 1, 1, 0, name, BM_PLUS, 0,
+            0, 0, ENDIAN_BIG, 0, IPS_OPTIONS_NO_VAR, 1);
+        ByteMathOption opt(data);
+        CHECK(opt.eval(c, &p) == IpsOption::MATCH);
+        uint32_t res = 0;
+        GetVarValueByIndex(&res, 0);
+        CHECK(res == 77);
+    }
+    SECTION("rvalue_variable_index > NUM_IPS_OPTIONS_VARS")
+    {
+        ByteMathData data;
+        INITIALIZE(data, 1, 1, 0, 0, name, BM_PLUS, 0, 0, 0, ENDIAN_BIG,
+            0, NUM_IPS_OPTIONS_VARS + 1, IPS_OPTIONS_NO_VAR);
+        ByteMathOption opt(data);
+        CHECK(opt.eval(c, &p) == IpsOption::MATCH);
+        uint32_t res = 0;
+        GetVarValueByIndex(&res, 0);
+        CHECK(res == 77);
+    }
+    SECTION("offset_variable_index > NUM_IPS_OPTIONS_VARS")
+    {
+        ByteMathData data;
+        INITIALIZE(data, 1, 1, 1, 0, name, BM_PLUS, 0, 0, 0, ENDIAN_BIG,
+            0, IPS_OPTIONS_NO_VAR, NUM_IPS_OPTIONS_VARS + 1);
+        ByteMathOption opt(data);
+        CHECK(opt.eval(c, &p) == IpsOption::MATCH);
+        uint32_t res = 0;
+        GetVarValueByIndex(&res, 0);
+        CHECK(res == 112);
+    }
+    SECTION("get negative number with MINUS")
+    {
+        ByteMathData data;
+        INITIALIZE(data, 1, 256, 0, 0, name, BM_MINUS, 0, 0, 0, ENDIAN_BIG,
+            0, IPS_OPTIONS_NO_VAR, IPS_OPTIONS_NO_VAR);
+        ByteMathOption opt(data);
+        CHECK(opt.eval(c, &p) == IpsOption::NO_MATCH);
+    }
+    SECTION("out of bounds of uint32_t, PLUS")
+    {
+        ByteMathData data;
+        INITIALIZE(data, 1, 4294967295, 0, 0, name, BM_PLUS, 0, 0, 0,
+            ENDIAN_BIG, 0, IPS_OPTIONS_NO_VAR, IPS_OPTIONS_NO_VAR);
+        ByteMathOption opt(data);
+        CHECK(opt.eval(c, &p) == IpsOption::NO_MATCH);
+    }
+    SECTION("out of bounds of uint32_t, MULTIPLY")
+    {
+        ByteMathData data;
+        INITIALIZE(data, 1, 2147483647, 0, 0, name, BM_MULTIPLY, 0, 0, 0,
+            ENDIAN_BIG, 0, IPS_OPTIONS_NO_VAR, IPS_OPTIONS_NO_VAR);
+        ByteMathOption opt(data);
+        CHECK(opt.eval(c, &p) == IpsOption::NO_MATCH);
+    }
+    SECTION("dividing on zero in rvalue_var")
+    {
+        SetVarValueByIndex(1, 0);
+        ByteMathData data;
+        INITIALIZE(data, 1, 0, 0, 0, name, BM_DIVIDE, 0,
+            0, 0, ENDIAN_BIG, 0, 1, IPS_OPTIONS_NO_VAR);
+        ByteMathOption opt(data);
+        CHECK(opt.eval(c, &p) == IpsOption::NO_MATCH);
+    }
+}
+
+//-------------------------------------------------------------------------
+// module tests
+//-------------------------------------------------------------------------
+
+TEST_CASE("ByteMathModule::begin", "[ips_byte_math]")
+{
+    ByteMathModule obj;
+    SECTION("test of \"begin\" method")
+    {
+        CHECK(obj.begin(nullptr, 0, nullptr));
+        ByteMathData expected;
+        INITIALIZE(expected, 0, 0, 0, 0, 0, BM_PLUS, 0, 0, 0, 0, 0, 0, 0);
+
+        CHECK_THAT(obj.data, ByteMathDataEquals(expected));
+    }
+}
+
+TEST_CASE("ByteMathModule::end", "[ips_byte_math]")
+{
+    ByteMathModule obj;
+
+    obj.begin(nullptr, 0, nullptr);
+
+    Value v_name("test");
+    Parameter p_name{"result", Parameter::PT_STRING, nullptr, nullptr,
+        "name of the variable to store the result"};
+    v_name.set(&p_name);
+    obj.set(nullptr, v_name, nullptr);
+
+    Value v_bytes(4.0);
+    Parameter p_bytes{"bytes", Parameter::PT_INT, "1:10", nullptr,
+        "number of bytes to pick up from the buffer"};
+    v_bytes.set(&p_bytes);
+    obj.set(nullptr, v_bytes, nullptr);
+
+    Value v_operation(0.0);
+    Parameter p_operation{"oper", Parameter::PT_ENUM, "+|-|*|/|<<|>>",
+        nullptr, "mathematical operation to perform"};
+    v_operation.set(&p_operation);
+    obj.set(nullptr, v_operation, nullptr);
+
+    char* name = new char[5];
+    strcpy(name, "test");
+
+    SECTION("without variables")
+    {
+        Value v_rvalue("7");
+        Parameter p_rvalue{"rvalue", Parameter::PT_STRING, nullptr, nullptr,
+            "value to use mathematical operation against"};
+        v_rvalue.set(&p_rvalue);
+        obj.set(nullptr, v_rvalue, nullptr);
+
+        CHECK(obj.end(nullptr, 0, nullptr));
+
+        ByteMathData expected;
+        INITIALIZE(expected, 4, 7, 0, 0, name, BM_PLUS, 0, 0, 0, ENDIAN_BIG,
+            0, IPS_OPTIONS_NO_VAR, IPS_OPTIONS_NO_VAR);
+
+        CHECK_THAT(obj.data, ByteMathDataEquals(expected));
+    }
+    SECTION("with rvalue var")
+    {
+        ClearIpsOptionsVars();
+        int8_t var_idx = AddVarNameToList("rvalue_test_var");
+        SetVarValueByIndex(3, var_idx);
+
+        Value v_rvalue("rvalue_test_var");
+        Parameter p_rvalue{"rvalue", Parameter::PT_STRING, nullptr, nullptr,
+            "value to use mathematical operation against"};
+        v_rvalue.set(&p_rvalue);
+        obj.set(nullptr, v_rvalue, nullptr);
+
+        CHECK(obj.end(nullptr, 0, nullptr));
+
+        ByteMathData expected;
+        INITIALIZE(expected, 4, 0, 0, 0, name, BM_PLUS, 0, 0, 0,
+            ENDIAN_BIG, 0, var_idx, IPS_OPTIONS_NO_VAR);
+        CHECK_THAT(obj.data, ByteMathDataEquals(expected));
+    }
+    SECTION("with offset variable")
+    {
+        ClearIpsOptionsVars();
+        int8_t var_idx = AddVarNameToList("offset_test_var");
+        SetVarValueByIndex(3, var_idx);
+
+        Value v_offvalue("offset_test_var");
+        Parameter p_offvalue{"offset", Parameter::PT_STRING, nullptr, nullptr,
+            "number of bytes into the buffer to start processing"};
+        v_offvalue.set(&p_offvalue);
+        obj.set(nullptr, v_offvalue, nullptr);
+
+        CHECK(obj.end(nullptr, 0, nullptr));
+
+        ByteMathData expected;
+        INITIALIZE(expected, 4, 0, 0, 0, name, BM_PLUS, 0, 0,
+            0, ENDIAN_BIG, 0, IPS_OPTIONS_NO_VAR, var_idx);
+        CHECK_THAT(obj.data, ByteMathDataEquals(expected));
+    }
+    SECTION("rvalue var doesn't exist")
+    {
+        ClearIpsOptionsVars();
+        Value v_rvalue("rvalue_test_var");
+        Parameter p_rvalue{"rvalue", Parameter::PT_STRING, nullptr, nullptr,
+            "value to use mathematical operation against"};
+        v_rvalue.set(&p_rvalue);
+        obj.set(nullptr, v_rvalue, nullptr);
+
+        CHECK(!(obj.end(nullptr, 0, nullptr)));
+    }
+    SECTION("offset var doesn't exist")
+    {
+        ClearIpsOptionsVars();
+        Value v_offvalue("offset_test_var");
+        Parameter p_offvalue{"offset", Parameter::PT_STRING, nullptr, nullptr,
+            "number of bytes into the buffer to start processing"};
+        v_offvalue.set(&p_offvalue);
+        obj.set(nullptr, v_offvalue, nullptr);
+
+        CHECK(!(obj.end(nullptr, 0, nullptr)));
+    }
+
+    delete[] obj.data.result_name;
+    delete[] name;
+}
+
+TEST_CASE("Test of byte_math_ctor", "[ips_byte_math]")
+{
+    ClearIpsOptionsVars();
+
+    std::string name = "test";
+    for (unsigned i = 0; i <= NUM_IPS_OPTIONS_VARS; ++i)
+    {
+        ByteMathModule obj;
+        obj.begin(nullptr, 0, nullptr);
+        Value v((name + std::to_string(i)).c_str());
+        Parameter p{"result", Parameter::PT_STRING, nullptr, nullptr,
+            "name of the variable to store the result"};
+        v.set(&p);
+        obj.set(nullptr, v, nullptr);
+        if (i < NUM_IPS_OPTIONS_VARS)
+        {
+            IpsOption* res = byte_math_ctor(&obj, nullptr);
+            delete res;
+        }
+        else
+        {
+            IpsOption* res_null = byte_math_ctor(&obj, nullptr);
+            CHECK(res_null == nullptr);
+            delete[] obj.data.result_name;
+        }
+    }
+}
+
+TEST_CASE("ByteMathModule::set valid", "[ips_byte_math]")
+{
+    ByteMathModule obj;
+    obj.begin(nullptr, 0, nullptr);
+
+    SECTION("set bytes")
+    {
+        Value v(4.0);
+        Parameter p{"bytes", Parameter::PT_INT, "1:10", nullptr,
+            "number of bytes to pick up from the buffer"};
+        v.set(&p);
+        ByteMathData expected;
+        INITIALIZE(expected, 4, 0, 0, 0, 0, BM_PLUS, 0, 0, 0, 0, 0, 0, 0);
+
+        CHECK(obj.set(nullptr, v, nullptr));
+        CHECK_THAT(obj.data, ByteMathDataEquals(expected));
+    }
+    SECTION("set offset")
+    {
+        Value v("3");
+        Parameter p{"offset", Parameter::PT_STRING, nullptr, nullptr,
+            "number of bytes into the buffer to start processing"};
+        v.set(&p);
+        ByteMathData expected;
+        INITIALIZE(expected, 0, 0, 3, 0, 0, BM_PLUS, 0, 0, 0, 0, 0, 0, 0);
+
+        CHECK(obj.set(nullptr, v, nullptr));
+        CHECK_THAT(obj.data, ByteMathDataEquals(expected));
+    }
+    SECTION("set option \"+\"")
+    {
+        Value v(0.0);
+        Parameter p{"oper", Parameter::PT_ENUM, "+|-|*|/|<<|>>", nullptr,
+            "mathematical operation to perform"};
+        v.set(&p);
+        ByteMathData expected;
+        INITIALIZE(expected, 0, 0, 0, 0, 0, BM_PLUS, 0, 0, 0, 0, 0, 0, 0);
+
+        CHECK(obj.set(nullptr, v, nullptr));
+        CHECK_THAT(obj.data, ByteMathDataEquals(expected));
+    }
+    SECTION("set option \"-\"")
+    {
+        Value v(1.0);
+        Parameter p{"oper", Parameter::PT_ENUM, "+|-|*|/|<<|>>", nullptr,
+            "mathematical operation to perform"};
+        v.set(&p);
+        ByteMathData expected;
+        INITIALIZE(expected, 0, 0, 0, 0, 0, BM_MINUS, 0, 0, 0, 0, 0, 0, 0);
+
+        CHECK(obj.set(nullptr, v, nullptr));
+        CHECK_THAT(obj.data, ByteMathDataEquals(expected));
+    }
+    SECTION("set option \"*\"")
+    {
+        Value v(2.0);
+        Parameter p{"oper", Parameter::PT_ENUM, "+|-|*|/|<<|>>", nullptr,
+            "mathematical operation to perform"};
+        v.set(&p);
+        ByteMathData expected;
+        INITIALIZE(expected, 0, 0, 0, 0, 0, BM_MULTIPLY, 0, 0, 0, 0, 0, 0, 0);
+
+        CHECK(obj.set(nullptr, v, nullptr));
+        CHECK_THAT(obj.data, ByteMathDataEquals(expected));
+    }
+    SECTION("set option \"/\"")
+    {
+        Value v(3.0);
+        Parameter p{"oper", Parameter::PT_ENUM, "+|-|*|/|<<|>>", nullptr,
+            "mathematical operation to perform"};
+        v.set(&p);
+        ByteMathData expected;
+        INITIALIZE(expected, 0, 0, 0, 0, 0, BM_DIVIDE, 0, 0, 0, 0, 0, 0, 0);
+
+        CHECK(obj.set(nullptr, v, nullptr));
+        CHECK_THAT(obj.data, ByteMathDataEquals(expected));
+    }
+    SECTION("set option \"<<\"")
+    {
+        Value v(4.0);
+        Parameter p{"oper", Parameter::PT_ENUM, "+|-|*|/|<<|>>", nullptr,
+            "mathematical operation to perform"};
+        v.set(&p);
+        ByteMathData expected;
+        INITIALIZE(expected, 0, 0, 0, 0, 0, BM_LEFT_SHIFT, 0, 0, 0, 0, 0, 0, 0);
+
+        CHECK(obj.set(nullptr, v, nullptr));
+        CHECK_THAT(obj.data, ByteMathDataEquals(expected));
+    }
+    SECTION("set option \">>\"")
+    {
+        Value v(5.0);
+        Parameter p{"oper", Parameter::PT_ENUM, "+|-|*|/|<<|>>", nullptr,
+            "mathematical operation to perform"};
+        v.set(&p);
+        ByteMathData expected;
+        INITIALIZE(expected, 0, 0, 0, 0, 0, BM_RIGHT_SHIFT, 0, 0, 0, 0, 0, 0, 0);
+
+        CHECK(obj.set(nullptr, v, nullptr));
+        CHECK_THAT(obj.data, ByteMathDataEquals(expected));
+    }
+    SECTION("set rvalue num")
+    {
+        Value v("21");
+        Parameter p{"rvalue", Parameter::PT_STRING, nullptr, nullptr,
+            "value to use mathematical operation against"};
+        v.set(&p);
+        ByteMathData expected;
+        INITIALIZE(expected, 0, 21, 0, 0, 0, BM_PLUS, 0, 0, 0, 0, 0, 0, 0);
+
+        CHECK(obj.set(nullptr, v, nullptr));
+        CHECK_THAT(obj.data, ByteMathDataEquals(expected));
+    }
+    SECTION("set result")
+    {
+        Value v("res_name");
+        Parameter p{"result", Parameter::PT_STRING, nullptr, nullptr,
+            "name of the variable to store the result"};
+        v.set(&p);
+
+        CHECK(obj.set(nullptr, v, nullptr));
+        CHECK_THAT(obj.data.result_name, Catch::Matchers::Equals("res_name"));
+    }
+    SECTION("set relative")
+    {
+        Value v(true);
+        Parameter p{"relative", Parameter::PT_IMPLIED, nullptr, nullptr,
+            "offset from cursor instead of start of buffer"};
+        v.set(&p);
+        obj.set(nullptr, v, nullptr);
+        ByteMathData expected;
+        INITIALIZE(expected, 0, 0, 0, 0, 0, BM_PLUS, 1, 0, 0, 0, 0, 0, 0);
+
+        CHECK(obj.set(nullptr, v, nullptr));
+        CHECK_THAT(obj.data, ByteMathDataEquals(expected));
+    }
+    SECTION("set endianness \"big\"")
+    {
+        Value v(0.0);
+        Parameter p{"endian", Parameter::PT_ENUM, "big|little", nullptr,
+            "specify big/little endian"};
+        v.set(&p);
+        obj.set(nullptr, v, nullptr);
+        ByteMathData expected;
+        INITIALIZE(expected, 0, 0, 0, 0, 0, BM_PLUS, 0, 0, 0, ENDIAN_BIG, 0, 0, 0);
+
+        CHECK(obj.set(nullptr, v, nullptr));
+        CHECK_THAT(obj.data, ByteMathDataEquals(expected));
+    }
+    SECTION("set endianness \"little\"")
+    {
+        Value v(1.0);
+        Parameter p{"endian", Parameter::PT_ENUM, "big|little", nullptr,
+            "specify big/little endian"};
+        v.set(&p);
+        ByteMathData expected;
+        INITIALIZE(expected, 0, 0, 0, 0, 0, BM_PLUS, 0, 0, 0, ENDIAN_LITTLE, 0, 0, 0);
+
+        CHECK(obj.set(nullptr, v, nullptr));
+        CHECK_THAT(obj.data, ByteMathDataEquals(expected));
+    }
+    SECTION("set dce")
+    {
+        Value v(true);
+        Parameter p{"dce", Parameter::PT_IMPLIED, nullptr, nullptr,
+            "dcerpc2 determines endianness"};
+        v.set(&p);
+        ByteMathData expected;
+        INITIALIZE(expected, 0, 0, 0, 0, 0, BM_PLUS, 0, 0, 0, ENDIAN_FUNC, 0, 0, 0);
+
+        CHECK(obj.set(nullptr, v, nullptr));
+        CHECK_THAT(obj.data, ByteMathDataEquals(expected));
+    }
+    SECTION("set string, hex base")
+    {
+        Value v(0.0);
+        Parameter p{"string", Parameter::PT_ENUM, "hex|dec|oct", nullptr,
+            "convert extracted string to dec/hex/oct"};
+        v.set(&p);
+        ByteMathData expected;
+        INITIALIZE(expected, 0, 0, 0, 0, 0, BM_PLUS, 0, 1, 16, 0, 0, 0, 0);
+
+        CHECK(obj.set(nullptr, v, nullptr));
+        CHECK_THAT(obj.data, ByteMathDataEquals(expected));
+    }
+    SECTION("set string, dec base")
+    {
+        Value v(1.0);
+        Parameter p{"string", Parameter::PT_ENUM, "hex|dec|oct", nullptr,
+            "convert extracted string to dec/hex/oct"};
+        v.set(&p);
+        ByteMathData expected;
+        INITIALIZE(expected, 0, 0, 0, 0, 0, BM_PLUS, 0, 1, 10, 0, 0, 0, 0);
+
+        CHECK(obj.set(nullptr, v, nullptr));
+        CHECK_THAT(obj.data, ByteMathDataEquals(expected));
+    }
+    SECTION("set string, oct base")
+    {
+        Value v(2.0);
+        Parameter p{"string", Parameter::PT_ENUM, "hex|dec|oct", nullptr,
+            "convert extracted string to dec/hex/oct"};
+        v.set(&p);
+        ByteMathData expected;
+        INITIALIZE(expected, 0, 0, 0, 0, 0, BM_PLUS, 0, 1, 8, 0, 0, 0, 0);
+
+        CHECK(obj.set(nullptr, v, nullptr));
+        CHECK_THAT(obj.data, ByteMathDataEquals(expected));
+    }
+    SECTION("set bitmask")
+    {
+        Value v(1023.0);
+        Parameter p{"bitmask", Parameter::PT_INT, "0x1:0xFFFFFFFF", nullptr,
+            "applies as bitwise AND to the extracted value before storage in 'name'"};
+        v.set(&p);
+        ByteMathData expected;
+        INITIALIZE(expected, 0, 0, 0, 1023, 0, BM_PLUS, 0, 0, 0, 0, 0, 0, 0);
+
+        CHECK(obj.set(nullptr, v, nullptr));
+        CHECK_THAT(obj.data, ByteMathDataEquals(expected));
+    }
+    SECTION("rvalue as variable")
+    {
+        Value v("r_test_var");
+        Parameter p{"rvalue", Parameter::PT_STRING, nullptr, nullptr,
+            "value to use mathematical operation against"};
+        v.set(&p);
+
+        CHECK(obj.set(nullptr, v, nullptr));
+        CHECK(strcmp(obj.rvalue_var.c_str(), "r_test_var") == 0);
+    }
+    SECTION("offset as variable")
+    {
+        Value v("off_test_var");
+        Parameter p{"offset", Parameter::PT_STRING, nullptr, nullptr,
+            "number of bytes into the buffer to start processing"};
+        v.set(&p);
+
+        CHECK(obj.set(nullptr, v, nullptr));
+        CHECK(strcmp(obj.off_var.c_str(), "off_test_var") == 0);
+    }
+
+    delete[] obj.data.result_name;
+}
+
+TEST_CASE("ByteMathModule::set invalid", "[ips_byte_math]")
+{
+    ByteMathModule obj;
+    obj.begin(nullptr, 0, nullptr);
+
+    SECTION("invalid parameter")
+    {
+        Value v(1023.0);
+        Parameter p{"error", Parameter::PT_INT, "nan", nullptr,
+            "not an option"};
+        v.set(&p);
+        ByteMathData expected;
+        INITIALIZE(expected, 0, 0, 0, 0, 0, BM_PLUS, 0, 0, 0, 0, 0, 0, 0);
+
+        CHECK(!obj.set(nullptr, v, nullptr));
+        CHECK_THAT(obj.data, ByteMathDataEquals(expected));
+    }
+    SECTION("rvalue = 0")
+    {
+        Value v("0");
+        Parameter p{"rvalue", Parameter::PT_STRING, nullptr, nullptr,
+            "value to use mathematical operation against"};
+        v.set(&p);
+        ByteMathData expected;
+        INITIALIZE(expected, 0, 0, 0, 0, 0, BM_PLUS, 0, 0, 0, 0, 0, 0, 0);
+
+        CHECK(!obj.set(nullptr, v, nullptr));
+        CHECK_THAT(obj.data, ByteMathDataEquals(expected));
+    }
+}
+//-------------------------------------------------------------------------
+// api tests
+//-------------------------------------------------------------------------
+
+TEST_CASE("ByteMathVerify valid", "[ips_byte_math]")
+{
+    ByteMathData obj;
+    char name[] = "test";
+
+    SECTION("Minimum values, no string conversion")
+    {
+        INITIALIZE(obj, 1, 0, -65535, 0, name, BM_PLUS, 1, 0, 0, 0, 0, 0, 0);
+        CHECK(ByteMathVerify(&obj));
+    }
+    SECTION("Maximum values, no string conversion")
+    {
+        INITIALIZE(obj, MAX_BYTES_TO_GRAB, 2147483647, 65535, 0xFFFFFFFF, name, BM_PLUS, 1, 0,
+            0, ENDIAN_FUNC, NUM_IPS_OPTIONS_VARS, NUM_IPS_OPTIONS_VARS, NUM_IPS_OPTIONS_VARS);
+        CHECK(ByteMathVerify(&obj));
+    }
+    SECTION("Minimum values, with string conversion")
+    {
+        INITIALIZE(obj, 1, 0, -65535, 0, name, BM_PLUS, 1, 1, 8, 0, 0, 0, 0);
+        CHECK(ByteMathVerify(&obj));
+    }
+    SECTION("Maximum values, with string conversion")
+    {
+        INITIALIZE(obj, PARSELEN, 2147483647, 65535, 0xFFFFFFFF, name, BM_PLUS, 1, 1, 16,
+            ENDIAN_FUNC, NUM_IPS_OPTIONS_VARS, NUM_IPS_OPTIONS_VARS, NUM_IPS_OPTIONS_VARS);
+        CHECK(ByteMathVerify(&obj));
+    }
+}
+
+TEST_CASE("ByteMathVerify invalid", "[ips_byte_math]")
+{
+    char* name = new char[5];
+    strcpy(name, "test");
+    ByteMathData obj;
+    INITIALIZE(obj, 1, 9, 25, 1023, name, BM_PLUS, 1, 0, 0, 0, 0, 0, 0);
+
+    SECTION("name existence check")
+    {
+        obj.result_name = nullptr;
+
+        CHECK((!ByteMathVerify(&obj)));
+    }
+    SECTION("name not numeric check")
+    {
+        delete[] name;
+        name = new char[5];
+        strcpy(name, "6in4");
+        obj.result_name = name;
+        CHECK((!ByteMathVerify(&obj)));
+    }
+    SECTION("shift > 32 checks")
+    {
+        obj.rvalue = 33;
+
+        obj.oper = BM_LEFT_SHIFT;
+        CHECK((!ByteMathVerify(&obj)));
+
+        obj.oper = BM_RIGHT_SHIFT;
+        CHECK((!ByteMathVerify(&obj)));
+    }
+    SECTION("shift and bytes_to_extract > 4  checks")
+    {
+        obj.bytes_to_extract = MAX_BYTES_TO_GRAB + 1;
+
+        obj.oper = BM_LEFT_SHIFT;
+        CHECK((!ByteMathVerify(&obj)));
+
+        obj.oper = BM_RIGHT_SHIFT;
+        CHECK((!ByteMathVerify(&obj)));
+    }
+    SECTION("no string conversion and bytes_to_extract > 4  checks")
+    {
+        obj.bytes_to_extract = MAX_BYTES_TO_GRAB + 1;
+        CHECK((!ByteMathVerify(&obj)));
+    }
+    SECTION("bitmask checks")
+    {
+        obj.bytes_to_extract = 2;
+        obj.bitmask_val = 1048575;
+        CHECK((!ByteMathVerify(&obj)));
+    }
+    delete[] name;
+}
+
+#endif
index 377d575b508c9fecbb4488c71ae581b0c348e126..f5a62255e2a28cbd87a346f78b1418d255834863 100644 (file)
 #include "protocols/packet.h"
 #include "utils/util.h"
 
+#ifdef UNIT_TEST
+#include <climits>
+#include "catch/snort_catch.h"
+#endif
+
 #include "extract.h"
 
 using namespace snort;
@@ -123,18 +128,11 @@ enum ByteTestOper
     CHECK_XOR
 };
 
-struct ByteTestData
+struct ByteTestData : public ByteData
 {
-    uint32_t bytes_to_compare;
     uint32_t cmp_value;
     ByteTestOper opcode;
-    int32_t offset;
     bool not_flag;
-    bool relative_flag;
-    bool data_string_convert_flag;
-    uint8_t endianness;
-    uint32_t base;
-    uint32_t bitmask_val;
     int8_t cmp_value_var;
     int8_t offset_var;
 };
@@ -143,42 +141,43 @@ struct ByteTestData
 // static functions
 // -----------------------------------------------------------------------------
 
-static inline bool byte_test_check(ByteTestOper op, uint32_t val, uint32_t cmp, bool not_flag)
+static inline bool byte_test_check(ByteTestOper op, uint32_t val, uint32_t cmp,
+    bool not_flag)
 {
     bool success = false;
 
     switch ( op )
     {
-    case CHECK_LT:
-        success = (val < cmp);
-        break;
-
     case CHECK_EQ:
         success = (val == cmp);
         break;
 
-    case CHECK_GT:
-        success = (val > cmp);
+    case CHECK_LT:
+        success = (val < cmp);
         break;
 
-    case CHECK_AND:
-        success = ((val & cmp) > 0);
+    case CHECK_GT:
+        success = (val > cmp);
         break;
 
-    case CHECK_XOR:
-        success = ((val ^ cmp) > 0);
+    case CHECK_LTE:
+        success = (val <= cmp);
         break;
 
     case CHECK_GTE:
         success = (val >= cmp);
         break;
 
-    case CHECK_LTE:
-        success = (val <= cmp);
+    case CHECK_AND:
+        success = ((val & cmp) > 0);
+        break;
+
+    case CHECK_XOR:
+        success = ((val ^ cmp) > 0);
         break;
     }
 
-    if ( not_flag )
+    if (not_flag)
     {
         success = !success;
     }
@@ -189,8 +188,9 @@ static inline bool byte_test_check(ByteTestOper op, uint32_t val, uint32_t cmp,
 class ByteTestOption : public IpsOption
 {
 public:
-    ByteTestOption(const ByteTestData& c) : IpsOption(s_name, RULE_OPTION_TYPE_BUFFER_USE)
-    { config = c; }
+    ByteTestOption(const ByteTestData& c) : IpsOption(s_name,
+        RULE_OPTION_TYPE_BUFFER_USE), config(c)
+    { }
 
     uint32_t hash() const override;
     bool operator==(const IpsOption&) const override;
@@ -210,7 +210,7 @@ private:
 
 uint32_t ByteTestOption::hash() const
 {
-    uint32_t a = config.bytes_to_compare;
+    uint32_t a = config.bytes_to_extract;
     uint32_t b = config.cmp_value;
     uint32_t c = config.opcode;
 
@@ -219,7 +219,7 @@ uint32_t ByteTestOption::hash() const
     a += config.offset;
     b += config.not_flag ? (1 << 24) : 0;
     b += config.relative_flag ? (1 << 16) : 0;
-    b += config.data_string_convert_flag ? (1 << 8) : 0;
+    b += config.string_convert_flag ? (1 << 8) : 0;
     b += config.endianness;
     c += config.base;
 
@@ -240,24 +240,24 @@ uint32_t ByteTestOption::hash() const
 
 bool ByteTestOption::operator==(const IpsOption& ips) const
 {
-    if ( !IpsOption::operator==(ips) )
+    if (!IpsOption::operator==(ips))
         return false;
 
     const ByteTestOption& rhs = (const ByteTestOption&)ips;
     const ByteTestData* left = &config;
     const ByteTestData* right = &rhs.config;
 
-    if (( left->bytes_to_compare == right->bytes_to_compare) &&
-        ( left->cmp_value == right->cmp_value) &&
-        ( left->opcode == right->opcode) &&
-        ( left->offset == right->offset) &&
-        ( left->not_flag == right->not_flag) &&
-        ( left->relative_flag == right->relative_flag) &&
-        ( left->data_string_convert_flag == right->data_string_convert_flag) &&
-        ( left->endianness == right->endianness) &&
-        ( left->base == right->base) &&
-        ( left->cmp_value_var == right->cmp_value_var) &&
-        ( left->offset_var == right->offset_var) &&
+    if (( left->bytes_to_extract == right->bytes_to_extract) and
+        ( left->cmp_value == right->cmp_value) and
+        ( left->opcode == right->opcode) and
+        ( left->offset == right->offset) and
+        ( left->not_flag == right->not_flag) and
+        ( left->relative_flag == right->relative_flag) and
+        ( left->string_convert_flag == right->string_convert_flag) and
+        ( left->endianness == right->endianness) and
+        ( left->base == right->base) and
+        ( left->cmp_value_var == right->cmp_value_var) and
+        ( left->offset_var == right->offset_var) and
         ( left->bitmask_val == right->bitmask_val))
     {
         return true;
@@ -274,7 +274,7 @@ IpsOption::EvalStatus ByteTestOption::eval(Cursor& c, Packet* p)
     uint32_t cmp_value = 0;
 
     // Get values from byte_extract variables, if present.
-    if (btd->cmp_value_var >= 0 && btd->cmp_value_var < NUM_IPS_OPTIONS_VARS)
+    if (btd->cmp_value_var >= 0 and btd->cmp_value_var < NUM_IPS_OPTIONS_VARS)
     {
         uint32_t val;
         GetVarValueByIndex(&val, btd->cmp_value_var);
@@ -285,7 +285,7 @@ IpsOption::EvalStatus ByteTestOption::eval(Cursor& c, Packet* p)
 
     int offset = 0;
 
-    if (btd->offset_var >= 0 && btd->offset_var < NUM_IPS_OPTIONS_VARS)
+    if (btd->offset_var >= 0 and btd->offset_var < NUM_IPS_OPTIONS_VARS)
     {
         uint32_t val;
         GetVarValueByIndex(&val, btd->offset_var);
@@ -294,53 +294,21 @@ IpsOption::EvalStatus ByteTestOption::eval(Cursor& c, Packet* p)
     else
         offset = btd->offset;
 
-    const uint8_t* start_ptr = btd->relative_flag ? c.start() : c.buffer();
-    start_ptr += offset;
+    unsigned len = btd->relative_flag ? c.length() : c.size();
+    if (len > btd->bytes_to_extract)
+            len = btd->bytes_to_extract;
 
-    uint8_t endian = btd->endianness;
-    if (endian == ENDIAN_FUNC)
-    {
-        if (!p->endianness ||
-            !p->endianness->get_offset_endianness(start_ptr - p->data, endian))
-            return NO_MATCH;
-    }
+    ByteTestData extract_config = *btd;
+    extract_config.bytes_to_extract = len;
+    extract_config.offset = offset;
 
     uint32_t value = 0;
+    int32_t payload_bytes_grabbed = extract_data(extract_config, c, p, value);
 
-    if (!btd->data_string_convert_flag)
-    {
-        if ( byte_extract(
-            endian, btd->bytes_to_compare,
-            start_ptr, c.buffer(), c.endo(), &value))
-            return NO_MATCH;
-    }
-    else
-    {
-        unsigned len = btd->relative_flag ? c.length() : c.size();
-
-        if ( len > btd->bytes_to_compare )
-            len = btd->bytes_to_compare;
+    if (payload_bytes_grabbed == NO_MATCH)
+        return NO_MATCH;
 
-        int payload_bytes_grabbed = string_extract(
-            len, btd->base, start_ptr, c.buffer(), c.endo(), &value);
-
-        if ( payload_bytes_grabbed < 0 )
-        {
-            return NO_MATCH;
-        }
-    }
-
-    if (btd->bitmask_val != 0 )
-    {
-        uint32_t num_tailing_zeros_bitmask = getNumberTailingZerosInBitmask(btd->bitmask_val);
-        value = value & btd->bitmask_val;
-        if ( value && num_tailing_zeros_bitmask )
-        {
-            value = value >> num_tailing_zeros_bitmask;
-        }
-    }
-
-    if ( byte_test_check(btd->opcode, value, cmp_value, btd->not_flag) )
+    if (byte_test_check(btd->opcode, value, cmp_value, btd->not_flag))
         return MATCH;
 
     return NO_MATCH;
@@ -360,7 +328,7 @@ static void parse_operator(const char* oper, ByteTestData& idx)
         cptr++;
     }
 
-    if (idx.not_flag && strlen(cptr) == 0)
+    if (idx.not_flag and strlen(cptr) == 0)
     {
         idx.opcode = CHECK_EQ;
     }
@@ -426,7 +394,7 @@ static const Parameter s_params[] =
       "variable name or value to test the converted result against" },
 
     { "~offset", Parameter::PT_STRING, nullptr, nullptr,
-      "variable name or number of bytes into the payload to start processing" },
+      "variable name or number of bytes into the payload to start processing"},
 
     { "relative", Parameter::PT_IMPLIED, nullptr, nullptr,
       "offset from cursor instead of start of buffer" },
@@ -492,7 +460,7 @@ bool ByteTestModule::begin(const char*, int, SnortConfig*)
 
 bool ByteTestModule::end(const char*, int, SnortConfig*)
 {
-    if ( off_var.empty() )
+    if (off_var.empty())
         data.offset_var = IPS_OPTIONS_NO_VAR;
     else
     {
@@ -504,7 +472,7 @@ bool ByteTestModule::end(const char*, int, SnortConfig*)
             return false;
         }
     }
-    if ( cmp_var.empty() )
+    if (cmp_var.empty())
         data.cmp_value_var = IPS_OPTIONS_NO_VAR;
     else
     {
@@ -516,12 +484,13 @@ bool ByteTestModule::end(const char*, int, SnortConfig*)
             return false;
         }
     }
-    if ( !data.endianness )
+    if (!data.endianness)
         data.endianness = ENDIAN_BIG;
 
-    if (numBytesInBitmask(data.bitmask_val) > data.bytes_to_compare)
+    if (numBytesInBitmask(data.bitmask_val) > data.bytes_to_extract)
     {
-        ParseError("Number of bytes in \"bitmask\" value is greater than bytes to extract.");
+        ParseError("Number of bytes in \"bitmask\" value is greater " \
+            "than bytes to extract.");
         return false;
     }
 
@@ -530,55 +499,55 @@ bool ByteTestModule::end(const char*, int, SnortConfig*)
 
 bool ByteTestModule::set(const char*, Value& v, SnortConfig*)
 {
-    if ( v.is("~count") )
-        data.bytes_to_compare = v.get_uint8();
+    if (v.is("~count"))
+        data.bytes_to_extract = v.get_uint8();
 
-    else if ( v.is("~operator") )
+    else if (v.is("~operator"))
         parse_operator(v.get_string(), data);
 
-    else if ( v.is("~compare") )
+    else if (v.is("~compare"))
     {
         long n;
-        if ( v.strtol(n) )
+        if (v.strtol(n))
             data.cmp_value = n;
         else
             cmp_var = v.get_string();
     }
-    else if ( v.is("~offset") )
+    else if (v.is("~offset"))
     {
         long n;
-        if ( v.strtol(n) )
+        if (v.strtol(n))
             data.offset = n;
         else
             off_var = v.get_string();
     }
-    else if ( v.is("relative") )
+    else if (v.is("relative"))
         data.relative_flag = true;
 
-    else if ( v.is("big") )
+    else if (v.is("big"))
         set_byte_order(data.endianness, ENDIAN_BIG, "byte_test");
 
-    else if ( v.is("little") )
+    else if (v.is("little"))
         set_byte_order(data.endianness, ENDIAN_LITTLE, "byte_test");
 
-    else if ( v.is("dce") )
+    else if (v.is("dce"))
         set_byte_order(data.endianness, ENDIAN_FUNC, "byte_test");
 
-    else if ( v.is("string") )
+    else if (v.is("string"))
     {
-        data.data_string_convert_flag = true;
+        data.string_convert_flag = true;
         data.base = 10;
     }
-    else if ( v.is("dec") )
+    else if (v.is("dec"))
         data.base = 10;
 
-    else if ( v.is("hex") )
+    else if (v.is("hex"))
         data.base = 16;
 
-    else if ( v.is("oct") )
+    else if (v.is("oct"))
         data.base = 8;
 
-    else if ( v.is("bitmask") )
+    else if (v.is("bitmask"))
         data.bitmask_val = v.get_uint32();
 
     else
@@ -647,3 +616,496 @@ const BaseApi* ips_byte_test[] =
     nullptr
 };
 
+//-------------------------------------------------------------------------
+// UNIT TESTS
+//-------------------------------------------------------------------------
+#ifdef UNIT_TEST
+#include <climits>
+
+#include "catch/snort_catch.h"
+
+#define NO_MATCH snort::IpsOption::EvalStatus::NO_MATCH
+#define MATCH snort::IpsOption::EvalStatus::MATCH
+
+void SetByteTestData(ByteTestData &byte_test, int value, ByteTestOper code = CHECK_EQ)
+{
+    byte_test.bytes_to_extract = value;
+    byte_test.cmp_value = value;
+    byte_test.opcode = code;
+    byte_test.offset = value;
+    byte_test.not_flag = value;
+    byte_test.relative_flag = value;
+    byte_test.string_convert_flag = value;
+    byte_test.endianness = value;
+    byte_test.base = value;
+    byte_test.bitmask_val = value;
+    byte_test.cmp_value_var = value;
+    byte_test.offset_var = value;
+}
+
+void SetByteTestDataMax(ByteTestData& byte_test)
+{
+    byte_test.bytes_to_extract = UINT_MAX;
+    byte_test.cmp_value = UINT_MAX;
+    byte_test.opcode = CHECK_XOR;
+    byte_test.offset = INT_MAX;
+    byte_test.not_flag = true;
+    byte_test.relative_flag = true;
+    byte_test.string_convert_flag = true;
+    byte_test.endianness = UCHAR_MAX;
+    byte_test.base = UINT_MAX;
+    byte_test.bitmask_val = UINT_MAX;
+    byte_test.cmp_value_var = CHAR_MAX;
+    byte_test.offset_var = CHAR_MAX;
+}
+
+class StubIpsOption : public IpsOption
+{
+public:
+    StubIpsOption(const char* name, option_type_t option_type) :
+        IpsOption(name, option_type)
+    { };
+
+};
+
+class StubEndianness : public Endianness
+{
+public:
+    StubEndianness() = default;
+    virtual bool get_offset_endianness(int32_t, uint8_t& ) override
+    { return false; }
+};
+
+TEST_CASE("byte_test_check test", "[ips_byte_test]")
+{
+    SECTION("Incorrect ByteTestOper, other data correct")
+    {
+        REQUIRE(byte_test_check(ByteTestOper(7), 1, 1, 0) == false);
+    }
+
+    SECTION("Incorrect ByteTestOper, true not_flag")
+    {
+        REQUIRE(byte_test_check(ByteTestOper(7), 1, 1, 1) == true);
+    }
+
+    SECTION("CHECK_EQ both true && false situation")
+    {
+        REQUIRE(byte_test_check(ByteTestOper(0), 1, 1, 0) == true);
+        REQUIRE(byte_test_check(ByteTestOper(0), 1, 2, 0) == false);
+    }
+
+    SECTION("CHECK_LT both true && false situation")
+    {
+        REQUIRE(byte_test_check(ByteTestOper(1), 1, 2, 0) == true);
+        REQUIRE(byte_test_check(ByteTestOper(1), 4, 1, 0) == false);
+    }
+
+    SECTION("CHECK_GT both true && false situation")
+    {
+        REQUIRE(byte_test_check(ByteTestOper(2), 2, 1, 0) == true);
+        REQUIRE(byte_test_check(ByteTestOper(2), 1, 4, 0) == false);
+    }
+
+    SECTION("CHECK_LTE both true && false situation")
+    {
+        REQUIRE(byte_test_check(ByteTestOper(3), 0, 1, 0) == true);
+        REQUIRE(byte_test_check(ByteTestOper(3), 4, 1, 0) == false);
+    }
+
+    SECTION("CHECK_GTE both true && false situation")
+    {
+        REQUIRE(byte_test_check(ByteTestOper(4), 1, 0, 0) == true);
+        REQUIRE(byte_test_check(ByteTestOper(4), 0, 4, 0) == false);
+    }
+
+    SECTION("CHECK_AND for bites both true && false situation")
+    {
+        REQUIRE(byte_test_check(ByteTestOper(5), 1, 1, 0) == true);
+        REQUIRE(byte_test_check(ByteTestOper(5), 1, 0, 0) == false);
+    }
+
+    SECTION("CHECK_XOR for bites both true && false situation")
+    {
+        REQUIRE(byte_test_check(ByteTestOper(6), 1, 0, 0) == true);
+        REQUIRE(byte_test_check(ByteTestOper(6), 1, 1, 0) == false);
+    }
+}
+
+TEST_CASE("ByteTestOption test", "[ips_byte_test]")
+{
+    ByteTestData byte_test;
+    SetByteTestData(byte_test, 1);
+    snort::IpsOption::set_buffer("hello_world");
+
+    SECTION("method hash")
+    {
+        ByteTestOption hash_test(byte_test);
+        ByteTestOption hash_test_equal(byte_test);
+
+        SECTION("Testing hash with very low values")
+        {
+            SECTION("Hash has same source")
+            {
+                CHECK(hash_test.hash() == hash_test_equal.hash());
+            }
+
+            SECTION("Compare hash from different source")
+            {
+                SetByteTestData(byte_test, 4);
+                ByteTestOption hash_test_diff(byte_test);
+                CHECK(hash_test.hash() != hash_test_diff.hash());
+            }
+        }
+
+        SECTION("Testing hash with maximum values")
+        {
+            SetByteTestDataMax(byte_test);
+            ByteTestOption hash_test_max(byte_test);
+            ByteTestOption hash_test_equal_max(byte_test);
+
+            SECTION("Hash has same source")
+            {
+                CHECK(hash_test_max.hash() == hash_test_equal_max.hash());
+            }
+
+            SECTION("Testing hash with maximum values from different source")
+            {
+                SetByteTestDataMax(byte_test);
+                ByteTestOption hash_test_max(byte_test);
+                CHECK(hash_test.hash() != hash_test_max.hash());
+            }
+        }
+    }
+
+    SECTION("operator ==")
+    {
+        ByteTestOption test(byte_test);
+
+        SECTION("Compare IpsOptions with different names")
+        {
+            StubIpsOption case_diff_name("not_hello_world",
+                option_type_t::RULE_OPTION_TYPE_BUFFER_USE);
+            REQUIRE(test != case_diff_name);
+        }
+
+        SECTION("Compare between equals objects")
+        {
+            ByteTestOption test_1(byte_test);
+            REQUIRE(test == test_1);
+        }
+
+        SECTION("byte_to_compare is different")
+        {
+            byte_test.bytes_to_extract = 2;
+            ByteTestOption test_2_1(byte_test);
+            REQUIRE(test != test_2_1);
+        }
+
+        SECTION("cmp_value is different")
+        {
+            byte_test.cmp_value = 2;
+            ByteTestOption test_2_2(byte_test);
+            REQUIRE(test != test_2_2);
+        }
+
+        SECTION("cmp_value is different")
+        {
+            byte_test.opcode = CHECK_LT;
+            ByteTestOption test_2_3(byte_test);
+            REQUIRE(test != test_2_3);
+        }
+
+        SECTION("offset is different")
+        {
+            byte_test.offset = 2;
+            ByteTestOption test_2_4(byte_test);
+            REQUIRE(test != test_2_4);
+        }
+
+        SECTION("not_flag is different")
+        {
+            byte_test.not_flag = 0;
+            ByteTestOption test_2_5(byte_test);
+            REQUIRE(test != test_2_5);
+        }
+
+        SECTION("relative_flag is different")
+        {
+            byte_test.relative_flag = 0;
+            ByteTestOption test_2_6(byte_test);
+            REQUIRE(test != test_2_6);
+        }
+
+        SECTION("string_convert_flag is different")
+        {
+            byte_test.string_convert_flag = 0;
+            ByteTestOption test_2_7(byte_test);
+            REQUIRE(test != test_2_7);
+        }
+
+        SECTION("endianness is different")
+        {
+            byte_test.endianness = 0;
+            ByteTestOption test_2_8(byte_test);
+            REQUIRE(test != test_2_8);
+        }
+
+        SECTION("base is different")
+        {
+            byte_test.base = 2;
+            ByteTestOption test_2_9(byte_test);
+            REQUIRE(test != test_2_9);
+        }
+
+        SECTION("bitmask_val is different")
+        {
+            byte_test.bitmask_val = 2;
+            ByteTestOption test_2_10(byte_test);
+            REQUIRE(test != test_2_10);
+        }
+
+        SECTION("cmp_value_var is different")
+        {
+            byte_test.cmp_value_var = 0;
+            ByteTestOption test_2_13(byte_test);
+            REQUIRE(test != test_2_13);
+        }
+
+        SECTION("cmp_value_var is different")
+        {
+            byte_test.offset_var = 0;
+            ByteTestOption test_2_12(byte_test);
+            REQUIRE(test != test_2_12);
+        }
+    }
+
+    SECTION("method eval")
+    {
+        Packet test_packet;
+        Cursor current_cursor;
+        SetByteTestData(byte_test, 1);
+
+        SECTION("Cursor not set correct for byte_extract")
+        {
+            byte_test.cmp_value_var = 3;
+            byte_test.offset_var = 3;
+            byte_test.string_convert_flag = 0;
+            ByteTestOption test_2(byte_test);
+            REQUIRE((test_2.eval(current_cursor, &test_packet)) == NO_MATCH);
+        }
+
+        SECTION("Byte_to_compare set to zero for string_extract")
+        {
+            byte_test.string_convert_flag = 1;
+            byte_test.bytes_to_extract = 0;
+            ByteTestOption test_3(byte_test);
+            uint8_t buff = 0;
+            current_cursor.set("hello_world_long_name", &buff, 50);
+            REQUIRE((test_3.eval(current_cursor, &test_packet)) == NO_MATCH);
+        }
+
+        SECTION("Byte_test_check with extract value not equal to need one")
+        {
+            byte_test.string_convert_flag = 0;
+            byte_test.relative_flag = 0;
+            uint8_t buff = 0;
+            current_cursor.set("hello_world_long_name", &buff, 50);
+            ByteTestOption test_4(byte_test);
+            REQUIRE((test_4.eval(current_cursor, &test_packet)) == NO_MATCH);
+        }
+
+        SECTION("Correct match")
+        {
+            byte_test.string_convert_flag = 0;
+            byte_test.relative_flag = 0;
+            byte_test.opcode = ByteTestOper(7);
+            byte_test.not_flag = 1;
+            uint8_t buff = 0;
+            current_cursor.set("hello_world_long_name", &buff, 50);
+            ByteTestOption test_5(byte_test);
+            REQUIRE((test_5.eval(current_cursor, &test_packet)) == MATCH);
+        }
+    }
+
+}
+
+TEST_CASE("ByteTestModule test", "[ips_byte_test]")
+{
+    ByteTestModule module_test;
+    ByteTestData byte_test;
+    SetByteTestData(byte_test, 1);
+
+    SECTION("method end")
+    {
+        std::string buff = "tmp";
+
+        SECTION("Undefined rule option for var")
+        {
+            module_test.cmp_var = buff;
+            module_test.data = byte_test;
+            REQUIRE(module_test.end("tmp", 0, nullptr) == false);
+        }
+
+        SECTION("Undefined rule option for offset_var")
+        {
+            module_test.cmp_var.clear();
+            module_test.off_var = buff;
+            module_test.data = byte_test;
+            REQUIRE(module_test.end("tmp", 0, nullptr) == false);
+        }
+
+        SECTION("Number of bytes in \"bitmask\" value is greater than bytes to extract")
+        {
+            byte_test.endianness = 0;
+            byte_test.bytes_to_extract = 0;
+            module_test.data = byte_test;
+            REQUIRE(module_test.end("tmp", 0, nullptr) == false);
+        }
+
+        SECTION("Case with returned value true")
+        {
+            module_test.data = byte_test;
+            REQUIRE(module_test.end("tmp", 0, nullptr) == true);
+        }
+    }
+
+    SECTION("method set")
+    {
+        Value value(false);
+
+        SECTION("All params incorrect")
+        {
+            REQUIRE(module_test.set(nullptr, value, nullptr) == false);
+        }
+
+        SECTION("Case param \"~count\"")
+        {
+            Parameter param("~count", snort::Parameter::Type::PT_BOOL,
+                nullptr, "default", "help");
+            value.set(&param);
+            REQUIRE(module_test.set(nullptr, value, nullptr) == true);
+        }
+
+        SECTION("Param \"~operator\" correct")
+        {
+            Parameter param("~operator", snort::Parameter::Type::PT_BOOL,
+                nullptr, "default", "help");
+            value.set(&param);
+            REQUIRE(module_test.set(nullptr, value, nullptr) == true);
+        }
+
+        SECTION("Case param \"~compare\"")
+        {
+            SECTION("Value doesn't have a str")
+            {
+                Parameter param("~compare", snort::Parameter::Type::PT_BOOL,
+                    nullptr, "default", "help");
+                value.set(&param);
+                REQUIRE(module_test.set(nullptr, value, nullptr) == true);
+            }
+
+            SECTION("When value has a str")
+            {
+                Value value_tmp("123");
+                Parameter param("~compare", snort::Parameter::Type::PT_BOOL,
+                    nullptr, "default", "help");
+                value_tmp.set(&param);
+                REQUIRE(module_test.set(nullptr, value_tmp, nullptr) == true);
+            }
+        }
+
+        SECTION("Case param \"~offset\"")
+        {
+            SECTION("Value doesn't have a str")
+            {
+                Parameter param("~offset", snort::Parameter::Type::PT_BOOL,
+                    nullptr, "default", "help");
+                value.set(&param);
+                REQUIRE(module_test.set(nullptr, value, nullptr) == true);
+            }
+
+            SECTION("When value has a str")
+            {
+                Value value_tmp("123");
+                Parameter param("~offset", snort::Parameter::Type::PT_BOOL,
+                    nullptr, "default", "help");
+                value_tmp.set(&param);
+                REQUIRE(module_test.set(nullptr, value_tmp, nullptr) == true);
+            }
+        }
+
+        SECTION("Case param \"relative\"")
+        {
+            Parameter param("relative", snort::Parameter::Type::PT_BOOL,
+                nullptr, "default", "help");
+            value.set(&param);
+            REQUIRE(module_test.set(nullptr, value, nullptr) == true);
+        }
+
+        SECTION("Case param \"big\"")
+        {
+            Parameter param("big", snort::Parameter::Type::PT_BOOL,
+                nullptr, "default", "help");
+            value.set(&param);
+            REQUIRE(module_test.set(nullptr, value, nullptr) == true);
+        }
+
+        SECTION("Case param \"little\"")
+        {
+            Parameter param("little", snort::Parameter::Type::PT_BOOL,
+                nullptr, "default", "help");
+            value.set(&param);
+            REQUIRE(module_test.set(nullptr, value, nullptr) == true);
+        }
+
+        SECTION("Case param \"dce\"")
+        {
+            Parameter param("dce", snort::Parameter::Type::PT_BOOL,
+                nullptr, "default", "help");
+            value.set(&param);
+            REQUIRE(module_test.set(nullptr, value, nullptr) == true);
+        }
+
+        SECTION("Case param \"string\"")
+        {
+            Parameter param("string", snort::Parameter::Type::PT_BOOL,
+                nullptr, "default", "help");
+            value.set(&param);
+            REQUIRE(module_test.set(nullptr, value, nullptr) == true);
+        }
+
+        SECTION("Case param \"dec\"")
+        {
+            Parameter param("dec", snort::Parameter::Type::PT_BOOL,
+                nullptr, "default", "help");
+            value.set(&param);
+            REQUIRE(module_test.set(nullptr, value, nullptr) == true);
+        }
+
+        SECTION("Case param \"hex\"")
+        {
+            Parameter param("hex", snort::Parameter::Type::PT_BOOL,
+                nullptr, "default", "help");
+            value.set(&param);
+            REQUIRE(module_test.set(nullptr, value, nullptr) == true);
+        }
+
+        SECTION("Case param \"oct\"")
+        {
+            Parameter param("oct", snort::Parameter::Type::PT_BOOL,
+                nullptr, "default", "help");
+            value.set(&param);
+            REQUIRE(module_test.set(nullptr, value, nullptr) == true);
+        }
+
+        SECTION("Case param \"bitmask\"")
+        {
+            Parameter param("bitmask", snort::Parameter::Type::PT_BOOL,
+                nullptr, "default", "help");
+            value.set(&param);
+            REQUIRE(module_test.set(nullptr, value, nullptr) == true);
+        }
+    }
+}
+
+#endif