From 6dea5e4fed979cb57f3dbc0c9144f1ff1854b800 Mon Sep 17 00:00:00 2001
From: Cyrille Bagard <nocbos@gmail.com>
Date: Wed, 7 Dec 2022 03:52:51 +0100
Subject: Provide functions to test bit fields against bit fields.

---
 plugins/pychrysalide/common/bits.c | 153 +++++++++++++++++++++++++++++++++++++
 plugins/pychrysalide/common/bits.h |   3 +
 src/common/bits.c                  | 128 +++++++++++++++++++++++++++++++
 src/common/bits.h                  |   6 ++
 tests/common/bitfield.py           |  41 ++++++++++
 5 files changed, 331 insertions(+)

diff --git a/plugins/pychrysalide/common/bits.c b/plugins/pychrysalide/common/bits.c
index 73fd55b..e73ce7a 100644
--- a/plugins/pychrysalide/common/bits.c
+++ b/plugins/pychrysalide/common/bits.c
@@ -86,6 +86,12 @@ static PyObject *py_bitfield_test_none(PyObject *, PyObject *);
 /* Détermine si un ensemble de bits est à 1 dans un champ. */
 static PyObject *py_bitfield_test_all(PyObject *, PyObject *);
 
+/* Teste l'état à 0 de bits selon un masque de bits. */
+static PyObject *py_bitfield_test_zeros_with(PyObject *, PyObject *);
+
+/* Teste l'état à 1 de bits selon un masque de bits. */
+static PyObject *py_bitfield_test_ones_with(PyObject *, PyObject *);
+
 /* Indique la taille d'un champ de bits donné. */
 static PyObject *py_bitfield_get_size(PyObject *, void *);
 
