]> git.ipfire.org Git - thirdparty/snort3.git/commitdiff
Merge pull request #1571 in SNORT/snort3 from ~RUCOMBS/snort3:wcochran53 to master
authorRuss Combs (rucombs) <rucombs@cisco.com>
Tue, 9 Apr 2019 13:33:49 +0000 (09:33 -0400)
committerRuss Combs (rucombs) <rucombs@cisco.com>
Tue, 9 Apr 2019 13:33:49 +0000 (09:33 -0400)
Squashed commit of the following:

commit 4c3045b03aaafc429c017dbffd3887c7031773b4
Author: russ <rucombs@cisco.com>
Date:   Sun Apr 7 22:09:02 2019 -0400

    offload: simplify zero byte bypass

commit 4b038913ceb7598ec61f6bef1b0b5b156ab013f6
Author: William Cochrane <w.cochrane@titan-ic.com>
Date:   Tue Mar 26 12:14:29 2019 +0000

    offload: Framework changes to support polling for completed
    batch searches

    When a batch search is issued, currently we poll to
    determine if that batch has completed its search.
    This change facilitates polling to return any batch
    that has completed its search.

commit 65a967dd7731286ba101a144d428554e9ad75cc0
Author: William Cochrane <w.cochrane@titan-ic.com>
Date:   Fri Mar 22 16:25:36 2019 +0000

    mpse: Adding performance profiling stats to Mpse batch search

    The Mpse batch search function does not have any
    performance profiling so this function is now wrapped
    to facilitate the addition of performance stats

commit 9140669833d97bd5f8e9ada4e2868576e82e5622
Author: William Cochrane <w.cochrane@titan-ic.com>
Date:   Thu Mar 21 18:00:34 2019 +0000

    detection: Don't send zero size searches to the regex offloader

    If a batch search request had nothing in it to be
    searched for there is no purpose in sending it to
    the offloader

commit 6f1b0ad1baa1a784d70403ef9786ca396d9ba850
Author: William Cochrane <w.cochrane@titan-ic.com>
Date:   Thu Mar 21 17:23:27 2019 +0000

    detection: Ensure offload search engine started with appropriate regex offloader

    If the offload_search_method is not specified then by
    default it will be the same as the normal search_method.
    If this search method is an async mpse it needs started
    using the MpseRegexOffload offloader otherwise it needs
    started using the ThreadRegexOffload offloader

19 files changed:
src/detection/detection_engine.cc
src/detection/detection_engine.h
src/detection/fp_detect.cc
src/detection/ips_context.h
src/detection/regex_offload.cc
src/framework/mpse.cc
src/framework/mpse.h
src/framework/mpse_batch.h
src/managers/mpse_manager.cc
src/managers/mpse_manager.h
src/search_engines/ac_banded.cc
src/search_engines/ac_bnfa.cc
src/search_engines/ac_full.cc
src/search_engines/ac_sparse.cc
src/search_engines/ac_sparse_bands.cc
src/search_engines/ac_std.cc
src/search_engines/hyperscan.cc
src/search_engines/test/hyperscan_test.cc
src/search_engines/test/search_tool_test.cc

index 0cc78b38da64b88a23753393b760fe222a81cceb..1c0522497cedb193f106ed0343ee8007c0507291 100644 (file)
@@ -71,15 +71,28 @@ void DetectionEngine::thread_init()
     // Note: offload_threads is really the maximum number of offload_requests
     if (offload_search_api and MpseManager::is_async_capable(offload_search_api))
     {
+        // Check that poll functionality has been provided
+        assert(MpseManager::is_poll_capable(offload_search_api));
+
         // If the search method is async capable then the searches will be performed directly
         // by the search engine, without requiring a processing thread.
         offloader = RegexOffload::get_offloader(sc->offload_threads, false);
     }
     else
     {
-        // If the search method is not async capable then offloaded searches will be performed
-        // in a separate processing thread that the RegexOffload instance needs to create.
-        offloader = RegexOffload::get_offloader(sc->offload_threads, true);
+        const MpseApi* search_api = fp->get_search_api();
+
+        if (MpseManager::is_async_capable(search_api))
+        {
+            assert(MpseManager::is_poll_capable(search_api));
+            offloader = RegexOffload::get_offloader(sc->offload_threads, false);
+        }
+        else
+        {
+            // If the search method is not async capable then offloaded searches will be performed
+            // in a separate processing thread that the RegexOffload instance needs to create.
+            offloader = RegexOffload::get_offloader(sc->offload_threads, true);
+        }
     }
 }
 
