From: Russ Combs (rucombs) Date: Mon, 11 Jul 2016 12:36:54 +0000 (-0400) Subject: Merge pull request #551 in SNORT/snort3 from nhttp47 to master X-Git-Tag: 3.0.0-233~341 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=65b2801;p=thirdparty%2Fsnort3.git Merge pull request #551 in SNORT/snort3 from nhttp47 to master Squashed commit of the following: commit 3b1af0f2fb6abc2680f63916e6e6dcba9a355187 Author: Tom Peters Date: Fri Jun 17 17:22:02 2016 -0400 Properly track transactions that include 100 status code --- diff --git a/src/service_inspectors/nhttp_inspect/nhttp_flow_data.cc b/src/service_inspectors/nhttp_inspect/nhttp_flow_data.cc index 9008fa7d1..a0a068e05 100644 --- a/src/service_inspectors/nhttp_inspect/nhttp_flow_data.cc +++ b/src/service_inspectors/nhttp_inspect/nhttp_flow_data.cc @@ -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; } diff --git a/src/service_inspectors/nhttp_inspect/nhttp_flow_data.h b/src/service_inspectors/nhttp_inspect/nhttp_flow_data.h index 096955aa7..13c6824be 100644 --- a/src/service_inspectors/nhttp_inspect/nhttp_flow_data.h +++ b/src/service_inspectors/nhttp_inspect/nhttp_flow_data.h @@ -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 }; diff --git a/src/service_inspectors/nhttp_inspect/nhttp_inspect.cc b/src/service_inspectors/nhttp_inspect/nhttp_inspect.cc index 15b5e665e..39157cab7 100644 --- a/src/service_inspectors/nhttp_inspect/nhttp_inspect.cc +++ b/src/service_inspectors/nhttp_inspect/nhttp_inspect.cc @@ -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); } } diff --git a/src/service_inspectors/nhttp_inspect/nhttp_msg_request.cc b/src/service_inspectors/nhttp_inspect/nhttp_msg_request.cc index c4afe8378..38a75cd67 100644 --- a/src/service_inspectors/nhttp_inspect/nhttp_msg_request.cc +++ b/src/service_inspectors/nhttp_inspect/nhttp_msg_request.cc @@ -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 diff --git a/src/service_inspectors/nhttp_inspect/nhttp_msg_section.cc b/src/service_inspectors/nhttp_inspect/nhttp_msg_section.cc index f9d617b67..f313e4eb7 100644 --- a/src/service_inspectors/nhttp_inspect/nhttp_msg_section.cc +++ b/src/service_inspectors/nhttp_inspect/nhttp_msg_section.cc @@ -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"); } diff --git a/src/service_inspectors/nhttp_inspect/nhttp_msg_section.h b/src/service_inspectors/nhttp_inspect/nhttp_msg_section.h index 61c583466..08115ec89 100644 --- a/src/service_inspectors/nhttp_inspect/nhttp_msg_section.h +++ b/src/service_inspectors/nhttp_inspect/nhttp_msg_section.h @@ -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; diff --git a/src/service_inspectors/nhttp_inspect/nhttp_msg_status.cc b/src/service_inspectors/nhttp_inspect/nhttp_msg_status.cc index 7029d3c15..fc91c2fe4 100644 --- a/src/service_inspectors/nhttp_inspect/nhttp_msg_status.cc +++ b/src/service_inspectors/nhttp_inspect/nhttp_msg_status.cc @@ -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; } diff --git a/src/service_inspectors/nhttp_inspect/nhttp_stream_splitter_scan.cc b/src/service_inspectors/nhttp_inspect/nhttp_stream_splitter_scan.cc index dd7a27844..e5830670e 100644 --- a/src/service_inspectors/nhttp_inspect/nhttp_stream_splitter_scan.cc +++ b/src/service_inspectors/nhttp_inspect/nhttp_stream_splitter_scan.cc @@ -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 diff --git a/src/service_inspectors/nhttp_inspect/nhttp_transaction.cc b/src/service_inspectors/nhttp_inspect/nhttp_transaction.cc index add162dbb..db7b0d198 100644 --- a/src/service_inspectors/nhttp_inspect/nhttp_transaction.cc +++ b/src/service_inspectors/nhttp_inspect/nhttp_transaction.cc @@ -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_; +} + diff --git a/src/service_inspectors/nhttp_inspect/nhttp_transaction.h b/src/service_inspectors/nhttp_inspect/nhttp_transaction.h index d039d5331..7d51bb704 100644 --- a/src/service_inspectors/nhttp_inspect/nhttp_transaction.h +++ b/src/service_inspectors/nhttp_inspect/nhttp_transaction.h @@ -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 diff --git a/src/service_inspectors/nhttp_inspect/test/CMakeLists.txt b/src/service_inspectors/nhttp_inspect/test/CMakeLists.txt index cee1fa8af..4e3bd2b35 100644 --- a/src/service_inspectors/nhttp_inspect/test/CMakeLists.txt +++ b/src/service_inspectors/nhttp_inspect/test/CMakeLists.txt @@ -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) diff --git a/src/service_inspectors/nhttp_inspect/test/Makefile.am b/src/service_inspectors/nhttp_inspect/test/Makefile.am index 1f28e9cd1..27197ea80 100644 --- a/src/service_inspectors/nhttp_inspect/test/Makefile.am +++ b/src/service_inspectors/nhttp_inspect/test/Makefile.am @@ -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 index 000000000..139e4e9f1 --- /dev/null +++ b/src/service_inspectors/nhttp_inspect/test/nhttp_transaction_test.cc @@ -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 +// 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 +#include +#include + +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); +} +