From 8df9996699cdf96887613f99181173295a19bd76 Mon Sep 17 00:00:00 2001
From: Cyrille Bagard <nocbos@gmail.com>
Date: Tue, 23 Mar 2021 08:57:58 +0100
Subject: Create methods to deal with hex conversions.

---
 plugins/pychrysalide/common/Makefile.am |   1 +
 plugins/pychrysalide/common/hex.c       | 186 ++++++++++++++++++++++++++++++++
 plugins/pychrysalide/common/hex.h       |  39 +++++++
 plugins/pychrysalide/common/module.c    |   2 +
 src/common/Makefile.am                  |   1 +
 src/common/hex.c                        | 139 ++++++++++++++++++++++++
 src/common/hex.h                        |  42 ++++++++
 tests/common/hex.py                     |  28 +++++
 8 files changed, 438 insertions(+)
 create mode 100644 plugins/pychrysalide/common/hex.c
 create mode 100644 plugins/pychrysalide/common/hex.h
 create mode 100644 src/common/hex.c
 create mode 100644 src/common/hex.h
 create mode 100644 tests/common/hex.py

diff --git a/plugins/pychrysalide/common/Makefile.am b/plugins/pychrysalide/common/Makefile.am
index 5f54fe8..1fb268d 100644
--- a/plugins/pychrysalide/common/Makefile.am
+++ b/plugins/pychrysalide/common/Makefile.am
@@ -4,6 +4,7 @@ noinst_LTLIBRARIES = libpychrysacommon.la
 libpychrysacommon_la_SOURCES =			\
 	bits.h bits.c						\
 	fnv1a.h fnv1a.c						\
+	hex.h hex.c							\
 	leb128.h leb128.c					\
 	module.h module.c					\
 	packed.h packed.c					\