@@ -342,26 +355,48 @@ bool DetectionEngine::get_check_tags()
 // offload / onload
 //--------------------------------------------------------------------------
 
-void DetectionEngine::do_offload(Packet* p)
+bool DetectionEngine::do_offload(Packet* p)
 {
     ContextSwitcher* sw = Snort::get_switcher();
 
     assert(p == p->context->packet);
     assert(p->context == sw->get_context());
 
-    trace_logf(detection, TRACE_DETECTION_ENGINE, "%" PRIu64 " de::offload %" PRIu64 " (r=%d)\n",
-        p->context->packet_number, p->context->context_num, offloader->count());
-
-    sw->suspend();
-
     p->context->conf = SnortConfig::get_conf();
-    p->set_offloaded();
 
     // Build the searches list in the packet context
     fp_partial(p);
 
-    offloader->put(p);
-    pc.offloads++;
+    if (p->context->searches.items.size() > 0)
+    {
+        trace_logf(detection, TRACE_DETECTION_ENGINE, "%" PRIu64 " de::offload %" PRIu64
+                " (r=%d)\n", p->context->packet_number, p->context->context_num,
+                offloader->count());
+
+        sw->suspend();
+        p->set_offloaded();
+
+        offloader->put(p);
+        pc.offloads++;
+
+        return true;
+    }
+    else
+    {
+        bool depends_on_suspended = p->flow ? p->flow->context_chain.front() :
+            sw->non_flow_chain.front();
+
+        if (!depends_on_suspended)
+        {
+            fp_complete(p);
+            return false;
+        }
+        else
+        {
+            sw->suspend();
+            return true;
+        }
+    }
 }
 
 bool DetectionEngine::offload(Packet* p)
@@ -374,8 +409,7 @@ bool DetectionEngine::offload(Packet* p)
 
     if ( can_offload and should_offload )
     {
-        do_offload(p);
-        return true;
+        return do_offload(p);
     }
 
     if ( depends_on_suspended )
index 87ef8c25e2244e3ca12684a5b2b80cef7ed7f03b..07665cb25a8233bb9d56d61f960c371546b417c5 100644 (file)
@@ -103,7 +103,7 @@ public:
 
 private:
     static struct SF_EVENTQ* get_event_queue();
-    static void do_offload(snort::Packet*);
+    static bool do_offload(snort::Packet*);
     static void offload_thread(IpsContext*);
     static void resume(snort::Packet*);
     static void resume_ready_suspends(IpsContextChain&);
index 5fd380f4f89cbdca20311587f958ef4d187e3e90..7bb88e0dd2ff850c62d28541f61d2b1e1d151536 100644 (file)
@@ -358,7 +358,7 @@ static int rule_tree_match(
     void* user, void* tree, int index, void* context, void* neg_list)
 {
     PMX* pmx = (PMX*)user;
-    OtnxMatchData* pomd = (OtnxMatchData*)context;
+    OtnxMatchData* pomd = ((IpsContext*)context)->otnx;
 
     detection_option_tree_root_t* root = (detection_option_tree_root_t*)tree;
     detection_option_eval_data_t eval_data;
@@ -856,7 +856,7 @@ void fp_clear_context(IpsContext& c)
 static int rule_tree_queue(
     void* user, void* tree, int index, void* context, void* list)
 {
-    OtnxMatchData* pomd = (OtnxMatchData*)context;
+    OtnxMatchData* pomd = ((IpsContext*)context)->otnx;
     MpseStash* stash = pomd->p->context->stash;
 
     if ( stash->push(user, tree, index, list) )
@@ -880,8 +880,8 @@ static inline int batch_search(
         int start_state = 0;
         MpseStash* stash = omd->p->context->stash;
         stash->init();
-        so->get_normal_mpse()->search(buf, len, rule_tree_queue, omd, &start_state);
-        stash->process(rule_tree_match, omd);
+        so->get_normal_mpse()->search(buf, len, rule_tree_queue, omd->p->context, &start_state);
+        stash->process(rule_tree_match, omd->p->context);
     }
     else
     {
@@ -1307,11 +1307,11 @@ void fp_full(Packet* p)
     init_match_info(c->otnx);
 
     c->searches.mf = rule_tree_queue;
-    c->searches.context = c->otnx;
+    c->searches.context = c;
     fpEvalPacket(p, FPTask::BOTH);
 
     if ( c->searches.search_sync() )
-        stash->process(rule_tree_match, c->otnx);
+        stash->process(rule_tree_match, c);
 
     fpFinalSelectEvent(c->otnx, p);
 }
@@ -1325,7 +1325,7 @@ void fp_partial(Packet* p)
     stash->disable_process();
     init_match_info(c->otnx);
     c->searches.mf = rule_tree_queue;
-    c->searches.context = c->otnx;
+    c->searches.context = c;
     fpEvalPacket(p, FPTask::FP);
 }
 
@@ -1334,7 +1334,7 @@ void fp_complete(Packet* p)
     IpsContext* c = p->context;
     MpseStash* stash = c->stash;
     stash->enable_process();
-    stash->process(rule_tree_match, c->otnx);
+    stash->process(rule_tree_match, c);
     fpEvalPacket(p, FPTask::NON_FP);
     fpFinalSelectEvent(c->otnx, p);
 }