@@ -695,6 +701,106 @@ static PyObject *py_bitfield_test_all(PyObject *self, PyObject *args)
 
 /******************************************************************************
 *                                                                             *
+*  Paramètres  : self = champ de bits à consulter.                            *
+*                args = arguments fournis pour la conduite de l'opération.    *
+*                                                                             *
+*  Description : Teste l'état à 0 de bits selon un masque de bits.            *
+*                                                                             *
+*  Retour      : true si les bits visés sont à l'état bas.                    *
+*                                                                             *
+*  Remarques   : -                                                            *
+*                                                                             *
+******************************************************************************/
+
+static PyObject *py_bitfield_test_zeros_with(PyObject *self, PyObject *args)
+{
+    PyObject *result;                       /* Bilan à faire remonter      */
+    unsigned long first;                    /* Indice du premier bit testé */
+    bitfield_t *mask;                       /* Champ de bits natif         */
+    int ret;                                /* Bilan de lecture des args.  */
+    py_bitfield_t *bf;                      /* Instance à manipuler        */
+    bool status;                            /* Bilan d'analyse             */
+
+#define BITFIELD_TEST_ZEROS_WITH_METHOD PYTHON_METHOD_DEF   \
+(                                                           \
+    test_zeros_with, "$self, first, mask, /",               \
+    METH_VARARGS, py_bitfield,                              \
+    "Test a range of bits against another bit field.\n"     \
+    "\n"                                                    \
+    "The area to process starts at bit *first* and the"     \
+    " test relies on bits set within the *mask* object.\n"  \
+    "\n"                                                    \
+    "The result is a boolean value: True if all tested"     \
+    " bits are unset, False otherwise."                     \
+)
+
+    ret = PyArg_ParseTuple(args, "kO&", &first, convert_to_bitfield, &mask);
+    if (!ret) return NULL;
+
+    bf = (py_bitfield_t *)self;
+
+    status = test_zeros_within_bit_field(bf->native, first, mask);
+
+    result = status ? Py_True : Py_False;
+    Py_INCREF(result);
+
+    return result;
+
+}
+
+
+/******************************************************************************
+*                                                                             *
+*  Paramètres  : self = champ de bits à consulter.                            *
+*                args = arguments fournis pour la conduite de l'opération.    *
+*                                                                             *
+*  Description : Teste l'état à 1 de bits selon un masque de bits.            *
+*                                                                             *
+*  Retour      : true si les bits visés sont à l'état haut.                   *
+*                                                                             *
+*  Remarques   : -                                                            *
+*                                                                             *
+******************************************************************************/
+
+static PyObject *py_bitfield_test_ones_with(PyObject *self, PyObject *args)
+{
+    PyObject *result;                       /* Bilan à faire remonter      */
+    unsigned long first;                    /* Indice du premier bit testé */
+    bitfield_t *mask;                       /* Champ de bits natif         */
+    int ret;                                /* Bilan de lecture des args.  */
+    py_bitfield_t *bf;                      /* Instance à manipuler        */
+    bool status;                            /* Bilan d'analyse             */
+
+#define BITFIELD_TEST_ONES_WITH_METHOD PYTHON_METHOD_DEF    \
+(                                                           \
+    test_ones_with, "$self, first, mask, /",                \
+    METH_VARARGS, py_bitfield,                              \
+    "Test a range of bits against another bit field.\n"     \
+    "\n"                                                    \
+    "The area to process starts at bit *first* and the"     \
+    " test relies on bits set within the *mask* object.\n"  \
+    "\n"                                                    \
+    "The result is a boolean value: True if all tested"     \
+    " bits are set, False otherwise."                       \
+)
+
+    ret = PyArg_ParseTuple(args, "kO&", &first, convert_to_bitfield, &mask);
+    if (!ret) return NULL;
+
+    bf = (py_bitfield_t *)self;
+
+    status = test_ones_within_bit_field(bf->native, first, mask);
+
+    result = status ? Py_True : Py_False;
+    Py_INCREF(result);
+
+    return result;
+
+}
+
+
+/******************************************************************************
+*                                                                             *
 *  Paramètres  : self    = classe représentant une instruction.               *
 *                closure = adresse non utilisée ici.                          *
 *                                                                             *
@@ -798,6 +904,8 @@ PyTypeObject *get_python_bitfield_type(void)
         BITFIELD_TEST_METHOD,
         BITFIELD_TEST_NONE_METHOD,
         BITFIELD_TEST_ALL_METHOD,
+        BITFIELD_TEST_ZEROS_WITH_METHOD,
+        BITFIELD_TEST_ONES_WITH_METHOD,
         { NULL }
     };
 
@@ -876,6 +984,51 @@ bool ensure_python_bitfield_is_registered(void)
 
 /******************************************************************************
 *                                                                             *
+*  Paramètres  : arg = argument quelconque à tenter de convertir.             *
+*                dst = destination des valeurs récupérées en cas de succès.   *
+*                                                                             *
+*  Description : Tente de convertir en champ de bits.                         *
+*                                                                             *
+*  Retour      : Bilan de l'opération, voire indications supplémentaires.     *
+*                                                                             *
+*  Remarques   : -                                                            *
+*                                                                             *
+******************************************************************************/
+
+int convert_to_bitfield(PyObject *arg, void *dst)
+{
+    int result;                             /* Bilan à retourner           */
+
+    result = PyObject_IsInstance(arg, (PyObject *)get_python_bitfield_type());
+
+    switch (result)
+    {
+        case -1:
+            /* L'exception est déjà fixée par Python */
+            result = 0;
+            break;
+
+        case 0:
+            PyErr_SetString(PyExc_TypeError, "unable to convert the provided argument to bit field");
+            break;
+
+        case 1:
+            *((bitfield_t **)dst) = ((py_bitfield_t *)arg)->native;
+            break;
+
+        default:
+            assert(false);
+            break;
+
+    }
+
+    return result;
+
+}
+
+
+/******************************************************************************
+*                                                                             *
 *  Paramètres  : field = structure interne à copier en objet Python.          *
 *                                                                             *
 *  Description : Convertit une structure de type 'bitfield_t' en objet Python.*
diff --git a/plugins/pychrysalide/common/bits.h b/plugins/pychrysalide/common/bits.h
index 6ddaaa5..804c3b5 100644
--- a/plugins/pychrysalide/common/bits.h
+++ b/plugins/pychrysalide/common/bits.h
@@ -40,6 +40,9 @@ PyTypeObject *get_python_bitfield_type(void);
 /* Prend en charge l'objet 'pychrysalide.common.BitField'. */
 bool ensure_python_bitfield_is_registered(void);
 
