]> git.ipfire.org Git - thirdparty/suricata.git/commitdiff
mpm: add Hyperscan integration
authorJustin Viiret <justin.viiret@intel.com>
Tue, 15 Mar 2016 01:40:24 +0000 (12:40 +1100)
committerVictor Julien <victor@inliniac.net>
Tue, 29 Mar 2016 10:43:29 +0000 (12:43 +0200)
This adds an MPM implementation that uses the Hyperscan regex engine
library from Intel, accessible as the "hs" mpm-algo.

configure.ac
src/Makefile.am
src/detect-engine.c
src/runmode-unittests.c
src/suricata.c
src/util-mpm-hs.c [new file with mode: 0644]
src/util-mpm-hs.h [new file with mode: 0644]
src/util-mpm.c
src/util-mpm.h

index 16e0dcfb42b4dce84c4d7212aa822cd8c2bf80eb..2b05189718da186ba6156c8b55d3136526036e46 100644 (file)
         AC_MSG_RESULT(no)
     fi
 
+  # libhs
+    enable_hyperscan="no"
+
+    # Try pkg-config first:
+    PKG_CHECK_MODULES([libhs], libhs,, [with_pkgconfig_libhs=no])
+    if test "$with_pkgconfig_libhs" != "no"; then
+        CPPFLAGS="${CPPFLAGS} ${libhs_CFLAGS}"
+        LIBS="${LIBS} ${libhs_LIBS}"
+    fi
+
+    AC_ARG_WITH(libhs_includes,
+            [  --with-libhs-includes=DIR  libhs include directory],
+            [with_libhs_includes="$withval"],[with_libhs_includes=no])
+    AC_ARG_WITH(libhs_libraries,
+            [  --with-libhs-libraries=DIR    libhs library directory],
+            [with_libhs_libraries="$withval"],[with_libhs_libraries="no"])
+
+    if test "$with_libhs_includes" != "no"; then
+        CPPFLAGS="${CPPFLAGS} -I${with_libhs_includes}"
+    fi
+    AC_CHECK_HEADER(hs.h,HYPERSCAN="yes",HYPERSCAN="no")
+    if test "$HYPERSCAN" = "yes"; then
+        if test "$with_libhs_libraries" != "no"; then
+            LDFLAGS="${LDFLAGS}  -L${with_libhs_libraries}"
+        fi
+
+        AC_CHECK_LIB(hs,hs_compile,,HYPERSCAN="no")
+        enable_hyperscan="yes"
+        if test "$HYPERSCAN" = "no"; then
+            echo
+            echo "   Hyperscan headers are present, but link test failed."
+            echo "   Check that you have a shared library and C++ linkage available."
+            echo
+            enable_hyperscan="no"
+        fi
+    fi
+    AS_IF([test "x$enable_hyperscan" = "xyes"], [AC_DEFINE([BUILD_HYPERSCAN], [1], [Intel Hyperscan support enabled])])
+
   # libyaml
     AC_ARG_WITH(libyaml_includes,
             [  --with-libyaml-includes=DIR  libyaml include directory],
@@ -1896,6 +1934,7 @@ SURICATA_BUILD_CONF="Suricata Configuration:
   Non-bundled htp:                         ${enable_non_bundled_htp}
   Old barnyard2 support:                   ${enable_old_barnyard2}
   CUDA enabled:                            ${enable_cuda}
+  Hyperscan support:                       ${enable_hyperscan}
 
   Suricatasc install:                      ${enable_python}
 
index 9555a28c50199319497dd31a12f049cf6a29f37e..aed9022e1a58cc9eac121ddea3941c316c0711e8 100644 (file)
@@ -372,6 +372,7 @@ util-mpm-ac-tile.c util-mpm-ac-tile.h \
 util-mpm-ac-tile-small.c \
 util-mpm-b2g.c util-mpm-b2g.h \
 util-mpm-b3g.c util-mpm-b3g.h \
+util-mpm-hs.c util-mpm-hs.h \
 util-mpm.c util-mpm.h \
 util-mpm-wumanber.c util-mpm-wumanber.h \
 util-optimize.h \
index 70afeda38c16b868dfdff5bc74ed65e10527d7b1..50a69153adc437c38da5909645f2bf2bed958fd5 100644 (file)
@@ -1068,6 +1068,9 @@ static uint8_t DetectEngineCtxLoadConf(DetectEngineCtx *de_ctx)
         /* for now, since we still haven't implemented any intelligence into
          * understanding the patterns and distributing mpm_ctx across sgh */
         if (de_ctx->mpm_matcher == DEFAULT_MPM || de_ctx->mpm_matcher == MPM_AC_GFBS ||
+#ifdef BUILD_HYPERSCAN
+            de_ctx->mpm_matcher == MPM_HS ||
+#endif
 #ifdef __SC_CUDA_SUPPORT__
             de_ctx->mpm_matcher == MPM_AC_BS || de_ctx->mpm_matcher == MPM_AC_CUDA) {
 #else
index 332202095800b3187276e5c49431f8efd4f01f4a..962e7c1dbb1a85695ef048b92e9ccea4454fb9bd 100644 (file)
 #include "util-memrchr.h"
 
 #include "util-mpm-ac.h"
+#include "util-mpm-hs.h"
 #include "detect-engine-mpm.h"
 
 #include "util-decode-asn1.h"
@@ -288,6 +289,9 @@ void RunUnittests(int list_unittests, char *regex_arg)
         uint32_t failed = UtRunTests(regex_arg);
         PacketPoolDestroy();
         UtCleanup();
+#ifdef BUILD_HYPERSCAN
+        MpmHSGlobalCleanup();
+#endif
 #ifdef __SC_CUDA_SUPPORT__
         if (PatternMatchDefaultMatcher() == MPM_AC_CUDA)
             MpmCudaBufferDeSetup();
index 5d8348ccfc5c7d9cc4c86b5420ab17ac1cb6ec54..dbe1b139c33b68919c9a9724bfb45451da271e22 100644 (file)
 #include "util-cuda-buffer.h"
 #include "util-mpm-ac.h"
 #endif
+#include "util-mpm-hs.h"
 #include "util-storage.h"
 #include "host-storage.h"
 
@@ -2615,6 +2616,10 @@ int main(int argc, char **argv)
 
     SC_ATOMIC_DESTROY(engine_stage);
 
+#ifdef BUILD_HYPERSCAN
+    MpmHSGlobalCleanup();
+#endif
+
 #ifdef __SC_CUDA_SUPPORT__
     if (PatternMatchDefaultMatcher() == MPM_AC_CUDA)
         MpmCudaBufferDeSetup();
diff --git a/src/util-mpm-hs.c b/src/util-mpm-hs.c
new file mode 100644 (file)
index 0000000..2f6237e
--- /dev/null
@@ -0,0 +1,2198 @@
+/* Copyright (C) 2007-2016 Open Information Security Foundation
+ *
+ * You can copy, redistribute or modify this Program under the terms of
+ * the GNU General Public License version 2 as published by the Free
+ * Software Foundation.
+ *
+ * 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
+ * version 2 along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ */
+
+/**
+ * \file
+ *
+ * \author Jim Xu <jim.xu@windriver.com>
+ * \author Justin Viiret <justin.viiret@intel.com>
+ *
+ * MPM pattern matcher that calls the Hyperscan regex matcher.
+ */
+
+#include "suricata-common.h"
+#include "suricata.h"
+
+#include "detect.h"
+#include "detect-parse.h"
+#include "detect-engine.h"
+
+#include "conf.h"
+#include "util-debug.h"
+#include "util-unittest.h"
+#include "util-unittest-helper.h"
+#include "util-memcmp.h"
+#include "util-mpm-hs.h"
+#include "util-memcpy.h"
+#include "util-hash.h"
+#include "util-hash-lookup3.h"
+
+#ifdef BUILD_HYPERSCAN
+
+#include <hs.h>
+
+void SCHSInitCtx(MpmCtx *);
+void SCHSInitThreadCtx(MpmCtx *, MpmThreadCtx *, uint32_t);
+void SCHSDestroyCtx(MpmCtx *);
+void SCHSDestroyThreadCtx(MpmCtx *, MpmThreadCtx *);
+int SCHSAddPatternCI(MpmCtx *, uint8_t *, uint16_t, uint16_t, uint16_t,
+                     uint32_t, SigIntId, uint8_t);
+int SCHSAddPatternCS(MpmCtx *, uint8_t *, uint16_t, uint16_t, uint16_t,
+                     uint32_t, SigIntId, uint8_t);
+int SCHSPreparePatterns(MpmCtx *mpm_ctx);
+uint32_t SCHSSearch(MpmCtx *mpm_ctx, MpmThreadCtx *mpm_thread_ctx,
+                    PatternMatcherQueue *pmq, uint8_t *buf, uint16_t buflen);
+void SCHSPrintInfo(MpmCtx *mpm_ctx);
+void SCHSPrintSearchStats(MpmThreadCtx *mpm_thread_ctx);
+void SCHSRegisterTests(void);
+
+/* size of the hash table used to speed up pattern insertions initially */
+#define INIT_HASH_SIZE 65536
+
+/* Initial size of the global database hash (used for de-duplication). */
+#define INIT_DB_HASH_SIZE 1000
+
+/* Global prototype scratch, built incrementally as Hyperscan databases are
+ * built and then cloned for each thread context. Access is serialised via
+ * g_scratch_proto_mutex. */
+static hs_scratch_t *g_scratch_proto = NULL;
+static SCMutex g_scratch_proto_mutex = SCMUTEX_INITIALIZER;
+
+/* Global hash table of Hyperscan databases, used for de-duplication. Access is
+ * serialised via g_db_table_mutex. */
+static HashTable *g_db_table = NULL;
+static SCMutex g_db_table_mutex = SCMUTEX_INITIALIZER;
+
+/**
+ * \internal
+ * \brief Wraps SCMalloc (which is a macro) so that it can be passed to
+ * hs_set_allocator() for Hyperscan's use.
+ */
+static void *SCHSMalloc(size_t size)
+{
+    return SCMalloc(size);
+}
+
+/**
+ * \internal
+ * \brief Wraps SCFree (which is a macro) so that it can be passed to
+ * hs_set_allocator() for Hyperscan's use.
+ */
+static void SCHSFree(void *ptr)
+{
+    SCFree(ptr);
+}
+
+/** \brief Register Suricata malloc/free with Hyperscan.
+ *
+ * Requests that Hyperscan use Suricata's allocator for allocation of
+ * databases, scratch space, etc.
+ */
+static void SCHSSetAllocators(void)
+{
+    hs_error_t err = hs_set_allocator(SCHSMalloc, SCHSFree);
+    if (err != HS_SUCCESS) {
+        SCLogError(SC_ERR_FATAL, "Failed to set Hyperscan allocator.");
+        exit(EXIT_FAILURE);
+    }
+}
+
+/**
+ * \internal
+ * \brief Convert a pattern into a regex string accepted by the Hyperscan
+ * compiler.
+ *
+ * For simplicity, we just take each byte of the original pattern and render it
+ * with a hex escape (i.e. ' ' -> "\x20")/
+ */
+static char *SCHSRenderPattern(uint8_t *pat, uint16_t pat_len)
+{
+    if (pat == NULL) {
+        return NULL;
+    }
+    const size_t hex_len = (pat_len * 4) + 1;
+    char *str = SCMalloc(hex_len);
+    if (str == NULL) {
+        return NULL;
+    }
+    memset(str, 0, hex_len);
+    char *sp = str;
+    for (uint16_t i = 0; i < pat_len; i++) {
+        snprintf(sp, 5, "\\x%02x", pat[i]);
+        sp += 4;
+    }
+    *sp = '\0';
+    return str;
+}
+
+/**
+ * \internal
+ * \brief Creates a hash of the pattern.  We use it for the hashing process
+ *        during the initial pattern insertion time, to cull duplicate sigs.
+ *
+ * \param pat    Pointer to the pattern.
+ * \param patlen Pattern length.
+ *
+ * \retval hash A 32 bit unsigned hash.
+ */
+static inline uint32_t SCHSInitHashRaw(uint8_t *pat, uint16_t patlen)
+{
+    uint32_t hash = patlen * pat[0];
+    if (patlen > 1)
+        hash += pat[1];
+
+    return (hash % INIT_HASH_SIZE);
+}
+
+/**
+ * \internal
+ * \brief Looks up a pattern.  We use it for the hashing process during
+ *        the initial pattern insertion time, to cull duplicate sigs.
+ *
+ * \param ctx    Pointer to the HS ctx.
+ * \param pat    Pointer to the pattern.
+ * \param patlen Pattern length.
+ * \param flags  Flags.  We don't need this.
+ *
+ * \retval hash A 32 bit unsigned hash.
+ */
+static inline SCHSPattern *SCHSInitHashLookup(SCHSCtx *ctx, uint8_t *pat,
+                                              uint16_t patlen, uint16_t offset,
+                                              uint16_t depth, char flags,
+                                              uint32_t pid)
+{
+    uint32_t hash = SCHSInitHashRaw(pat, patlen);
+
+    if (ctx->init_hash == NULL) {
+        return NULL;
+    }
+
+    SCHSPattern *t = ctx->init_hash[hash];
+    for (; t != NULL; t = t->next) {
+        /* Since Hyperscan uses offset/depth, we must distinguish between
+         * patterns with the same ID but different offset/depth here. */
+        if (t->id == pid && t->offset == offset && t->depth == depth) {
+            BUG_ON(t->len != patlen);
+            BUG_ON(SCMemcmp(t->original_pat, pat, patlen) != 0);
+            return t;
+        }
+    }
+
+    return NULL;
+}
+
+/**
+ * \internal
+ * \brief Allocates a new pattern instance.
+ *
+ * \param mpm_ctx Pointer to the mpm context.
+ *
+ * \retval p Pointer to the newly created pattern.
+ */
+static inline SCHSPattern *SCHSAllocPattern(MpmCtx *mpm_ctx)
+{
+    SCHSPattern *p = SCMalloc(sizeof(SCHSPattern));
+    if (unlikely(p == NULL)) {
+        exit(EXIT_FAILURE);
+    }
+    memset(p, 0, sizeof(SCHSPattern));
+
+    mpm_ctx->memory_cnt++;
+    mpm_ctx->memory_size += sizeof(SCHSPattern);
+
+    return p;
+}
+
+/**
+ * \internal
+ * \brief Used to free SCHSPattern instances.
+ *
+ * \param mpm_ctx Pointer to the mpm context.
+ * \param p       Pointer to the SCHSPattern instance to be freed.
+ * \param free    Free the above pointer or not.
+ */
+static inline void SCHSFreePattern(MpmCtx *mpm_ctx, SCHSPattern *p)
+{
+    if (p != NULL && p->original_pat != NULL) {
+        SCFree(p->original_pat);
+        mpm_ctx->memory_cnt--;
+        mpm_ctx->memory_size -= p->len;
+    }
+
+    if (p != NULL && p->sids != NULL) {
+        SCFree(p->sids);
+    }
+
+    if (p != NULL) {
+        SCFree(p);
+        mpm_ctx->memory_cnt--;
+        mpm_ctx->memory_size -= sizeof(SCHSPattern);
+    }
+}
+
+static inline uint32_t SCHSInitHash(SCHSPattern *p)
+{
+    uint32_t hash = p->len * p->original_pat[0];
+    if (p->len > 1)
+        hash += p->original_pat[1];
+
+    return (hash % INIT_HASH_SIZE);
+}
+
+static inline int SCHSInitHashAdd(SCHSCtx *ctx, SCHSPattern *p)
+{
+    uint32_t hash = SCHSInitHash(p);
+
+    if (ctx->init_hash == NULL) {
+        return 0;
+    }
+
+    if (ctx->init_hash[hash] == NULL) {
+        ctx->init_hash[hash] = p;
+        return 0;
+    }
+
+    SCHSPattern *tt = NULL;
+    SCHSPattern *t = ctx->init_hash[hash];
+
+    /* get the list tail */
+    do {
+        tt = t;
+        t = t->next;
+    } while (t != NULL);
+
+    tt->next = p;
+
+    return 0;
+}
+
+/**
+ * \internal
+ * \brief Add a pattern to the mpm-hs context.
+ *
+ * \param mpm_ctx Mpm context.
+ * \param pat     Pointer to the pattern.
+ * \param patlen  Length of the pattern.
+ * \param pid     Pattern id
+ * \param sid     Signature id (internal id).
+ * \param flags   Pattern's MPM_PATTERN_* flags.
+ *
+ * \retval  0 On success.
+ * \retval -1 On failure.
+ */
+static int SCHSAddPattern(MpmCtx *mpm_ctx, uint8_t *pat, uint16_t patlen,
+                          uint16_t offset, uint16_t depth, uint32_t pid,
+                          SigIntId sid, uint8_t flags)
+{
+    SCHSCtx *ctx = (SCHSCtx *)mpm_ctx->ctx;
+
+    if (offset != 0) {
+        flags |= MPM_PATTERN_FLAG_OFFSET;
+    }
+    if (depth != 0) {
+        flags |= MPM_PATTERN_FLAG_DEPTH;
+    }
+
+    if (patlen == 0) {
+        SCLogWarning(SC_ERR_INVALID_ARGUMENTS, "pattern length 0");
+        return 0;
+    }
+
+    /* check if we have already inserted this pattern */
+    SCHSPattern *p =
+        SCHSInitHashLookup(ctx, pat, patlen, offset, depth, flags, pid);
+    if (p == NULL) {
+        SCLogDebug("Allocing new pattern");
+
+        /* p will never be NULL */
+        p = SCHSAllocPattern(mpm_ctx);
+
+        p->len = patlen;
+        p->flags = flags;
+        p->id = pid;
+
+        p->offset = offset;
+        p->depth = depth;
+
+        p->original_pat = SCMalloc(patlen);
+        if (p->original_pat == NULL)
+            goto error;
+        mpm_ctx->memory_cnt++;
+        mpm_ctx->memory_size += patlen;
+        memcpy(p->original_pat, pat, patlen);
+
+        /* put in the pattern hash */
+        SCHSInitHashAdd(ctx, p);
+
+        mpm_ctx->pattern_cnt++;
+
+        if (mpm_ctx->maxlen < patlen)
+            mpm_ctx->maxlen = patlen;
+
+        if (mpm_ctx->minlen == 0) {
+            mpm_ctx->minlen = patlen;
+        } else {
+            if (mpm_ctx->minlen > patlen)
+                mpm_ctx->minlen = patlen;
+        }
+
+        p->sids_size = 1;
+        p->sids = SCMalloc(p->sids_size * sizeof(SigIntId));
+        BUG_ON(p->sids == NULL);
+        p->sids[0] = sid;
+    } else {
+        /* TODO figure out how we can be called multiple times for the same CTX with the same sid */
+
+        int found = 0;
+        uint32_t x = 0;
+        for (x = 0; x < p->sids_size; x++) {
+            if (p->sids[x] == sid) {
+                found = 1;
+                break;
+            }
+        }
+        if (!found) {
+            SigIntId *sids = SCRealloc(p->sids, (sizeof(SigIntId) * (p->sids_size + 1)));
+            BUG_ON(sids == NULL);
+            p->sids = sids;
+            p->sids[p->sids_size] = sid;
+            p->sids_size++;
+        }
+    }
+
+    return 0;
+
+error:
+    SCHSFreePattern(mpm_ctx, p);
+    return -1;
+}
+
+/**
+ * \brief Pattern database information used only as input to the Hyperscan
+ * compiler.
+ */
+typedef struct SCHSCompileData_ {
+    unsigned int *ids;
+    unsigned int *flags;
+    char **expressions;
+    hs_expr_ext_t **ext;
+    unsigned int pattern_cnt;
+} SCHSCompileData;
+
+static SCHSCompileData *SCHSAllocCompileData(unsigned int pattern_cnt)
+{
+    SCHSCompileData *cd = SCMalloc(pattern_cnt * sizeof(SCHSCompileData));
+    if (cd == NULL) {
+        goto error;
+    }
+    memset(cd, 0, pattern_cnt * sizeof(SCHSCompileData));
+
+    cd->pattern_cnt = pattern_cnt;
+
+    cd->ids = SCMalloc(pattern_cnt * sizeof(unsigned int));
+    if (cd->ids == NULL) {
+        goto error;
+    }
+    memset(cd->ids, 0, pattern_cnt * sizeof(unsigned int));
+
+    cd->flags = SCMalloc(pattern_cnt * sizeof(unsigned int));
+    if (cd->flags == NULL) {
+        goto error;
+    }
+    memset(cd->flags, 0, pattern_cnt * sizeof(unsigned int));
+
+    cd->expressions = SCMalloc(pattern_cnt * sizeof(char *));
+    if (cd->expressions == NULL) {
+        goto error;
+    }
+    memset(cd->expressions, 0, pattern_cnt * sizeof(char *));
+
+    cd->ext = SCMalloc(pattern_cnt * sizeof(hs_expr_ext_t *));
+    if (cd->ext == NULL) {
+        goto error;
+    }
+    memset(cd->ext, 0, pattern_cnt * sizeof(hs_expr_ext_t *));
+
+    return cd;
+
+error:
+    SCLogDebug("SCHSCompileData alloc failed");
+    if (cd) {
+        SCFree(cd->ids);
+        SCFree(cd->flags);
+        SCFree(cd->expressions);
+        SCFree(cd->ext);
+        SCFree(cd);
+    }
+    return NULL;
+}
+
+static void SCHSFreeCompileData(SCHSCompileData *cd)
+{
+    if (cd == NULL) {
+        return;
+    }
+
+    SCFree(cd->ids);
+    SCFree(cd->flags);
+    if (cd->expressions) {
+        for (unsigned int i = 0; i < cd->pattern_cnt; i++) {
+            SCFree(cd->expressions[i]);
+        }
+        SCFree(cd->expressions);
+    }
+    if (cd->ext) {
+        for (unsigned int i = 0; i < cd->pattern_cnt; i++) {
+            SCFree(cd->ext[i]);
+        }
+        SCFree(cd->ext);
+    }
+    SCFree(cd);
+}
+
+typedef struct PatternDatabase_ {
+    SCHSPattern **parray;
+    hs_database_t *hs_db;
+    uint32_t pattern_cnt;
+
+    /* Reference count: number of MPM contexts using this pattern database. */
+    uint32_t ref_cnt;
+} PatternDatabase;
+
+static uint32_t SCHSPatternHash(const SCHSPattern *p, uint32_t hash)
+{
+    BUG_ON(p->original_pat == NULL);
+    BUG_ON(p->sids == NULL);
+
+    hash = hashlittle_safe(&p->len, sizeof(p->len), hash);
+    hash = hashlittle_safe(&p->flags, sizeof(p->flags), hash);
+    hash = hashlittle_safe(p->original_pat, p->len, hash);
+    hash = hashlittle_safe(&p->id, sizeof(p->id), hash);
+    hash = hashlittle_safe(&p->offset, sizeof(p->offset), hash);
+    hash = hashlittle_safe(&p->depth, sizeof(p->depth), hash);
+    hash = hashlittle_safe(&p->sids_size, sizeof(p->sids_size), hash);
+    hash = hashlittle_safe(p->sids, p->sids_size * sizeof(SigIntId), hash);
+    return hash;
+}
+
+static char SCHSPatternCompare(const SCHSPattern *p1, const SCHSPattern *p2)
+{
+    if ((p1->len != p2->len) || (p1->flags != p2->flags) ||
+        (p1->id != p2->id) || (p1->offset != p2->offset) ||
+        (p1->depth != p2->depth) || (p1->sids_size != p2->sids_size)) {
+        return 0;
+    }
+
+    if (SCMemcmp(p1->original_pat, p2->original_pat, p1->len) != 0) {
+        return 0;
+    }
+
+    if (SCMemcmp(p1->sids, p2->sids, p1->sids_size * sizeof(p1->sids[0])) !=
+        0) {
+        return 0;
+    }
+
+    return 1;
+}
+
+static uint32_t PatternDatabaseHash(HashTable *ht, void *data, uint16_t len)
+{
+    const PatternDatabase *pd = data;
+    uint32_t hash = 0;
+    hash = hashword(&pd->pattern_cnt, 1, hash);
+
+    for (uint32_t i = 0; i < pd->pattern_cnt; i++) {
+        hash = SCHSPatternHash(pd->parray[i], hash);
+    }
+
+    hash %= ht->array_size;
+    return hash;
+}
+
+static char PatternDatabaseCompare(void *data1, uint16_t len1, void *data2,
+                                   uint16_t len2)
+{
+    const PatternDatabase *pd1 = data1;
+    const PatternDatabase *pd2 = data2;
+
+    if (pd1->pattern_cnt != pd2->pattern_cnt) {
+        return 0;
+    }
+
+    for (uint32_t i = 0; i < pd1->pattern_cnt; i++) {
+        if (SCHSPatternCompare(pd1->parray[i], pd2->parray[i]) == 0) {
+            return 0;
+        }
+    }
+
+    return 1;
+}
+
+static void PatternDatabaseFree(PatternDatabase *pd)
+{
+    BUG_ON(pd->ref_cnt != 0);
+
+    if (pd->parray != NULL) {
+        for (uint32_t i = 0; i < pd->pattern_cnt; i++) {
+            SCHSPattern *p = pd->parray[i];
+            if (p != NULL) {
+                SCFree(p->original_pat);
+                SCFree(p->sids);
+                SCFree(p);
+            }
+        }
+        SCFree(pd->parray);
+    }
+
+    hs_free_database(pd->hs_db);
+
+    SCFree(pd);
+}
+
+static void PatternDatabaseTableFree(void *data)
+{
+    /* Stub function handed to hash table; actual freeing of PatternDatabase
+     * structures is done in MPM destruction when the ref_cnt drops to zero. */
+}
+
+static PatternDatabase *PatternDatabaseAlloc(uint32_t pattern_cnt)
+{
+    PatternDatabase *pd = SCMalloc(sizeof(PatternDatabase));
+    if (pd == NULL) {
+        return NULL;
+    }
+    memset(pd, 0, sizeof(PatternDatabase));
+    pd->pattern_cnt = pattern_cnt;
+    pd->ref_cnt = 0;
+    pd->hs_db = NULL;
+
+    /* alloc the pattern array */
+    pd->parray =
+        (SCHSPattern **)SCMalloc(pd->pattern_cnt * sizeof(SCHSPattern *));
+    if (pd->parray == NULL) {
+        SCFree(pd);
+        return NULL;
+    }
+    memset(pd->parray, 0, pd->pattern_cnt * sizeof(SCHSPattern *));
+
+    return pd;
+}
+
+/**
+ * \brief Process the patterns added to the mpm, and create the internal tables.
+ *
+ * \param mpm_ctx Pointer to the mpm context.
+ */
+int SCHSPreparePatterns(MpmCtx *mpm_ctx)
+{
+    SCHSCtx *ctx = (SCHSCtx *)mpm_ctx->ctx;
+
+    if (mpm_ctx->pattern_cnt == 0 || ctx->init_hash == NULL) {
+        SCLogDebug("no patterns supplied to this mpm_ctx");
+        return 0;
+    }
+
+    hs_error_t err;
+    hs_compile_error_t *compile_err = NULL;
+    SCHSCompileData *cd = NULL;
+    PatternDatabase *pd = NULL;
+
+    cd = SCHSAllocCompileData(mpm_ctx->pattern_cnt);
+    if (cd == NULL) {
+        goto error;
+    }
+
+    pd = PatternDatabaseAlloc(mpm_ctx->pattern_cnt);
+    if (pd == NULL) {
+        goto error;
+    }
+
+    /* populate the pattern array with the patterns in the hash */
+    for (uint32_t i = 0, p = 0; i < INIT_HASH_SIZE; i++) {
+        SCHSPattern *node = ctx->init_hash[i], *nnode = NULL;
+        while (node != NULL) {
+            nnode = node->next;
+            node->next = NULL;
+            pd->parray[p++] = node;
+            node = nnode;
+        }
+    }
+
+    /* we no longer need the hash, so free its memory */
+    SCFree(ctx->init_hash);
+    ctx->init_hash = NULL;
+
+    /* Serialise whole database compilation as a relatively easy way to ensure
+     * dedupe is safe. */
+    SCMutexLock(&g_db_table_mutex);
+
+    /* Init global pattern database hash if necessary. */
+    if (g_db_table == NULL) {
+        g_db_table = HashTableInit(INIT_DB_HASH_SIZE, PatternDatabaseHash,
+                                   PatternDatabaseCompare,
+                                   PatternDatabaseTableFree);
+        if (g_db_table == NULL) {
+            SCMutexUnlock(&g_db_table_mutex);
+            goto error;
+        }
+    }
+
+    /* Check global hash table to see if we've seen this pattern database
+     * before, and reuse the Hyperscan database if so. */
+    PatternDatabase *pd_cached = HashTableLookup(g_db_table, pd, 1);
+
+    if (pd_cached != NULL) {
+        SCLogDebug("Reusing cached database %p with %" PRIu32
+                   " patterns (ref_cnt=%" PRIu32 ")",
+                   pd_cached->hs_db, pd_cached->pattern_cnt,
+                   pd_cached->ref_cnt);
+        pd_cached->ref_cnt++;
+        ctx->pattern_db = pd_cached;
+        SCMutexUnlock(&g_db_table_mutex);
+        PatternDatabaseFree(pd);
+        SCHSFreeCompileData(cd);
+        return 0;
+    }
+
+    BUG_ON(ctx->pattern_db != NULL); /* already built? */
+
+    for (uint32_t i = 0; i < pd->pattern_cnt; i++) {
+        const SCHSPattern *p = pd->parray[i];
+
+        cd->ids[i] = i;
+        cd->flags[i] = HS_FLAG_SINGLEMATCH;
+        if (p->flags & MPM_PATTERN_FLAG_NOCASE) {
+            cd->flags[i] |= HS_FLAG_CASELESS;
+        }
+
+        cd->expressions[i] = SCHSRenderPattern(p->original_pat, p->len);
+
+        if (p->flags & (MPM_PATTERN_FLAG_OFFSET | MPM_PATTERN_FLAG_DEPTH)) {
+            cd->ext[i] = SCMalloc(sizeof(hs_expr_ext_t));
+            if (cd->ext[i] == NULL) {
+                goto error;
+            }
+            memset(cd->ext[i], 0, sizeof(hs_expr_ext_t));
+
+            if (p->flags & MPM_PATTERN_FLAG_OFFSET) {
+                cd->ext[i]->flags |= HS_EXT_FLAG_MIN_OFFSET;
+                cd->ext[i]->min_offset = p->offset + p->len;
+            }
+            if (p->flags & MPM_PATTERN_FLAG_DEPTH) {
+                cd->ext[i]->flags |= HS_EXT_FLAG_MAX_OFFSET;
+                cd->ext[i]->max_offset = p->offset + p->depth;
+            }
+        }
+    }
+
+    BUG_ON(mpm_ctx->pattern_cnt == 0);
+
+    err = hs_compile_ext_multi((const char *const *)cd->expressions, cd->flags,
+                               cd->ids, (const hs_expr_ext_t *const *)cd->ext,
+                               cd->pattern_cnt, HS_MODE_BLOCK, NULL, &pd->hs_db,
+                               &compile_err);
+
+    if (err != HS_SUCCESS) {
+        SCLogError(SC_ERR_FATAL, "failed to compile hyperscan database");
+        if (compile_err) {
+            SCLogError(SC_ERR_FATAL, "compile error: %s", compile_err->message);
+        }
+        hs_free_compile_error(compile_err);
+        goto error;
+    }
+
+    ctx->pattern_db = pd;
+
+    SCMutexLock(&g_scratch_proto_mutex);
+    err = hs_alloc_scratch(pd->hs_db, &g_scratch_proto);
+    SCMutexUnlock(&g_scratch_proto_mutex);
+    if (err != HS_SUCCESS) {
+        SCLogError(SC_ERR_FATAL, "failed to allocate scratch");
+        goto error;
+    }
+
+    err = hs_database_size(pd->hs_db, &ctx->hs_db_size);
+    if (err != HS_SUCCESS) {
+        SCLogError(SC_ERR_FATAL, "failed to query database size");
+        goto error;
+    }
+
+    mpm_ctx->memory_cnt++;
+    mpm_ctx->memory_size += ctx->hs_db_size;
+
+    SCLogDebug("Built %" PRIu32 " patterns into a database of size %" PRIuMAX
+               " bytes", mpm_ctx->pattern_cnt, (uintmax_t)ctx->hs_db_size);
+
+    /* Cache this database globally for later. */
+    pd->ref_cnt = 1;
+    HashTableAdd(g_db_table, pd, 1);
+    SCMutexUnlock(&g_db_table_mutex);
+
+    SCHSFreeCompileData(cd);
+    return 0;
+
+error:
+    SCMutexUnlock(&g_db_table_mutex);
+    if (pd) {
+        PatternDatabaseFree(pd);
+    }
+    if (cd) {
+        SCHSFreeCompileData(cd);
+    }
+    return -1;
+}
+
+/**
+ * \brief Init the mpm thread context.
+ *
+ * \param mpm_ctx        Pointer to the mpm context.
+ * \param mpm_thread_ctx Pointer to the mpm thread context.
+ * \param matchsize      We don't need this.
+ */
+void SCHSInitThreadCtx(MpmCtx *mpm_ctx, MpmThreadCtx *mpm_thread_ctx,
+                       uint32_t matchsize)
+{
+    memset(mpm_thread_ctx, 0, sizeof(MpmThreadCtx));
+
+    SCHSThreadCtx *ctx = SCMalloc(sizeof(SCHSThreadCtx));
+    if (ctx == NULL) {
+        exit(EXIT_FAILURE);
+    }
+    mpm_thread_ctx->ctx = ctx;
+
+    memset(ctx, 0, sizeof(SCHSThreadCtx));
+    mpm_thread_ctx->memory_cnt++;
+    mpm_thread_ctx->memory_size += sizeof(SCHSThreadCtx);
+
+    ctx->scratch = NULL;
+    ctx->scratch_size = 0;
+
+    SCMutexLock(&g_scratch_proto_mutex);
+
+    if (g_scratch_proto == NULL) {
+        /* There is no scratch prototype: this means that we have not compiled
+         * any Hyperscan databases. */
+        SCMutexUnlock(&g_scratch_proto_mutex);
+        SCLogDebug("No scratch space prototype");
+        return;
+    }
+
+    hs_error_t err = hs_clone_scratch(g_scratch_proto,
+                                      (hs_scratch_t **)&ctx->scratch);
+
+    SCMutexUnlock(&g_scratch_proto_mutex);
+
+    if (err != HS_SUCCESS) {
+        SCLogError(SC_ERR_FATAL, "Unable to clone scratch prototype");
+        exit(EXIT_FAILURE);
+    }
+
+    err = hs_scratch_size(ctx->scratch, &ctx->scratch_size);
+    if (err != HS_SUCCESS) {
+        SCLogError(SC_ERR_FATAL, "Unable to query scratch size");
+        exit(EXIT_FAILURE);
+    }
+
+    mpm_thread_ctx->memory_cnt++;
+    mpm_thread_ctx->memory_size += ctx->scratch_size;
+}
+
+/**
+ * \brief Initialize the HS context.
+ *
+ * \param mpm_ctx       Mpm context.
+ */
+void SCHSInitCtx(MpmCtx *mpm_ctx)
+{
+    if (mpm_ctx->ctx != NULL)
+        return;
+
+    mpm_ctx->ctx = SCMalloc(sizeof(SCHSCtx));
+    if (mpm_ctx->ctx == NULL) {
+        exit(EXIT_FAILURE);
+    }
+    memset(mpm_ctx->ctx, 0, sizeof(SCHSCtx));
+
+    mpm_ctx->memory_cnt++;
+    mpm_ctx->memory_size += sizeof(SCHSCtx);
+
+    /* initialize the hash we use to speed up pattern insertions */
+    SCHSCtx *ctx = (SCHSCtx *)mpm_ctx->ctx;
+    ctx->init_hash = SCMalloc(sizeof(SCHSPattern *) * INIT_HASH_SIZE);
+    if (ctx->init_hash == NULL) {
+        exit(EXIT_FAILURE);
+    }
+    memset(ctx->init_hash, 0, sizeof(SCHSPattern *) * INIT_HASH_SIZE);
+}
+
+/**
+ * \brief Destroy the mpm thread context.
+ *
+ * \param mpm_ctx        Pointer to the mpm context.
+ * \param mpm_thread_ctx Pointer to the mpm thread context.
+ */
+void SCHSDestroyThreadCtx(MpmCtx *mpm_ctx, MpmThreadCtx *mpm_thread_ctx)
+{
+    SCHSPrintSearchStats(mpm_thread_ctx);
+
+    SCHSThreadCtx *thr_ctx = (SCHSThreadCtx *)mpm_thread_ctx->ctx;
+
+    if (thr_ctx->scratch != NULL) {
+        hs_free_scratch(thr_ctx->scratch);
+        mpm_thread_ctx->memory_cnt--;
+        mpm_thread_ctx->memory_size -= thr_ctx->scratch_size;
+    }
+
+    if (mpm_thread_ctx->ctx != NULL) {
+        SCFree(mpm_thread_ctx->ctx);
+        mpm_thread_ctx->ctx = NULL;
+        mpm_thread_ctx->memory_cnt--;
+        mpm_thread_ctx->memory_size -= sizeof(SCHSThreadCtx);
+    }
+}
+
+/**
+ * \brief Destroy the mpm context.
+ *
+ * \param mpm_ctx Pointer to the mpm context.
+ */
+void SCHSDestroyCtx(MpmCtx *mpm_ctx)
+{
+    SCHSCtx *ctx = (SCHSCtx *)mpm_ctx->ctx;
+    if (ctx == NULL)
+        return;
+
+    if (ctx->init_hash != NULL) {
+        SCFree(ctx->init_hash);
+        ctx->init_hash = NULL;
+        mpm_ctx->memory_cnt--;
+        mpm_ctx->memory_size -= (INIT_HASH_SIZE * sizeof(SCHSPattern *));
+    }
+
+    /* Decrement pattern database ref count, and delete it entirely if the
+     * count has dropped to zero. */
+    SCMutexLock(&g_db_table_mutex);
+    PatternDatabase *pd = ctx->pattern_db;
+    if (pd) {
+        BUG_ON(pd->ref_cnt == 0);
+        pd->ref_cnt--;
+        if (pd->ref_cnt == 0) {
+            HashTableRemove(g_db_table, pd, 1);
+            PatternDatabaseFree(pd);
+        }
+    }
+    SCMutexUnlock(&g_db_table_mutex);
+
+    SCFree(mpm_ctx->ctx);
+    mpm_ctx->memory_cnt--;
+    mpm_ctx->memory_size -= sizeof(SCHSCtx);
+}
+
+typedef struct SCHSCallbackCtx_ {
+    SCHSCtx *ctx;
+    void *pmq;
+    uint32_t match_count;
+} SCHSCallbackCtx;
+
+/* Hyperscan MPM match event handler */
+static int SCHSMatchEvent(unsigned int id, unsigned long long from,
+                          unsigned long long to, unsigned int flags,
+                          void *ctx)
+{
+    SCHSCallbackCtx *cctx = ctx;
+    PatternMatcherQueue *pmq = cctx->pmq;
+    const PatternDatabase *pd = cctx->ctx->pattern_db;
+    const SCHSPattern *pat = pd->parray[id];
+
+    SCLogDebug("Hyperscan Match %" PRIu32 ": id=%" PRIu32 " @ %" PRIuMAX
+               " (pat id=%" PRIu32 ")",
+               cctx->match_count, (uint32_t)id, (uintmax_t)to, pat->id);
+
+    pmq->pattern_id_bitarray[pat->id / 8] |= (1 << (pat->id % 8));
+    MpmAddPid(pmq, pat->id);
+    MpmAddSids(pmq, pat->sids, pat->sids_size);
+
+    cctx->match_count++;
+    return 0;
+}
+
+/**
+ * \brief The Hyperscan search function.
+ *
+ * \param mpm_ctx        Pointer to the mpm context.
+ * \param mpm_thread_ctx Pointer to the mpm thread context.
+ * \param pmq            Pointer to the Pattern Matcher Queue to hold
+ *                       search matches.
+ * \param buf            Buffer to be searched.
+ * \param buflen         Buffer length.
+ *
+ * \retval matches Match count.
+ */
+uint32_t SCHSSearch(MpmCtx *mpm_ctx, MpmThreadCtx *mpm_thread_ctx,
+                    PatternMatcherQueue *pmq, uint8_t *buf, uint16_t buflen)
+{
+    uint32_t ret = 0;
+    SCHSCtx *ctx = (SCHSCtx *)mpm_ctx->ctx;
+    SCHSThreadCtx *hs_thread_ctx = (SCHSThreadCtx *)(mpm_thread_ctx->ctx);
+    const PatternDatabase *pd = ctx->pattern_db;
+
+    SCHSCallbackCtx cctx = {.ctx = ctx, .pmq = pmq, .match_count = 0};
+
+    /* scratch should have been cloned from g_scratch_proto at thread init. */
+    hs_scratch_t *scratch = hs_thread_ctx->scratch;
+    BUG_ON(pd->hs_db == NULL);
+    BUG_ON(scratch == NULL);
+
+    hs_error_t err = hs_scan(pd->hs_db, (const char *)buf, buflen, 0, scratch,
+                             SCHSMatchEvent, &cctx);
+    if (err != HS_SUCCESS) {
+        SCLogError(SC_ERR_FATAL, "Scanning with Hyperscan returned error %d",
+                   err);
+    } else {
+        ret = cctx.match_count;
+    }
+
+    return ret;
+}
+
+/**
+ * \brief Add a case insensitive pattern.  Although we have different calls for
+ *        adding case sensitive and insensitive patterns, we make a single call
+ *        for either case.  No special treatment for either case.
+ *
+ * \param mpm_ctx Pointer to the mpm context.
+ * \param pat     The pattern to add.
+ * \param patlen  The pattern length.
+ * \param offset  The pattern offset.
+ * \param depth   The pattern depth.
+ * \param pid     The pattern id.
+ * \param sid     The pattern signature id.
+ * \param flags   Flags associated with this pattern.
+ *
+ * \retval  0 On success.
+ * \retval -1 On failure.
+ */
+int SCHSAddPatternCI(MpmCtx *mpm_ctx, uint8_t *pat, uint16_t patlen,
+                     uint16_t offset, uint16_t depth, uint32_t pid,
+                     SigIntId sid, uint8_t flags)
+{
+    flags |= MPM_PATTERN_FLAG_NOCASE;
+    return SCHSAddPattern(mpm_ctx, pat, patlen, offset, depth, pid, sid, flags);
+}
+
+/**
+ * \brief Add a case sensitive pattern.  Although we have different calls for
+ *        adding case sensitive and insensitive patterns, we make a single call
+ *        for either case.  No special treatment for either case.
+ *
+ * \param mpm_ctx Pointer to the mpm context.
+ * \param pat     The pattern to add.
+ * \param patlen  The pattern length.
+ * \param offset  The pattern offset.
+ * \param depth   The pattern depth.
+ * \param pid     The pattern id.
+ * \param sid     The pattern signature id.
+ * \param flags   Flags associated with this pattern.
+ *
+ * \retval  0 On success.
+ * \retval -1 On failure.
+ */
+int SCHSAddPatternCS(MpmCtx *mpm_ctx, uint8_t *pat, uint16_t patlen,
+                     uint16_t offset, uint16_t depth, uint32_t pid,
+                     SigIntId sid, uint8_t flags)
+{
+    return SCHSAddPattern(mpm_ctx, pat, patlen, offset, depth, pid, sid, flags);
+}
+
+void SCHSPrintSearchStats(MpmThreadCtx *mpm_thread_ctx)
+{
+    return;
+}
+
+void SCHSPrintInfo(MpmCtx *mpm_ctx)
+{
+    SCHSCtx *ctx = (SCHSCtx *)mpm_ctx->ctx;
+    PatternDatabase *pd = ctx->pattern_db;
+
+    printf("MPM HS Information:\n");
+    printf("Memory allocs:   %" PRIu32 "\n", mpm_ctx->memory_cnt);
+    printf("Memory alloced:  %" PRIu32 "\n", mpm_ctx->memory_size);
+    printf(" Sizeof:\n");
+    printf("  MpmCtx         %" PRIuMAX "\n", (uintmax_t)sizeof(MpmCtx));
+    printf("  SCHSCtx:       %" PRIuMAX "\n", (uintmax_t)sizeof(SCHSCtx));
+    printf("  SCHSPattern    %" PRIuMAX "\n", (uintmax_t)sizeof(SCHSPattern));
+    printf("Unique Patterns: %" PRIu32 "\n", mpm_ctx->pattern_cnt);
+    printf("Smallest:        %" PRIu32 "\n", mpm_ctx->minlen);
+    printf("Largest:         %" PRIu32 "\n", mpm_ctx->maxlen);
+    printf("\n");
+
+    if (ctx) {
+        char *db_info = NULL;
+        if (hs_database_info(pd->hs_db, &db_info) == HS_SUCCESS) {
+            printf("HS Database Info: %s\n", db_info);
+            SCFree(db_info);
+        }
+        printf("HS Database Size: %" PRIuMAX " bytes\n",
+               (uintmax_t)ctx->hs_db_size);
+    }
+
+    printf("\n");
+}
+
+/************************** Mpm Registration ***************************/
+
+/**
+ * \brief Register the Hyperscan MPM.
+ */
+void MpmHSRegister(void)
+{
+    mpm_table[MPM_HS].name = "hs";
+    mpm_table[MPM_HS].max_pattern_length = 0;
+
+    mpm_table[MPM_HS].InitCtx = SCHSInitCtx;
+    mpm_table[MPM_HS].InitThreadCtx = SCHSInitThreadCtx;
+    mpm_table[MPM_HS].DestroyCtx = SCHSDestroyCtx;
+    mpm_table[MPM_HS].DestroyThreadCtx = SCHSDestroyThreadCtx;
+    mpm_table[MPM_HS].AddPattern = SCHSAddPatternCS;
+    mpm_table[MPM_HS].AddPatternNocase = SCHSAddPatternCI;
+    mpm_table[MPM_HS].Prepare = SCHSPreparePatterns;
+    mpm_table[MPM_HS].Search = SCHSSearch;
+    mpm_table[MPM_HS].Cleanup = NULL;
+    mpm_table[MPM_HS].PrintCtx = SCHSPrintInfo;
+    mpm_table[MPM_HS].PrintThreadCtx = SCHSPrintSearchStats;
+    mpm_table[MPM_HS].RegisterUnittests = SCHSRegisterTests;
+
+    /* Set Hyperscan memory allocators */
+    SCHSSetAllocators();
+}
+
+/**
+ * \brief Clean up global memory used by all Hyperscan MPM instances.
+ *
+ * Currently, this is just the global scratch prototype.
+ */
+void MpmHSGlobalCleanup(void)
+{
+    SCMutexLock(&g_scratch_proto_mutex);
+    if (g_scratch_proto) {
+        SCLogInfo("Cleaning up Hyperscan global scratch");
+        hs_free_scratch(g_scratch_proto);
+        g_scratch_proto = NULL;
+    }
+    SCMutexUnlock(&g_scratch_proto_mutex);
+
+    SCMutexLock(&g_db_table_mutex);
+    if (g_db_table != NULL) {
+        SCLogInfo("Clearing Hyperscan database cache");
+        HashTableFree(g_db_table);
+        g_db_table = NULL;
+    }
+    SCMutexUnlock(&g_db_table_mutex);
+}
+
+/*************************************Unittests********************************/
+
+#ifdef UNITTESTS
+
+static int SCHSTest01(void)
+{
+    int result = 0;
+    MpmCtx mpm_ctx;
+    MpmThreadCtx mpm_thread_ctx;
+    PatternMatcherQueue pmq;
+
+    memset(&mpm_ctx, 0, sizeof(MpmCtx));
+    memset(&mpm_thread_ctx, 0, sizeof(MpmThreadCtx));
+    MpmInitCtx(&mpm_ctx, MPM_HS);
+
+    /* 1 match */
+    MpmAddPatternCS(&mpm_ctx, (uint8_t *)"abcd", 4, 0, 0, 0, 0, 0);
+    PmqSetup(&pmq, 1);
+
+    SCHSPreparePatterns(&mpm_ctx);
+    SCHSInitThreadCtx(&mpm_ctx, &mpm_thread_ctx, 0);
+
+    char *buf = "abcdefghjiklmnopqrstuvwxyz";
+
+    uint32_t cnt = SCHSSearch(&mpm_ctx, &mpm_thread_ctx, &pmq, (uint8_t *)buf,
+                              strlen(buf));
+
+    if (cnt == 1)
+        result = 1;
+    else
+        printf("1 != %" PRIu32 " ", cnt);
+
+    SCHSDestroyCtx(&mpm_ctx);
+    SCHSDestroyThreadCtx(&mpm_ctx, &mpm_thread_ctx);
+    PmqFree(&pmq);
+    return result;
+}
+
+static int SCHSTest02(void)
+{
+    int result = 0;
+    MpmCtx mpm_ctx;
+    MpmThreadCtx mpm_thread_ctx;
+    PatternMatcherQueue pmq;
+
+    memset(&mpm_ctx, 0, sizeof(MpmCtx));
+    memset(&mpm_thread_ctx, 0, sizeof(MpmThreadCtx));
+    MpmInitCtx(&mpm_ctx, MPM_HS);
+
+    /* 1 match */
+    MpmAddPatternCS(&mpm_ctx, (uint8_t *)"abce", 4, 0, 0, 0, 0, 0);
+    PmqSetup(&pmq, 1);
+
+    SCHSPreparePatterns(&mpm_ctx);
+    SCHSInitThreadCtx(&mpm_ctx, &mpm_thread_ctx, 0);
+
+    char *buf = "abcdefghjiklmnopqrstuvwxyz";
+    uint32_t cnt = SCHSSearch(&mpm_ctx, &mpm_thread_ctx, &pmq, (uint8_t *)buf,
+                              strlen(buf));
+
+    if (cnt == 0)
+        result = 1;
+    else
+        printf("0 != %" PRIu32 " ", cnt);
+
+    SCHSDestroyCtx(&mpm_ctx);
+    SCHSDestroyThreadCtx(&mpm_ctx, &mpm_thread_ctx);
+    PmqFree(&pmq);
+    return result;
+}
+
+static int SCHSTest03(void)
+{
+    int result = 0;
+    MpmCtx mpm_ctx;
+    MpmThreadCtx mpm_thread_ctx;
+    PatternMatcherQueue pmq;
+
+    memset(&mpm_ctx, 0x00, sizeof(MpmCtx));
+    memset(&mpm_thread_ctx, 0, sizeof(MpmThreadCtx));
+    MpmInitCtx(&mpm_ctx, MPM_HS);
+
+    /* 1 match */
+    MpmAddPatternCS(&mpm_ctx, (uint8_t *)"abcd", 4, 0, 0, 0, 0, 0);
+    /* 1 match */
+    MpmAddPatternCS(&mpm_ctx, (uint8_t *)"bcde", 4, 0, 0, 1, 0, 0);
+    /* 1 match */
+    MpmAddPatternCS(&mpm_ctx, (uint8_t *)"fghj", 4, 0, 0, 2, 0, 0);
+    PmqSetup(&pmq, 3);
+
+    SCHSPreparePatterns(&mpm_ctx);
+    SCHSInitThreadCtx(&mpm_ctx, &mpm_thread_ctx, 0);
+
+    char *buf = "abcdefghjiklmnopqrstuvwxyz";
+    uint32_t cnt = SCHSSearch(&mpm_ctx, &mpm_thread_ctx, &pmq, (uint8_t *)buf,
+                              strlen(buf));
+
+    if (cnt == 3)
+        result = 1;
+    else
+        printf("3 != %" PRIu32 " ", cnt);
+
+    SCHSDestroyCtx(&mpm_ctx);
+    SCHSDestroyThreadCtx(&mpm_ctx, &mpm_thread_ctx);
+    PmqFree(&pmq);
+    return result;
+}
+
+static int SCHSTest04(void)
+{
+    int result = 0;
+    MpmCtx mpm_ctx;
+    MpmThreadCtx mpm_thread_ctx;
+    PatternMatcherQueue pmq;
+
+    memset(&mpm_ctx, 0, sizeof(MpmCtx));
+    memset(&mpm_thread_ctx, 0, sizeof(MpmThreadCtx));
+    MpmInitCtx(&mpm_ctx, MPM_HS);
+
+    MpmAddPatternCS(&mpm_ctx, (uint8_t *)"abcd", 4, 0, 0, 0, 0, 0);
+    MpmAddPatternCS(&mpm_ctx, (uint8_t *)"bcdegh", 6, 0, 0, 1, 0, 0);
+    MpmAddPatternCS(&mpm_ctx, (uint8_t *)"fghjxyz", 7, 0, 0, 2, 0, 0);
+    PmqSetup(&pmq, 3);
+
+    SCHSPreparePatterns(&mpm_ctx);
+    SCHSInitThreadCtx(&mpm_ctx, &mpm_thread_ctx, 0);
+
+    char *buf = "abcdefghjiklmnopqrstuvwxyz";
+    uint32_t cnt = SCHSSearch(&mpm_ctx, &mpm_thread_ctx, &pmq, (uint8_t *)buf,
+                              strlen(buf));
+
+    if (cnt == 1)
+        result = 1;
+    else
+        printf("1 != %" PRIu32 " ", cnt);
+
+    SCHSDestroyCtx(&mpm_ctx);
+    SCHSDestroyThreadCtx(&mpm_ctx, &mpm_thread_ctx);
+    PmqFree(&pmq);
+    return result;
+}
+
+static int SCHSTest05(void)
+{
+    int result = 0;
+    MpmCtx mpm_ctx;
+    MpmThreadCtx mpm_thread_ctx;
+    PatternMatcherQueue pmq;
+
+    memset(&mpm_ctx, 0x00, sizeof(MpmCtx));
+    memset(&mpm_thread_ctx, 0, sizeof(MpmThreadCtx));
+    MpmInitCtx(&mpm_ctx, MPM_HS);
+
+    MpmAddPatternCI(&mpm_ctx, (uint8_t *)"ABCD", 4, 0, 0, 0, 0, 0);
+    MpmAddPatternCI(&mpm_ctx, (uint8_t *)"bCdEfG", 6, 0, 0, 1, 0, 0);
+    MpmAddPatternCI(&mpm_ctx, (uint8_t *)"fghJikl", 7, 0, 0, 2, 0, 0);
+    PmqSetup(&pmq, 3);
+
+    SCHSPreparePatterns(&mpm_ctx);
+    SCHSInitThreadCtx(&mpm_ctx, &mpm_thread_ctx, 0);
+
+    char *buf = "abcdefghjiklmnopqrstuvwxyz";
+    uint32_t cnt = SCHSSearch(&mpm_ctx, &mpm_thread_ctx, &pmq, (uint8_t *)buf,
+                              strlen(buf));
+
+    if (cnt == 3)
+        result = 1;
+    else
+        printf("3 != %" PRIu32 " ", cnt);
+
+    SCHSDestroyCtx(&mpm_ctx);
+    SCHSDestroyThreadCtx(&mpm_ctx, &mpm_thread_ctx);
+    PmqFree(&pmq);
+    return result;
+}
+
+static int SCHSTest06(void)
+{
+    int result = 0;
+    MpmCtx mpm_ctx;
+    MpmThreadCtx mpm_thread_ctx;
+    PatternMatcherQueue pmq;
+
+    memset(&mpm_ctx, 0, sizeof(MpmCtx));
+    memset(&mpm_thread_ctx, 0, sizeof(MpmThreadCtx));
+    MpmInitCtx(&mpm_ctx, MPM_HS);
+
+    MpmAddPatternCS(&mpm_ctx, (uint8_t *)"abcd", 4, 0, 0, 0, 0, 0);
+    PmqSetup(&pmq, 1);
+
+    SCHSPreparePatterns(&mpm_ctx);
+    SCHSInitThreadCtx(&mpm_ctx, &mpm_thread_ctx, 0);
+
+    char *buf = "abcd";
+    uint32_t cnt = SCHSSearch(&mpm_ctx, &mpm_thread_ctx, &pmq, (uint8_t *)buf,
+                              strlen(buf));
+
+    if (cnt == 1)
+        result = 1;
+    else
+        printf("1 != %" PRIu32 " ", cnt);
+
+    SCHSDestroyCtx(&mpm_ctx);
+    SCHSDestroyThreadCtx(&mpm_ctx, &mpm_thread_ctx);
+    PmqFree(&pmq);
+    return result;
+}
+
+static int SCHSTest07(void)
+{
+    int result = 0;
+    MpmCtx mpm_ctx;
+    MpmThreadCtx mpm_thread_ctx;
+    PatternMatcherQueue pmq;
+
+    memset(&mpm_ctx, 0, sizeof(MpmCtx));
+    memset(&mpm_thread_ctx, 0, sizeof(MpmThreadCtx));
+    MpmInitCtx(&mpm_ctx, MPM_HS);
+
+    /* should match 30 times */
+    MpmAddPatternCS(&mpm_ctx, (uint8_t *)"A", 1, 0, 0, 0, 0, 0);
+    /* should match 29 times */
+    MpmAddPatternCS(&mpm_ctx, (uint8_t *)"AA", 2, 0, 0, 1, 0, 0);
+    /* should match 28 times */
+    MpmAddPatternCS(&mpm_ctx, (uint8_t *)"AAA", 3, 0, 0, 2, 0, 0);
+    /* 26 */
+    MpmAddPatternCS(&mpm_ctx, (uint8_t *)"AAAAA", 5, 0, 0, 3, 0, 0);
+    /* 21 */
+    MpmAddPatternCS(&mpm_ctx, (uint8_t *)"AAAAAAAAAA", 10, 0, 0, 4, 0, 0);
+    /* 1 */
+    MpmAddPatternCS(&mpm_ctx, (uint8_t *)"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", 30,
+                    0, 0, 5, 0, 0);
+    PmqSetup(&pmq, 6);
+
+    SCHSPreparePatterns(&mpm_ctx);
+    SCHSInitThreadCtx(&mpm_ctx, &mpm_thread_ctx, 0);
+
+    char *buf = "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA";
+    uint32_t cnt = SCHSSearch(&mpm_ctx, &mpm_thread_ctx, &pmq, (uint8_t *)buf,
+                              strlen(buf));
+
+    if (cnt == 6)
+        result = 1;
+    else
+        printf("6 != %" PRIu32 " ", cnt);
+
+    SCHSDestroyCtx(&mpm_ctx);
+    SCHSDestroyThreadCtx(&mpm_ctx, &mpm_thread_ctx);
+    PmqFree(&pmq);
+    return result;
+}
+
+static int SCHSTest08(void)
+{
+    int result = 0;
+    MpmCtx mpm_ctx;
+    MpmThreadCtx mpm_thread_ctx;
+    PatternMatcherQueue pmq;
+
+    memset(&mpm_ctx, 0, sizeof(MpmCtx));
+    memset(&mpm_thread_ctx, 0, sizeof(MpmThreadCtx));
+    MpmInitCtx(&mpm_ctx, MPM_HS);
+
+    /* 1 match */
+    MpmAddPatternCS(&mpm_ctx, (uint8_t *)"abcd", 4, 0, 0, 0, 0, 0);
+    PmqSetup(&pmq, 1);
+
+    SCHSPreparePatterns(&mpm_ctx);
+    SCHSInitThreadCtx(&mpm_ctx, &mpm_thread_ctx, 0);
+
+    uint32_t cnt =
+        SCHSSearch(&mpm_ctx, &mpm_thread_ctx, &pmq, (uint8_t *)"a", 1);
+
+    if (cnt == 0)
+        result = 1;
+    else
+        printf("0 != %" PRIu32 " ", cnt);
+
+    SCHSDestroyCtx(&mpm_ctx);
+    SCHSDestroyThreadCtx(&mpm_ctx, &mpm_thread_ctx);
+    PmqFree(&pmq);
+    return result;
+}
+
+static int SCHSTest09(void)
+{
+    int result = 0;
+    MpmCtx mpm_ctx;
+    MpmThreadCtx mpm_thread_ctx;
+    PatternMatcherQueue pmq;
+
+    memset(&mpm_ctx, 0, sizeof(MpmCtx));
+    memset(&mpm_thread_ctx, 0, sizeof(MpmThreadCtx));
+    MpmInitCtx(&mpm_ctx, MPM_HS);
+
+    /* 1 match */
+    MpmAddPatternCS(&mpm_ctx, (uint8_t *)"ab", 2, 0, 0, 0, 0, 0);
+    PmqSetup(&pmq, 1);
+
+    SCHSPreparePatterns(&mpm_ctx);
+    SCHSInitThreadCtx(&mpm_ctx, &mpm_thread_ctx, 0);
+
+    uint32_t cnt =
+        SCHSSearch(&mpm_ctx, &mpm_thread_ctx, &pmq, (uint8_t *)"ab", 2);
+
+    if (cnt == 1)
+        result = 1;
+    else
+        printf("1 != %" PRIu32 " ", cnt);
+
+    SCHSDestroyCtx(&mpm_ctx);
+    SCHSDestroyThreadCtx(&mpm_ctx, &mpm_thread_ctx);
+    PmqFree(&pmq);
+    return result;
+}
+
+static int SCHSTest10(void)
+{
+    int result = 0;
+    MpmCtx mpm_ctx;
+    MpmThreadCtx mpm_thread_ctx;
+    PatternMatcherQueue pmq;
+
+    memset(&mpm_ctx, 0, sizeof(MpmCtx));
+    memset(&mpm_thread_ctx, 0, sizeof(MpmThreadCtx));
+    MpmInitCtx(&mpm_ctx, MPM_HS);
+
+    /* 1 match */
+    MpmAddPatternCS(&mpm_ctx, (uint8_t *)"abcdefgh", 8, 0, 0, 0, 0, 0);
+    PmqSetup(&pmq, 1);
+
+    SCHSPreparePatterns(&mpm_ctx);
+    SCHSInitThreadCtx(&mpm_ctx, &mpm_thread_ctx, 0);
+
+    char *buf = "01234567890123456789012345678901234567890123456789"
+                "01234567890123456789012345678901234567890123456789"
+                "abcdefgh"
+                "01234567890123456789012345678901234567890123456789"
+                "01234567890123456789012345678901234567890123456789";
+    uint32_t cnt = SCHSSearch(&mpm_ctx, &mpm_thread_ctx, &pmq, (uint8_t *)buf,
+                              strlen(buf));
+
+    if (cnt == 1)
+        result = 1;
+    else
+        printf("1 != %" PRIu32 " ", cnt);
+
+    SCHSDestroyCtx(&mpm_ctx);
+    SCHSDestroyThreadCtx(&mpm_ctx, &mpm_thread_ctx);
+    PmqFree(&pmq);
+    return result;
+}
+
+static int SCHSTest11(void)
+{
+    int result = 0;
+    MpmCtx mpm_ctx;
+    MpmThreadCtx mpm_thread_ctx;
+    PatternMatcherQueue pmq;
+
+    memset(&mpm_ctx, 0, sizeof(MpmCtx));
+    memset(&mpm_thread_ctx, 0, sizeof(MpmThreadCtx));
+    MpmInitCtx(&mpm_ctx, MPM_HS);
+
+    if (MpmAddPatternCS(&mpm_ctx, (uint8_t *)"he", 2, 0, 0, 1, 0, 0) == -1)
+        goto end;
+    if (MpmAddPatternCS(&mpm_ctx, (uint8_t *)"she", 3, 0, 0, 2, 0, 0) == -1)
+        goto end;
+    if (MpmAddPatternCS(&mpm_ctx, (uint8_t *)"his", 3, 0, 0, 3, 0, 0) == -1)
+        goto end;
+    if (MpmAddPatternCS(&mpm_ctx, (uint8_t *)"hers", 4, 0, 0, 4, 0, 0) == -1)
+        goto end;
+    PmqSetup(&pmq, 5);
+
+    if (SCHSPreparePatterns(&mpm_ctx) == -1)
+        goto end;
+
+    SCHSInitThreadCtx(&mpm_ctx, &mpm_thread_ctx, 0);
+
+    result = 1;
+
+    char *buf = "he";
+    result &= (SCHSSearch(&mpm_ctx, &mpm_thread_ctx, &pmq, (uint8_t *)buf,
+                          strlen(buf)) == 1);
+    buf = "she";
+    result &= (SCHSSearch(&mpm_ctx, &mpm_thread_ctx, &pmq, (uint8_t *)buf,
+                          strlen(buf)) == 2);
+    buf = "his";
+    result &= (SCHSSearch(&mpm_ctx, &mpm_thread_ctx, &pmq, (uint8_t *)buf,
+                          strlen(buf)) == 1);
+    buf = "hers";
+    result &= (SCHSSearch(&mpm_ctx, &mpm_thread_ctx, &pmq, (uint8_t *)buf,
+                          strlen(buf)) == 2);
+
+end:
+    SCHSDestroyCtx(&mpm_ctx);
+    SCHSDestroyThreadCtx(&mpm_ctx, &mpm_thread_ctx);
+    PmqFree(&pmq);
+    return result;
+}
+
+static int SCHSTest12(void)
+{
+    int result = 0;
+    MpmCtx mpm_ctx;
+    MpmThreadCtx mpm_thread_ctx;
+    PatternMatcherQueue pmq;
+
+    memset(&mpm_ctx, 0x00, sizeof(MpmCtx));
+    memset(&mpm_thread_ctx, 0, sizeof(MpmThreadCtx));
+    MpmInitCtx(&mpm_ctx, MPM_HS);
+
+    /* 1 match */
+    MpmAddPatternCS(&mpm_ctx, (uint8_t *)"wxyz", 4, 0, 0, 0, 0, 0);
+    /* 1 match */
+    MpmAddPatternCS(&mpm_ctx, (uint8_t *)"vwxyz", 5, 0, 0, 1, 0, 0);
+    PmqSetup(&pmq, 2);
+
+    SCHSPreparePatterns(&mpm_ctx);
+    SCHSInitThreadCtx(&mpm_ctx, &mpm_thread_ctx, 0);
+
+    char *buf = "abcdefghijklmnopqrstuvwxyz";
+    uint32_t cnt = SCHSSearch(&mpm_ctx, &mpm_thread_ctx, &pmq, (uint8_t *)buf,
+                              strlen(buf));
+
+    if (cnt == 2)
+        result = 1;
+    else
+        printf("2 != %" PRIu32 " ", cnt);
+
+    SCHSDestroyCtx(&mpm_ctx);
+    SCHSDestroyThreadCtx(&mpm_ctx, &mpm_thread_ctx);
+    PmqFree(&pmq);
+    return result;
+}
+
+static int SCHSTest13(void)
+{
+    int result = 0;
+    MpmCtx mpm_ctx;
+    MpmThreadCtx mpm_thread_ctx;
+    PatternMatcherQueue pmq;
+
+    memset(&mpm_ctx, 0x00, sizeof(MpmCtx));
+    memset(&mpm_thread_ctx, 0, sizeof(MpmThreadCtx));
+    MpmInitCtx(&mpm_ctx, MPM_HS);
+
+    /* 1 match */
+    char *pat = "abcdefghijklmnopqrstuvwxyzABCD";
+    MpmAddPatternCS(&mpm_ctx, (uint8_t *)pat, strlen(pat), 0, 0, 0, 0, 0);
+    PmqSetup(&pmq, 1);
+
+    SCHSPreparePatterns(&mpm_ctx);
+    SCHSInitThreadCtx(&mpm_ctx, &mpm_thread_ctx, 0);
+
+    char *buf = "abcdefghijklmnopqrstuvwxyzABCD";
+    uint32_t cnt = SCHSSearch(&mpm_ctx, &mpm_thread_ctx, &pmq, (uint8_t *)buf,
+                              strlen(buf));
+
+    if (cnt == 1)
+        result = 1;
+    else
+        printf("1 != %" PRIu32 " ", cnt);
+
+    SCHSDestroyCtx(&mpm_ctx);
+    SCHSDestroyThreadCtx(&mpm_ctx, &mpm_thread_ctx);
+    PmqFree(&pmq);
+    return result;
+}
+
+static int SCHSTest14(void)
+{
+    int result = 0;
+    MpmCtx mpm_ctx;
+    MpmThreadCtx mpm_thread_ctx;
+    PatternMatcherQueue pmq;
+
+    memset(&mpm_ctx, 0x00, sizeof(MpmCtx));
+    memset(&mpm_thread_ctx, 0, sizeof(MpmThreadCtx));
+    MpmInitCtx(&mpm_ctx, MPM_HS);
+
+    /* 1 match */
+    char *pat = "abcdefghijklmnopqrstuvwxyzABCDE";
+    MpmAddPatternCS(&mpm_ctx, (uint8_t *)pat, strlen(pat), 0, 0, 0, 0, 0);
+    PmqSetup(&pmq, 1);
+
+    SCHSPreparePatterns(&mpm_ctx);
+    SCHSInitThreadCtx(&mpm_ctx, &mpm_thread_ctx, 0);
+
+    char *buf = "abcdefghijklmnopqrstuvwxyzABCDE";
+    uint32_t cnt = SCHSSearch(&mpm_ctx, &mpm_thread_ctx, &pmq, (uint8_t *)buf,
+                              strlen(buf));
+
+    if (cnt == 1)
+        result = 1;
+    else
+        printf("1 != %" PRIu32 " ", cnt);
+
+    SCHSDestroyCtx(&mpm_ctx);
+    SCHSDestroyThreadCtx(&mpm_ctx, &mpm_thread_ctx);
+    PmqFree(&pmq);
+    return result;
+}
+
+static int SCHSTest15(void)
+{
+    int result = 0;
+    MpmCtx mpm_ctx;
+    MpmThreadCtx mpm_thread_ctx;
+    PatternMatcherQueue pmq;
+
+    memset(&mpm_ctx, 0x00, sizeof(MpmCtx));
+    memset(&mpm_thread_ctx, 0, sizeof(MpmThreadCtx));
+    MpmInitCtx(&mpm_ctx, MPM_HS);
+
+    /* 1 match */
+    char *pat = "abcdefghijklmnopqrstuvwxyzABCDEF";
+    MpmAddPatternCS(&mpm_ctx, (uint8_t *)pat, strlen(pat), 0, 0, 0, 0, 0);
+    PmqSetup(&pmq, 1);
+
+    SCHSPreparePatterns(&mpm_ctx);
+    SCHSInitThreadCtx(&mpm_ctx, &mpm_thread_ctx, 0);
+
+    char *buf = "abcdefghijklmnopqrstuvwxyzABCDEF";
+    uint32_t cnt = SCHSSearch(&mpm_ctx, &mpm_thread_ctx, &pmq, (uint8_t *)buf,
+                              strlen(buf));
+
+    if (cnt == 1)
+        result = 1;
+    else
+        printf("1 != %" PRIu32 " ", cnt);
+
+    SCHSDestroyCtx(&mpm_ctx);
+    SCHSDestroyThreadCtx(&mpm_ctx, &mpm_thread_ctx);
+    PmqFree(&pmq);
+    return result;
+}
+
+static int SCHSTest16(void)
+{
+    int result = 0;
+    MpmCtx mpm_ctx;
+    MpmThreadCtx mpm_thread_ctx;
+    PatternMatcherQueue pmq;
+
+    memset(&mpm_ctx, 0x00, sizeof(MpmCtx));
+    memset(&mpm_thread_ctx, 0, sizeof(MpmThreadCtx));
+    MpmInitCtx(&mpm_ctx, MPM_HS);
+
+    /* 1 match */
+    char *pat = "abcdefghijklmnopqrstuvwxyzABC";
+    MpmAddPatternCS(&mpm_ctx, (uint8_t *)pat, strlen(pat), 0, 0, 0, 0, 0);
+    PmqSetup(&pmq, 1);
+
+    SCHSPreparePatterns(&mpm_ctx);
+    SCHSInitThreadCtx(&mpm_ctx, &mpm_thread_ctx, 0);
+
+    char *buf = "abcdefghijklmnopqrstuvwxyzABC";
+    uint32_t cnt = SCHSSearch(&mpm_ctx, &mpm_thread_ctx, &pmq, (uint8_t *)buf,
+                              strlen(buf));
+
+    if (cnt == 1)
+        result = 1;
+    else
+        printf("1 != %" PRIu32 " ", cnt);
+
+    SCHSDestroyCtx(&mpm_ctx);
+    SCHSDestroyThreadCtx(&mpm_ctx, &mpm_thread_ctx);
+    PmqFree(&pmq);
+    return result;
+}
+
+static int SCHSTest17(void)
+{
+    int result = 0;
+    MpmCtx mpm_ctx;
+    MpmThreadCtx mpm_thread_ctx;
+    PatternMatcherQueue pmq;
+
+    memset(&mpm_ctx, 0x00, sizeof(MpmCtx));
+    memset(&mpm_thread_ctx, 0, sizeof(MpmThreadCtx));
+    MpmInitCtx(&mpm_ctx, MPM_HS);
+
+    /* 1 match */
+    char *pat = "abcdefghijklmnopqrstuvwxyzAB";
+    MpmAddPatternCS(&mpm_ctx, (uint8_t *)pat, strlen(pat), 0, 0, 0, 0, 0);
+    PmqSetup(&pmq, 1);
+
+    SCHSPreparePatterns(&mpm_ctx);
+    SCHSInitThreadCtx(&mpm_ctx, &mpm_thread_ctx, 0);
+
+    char *buf = "abcdefghijklmnopqrstuvwxyzAB";
+    uint32_t cnt = SCHSSearch(&mpm_ctx, &mpm_thread_ctx, &pmq, (uint8_t *)buf,
+                              strlen(buf));
+
+    if (cnt == 1)
+        result = 1;
+    else
+        printf("1 != %" PRIu32 " ", cnt);
+
+    SCHSDestroyCtx(&mpm_ctx);
+    SCHSDestroyThreadCtx(&mpm_ctx, &mpm_thread_ctx);
+    PmqFree(&pmq);
+    return result;
+}
+
+static int SCHSTest18(void)
+{
+    int result = 0;
+    MpmCtx mpm_ctx;
+    MpmThreadCtx mpm_thread_ctx;
+    PatternMatcherQueue pmq;
+
+    memset(&mpm_ctx, 0x00, sizeof(MpmCtx));
+    memset(&mpm_thread_ctx, 0, sizeof(MpmThreadCtx));
+    MpmInitCtx(&mpm_ctx, MPM_HS);
+
+    /* 1 match */
+    char *pat = "abcde"
+                "fghij"
+                "klmno"
+                "pqrst"
+                "uvwxy"
+                "z";
+    MpmAddPatternCS(&mpm_ctx, (uint8_t *)pat, strlen(pat), 0, 0, 0, 0, 0);
+    PmqSetup(&pmq, 1);
+
+    SCHSPreparePatterns(&mpm_ctx);
+    SCHSInitThreadCtx(&mpm_ctx, &mpm_thread_ctx, 0);
+
+    char *buf = "abcde"
+                "fghij"
+                "klmno"
+                "pqrst"
+                "uvwxy"
+                "z";
+    uint32_t cnt = SCHSSearch(&mpm_ctx, &mpm_thread_ctx, &pmq, (uint8_t *)buf,
+                              strlen(buf));
+
+    if (cnt == 1)
+        result = 1;
+    else
+        printf("1 != %" PRIu32 " ", cnt);
+
+    SCHSDestroyCtx(&mpm_ctx);
+    SCHSDestroyThreadCtx(&mpm_ctx, &mpm_thread_ctx);
+    PmqFree(&pmq);
+    return result;
+}
+
+static int SCHSTest19(void)
+{
+    int result = 0;
+    MpmCtx mpm_ctx;
+    MpmThreadCtx mpm_thread_ctx;
+    PatternMatcherQueue pmq;
+
+    memset(&mpm_ctx, 0x00, sizeof(MpmCtx));
+    memset(&mpm_thread_ctx, 0, sizeof(MpmThreadCtx));
+    MpmInitCtx(&mpm_ctx, MPM_HS);
+
+    /* 1 */
+    char *pat = "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA";
+    MpmAddPatternCS(&mpm_ctx, (uint8_t *)pat, strlen(pat), 0, 0, 0, 0, 0);
+    PmqSetup(&pmq, 1);
+
+    SCHSPreparePatterns(&mpm_ctx);
+    SCHSInitThreadCtx(&mpm_ctx, &mpm_thread_ctx, 0);
+
+    char *buf = "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA";
+    uint32_t cnt = SCHSSearch(&mpm_ctx, &mpm_thread_ctx, &pmq, (uint8_t *)buf,
+                              strlen(buf));
+
+    if (cnt == 1)
+        result = 1;
+    else
+        printf("1 != %" PRIu32 " ", cnt);
+
+    SCHSDestroyCtx(&mpm_ctx);
+    SCHSDestroyThreadCtx(&mpm_ctx, &mpm_thread_ctx);
+    PmqFree(&pmq);
+    return result;
+}
+
+static int SCHSTest20(void)
+{
+    int result = 0;
+    MpmCtx mpm_ctx;
+    MpmThreadCtx mpm_thread_ctx;
+    PatternMatcherQueue pmq;
+
+    memset(&mpm_ctx, 0x00, sizeof(MpmCtx));
+    memset(&mpm_thread_ctx, 0, sizeof(MpmThreadCtx));
+    MpmInitCtx(&mpm_ctx, MPM_HS);
+
+    /* 1 */
+    char *pat = "AAAAA"
+                "AAAAA"
+                "AAAAA"
+                "AAAAA"
+                "AAAAA"
+                "AAAAA"
+                "AA";
+    MpmAddPatternCS(&mpm_ctx, (uint8_t *)pat, strlen(pat), 0, 0, 0, 0, 0);
+    PmqSetup(&pmq, 1);
+
+    SCHSPreparePatterns(&mpm_ctx);
+    SCHSInitThreadCtx(&mpm_ctx, &mpm_thread_ctx, 0);
+
+    char *buf = "AAAAA"
+                "AAAAA"
+                "AAAAA"
+                "AAAAA"
+                "AAAAA"
+                "AAAAA"
+                "AA";
+    uint32_t cnt = SCHSSearch(&mpm_ctx, &mpm_thread_ctx, &pmq, (uint8_t *)buf,
+                              strlen(buf));
+
+    if (cnt == 1)
+        result = 1;
+    else
+        printf("1 != %" PRIu32 " ", cnt);
+
+    SCHSDestroyCtx(&mpm_ctx);
+    SCHSDestroyThreadCtx(&mpm_ctx, &mpm_thread_ctx);
+    PmqFree(&pmq);
+    return result;
+}
+
+static int SCHSTest21(void)
+{
+    int result = 0;
+    MpmCtx mpm_ctx;
+    MpmThreadCtx mpm_thread_ctx;
+    PatternMatcherQueue pmq;
+
+    memset(&mpm_ctx, 0x00, sizeof(MpmCtx));
+    memset(&mpm_thread_ctx, 0, sizeof(MpmThreadCtx));
+    MpmInitCtx(&mpm_ctx, MPM_HS);
+
+    /* 1 */
+    MpmAddPatternCS(&mpm_ctx, (uint8_t *)"AA", 2, 0, 0, 0, 0, 0);
+    PmqSetup(&pmq, 1);
+
+    SCHSPreparePatterns(&mpm_ctx);
+    SCHSInitThreadCtx(&mpm_ctx, &mpm_thread_ctx, 0);
+
+    uint32_t cnt =
+        SCHSSearch(&mpm_ctx, &mpm_thread_ctx, &pmq, (uint8_t *)"AA", 2);
+
+    if (cnt == 1)
+        result = 1;
+    else
+        printf("1 != %" PRIu32 " ", cnt);
+
+    SCHSDestroyCtx(&mpm_ctx);
+    SCHSDestroyThreadCtx(&mpm_ctx, &mpm_thread_ctx);
+    PmqFree(&pmq);
+    return result;
+}
+
+static int SCHSTest22(void)
+{
+    int result = 0;
+    MpmCtx mpm_ctx;
+    MpmThreadCtx mpm_thread_ctx;
+    PatternMatcherQueue pmq;
+
+    memset(&mpm_ctx, 0x00, sizeof(MpmCtx));
+    memset(&mpm_thread_ctx, 0, sizeof(MpmThreadCtx));
+    MpmInitCtx(&mpm_ctx, MPM_HS);
+
+    /* 1 match */
+    MpmAddPatternCS(&mpm_ctx, (uint8_t *)"abcd", 4, 0, 0, 0, 0, 0);
+    /* 1 match */
+    MpmAddPatternCS(&mpm_ctx, (uint8_t *)"abcde", 5, 0, 0, 1, 0, 0);
+    PmqSetup(&pmq, 2);
+
+    SCHSPreparePatterns(&mpm_ctx);
+    SCHSInitThreadCtx(&mpm_ctx, &mpm_thread_ctx, 0);
+
+    char *buf = "abcdefghijklmnopqrstuvwxyz";
+    uint32_t cnt = SCHSSearch(&mpm_ctx, &mpm_thread_ctx, &pmq, (uint8_t *)buf,
+                              strlen(buf));
+
+    if (cnt == 2)
+        result = 1;
+    else
+        printf("2 != %" PRIu32 " ", cnt);
+
+    SCHSDestroyCtx(&mpm_ctx);
+    SCHSDestroyThreadCtx(&mpm_ctx, &mpm_thread_ctx);
+    PmqFree(&pmq);
+    return result;
+}
+
+static int SCHSTest23(void)
+{
+    int result = 0;
+    MpmCtx mpm_ctx;
+    MpmThreadCtx mpm_thread_ctx;
+    PatternMatcherQueue pmq;
+
+    memset(&mpm_ctx, 0x00, sizeof(MpmCtx));
+    memset(&mpm_thread_ctx, 0, sizeof(MpmThreadCtx));
+    MpmInitCtx(&mpm_ctx, MPM_HS);
+
+    /* 1 */
+    MpmAddPatternCS(&mpm_ctx, (uint8_t *)"AA", 2, 0, 0, 0, 0, 0);
+    PmqSetup(&pmq, 1);
+
+    SCHSPreparePatterns(&mpm_ctx);
+    SCHSInitThreadCtx(&mpm_ctx, &mpm_thread_ctx, 0);
+
+    uint32_t cnt =
+        SCHSSearch(&mpm_ctx, &mpm_thread_ctx, &pmq, (uint8_t *)"aa", 2);
+
+    if (cnt == 0)
+        result = 1;
+    else
+        printf("1 != %" PRIu32 " ", cnt);
+
+    SCHSDestroyCtx(&mpm_ctx);
+    SCHSDestroyThreadCtx(&mpm_ctx, &mpm_thread_ctx);
+    PmqFree(&pmq);
+    return result;
+}
+
+static int SCHSTest24(void)
+{
+    int result = 0;
+    MpmCtx mpm_ctx;
+    MpmThreadCtx mpm_thread_ctx;
+    PatternMatcherQueue pmq;
+
+    memset(&mpm_ctx, 0x00, sizeof(MpmCtx));
+    memset(&mpm_thread_ctx, 0, sizeof(MpmThreadCtx));
+    MpmInitCtx(&mpm_ctx, MPM_HS);
+
+    /* 1 */
+    MpmAddPatternCI(&mpm_ctx, (uint8_t *)"AA", 2, 0, 0, 0, 0, 0);
+    PmqSetup(&pmq, 1);
+
+    SCHSPreparePatterns(&mpm_ctx);
+    SCHSInitThreadCtx(&mpm_ctx, &mpm_thread_ctx, 0);
+
+    uint32_t cnt =
+        SCHSSearch(&mpm_ctx, &mpm_thread_ctx, &pmq, (uint8_t *)"aa", 2);
+
+    if (cnt == 1)
+        result = 1;
+    else
+        printf("1 != %" PRIu32 " ", cnt);
+
+    SCHSDestroyCtx(&mpm_ctx);
+    SCHSDestroyThreadCtx(&mpm_ctx, &mpm_thread_ctx);
+    PmqFree(&pmq);
+    return result;
+}
+
+static int SCHSTest25(void)
+{
+    int result = 0;
+    MpmCtx mpm_ctx;
+    MpmThreadCtx mpm_thread_ctx;
+    PatternMatcherQueue pmq;
+
+    memset(&mpm_ctx, 0x00, sizeof(MpmCtx));
+    memset(&mpm_thread_ctx, 0, sizeof(MpmThreadCtx));
+    MpmInitCtx(&mpm_ctx, MPM_HS);
+
+    MpmAddPatternCI(&mpm_ctx, (uint8_t *)"ABCD", 4, 0, 0, 0, 0, 0);
+    MpmAddPatternCI(&mpm_ctx, (uint8_t *)"bCdEfG", 6, 0, 0, 1, 0, 0);
+    MpmAddPatternCI(&mpm_ctx, (uint8_t *)"fghiJkl", 7, 0, 0, 2, 0, 0);
+    PmqSetup(&pmq, 3);
+
+    SCHSPreparePatterns(&mpm_ctx);
+    SCHSInitThreadCtx(&mpm_ctx, &mpm_thread_ctx, 0);
+
+    char *buf = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
+    uint32_t cnt = SCHSSearch(&mpm_ctx, &mpm_thread_ctx, &pmq, (uint8_t *)buf,
+                              strlen(buf));
+
+    if (cnt == 3)
+        result = 1;
+    else
+        printf("3 != %" PRIu32 " ", cnt);
+
+    SCHSDestroyCtx(&mpm_ctx);
+    SCHSDestroyThreadCtx(&mpm_ctx, &mpm_thread_ctx);
+    PmqFree(&pmq);
+    return result;
+}
+
+static int SCHSTest26(void)
+{
+    int result = 0;
+    MpmCtx mpm_ctx;
+    MpmThreadCtx mpm_thread_ctx;
+    PatternMatcherQueue pmq;
+
+    memset(&mpm_ctx, 0x00, sizeof(MpmCtx));
+    memset(&mpm_thread_ctx, 0, sizeof(MpmThreadCtx));
+    MpmInitCtx(&mpm_ctx, MPM_HS);
+
+    MpmAddPatternCI(&mpm_ctx, (uint8_t *)"Works", 5, 0, 0, 0, 0, 0);
+    MpmAddPatternCS(&mpm_ctx, (uint8_t *)"Works", 5, 0, 0, 1, 0, 0);
+    PmqSetup(&pmq, 2);
+
+    SCHSPreparePatterns(&mpm_ctx);
+    SCHSInitThreadCtx(&mpm_ctx, &mpm_thread_ctx, 0);
+
+    char *buf = "works";
+    uint32_t cnt = SCHSSearch(&mpm_ctx, &mpm_thread_ctx, &pmq, (uint8_t *)buf,
+                              strlen(buf));
+
+    if (cnt == 1)
+        result = 1;
+    else
+        printf("3 != %" PRIu32 " ", cnt);
+
+    SCHSDestroyCtx(&mpm_ctx);
+    SCHSDestroyThreadCtx(&mpm_ctx, &mpm_thread_ctx);
+    PmqFree(&pmq);
+    return result;
+}
+
+static int SCHSTest27(void)
+{
+    int result = 0;
+    MpmCtx mpm_ctx;
+    MpmThreadCtx mpm_thread_ctx;
+    PatternMatcherQueue pmq;
+
+    memset(&mpm_ctx, 0, sizeof(MpmCtx));
+    memset(&mpm_thread_ctx, 0, sizeof(MpmThreadCtx));
+    MpmInitCtx(&mpm_ctx, MPM_HS);
+
+    /* 0 match */
+    MpmAddPatternCS(&mpm_ctx, (uint8_t *)"ONE", 3, 0, 0, 0, 0, 0);
+    PmqSetup(&pmq, 1);
+
+    SCHSPreparePatterns(&mpm_ctx);
+    SCHSInitThreadCtx(&mpm_ctx, &mpm_thread_ctx, 0);
+
+    char *buf = "tone";
+    uint32_t cnt = SCHSSearch(&mpm_ctx, &mpm_thread_ctx, &pmq, (uint8_t *)buf,
+                              strlen(buf));
+
+    if (cnt == 0)
+        result = 1;
+    else
+        printf("0 != %" PRIu32 " ", cnt);
+
+    SCHSDestroyCtx(&mpm_ctx);
+    SCHSDestroyThreadCtx(&mpm_ctx, &mpm_thread_ctx);
+    PmqFree(&pmq);
+    return result;
+}
+
+static int SCHSTest28(void)
+{
+    int result = 0;
+    MpmCtx mpm_ctx;
+    MpmThreadCtx mpm_thread_ctx;
+    PatternMatcherQueue pmq;
+
+    memset(&mpm_ctx, 0, sizeof(MpmCtx));
+    memset(&mpm_thread_ctx, 0, sizeof(MpmThreadCtx));
+    MpmInitCtx(&mpm_ctx, MPM_HS);
+
+    /* 0 match */
+    MpmAddPatternCS(&mpm_ctx, (uint8_t *)"one", 3, 0, 0, 0, 0, 0);
+    PmqSetup(&pmq, 1);
+
+    SCHSPreparePatterns(&mpm_ctx);
+    SCHSInitThreadCtx(&mpm_ctx, &mpm_thread_ctx, 0);
+
+    char *buf = "tONE";
+    uint32_t cnt = SCHSSearch(&mpm_ctx, &mpm_thread_ctx, &pmq, (uint8_t *)buf,
+                              strlen(buf));
+
+    if (cnt == 0)
+        result = 1;
+    else
+        printf("0 != %" PRIu32 " ", cnt);
+
+    SCHSDestroyCtx(&mpm_ctx);
+    SCHSDestroyThreadCtx(&mpm_ctx, &mpm_thread_ctx);
+    PmqFree(&pmq);
+    return result;
+}
+
+static int SCHSTest29(void)
+{
+    uint8_t *buf = (uint8_t *)"onetwothreefourfivesixseveneightnine";
+    uint16_t buflen = strlen((char *)buf);
+    Packet *p = NULL;
+    ThreadVars th_v;
+    DetectEngineThreadCtx *det_ctx = NULL;
+    int result = 0;
+
+    memset(&th_v, 0, sizeof(th_v));
+    p = UTHBuildPacket(buf, buflen, IPPROTO_TCP);
+
+    DetectEngineCtx *de_ctx = DetectEngineCtxInit();
+    if (de_ctx == NULL)
+        goto end;
+    de_ctx->mpm_matcher = MPM_HS;
+
+    de_ctx->flags |= DE_QUIET;
+
+    de_ctx->sig_list = SigInit(
+        de_ctx, "alert tcp any any -> any any "
+                "(content:\"onetwothreefourfivesixseveneightnine\"; sid:1;)");
+    if (de_ctx->sig_list == NULL)
+        goto end;
+    de_ctx->sig_list->next =
+        SigInit(de_ctx, "alert tcp any any -> any any "
+                        "(content:\"onetwothreefourfivesixseveneightnine\"; "
+                        "fast_pattern:3,3; sid:2;)");
+    if (de_ctx->sig_list->next == NULL)
+        goto end;
+
+    SigGroupBuild(de_ctx);
+    DetectEngineThreadCtxInit(&th_v, (void *)de_ctx, (void *)&det_ctx);
+
+    SigMatchSignatures(&th_v, de_ctx, det_ctx, p);
+    if (PacketAlertCheck(p, 1) != 1) {
+        printf("if (PacketAlertCheck(p, 1) != 1) failure\n");
+        goto end;
+    }
+    if (PacketAlertCheck(p, 2) != 1) {
+        printf("if (PacketAlertCheck(p, 1) != 2) failure\n");
+        goto end;
+    }
+
+    result = 1;
+end:
+    if (de_ctx != NULL) {
+        SigGroupCleanup(de_ctx);
+        SigCleanSignatures(de_ctx);
+
+        DetectEngineThreadCtxDeinit(&th_v, (void *)det_ctx);
+        DetectEngineCtxFree(de_ctx);
+    }
+
+    UTHFreePackets(&p, 1);
+    return result;
+}
+
+#endif /* UNITTESTS */
+
+void SCHSRegisterTests(void)
+{
+#ifdef UNITTESTS
+    UtRegisterTest("SCHSTest01", SCHSTest01, 1);
+    UtRegisterTest("SCHSTest02", SCHSTest02, 1);
+    UtRegisterTest("SCHSTest03", SCHSTest03, 1);
+    UtRegisterTest("SCHSTest04", SCHSTest04, 1);
+    UtRegisterTest("SCHSTest05", SCHSTest05, 1);
+    UtRegisterTest("SCHSTest06", SCHSTest06, 1);
+    UtRegisterTest("SCHSTest07", SCHSTest07, 1);
+    UtRegisterTest("SCHSTest08", SCHSTest08, 1);
+    UtRegisterTest("SCHSTest09", SCHSTest09, 1);
+    UtRegisterTest("SCHSTest10", SCHSTest10, 1);
+    UtRegisterTest("SCHSTest11", SCHSTest11, 1);
+    UtRegisterTest("SCHSTest12", SCHSTest12, 1);
+    UtRegisterTest("SCHSTest13", SCHSTest13, 1);
+    UtRegisterTest("SCHSTest14", SCHSTest14, 1);
+    UtRegisterTest("SCHSTest15", SCHSTest15, 1);
+    UtRegisterTest("SCHSTest16", SCHSTest16, 1);
+    UtRegisterTest("SCHSTest17", SCHSTest17, 1);
+    UtRegisterTest("SCHSTest18", SCHSTest18, 1);
+    UtRegisterTest("SCHSTest19", SCHSTest19, 1);
+    UtRegisterTest("SCHSTest20", SCHSTest20, 1);
+    UtRegisterTest("SCHSTest21", SCHSTest21, 1);
+    UtRegisterTest("SCHSTest22", SCHSTest22, 1);
+    UtRegisterTest("SCHSTest23", SCHSTest23, 1);
+    UtRegisterTest("SCHSTest24", SCHSTest24, 1);
+    UtRegisterTest("SCHSTest25", SCHSTest25, 1);
+    UtRegisterTest("SCHSTest26", SCHSTest26, 1);
+    UtRegisterTest("SCHSTest27", SCHSTest27, 1);
+    UtRegisterTest("SCHSTest28", SCHSTest28, 1);
+    UtRegisterTest("SCHSTest29", SCHSTest29, 1);
+#endif
+
+    return;
+}
+
+#endif /* BUILD_HYPERSCAN */
diff --git a/src/util-mpm-hs.h b/src/util-mpm-hs.h
new file mode 100644 (file)
index 0000000..4778ab8
--- /dev/null
@@ -0,0 +1,76 @@
+/* Copyright (C) 2007-2016 Open Information Security Foundation
+ *
+ * You can copy, redistribute or modify this Program under the terms of
+ * the GNU General Public License version 2 as published by the Free
+ * Software Foundation.
+ *
+ * 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
+ * version 2 along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ */
+
+/**
+ * \file
+ *
+ * \author Jim Xu <jim.xu@windriver.com>
+ * \author Justin Viiret <justin.viiret@intel.com>
+ *
+ * MPM pattern matcher that calls the Hyperscan regex matcher.
+ */
+
+#ifndef __UTIL_MPM_HS__H__
+#define __UTIL_MPM_HS__H__
+
+typedef struct SCHSPattern_ {
+    /* length of the pattern */
+    uint16_t len;
+    /* flags describing the pattern */
+    uint8_t flags;
+    /* holds the original pattern that was added */
+    uint8_t *original_pat;
+    /* pattern id */
+    uint32_t id;
+
+    uint16_t offset;
+    uint16_t depth;
+
+    /* sid(s) for this pattern */
+    uint32_t sids_size;
+    SigIntId *sids;
+
+    /* only used at ctx init time, when this structure is part of a hash
+     * table. */
+    struct SCHSPattern_ *next;
+} SCHSPattern;
+
+typedef struct SCHSCtx_ {
+    /* hash used during ctx initialization */
+    SCHSPattern **init_hash;
+
+    /* pattern database and pattern arrays. */
+    void *pattern_db;
+
+    /* size of database, for accounting. */
+    size_t hs_db_size;
+} SCHSCtx;
+
+typedef struct SCHSThreadCtx_ {
+    /* Hyperscan scratch space region for this thread, capable of handling any
+     * database that has been compiled. */
+    void *scratch;
+
+    /* size of scratch space, for accounting. */
+    size_t scratch_size;
+} SCHSThreadCtx;
+
+void MpmHSRegister(void);
+
+void MpmHSGlobalCleanup(void);
+
+#endif /* __UTIL_MPM_HS__H__ */
index ac185e301fa387eac74850284d8e280e4dfa578c..01aafc152f79c38763a3e84113749a1e0e188fa0 100644 (file)
@@ -35,6 +35,7 @@
 #include "util-mpm-ac-gfbs.h"
 #include "util-mpm-ac-bs.h"
 #include "util-mpm-ac-tile.h"
+#include "util-mpm-hs.h"
 #include "util-hashlist.h"
 
 #include "detect-engine.h"
@@ -674,6 +675,9 @@ void MpmTableSetup(void)
     MpmACBSRegister();
     MpmACGfbsRegister();
     MpmACTileRegister();
+#ifdef BUILD_HYPERSCAN
+    MpmHSRegister();
+#endif /* BUILD_HYPERSCAN */
 #ifdef __SC_CUDA_SUPPORT__
     MpmACCudaRegister();
 #endif /* __SC_CUDA_SUPPORT__ */
index da650884b88f4cf113f179d2a340f63e4e9b080c..15d8d5b9e3ffe8156de9f501c3b5de6954c4dfce 100644 (file)
@@ -72,6 +72,7 @@ enum {
     MPM_AC_GFBS,
     MPM_AC_BS,
     MPM_AC_TILE,
+    MPM_HS,
     /* table size */
     MPM_TABLE_SIZE,
 };