index 9a68f490c1878c48d79abb783d37c61ba9a82813..a501e13f9513fef97e0baf70bbd8fd63e0ac916a 100644 (file)
 // single packet.  the state is stored in IpsContextData instances, which
 // are accessed by id.
 
-#include "main/snort_types.h"
+#include <list>
+
+#include "detection/detection_util.h"
 #include "framework/codec.h"
 #include "framework/mpse.h"
 #include "framework/mpse_batch.h"
-
-// required to get a decent decl of pkth
-#include "protocols/packet.h"
-
-#include "detection/detection_util.h"
+#include "main/snort_types.h"
+#include "protocols/packet.h" // required to get a decent decl of pkth
 
 class MpseStash;
 struct OtnxMatchData;
 struct SF_EVENTQ;
+struct RegexRequest;
 
 namespace snort
 {
@@ -143,6 +143,7 @@ public:
     MpseBatch searches;
     MpseStash* stash;
     OtnxMatchData* otnx;
+    std::list<RegexRequest*>::iterator regex_req_it;
     SF_EVENTQ* equeue;
 
     DataPointer file_data;
index 52def1566fc7866af06e4a86f904e4c11dcb7eb5..9a426453db7c46ad62b5a6137577c1a423c371a0 100644 (file)
@@ -108,49 +108,50 @@ void MpseRegexOffload::put(snort::Packet* p)
 {
     assert(p);
     assert(!idle.empty());
+    assert(p->context->searches.items.size() > 0);
 
     RegexRequest* req = idle.front();
     idle.pop_front();  // FIXIT-H use splice to move instead
+
     busy.emplace_back(req);
+    // Because a list is a doubly linked list we can store the iterator
+    // for later quick removal of this item from the list
+    p->context->regex_req_it = std::prev(busy.end());
 
     req->packet = p;
-
-    if (p->context->searches.items.size() > 0)
-        p->context->searches.offload_search();
+    p->context->searches.offload_search();
 }
 
 bool MpseRegexOffload::get(snort::Packet*& p)
 {
     assert(!busy.empty());
 
-    for ( auto i = busy.begin(); i != busy.end(); i++ )
-    {
-        RegexRequest* req = *i;
-        snort::IpsContext* c = req->packet->context;
-
-        if ( c->searches.items.size() > 0 )
-        {
-            snort::Mpse::MpseRespType resp_ret = c->searches.receive_offload_responses();
+    snort::Mpse::MpseRespType resp_ret;
+    snort::MpseBatch* batch;
 
-            if (resp_ret == snort::Mpse::MPSE_RESP_NOT_COMPLETE)
-                continue;
+    resp_ret = snort::MpseBatch::poll_offload_responses(batch);
 
-            else if (resp_ret == snort::Mpse::MPSE_RESP_COMPLETE_FAIL)
+    if (resp_ret != snort::Mpse::MPSE_RESP_NOT_COMPLETE)
+    {
+        if (resp_ret == snort::Mpse::MPSE_RESP_COMPLETE_FAIL)
+        {
+            if (batch->can_fallback())
             {
-                if (c->searches.can_fallback())
-                {
-                    // FIXIT-M Add peg counts to record offload search fallback attempts
-                    c->searches.search_sync();
-                }
-                // FIXIT-M else Add peg counts to record offload search failures
+                // FIXIT-M Add peg counts to record offload search fallback attempts
+                batch->search_sync();
             }
-            c->searches.items.clear();
+            // FIXIT-M else Add peg counts to record offload search failures
         }
 
-        p = req->packet;
-        req->packet = nullptr;
+        snort::IpsContext* c = (snort::IpsContext*)(batch->context);
+        p = c->packet;
 
-        busy.erase(i);
+        // Finished with items in batch so clear
+        batch->items.clear();
+
+        RegexRequest* req = *(c->regex_req_it);
+        req->packet = nullptr;
+        busy.erase(c->regex_req_it);
         idle.emplace_back(req);
 
         return true;
@@ -195,19 +196,19 @@ void ThreadRegexOffload::put(snort::Packet* p)
 {
     assert(p);
     assert(!idle.empty());
+    assert(p->context->searches.items.size() > 0);
 
     RegexRequest* req = idle.front();
     idle.pop_front();  // FIXIT-H use splice to move instead
+
     busy.emplace_back(req);
+    p->context->regex_req_it = std::prev(busy.end());
 
     std::unique_lock<std::mutex> lock(req->mutex);
     req->packet = p;
 
-    if (p->context->searches.items.size() > 0)
-    {
-        req->offload = true;
-        req->cond.notify_one();
-    }
+    req->offload = true;
+    req->cond.notify_one();
 }
 
 bool ThreadRegexOffload::get(snort::Packet*& p)
@@ -222,6 +223,7 @@ bool ThreadRegexOffload::get(snort::Packet*& p)
             continue;
 
         p = req->packet;
+        assert(p->context->regex_req_it == i);
         req->packet = nullptr;
 
         busy.erase(i);
@@ -260,27 +262,27 @@ void ThreadRegexOffload::worker(RegexRequest* req, snort::SnortConfig* initial_c
         assert(req->packet->is_offloaded());
         assert(req->packet->context->searches.items.size() > 0);
 
-        snort::MpseBatch& batch = req->packet->context->searches;
-        batch.offload_search();
+        snort::IpsContext* c = req->packet->context;
         snort::Mpse::MpseRespType resp_ret;
 
+        c->searches.offload_search();
         do
         {
-            resp_ret = batch.receive_offload_responses();
+            resp_ret = c->searches.receive_offload_responses();
         }
         while (resp_ret == snort::Mpse::MPSE_RESP_NOT_COMPLETE);
 
         if (resp_ret == snort::Mpse::MPSE_RESP_COMPLETE_FAIL)
         {
-            if (batch.can_fallback())
+            if (c->searches.can_fallback())
             {
                 // FIXIT-M Add peg counts to record offload search fallback attempts
-                batch.search_sync();
+                c->searches.search_sync();
             }
             // FIXIT-M else Add peg counts to record offload search failures
         }
 
-        batch.items.clear();
+        c->searches.items.clear();
         req->offload = false;
     }
     snort::ModuleManager::accumulate_offload("search_engine");
index 7d014f3aeaee6636e49f260c2ffad8b64ab7cef0..e23529fdd184f7964ef3048e5c1c4765fc638309 100644 (file)
@@ -70,6 +70,12 @@ int Mpse::search_all(
 }
 
 void Mpse::search(MpseBatch& batch, MpseType mpse_type)
+{
+    DeepProfile profile(mpsePerfStats);
+    _search(batch, mpse_type);
+}
+
+void Mpse::_search(MpseBatch& batch, MpseType mpse_type)
 {
     int start_state;
 
@@ -100,5 +106,33 @@ void Mpse::search(MpseBatch& batch, MpseType mpse_type)
     }
 }
 
+Mpse::MpseRespType Mpse::poll_responses(MpseBatch*& batch, MpseType mpse_type)
+{
+    const MpseApi* search_api = nullptr;
+
+    if ( SnortConfig::get_conf()->fast_pattern_config )
+    {
+        switch (mpse_type)
+        {
+            case MPSE_TYPE_NORMAL:
+                search_api = SnortConfig::get_conf()->fast_pattern_config->get_search_api();
+                break;
+            case MPSE_TYPE_OFFLOAD:
+                search_api = SnortConfig::get_conf()->fast_pattern_config->get_offload_search_api();
+                if (!search_api)
+                    search_api = SnortConfig::get_conf()->fast_pattern_config->get_search_api();
+                break;
+        }
+    }
+
+    if (search_api)
+    {
+        assert(search_api->poll);
+        return search_api->poll(batch, mpse_type);
+    }
+
+    return MPSE_RESP_NOT_COMPLETE;
+}
+
 }
 
index 63b13e9ba812e6e8c35c37c64abdd13ef3936786..deb4ca6e20140d8522239e3050c13d64f14ae82f 100644 (file)
@@ -88,11 +88,13 @@ public:
     virtual int search_all(
         const uint8_t* T, int n, MpseMatch, void* context, int* current_state);
 
-    virtual void search(MpseBatch& batch, MpseType mpse_type);
+    void search(MpseBatch&, MpseType);
 
-    virtual MpseRespType receive_responses(MpseBatch&)
+    virtual MpseRespType receive_responses(MpseBatch&, MpseType)
     { return MPSE_RESP_COMPLETE_SUCCESS; }
 
+    static MpseRespType poll_responses(MpseBatch*&, MpseType);
+
     virtual void set_opt(int) { }
     virtual int print_info() { return 0; }
     virtual int get_pattern_count() const { return 0; }
@@ -109,6 +111,8 @@ protected:
     virtual int _search(
         const uint8_t* T, int n, MpseMatch, void* context, int* current_state) = 0;
 
+    virtual void _search(MpseBatch&, MpseType);
+
 private:
     std::string method;
     int verbose;
@@ -125,6 +129,8 @@ typedef Mpse* (* MpseNewFunc)(
 
 typedef void (* MpseDelFunc)(Mpse*);
 
+typedef Mpse::MpseRespType (* MpsePollFunc)(MpseBatch*&, Mpse::MpseType);
+
 #define MPSE_BASE   0x00
 #define MPSE_TRIM   0x01
 #define MPSE_REGEX  0x02
@@ -143,6 +149,7 @@ struct MpseApi
     MpseDelFunc dtor;
     MpseExeFunc init;
     MpseExeFunc print;
+    MpsePollFunc poll;
 };
 }
 #endif
index 0903e4c76b367ac9a3b803e8d75dd636b80f0c43..d09e0ad3761fcb14ba1b1de18c217ba9583aed50 100644 (file)
@@ -116,6 +116,13 @@ struct MpseBatch
 
     bool search_sync();
     bool can_fallback() const;
+
+    static Mpse::MpseRespType poll_responses(MpseBatch*& batch)
+    { return Mpse::poll_responses(batch, snort::Mpse::MPSE_TYPE_NORMAL); }
+
+    static Mpse::MpseRespType poll_offload_responses(MpseBatch*& batch)
+    { return Mpse::poll_responses(batch, snort::Mpse::MPSE_TYPE_OFFLOAD); }
+
 };
 
 inline void MpseBatch::search()
@@ -126,7 +133,8 @@ inline void MpseBatch::search()
 
 inline Mpse::MpseRespType MpseBatch::receive_responses()
 {
-    return items.begin()->second.so[0]->get_normal_mpse()->receive_responses(*this);
+    return items.begin()->second.so[0]->get_normal_mpse()->
+        receive_responses(*this, Mpse::MPSE_TYPE_NORMAL);
 }
 
 inline void MpseBatch::offload_search()
@@ -142,7 +150,7 @@ inline Mpse::MpseRespType MpseBatch::receive_offload_responses()
     assert(items.begin()->second.so[0]->get_offload_mpse());
 
     return items.begin()->second.so[0]->get_offload_mpse()->
-        receive_responses(*this);
+        receive_responses(*this, Mpse::MPSE_TYPE_OFFLOAD);
 }
 
 inline bool MpseBatch::can_fallback() const