+/* Tente de convertir en champ de bits. */
+int convert_to_bitfield(PyObject *, void *);
+
 /* Convertit une structure de type 'bitfield_t' en objet Python. */
 PyObject *build_from_internal_bitfield(const bitfield_t *);
 
diff --git a/src/common/bits.c b/src/common/bits.c
index a450bb2..d3b45bb 100644
--- a/src/common/bits.c
+++ b/src/common/bits.c
@@ -51,6 +51,9 @@ static bitfield_t *_create_bit_field(size_t);
 /* Détermine si un ensemble de bits est homogène dans un champ. */
 static bool test_state_in_bit_field(const bitfield_t *, size_t, size_t, bool);
 
+/* Teste l'état de bits selon un masque de bits. */
+static bool test_state_within_bit_field(const bitfield_t *, size_t, const bitfield_t *, bool);
+
 
 
 /******************************************************************************
@@ -556,6 +559,131 @@ bool test_all_in_bit_field(const bitfield_t *field, size_t first, size_t count)
 
 /******************************************************************************
 *                                                                             *
+*  Paramètres  : field = champ de bits à modifier.                            *
+*                first = indice du premier bit à traiter.                     *
+*                mask  = second champ de bits à tester logiquement.           *
+*                state = état global à retrouver idéalement.                  *
+*                                                                             *
+*  Description : Teste l'état de bits selon un masque de bits.                *
+*                                                                             *
+*  Retour      : true si les bits visés sont tous à l'état indiqué.           *
+*                                                                             *
+*  Remarques   : -                                                            *
+*                                                                             *
+******************************************************************************/
+
+static bool test_state_within_bit_field(const bitfield_t *field, size_t first, const bitfield_t *mask, bool state)
+{
+    bool result;                            /* Bilan à retourner           */
+    size_t start;                           /* Mot de départ dans le champ */
+    size_t offset;                          /* Décalage des mots à testter */
+    size_t remaining;                       /* Taille du dernier tronçon   */
+    unsigned long finalcut;                 /* Limitation du mot final     */
+    size_t i;                               /* Boucle de parcours          */
+    size_t windex;                          /* Indice du mot courant       */
+    unsigned long word;                     /* Mot reconstituté à tester   */
+    unsigned long bitmask;                  /* Masque à appliquer          */
+    unsigned long test;                     /* Valeur résultante du test   */
+
+    result = true;
+
+    assert((first + mask->length) <= field->length);
+
+    start = first / (sizeof(unsigned long) * 8);
+    offset = first % (sizeof(unsigned long) * 8);
+
+    remaining = mask->length % (sizeof(unsigned long) * 8);
+
+    if (remaining == 0)
+        finalcut = ~0lu;
+    else
+        finalcut = (1lu << remaining) - 1;
+
+    for (i = 0; i < mask->requested && result; i++)
+    {
+        windex = start + i;
+
+        if (offset == 0)
+            word = field->bits[windex];
+
+        else
+        {
+            word = field->bits[windex] >> offset;
+            if ((windex + 1) < field->requested)
+                word |= field->bits[start + i + 1] << (sizeof(unsigned long) * 8 - offset);
+        }
+
+        bitmask = mask->bits[i];
+
+        test = word ^ bitmask;
+
+        if ((i + 1) == mask->requested)
+        {
+            bitmask &= finalcut;
+            test &= finalcut;
+        }
+
+        result = (state ? test == 0 : test == bitmask);
+
+    }
+
+    return result;
+
+}
+
+
+/******************************************************************************
+*                                                                             *
+*  Paramètres  : field = champ de bits à modifier.                            *
+*                first = indice du premier bit à traiter.                     *
+*                mask  = second champ de bits à tester logiquement.           *
+*                                                                             *
+*  Description : Teste l'état à 0 de bits selon un masque de bits.            *
+*                                                                             *
+*  Retour      : true si les bits visés sont à l'état bas.                    *
+*                                                                             *
+*  Remarques   : -                                                            *
+*                                                                             *
+******************************************************************************/
+
+bool test_zeros_within_bit_field(const bitfield_t *field, size_t first, const bitfield_t *mask)
+{
+    bool result;                            /* Valeur retrouvée à renvoyer */
+
+    result = test_state_within_bit_field(field, first, mask, false);
+
+    return result;
+
+}
+
+
+/******************************************************************************
+*                                                                             *
+*  Paramètres  : field = champ de bits à modifier.                            *
+*                first = indice du premier bit à traiter.                     *
+*                mask  = second champ de bits à tester logiquement.           *
+*                                                                             *
+*  Description : Teste l'état à 1 de bits selon un masque de bits.            *
+*                                                                             *
+*  Retour      : true si les bits visés sont à l'état haut.                   *
+*                                                                             *
+*  Remarques   : -                                                            *
+*                                                                             *
+******************************************************************************/
+
+bool test_ones_within_bit_field(const bitfield_t *field, size_t first, const bitfield_t *mask)
+{
+    bool result;                            /* Valeur retrouvée à renvoyer */
+
+    result = test_state_within_bit_field(field, first, mask, true);
+
+    return result;
+
+}
+
+
+/******************************************************************************
+*                                                                             *
 *  Paramètres  : field = champ de bits à consulter.                           *
 *                                                                             *
 *  Description : Détermine le nombre de bits à 1 dans un champ.               *
diff --git a/src/common/bits.h b/src/common/bits.h
index 96ea06a..fb231d5 100644
--- a/src/common/bits.h
+++ b/src/common/bits.h
@@ -78,6 +78,12 @@ bool test_none_in_bit_field(const bitfield_t *, size_t, size_t);
 /* Détermine si un ensemble de bits est à 1 dans un champ. */
 bool test_all_in_bit_field(const bitfield_t *, size_t, size_t);
 
