]> git.ipfire.org Git - thirdparty/snort3.git/commitdiff
Merge pull request #551 in SNORT/snort3 from nhttp47 to master
authorRuss Combs (rucombs) <rucombs@cisco.com>
Mon, 11 Jul 2016 12:36:54 +0000 (08:36 -0400)
committerRuss Combs (rucombs) <rucombs@cisco.com>
Mon, 11 Jul 2016 12:36:54 +0000 (08:36 -0400)
Squashed commit of the following:

commit 3b1af0f2fb6abc2680f63916e6e6dcba9a355187
Author: Tom Peters <thopeter@cisco.com>
Date:   Fri Jun 17 17:22:02 2016 -0400

    Properly track transactions that include 100 status code

13 files changed:
src/service_inspectors/nhttp_inspect/nhttp_flow_data.cc
src/service_inspectors/nhttp_inspect/nhttp_flow_data.h
src/service_inspectors/nhttp_inspect/nhttp_inspect.cc
src/service_inspectors/nhttp_inspect/nhttp_msg_request.cc
src/service_inspectors/nhttp_inspect/nhttp_msg_section.cc
src/service_inspectors/nhttp_inspect/nhttp_msg_section.h
src/service_inspectors/nhttp_inspect/nhttp_msg_status.cc
src/service_inspectors/nhttp_inspect/nhttp_stream_splitter_scan.cc
src/service_inspectors/nhttp_inspect/nhttp_transaction.cc
src/service_inspectors/nhttp_inspect/nhttp_transaction.h
src/service_inspectors/nhttp_inspect/test/CMakeLists.txt
src/service_inspectors/nhttp_inspect/test/Makefile.am
src/service_inspectors/nhttp_inspect/test/nhttp_transaction_test.cc [new file with mode: 0644]

index 9008fa7d1c2addcb902493f06711a2695d169624..a0a068e0592e445f83df53fad76c0c59e0e0e43a 100644 (file)
@@ -61,7 +61,7 @@ NHttpFlowData::~NHttpFlowData()
             (section_type[k] != SEC_BODY_OLD))
             // Body sections are reassembled in a static buffer
             delete[] section_buffer[k];
-        delete transaction[k];
+        NHttpTransaction::delete_transaction(transaction[k]);
         delete cutter[k];
         if (compress_stream[k] != nullptr)
         {
@@ -105,7 +105,7 @@ void NHttpFlowData::half_reset(SourceId source_id)
     if (source_id == SRC_CLIENT)
     {
         type_expected[SRC_CLIENT] = SEC_REQUEST;
-        expected_msg_num[SRC_CLIENT]++;
+        expected_trans_num[SRC_CLIENT]++;
         method_id = METH__NOT_PRESENT;
         if (mime_state != nullptr)
         {
@@ -116,7 +116,8 @@ void NHttpFlowData::half_reset(SourceId source_id)
     else
     {
         type_expected[SRC_SERVER] = SEC_STATUS;
-        expected_msg_num[SRC_SERVER]++;
+        if (transaction[SRC_SERVER]->final_response())
+            expected_trans_num[SRC_SERVER]++;
         status_code_num = STAT_NOT_PRESENT;
     }
 }
@@ -169,7 +170,7 @@ void NHttpFlowData::delete_pipeline()
 {
     for (int k=pipeline_front; k != pipeline_back; k = (k+1) % MAX_PIPELINE)
     {
-        delete pipeline[k];
+        NHttpTransaction::delete_transaction(pipeline[k]);
     }
     delete[] pipeline;
 }
index 096955aa78b12b1264c246d3e9cf0eadb1588584..13c6824bed2f480989b65a71c2ebd85bd0cdf799 100644 (file)
@@ -54,6 +54,9 @@ public:
     friend class NHttpMsgBodyOld;
     friend class NHttpStreamSplitter;
     friend class NHttpTransaction;
+#ifdef REG_TEST
+    friend class NHttpUnitTestSetup;
+#endif
 
 private:
     // Convenience routines
@@ -104,7 +107,7 @@ private:
     int64_t detect_depth_remaining[2] = { NHttpEnums::STAT_NOT_PRESENT,
         NHttpEnums::STAT_NOT_PRESENT };
     MimeSession* mime_state = nullptr;  // SRC_CLIENT only
-    uint64_t expected_msg_num[2] = { 1, 1 };
+    uint64_t expected_trans_num[2] = { 1, 1 };
 
     // number of user data octets seen so far (regular body or chunks)
     int64_t body_octets[2] = { NHttpEnums::STAT_NOT_PRESENT, NHttpEnums::STAT_NOT_PRESENT };
index 15b5e665eb489689e4f49021a6b5c5eb35a1f2ce..39157cab748916fcdc6152ffe406681891d71f53 100644 (file)
@@ -215,15 +215,15 @@ void NHttpInspect::clear(NHttpFlowData* session_data, SourceId source_id)
     latest_section = nullptr;
 
     // If current transaction is complete then we are done with it and should reclaim the space
-    if ((source_id == SRC_SERVER) && (session_data->type_expected[SRC_SERVER] == SEC_STATUS))
+    if ((source_id == SRC_SERVER) && (session_data->type_expected[SRC_SERVER] == SEC_STATUS) &&
+         session_data->transaction[SRC_SERVER]->final_response())
     {
-        delete session_data->transaction[SRC_SERVER];
+        NHttpTransaction::delete_transaction(session_data->transaction[SRC_SERVER]);
         session_data->transaction[SRC_SERVER] = nullptr;
     }
     else
     {
         // Get rid of most recent body section if present
-        delete session_data->transaction[source_id]->get_body();
         session_data->transaction[source_id]->set_body(nullptr);
     }
 }
index c4afe837820fa3f2ffb345922796be4169afecd1..38a75cd678c64253356dc617d5e37d932da9a2ac 100644 (file)
@@ -212,7 +212,7 @@ void NHttpMsgRequest::gen_events()
         infractions += INF_ZERO_NINE_CONTINUE;
         events.create_event(EVENT_ZERO_NINE_CONTINUE);
     }