index 18ae48d8be3ea8624e036c0c9cef85d029baa9ef..656e9d2dfd84cc5445dfd686fcd18ba01d5927d9 100644 (file)
@@ -183,6 +183,12 @@ bool MpseManager::is_regex_capable(const MpseApi* api)
     return (api->flags & MPSE_REGEX) != 0;
 }
 
+bool MpseManager::is_poll_capable(const MpseApi* api)
+{
+    assert(api);
+    return (api->poll);
+}
+
 // was called during drop stats but actually commented out
 // FIXIT-M this one has to accumulate across threads
 #if 0
index d6a84616b4a8dcc752ffe98556acf5d7283802fd..806947f660d74054f808452836cd3274e34354a0 100644 (file)
@@ -78,6 +78,7 @@ public:
     static bool search_engine_trim(const snort::MpseApi*);
     static bool is_async_capable(const snort::MpseApi*);
     static bool is_regex_capable(const snort::MpseApi*);
+    static bool is_poll_capable(const snort::MpseApi* api);
     static void print_mpse_summary(const snort::MpseApi*);
     static void print_search_engine_stats();
 
index cfaf00d7e336f8b3160d1f734fe0822c3d4ad624..e7009da3604ddfabd5ce49ef3bdb416ef0af7910 100644 (file)
@@ -131,6 +131,7 @@ static const MpseApi acb_api =
     acb_dtor,
     acb_init,
     acb_print,