+/* Teste l'état à 0 de bits selon un masque de bits. */
+bool test_zeros_within_bit_field(const bitfield_t *, size_t, const bitfield_t *);
+
+/* Teste l'état à 1 de bits selon un masque de bits. */
+bool test_ones_within_bit_field(const bitfield_t *, size_t, const bitfield_t *);
+
 /* Détermine le nombre de bits à 1 dans un champ. */
 size_t popcount_for_bit_field(const bitfield_t *);
 
diff --git a/tests/common/bitfield.py b/tests/common/bitfield.py
index e014111..7359a8a 100644
--- a/tests/common/bitfield.py
+++ b/tests/common/bitfield.py
@@ -118,6 +118,47 @@ class TestBitFields(ChrysalideTestCase):
         self.assertTrue(bf.test_none(0, 54))
 
 
+    def testBitFieldWithBitField(self):
+        """Test bits in bitfields against other bitfields."""
+
+        bf = BitField(32, 0)
+        bf.set(8, 16)
+
+        mask = BitField(8, 1)
+
+        self.assertTrue(bf.test_ones_with(8, mask))
+        self.assertTrue(bf.test_ones_with(16, mask))
+        self.assertFalse(bf.test_ones_with(17, mask))
+        self.assertTrue(bf.test_zeros_with(24, mask))
+
+        bf = BitField(256, 0)
+        bf.set(60, 8)
+        bf.set(126, 10)
+
+        mask = BitField(4, 1)
+
+        self.assertTrue(bf.test_zeros_with(8, mask))
+        self.assertTrue(bf.test_zeros_with(122, mask))
+
+        self.assertFalse(bf.test_zeros_with(58, mask))
+        self.assertFalse(bf.test_ones_with(58, mask))
+        self.assertTrue(bf.test_ones_with(60, mask))
+        self.assertFalse(bf.test_zeros_with(63, mask))
+        self.assertTrue(bf.test_ones_with(64, mask))
+        self.assertFalse(bf.test_zeros_with(65, mask))
+        self.assertFalse(bf.test_ones_with(65, mask))
+
+        self.assertFalse(bf.test_zeros_with(125, mask))
+        self.assertFalse(bf.test_ones_with(125, mask))
+        self.assertTrue(bf.test_ones_with(128, mask))
+        self.assertFalse(bf.test_zeros_with(129, mask))
+        self.assertTrue(bf.test_ones_with(132, mask))
+        self.assertFalse(bf.test_zeros_with(133, mask))
+        self.assertFalse(bf.test_ones_with(133, mask))
+
+        self.assertTrue(bf.test_zeros_with(136, mask))
+
+
     def testPopCountForBitField(self):
         """Count bits set to 1 in bitfield."""
 
-- 
cgit v0.11.2-87-g4458