]> git.ipfire.org Git - thirdparty/snort3.git/commitdiff
Pull request #4872: s7comm: added stream splitter abort checks
authorOleksandr Stepanov -X (ostepano - SOFTSERVE INC at Cisco) <ostepano@cisco.com>
Tue, 9 Sep 2025 01:47:59 +0000 (01:47 +0000)
committerChris Sherwin (chsherwi) <chsherwi@cisco.com>
Tue, 9 Sep 2025 01:47:59 +0000 (01:47 +0000)
Merge in SNORT/snort3 from ~OSTEPANO/snort3:s7_splitter to master

Squashed commit of the following:

commit 9b5693da71faf7dc68d1ef55f219ede6f4c54128
Author: Oleksandr Stepanov <ostepano@cisco.com>
Date:   Thu Aug 14 05:14:39 2025 -0400

    s7comm: added stream splitter abort checks

src/service_inspectors/s7commplus/CMakeLists.txt
src/service_inspectors/s7commplus/s7comm_decode.cc
src/service_inspectors/s7commplus/s7comm_decode.h
src/service_inspectors/s7commplus/s7comm_paf.cc
src/service_inspectors/s7commplus/s7comm_paf.h
src/service_inspectors/s7commplus/test/CMakeLists.txt [new file with mode: 0644]
src/service_inspectors/s7commplus/test/s7comm_paf_test.cc [new file with mode: 0644]