+    nullptr,
 };
 
 const BaseApi* se_ac_banded = &acb_api.base;
index 902296089ecf28999df28bce059d698d6c762870..8010ec515986edae336a11051167a3c726932a46 100644 (file)
@@ -149,6 +149,7 @@ static const MpseApi bnfa_api =
     bnfa_dtor,
     bnfa_init,
     bnfa_print,
+    nullptr,
 };
 
 const BaseApi* se_ac_bnfa[] =
index 04a5f6bbe19c54d4cb8b82f490a8011895e8ff0a..ad542c7f10ad245b3f0121d6c7fd188bdfacc41f 100644 (file)
@@ -138,6 +138,7 @@ static const MpseApi acf_api =
     acf_dtor,
     acf_init,
     acf_print,
+    nullptr,
 };
 
 const BaseApi* se_ac_full = &acf_api.base;
index 8ab5f56b7874d90a3786a18ccccf358bf4e237dd..b5c89c1bea46fd29d2d0dd9e29eb0c10168f7b5d 100644 (file)
@@ -125,6 +125,7 @@ static const MpseApi acs_api =
     acs_dtor,
     acs_init,
     acs_print,
+    nullptr,
 };
 
 const BaseApi* se_ac_sparse = &acs_api.base;
index b09101d93eeec1520beb8e5ce884412a6fd37434..61b3144c1c8d99e11f719c057cf9112dff183524 100644 (file)
@@ -125,6 +125,7 @@ static const MpseApi acsb_api =
     acsb_dtor,
     acsb_init,
     acsb_print,