-    else if (zero_nine && (msg_num != 1))
+    else if (zero_nine && (trans_num != 1))
     {
         // Switched to 0.9 request after previously sending non-0.9 request on this connection
         infractions += INF_ZERO_NINE_NOT_FIRST;
@@ -237,7 +237,7 @@ void NHttpMsgRequest::update_flow()
             // FIXIT-L Add a configuration option to not do this. This would support an HTTP server
             // that responds to a 0.9 GET request with a full-blown 1.0 or 1.1 response with status
             // line and headers.
-            session_data->zero_nine_expected = msg_num;
+            session_data->zero_nine_expected = trans_num;
         }
     }
     else
index f9d617b678194891cdd0a19a64055e29c00ea839..f313e4eb725343aed6b764bcd341612fd0cc3ba2 100644 (file)
@@ -41,7 +41,7 @@ NHttpMsgSection::NHttpMsgSection(const uint8_t* buffer, const uint16_t buf_size,
     session_data(session_data_),
     source_id(source_id_),
     flow(flow_),
-    msg_num(session_data->expected_msg_num[source_id]),
+    trans_num(session_data->expected_trans_num[source_id]),
     params(params_),
     transaction(NHttpTransaction::attach_my_transaction(session_data, source_id)),
     tcp_close(session_data->tcp_close[source_id]),
@@ -214,7 +214,7 @@ const Field& NHttpMsgSection::get_classic_buffer(unsigned id, uint64_t sub_id, u
 
 void NHttpMsgSection::print_section_title(FILE* output, const char* title) const
 {
-    fprintf(output, "HTTP message %" PRIu64 " %s:\n", msg_num, title);
+    fprintf(output, "HTTP message %" PRIu64 " %s:\n", trans_num, title);
     msg_text.print(output, "Input");
 }
 
index 61c58346641adcb2dfa2c67212d3151bd14bc7a4..08115ec898cdf2c8bf6956d64b83aeb72c6247e7 100644 (file)
@@ -69,7 +69,7 @@ protected:
     NHttpFlowData* const session_data;
     const NHttpEnums::SourceId source_id;
     Flow* const flow;
-    uint64_t msg_num;
+    uint64_t trans_num;
     const NHttpParaList* const params;
     NHttpTransaction* const transaction;
     const bool tcp_close;
index 7029d3c1568e465de24b72cdbcc6f2bfb714a6d1..fc91c2fe435cc86257a5a1fb3beb6e2f1fedf31b 100644 (file)
@@ -163,6 +163,12 @@ void NHttpMsgStatus::update_flow()
         session_data->status_code_num = status_code_num;
         session_data->infractions[source_id].reset();
         session_data->events[source_id].reset();
+        // 100 response means the next response message will be added to this transaction instead
+        // of being part of another transaction. As implemented it is possible for multiple 100
+        // responses to all be included in the same transaction. It's not obvious whether that is
+        // the best way to handle what should be a highly abnormal situation.
+        if (status_code_num == 100)
+            transaction->second_response_coming();
     }
     session_data->section_type[source_id] = SEC__NOT_COMPUTE;
 }
