Implement rule_db_update, rule_db_compile_activate, db_import, and
db_export.

rule_db_update supports ADD and REMOVE operations with O(1) duplicate
detection via rte_hash. Extended parameters (min_offset, max_offset)
are extracted from rule_flags bits 37-63.

Compilation uses hs_compile_ext_multi() with per-rule extended
parameter support. Per-QP scratch is allocated with rollback on
partial failure.

The configure path optionally imports a serialized database when
cfg->rule_db is provided.

Signed-off-by: Prudvi Deti <[email protected]>
---
 doc/guides/regexdevs/hs.rst |  18 ++
 drivers/regex/hs/hs_regex.c | 476 +++++++++++++++++++++++++++++++++++-
 drivers/regex/hs/hs_regex.h |  15 ++
 3 files changed, 508 insertions(+), 1 deletion(-)

diff --git a/doc/guides/regexdevs/hs.rst b/doc/guides/regexdevs/hs.rst
index dc22514..99a0e82 100644
--- a/doc/guides/regexdevs/hs.rst
+++ b/doc/guides/regexdevs/hs.rst
@@ -155,6 +155,24 @@ Alternatively, a pre-compiled serialized database can be 
loaded
 during ``rte_regexdev_configure()`` via ``cfg.rule_db`` and
 ``cfg.rule_db_len``, or at any time via ``rte_regexdev_rule_db_import()``.
 
+Serialized databases are not portable across CPU platforms or
+Hyperscan library versions: importing a database built for a
+different CPU type or a different Hyperscan version fails with
+``HS_DB_PLATFORM_ERROR`` or ``HS_DB_VERSION_ERROR`` respectively. Only
+import databases exported (via ``rule_db_export()``) from a matching
+CPU platform and Hyperscan version.
+
+``rule_db_export()`` treats its output as an opaque byte buffer with
+no alignment requirement: Hyperscan's serialized format is copied via
+``memcpy()`` and is not accessed through any aligned type.
+
+``rule_db_update()`` processes rules in order and commits each one
+(add or remove) as it succeeds. On failure, it returns the index of
+the first failed rule; rules before that index are already
+committed. Applications must not resubmit the original full array on
+partial failure — only correct the failed rule and resubmit it along
+with any remaining rules from the returned index onward.
+
 Statistics
 ~~~~~~~~~~
 
diff --git a/drivers/regex/hs/hs_regex.c b/drivers/regex/hs/hs_regex.c
index 888ff62..9462d49 100644
--- a/drivers/regex/hs/hs_regex.c
+++ b/drivers/regex/hs/hs_regex.c
@@ -39,6 +39,10 @@ RTE_LOG_REGISTER_DEFAULT(hs_regex_logtype, NOTICE);
 #define HS_LOG(level, ...) \
        RTE_LOG_LINE(level, HS_REGEX, __VA_ARGS__)
 