+    nullptr,
 };
 
 const BaseApi* se_ac_sparse_bands = &acsb_api.base;
index 103abf2c2427467e43b7021b5f8a7076b4df0b1b..bd090c5b551b250b92787c333e1e1e0f7c6bf1d2 100644 (file)
@@ -116,6 +116,7 @@ static const MpseApi ac_api =
     ac_dtor,
     ac_init,
     ac_print,
+    nullptr,
 };
 
 #ifdef BUILDING_SO
index 5c9013e6b4be8a85c64c492637e85b6032bcc4a0..15c1201f893bf3550a7a213450310546b5981558 100644 (file)
@@ -378,6 +378,7 @@ static const MpseApi hs_api =
     hs_dtor,
     hs_init,
     hs_print,
+    nullptr,
 };
 
 #ifdef BUILDING_SO
index 9d95fb8313bd288b42276710c0ae62be4a092a89..924f36ec6fee423791a83ed1c6416c8a75c684ca 100644 (file)
@@ -57,6 +57,11 @@ int Mpse::search_all(
 }
 
 void Mpse::search(MpseBatch& batch, MpseType mpse_type)
+{
+    _search(batch, mpse_type);
+}
+
+void Mpse::_search(MpseBatch& batch, MpseType mpse_type)
 {
     int start_state;
 
index 89d90a83b198623dc9d127e407fa4c2154cf0153..e28286811cd44c26cee39ad3ad2248b2d8b6c8ce 100644 (file)
@@ -113,6 +113,11 @@ int Mpse::search_all(
 }
 
 void Mpse::search(MpseBatch& batch, MpseType mpse_type)
+{
+    _search(batch, mpse_type);
+}
+
+void Mpse::_search(MpseBatch& batch, MpseType mpse_type)
 {
     int start_state;