index 6280158d1b72d09e19deddb1a39d27b1aca6fad9..961a7b7ba6eafbf5a6b9f79a622a363bb27bff57 100644 (file)
@@ -1,3 +1,5 @@
+add_subdirectory(test)
+
 set( FILE_LIST
     s7comm.cc
     s7comm.h
index 13f91c7d78be40dc3b5f63f040bd7dbcf9bd5645..0b6fc15d73a181cfcfdb782357c1d2eea4039ea1 100644 (file)
@@ -116,7 +116,7 @@ bool S7commplusDecode(Packet* p, S7commplusFlowData* mfd)
     const S7commplusHeader* s7commplus_header;
     uint16_t tpkt_length;
 
-    if (p->dsize < TPKT_MIN_HDR_LEN)
+    if (p->dsize < TPKT_MIN_PACKET_LEN)
         return false;
 
     tpkt_header = (const TpktHeader*)p->data;
@@ -124,11 +124,11 @@ bool S7commplusDecode(Packet* p, S7commplusFlowData* mfd)
     tpkt_length = ntohs(tpkt_header->length);
 
     /* It might be a TPKT/COTP packet for other purpose, e.g. connect */
-    if (cotp_header->length != COTP_HDR_LEN_FOR_S7COMMPLUS||
-        cotp_header->pdu_type != COTP_HDR_PDU_TYPE_DATA)
+    if (cotp_header->length != COTP_MIN_PACKET_LEN ||
+        (cotp_header->pdu_type >> 4) != COTP_DATA_TRANSFER_TPDU)
         return true;
     /* It might be COTP fragment data */
-    if (tpkt_length == TPKT_MIN_HDR_LEN)
+    if (tpkt_length == TPKT_MIN_PACKET_LEN)
     {
         mfd->reset();
         return true;
index 9c798328a52d1bf9596a94350154e69804f27a7a..c86ffc65c6cea12d853a3c6bf7b95693efaac1ad 100644 (file)
@@ -29,26 +29,30 @@ struct Packet;
 
 class S7commplusFlowData;
 
+/* TPKT defines */
+#define TPKT_SUPPORTED_VERSION 0x03
+#define TPKT_MIN_PACKET_LEN 7
+
+/* COTP defines */
+#define COTP_MIN_PACKET_LEN 2
+#define COTP_CONNECTION_REQUEST_TPDU 0x0E
+#define COTP_DATA_TRANSFER_TPDU 0x0F
+
 /* S7comm defines */
 #define S7COMMPLUS_PDUTYPE_CONNECT                 0x01
 #define S7COMMPLUS_PDUTYPE_DATA                    0x02
 #define S7COMMPLUS_PDUTYPE_DATAFW1_5               0x03
 #define S7COMMPLUS_PDUTYPE_KEEPALIVE               0xFF
 
-#define COTP_HDR_LEN_FOR_S7COMMPLUS 2
-#define COTP_HDR_PDU_TYPE_DATA  0xF0
-
 #define S7COMM_PROTOCOL_ID      0x32
-#define S7COMMPLUS_PROTOCOL_ID 0x72
+#define S7COMMPLUS_PROTOCOL_ID  0x72
 
-#define TPKT_MIN_HDR_LEN 7     /* length field in TPKT header for S7comm */
-#define TPKT_MAX_HDR_LEN       /* Undecided */
 #define S7COMMPLUS_MIN_HDR_LEN 4
 #define HDR_VERSION_TWO 0x02
 #define INTEGRITY_PART_LEN 33 /* length of Integrity part in V3 Header packets */
 
 /* Need 8 bytes for MBAP Header + Function Code */
-#define S7COMMPLUS_MIN_LEN 8       this value needs to be decided
+#define S7COMMPLUS_MIN_LEN 8       // this value needs to be decided
 
 /* GIDs, SIDs, and Strings */
 #define GENERATOR_SPP_S7COMMPLUS 149   /* matches generators.h */
index b620a4b901928d31e5bfdaa694fd8d24a93fc4f7..ab9eeba31e0f2a24e341e752349854eb133f8ab9 100644 (file)
@@ -34,7 +34,8 @@
 
 using namespace snort;
 
-#define S7COMMPLUS_MIN_HDR_LEN 4        // Enough for Unit ID + Function
+
+static std::vector<uint8_t> cotp_invalid_codes { 0x00, 0x03, 0x09, 0x0A };
 
 S7commplusSplitter::S7commplusSplitter(bool b) : StreamSplitter(b)
 {
@@ -44,54 +45,169 @@ S7commplusSplitter::S7commplusSplitter(bool b) : StreamSplitter(b)
 
 // S7comm/TCP PAF:
 // Statefully inspects S7comm traffic from the start of a session,
-// Reads up until the length octet is found, then sets a flush point.
+// Processes network packets byte-by-byte to validate and parse encapsulation protocol headers.
+// Sets flush point at successful find of S7COMMPLUS protocol ID inside TPKT and COTP encapsulation.
 
 StreamSplitter::Status S7commplusSplitter::scan(
     Packet*, const uint8_t* data, uint32_t len, uint32_t /*flags*/, uint32_t* fp)
 {
     uint32_t bytes_processed = 0;
+    uint8_t* processed_data = const_cast<uint8_t*>(data);
 
     /* Process this packet 1 byte at a time */
     while (bytes_processed < len)
     {
         switch (state)
         {
-        /* Skip the Transaction & Protocol IDs */
         case S7COMMPLUS_PAF_STATE__TPKT_VER:
-        case S7COMMPLUS_PAF_STATE__TPKT_RESERVED:
-        case S7COMMPLUS_PAF_STATE__COTP_LEN:
-        case S7COMMPLUS_PAF_STATE__COTP_PDU_TYPE:
-            state = (s7commplus_paf_state_t)(((int)state) + 1);     //Set the state to next PAF
-                                                                    // state
+        {
+            uint8_t tpkt_version = *processed_data;
+            if ( tpkt_version != TPKT_SUPPORTED_VERSION)
+            {
+                reset_state();
+                return StreamSplitter::ABORT;
+            }
+
+            ++state;
             break;
+        }
+        case S7COMMPLUS_PAF_STATE__TPKT_RESERVED:
+        {
+            uint8_t tpkt_reserved_bytes = *processed_data;
+            if (tpkt_reserved_bytes != 0)
+            {
+                reset_state();
+                return StreamSplitter::ABORT;
+            }
 
-        /* Read length 1 byte at a time, in case a TCP segment is sent
-         * with xxx bytes from the S7CPAP header */
+            ++state;
+            break;
+        }
         case S7COMMPLUS_PAF_STATE__TPKT_LEN_1:
-            tpkt_length |= ( *(data + bytes_processed) << 8 );
-            state = S7COMMPLUS_PAF_STATE__TPKT_LEN_2;
+        {
+            tpkt_length = *(processed_data) << 8;
+            ++state;
             break;
-
+        }
         case S7COMMPLUS_PAF_STATE__TPKT_LEN_2:
-            tpkt_length |= *(data + bytes_processed);
-            state = S7COMMPLUS_PAF_STATE__COTP_LEN;
+        {
+            tpkt_length |= *(processed_data);
+            if (tpkt_length < TPKT_MIN_PACKET_LEN)
+            {
+                reset_state();
+                return StreamSplitter::ABORT;
+            }
+
+            ++state;
+            break;
+        }
+        case S7COMMPLUS_PAF_STATE__COTP_LEN:
+        {
+            uint8_t cotp_length = *(processed_data);
+            if (cotp_length < COTP_MIN_PACKET_LEN)
+            {
+                reset_state();
+                return StreamSplitter::ABORT;
+            }
+            
+            ++state;
+            break;
+        }
+        case S7COMMPLUS_PAF_STATE__COTP_PDU_TYPE:
+        {
+            uint8_t cotp_tpdu_and_flags = *(processed_data);
+            uint8_t cotp_tpdu = cotp_tpdu_and_flags >> 4;
+            uint8_t flags = cotp_tpdu_and_flags & 0x0F; // get lower 4 bits
+
+            if ( (std::any_of(cotp_invalid_codes.begin(), cotp_invalid_codes.end(),
+                [&cotp_tpdu](uint8_t code) { return cotp_tpdu == code; })) or flags)
+            {
+                reset_state();
+                return StreamSplitter::ABORT;
+            }
+
+            if (cotp_tpdu == COTP_CONNECTION_REQUEST_TPDU)
+            {
+                state = S7COMMPLUS_PAF_STATE__COTP_CR_DST_REF_1;
+            }
+            else if ( cotp_tpdu == COTP_DATA_TRANSFER_TPDU )
+            {
+                state = S7COMMPLUS_PAF_STATE__COTP_DT_TPDU_NUM_EOT;
+            }
+            else
+            {
+                *fp = tpkt_length;
+                reset_state();
+                return StreamSplitter::FLUSH;
+            }
+
             break;
+        }
+        case S7COMMPLUS_PAF_STATE__COTP_CR_DST_REF_1:
+        case S7COMMPLUS_PAF_STATE__COTP_CR_DST_REF_2:
+        case S7COMMPLUS_PAF_STATE__COTP_CR_SRC_REF_1:
+        case S7COMMPLUS_PAF_STATE__COTP_CR_SRC_REF_2:
+            ++state;
+            break;
+        
+        case S7COMMPLUS_PAF_STATE__COTP_CR_CLASS_OPTIONS:
+        {
+            uint8_t cotp_cr_class_options = *processed_data;
 
-        case S7COMMPLUS_PAF_STATE__SET_FLUSH:
-            if ((tpkt_length < TPKT_MIN_HDR_LEN))
+            if (!cotp_cr_class_options or (cotp_cr_class_options == 0x10)) // Class 0 or 1
             {
-                DetectionEngine::queue_event(GID_S7COMMPLUS, S7COMMPLUS_BAD_LENGTH);
+                *fp = tpkt_length;
+                reset_state();
+                return StreamSplitter::FLUSH;
+            }
+            else
+            {
+                reset_state();
+                return StreamSplitter::ABORT;
+            }
+        }
+        case S7COMMPLUS_PAF_STATE__COTP_DT_TPDU_NUM_EOT:
+        {
+            auto dst_ref_last_packet = *processed_data;
+            if (dst_ref_last_packet & 0x80) // 1st bit indicates that data is finished
+                ++state;
+            else
+            {
+                //COTP fragment, flush and wait for data
+                *fp = tpkt_length;
+                reset_state();
+                return StreamSplitter::FLUSH;
+            }
+            break;
+        }
+        case S7COMMPLUS_PAF_STATE__S7_PROTOCOL_ID:
+        {
+            uint8_t s7_protocol_id = *processed_data;
+            if (s7_protocol_id != S7COMMPLUS_PROTOCOL_ID)
+            {
+                reset_state();
+                return StreamSplitter::ABORT;
             }
 
-            *fp = tpkt_length;      // flush point at the end of payload
-            state = S7COMMPLUS_PAF_STATE__TPKT_VER;
-            tpkt_length = 0;
+            *fp = tpkt_length;
+            reset_state();
             return StreamSplitter::FLUSH;
         }
+        default:
+            assert(false);
+            reset_state();
+            return StreamSplitter::ABORT;
+        }
 
         bytes_processed++;
+        processed_data++;
     }
 
     return StreamSplitter::SEARCH;
 }
 
+void S7commplusSplitter::reset_state()
+{
+    tpkt_length = 0;
+    state = S7COMMPLUS_PAF_STATE__TPKT_VER;
+}
index 349467c9b5153eb3da3ab37a7f0e11e6bef08847..a38c17450fb601d6fb999d5a1006922a07651b1e 100644 (file)
@@ -26,7 +26,7 @@
 
 #include "stream/stream_splitter.h"
 
-enum s7commplus_paf_state_t
+enum s7commplus_paf_state_t : uint8_t
 {
     S7COMMPLUS_PAF_STATE__TPKT_VER = 0,
     S7COMMPLUS_PAF_STATE__TPKT_RESERVED,
@@ -34,9 +34,29 @@ enum s7commplus_paf_state_t
     S7COMMPLUS_PAF_STATE__TPKT_LEN_2,
     S7COMMPLUS_PAF_STATE__COTP_LEN,
     S7COMMPLUS_PAF_STATE__COTP_PDU_TYPE,
-    S7COMMPLUS_PAF_STATE__SET_FLUSH
+    S7COMMPLUS_PAF_STATE__COTP_CR_DST_REF_1,
+    S7COMMPLUS_PAF_STATE__COTP_CR_DST_REF_2,
+    S7COMMPLUS_PAF_STATE__COTP_CR_SRC_REF_1,
+    S7COMMPLUS_PAF_STATE__COTP_CR_SRC_REF_2,
+    S7COMMPLUS_PAF_STATE__COTP_CR_CLASS_OPTIONS,
+    S7COMMPLUS_PAF_STATE__COTP_DT_TPDU_NUM_EOT,
+    S7COMMPLUS_PAF_STATE__S7_PROTOCOL_ID,
+    S7COMMPLUS_PAF_STATE__MAX
 };
 
+inline s7commplus_paf_state_t& operator++(s7commplus_paf_state_t& state)
+{
+    if(state >= S7COMMPLUS_PAF_STATE__MAX)
+    {
+        state = S7COMMPLUS_PAF_STATE__MAX;
+    }
+    else
+    {
+        state = static_cast<s7commplus_paf_state_t>(static_cast<uint8_t>(state) + 1);
+    }
+    return state;
+}
+
 class S7commplusSplitter : public snort::StreamSplitter
 {
 public:
@@ -48,6 +68,9 @@ public:
     bool is_paf() override { return true; }
 
 private:
+
+    void reset_state();
+
     s7commplus_paf_state_t state;
     uint16_t tpkt_length;
 };
diff --git a/src/service_inspectors/s7commplus/test/CMakeLists.txt b/src/service_inspectors/s7commplus/test/CMakeLists.txt
new file mode 100644 (file)
index 0000000..d5b9e61
--- /dev/null
@@ -0,0 +1,3 @@
+add_cpputest( s7comm_paf_test
+    SOURCES ../s7comm_paf.cc
+)
\ No newline at end of file
diff --git a/src/service_inspectors/s7commplus/test/s7comm_paf_test.cc b/src/service_inspectors/s7commplus/test/s7comm_paf_test.cc
new file mode 100644 (file)
index 0000000..03d1b29
--- /dev/null
@@ -0,0 +1,280 @@
+//--------------------------------------------------------------------------
+// Copyright (C) 2022-2025 Cisco and/or its affiliates. All rights reserved.
+//
+// This program is free software; you can redistribute it and/or modify it
+// under the terms of the GNU General Public License Version 2 as published
+// by the Free Software Foundation.  You may not use, modify or distribute
+// this program under any other version of the GNU General Public License.
+//
+// This program is distributed in the hope that it will be useful, but
+// WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+// General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along
+// with this program; if not, write to the Free Software Foundation, Inc.,
+// 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+//--------------------------------------------------------------------------
+//
+// s7comm_paf_test.cc author Oleksandr Stepanov <ostepano@cisco.com>
+
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "../s7comm_paf.h"
+#include "protocols/packet.h"
+
+#include <CppUTest/CommandLineTestRunner.h>
+#include <CppUTest/TestHarness.h>
+#include <CppUTestExt/MockSupport.h>
+
+namespace snort
+{
+Packet::Packet(bool) { }
+Packet::~Packet() = default;
+const StreamBuffer StreamSplitter::reassemble(Flow*, unsigned int, unsigned int,
+    unsigned char const*, unsigned int, unsigned int, unsigned int &) { return {}; }
+unsigned StreamSplitter::max(snort::Flow*) { return 0; }
+}
+
+
+S7commplusSplitter* test_splitter = nullptr;
+snort::Packet mock_packet(true);
+
+TEST_GROUP(s7commplus_stream_splitter_tests)
+{
+    void setup() override
+    {
+        test_splitter = new S7commplusSplitter(true);
+    }
+    void teardown() override
+    {
+        delete test_splitter;
+    }
+};
+
+TEST(s7commplus_stream_splitter_tests, test_splitter_is_paf)
+{
+    CHECK_TRUE(test_splitter->is_paf());
+}
+
+TEST(s7commplus_stream_splitter_tests, splitter_search_on_not_enough_bytes)
+{
+    const uint8_t* test_data = (uint8_t*)"\x03";
+    uint32_t test_data_len = 1;
+    uint32_t flush_point = 0;
+    auto result = test_splitter->scan(&mock_packet, test_data, test_data_len, 0, &flush_point);
+
+    CHECK_EQUAL(result, snort::StreamSplitter::SEARCH);
+}
+
+TEST(s7commplus_stream_splitter_tests, splitter_abort_on_incorrect_tpkt_version)
+{
+    const uint8_t* test_data = (uint8_t*)"\x01";
+    uint32_t test_data_len = 1;
+    uint32_t flush_point = 0;
+    auto result = test_splitter->scan(&mock_packet, test_data, test_data_len, 0, &flush_point);
+
+    CHECK_EQUAL(result, snort::StreamSplitter::ABORT);
+}
+
+TEST(s7commplus_stream_splitter_tests, splitter_abort_on_incorrect_reserved_bytes)
+{
+    const uint8_t* test_data = (uint8_t*)"\x03\xff\xff";
+    uint32_t test_data_len = 3;
+    uint32_t flush_point = 0;
+    auto result = test_splitter->scan(&mock_packet, test_data, test_data_len, 0, &flush_point);
+
+    CHECK_EQUAL(result, snort::StreamSplitter::ABORT);
+}
+
+TEST(s7commplus_stream_splitter_tests, splitter_abort_on_incorrect_tpkt_length)
+{
+    const uint8_t* test_data = (uint8_t*)"\x03\x00\x00\x00\x00";
+    uint32_t test_data_len = 5;
+    uint32_t flush_point = 0;
+    auto result = test_splitter->scan(&mock_packet, test_data, test_data_len, 0, &flush_point);
+
+    CHECK_EQUAL(result, snort::StreamSplitter::ABORT);
+}
+
+TEST(s7commplus_stream_splitter_tests, splitter_abort_on_incorrect_cotp_length)
+{
+    const uint8_t* test_data = (uint8_t*)"\x03\x00\x00\x10\x01";
+    uint32_t test_data_len = 5;
+    uint32_t flush_point = 0;
+    auto result = test_splitter->scan(&mock_packet, test_data, test_data_len, 0, &flush_point);
+
+    CHECK_EQUAL(result, snort::StreamSplitter::ABORT);
+}
+
+TEST(s7commplus_stream_splitter_tests, splitter_abort_on_incorrect_cotp_pdu_type)
+{
+    uint8_t* test_data = (uint8_t*)"\x03\x00\x00\x10\x02\x00";
+    uint32_t test_data_len = 6;
+    uint32_t flush_point = 0;
+    auto result = test_splitter->scan(&mock_packet, test_data, test_data_len, 0, &flush_point);
+
+    CHECK_EQUAL(result, snort::StreamSplitter::ABORT);
+
+    test_data = (uint8_t*)"\x03\x00\x00\x10\x02\x03";
+    result = test_splitter->scan(&mock_packet, test_data, test_data_len, 0, &flush_point);
+
+    CHECK_EQUAL(result, snort::StreamSplitter::ABORT);
+
+    test_data = (uint8_t*)"\x03\x00\x00\x10\x02\x09";
+    result = test_splitter->scan(&mock_packet, test_data, test_data_len, 0, &flush_point);
+
+    CHECK_EQUAL(result, snort::StreamSplitter::ABORT);
+
+    test_data = (uint8_t*)"\x03\x00\x00\x10\x02\x0A";
+    result = test_splitter->scan(&mock_packet, test_data, test_data_len, 0, &flush_point);
+
+    CHECK_EQUAL(result, snort::StreamSplitter::ABORT);
+}
+
+TEST(s7commplus_stream_splitter_tests, splitter_abort_on_cotp_cr_class_2_or_higher)
+{
+    uint8_t* test_data = (uint8_t*)"\x03\x00\x00\x10\x02\x0e\x00\x00\x01\x01\x20";
+    uint32_t test_data_len = 11;
+    uint32_t flush_point = 0;
+    auto result = test_splitter->scan(&mock_packet, test_data, test_data_len, 0, &flush_point);
+
+    CHECK_EQUAL(result, snort::StreamSplitter::ABORT);
+
+    test_data = (uint8_t*)"\x03\x00\x00\x10\x02\x0e\x00\x00\x01\x01\x40";
+    result = test_splitter->scan(&mock_packet, test_data, test_data_len, 0, &flush_point);
+
+    CHECK_EQUAL(result, snort::StreamSplitter::ABORT);
+}
+
+TEST(s7commplus_stream_splitter_tests, splitter_flush_on_cotp_cr_class_0_or_1)
+{
+    uint8_t* test_data = (uint8_t*)"\x03\x00\x00\x24\x06\xe0\x00\x00\x01\x01\x00";
+    uint32_t test_data_len = 11;
+    uint32_t flush_point = 0;
+    auto result = test_splitter->scan(&mock_packet, test_data, test_data_len, 0, &flush_point);
+
+    CHECK_EQUAL(result, snort::StreamSplitter::FLUSH);
+    CHECK_EQUAL(flush_point, 36);
+
+    test_data = (uint8_t*)"\x03\x00\x00\x24\x06\xe0\x00\x00\x01\x01\x10";
+    result = test_splitter->scan(&mock_packet, test_data, test_data_len, 0, &flush_point);
+
+    CHECK_EQUAL(result, snort::StreamSplitter::FLUSH);
+    CHECK_EQUAL(flush_point, 36);
+}
+
+TEST(s7commplus_stream_splitter_tests, splitter_abort_on_abnormal_class_options)
+{
+    uint8_t* test_data = (uint8_t*)"\x03\x00\x00\x24\x06\xe0\x00\x00\x01\x01\x01";
+    uint32_t test_data_len = 11;
+    uint32_t flush_point = 0;
+    auto result = test_splitter->scan(&mock_packet, test_data, test_data_len, 0, &flush_point);
+
+    CHECK_EQUAL(result, snort::StreamSplitter::ABORT);
+}
+
+TEST(s7commplus_stream_splitter_tests, splitter_flush_on_data_transfer_correct_s7_protocol)
+{
+    uint8_t* test_data = (uint8_t*)"\x03\x00\x00\x24\x06\xf0\x80\x72";
+    uint32_t test_data_len = 8;
+    uint32_t flush_point = 0;
+    auto result = test_splitter->scan(&mock_packet, test_data, test_data_len, 0, &flush_point);
+
+    CHECK_EQUAL(result, snort::StreamSplitter::FLUSH);
+    CHECK_EQUAL(flush_point, 36);
+}
+
+TEST(s7commplus_stream_splitter_tests, splitter_search_and_flush_correct_s7_protocol)
+{
+    uint8_t* test_data = (uint8_t*)"\x03\x00";
+    uint32_t test_data_len = 2;
+    uint32_t flush_point = 0;
+    auto result = test_splitter->scan(&mock_packet, test_data, test_data_len, 0, &flush_point);
+
+    CHECK_EQUAL(result, snort::StreamSplitter::SEARCH);
+
+    test_data = (uint8_t*)"\x00\x24";
+
+    result = test_splitter->scan(&mock_packet, test_data, test_data_len, 0, &flush_point);
+
+    CHECK_EQUAL(result, snort::StreamSplitter::SEARCH);
+
+
+    test_data = (uint8_t*)"\x06\xf0\x80\x72";
+    test_data_len = 4;
+
+    result = test_splitter->scan(&mock_packet, test_data, test_data_len, 0, &flush_point);
+
+    CHECK_EQUAL(result, snort::StreamSplitter::FLUSH);
+    CHECK_EQUAL(flush_point, 36);
+}
+
+TEST(s7commplus_stream_splitter_tests, splitter_flush_on_cotp_fragment)
+{
+    uint8_t* test_data = (uint8_t*)"\x03\x00\x00\x24\x06\xf0\x00";
+    uint32_t test_data_len = 7;
+    uint32_t flush_point = 0;
+    auto result = test_splitter->scan(&mock_packet, test_data, test_data_len, 0, &flush_point);
+
+    CHECK_EQUAL(result, snort::StreamSplitter::FLUSH);
+    CHECK_EQUAL(flush_point, 36);
+}
+
+TEST(s7commplus_stream_splitter_tests, splitter_abort_on_data_transfer_incorrect_s7_protocol)
+{
+    uint8_t* test_data = (uint8_t*)"\x03\x00\x00\x24\x06\xf0\x80\x34";
+    uint32_t test_data_len = 8;
+    uint32_t flush_point = 0;
+    auto result = test_splitter->scan(&mock_packet, test_data, test_data_len, 0, &flush_point);
+
+    CHECK_EQUAL(result, snort::StreamSplitter::ABORT);
+}
+
+TEST_GROUP(s7commplus_misc)
+{
+
+};
+
+TEST(s7commplus_misc, verify_s7commplus_paf_state)
+{
+    s7commplus_paf_state_t test_state = (s7commplus_paf_state_t)0;
+    CHECK_EQUAL(s7commplus_paf_state_t::S7COMMPLUS_PAF_STATE__TPKT_VER, test_state);
+    ++test_state;
+    CHECK_EQUAL(s7commplus_paf_state_t::S7COMMPLUS_PAF_STATE__TPKT_RESERVED, test_state);
+    ++test_state;
+    CHECK_EQUAL(s7commplus_paf_state_t::S7COMMPLUS_PAF_STATE__TPKT_LEN_1, test_state);
+    ++test_state;
+    CHECK_EQUAL(s7commplus_paf_state_t::S7COMMPLUS_PAF_STATE__TPKT_LEN_2, test_state);
+    ++test_state;
+    CHECK_EQUAL(s7commplus_paf_state_t::S7COMMPLUS_PAF_STATE__COTP_LEN, test_state);
+    ++test_state;
+    CHECK_EQUAL(s7commplus_paf_state_t::S7COMMPLUS_PAF_STATE__COTP_PDU_TYPE, test_state);
+    ++test_state;
+    CHECK_EQUAL(s7commplus_paf_state_t::S7COMMPLUS_PAF_STATE__COTP_CR_DST_REF_1, test_state);
+    ++test_state;
+    CHECK_EQUAL(s7commplus_paf_state_t::S7COMMPLUS_PAF_STATE__COTP_CR_DST_REF_2, test_state);
+    ++test_state;
+    CHECK_EQUAL(s7commplus_paf_state_t::S7COMMPLUS_PAF_STATE__COTP_CR_SRC_REF_1, test_state);
+    ++test_state;
+    CHECK_EQUAL(s7commplus_paf_state_t::S7COMMPLUS_PAF_STATE__COTP_CR_SRC_REF_2, test_state);
+    ++test_state;
+    CHECK_EQUAL(s7commplus_paf_state_t::S7COMMPLUS_PAF_STATE__COTP_CR_CLASS_OPTIONS, test_state);
+    ++test_state;
+    CHECK_EQUAL(s7commplus_paf_state_t::S7COMMPLUS_PAF_STATE__COTP_DT_TPDU_NUM_EOT, test_state);
+    ++test_state;
+    CHECK_EQUAL(s7commplus_paf_state_t::S7COMMPLUS_PAF_STATE__S7_PROTOCOL_ID, test_state);
+    ++test_state;
+    CHECK_EQUAL(s7commplus_paf_state_t::S7COMMPLUS_PAF_STATE__MAX, test_state);
+    ++test_state;
+    CHECK_EQUAL(s7commplus_paf_state_t::S7COMMPLUS_PAF_STATE__MAX, test_state);
+}
+
+int main(int argc, char** argv)
+{
+    int rc = CommandLineTestRunner::RunAllTests(argc, argv);
+    return rc;
+}
\ No newline at end of file