index dd7a27844dd2064f5e0f906e32c9df53e70655c6..e5830670eec1fb3fc744bd0a1822d403baf23158 100644 (file)
@@ -125,7 +125,7 @@ StreamSplitter::Status NHttpStreamSplitter::scan(Flow* flow, const uint8_t* data
 
     // Check for 0.9 response message
     if ((type == SEC_STATUS) &&
-        (session_data->expected_msg_num[SRC_SERVER] == session_data->zero_nine_expected))
+        (session_data->expected_trans_num[SRC_SERVER] == session_data->zero_nine_expected))
     {
         // 0.9 response is a body that runs to connection end with no headers. NHttpInspect does
         // not support no headers. Processing this imaginary status line and empty headers allows
index add162dbb75393b0f282c810aef86ea7bb142aa1..db7b0d198c5864989eaa8195be27fef91a3c0224 100644 (file)
@@ -33,10 +33,10 @@ NHttpTransaction::~NHttpTransaction()
 {
     delete request;
     delete status;
-    delete header[0];
-    delete header[1];
-    delete trailer[0];
-    delete trailer[1];
+    delete header[SRC_CLIENT];
+    delete header[SRC_SERVER];
+    delete trailer[SRC_CLIENT];
+    delete trailer[SRC_SERVER];
     delete latest_body;
 }
 
@@ -49,58 +49,120 @@ NHttpTransaction* NHttpTransaction::attach_my_transaction(NHttpFlowData* session
     // 3. garbage collects unneeded transactions
     // 4. returns the current transaction
 
-    // Request section: put the old transaction in the pipeline and replace it with a new
-    // transaction. If the pipeline overflows or underflows we stop using it and just delete the
-    // old transaction.
+    // Request section: replace the old request transaction with a new transaction.
     if (session_data->section_type[source_id] == SEC_REQUEST)
     {
-        // When pipelining is not occurring the response should already have taken this transaction
-        // and left nullptr.
+        // If the HTTP request and response messages are alternating (usual situation) the old
+        // request transaction will have been moved to the server side when the last response
+        // message was received. This will be nullptr and we don't need to deal with the old
+        // request transaction here.
         if (session_data->transaction[SRC_CLIENT] != nullptr)
         {
-            if ((session_data->pipeline_overflow) || (session_data->pipeline_underflow))
+            // The old request transaction is still here. Typically that is because the
+            // the current request has arrived before the previous response (pipelining). We need
+            // to add this transaction to our pipeline where it will wait for the matching
+            // response. But there are some special cases to check first.
+            if (session_data->transaction[SRC_CLIENT]->response_seen)
             {
-                delete session_data->transaction[SRC_CLIENT];
+                // The response started before the request finished. When the response took the
+                // old request transaction it did not leave the usual nullptr because we still
+                // needed it. Instead the two sides have been sharing the transaction. This is a
+                // soft delete that eliminates our interest in this transaction without disturbing
+                // the possibly ongoing response processing.
+                delete_transaction(session_data->transaction[SRC_CLIENT]);
+            }
+            else if ((session_data->pipeline_overflow) || (session_data->pipeline_underflow))
+            {
+                // Pipelining previously broke down and both sides are processed separately from
+                // now on. We just throw things away when we are done with them.
+                delete_transaction(session_data->transaction[SRC_CLIENT]);
             }
             else if (!session_data->add_to_pipeline(session_data->transaction[SRC_CLIENT]))
             {
                 // The pipeline is full and just overflowed.
-                session_data->infractions[source_id] += INF_PARTIAL_START;
+                session_data->infractions[source_id] += INF_PIPELINE_OVERFLOW;
                 session_data->events[source_id].create_event(EVENT_PIPELINE_MAX);
-                delete session_data->transaction[SRC_CLIENT];
+                delete_transaction(session_data->transaction[SRC_CLIENT]);
             }
         }
         session_data->transaction[SRC_CLIENT] = new NHttpTransaction;
     }
+    // This transaction has more than one response. This is a new response which is replacing the
+    // interim response. The two responses cannot coexist so we must clean up the interim response.
+    else if ((session_data->section_type[source_id] == SEC_STATUS) &&
+             (session_data->transaction[SRC_SERVER] != nullptr) &&
+              session_data->transaction[SRC_SERVER]->second_response_expected)
+    {
+        assert(session_data->transaction[SRC_SERVER] != nullptr);
+        session_data->transaction[SRC_SERVER]->second_response_expected = false;
+        delete session_data->transaction[SRC_SERVER]->status;
+        session_data->transaction[SRC_SERVER]->status = nullptr;
+        delete session_data->transaction[SRC_SERVER]->header[SRC_SERVER];
+        session_data->transaction[SRC_SERVER]->header[SRC_SERVER] = nullptr;
+        delete session_data->transaction[SRC_SERVER]->trailer[SRC_SERVER];
+        session_data->transaction[SRC_SERVER]->trailer[SRC_SERVER] = nullptr;
+    }
     // Status section: delete the current transaction and get a new one from the pipeline. If the
-    // pipeline is empty check for a request-side transaction that just finished and take it. If
-    // there is no transaction available then declare an underflow and create a new transaction
-    // specifically for the response side.
+    // pipeline is empty check for a request transaction and take it. If there is no transaction
+    // available then declare an underflow and create a new transaction specifically for the
+    // response side.
     else if (session_data->section_type[source_id] == SEC_STATUS)
     {
-        delete session_data->transaction[SRC_SERVER];
+        delete_transaction(session_data->transaction[SRC_SERVER]);
         if (session_data->pipeline_underflow)
         {
+            // A previous underflow separated the two sides forever
             session_data->transaction[SRC_SERVER] = new NHttpTransaction;
         }
         else if ((session_data->transaction[SRC_SERVER] = session_data->take_from_pipeline()) ==
             nullptr)
         {
-            if ((session_data->transaction[SRC_CLIENT] != nullptr) &&
-                (session_data->type_expected[SRC_CLIENT] == SEC_REQUEST))
+            if ((session_data->transaction[SRC_CLIENT] == nullptr) ||
+                (session_data->transaction[SRC_CLIENT]->response_seen))
             {
+                // Either there is no request at all or there is a request but a previous response
+                // already took it. Either way we have more responses than requests.
+                session_data->pipeline_underflow = true;
+                session_data->transaction[SRC_SERVER] = new NHttpTransaction;
+            }
+
+            else if (session_data->type_expected[SRC_CLIENT] == SEC_REQUEST)
+            {
+                // This is the normal case where the requests and responses are alternating (no
+                // pipelining). Processing of the response is complete so the request just takes
+                // it.
                 session_data->transaction[SRC_SERVER] = session_data->transaction[SRC_CLIENT];
                 session_data->transaction[SRC_CLIENT] = nullptr;
             }
             else
             {
-                session_data->pipeline_underflow = true;
-                session_data->transaction[SRC_SERVER] = new NHttpTransaction;
+                // Response message is starting before the request message has finished. Request
+                // side is not finished with this transaction so two sides share it
+                session_data->transaction[SRC_CLIENT]->shared_ownership = true;
+                session_data->transaction[SRC_SERVER] = session_data->transaction[SRC_CLIENT];
             }
         }
+        session_data->transaction[SRC_SERVER]->response_seen = true;
     }
 
-    assert((source_id == SRC_SERVER) || (session_data->transaction[source_id] != nullptr));
+    assert(session_data->transaction[source_id] != nullptr);
     return session_data->transaction[source_id];
 }
 
+void NHttpTransaction::delete_transaction(NHttpTransaction* transaction)
+{
+    if (transaction != nullptr)
+    {
+        if (!transaction->shared_ownership)
+            delete transaction;
+        else
+            transaction->shared_ownership = false;
+    }
+}
+
+void NHttpTransaction::set_body(NHttpMsgBody* latest_body_)
+{
+    delete latest_body;
+    latest_body = latest_body_;
+}
+
index d039d5331363c13dc0520779f37cf37426ecbb4b..7d51bb704e246c93e832eb4166ffe22123e15e02 100644 (file)
@@ -36,7 +36,7 @@ class NHttpTransaction
 public:
     static NHttpTransaction* attach_my_transaction(NHttpFlowData* session_data,
         NHttpEnums::SourceId source_id);
-    ~NHttpTransaction();
+    static void delete_transaction(NHttpTransaction* transaction);
 
     NHttpMsgRequest* get_request() const { return request; }
     void set_request(NHttpMsgRequest* request_) { request = request_; }
@@ -54,16 +54,28 @@ public:
         { trailer[source_id] = trailer_; }
 
     NHttpMsgBody* get_body() const { return latest_body; }
-    void set_body(NHttpMsgBody* latest_body_) { latest_body = latest_body_; }
+    void set_body(NHttpMsgBody* latest_body_);
+
+    void second_response_coming() { assert(response_seen); second_response_expected = true; }
+    bool final_response() const { return !second_response_expected; }
 
 private:
     NHttpTransaction() = default;
+    ~NHttpTransaction();
 
     NHttpMsgRequest* request = nullptr;
     NHttpMsgStatus* status = nullptr;
     NHttpMsgHeader* header[2] = { nullptr, nullptr };
     NHttpMsgTrailer* trailer[2] = { nullptr, nullptr };
     NHttpMsgBody* latest_body = nullptr;
+
+    bool response_seen = false;
+    bool second_response_expected = false;
+
+    // This is a form of reference counting that prevents premature/double deletion of a
+    // transaction in the fairly rare case where the request and response are received in
+    // parallel.
+    bool shared_ownership = false;
 };
 
 #endif