+static int
+hs_regex_rule_db_import(struct rte_regexdev *dev, const char *rule_db,
+                       uint32_t rule_db_len);
+
 /* Device Info */
 static int
 hs_regex_info_get(struct rte_regexdev *dev __rte_unused,
@@ -66,6 +70,7 @@ hs_regex_configure(struct rte_regexdev *dev,
                   const struct rte_regexdev_config *cfg)
 {
        struct hs_regex_priv *priv;
+       int ret;
 
        if (dev == NULL || cfg == NULL)
                return -EINVAL;
@@ -93,7 +98,6 @@ hs_regex_configure(struct rte_regexdev *dev,
        /* Reconfigure replaces rules, database, and queue resources. */
        if (priv->rules) {
                uint32_t i;
-
                for (i = 0; i < priv->nb_rules; i++)
                        rte_free(priv->rules[i].pattern);
                rte_free(priv->rules);
@@ -101,6 +105,10 @@ hs_regex_configure(struct rte_regexdev *dev,
                priv->nb_rules = 0;
                priv->rules_cap = 0;
        }
+       if (priv->rule_id_hash) {
+               rte_hash_free(priv->rule_id_hash);
+               priv->rule_id_hash = NULL;
+       }
        if (priv->db) {
                hs_free_database(priv->db);
                priv->db = NULL;
@@ -139,6 +147,22 @@ hs_regex_configure(struct rte_regexdev *dev,
               priv->nb_queue_pairs, priv->max_matches);
 
        priv->dev_state = HS_REGEX_DEV_CONFIGURED;
+
+       if (cfg->rule_db != NULL && cfg->rule_db_len > 0) {
+               ret = hs_regex_rule_db_import(dev, cfg->rule_db,
+                                            cfg->rule_db_len);
+               if (ret < 0) {
+                       HS_LOG(ERR, "Failed to import rule DB in configure");
+                       rte_free(priv->qps);
+                       priv->qps = NULL;
+                       priv->nb_queue_pairs = 0;
+                       priv->max_matches = 0;
+                       priv->nb_groups = 0;
+                       priv->dev_state = HS_REGEX_DEV_CREATED;
+                       return ret;
+               }
+       }
+
        return 0;
 }
 
@@ -240,6 +264,452 @@ hs_regex_qp_setup(struct rte_regexdev *dev, uint16_t 
qp_id,
        return 0;
 }
 
+/*
+ * Rule Database Update
+ * On failure, returns the index of the first failed rule; rules
+ * before that index are already committed (not rolled back).
+ */
+static int
+hs_regex_rule_db_update(struct rte_regexdev *dev,
+                       const struct rte_regexdev_rule *rules,
+                       uint16_t nb_rules)
+{
+       struct hs_regex_priv *priv;
+       const uint64_t known_flags = RTE_REGEX_PCRE_RULE_ALLOW_EMPTY_F |
+               RTE_REGEX_PCRE_RULE_CASELESS_F |
+               RTE_REGEX_PCRE_RULE_DOTALL_F |
+               RTE_REGEX_PCRE_RULE_MULTILINE_F |
+               RTE_REGEX_PCRE_RULE_UCP_F |
+               RTE_REGEX_PCRE_RULE_UTF_F;
+       uint64_t flag_bits;
+       uint64_t rf;
+       uint16_t i;
+
+       if (dev == NULL || rules == NULL)
+               return -EINVAL;
+
+       priv = dev->data->dev_private;
+       if (priv == NULL)
+               return -EINVAL;
+
+       if (nb_rules == 0)
+               return 0;
+
+       if (!priv->rule_id_hash) {
+               char hash_name[RTE_HASH_NAMESIZE];
+               struct rte_hash_parameters hp = {
+                       .entries = HS_REGEX_MAX_RULES,
+                       .key_len = sizeof(uint32_t),
+                       .socket_id = SOCKET_ID_ANY,
+               };
+
+               snprintf(hash_name, sizeof(hash_name), "hs_rule_ids_%u",
+                        dev->data->dev_id);
+               hp.name = hash_name;
+               priv->rule_id_hash = rte_hash_create(&hp);
+               if (!priv->rule_id_hash) {
+                       HS_LOG(ERR, "Failed to create rule_id hash");
+                       return -ENOMEM;
+               }
+       }
+
+       for (i = 0; i < nb_rules; i++) {
+               if (rules[i].op != RTE_REGEX_RULE_OP_ADD &&
+                   rules[i].op != RTE_REGEX_RULE_OP_REMOVE) {
+                       HS_LOG(ERR, "Rule %u: unsupported operation %u",
+                              rules[i].rule_id, rules[i].op);
+                       rte_errno = EINVAL;
+                       return i;
+               }
+
+               flag_bits = rules[i].rule_flags &
+                       ((1ULL << HS_REGEX_EXT_MAX_OFFSET_SHIFT) - 1);
+
+               if (flag_bits & ~known_flags) {
+                       HS_LOG(ERR, "Rule %u: unsupported flags 0x%" PRIx64,
+                              rules[i].rule_id,
+                              (uint64_t)(flag_bits & ~known_flags));
+                       rte_errno = ENOTSUP;
+                       return i;
+               }
+
+               if (rules[i].op == RTE_REGEX_RULE_OP_ADD) {
+                       int hash_ret;
+                       uint32_t idx;
+
+                       if (rules[i].rule_id > 0xFFFFF) {
+                               HS_LOG(WARNING,
+                                      "Rule ID %u exceeds 20-bit match result 
width; "
+                                      "reported ID will be truncated",
+                                      rules[i].rule_id);
+                       }
+
+                       /* Reject empty or NULL patterns. */
+                       if (!rules[i].pcre_rule || rules[i].pcre_rule_len == 0) 
{
+                               HS_LOG(ERR, "Rule %u: NULL or empty pattern",
+                                      rules[i].rule_id);
+                               rte_errno = EINVAL;
+                               return i;
+                       }
+
+                       if (priv->rule_id_hash &&
+                           rte_hash_lookup(priv->rule_id_hash,
+                                          &rules[i].rule_id) >= 0) {
+                               HS_LOG(ERR, "Rule %u: duplicate rule_id",
+                                      rules[i].rule_id);
+                               rte_errno = EINVAL;
+                               return i;
+                       }
+
+                       if (priv->nb_rules >= HS_REGEX_MAX_RULES) {
+                               HS_LOG(ERR, "Rule limit reached (%u)",
+                                      HS_REGEX_MAX_RULES);
+                               rte_errno = ENOSPC;
+                               return i;
+                       }
+
+                       if (priv->nb_rules >= priv->rules_cap) {
+                               uint32_t new_cap = priv->rules_cap ?
+                                       priv->rules_cap * 2 :
+                                       HS_REGEX_INITIAL_RULES_CAP;
+                               struct hs_regex_rule *tmp = rte_realloc(
+                                       priv->rules,
+                                       new_cap * sizeof(struct hs_regex_rule), 
0);
+                               if (!tmp) {
+                                       HS_LOG(ERR, "Failed to grow rules");
+                                       rte_errno = ENOMEM;
+                                       return i;
+                               }
+                               priv->rules = tmp;
+                               priv->rules_cap = new_cap;
+                       }
+
+                       idx = priv->nb_rules;
+
+                       priv->rules[idx].pattern = rte_malloc("hs_pattern",
+                               rules[i].pcre_rule_len + 1, 0);
+                       if (!priv->rules[idx].pattern) {
+                               rte_errno = ENOMEM;
+                               return i;
+                       }
+                       memcpy(priv->rules[idx].pattern,
+                              rules[i].pcre_rule, rules[i].pcre_rule_len);
+                       priv->rules[idx].pattern[rules[i].pcre_rule_len] = '\0';
+
+                       priv->rules[idx].rule_id = rules[i].rule_id;
+                       priv->rules[idx].group_id = rules[i].group_id;
+                       priv->rules[idx].rule_flags = rules[i].rule_flags;
+
+                       rf = rules[i].rule_flags;
+                       priv->rules[idx].max_offset =
+                               (rf >> HS_REGEX_EXT_MAX_OFFSET_SHIFT) &
+                               HS_REGEX_EXT_MAX_OFFSET_MASK;
+                       priv->rules[idx].min_offset =
+                               (rf >> HS_REGEX_EXT_MIN_OFFSET_SHIFT) &
+                               HS_REGEX_EXT_MIN_OFFSET_MASK;
+                       priv->rules[idx].min_length = 0;
+
+                       hash_ret = rte_hash_add_key(priv->rule_id_hash,
+                                               &rules[i].rule_id);
+                       if (hash_ret < 0) {
+                               HS_LOG(ERR, "Rule %u: failed to add rule_id to 
hash: %d",
+                                      rules[i].rule_id, hash_ret);
+                               rte_free(priv->rules[idx].pattern);
+                               memset(&priv->rules[idx], 0,
+                                      sizeof(priv->rules[idx]));
+                               rte_errno = -hash_ret;
+                               return i;
+                       }
+
+                       priv->nb_rules++;
+
+               } else if (rules[i].op == RTE_REGEX_RULE_OP_REMOVE) {
+                       int hash_ret;
+                       uint32_t j;
+
+                       for (j = 0; j < priv->nb_rules; j++) {
+                               if (priv->rules[j].rule_id == rules[i].rule_id)
+                                       break;
+                       }
+                       if (j == priv->nb_rules) {
+                               HS_LOG(ERR, "Rule %u: rule_id not found",
+                                      rules[i].rule_id);
+                               rte_errno = ENOENT;
+                               return i;
+                       }
+
+                       hash_ret = rte_hash_del_key(priv->rule_id_hash,
+                                               &rules[i].rule_id);
+                       if (hash_ret < 0) {
+                               HS_LOG(ERR, "Rule %u: failed to remove rule_id 
from hash: %d",
+                                      rules[i].rule_id, hash_ret);
+                               rte_errno = -hash_ret;
+                               return i;
+                       }
+
+                       rte_free(priv->rules[j].pattern);
+                       memmove(&priv->rules[j], &priv->rules[j + 1],
+                               (priv->nb_rules - j - 1) *
+                               sizeof(struct hs_regex_rule));
+                       priv->nb_rules--;
+               }
+       }
+
+       priv->db_compiled = 0;
+       HS_LOG(INFO, "Rule DB updated: %u total rules", priv->nb_rules);
+       return nb_rules;
+}
+
+/* Compile and Activate */
+static int
+hs_regex_rule_db_compile_activate(struct rte_regexdev *dev)
+{
+       struct hs_regex_priv *priv;
+       hs_compile_error_t *compile_err = NULL;
+       hs_error_t err;
+       const char **expressions;
+       unsigned int *flags;
+       unsigned int *ids;
+       hs_expr_ext_t *ext;
+       const hs_expr_ext_t **ext_ptrs;
+       uint32_t i;
+
+       if (dev == NULL)
+               return -EINVAL;
+
+       priv = dev->data->dev_private;
+       if (priv == NULL)
+               return -EINVAL;
+
+       if (priv->nb_rules == 0) {
+               HS_LOG(ERR, "No rules to compile");
+               return -EINVAL;
+       }
+
+       if (priv->qps == NULL) {
+               HS_LOG(ERR, "Cannot compile: queue pairs not allocated");
+               return -EINVAL;
+       }
+
+       if (priv->db) {
+               hs_free_database(priv->db);
+               priv->db = NULL;
+               priv->db_compiled = 0;
+       }
+
+       expressions = rte_malloc("hs_expr",
+                                sizeof(char *) * priv->nb_rules, 0);
+       flags = rte_malloc("hs_flags",
+                          sizeof(unsigned int) * priv->nb_rules, 0);
+       ids = rte_malloc("hs_ids",
+                        sizeof(unsigned int) * priv->nb_rules, 0);
+       ext = rte_zmalloc("hs_ext",
+                         sizeof(hs_expr_ext_t) * priv->nb_rules, 0);
+       ext_ptrs = rte_malloc("hs_ext_ptrs",
+                             sizeof(hs_expr_ext_t *) * priv->nb_rules, 0);
+
+       if (!expressions || !flags || !ids || !ext || !ext_ptrs) {
+               rte_free(expressions);
+               rte_free(flags);
+               rte_free(ids);
+               rte_free(ext);
+               rte_free(ext_ptrs);
+               return -ENOMEM;
+       }
+
+       for (i = 0; i < priv->nb_rules; i++) {
+               expressions[i] = priv->rules[i].pattern;
+               ids[i] = priv->rules[i].rule_id;
+
+               flags[i] = 0;
+               if (priv->rules[i].rule_flags & RTE_REGEX_PCRE_RULE_CASELESS_F)
+                       flags[i] |= HS_FLAG_CASELESS;
+               if (priv->rules[i].rule_flags & RTE_REGEX_PCRE_RULE_DOTALL_F)
+                       flags[i] |= HS_FLAG_DOTALL;
+               if (priv->rules[i].rule_flags & RTE_REGEX_PCRE_RULE_MULTILINE_F)
+                       flags[i] |= HS_FLAG_MULTILINE;
+               if (priv->rules[i].rule_flags & RTE_REGEX_PCRE_RULE_UTF_F)
+                       flags[i] |= HS_FLAG_UTF8;
+
+               ext[i].flags = 0;
+               if (priv->rules[i].min_offset) {
+                       ext[i].flags |= HS_EXT_FLAG_MIN_OFFSET;
+                       ext[i].min_offset = priv->rules[i].min_offset;
+               }
+               if (priv->rules[i].max_offset) {
+                       ext[i].flags |= HS_EXT_FLAG_MAX_OFFSET;
+                       ext[i].max_offset = priv->rules[i].max_offset;
+               }
+               if (priv->rules[i].min_length) {
+                       ext[i].flags |= HS_EXT_FLAG_MIN_LENGTH;
+                       ext[i].min_length = priv->rules[i].min_length;
+               }
+               ext_ptrs[i] = &ext[i];
+       }
+
+       err = hs_compile_ext_multi(expressions, flags, ids, ext_ptrs,
+                                  priv->nb_rules, HS_MODE_BLOCK, NULL,
+                                  &priv->db, &compile_err);
+
+       rte_free(expressions);
+       rte_free(flags);
+       rte_free(ids);
+       rte_free(ext);
+       rte_free(ext_ptrs);
+
+       if (err != HS_SUCCESS) {
+               HS_LOG(ERR, "hs_compile_ext_multi failed: %s (pattern %d)",
+                      compile_err ? compile_err->message : "unknown",
+                      compile_err ? compile_err->expression : -1);
+               if (compile_err)
+                       hs_free_compile_error(compile_err);
+               return -EINVAL;
+       }
+
+       for (i = 0; i < priv->nb_queue_pairs; i++) {
+               struct hs_regex_qp *qp = &priv->qps[i];
+
+               if (qp->scratch) {
+                       hs_free_scratch(qp->scratch);
+                       qp->scratch = NULL;
+               }
+               err = hs_alloc_scratch(priv->db, &qp->scratch);
+               if (err != HS_SUCCESS) {
+                       uint32_t j;
+
+                       HS_LOG(ERR, "Scratch alloc failed for qp %u", i);
+                       for (j = 0; j < i; j++) {
+                               if (priv->qps[j].scratch) {
+                                       hs_free_scratch(priv->qps[j].scratch);
+                                       priv->qps[j].scratch = NULL;
+                               }
+                       }
+                       hs_free_database(priv->db);
+                       priv->db = NULL;
+                       return -ENOMEM;
+               }
+       }
+
+       priv->db_compiled = 1;
+       HS_LOG(INFO, "Compiled %u rules into Hyperscan database",
+              priv->nb_rules);
+       return 0;
+}
+
+static int
+hs_regex_rule_db_import(struct rte_regexdev *dev, const char *rule_db,
+                       uint32_t rule_db_len)
+{
+       struct hs_regex_priv *priv;
+       hs_error_t err;
+       uint32_t i;
+
+       if (dev == NULL)
+               return -EINVAL;
+
+       priv = dev->data->dev_private;
+       if (priv == NULL)
+               return -EINVAL;
+
+       if (!rule_db || rule_db_len == 0) {
+               HS_LOG(ERR, "Invalid rule_db pointer or length");
+               return -EINVAL;
+       }
+
+       if (rule_db_len > HS_REGEX_MAX_RULE_DB_LEN) {
+               HS_LOG(ERR, "rule_db_len %u exceeds max %u",
+                      rule_db_len, HS_REGEX_MAX_RULE_DB_LEN);
+               return -EINVAL;
+       }
+
+       if (priv->qps == NULL) {
+               HS_LOG(ERR, "Cannot import: queue pairs not allocated");
+               return -EINVAL;
+       }
+
+       if (priv->db) {
+               hs_free_database(priv->db);
+               priv->db = NULL;
+       }
+       priv->db_compiled = 0;
+
+       err = hs_deserialize_database(rule_db, (size_t)rule_db_len, &priv->db);
+       if (err != HS_SUCCESS) {
+               HS_LOG(ERR, "hs_deserialize_database failed (error %d)", err);
+               return -EINVAL;
+       }
+
+       for (i = 0; i < priv->nb_queue_pairs; i++) {
+               struct hs_regex_qp *qp = &priv->qps[i];
+
+               if (qp->scratch) {
+                       hs_free_scratch(qp->scratch);
+                       qp->scratch = NULL;
+               }
+               err = hs_alloc_scratch(priv->db, &qp->scratch);
+               if (err != HS_SUCCESS) {
+                       uint32_t j;
+
+                       HS_LOG(ERR, "Scratch alloc failed for qp %u"
+                              " after import", i);
+                       for (j = 0; j < i; j++) {
+                               if (priv->qps[j].scratch) {
+                                       hs_free_scratch(priv->qps[j].scratch);
+                                       priv->qps[j].scratch = NULL;
+                               }
+                       }
+                       hs_free_database(priv->db);
+                       priv->db = NULL;
+                       return -ENOMEM;
+               }
+       }
+
+       priv->db_compiled = 1;
+       HS_LOG(INFO, "Imported serialized Hyperscan database (%u bytes)",
+              rule_db_len);
+       return 0;
+}
+
+static int
+hs_regex_rule_db_export(struct rte_regexdev *dev, char *rule_db)
+{
+       struct hs_regex_priv *priv;
+       hs_error_t err;
+       char *buf;
+       size_t len;
+
+       if (dev == NULL)
+               return -EINVAL;
+
+       priv = dev->data->dev_private;
+       if (priv == NULL)
+               return -EINVAL;
+
+       if (!priv->db) {
+               HS_LOG(ERR, "No database to export");
+               return -EINVAL;
+       }
+
+       err = hs_serialize_database(priv->db, &buf, &len);
+       if (err != HS_SUCCESS) {
+               HS_LOG(ERR, "hs_serialize_database failed (error %d)", err);
+               return -EIO;
+       }
+
+       if (rule_db == NULL) {
+               free(buf);
+               if (len > INT_MAX) {
+                       HS_LOG(ERR, "Serialized DB too large (%zu bytes)", len);
+                       return -EOVERFLOW;
+               }
+               return (int)len;
+       }
+
+       memcpy(rule_db, buf, len);
+       free(buf);
+       return 0;
+}
+
 /* Fast path stubs replaced by real implementations in later patches. */
 
 static uint16_t
@@ -264,6 +734,10 @@ static const struct rte_regexdev_ops hs_regexdev_ops = {
        .dev_info_get = hs_regex_info_get,
        .dev_configure = hs_regex_configure,
        .dev_qp_setup = hs_regex_qp_setup,
+       .dev_rule_db_update = hs_regex_rule_db_update,
+       .dev_rule_db_compile_activate = hs_regex_rule_db_compile_activate,
+       .dev_db_import = hs_regex_rule_db_import,
+       .dev_db_export = hs_regex_rule_db_export,
 };
 
 /* Device Lifecycle */
diff --git a/drivers/regex/hs/hs_regex.h b/drivers/regex/hs/hs_regex.h
index be4c2e9..e48ac86 100644
--- a/drivers/regex/hs/hs_regex.h
+++ b/drivers/regex/hs/hs_regex.h
@@ -6,6 +6,7 @@
 #define HS_REGEX_H
 
 #include <rte_regexdev.h>
+#include <rte_hash.h>
 #include <hs/hs.h>
 
 #define HS_REGEX_DRIVER_NAME "regex_hs"
@@ -15,6 +16,16 @@
 #define HS_REGEX_MAX_RULES 1000000
 #define HS_REGEX_DEFAULT_NB_DESC 1024
 #define HS_REGEX_MAX_NB_DESC 32768
+/* Sanity cap on imported serialized database size (defense in depth;
+ * Hyperscan allocates memory proportional to this size).
+ */
+#define HS_REGEX_MAX_RULE_DB_LEN (512U * 1024 * 1024)
+
+/* Ext params encoded in rule_flags bits 37-63 */
+#define HS_REGEX_EXT_MAX_OFFSET_SHIFT 37
+#define HS_REGEX_EXT_MAX_OFFSET_MASK  0x1FFFULL
+#define HS_REGEX_EXT_MIN_OFFSET_SHIFT 50
+#define HS_REGEX_EXT_MIN_OFFSET_MASK  0x3FFFULL
 
 /* Device lifecycle state machine. */
 enum hs_regex_dev_state {
@@ -30,6 +41,9 @@ struct hs_regex_rule {
        uint32_t rule_id;
        uint16_t group_id;
        uint64_t rule_flags;
+       uint64_t min_offset;
+       uint64_t max_offset;
+       uint64_t min_length;
 };
 
 /* Queue pair */
@@ -48,6 +62,7 @@ struct hs_regex_qp {
 /* Per-device private data */
 struct hs_regex_priv {
        struct hs_regex_rule *rules;
+       struct rte_hash *rule_id_hash;
        uint32_t nb_rules;
        uint32_t rules_cap;
 
-- 
2.43.0

Reply via email to