diff --git a/plugins/pychrysalide/common/hex.c b/plugins/pychrysalide/common/hex.c
new file mode 100644
index 0000000..6f84c19
--- /dev/null
+++ b/plugins/pychrysalide/common/hex.c
@@ -0,0 +1,186 @@
+
+/* Chrysalide - Outil d'analyse de fichiers binaires
+ * hex.c - équivalent Python du fichier "common/hex.c"
+ *
+ * Copyright (C) 2021 Cyrille Bagard
+ *
+ *  This file is part of Chrysalide.
+ *
+ *  Chrysalide is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  Chrysalide is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+
+#include "hex.h"
+
+
+#include <assert.h>
+#include <malloc.h>
+#include <pygobject.h>
+
+
+#include <common/hex.h>
+
+
+#include "../access.h"
+#include "../helpers.h"
+
+
+
+/* Encode des données en chaîne hexadécimale. */
+static PyObject *py_hex_encode_hex(PyObject *, PyObject *);
+
+/* Décode un caractère hexadécimal. */
+static PyObject *py_hex_decode_hex_digit(PyObject *, PyObject *);
+
+
+
+/******************************************************************************
+*                                                                             *
+*  Paramètres  : self = objet Python concerné par l'appel.                    *
+*                args = arguments à utiliser pour l'opération.                *
+*                                                                             *
+*  Description : Encode des données en chaîne hexadécimale.                   *
+*                                                                             *
+*  Retour      : Chaîne hexadécimale.                                         *
+*                                                                             *
+*  Remarques   : -                                                            *
+*                                                                             *
+******************************************************************************/
+
+static PyObject *py_hex_encode_hex(PyObject *self, PyObject *args)
+{
+    PyObject *result;                       /* Valeur à retourner          */
+    int lower;                              /* Taille des caractères finaux*/
+    const char *data;                       /* Données à traiter           */
+    Py_ssize_t len;                         /* Quantité de ces données     */
+    int ret;                                /* Bilan de lecture des args.  */
+    char *buffer;                           /* Tampon de travail           */
+
+#define HEX_ENCODE_HEX_METHOD PYTHON_METHOD_DEF                         \
+(                                                                       \
+    encode_hex, "data, /, lower = True",                                \
+    METH_VARARGS, py_hex,                                               \
+    "Convert data to a hex string.\n"                                   \
+    "\n"                                                                \
+    "The *data* has to be a string or a read-only bytes-like object."   \
+    " The *lower* argument defines the case of the result string.\n"    \
+    "\n"                                                                \
+    "This method may be only usefull for the internal test suite as"    \
+    " there is a native Python alternative:\n"                          \
+    "\n"                                                                \
+    "    b'ABC'.hex()\n"                                                \
+    "    '414243'"                                                      \
+)
+
+    lower = 1;
+
+    ret = PyArg_ParseTuple(args, "s#|p", &data, &len, &lower);
+    if (!ret) return NULL;
+
+    buffer = malloc((len * 2 + 1) * sizeof(char));
+
+    encode_hex(data, len, lower, buffer);
+
+    result = PyUnicode_FromString(buffer);
+
+    free(buffer);
+
+    return result;
+
+}
+
+
+/******************************************************************************
+*                                                                             *
+*  Paramètres  : self = objet Python concerné par l'appel.                    *
+*                args = arguments à utiliser pour l'opération.                *
+*                                                                             *
+*  Description : Décode un caractère hexadécimal.                             *
+*                                                                             *
+*  Retour      : Bilan de l'opération : valeur en cas de succès, None sinon.  *
+*                                                                             *
+*  Remarques   : -                                                            *
+*                                                                             *
+******************************************************************************/
+
+static PyObject *py_hex_decode_hex_digit(PyObject *self, PyObject *args)
+{
+    PyObject *result;                       /* Valeur à retourner          */
+    char byte;                              /* Valeur hexadécimale         */
+    int ret;                                /* Bilan de lecture des args.  */
+    uint8_t value;                          /* Valeur brute transcrite     */
+    bool status;                            /* Bilan de l'opération        */
+
+#define HEX_DECODE_HEX_DIGIT_METHOD PYTHON_METHOD_DEF       \
+(                                                           \
+    decode_hex_digit, "chr",                                \
+    METH_VARARGS, py_hex,                                   \
+    "Convert a string character to an integer value."       \
+    "\n"                                                    \
+    "The *chr* can be a string character of length 1.\n"    \
+    "\n"                                                    \
+    "The result is an integer value on success or *None*"   \
+    " in case of failure."                                  \
+)
+
+    ret = PyArg_ParseTuple(args, "C", &byte);
+    if (!ret) return NULL;
+
+    status = decode_hex_digit((const char *)&byte, &value);
+
+    if (status)
+        result = PyLong_FromUnsignedLong(value);
+
+    else
+    {
+        result = Py_None;
+        Py_INCREF(result);
+    }
+
+    return result;
+
+}
+
+
+/******************************************************************************
+*                                                                             *
+*  Paramètres  : -                                                            *
+*                                                                             *
+*  Description : Définit une extension du module 'common' à compléter.        *
+*                                                                             *
+*  Retour      : Bilan de l'opération.                                        *
+*                                                                             *
+*  Remarques   : -                                                            *
+*                                                                             *
+******************************************************************************/
+
+bool populate_common_module_with_hex(void)
+{
+    bool result;                            /* Bilan à retourner           */
+    PyObject *module;                       /* Module à recompléter        */
+
+    static PyMethodDef py_hex_methods[] = {
+        HEX_ENCODE_HEX_METHOD,
+        HEX_DECODE_HEX_DIGIT_METHOD,
+        { NULL }
+    };
+
+    module = get_access_to_python_module("pychrysalide.common");
+
+    result = register_python_module_methods(module, py_hex_methods);
+
+    return result;
+
+}
diff --git a/plugins/pychrysalide/common/hex.h b/plugins/pychrysalide/common/hex.h
new file mode 100644
index 0000000..6dee397
--- /dev/null
+++ b/plugins/pychrysalide/common/hex.h
@@ -0,0 +1,39 @@
+
+/* Chrysalide - Outil d'analyse de fichiers binaires
+ * hex.h - prototypes pour l'équivalent Python du fichier "common/hex.h"
+ *
+ * Copyright (C) 2021 Cyrille Bagard
+ *
+ *  This file is part of Chrysalide.
+ *
+ *  Chrysalide is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  Chrysalide is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+
+#ifndef _PLUGINS_PYCHRYSALIDE_COMMON_HEX_H
+#define _PLUGINS_PYCHRYSALIDE_COMMON_HEX_H
+
+
+#include <Python.h>
+#include <stdbool.h>
+
+
+
+/* Définit une extension du module 'common' à compléter. */
+bool populate_common_module_with_hex(void);
+
+
+
+#endif  /* _PLUGINS_PYCHRYSALIDE_COMMON_HEX_H */
diff --git a/plugins/pychrysalide/common/module.c b/plugins/pychrysalide/common/module.c
index cc50c43..6ced1b7 100644
--- a/plugins/pychrysalide/common/module.c
+++ b/plugins/pychrysalide/common/module.c
@@ -27,6 +27,7 @@
 
 #include "bits.h"
 #include "fnv1a.h"
