From: Russ Combs (rucombs) Date: Fri, 4 Oct 2024 15:55:59 +0000 (+0000) Subject: Pull request #4465: tcp_pdu: new inspector for simple length based flushing X-Git-Tag: 3.4.0.0~10 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=0b41addfe76a8381d847a8cc5d044ebb08fa9fdf;p=thirdparty%2Fsnort3.git Pull request #4465: tcp_pdu: new inspector for simple length based flushing Merge in SNORT/snort3 from ~RUCOMBS/snort3:tcp_pdu to master Squashed commit of the following: commit 58b1bc69c07c863d04c446207eb317d99ed1e7fd Author: Russ Combs Date: Mon Sep 16 14:06:01 2024 -0400 tcp_pdu: new inspector for simple length based flushing tcp_pdu provides a generic protocol-aware-flushing capability for PDUs that contain a length field. The field may be at a configurable offset from the start of the PDU, it has a configurable length, and may specify the total length of the PDU or the number of bytes following the length field. --- diff --git a/src/service_inspectors/CMakeLists.txt b/src/service_inspectors/CMakeLists.txt index fa902c170..55aafe036 100644 --- a/src/service_inspectors/CMakeLists.txt +++ b/src/service_inspectors/CMakeLists.txt @@ -20,6 +20,7 @@ add_subdirectory(sip) add_subdirectory(smtp) add_subdirectory(ssh) add_subdirectory(ssl) +add_subdirectory(tcp_pdu) add_subdirectory(wizard) if (STATIC_INSPECTORS) @@ -41,6 +42,7 @@ if (STATIC_INSPECTORS) $ $ $ + $ $ ) endif() diff --git a/src/service_inspectors/service_inspectors.cc b/src/service_inspectors/service_inspectors.cc index 86a5b01db..d790acca3 100644 --- a/src/service_inspectors/service_inspectors.cc +++ b/src/service_inspectors/service_inspectors.cc @@ -43,6 +43,7 @@ extern const BaseApi* sin_pop; extern const BaseApi* sin_rpc_decode; extern const BaseApi* sin_smtp; extern const BaseApi* sin_ssh; +extern const BaseApi* sin_tcp_pdu; extern const BaseApi* sin_telnet; extern const BaseApi* sin_wizard; @@ -71,6 +72,7 @@ const BaseApi* service_inspectors[] = sin_rpc_decode, sin_smtp, sin_ssh, + sin_tcp_pdu, sin_telnet, sin_wizard, #endif diff --git a/src/service_inspectors/tcp_pdu/CMakeLists.txt b/src/service_inspectors/tcp_pdu/CMakeLists.txt new file mode 100644 index 000000000..453b05311 --- /dev/null +++ b/src/service_inspectors/tcp_pdu/CMakeLists.txt @@ -0,0 +1,17 @@ + +set( FILE_LIST + tcp_pdu.cc + tcp_pdu.h + tcp_pdu_splitter.cc +) + +if (STATIC_INSPECTORS) + add_library( tcp_pdu OBJECT ${FILE_LIST}) + +else (STATIC_INSPECTORS) + add_dynamic_module(tcp_pdu inspectors ${FILE_LIST}) + +endif (STATIC_INSPECTORS) + +add_subdirectory(test) + diff --git a/src/service_inspectors/tcp_pdu/dev_notes.txt b/src/service_inspectors/tcp_pdu/dev_notes.txt new file mode 100644 index 000000000..c6a9a2e69 --- /dev/null +++ b/src/service_inspectors/tcp_pdu/dev_notes.txt @@ -0,0 +1,49 @@ + +The TcpPdu splitter provides a generic TCP stream flush function to support +IPS. This works for PDUs that contain a length field at a fixed offset that +can be extracted and used to set a flush point. + +The general format supported looks like this: + + = | | ... + ::=
[] +
::= [][] + +Where: + +* [ X ] indicates that X is optional and | is the flush point. + +* refers to one side of a flow. + +* All s in both directions have the same header structure as defined by + the configuration. + +* is assumed to be in network byte order. + +So a PDU with a 4 byte length field in the middle of a 12 byte header would be +configured with offset = size = skip = 4. + +tcp_pdu is not service specific. An appropriate wizard pattern must direct the +paylaod to a tcp_pdu instance configured for the flow. + +The initial implementation supports these parameters: + +* int tcp_pdu.offset = 0: index to first byte of length field { 0:65535 } +* int tcp_pdu.size = 4: number of bytes in length field { 1:4 } +* int tcp_pdu.skip = 0: bytes after length field to end of header { 0:65535 } +* bool tcp_pdu.relative = false: extracted length follows field (instead of whole PDU) + +Additional parameters that may be supported in the future if required: + +* int tcp_pdu.bitmask = 0xFFFFFFFF: applies as an AND to the extracted value to get length { 0x1:0xFFFFFFFF } +* int tcp_pdu.multiplier = 1: scale extracted value by given amount after masking { 1:65535 } + +Still other possibilities: + +* bool tcp_pdu.big = false: big endian +* bool tcp_pdu.little = false: little endian +* bool tcp_pdu.string = false: convert from string +* bool tcp_pdu.hex = false: convert from hex string +* bool tcp_pdu.oct = false: convert from octal string +* bool tcp_pdu.dec = false: convert from decimal string + diff --git a/src/service_inspectors/tcp_pdu/tcp_pdu.cc b/src/service_inspectors/tcp_pdu/tcp_pdu.cc new file mode 100644 index 000000000..f70d91dbc --- /dev/null +++ b/src/service_inspectors/tcp_pdu/tcp_pdu.cc @@ -0,0 +1,196 @@ +//-------------------------------------------------------------------------- +// Copyright (C) 2024-2024 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. +//-------------------------------------------------------------------------- + +// tcp_pdu.cc author Russ Combs + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "framework/decode_data.h" +#include "framework/inspector.h" +#include "framework/module.h" +#include "profiler/profiler.h" + +#include "tcp_pdu.h" + +using namespace snort; +using namespace std; + +//------------------------------------------------------------------------- +// common foo +//------------------------------------------------------------------------- + +#define s_name "tcp_pdu" +#define s_help "set TCP flush points based on PDU length field" + +static const PegInfo pdu_pegs[] = +{ + { CountType::SUM, "scans", "total segments scanned" }, + { CountType::SUM, "flushes", "total PDUs flushed for detection" }, + { CountType::SUM, "aborts", "total unrecoverable scan errors" }, + { CountType::END, nullptr, nullptr } +}; + +THREAD_LOCAL PduCounts pdu_counts; + +static THREAD_LOCAL snort::ProfileStats pdu_prof; + +//------------------------------------------------------------------------- +// module foo +//------------------------------------------------------------------------- + +static const Parameter s_params[] = +{ + { "offset", Parameter::PT_INT, "0:65535", "0", + "index to first byte of length field" }, + + { "size", Parameter::PT_INT, "1:4", "4", + "number of bytes in length field" }, + + { "skip", Parameter::PT_INT, "0:65535", "0", + "bytes after length field to end of header" }, + + { "relative", Parameter::PT_BOOL, nullptr, "false", + "extracted length follows field (instead of whole PDU)" }, + + { nullptr, Parameter::PT_MAX, nullptr, nullptr, nullptr } +}; + +class TcpPduModule : public snort::Module +{ +public: + TcpPduModule() : Module(s_name, s_help, s_params) + { } + + const PegInfo* get_pegs() const override + { return pdu_pegs; } + + PegCount* get_counts() const override + { return (PegCount*)&pdu_counts; } + + snort::ProfileStats* get_profile() const override + { return &pdu_prof; } + + Usage get_usage() const override + { return INSPECT; } + + bool is_bindable() const override + { return true; } + + bool set(const char*, Value&, SnortConfig*) override; + + TcpPduConfig& get_config() + { return config; } + +private: + TcpPduConfig config; +}; + +bool TcpPduModule::set(const char*, Value& v, SnortConfig*) +{ + if (v.is("offset")) + config.offset = v.get_int32(); + + else if (v.is("size")) + config.size = v.get_uint8(); + + else if (v.is("skip")) + config.skip = v.get_uint8(); + + else if (v.is("relative")) + config.relative = v.get_bool(); + + return true; +} + +//------------------------------------------------------------------------- +// inspector foo +//------------------------------------------------------------------------- + +class TcpPdu : public Inspector +{ +public: + TcpPdu(TcpPduConfig& c) : config(c) { } + + StreamSplitter* get_splitter(bool c2s) override + { return new TcpPduSplitter(c2s, config); } + +private: + TcpPduConfig config; +}; + +//------------------------------------------------------------------------- +// api foo +//------------------------------------------------------------------------- + +static Module* mod_ctor() +{ return new TcpPduModule; } + +static void mod_dtor(Module* m) +{ delete m; } + +static Inspector* pdu_ctor(Module* m) +{ + TcpPduModule* tpm = (TcpPduModule*)m; + return new TcpPdu(tpm->get_config()); +} + +static void pdu_dtor(Inspector* p) +{ + delete p; +} + +static const InspectApi pdu_api = +{ + { + PT_INSPECTOR, + sizeof(InspectApi), + INSAPI_VERSION, + 0, + API_RESERVED, + API_OPTIONS, + s_name, + s_help, + mod_ctor, + mod_dtor + }, + IT_SERVICE, + PROTO_BIT__PDU, + nullptr, // buffers + s_name, + nullptr, // init + nullptr, // pterm + nullptr, // tinit + nullptr, // tterm + pdu_ctor, + pdu_dtor, + nullptr, // ssn + nullptr // reset +}; + +#ifdef BUILDING_SO +SO_PUBLIC const BaseApi* snort_plugins[] = +{ + &pdu_api.base, + nullptr +}; +#else +const BaseApi* sin_tcp_pdu = &pdu_api.base; +#endif + diff --git a/src/service_inspectors/tcp_pdu/tcp_pdu.h b/src/service_inspectors/tcp_pdu/tcp_pdu.h new file mode 100644 index 000000000..5c32181f2 --- /dev/null +++ b/src/service_inspectors/tcp_pdu/tcp_pdu.h @@ -0,0 +1,64 @@ +//-------------------------------------------------------------------------- +// Copyright (C) 2024-2024 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. +//-------------------------------------------------------------------------- + +// tcp_pdu.h author Russ Combs + +// provides a simple flush mechanism for TCP PDUs with +// a fixed size header containing a length field + +#ifndef TCP_PDU_H +#define TCP_PDU_H + +#include "framework/counts.h" +#include "main/snort_types.h" +#include "stream/stream_splitter.h" + +struct TcpPduConfig +{ + unsigned size = 0; + unsigned offset = 0; + unsigned skip = 0; + bool relative = false; +}; + +struct PduCounts +{ + PegCount scans; + PegCount flushes; + PegCount aborts; +}; + +extern THREAD_LOCAL PduCounts pdu_counts; + +class TcpPduSplitter : public snort::StreamSplitter +{ +public: + TcpPduSplitter(bool b, TcpPduConfig& c) : snort::StreamSplitter(b), config(c) { } + + bool is_paf() override { return true; } + + Status scan(struct snort::Packet*, const uint8_t*, uint32_t, uint32_t, uint32_t*) override; + +private: + TcpPduConfig config; + unsigned index = 0; + uint32_t value = 0; +}; + +#endif + diff --git a/src/service_inspectors/tcp_pdu/tcp_pdu_splitter.cc b/src/service_inspectors/tcp_pdu/tcp_pdu_splitter.cc new file mode 100644 index 000000000..5a3e3dcc1 --- /dev/null +++ b/src/service_inspectors/tcp_pdu/tcp_pdu_splitter.cc @@ -0,0 +1,73 @@ +//-------------------------------------------------------------------------- +// Copyright (C) 2024-2024 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. +//-------------------------------------------------------------------------- + +// tcp_pdu_splitter.cc author Russ Combs + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "tcp_pdu.h" + +using namespace snort; + +//------------------------------------------------------------------------- +// splitter foo +//------------------------------------------------------------------------- + +StreamSplitter::Status TcpPduSplitter::scan(Packet*, const uint8_t* data, uint32_t len, uint32_t, uint32_t* fp) +{ + ++pdu_counts.scans; + unsigned prefix = config.offset + config.size; + + for ( unsigned i = 0; i < len; ++i ) + { + if ( index < config.offset ) + ++index; + + else if ( index < prefix ) + { + ++index; + value <<= 8; + value |= data[i]; + } + else + break; + } + if ( index == prefix ) + { + unsigned header = config.offset + config.size + config.skip; + + if ( config.relative ) + value += header; + + *fp = value; + value = 0; + index = 0; + + if ( config.relative or (*fp >= header) ) + { + ++pdu_counts.flushes; + return FLUSH; + } + ++pdu_counts.aborts; + return ABORT; + } + return SEARCH; +} + diff --git a/src/service_inspectors/tcp_pdu/test/CMakeLists.txt b/src/service_inspectors/tcp_pdu/test/CMakeLists.txt new file mode 100644 index 000000000..cea38ffd9 --- /dev/null +++ b/src/service_inspectors/tcp_pdu/test/CMakeLists.txt @@ -0,0 +1,7 @@ + +add_cpputest( tcp_pdu_test + SOURCES + ../tcp_pdu_splitter.cc + ../../../stream/stream_splitter.cc +) + diff --git a/src/service_inspectors/tcp_pdu/test/tcp_pdu_test.cc b/src/service_inspectors/tcp_pdu/test/tcp_pdu_test.cc new file mode 100644 index 000000000..1973efc70 --- /dev/null +++ b/src/service_inspectors/tcp_pdu/test/tcp_pdu_test.cc @@ -0,0 +1,374 @@ +//-------------------------------------------------------------------------- +// Copyright (C) 2015-2024 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. +//-------------------------------------------------------------------------- + +// tcp_pdu_test.cc author Russ Combs + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include + +#include "detection/detection_engine.h" +#include "main/snort_config.h" +#include "stream/flush_bucket.h" +#include "stream/stream.h" + +#include "../tcp_pdu.h" + +// must appear after snort_config.h to avoid broken c++ map include +#include +#include + +using namespace snort; + +//------------------------------------------------------------------------- +// stubs, spies, etc. +//------------------------------------------------------------------------- + +const SnortConfig* SnortConfig::get_conf() +{ return nullptr; } + +uint16_t FlushBucket::get_size() +{ return 0; } + +uint8_t* DetectionEngine::get_next_buffer(unsigned int&) +{ return nullptr; } + +Packet* DetectionEngine::get_current_packet() +{ return nullptr; } + +StreamSplitter* Stream::get_splitter(Flow*, bool) +{ return nullptr; } + +void Stream::flush_client(Packet*) +{ } + +void Stream::flush_server(Packet*) +{ } + +THREAD_LOCAL PduCounts pdu_counts; + +//------------------------------------------------------------------------- +// 4 byte length followed by data, no offset, relative +// check with scan sizes 1, 2, 3, 4, 5 +//------------------------------------------------------------------------- + +TEST_GROUP(relative_length_only) +{ + // 4 byte length followed by 3 bytes data + const uint8_t data[7] = { 0, 1, 2, 3, 4, 5, 6 }; // cppcheck-suppress unreadVariable + StreamSplitter* ss = nullptr; + + void setup() override + { + TcpPduConfig c = { 4, 0, 0, true }; + ss = new TcpPduSplitter(true, c); // cppcheck-suppress unreadVariable + } + void teardown() override + { delete ss; } +}; + +TEST(relative_length_only, n1) +{ + uint32_t fp = 0; + StreamSplitter::Status result; + + for ( auto i = 0; i < 3; ++i ) + { + result = ss->scan(nullptr, data+i, 1, 0, &fp); + CHECK(result == StreamSplitter::SEARCH); + } + + result = ss->scan(nullptr, data+3, 1, 0, &fp); + CHECK(result == StreamSplitter::FLUSH); + CHECK(fp == 4+0x10203); +} + +TEST(relative_length_only, n2) +{ + uint32_t fp = 0; + StreamSplitter::Status result; + + result = ss->scan(nullptr, data+0, 2, 0, &fp); + CHECK(result == StreamSplitter::SEARCH); + + result = ss->scan(nullptr, data+2, 2, 0, &fp); + CHECK(result == StreamSplitter::FLUSH); + CHECK(fp == 4+0x10203); +} + +TEST(relative_length_only, n3) +{ + uint32_t fp = 0; + StreamSplitter::Status result; + + result = ss->scan(nullptr, data+0, 3, 0, &fp); + CHECK(result == StreamSplitter::SEARCH); + + result = ss->scan(nullptr, data+3, 3, 0, &fp); + CHECK(result == StreamSplitter::FLUSH); + CHECK(fp == 4+0x10203); +} + +TEST(relative_length_only, n4) +{ + uint32_t fp = 0; + StreamSplitter::Status result; + + result = ss->scan(nullptr, data+0, 4, 0, &fp); + CHECK(result == StreamSplitter::FLUSH); + CHECK(fp == 4+0x10203); +} + +TEST(relative_length_only, n5) +{ + uint32_t fp = 0; + StreamSplitter::Status result; + + result = ss->scan(nullptr, data+0, 5, 0, &fp); + CHECK(result == StreamSplitter::FLUSH); + CHECK(fp == 4+0x10203); +} + +//------------------------------------------------------------------------- +// 3 byte offset, 4 byte length, 2 byte skip, relative +// check with scan sizes 1, 2, 3, 4, 7, 8 +//------------------------------------------------------------------------- + +TEST_GROUP(relative_offset_length) +{ + const uint8_t data[10] = { 9, 8, 7, 0, 1, 2, 3, 4, 5, 6 }; // cppcheck-suppress unreadVariable + StreamSplitter* ss = nullptr; + + void setup() override + { + TcpPduConfig c = { 4, 3, 2, true }; + ss = new TcpPduSplitter(true, c); // cppcheck-suppress unreadVariable + } + void teardown() override + { delete ss; } +}; + +TEST(relative_offset_length, n1) +{ + uint32_t fp = 0; + StreamSplitter::Status result; + + for ( auto i = 0; i < 6; ++i ) + { + result = ss->scan(nullptr, data+i, 1, 0, &fp); + CHECK(result == StreamSplitter::SEARCH); + } + + result = ss->scan(nullptr, data+6, 1, 0, &fp); + CHECK(result == StreamSplitter::FLUSH); + CHECK(fp == 3+4+2+0x10203); +} + +TEST(relative_offset_length, n2) +{ + uint32_t fp = 0; + StreamSplitter::Status result; + + for ( auto i = 0; i < 6; i+=2 ) + { + result = ss->scan(nullptr, data+i, 2, 0, &fp); + CHECK(result == StreamSplitter::SEARCH); + } + + result = ss->scan(nullptr, data+6, 2, 0, &fp); + CHECK(result == StreamSplitter::FLUSH); + CHECK(fp == 3+4+2+0x10203); +} + +TEST(relative_offset_length, n3) +{ + uint32_t fp = 0; + StreamSplitter::Status result; + + result = ss->scan(nullptr, data+0, 3, 0, &fp); + CHECK(result == StreamSplitter::SEARCH); + + result = ss->scan(nullptr, data+3, 3, 0, &fp); + CHECK(result == StreamSplitter::SEARCH); + + result = ss->scan(nullptr, data+6, 3, 0, &fp); + CHECK(result == StreamSplitter::FLUSH); + CHECK(fp == 3+4+2+0x10203); +} + +TEST(relative_offset_length, n4) +{ + uint32_t fp = 0; + StreamSplitter::Status result; + + result = ss->scan(nullptr, data+0, 4, 0, &fp); + CHECK(result == StreamSplitter::SEARCH); + + result = ss->scan(nullptr, data+4, 4, 0, &fp); + CHECK(result == StreamSplitter::FLUSH); + CHECK(fp == 3+4+2+0x10203); +} + +TEST(relative_offset_length, n7) +{ + uint32_t fp = 0; + StreamSplitter::Status result; + + result = ss->scan(nullptr, data+0, 7, 0, &fp); + CHECK(result == StreamSplitter::FLUSH); + CHECK(fp == 3+4+2+0x10203); +} + +TEST(relative_offset_length, n8) +{ + uint32_t fp = 0; + StreamSplitter::Status result; + + result = ss->scan(nullptr, data+0, 8, 0, &fp); + CHECK(result == StreamSplitter::FLUSH); + CHECK(fp == 3+4+2+0x10203); +} + +//------------------------------------------------------------------------- +// various +//------------------------------------------------------------------------- + +TEST_GROUP(various) +{ + const uint8_t data[8] = { 9, 8, 0, 1, 2, 3, 4, 5 }; // cppcheck-suppress unreadVariable + StreamSplitter* ss = nullptr; + + void teardown() override + { delete ss; } +}; + +TEST(various, absolute2) +{ + TcpPduConfig c = { 2, 3, 0, false }; + ss = new TcpPduSplitter(true, c); + + uint32_t fp = 0; + StreamSplitter::Status result; + + result = ss->scan(nullptr, data+0, 3, 0, &fp); + CHECK(result == StreamSplitter::SEARCH); + + result = ss->scan(nullptr, data+3, 3, 0, &fp); + CHECK(result == StreamSplitter::FLUSH); + CHECK(fp == 0x102); +} + +TEST(various, absolute3) +{ + TcpPduConfig c = { 3, 2, 0, false }; + ss = new TcpPduSplitter(true, c); + + uint32_t fp = 0; + StreamSplitter::Status result; + + result = ss->scan(nullptr, data+0, 3, 0, &fp); + CHECK(result == StreamSplitter::SEARCH); + + result = ss->scan(nullptr, data+3, 3, 0, &fp); + CHECK(result == StreamSplitter::FLUSH); + CHECK(fp == 0x102); +} + +TEST(various, abort) +{ + TcpPduConfig c = { 1, 2, 0, false }; + ss = new TcpPduSplitter(true, c); + + uint32_t fp = 0; + StreamSplitter::Status result; + + result = ss->scan(nullptr, data+0, 3, 0, &fp); + CHECK(result == StreamSplitter::ABORT); +} + +TEST(various, header_only) +{ + TcpPduConfig c = { 1, 2, 0, true }; + ss = new TcpPduSplitter(true, c); + + uint32_t fp = 0; + StreamSplitter::Status result; + + result = ss->scan(nullptr, data+0, 3, 0, &fp); + CHECK(result == StreamSplitter::FLUSH); + CHECK(fp == 2+1+0); +} + +//------------------------------------------------------------------------- +// multiple PDUs flushed on same flow / direction +//------------------------------------------------------------------------- + +TEST_GROUP(multi_flush) +{ +// __STRDUMP_DISABLE__ + // 2 byte offset ('O'), 1 byte length, data ('D') + // cppcheck-suppress unreadVariable + const uint8_t data[17] = { 'O', 'O', 3, 'D', 'D', 'D', 'O', 'O', 1, 'D', 'O', 'O', 4, 'D', 'D', 'D', 'D' }; +// __STRDUMP_ENABLE__ + StreamSplitter* ss = nullptr; + + void setup() override + { + TcpPduConfig c = { 1, 2, 0, true }; + ss = new TcpPduSplitter(true, c); // cppcheck-suppress unreadVariable + } + void teardown() override + { delete ss; } +}; + +TEST(multi_flush, pdu3) +{ + uint32_t fp = 0; + StreamSplitter::Status result; + + // PDU 1 + result = ss->scan(nullptr, data+0, 3, 0, &fp); + CHECK(result == StreamSplitter::FLUSH); + CHECK(fp == 6); + + // PDU 2 + result = ss->scan(nullptr, data+6, 4, 0, &fp); + CHECK(result == StreamSplitter::FLUSH); + CHECK(fp == 4); + + // PDU 3 + result = ss->scan(nullptr, data+10, 1, 0, &fp); + CHECK(result == StreamSplitter::SEARCH); + + result = ss->scan(nullptr, data+11, 3, 0, &fp); + CHECK(result == StreamSplitter::FLUSH); + CHECK(fp == 7); +} + +//------------------------------------------------------------------------- +// main +//------------------------------------------------------------------------- + +int main(int argc, char** argv) +{ + MemoryLeakWarningPlugin::turnOffNewDeleteOverloads(); + return CommandLineTestRunner::RunAllTests(argc, argv); +} +