index cee1fa8afa3d9ce3becc87cea4d6e2b3846f516c..4e3bd2b3520139ef3242f384fa6e7770ac98ea8d 100644 (file)
@@ -1,4 +1,5 @@
 add_cpputest(nhttp_uri_norm_test nhttp_inspect framework)
 add_cpputest(nhttp_normalizers_test nhttp_inspect framework)
 add_cpputest(nhttp_module_test nhttp_inspect framework)
+add_cpputest(nhttp_transaction_test nhttp_inspect framework)
 
index 1f28e9cd1abe07c6cebdb9db52feeabd89ec53b8..27197ea806edf530af4d006441add981bef7493e 100644 (file)
@@ -4,7 +4,8 @@ AM_DEFAULT_SOURCE_EXT = .cc
 check_PROGRAMS = \
 nhttp_uri_norm_test \
 nhttp_normalizers_test \
-nhttp_module_test
+nhttp_module_test \
+nhttp_transaction_test
 
 TESTS = $(check_PROGRAMS)
 
@@ -37,3 +38,11 @@ nhttp_module_test_LDADD = \
 ../../../framework/module.o \
 @CPPUTEST_LDFLAGS@
 
+nhttp_transaction_test_CPPFLAGS = $(AM_CPPFLAGS) @CPPUTEST_CPPFLAGS@
+nhttp_transaction_test_LDADD = \
+../nhttp_transaction.o \
+../nhttp_flow_data.o \
+../nhttp_test_manager.o \
+../nhttp_test_input.o \
+@CPPUTEST_LDFLAGS@
+
diff --git a/src/service_inspectors/nhttp_inspect/test/nhttp_transaction_test.cc b/src/service_inspectors/nhttp_inspect/test/nhttp_transaction_test.cc
new file mode 100644 (file)
index 0000000..139e4e9
--- /dev/null
@@ -0,0 +1,469 @@
+//--------------------------------------------------------------------------
+// Copyright (C) 2016 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.
+//--------------------------------------------------------------------------
+
+// nhttp_transaction_test.cc author Tom Peters <thopeter@cisco.com>
+// unit test main
+
+#include "service_inspectors/nhttp_inspect/nhttp_transaction.h"
+#include "service_inspectors/nhttp_inspect/nhttp_module.h"
+#include "service_inspectors/nhttp_inspect/nhttp_flow_data.h"
+#include "service_inspectors/nhttp_inspect/nhttp_enum.h"
+
+#include <CppUTest/CommandLineTestRunner.h>
+#include <CppUTest/TestHarness.h>
+#include <CppUTestExt/MockSupport.h>
+
+using namespace NHttpEnums;
+
+// Stubs whose sole purpose is to make the test code link
+unsigned FlowData::flow_id = 0;
+FlowData::FlowData(unsigned, Inspector*) {}
+FlowData::~FlowData() {}
+int SnortEventqAdd(unsigned int, unsigned int, RuleType) { return 0; }
+THREAD_LOCAL PegCount NHttpModule::peg_counts[1];
+
+class NHttpUnitTestSetup
+{
+public:
+    static SectionType* get_section_type(NHttpFlowData* flow_data)
+        { assert(flow_data!=nullptr); return flow_data->section_type; }
+    static SectionType* get_type_expected(NHttpFlowData* flow_data)
+        { assert(flow_data!=nullptr); return flow_data->type_expected; }
+};
+
+TEST_GROUP(nhttp_transaction_test)
+{
+    NHttpFlowData* const flow_data = new NHttpFlowData;
+    SectionType* const section_type = NHttpUnitTestSetup::get_section_type(flow_data);
+    SectionType* const type_expected = NHttpUnitTestSetup::get_type_expected(flow_data);
+
+    void teardown()
+    {
+        delete flow_data;
+    }
+};
+
+TEST(nhttp_transaction_test, simple_transaction)
+{
+    // This test is a request message with a chunked body and trailers followed by a similar
+    // response message. No overlap in time.
+    // Request
+    type_expected[SRC_CLIENT] = SEC_REQUEST;
+    section_type[SRC_CLIENT] = SEC_REQUEST;
+    NHttpTransaction* trans = NHttpTransaction::attach_my_transaction(flow_data, SRC_CLIENT);
+    CHECK(trans != nullptr);
+    type_expected[SRC_CLIENT] = SEC_HEADER;
+    section_type[SRC_CLIENT] = SEC_HEADER;
+    CHECK(trans == NHttpTransaction::attach_my_transaction(flow_data, SRC_CLIENT));
+    type_expected[SRC_CLIENT] = SEC_BODY_CHUNK;
+    section_type[SRC_CLIENT] = SEC_BODY_CHUNK;
+    for (unsigned k=0; k<100; k++)
+    {
+        CHECK(trans == NHttpTransaction::attach_my_transaction(flow_data, SRC_CLIENT));
+    }
+    type_expected[SRC_CLIENT] = SEC_TRAILER;
+    section_type[SRC_CLIENT] = SEC_TRAILER;
+    CHECK(trans == NHttpTransaction::attach_my_transaction(flow_data, SRC_CLIENT));
+    type_expected[SRC_CLIENT] = SEC_REQUEST;
+
+    // Response
+    section_type[SRC_SERVER] = SEC_STATUS;
+    CHECK(trans == NHttpTransaction::attach_my_transaction(flow_data, SRC_SERVER));
+    section_type[SRC_SERVER] = SEC_HEADER;
+    CHECK(trans == NHttpTransaction::attach_my_transaction(flow_data, SRC_SERVER));
+    section_type[SRC_SERVER] = SEC_BODY_CHUNK;
+    for (unsigned k=0; k<100; k++)
+    {
+        CHECK(trans == NHttpTransaction::attach_my_transaction(flow_data, SRC_SERVER));
+    }
+    section_type[SRC_SERVER] = SEC_TRAILER;
+    CHECK(trans == NHttpTransaction::attach_my_transaction(flow_data, SRC_SERVER));
+}
+
+TEST(nhttp_transaction_test, orphan_response)
+{
+    // Response message without a request
+    type_expected[SRC_CLIENT] = SEC_REQUEST;
+    section_type[SRC_SERVER] = SEC_STATUS;
+    NHttpTransaction* trans = NHttpTransaction::attach_my_transaction(flow_data, SRC_SERVER);
+    CHECK(trans != nullptr);
+    section_type[SRC_SERVER] = SEC_HEADER;
+    CHECK(trans == NHttpTransaction::attach_my_transaction(flow_data, SRC_SERVER));
+    section_type[SRC_SERVER] = SEC_BODY_CHUNK;
+    for (unsigned k=0; k<10; k++)
+    {
+        CHECK(trans == NHttpTransaction::attach_my_transaction(flow_data, SRC_SERVER));
+    }
+    section_type[SRC_SERVER] = SEC_TRAILER;
+    CHECK(trans == NHttpTransaction::attach_my_transaction(flow_data, SRC_SERVER));
+}
+
+TEST(nhttp_transaction_test, simple_pipeline)
+{
+    // Pipeline with four requests followed by four responses
+    NHttpTransaction* trans[4];
+    for (unsigned k=0; k < 4; k++)
+    {
+        type_expected[SRC_CLIENT] = SEC_REQUEST;
+        section_type[SRC_CLIENT] = SEC_REQUEST;
+        trans[k] = NHttpTransaction::attach_my_transaction(flow_data, SRC_CLIENT);
+        CHECK(trans[k] != nullptr);
+        type_expected[SRC_CLIENT] = SEC_HEADER;
+        section_type[SRC_CLIENT] = SEC_HEADER;
+        CHECK(trans[k] == NHttpTransaction::attach_my_transaction(flow_data, SRC_CLIENT));
+        for (unsigned j=0; j < k; j++)
+        {
+            CHECK(trans[k] != trans[j]);
+        }
+    }
+    type_expected[SRC_CLIENT] = SEC_REQUEST;
+
+    for (unsigned k=0; k < 4; k++)
+    {
+        section_type[SRC_SERVER] = SEC_STATUS;
+        CHECK(trans[k] == NHttpTransaction::attach_my_transaction(flow_data, SRC_SERVER));
+        section_type[SRC_SERVER] = SEC_HEADER;
+        CHECK(trans[k] == NHttpTransaction::attach_my_transaction(flow_data, SRC_SERVER));
+        section_type[SRC_SERVER] = SEC_BODY_CL;
+        CHECK(trans[k] == NHttpTransaction::attach_my_transaction(flow_data, SRC_SERVER));
+    }
+}
+
+TEST(nhttp_transaction_test, concurrent_request_response)
+{
+    // Response starts before request completes, request completes first
+    type_expected[SRC_CLIENT] = SEC_REQUEST;
+    section_type[SRC_CLIENT] = SEC_REQUEST;
+    NHttpTransaction* trans = NHttpTransaction::attach_my_transaction(flow_data, SRC_CLIENT);
+    CHECK(trans != nullptr);
+    type_expected[SRC_CLIENT] = SEC_HEADER;
+    section_type[SRC_CLIENT] = SEC_HEADER;
+    CHECK(trans == NHttpTransaction::attach_my_transaction(flow_data, SRC_CLIENT));
+    type_expected[SRC_CLIENT] = SEC_BODY_CHUNK;
+
+    section_type[SRC_SERVER] = SEC_STATUS;
+    CHECK(trans == NHttpTransaction::attach_my_transaction(flow_data, SRC_SERVER));
+    section_type[SRC_SERVER] = SEC_HEADER;
+    CHECK(trans == NHttpTransaction::attach_my_transaction(flow_data, SRC_SERVER));
+
+    section_type[SRC_CLIENT] = SEC_BODY_CHUNK;
+    for (unsigned k=0; k<4; k++)
+    {
+        CHECK(trans == NHttpTransaction::attach_my_transaction(flow_data, SRC_CLIENT));
+    }
+    type_expected[SRC_CLIENT] = SEC_TRAILER;
+    section_type[SRC_CLIENT] = SEC_TRAILER;
+    CHECK(trans == NHttpTransaction::attach_my_transaction(flow_data, SRC_CLIENT));
+    type_expected[SRC_CLIENT] = SEC_REQUEST;
+
+    section_type[SRC_SERVER] = SEC_BODY_CHUNK;
+    for (unsigned k=0; k<6; k++)
+    {
+        CHECK(trans == NHttpTransaction::attach_my_transaction(flow_data, SRC_SERVER));
+    }
+    section_type[SRC_SERVER] = SEC_TRAILER;
+    CHECK(trans == NHttpTransaction::attach_my_transaction(flow_data, SRC_SERVER));
+}
+
+TEST(nhttp_transaction_test, pipeline_underflow)
+{
+    // Underflow scenario with request, two responses, request, response
+    type_expected[SRC_CLIENT] = SEC_REQUEST;
+    section_type[SRC_CLIENT] = SEC_REQUEST;
+    NHttpTransaction* trans = NHttpTransaction::attach_my_transaction(flow_data, SRC_CLIENT);
+    CHECK(trans != nullptr);
+    type_expected[SRC_CLIENT] = SEC_HEADER;
+    section_type[SRC_CLIENT] = SEC_HEADER;
+    CHECK(trans == NHttpTransaction::attach_my_transaction(flow_data, SRC_CLIENT));
+    type_expected[SRC_CLIENT] = SEC_REQUEST;
+
+    section_type[SRC_SERVER] = SEC_STATUS;
+    CHECK(trans == NHttpTransaction::attach_my_transaction(flow_data, SRC_SERVER));
+    section_type[SRC_SERVER] = SEC_HEADER;
+    CHECK(trans == NHttpTransaction::attach_my_transaction(flow_data, SRC_SERVER));
+
+    section_type[SRC_SERVER] = SEC_STATUS;
+    trans = NHttpTransaction::attach_my_transaction(flow_data, SRC_SERVER);
+    CHECK(trans != nullptr);
+    section_type[SRC_SERVER] = SEC_HEADER;
+    CHECK(trans == NHttpTransaction::attach_my_transaction(flow_data, SRC_SERVER));
+
+    section_type[SRC_CLIENT] = SEC_REQUEST;
+    NHttpTransaction* trans2 = NHttpTransaction::attach_my_transaction(flow_data, SRC_CLIENT);
+    CHECK((trans2 != nullptr) && (trans2 != trans));
+    type_expected[SRC_CLIENT] = SEC_HEADER;
+    section_type[SRC_CLIENT] = SEC_HEADER;
+    CHECK(trans2 == NHttpTransaction::attach_my_transaction(flow_data, SRC_CLIENT));
+    type_expected[SRC_CLIENT] = SEC_REQUEST;
+
+    section_type[SRC_SERVER] = SEC_STATUS;
+    trans = NHttpTransaction::attach_my_transaction(flow_data, SRC_SERVER);
+    CHECK((trans != nullptr) && (trans != trans2));
+    section_type[SRC_SERVER] = SEC_HEADER;
+    CHECK(trans == NHttpTransaction::attach_my_transaction(flow_data, SRC_SERVER));
+}
+
+TEST(nhttp_transaction_test, concurrent_request_response_underflow)
+{
+    // Response starts before request completes, response completes first, second response
+    type_expected[SRC_CLIENT] = SEC_REQUEST;
+    section_type[SRC_CLIENT] = SEC_REQUEST;
+    NHttpTransaction* trans = NHttpTransaction::attach_my_transaction(flow_data, SRC_CLIENT);
+    CHECK(trans != nullptr);
+    type_expected[SRC_CLIENT] = SEC_HEADER;
+    section_type[SRC_CLIENT] = SEC_HEADER;
+    CHECK(trans == NHttpTransaction::attach_my_transaction(flow_data, SRC_CLIENT));
+    type_expected[SRC_CLIENT] = SEC_BODY_CHUNK;
+
+    section_type[SRC_SERVER] = SEC_STATUS;
+    CHECK(trans == NHttpTransaction::attach_my_transaction(flow_data, SRC_SERVER));
+    section_type[SRC_SERVER] = SEC_HEADER;
+    CHECK(trans == NHttpTransaction::attach_my_transaction(flow_data, SRC_SERVER));
+    section_type[SRC_SERVER] = SEC_BODY_CHUNK;
+    for (unsigned k=0; k<6; k++)
+    {
+        CHECK(trans == NHttpTransaction::attach_my_transaction(flow_data, SRC_SERVER));
+    }
+    section_type[SRC_SERVER] = SEC_TRAILER;
+    CHECK(trans == NHttpTransaction::attach_my_transaction(flow_data, SRC_SERVER));
+
+    section_type[SRC_CLIENT] = SEC_BODY_CHUNK;
+    for (unsigned k=0; k<4; k++)
+    {
+        CHECK(trans == NHttpTransaction::attach_my_transaction(flow_data, SRC_CLIENT));
+    }
+    type_expected[SRC_CLIENT] = SEC_TRAILER;
+    section_type[SRC_CLIENT] = SEC_TRAILER;
+    CHECK(trans == NHttpTransaction::attach_my_transaction(flow_data, SRC_CLIENT));
+    type_expected[SRC_CLIENT] = SEC_REQUEST;
+
+    section_type[SRC_SERVER] = SEC_STATUS;
+    NHttpTransaction* trans2 = NHttpTransaction::attach_my_transaction(flow_data, SRC_SERVER);
+    CHECK((trans2 != nullptr) && (trans2 != trans));
+    section_type[SRC_SERVER] = SEC_HEADER;
+    CHECK(trans2 == NHttpTransaction::attach_my_transaction(flow_data, SRC_SERVER));
+    section_type[SRC_SERVER] = SEC_BODY_CHUNK;
+    for (unsigned k=0; k<6; k++)
+    {
+        CHECK(trans2 == NHttpTransaction::attach_my_transaction(flow_data, SRC_SERVER));
+    }
+    section_type[SRC_SERVER] = SEC_TRAILER;
+    CHECK(trans2 == NHttpTransaction::attach_my_transaction(flow_data, SRC_SERVER));
+}
+
+TEST(nhttp_transaction_test, basic_continue)
+{
+    // Request with interim response and final response
+    // Request headers
+    type_expected[SRC_CLIENT] = SEC_REQUEST;
+    section_type[SRC_CLIENT] = SEC_REQUEST;
+    NHttpTransaction* trans = NHttpTransaction::attach_my_transaction(flow_data, SRC_CLIENT);
+    CHECK(trans != nullptr);
+    type_expected[SRC_CLIENT] = SEC_HEADER;
+    section_type[SRC_CLIENT] = SEC_HEADER;
+    CHECK(trans == NHttpTransaction::attach_my_transaction(flow_data, SRC_CLIENT));
+    type_expected[SRC_CLIENT] = SEC_BODY_CHUNK;
+
+    // Interim response
+    section_type[SRC_SERVER] = SEC_STATUS;
+    CHECK(trans == NHttpTransaction::attach_my_transaction(flow_data, SRC_SERVER));
+    trans->second_response_coming();
+    section_type[SRC_SERVER] = SEC_HEADER;
+    CHECK(trans == NHttpTransaction::attach_my_transaction(flow_data, SRC_SERVER));
+
+    // Request body
+    section_type[SRC_CLIENT] = SEC_BODY_CHUNK;
+    for (unsigned k=0; k<4; k++)
+    {
+        CHECK(trans == NHttpTransaction::attach_my_transaction(flow_data, SRC_CLIENT));
+    }
+    type_expected[SRC_CLIENT] = SEC_TRAILER;
+    section_type[SRC_CLIENT] = SEC_TRAILER;
+    CHECK(trans == NHttpTransaction::attach_my_transaction(flow_data, SRC_CLIENT));
+    type_expected[SRC_CLIENT] = SEC_REQUEST;
+
+    // Second response
+    section_type[SRC_SERVER] = SEC_STATUS;
+    CHECK(trans == NHttpTransaction::attach_my_transaction(flow_data, SRC_SERVER));
+    section_type[SRC_SERVER] = SEC_HEADER;
+    CHECK(trans == NHttpTransaction::attach_my_transaction(flow_data, SRC_SERVER));
+    section_type[SRC_SERVER] = SEC_BODY_CHUNK;
+    for (unsigned k=0; k<6; k++)
+    {
+        CHECK(trans == NHttpTransaction::attach_my_transaction(flow_data, SRC_SERVER));
+    }
+    section_type[SRC_SERVER] = SEC_TRAILER;
+    CHECK(trans == NHttpTransaction::attach_my_transaction(flow_data, SRC_SERVER));
+}
+
+TEST(nhttp_transaction_test, multiple_continue)
+{
+    // Request with interim response and final response
+    // Request headers
+    type_expected[SRC_CLIENT] = SEC_REQUEST;
+    section_type[SRC_CLIENT] = SEC_REQUEST;
+    NHttpTransaction* trans = NHttpTransaction::attach_my_transaction(flow_data, SRC_CLIENT);
+    CHECK(trans != nullptr);
+    type_expected[SRC_CLIENT] = SEC_HEADER;
+    section_type[SRC_CLIENT] = SEC_HEADER;
+    CHECK(trans == NHttpTransaction::attach_my_transaction(flow_data, SRC_CLIENT));
+    type_expected[SRC_CLIENT] = SEC_BODY_CHUNK;
+
+    // Interim responses
+    for (unsigned k=0; k < 10; k++)
+    {
+        section_type[SRC_SERVER] = SEC_STATUS;
+        CHECK(trans == NHttpTransaction::attach_my_transaction(flow_data, SRC_SERVER));
+        trans->second_response_coming();
+        section_type[SRC_SERVER] = SEC_HEADER;
+        CHECK(trans == NHttpTransaction::attach_my_transaction(flow_data, SRC_SERVER));
+    }
+
+    // Request body
+    section_type[SRC_CLIENT] = SEC_BODY_CHUNK;
+    for (unsigned k=0; k<4; k++)
+    {
+        CHECK(trans == NHttpTransaction::attach_my_transaction(flow_data, SRC_CLIENT));
+    }
+    type_expected[SRC_CLIENT] = SEC_TRAILER;
+    section_type[SRC_CLIENT] = SEC_TRAILER;
+    CHECK(trans == NHttpTransaction::attach_my_transaction(flow_data, SRC_CLIENT));
+    type_expected[SRC_CLIENT] = SEC_REQUEST;
+
+    // Final response
+    section_type[SRC_SERVER] = SEC_STATUS;
+    CHECK(trans == NHttpTransaction::attach_my_transaction(flow_data, SRC_SERVER));
+    section_type[SRC_SERVER] = SEC_HEADER;
+    CHECK(trans == NHttpTransaction::attach_my_transaction(flow_data, SRC_SERVER));
+    section_type[SRC_SERVER] = SEC_BODY_CHUNK;
+    for (unsigned k=0; k<6; k++)
+    {
+        CHECK(trans == NHttpTransaction::attach_my_transaction(flow_data, SRC_SERVER));
+    }
+    section_type[SRC_SERVER] = SEC_TRAILER;
+    CHECK(trans == NHttpTransaction::attach_my_transaction(flow_data, SRC_SERVER));
+}
+
+TEST(nhttp_transaction_test, multiple_orphan_continue)
+{
+    type_expected[SRC_CLIENT] = SEC_REQUEST;
+    // Repeated interim and final response messages without a request
+    for (unsigned k=0; k < 10; k++)
+    {
+        // Interim response
+        section_type[SRC_SERVER] = SEC_STATUS;
+        NHttpTransaction* trans = NHttpTransaction::attach_my_transaction(flow_data, SRC_SERVER);
+        CHECK(trans != nullptr);
+        trans->second_response_coming();
+        section_type[SRC_SERVER] = SEC_HEADER;
+        CHECK(trans == NHttpTransaction::attach_my_transaction(flow_data, SRC_SERVER));
+        section_type[SRC_SERVER] = SEC_BODY_CHUNK;
+        CHECK(trans == NHttpTransaction::attach_my_transaction(flow_data, SRC_SERVER));
+        section_type[SRC_SERVER] = SEC_TRAILER;
+        CHECK(trans == NHttpTransaction::attach_my_transaction(flow_data, SRC_SERVER));
+
+        // Final response
+        section_type[SRC_SERVER] = SEC_STATUS;
+        CHECK(trans == NHttpTransaction::attach_my_transaction(flow_data, SRC_SERVER));
+        section_type[SRC_SERVER] = SEC_HEADER;
+        CHECK(trans == NHttpTransaction::attach_my_transaction(flow_data, SRC_SERVER));
+        section_type[SRC_SERVER] = SEC_BODY_CHUNK;
+        CHECK(trans == NHttpTransaction::attach_my_transaction(flow_data, SRC_SERVER));
+        section_type[SRC_SERVER] = SEC_TRAILER;
+        CHECK(trans == NHttpTransaction::attach_my_transaction(flow_data, SRC_SERVER));
+    }
+}
+
+TEST(nhttp_transaction_test, pipeline_continue_pipeline)
+{
+    // 3.5 requests in pipeline, 3 responses + continue response, body + 3 requests in pipeline,
+    // final response + 3 responses
+    NHttpTransaction* trans[7];
+    // Four requests in pipeline, the final one will be continued later
+    for (unsigned k=0; k < 4; k++)
+    {
+        type_expected[SRC_CLIENT] = SEC_REQUEST;
+        section_type[SRC_CLIENT] = SEC_REQUEST;
+        trans[k] = NHttpTransaction::attach_my_transaction(flow_data, SRC_CLIENT);
+        CHECK(trans[k] != nullptr);
+        type_expected[SRC_CLIENT] = SEC_HEADER;
+        section_type[SRC_CLIENT] = SEC_HEADER;
+        CHECK(trans[k] == NHttpTransaction::attach_my_transaction(flow_data, SRC_CLIENT));
+        for (unsigned j=0; j < k; j++)
+        {
+            CHECK(trans[k] != trans[j]);
+        }
+    }
+    type_expected[SRC_CLIENT] = SEC_BODY_CL;
+
+    // Three responses to the pipeline
+    for (unsigned k=0; k < 3; k++)
+    {
+        section_type[SRC_SERVER] = SEC_STATUS;
+        CHECK(trans[k] == NHttpTransaction::attach_my_transaction(flow_data, SRC_SERVER));
+        section_type[SRC_SERVER] = SEC_HEADER;
+        CHECK(trans[k] == NHttpTransaction::attach_my_transaction(flow_data, SRC_SERVER));
+        section_type[SRC_SERVER] = SEC_BODY_CL;
+        CHECK(trans[k] == NHttpTransaction::attach_my_transaction(flow_data, SRC_SERVER));
+    }
+
+    // Interim response to fourth request
+    section_type[SRC_SERVER] = SEC_STATUS;
+    CHECK(trans[3] == NHttpTransaction::attach_my_transaction(flow_data, SRC_SERVER));
+    trans[3]->second_response_coming();
+    section_type[SRC_SERVER] = SEC_HEADER;
+    CHECK(trans[3] == NHttpTransaction::attach_my_transaction(flow_data, SRC_SERVER));
+
+    // Finish the fourth request
+    section_type[SRC_CLIENT] = SEC_BODY_CL;
+    CHECK(trans[3] == NHttpTransaction::attach_my_transaction(flow_data, SRC_CLIENT));
+
+    // Requests 5-7 in pipeline
+    for (unsigned k=4; k < 7; k++)
+    {
+        type_expected[SRC_CLIENT] = SEC_REQUEST;
+        section_type[SRC_CLIENT] = SEC_REQUEST;
+        trans[k] = NHttpTransaction::attach_my_transaction(flow_data, SRC_CLIENT);
+        CHECK(trans[k] != nullptr);
+        type_expected[SRC_CLIENT] = SEC_HEADER;
+        section_type[SRC_CLIENT] = SEC_HEADER;
+        CHECK(trans[k] == NHttpTransaction::attach_my_transaction(flow_data, SRC_CLIENT));
+        for (unsigned j=5; j < k; j++)
+        {
+            CHECK(trans[k] != trans[j]);
+        }
+    }
+    type_expected[SRC_CLIENT] = SEC_REQUEST;
+
+    // Final response to 4 and responses to 5-7
+    for (unsigned k=3; k < 7; k++)
+    {
+        section_type[SRC_SERVER] = SEC_STATUS;
+        CHECK(trans[k] == NHttpTransaction::attach_my_transaction(flow_data, SRC_SERVER));
+        section_type[SRC_SERVER] = SEC_HEADER;
+        CHECK(trans[k] == NHttpTransaction::attach_my_transaction(flow_data, SRC_SERVER));
+        section_type[SRC_SERVER] = SEC_BODY_CL;
+        CHECK(trans[k] == NHttpTransaction::attach_my_transaction(flow_data, SRC_SERVER));
+    }
+}
+
+int main(int argc, char** argv)
+{
+    return CommandLineTestRunner::RunAllTests(argc, argv);
+}
+