+#include "hex.h"
 #include "leb128.h"
 #include "packed.h"
 #include "pathname.h"
@@ -97,6 +98,7 @@ bool populate_common_module(void)
     result = true;
 
     if (result) result = populate_common_module_with_fnv1a();
+    if (result) result = populate_common_module_with_hex();
     if (result) result = populate_common_module_with_leb128();
     if (result) result = populate_common_module_with_pathname();
     if (result) result = populate_common_module_with_pearson();
diff --git a/src/common/Makefile.am b/src/common/Makefile.am
index 2162d8c..3379eed 100644
--- a/src/common/Makefile.am
+++ b/src/common/Makefile.am
@@ -13,6 +13,7 @@ libcommon_la_SOURCES =					\
 	endianness.h endianness.c			\
 	environment.h environment.c			\
 	extstr.h extstr.c					\
+	hex.h hex.c							\
 	ibuf.h ibuf.c						\
 	io.h io.c							\
 	fnv1a.h fnv1a.c						\
diff --git a/src/common/hex.c b/src/common/hex.c
new file mode 100644
index 0000000..afa23a8
--- /dev/null
+++ b/src/common/hex.c
@@ -0,0 +1,139 @@
+
+/* Chrysalide - Outil d'analyse de fichiers binaires
+ * hex.c - construction et interprétation de chaînes hexadécimales
+ *
+ * Copyright (C) 2021 Cyrille Bagard
+ *
+ *  This file is part of Chrysalide.
+ *
+ *  Chrysalide is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  Chrysalide is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with Chrysalide.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+
+#include "hex.h"
+
+
+#include <ctype.h>
+
+
+
+/******************************************************************************
+*                                                                             *
+*  Paramètres  : src   = source de données à considérer.                      *
+*                len   = quantité de ces données à traiter.                   *
+*                lower = spécifie un encodage à l'aide de minuscules.         *
+*                dst   = zone allouée pour les données résultantes. [OUT]     *
+*                                                                             *
+*  Description : Encode des données en chaîne hexadécimale.                   *
+*                                                                             *
+*  Retour      : -                                                            *
+*                                                                             *
+*  Remarques   : -                                                            *
+*                                                                             *
+******************************************************************************/
+
+void encode_hex(const char *src, size_t len, bool lower, char *dst)
+{
+    size_t i;                               /* Boucle de parcours #1       */
+    uint8_t index;                          /* Indice de recherche         */
+
+    /**
+     * base = "".join([ "%02X" % i for i in range(256) ])
+     * encoded = "\n".join([ base[i : i + 32] for i in range(0, 512, 32) ])
+     * print(encoded)
+     */
+    const char encoding_lookup[] = {
+        "000102030405060708090A0B0C0D0E0F"
+        "101112131415161718191A1B1C1D1E1F"
+        "202122232425262728292A2B2C2D2E2F"
+        "303132333435363738393A3B3C3D3E3F"
+        "404142434445464748494A4B4C4D4E4F"
+        "505152535455565758595A5B5C5D5E5F"
+        "606162636465666768696A6B6C6D6E6F"
+        "707172737475767778797A7B7C7D7E7F"
+        "808182838485868788898A8B8C8D8E8F"
+        "909192939495969798999A9B9C9D9E9F"
+        "A0A1A2A3A4A5A6A7A8A9AAABACADAEAF"
+        "B0B1B2B3B4B5B6B7B8B9BABBBCBDBEBF"
+        "C0C1C2C3C4C5C6C7C8C9CACBCCCDCECF"
+        "D0D1D2D3D4D5D6D7D8D9DADBDCDDDEDF"
+        "E0E1E2E3E4E5E6E7E8E9EAEBECEDEEEF"
+        "F0F1F2F3F4F5F6F7F8F9FAFBFCFDFEFF"
+    };
+
+    for (i = 0; i < len; i++)
+    {
+        index = src[i];
+
+        if (lower)
+        {
+            (*dst++) = tolower(encoding_lookup[index * 2]);
+            (*dst++) = tolower(encoding_lookup[index * 2 + 1]);
+        }
+        else
+        {
+            (*dst++) = encoding_lookup[index * 2];
+            (*dst++) = encoding_lookup[index * 2 + 1];
+        }
+
+    }
+
+    *dst = '\0';
+
+}
+
+
+/******************************************************************************
+*                                                                             *
+*  Paramètres  : digit = caractère hexadécimale à transcrire.                 *
+*                value = valeur récupérée. [OUT]                              *
+*                                                                             *
+*  Description : Décode un caractère hexadécimal.                             *
+*                                                                             *
+*  Retour      : Bilan de l'opération.                                        *
+*                                                                             *
+*  Remarques   : -                                                            *
+*                                                                             *
+******************************************************************************/
+
+bool decode_hex_digit(const char *digit, uint8_t *value)
+{
+    bool result;                            /* Validité à retourner        */
+
+    switch (*digit)
+    {
+        case '0' ... '9':
+            *value = (*digit) - '0';
+            result = true;
+            break;
+
+        case 'a' ... 'f':
+            *value = 0xa + (*digit) - 'a';
+            result = true;
+            break;
+
+        case 'A' ... 'F':
+            *value = 0xa + (*digit) - 'A';
+            result = true;
+            break;
+
+        default:
+            result = false;
+            break;
+
+    }
+
+    return result;
+
+}
diff --git a/src/common/hex.h b/src/common/hex.h
new file mode 100644
index 0000000..97cd0da
--- /dev/null
+++ b/src/common/hex.h
@@ -0,0 +1,42 @@
+
+/* Chrysalide - Outil d'analyse de fichiers binaires
+ * hex.h - prototypes pour la construction et l'interprétation de chaînes hexadécimales
+ *
+ * Copyright (C) 2021 Cyrille Bagard
+ *
+ *  This file is part of Chrysalide.
+ *
+ *  Chrysalide is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  Chrysalide is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with Chrysalide.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+
+#ifndef _COMMON_HEX_H
+#define _COMMON_HEX_H
+
+
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdint.h>
+
+
+
+/* Encode des données en chaîne hexadécimale. */
+void encode_hex(const char *, size_t, bool, char *);
+
+/* Décode un caractère hexadécimal. */
+bool decode_hex_digit(const char *, uint8_t *);
+
+
+
+#endif  /* _COMMON_HEX_H */
diff --git a/tests/common/hex.py b/tests/common/hex.py
new file mode 100644
index 0000000..f76b6a8
--- /dev/null
+++ b/tests/common/hex.py
@@ -0,0 +1,28 @@
+
+from chrysacase import ChrysalideTestCase
+from pychrysalide.common import encode_hex, decode_hex_digit
+
+
+class TestHexValues(ChrysalideTestCase):
+    """TestCase for common hexadecimal features*"""
+
+    def testHexEncoding(self):
+        """Convert data to hex string."""
+
+        ref = b'ABC'
+
+        self.assertEqual(encode_hex(ref), ref.hex())
+
+        ref = 'ABC'
+
+        self.assertEqual(encode_hex(ref), bytes(ref, 'ascii').hex())
+
+        ref = 'ABC'
+
+        self.assertEqual(encode_hex(ref, False), bytes(ref, 'ascii').hex().upper())
+
+
+    def testHexDecoding(self):
+        """Convert a hex string to value."""
+
+        self.assertEqual(decode_hex_digit('A'), 0xa)
-- 
cgit v0.11.2-87-g4458