From 3c493d4cd2c9e91a2cee08c80e3629ea75788605 Mon Sep 17 00:00:00 2001
From: Cyrille Bagard <nocbos@gmail.com>
Date: Sat, 29 May 2021 12:13:16 +0200
Subject: Define roles for allowed connections to servers.

---
 plugins/pychrysalide/analysis/binary.c       |    5 +-
 plugins/pychrysalide/analysis/db/Makefile.am |    2 +
 plugins/pychrysalide/analysis/db/admin.c     |  227 +++++
 plugins/pychrysalide/analysis/db/admin.h     |   45 +
 plugins/pychrysalide/analysis/db/analyst.c   |  895 ++++++++++++++++++++
 plugins/pychrysalide/analysis/db/analyst.h   |   45 +
 plugins/pychrysalide/analysis/db/client.c    |  713 +---------------
 plugins/pychrysalide/analysis/db/module.c    |    4 +
 plugins/pychrysalide/analysis/db/server.c    |    3 +-
 src/analysis/binary.c                        |   36 +-
 src/analysis/binary.h                        |    4 +-
 src/analysis/db/Makefile.am                  |    6 +
 src/analysis/db/admin.c                      |  292 +++++++
 src/analysis/db/admin.h                      |   62 ++
 src/analysis/db/analyst.c                    | 1136 +++++++++++++++++++++++++
 src/analysis/db/analyst.h                    |   95 +++
 src/analysis/db/backend-int.h                |   68 ++
 src/analysis/db/backend.c                    |  267 ++++++
 src/analysis/db/backend.h                    |   56 ++
 src/analysis/db/cdb.c                        |  349 ++++----
 src/analysis/db/cdb.h                        |    9 -
 src/analysis/db/client-int.h                 |   71 ++
 src/analysis/db/client.c                     | 1147 ++------------------------
 src/analysis/db/client.h                     |   39 -
 src/analysis/db/controller.c                 |  449 ++++++++++
 src/analysis/db/controller.h                 |   59 ++
 src/analysis/db/protocol.h                   |   42 +-
 src/analysis/db/server.c                     |  424 ++++++++--
 src/gui/dialogs/snapshots.c                  |   48 +-
 src/gui/panels/history.c                     |    8 +-
 tests/analysis/db/conn.py                    |  125 +++
 31 files changed, 4595 insertions(+), 2136 deletions(-)
 create mode 100644 plugins/pychrysalide/analysis/db/admin.c
 create mode 100644 plugins/pychrysalide/analysis/db/admin.h
 create mode 100644 plugins/pychrysalide/analysis/db/analyst.c
 create mode 100644 plugins/pychrysalide/analysis/db/analyst.h
 create mode 100644 src/analysis/db/admin.c
 create mode 100644 src/analysis/db/admin.h
 create mode 100644 src/analysis/db/analyst.c
 create mode 100644 src/analysis/db/analyst.h
 create mode 100644 src/analysis/db/backend-int.h
 create mode 100644 src/analysis/db/backend.c
 create mode 100644 src/analysis/db/backend.h
 create mode 100644 src/analysis/db/client-int.h
 create mode 100644 src/analysis/db/controller.c
 create mode 100644 src/analysis/db/controller.h
 create mode 100644 tests/analysis/db/conn.py

diff --git a/plugins/pychrysalide/analysis/binary.c b/plugins/pychrysalide/analysis/binary.c
index 2f8af5f..68f2d88 100644
--- a/plugins/pychrysalide/analysis/binary.c
+++ b/plugins/pychrysalide/analysis/binary.c
@@ -127,7 +127,7 @@ static PyObject *py_loaded_binary_get_client(PyObject *self, PyObject *args)
     int internal;                           /* Nature du client visé       */
     int ret;                                /* Bilan de lecture des args.  */
     GLoadedBinary *binary;                  /* Binaire en cours d'analyse  */
-    GHubClient *client;                     /* Eventuel client en place    */
+    GAnalystClient *client;                 /* Eventuel client en place    */
 
 #define LOADED_BINARY_GET_CLIENT_METHOD PYTHON_METHOD_DEF               \
 (                                                                       \
@@ -135,6 +135,9 @@ static PyObject *py_loaded_binary_get_client(PyObject *self, PyObject *args)
     METH_VARARGS, py_loaded_binary,                                     \
     "Provide the client connected to an internal or remote server"      \
     " if defined, or return None otherwise.\n"                          \
+    "\n"                                                                \
+    "The returned object is a pychrysalide.analysis.db.AnalystClient"   \
+    " instance or *None*."                                              \
 )
 
     internal = 1;
diff --git a/plugins/pychrysalide/analysis/db/Makefile.am b/plugins/pychrysalide/analysis/db/Makefile.am
index fdf491e..721f5b6 100644
--- a/plugins/pychrysalide/analysis/db/Makefile.am
+++ b/plugins/pychrysalide/analysis/db/Makefile.am
@@ -2,6 +2,8 @@
 noinst_LTLIBRARIES = libpychrysaanalysisdb.la
 
 libpychrysaanalysisdb_la_SOURCES =		\
+	admin.h admin.c						\
+	analyst.h analyst.c					\
 	certs.h certs.c						\
 	client.h client.c					\
 	collection.h collection.c			\
diff --git a/plugins/pychrysalide/analysis/db/admin.c b/plugins/pychrysalide/analysis/db/admin.c
new file mode 100644
index 0000000..52027ac
--- /dev/null
+++ b/plugins/pychrysalide/analysis/db/admin.c
@@ -0,0 +1,227 @@
+
+/* Chrysalide - Outil d'analyse de fichiers binaires
+ * admin.c - équivalent Python du fichier "analysis/db/admin.c"
+ *
+ * Copyright (C) 2019 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 "admin.h"
+
+
+#include <assert.h>
+#include <pygobject.h>
+
+
+#include <i18n.h>
+#include <analysis/db/admin.h>
+
+
+#include "client.h"
+#include "../../access.h"
+#include "../../helpers.h"
+
+
+
+/* Crée un nouvel objet Python de type 'AdminClient'. */
+static PyObject *py_admin_client_new(PyTypeObject *, PyObject *, PyObject *);
+
+
+
+/******************************************************************************
+*                                                                             *
+*  Paramètres  : type = type de l'objet à instancier.                         *
+*                args = arguments fournis à l'appel.                          *
+*                kwds = arguments de type key=val fournis.                    *
+*                                                                             *
+*  Description : Crée un nouvel objet Python de type 'AdminClient'.           *
+*                                                                             *
+*  Retour      : Instance Python mise en place.                               *
+*                                                                             *
+*  Remarques   : -                                                            *
+*                                                                             *
+******************************************************************************/
+
+static PyObject *py_admin_client_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
+{
+    PyObject *result;                       /* Instance à retourner        */
+    GAdminClient *client;                   /* Serveur mis en place        */
+
+#define ADMIN_CLIENT_DOC                                                              \
+    "AdminClient provides and receives binary updates to and from a connected"        \
+    " to a server.\n"                                                                   \
+    "\n"                                                                                \
+    "Such clients must be authenticated and communications are encrypted using TLS.\n"  \
+    "\n"                                                                                \
+    "Instances can be created using the following constructor:\n"                       \
+    "\n"                                                                                \
+    "    AdminClient()"                                                                 \
+    "\n"                                                                                \
+    "AdminClient instances emit the following signals:\n"                               \
+    "* 'snapshots-updated'\n"                                                           \
+    "    This signal is emitted when the snapshot list has evolved.\n"                  \
+    "\n"                                                                                \
+    "    Handlers are expected to have only one argument: the client managing the"      \
+    "    updated snapshots.\n"                                                          \
+    "* 'snapshot-changed'\n"                                                            \
+    "    This signal is emitted when the identifier of the current snapshot changed.\n" \
+    "\n"                                                                                \
+    "    Handlers are expected to have only one argument: the client managing the"      \
+    "    snapshots."
+
+    client = g_admin_client_new();
+
+    if (client != NULL)
+    {
+        result = pygobject_new(G_OBJECT(client));
+        g_object_unref(client);
+    }
+    else result = NULL;
+
+    return result;
+
+}
+
+
+/******************************************************************************
+*                                                                             *
+*  Paramètres  : -                                                            *
+*                                                                             *
+*  Description : Fournit un accès à une définition de type à diffuser.        *
+*                                                                             *
+*  Retour      : Définition d'objet pour Python.                              *
+*                                                                             *
+*  Remarques   : -                                                            *
+*                                                                             *
+******************************************************************************/
+
+PyTypeObject *get_python_admin_client_type(void)
+{
+    static PyMethodDef py_admin_client_methods[] = {
+        { NULL }
+    };
+
+    static PyGetSetDef py_admin_client_getseters[] = {
+        { NULL }
+    };
+
+    static PyTypeObject py_admin_client_type = {
+
+        PyVarObject_HEAD_INIT(NULL, 0)
+
+        .tp_name        = "pychrysalide.analysis.db.AdminClient",
+        .tp_basicsize   = sizeof(PyGObject),
+
+        .tp_flags       = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE,
+
+        .tp_doc         = ADMIN_CLIENT_DOC,
+
+        .tp_methods     = py_admin_client_methods,
+        .tp_getset      = py_admin_client_getseters,
+        .tp_new         = py_admin_client_new,
+
+    };
+
+    return &py_admin_client_type;
+
+}
+
+
+/******************************************************************************
+*                                                                             *
+*  Paramètres  : module = module dont la définition est à compléter.          *
+*                                                                             *
+*  Description : Prend en charge l'objet 'pychrysalide....db.AdminClient'.  *
+*                                                                             *
+*  Retour      : Bilan de l'opération.                                        *
+*                                                                             *
+*  Remarques   : -                                                            *
+*                                                                             *
+******************************************************************************/
+
+bool ensure_python_admin_client_is_registered(void)
+{
+    PyTypeObject *type;                     /* Type Python 'AdminClient'   */
+    PyObject *module;                       /* Module à recompléter        */
+    PyObject *dict;                         /* Dictionnaire du module      */
+
+    type = get_python_admin_client_type();
+
+    if (!PyType_HasFeature(type, Py_TPFLAGS_READY))
+    {
+        if (!ensure_python_hub_client_is_registered())
+            return false;
+
+        module = get_access_to_python_module("pychrysalide.analysis.db");
+
+        dict = PyModule_GetDict(module);
+
+        if (!register_class_for_pygobject(dict, G_TYPE_ADMIN_CLIENT, type, get_python_hub_client_type()))
+            return false;
+
+    }
+
+    return true;
+
+}
+
+
+/******************************************************************************
+*                                                                             *
+*  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 client administrateur.                 *
+*                                                                             *
+*  Retour      : Bilan de l'opération, voire indications supplémentaires.     *
+*                                                                             *
+*  Remarques   : -                                                            *
+*                                                                             *
+******************************************************************************/
+
+int convert_to_admin_client(PyObject *arg, void *dst)
+{
+    int result;                             /* Bilan à retourner           */
+
+    result = PyObject_IsInstance(arg, (PyObject *)get_python_admin_client_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 admin client");
+            break;
+
+        case 1:
+            *((GAdminClient **)dst) = G_ADMIN_CLIENT(pygobject_get(arg));
+            break;
+
+        default:
+            assert(false);
+            break;
+
+    }
+
+    return result;
+
+}
diff --git a/plugins/pychrysalide/analysis/db/admin.h b/plugins/pychrysalide/analysis/db/admin.h
new file mode 100644
index 0000000..8a2dfeb
--- /dev/null
+++ b/plugins/pychrysalide/analysis/db/admin.h
@@ -0,0 +1,45 @@
+
+/* Chrysalide - Outil d'analyse de fichiers binaires
+ * admin.h - prototypes pour l'équivalent Python du fichier "analysis/db/admin.h"
+ *
+ * Copyright (C) 2019 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_ANALYSIS_DB_ADMIN_H
+#define _PLUGINS_PYCHRYSALIDE_ANALYSIS_DB_ADMIN_H
+
+
+#include <Python.h>
+#include <stdbool.h>
+
+
+
+/* Fournit un accès à une définition de type à diffuser. */
+PyTypeObject *get_python_admin_client_type(void);
+
+/* Prend en charge l'objet 'pychrysalide.analysis.db.AdminClient'. */
+bool ensure_python_admin_client_is_registered(void);
+
+/* Tente de convertir en client administrateur. */
+int convert_to_admin_client(PyObject *, void *);
+
+
+
+#endif  /* _PLUGINS_PYCHRYSALIDE_ANALYSIS_DB_ADMIN_H */
diff --git a/plugins/pychrysalide/analysis/db/analyst.c b/plugins/pychrysalide/analysis/db/analyst.c
new file mode 100644
index 0000000..83e3878
--- /dev/null
+++ b/plugins/pychrysalide/analysis/db/analyst.c
@@ -0,0 +1,895 @@
+
+/* Chrysalide - Outil d'analyse de fichiers binaires
+ * analyst.c - équivalent Python du fichier "analysis/db/analyst.c"
+ *
+ * Copyright (C) 2019 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 "analyst.h"
+
+
+#include <assert.h>
+#include <pygobject.h>
+
+
+#include <i18n.h>
+#include <analysis/db/analyst.h>
+#include <core/collections.h>
+
+
+#include "client.h"
+#include "collection.h"
+#include "../../access.h"
+#include "../../helpers.h"
+#include "../../struct.h"
+
+
+
+/* Crée un nouvel objet Python de type 'AnalystClient'. */
+static PyObject *py_analyst_client_new(PyTypeObject *, PyObject *, PyObject *);
+
+/* Effectue une demande de sauvegarde de l'état courant. */
+static PyObject *py_analyst_client_save(PyObject *, PyObject *);
+
+/* Active les éléments en amont d'un horodatage donné. */
+static PyObject *py_analyst_client_set_last_active(PyObject *, PyObject *);
+
+/* Définit la désignation d'un instantané donné. */
+static PyObject *py_analyst_client_set_snapshot_name(PyObject *, PyObject *);
+
+/* Définit la désignation d'un instantané donné. */
+static PyObject *py_analyst_client_set_snapshot_desc(PyObject *, PyObject *);
+
+/* Restaure un ancien instantané. */
+static PyObject *py_analyst_client_restore_snapshot(PyObject *, PyObject *);
+
+/* Crée un nouvel instantané à partir d'un autre. */
+static PyObject *py_analyst_client_create_snapshot(PyObject *, PyObject *);
+
+/* Supprime un ancien instantané. */
+static PyObject *py_analyst_client_remove_snapshot(PyObject *, PyObject *);
+
+/* Fournit la liste des instantanés existants. */
+static PyObject *py_analyst_client_get_snapshots(PyObject *, void *);
+
+/* Fournit l'identifiant de l'instantané courant. */
+static PyObject *py_analyst_client_get_current_snapshot(PyObject *, void *);
+
+
+
+/******************************************************************************
+*                                                                             *
+*  Paramètres  : type = type de l'objet à instancier.                         *
+*                args = arguments fournis à l'appel.                          *
+*                kwds = arguments de type key=val fournis.                    *
+*                                                                             *
+*  Description : Crée un nouvel objet Python de type 'AnalystClient'.         *
+*                                                                             *
+*  Retour      : Instance Python mise en place.                               *
+*                                                                             *
+*  Remarques   : -                                                            *
+*                                                                             *
+******************************************************************************/
+
+static PyObject *py_analyst_client_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
+{
+    PyObject *result;                       /* Instance à retourner        */
+    const char *hash;                       /* Empreinte du binaire visé   */
+    PyObject *list;                         /* Liste Python de collections */
+    int ret;                                /* Bilan de lecture des args.  */
+    Py_ssize_t length;                      /* Nombre d'éléments collectés */
+    GList *collections;                     /* Liste native de collections */
+    Py_ssize_t i;                           /* Boucle de parcours          */
+    PyObject *item;                         /* Elément de la liste Python  */
+    GDbCollection *collec;                  /* Version équivalente native  */
+    GAnalystClient *client;                 /* Serveur mis en place        */
+
+#define ANALYST_CLIENT_DOC                                                              \
+    "AnalystClient provides and receives binary updates to and from a connected"        \
+    " to a server.\n"                                                                   \
+    "\n"                                                                                \
+    "Such clients must be authenticated and communications are encrypted using TLS.\n"  \
+    "\n"                                                                                \
+    "Instances can be created using the following constructor:\n"                       \
+    "\n"                                                                                \
+    "    AnalystClient(hash, list)"                                                     \
+    "\n"                                                                                \
+    "Where hash is a SHA256 fingerprint of the studied binary and list is a list of"    \
+    " pychrysalide.analysis.db.DbCollection instances ; this kind of list can be"       \
+    " retrived with the pychrysalide.analysis.LoadedBinary.collections attribute."      \
+    "\n"                                                                                \
+    "AnalystClient instances emit the following signals:\n"                             \
+    "* 'snapshots-updated'\n"                                                           \
+    "    This signal is emitted when the snapshot list has evolved.\n"                  \
+    "\n"                                                                                \
+    "    Handlers are expected to have only one argument: the client managing the"      \
+    "    updated snapshots.\n"                                                          \
+    "* 'snapshot-changed'\n"                                                            \
+    "    This signal is emitted when the identifier of the current snapshot changed.\n" \
+    "\n"                                                                                \
+    "    Handlers are expected to have only one argument: the client managing the"      \
+    "    snapshots."
+
+    ret = PyArg_ParseTuple(args, "sO", &hash, &list);
+    if (!ret) return NULL;
+
+    if (!PySequence_Check(list))
+    {
+        PyErr_SetString(PyExc_TypeError, _("The second argument must be a collection list"));
+        return NULL;
+    }
+
+    length = PySequence_Length(list);
+
+    collections = NULL;
+
+    for (i = 0; i < length; i++)
+    {
+        item = PySequence_GetItem(list, i);
+
+        ret = convert_to_db_collection(item, &collec);
+
+        Py_DECREF(item);
+
+        if (ret != 1)
+        {
+            delete_collections_list(&collections);
+            result = NULL;
+            goto exit;
+        }
+
+        g_object_ref(G_OBJECT(collec));
+        collections = g_list_append(collections, collec);
+
+    }
+
+    client = g_analyst_client_new(hash, collections);
+
+    if (client != NULL)
+    {
+        result = pygobject_new(G_OBJECT(client));
+        g_object_unref(client);
+    }
+    else result = NULL;
+
+ exit:
+
+    return result;
+
+}
+
+
+/******************************************************************************
+*                                                                             *
+*  Paramètres  : self = client à manipuler.                                   *
+*                args = arguments d'appel non utilisés ici.                   *
+*                                                                             *
+*  Description : Effectue une demande de sauvegarde de l'état courant.        *
+*                                                                             *
+*  Retour      : True si la commande a bien été envoyée, False sinon.         *
+*                                                                             *
+*  Remarques   : -                                                            *
+*                                                                             *
+******************************************************************************/
+
+static PyObject *py_analyst_client_save(PyObject *self, PyObject *args)
+{
+    PyObject *result;                       /* Bilan à retourner           */
+    GAnalystClient *client;                 /* Version native du serveur   */
+    bool status;                            /* Bilan de l'opération        */
+
+#define ANALYST_CLIENT_SAVE_METHOD PYTHON_METHOD_DEF                        \
+(                                                                           \
+    save, "$self, /",                                                       \
+    METH_NOARGS, py_analyst_client,                                         \
+    "Ask the server for saving the current state of the analyzed binary"    \
+    " and returns the status of the request transmission."                  \
+)
+
+    client = G_ANALYST_CLIENT(pygobject_get(self));
+
+    status = g_analyst_client_save(client);
+
+    result = status ? Py_True : Py_False;
+    Py_INCREF(result);
+
+    return result;
+
+}
+
+
+/******************************************************************************
+*                                                                             *
+*  Paramètres  : self = client à manipuler.                                   *
+*                args = arguments d'appel à consulter.                        *
+*                                                                             *
+*  Description : Active les éléments en amont d'un horodatage donné.          *
+*                                                                             *
+*  Retour      : True si la commande a bien été envoyée, False sinon.         *
+*                                                                             *
+*  Remarques   : -                                                            *
+*                                                                             *
+******************************************************************************/
+
+static PyObject *py_analyst_client_set_last_active(PyObject *self, PyObject *args)
+{
+    PyObject *result;                       /* Bilan à retourner           */
+    unsigned long long timestamp;           /* Horodatage de limite        */
+    int ret;                                /* Bilan de lecture des args.  */
+    GAnalystClient *client;                 /* Version native du serveur   */
+    bool status;                            /* Bilan de l'opération        */
+
+#define ANALYST_CLIENT_SET_LAST_ACTIVE_METHOD PYTHON_METHOD_DEF         \
+(                                                                       \
+    set_last_active, "$self, timestamp, /",                             \
+    METH_VARARGS, py_analyst_client,                                    \
+    "Define the timestamp of the last active item in the collection"    \
+    " and returns the status of the request transmission."              \
+    "\n"                                                                \
+    "This method should not be used directly. Prefer calling"           \
+    " pychrysalide.analysis.LoadedBinary.set_last_active() instead,"    \
+    " as some items may be volatile and thus not handled by clients."   \
+)
+
+    ret = PyArg_ParseTuple(args, "K", &timestamp);
+    if (!ret) return NULL;
+
+    client = G_ANALYST_CLIENT(pygobject_get(self));
+
+    status = g_analyst_client_set_last_active(client, timestamp);
+
+    result = status ? Py_True : Py_False;
+    Py_INCREF(result);
+
+    return result;
+
+}
+
+
+/******************************************************************************
+*                                                                             *
+*  Paramètres  : self = client à manipuler.                                   *
+*                args = arguments d'appel à consulter.                        *
+*                                                                             *
+*  Description : Définit la désignation d'un instantané donné.                *
+*                                                                             *
+*  Retour      : True si la commande a bien été envoyée, False sinon.         *
+*                                                                             *
+*  Remarques   : -                                                            *
+*                                                                             *
+******************************************************************************/
+
+static PyObject *py_analyst_client_set_snapshot_name(PyObject *self, PyObject *args)
+{
+    PyObject *result;                       /* Bilan à retourner           */
+    const char *raw_id;                     /* Identifiant brut            */
+    const char *text;                       /* Texte fourni à transmettre  */
+    int ret;                                /* Bilan de lecture des args.  */
+    snapshot_id_t id;                       /* Identifiant utilisable      */
+    bool status;                            /* Bilan d'opération           */
+    GAnalystClient *client;                 /* Version native du serveur   */
+
+#define ANALYST_CLIENT_SET_SNAPSHOT_NAME_METHOD PYTHON_METHOD_DEF           \
+(                                                                           \
+    set_snapshot_name, "$self, id, name, /",                                \
+    METH_VARARGS, py_analyst_client,                                        \
+    "Ask the server for defining a new name of for a snapshot using its"    \
+    " identifier and returns the status of the request transmission."       \
+    "\n"                                                                    \
+    "A 'snapshots-updated' signal is emitted once the request has been"     \
+    " processed with success."                                              \
+)
+
+    ret = PyArg_ParseTuple(args, "ss", &raw_id, &text);
+    if (!ret) return NULL;
+
+    status = init_snapshot_id_from_text(&id, raw_id);
+    if (!status)
+    {
+        PyErr_SetString(PyExc_TypeError, _("provided value is not a valid snapshot identifier."));
+        return NULL;
+    }
+
+    client = G_ANALYST_CLIENT(pygobject_get(self));
+
+    status = g_analyst_client_set_snapshot_name(client, &id, text);
+
+    result = status ? Py_True : Py_False;
+    Py_INCREF(result);
+
+    return result;
+
+}
+
+
+/******************************************************************************
+*                                                                             *
+*  Paramètres  : self = client à manipuler.                                   *
+*                args = arguments d'appel à consulter.                        *
+*                                                                             *
+*  Description : Définit la désignation d'un instantané donné.                *
+*                                                                             *
+*  Retour      : True si la commande a bien été envoyée, False sinon.         *
+*                                                                             *
+*  Remarques   : -                                                            *
+*                                                                             *
+******************************************************************************/
+
+static PyObject *py_analyst_client_set_snapshot_desc(PyObject *self, PyObject *args)
+{
+    PyObject *result;                       /* Bilan à retourner           */
+    const char *raw_id;                     /* Identifiant brut            */
+    const char *text;                       /* Texte fourni à transmettre  */
+    int ret;                                /* Bilan de lecture des args.  */
+    snapshot_id_t id;                       /* Identifiant utilisable      */
+    bool status;                            /* Bilan d'opération           */
+    GAnalystClient *client;                 /* Version native du serveur   */
+
+#define ANALYST_CLIENT_SET_SNAPSHOT_DESC_METHOD PYTHON_METHOD_DEF           \
+(                                                                           \
+    set_snapshot_desc, "$self, id, desc, /",                                \
+    METH_VARARGS, py_analyst_client,                                        \
+    "Ask the server for defining a new description for a snapshot using"    \
+    " its identifier and returns the status of the request transmission."   \
+    "\n"                                                                    \
+    "A 'snapshots-updated' signal is emitted once the request has been"     \
+    " processed with success."                                              \
+)
+
+    ret = PyArg_ParseTuple(args, "ss", &raw_id, &text);
+    if (!ret) return NULL;
+
+    status = init_snapshot_id_from_text(&id, raw_id);
+    if (!status)
+    {
+        PyErr_SetString(PyExc_TypeError, _("provided value is not a valid snapshot identifier."));
+        return NULL;
+    }
+
+    client = G_ANALYST_CLIENT(pygobject_get(self));
+
+    status = g_analyst_client_set_snapshot_desc(client, &id, text);
+
+    result = status ? Py_True : Py_False;
+    Py_INCREF(result);
+
+    return result;
+
+}
+
+
+/******************************************************************************
+*                                                                             *
+*  Paramètres  : self = client à manipuler.                                   *
+*                args = arguments d'appel à consulter.                        *
+*                                                                             *
+*  Description : Restaure un ancien instantané.                               *
+*                                                                             *
+*  Retour      : True si la commande a bien été envoyée, False sinon.         *
+*                                                                             *
+*  Remarques   : -                                                            *
+*                                                                             *
+******************************************************************************/
+
+static PyObject *py_analyst_client_restore_snapshot(PyObject *self, PyObject *args)
+{
+    PyObject *result;                       /* Bilan à retourner           */
+    const char *raw_id;                     /* Identifiant brut            */
+    int ret;                                /* Bilan de lecture des args.  */
+    snapshot_id_t id;                       /* Identifiant utilisable      */
+    bool status;                            /* Bilan d'opération           */
+    GAnalystClient *client;                 /* Version native du serveur   */
+
+#define ANALYST_CLIENT_RESTORE_SNAPSHOT_METHOD PYTHON_METHOD_DEF            \
+(                                                                           \
+    restore_snapshot, "$self, id, /",                                       \
+    METH_VARARGS, py_analyst_client,                                        \
+    "Ask the server for restoring a given snapshot using"                   \
+    " its identifier and returns the status of the request transmission."   \
+    "\n"                                                                    \
+    "A 'snapshot-changed' signal is emitted once the request has been"      \
+    " processed with success."                                              \
+)
+
+    ret = PyArg_ParseTuple(args, "s", &raw_id);
+    if (!ret) return NULL;
+
+    status = init_snapshot_id_from_text(&id, raw_id);
+    if (!status)
+    {
+        PyErr_SetString(PyExc_TypeError, _("provided value is not a valid snapshot identifier."));
+        return NULL;
+    }
+
+    client = G_ANALYST_CLIENT(pygobject_get(self));
+
+    status = g_analyst_client_restore_snapshot(client, &id);
+
+    result = status ? Py_True : Py_False;
+    Py_INCREF(result);
+
+    return result;
+
+}
+
+
+/******************************************************************************
+*                                                                             *
+*  Paramètres  : self = client à manipuler.                                   *
+*                args = arguments d'appel à consulter.                        *
+*                                                                             *
+*  Description : Crée un nouvel instantané à partir d'un autre.               *
+*                                                                             *
+*  Retour      : True si la commande a bien été envoyée, False sinon.         *
+*                                                                             *
+*  Remarques   : -                                                            *
+*                                                                             *
+******************************************************************************/
+
+static PyObject *py_analyst_client_create_snapshot(PyObject *self, PyObject *args)
+{
+    PyObject *result;                       /* Bilan à retourner           */
+    GAnalystClient *client;                 /* Version native du serveur   */
+    bool status;                            /* Bilan d'opération           */
+
+#define ANALYST_CLIENT_CREATE_SNAPSHOT_METHOD PYTHON_METHOD_DEF             \
+(                                                                           \
+    create_snapshot, "$self, /",                                            \
+    METH_NOARGS, py_analyst_client,                                         \
+    "Ask the server for creating a new snapshot of the current state"       \
+    " and returns the status of the request transmission."                  \
+    "\n"                                                                    \
+    "A 'snapshots-updated' signal is emitted once the request has been"     \
+    " processed with success."                                              \
+)
+
+    client = G_ANALYST_CLIENT(pygobject_get(self));
+
+    status = g_analyst_client_create_snapshot(client);
+
+    result = status ? Py_True : Py_False;
+    Py_INCREF(result);
+
+    return result;
+
+}
+
+
+/******************************************************************************
+*                                                                             *
+*  Paramètres  : self = client à manipuler.                                   *
+*                args = arguments d'appel à consulter.                        *
+*                                                                             *
+*  Description : Supprime un ancien instantané.                               *
+*                                                                             *
+*  Retour      : True si la commande a bien été envoyée, False sinon.         *
+*                                                                             *
+*  Remarques   : -                                                            *
+*                                                                             *
+******************************************************************************/
+
+static PyObject *py_analyst_client_remove_snapshot(PyObject *self, PyObject *args)
+{
+    PyObject *result;                       /* Bilan à retourner           */
+    const char *raw_id;                     /* Identifiant brut            */
+    int rec;                                /* Indicateur de récursivité   */
+    int ret;                                /* Bilan de lecture des args.  */
+    snapshot_id_t id;                       /* Identifiant utilisable      */
+    bool status;                            /* Bilan d'opération           */
+    GAnalystClient *client;                 /* Version native du serveur   */
+
+#define ANALYST_CLIENT_REMOVE_SNAPSHOT_METHOD PYTHON_METHOD_DEF             \
+(                                                                           \
+    remove_snapshot, "$self, id, recursive, /",                             \
+    METH_VARARGS, py_analyst_client,                                        \
+    "Ask the server for removing a given snapshot using"                    \
+    " its identifier and returns the status of the request transmission."   \
+    "\n"                                                                    \
+    "If this removal has not to be recursive, all children snapshots get"   \
+    " reassigned to the parent snapshot of the target."                     \
+    "\n"                                                                    \
+    "A 'snapshots-updated' signal is emitted once the request has been"     \
+    " processed with success."                                              \
+)
+
+    ret = PyArg_ParseTuple(args, "sp", &raw_id, &rec);
+    if (!ret) return NULL;
+
+    status = init_snapshot_id_from_text(&id, raw_id);
+    if (!status)
+    {
+        PyErr_SetString(PyExc_TypeError, _("provided value is not a valid snapshot identifier."));
+        return NULL;
+    }
+
+    client = G_ANALYST_CLIENT(pygobject_get(self));
+
+    status = g_analyst_client_remove_snapshot(client, &id, rec);
+
+    result = status ? Py_True : Py_False;
+    Py_INCREF(result);
+
+    return result;
+
+}
+
+
+/******************************************************************************
+*                                                                             *
+*  Paramètres  : self    = objet Python concerné par l'appel.                 *
+*                closure = non utilisé ici.                                   *
+*                                                                             *
+*  Description : Fournit la liste des instantanés existants.                  *
+*                                                                             *
+*  Retour      : Liste d'instantanés ou None.                                 *
+*                                                                             *
+*  Remarques   : -                                                            *
+*                                                                             *
+******************************************************************************/
+
+static PyObject *py_analyst_client_get_snapshots(PyObject *self, void *closure)
+{
+    PyObject *result;                       /* Valeur à retourner          */
+    GAnalystClient *client;                 /* Version native du serveur   */
+    snapshot_info_t *info;                  /* Liste d'instantanés présents*/
+    size_t count;                           /* Taille de cette liste       */
+    bool status;                            /* Validité de cet identifiant */
+    PyTypeObject *base;                     /* Modèle d'objet à créer      */
+    size_t i;                               /* Boucle de parcours          */
+    PyObject *item;                         /* Nouvelle description        */
+    char *text;                             /* Valeur textuelle à placer   */
+    PyObject *attrib;                       /* Attribut à constituer       */
+    int ret;                                /* Bilan d'une mise en place   */
+    bool failed;                            /* Détection d'une erreur      */
+
+#define ANALYST_CLIENT_SNAPSHOTS_ATTRIB PYTHON_GET_DEF_FULL                             \
+(                                                                                       \
+    snapshots, py_analyst_client,                                                       \
+    "List of all existing snapshots, provided as a tuple of pychrysalide.StructObject." \
+    "\n"                                                                                \
+    "Each snapshot is characterised by the following properties :\n"                    \
+    "* parent_id : identifier of the parent snapshot;\n"                                \
+    "* id : identifier of the snapshot;\n"                                              \
+    "* created : timestamp of the creation date;\n"                                     \
+    "* name : name of the snapshot, or None;\n"                                         \
+    "* desc : description of the snapshot, or None."                                    \
+)
+
+    client = G_ANALYST_CLIENT(pygobject_get(self));
+
+    status = g_analyst_client_get_snapshots(client, &info, &count);
+
+    if (status)
+    {
+        result = PyTuple_New(count);
+
+        base = get_python_py_struct_type();
+
+        failed = false;
+
+        for (i = 0; i < count; i++)
+        {
+            item = PyObject_CallFunction((PyObject *)base, NULL);
+            assert(item != NULL);
+
+            text = snapshot_id_as_string(get_snapshot_info_parent_id(&info[i]));
+            attrib = PyUnicode_FromString(text);
+            ret = PyDict_SetItemString(item, "parent_id", attrib);
+            if (ret != 0) break;
+
+            text = snapshot_id_as_string(get_snapshot_info_id(&info[i]));
+            attrib = PyUnicode_FromString(text);
+            ret = PyDict_SetItemString(item, "id", attrib);
+            if (ret != 0) break;
+
+            attrib = PyLong_FromUnsignedLongLong(get_snapshot_info_created(&info[i]));
+            ret = PyDict_SetItemString(item, "created", attrib);
+            if (ret != 0) break;
+
+            text = get_snapshot_info_name(&info[i]);
+
+            if (text != NULL)
+                attrib = PyUnicode_FromString(text);
+            else
+            {
+                attrib = Py_None;
+                Py_INCREF(attrib);
+            }
+
+            ret = PyDict_SetItemString(item, "name", attrib);
+            if (ret != 0) break;
+
+            text = get_snapshot_info_desc(&info[i]);
+
+            if (text != NULL)
+                attrib = PyUnicode_FromString(text);
+            else
+            {
+                attrib = Py_None;
+                Py_INCREF(attrib);
+            }
+
+            ret = PyDict_SetItemString(item, "desc", attrib);
+            if (ret != 0) break;
+
+            PyTuple_SetItem(result, i, item);
+
+        }
+
+        failed = (i < count);
+
+        for (i = 0; i < count; i++)
+            exit_snapshot_info(&info[i]);
+
+        free(info);
+
+        if (failed)
+            goto on_failure;
+
+    }
+
+    else
+    {
+        result = Py_None;
+        Py_INCREF(result);
+    }
+
+    return result;
+
+ on_failure:
+
+    Py_DECREF(result);
+
+    return NULL;
+
+}
+
+
+/******************************************************************************
+*                                                                             *
+*  Paramètres  : self    = objet Python concerné par l'appel.                 *
+*                closure = non utilisé ici.                                   *
+*                                                                             *
+*  Description : Fournit l'identifiant de l'instantané courant.               *
+*                                                                             *
+*  Retour      : Identifiant d'instantané ou None.                            *
+*                                                                             *
+*  Remarques   : -                                                            *
+*                                                                             *
+******************************************************************************/
+
+static PyObject *py_analyst_client_get_current_snapshot(PyObject *self, void *closure)
+{
+    PyObject *result;                       /* Valeur à retourner          */
+    GAnalystClient *client;                 /* Version native du serveur   */
+    snapshot_id_t id;                       /* Identifiant à transmettre   */
+    bool status;                            /* Validité de cet identifiant */
+
+#define ANALYST_CLIENT_CURRENT_SNAPSHOT_ATTRIB PYTHON_GETSET_DEF_FULL   \
+(                                                                       \
+    current_snapshot, py_analyst_client,                                \
+    "Identifier of the current snapshot, provided as a string."         \
+    "\n"                                                                \
+    "The returned value is a cached version of the value stored at"     \
+    " server side. Thus, defining a new current snapshot is"            \
+    " successful as soon as the request to this server is sent."        \
+)
+
+    client = G_ANALYST_CLIENT(pygobject_get(self));
+
+    status = g_analyst_client_get_current_snapshot(client, &id);
+
+    if (status)
+        result = PyUnicode_FromString(snapshot_id_as_string(&id));
+
+    else
+    {
+        result = Py_None;
+        Py_INCREF(result);
+    }
+
+    return result;
+
+}
+
+
+/******************************************************************************
+*                                                                             *
+*  Paramètres  : self    = objet Python concerné par l'appel.                 *
+*                value   = valeur fournie à intégrer ou prendre en compte.    *
+*                closure = adresse non utilisée ici.                          *
+*                                                                             *
+*  Description : Définit l'identifiant de l'instantané courant.               *
+*                                                                             *
+*  Retour      : Bilan de l'opération.                                        *
+*                                                                             *
+*  Remarques   : -                                                            *
+*                                                                             *
+******************************************************************************/
+
+static int py_analyst_client_set_current_snapshot(PyObject *self, PyObject *value, void *closure)
+{
+    int ret;                                /* Bilan d'analyse             */
+    void *raw;                              /* Valeur brute d'identifiant  */
+    snapshot_id_t id;                       /* Identifiant reconnu         */
+    bool status;                            /* Bilan d'une conversion      */
+    GAnalystClient *client;                 /* Version native du serveur   */
+
+    ret = PyUnicode_Check(value);
+    if (!ret) return -1;
+
+    raw = PyUnicode_DATA(value);
+
+    status = init_snapshot_id_from_text(&id, raw);
+    if (!status)
+    {
+        PyErr_SetString(PyExc_TypeError, _("provided value is not a valid snapshot identifier."));
+        return -1;
+    }
+
+    client = G_ANALYST_CLIENT(pygobject_get(self));
+
+    status = g_analyst_client_set_current_snapshot(client, &id);
+    if (!status)
+    {
+        PyErr_SetString(PyExc_TypeError, "unable to send the provided snapshot identifier");
+        return -1;
+    }
+
+    return 0;
+
+}
+
+
+/******************************************************************************
+*                                                                             *
+*  Paramètres  : -                                                            *
+*                                                                             *
+*  Description : Fournit un accès à une définition de type à diffuser.        *
+*                                                                             *
+*  Retour      : Définition d'objet pour Python.                              *
+*                                                                             *
+*  Remarques   : -                                                            *
+*                                                                             *
+******************************************************************************/
+
+PyTypeObject *get_python_analyst_client_type(void)
+{
+    static PyMethodDef py_analyst_client_methods[] = {
+        ANALYST_CLIENT_SAVE_METHOD,
+        ANALYST_CLIENT_SET_LAST_ACTIVE_METHOD,
+        ANALYST_CLIENT_SET_SNAPSHOT_NAME_METHOD,
+        ANALYST_CLIENT_SET_SNAPSHOT_DESC_METHOD,
+        ANALYST_CLIENT_RESTORE_SNAPSHOT_METHOD,
+        ANALYST_CLIENT_CREATE_SNAPSHOT_METHOD,
+        ANALYST_CLIENT_REMOVE_SNAPSHOT_METHOD,
+        { NULL }
+    };
+
+    static PyGetSetDef py_analyst_client_getseters[] = {
+        ANALYST_CLIENT_SNAPSHOTS_ATTRIB,
+        ANALYST_CLIENT_CURRENT_SNAPSHOT_ATTRIB,
+        { NULL }
+    };
+
+    static PyTypeObject py_analyst_client_type = {
+
+        PyVarObject_HEAD_INIT(NULL, 0)
+
+        .tp_name        = "pychrysalide.analysis.db.AnalystClient",
+        .tp_basicsize   = sizeof(PyGObject),
+
+        .tp_flags       = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE,
+
+        .tp_doc         = ANALYST_CLIENT_DOC,
+
+        .tp_methods     = py_analyst_client_methods,
+        .tp_getset      = py_analyst_client_getseters,
+        .tp_new         = py_analyst_client_new,
+
+    };
+
+    return &py_analyst_client_type;
+
+}
+
+
+/******************************************************************************
+*                                                                             *
+*  Paramètres  : module = module dont la définition est à compléter.          *
+*                                                                             *
+*  Description : Prend en charge l'objet 'pychrysalide....db.AnalystClient'.  *
+*                                                                             *
+*  Retour      : Bilan de l'opération.                                        *
+*                                                                             *
+*  Remarques   : -                                                            *
+*                                                                             *
+******************************************************************************/
+
+bool ensure_python_analyst_client_is_registered(void)
+{
+    PyTypeObject *type;                     /* Type Python 'AnalystClient' */
+    PyObject *module;                       /* Module à recompléter        */
+    PyObject *dict;                         /* Dictionnaire du module      */
+
+    type = get_python_analyst_client_type();
+
+    if (!PyType_HasFeature(type, Py_TPFLAGS_READY))
+    {
+        if (!ensure_python_hub_client_is_registered())
+            return false;
+
+        module = get_access_to_python_module("pychrysalide.analysis.db");
+
+        dict = PyModule_GetDict(module);
+
+        if (!register_class_for_pygobject(dict, G_TYPE_ANALYST_CLIENT, type, get_python_hub_client_type()))
+            return false;
+
+    }
+
+    return true;
+
+}
+
+
+/******************************************************************************
+*                                                                             *
+*  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 client analyste.                       *
+*                                                                             *
+*  Retour      : Bilan de l'opération, voire indications supplémentaires.     *
+*                                                                             *
+*  Remarques   : -                                                            *
+*                                                                             *
+******************************************************************************/
+
+int convert_to_analyst_client(PyObject *arg, void *dst)
+{
+    int result;                             /* Bilan à retourner           */
+
+    result = PyObject_IsInstance(arg, (PyObject *)get_python_analyst_client_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 analyst client");
+            break;
+
+        case 1:
+            *((GAnalystClient **)dst) = G_ANALYST_CLIENT(pygobject_get(arg));
+            break;
+
+        default:
+            assert(false);
+            break;
+
+    }
+
+    return result;
+
+}
diff --git a/plugins/pychrysalide/analysis/db/analyst.h b/plugins/pychrysalide/analysis/db/analyst.h
new file mode 100644
index 0000000..b250933
--- /dev/null
+++ b/plugins/pychrysalide/analysis/db/analyst.h
@@ -0,0 +1,45 @@
+
+/* Chrysalide - Outil d'analyse de fichiers binaires
+ * analyst.h - prototypes pour l'équivalent Python du fichier "analysis/db/analyst.h"
+ *
+ * Copyright (C) 2019 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_ANALYSIS_DB_ANALYST_H
+#define _PLUGINS_PYCHRYSALIDE_ANALYSIS_DB_ANALYST_H
+
+
+#include <Python.h>
+#include <stdbool.h>
+
+
+
+/* Fournit un accès à une définition de type à diffuser. */
+PyTypeObject *get_python_analyst_client_type(void);
+
+/* Prend en charge l'objet 'pychrysalide.analysis.db.AnalystClient'. */
+bool ensure_python_analyst_client_is_registered(void);
+
+/* Tente de convertir en client analyste. */
+int convert_to_analyst_client(PyObject *, void *);
+
+
+
+#endif  /* _PLUGINS_PYCHRYSALIDE_ANALYSIS_DB_ANALYST_H */
diff --git a/plugins/pychrysalide/analysis/db/client.c b/plugins/pychrysalide/analysis/db/client.c
index d5d8b48..0cd9704 100644
--- a/plugins/pychrysalide/analysis/db/client.c
+++ b/plugins/pychrysalide/analysis/db/client.c
@@ -41,84 +41,13 @@
 
 
 
-/* Crée un nouvel objet Python de type 'HubClient'. */
-static PyObject *py_hub_client_new(PyTypeObject *, PyObject *, PyObject *);
-
-/* Démarre la connexion à la base de données. */
-static PyObject *py_hub_client_start(PyObject *, PyObject *);
-
-/* Arrête la connexion à la base de données. */
-static PyObject *py_hub_client_stop(PyObject *, PyObject *);
-
-/* Effectue une demande de sauvegarde de l'état courant. */
-static PyObject *py_hub_client_save(PyObject *, PyObject *);
-
-/* Active les éléments en amont d'un horodatage donné. */
-static PyObject *py_hub_client_set_last_active(PyObject *, PyObject *);
-
-/* Définit la désignation d'un instantané donné. */
-static PyObject *py_hub_client_set_snapshot_name(PyObject *, PyObject *);
-
-/* Définit la désignation d'un instantané donné. */
-static PyObject *py_hub_client_set_snapshot_desc(PyObject *, PyObject *);
-
-/* Restaure un ancien instantané. */
-static PyObject *py_hub_client_restore_snapshot(PyObject *, PyObject *);
-
-/* Crée un nouvel instantané à partir d'un autre. */
-static PyObject *py_hub_client_create_snapshot(PyObject *, PyObject *);
-
-/* Supprime un ancien instantané. */
-static PyObject *py_hub_client_remove_snapshot(PyObject *, PyObject *);
-
-/* Fournit la liste des instantanés existants. */
-static PyObject *py_hub_client_get_snapshots(PyObject *, void *);
-
-/* Fournit l'identifiant de l'instantané courant. */
-static PyObject *py_hub_client_get_current_snapshot(PyObject *, void *);
-
-
-
-/******************************************************************************
-*                                                                             *
-*  Paramètres  : type = type de l'objet à instancier.                         *
-*                args = arguments fournis à l'appel.                          *
-*                kwds = arguments de type key=val fournis.                    *
-*                                                                             *
-*  Description : Crée un nouvel objet Python de type 'HubClient'.             *
-*                                                                             *
-*  Retour      : Instance Python mise en place.                               *
-*                                                                             *
-*  Remarques   : -                                                            *
-*                                                                             *
-******************************************************************************/
-
-static PyObject *py_hub_client_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
-{
-    PyObject *result;                       /* Instance à retourner        */
-    const char *hash;                       /* Empreinte du binaire visé   */
-    PyObject *list;                         /* Liste Python de collections */
-    int ret;                                /* Bilan de lecture des args.  */
-    Py_ssize_t length;                      /* Nombre d'éléments collectés */
-    GList *collections;                     /* Liste native de collections */
-    Py_ssize_t i;                           /* Boucle de parcours          */
-    PyObject *item;                         /* Elément de la liste Python  */
-    GDbCollection *collec;                  /* Version équivalente native  */
-    GHubClient *client;                     /* Serveur mis en place        */
-
 #define HUB_CLIENT_DOC                                                                  \
     "HubClient provides and receives binary updates to and from a connected"            \
     " to a server.\n"                                                                   \
     "\n"                                                                                \
     "Such clients must be authenticated and communications are encrypted using TLS.\n"  \
     "\n"                                                                                \
-    "Instances can be created using the following constructor:\n"                       \
-    "\n"                                                                                \
-    "    HubClient(hash, list)"                                                         \
-    "\n"                                                                                \
-    "Where hash is a SHA256 fingerprint of the studied binary and list is a list of"    \
-    " pychrysalide.analysis.db.DbCollection instances ; this kind of list can be"       \
-    " retrived with the pychrysalide.analysis.LoadedBinary.collections attribute."      \
+    "Instances can be created directly."                                                \
     "\n"                                                                                \
     "HubClient instances emit the following signals:\n"                                 \
     "* 'snapshots-updated'\n"                                                           \
@@ -132,53 +61,14 @@ static PyObject *py_hub_client_new(PyTypeObject *type, PyObject *args, PyObject
     "    Handlers are expected to have only one argument: the client managing the"      \
     "    snapshots."
 
-    ret = PyArg_ParseTuple(args, "sO", &hash, &list);
-    if (!ret) return NULL;
-
-    if (!PySequence_Check(list))
-    {
-        PyErr_SetString(PyExc_TypeError, _("The second argument must be a collection list"));
-        return NULL;
-    }
-
-    length = PySequence_Length(list);
-
-    collections = NULL;
-
-    for (i = 0; i < length; i++)
-    {
-        item = PySequence_GetItem(list, i);
-
-        ret = convert_to_db_collection(item, &collec);
-
-        Py_DECREF(item);
 
-        if (ret != 1)
-        {
-            delete_collections_list(&collections);
-            result = NULL;
-            goto exit;
-        }
 
-        g_object_ref(G_OBJECT(collec));
-        collections = g_list_append(collections, collec);
-
-    }
-
-    client = g_hub_client_new(hash, collections);
-
-    if (client != NULL)
-    {
-        result = pygobject_new(G_OBJECT(client));
-        g_object_unref(client);
-    }
-    else result = NULL;
-
- exit:
+/* Démarre la connexion à la base de données. */
+static PyObject *py_hub_client_start(PyObject *, PyObject *);
 
-    return result;
+/* Arrête la connexion à la base de données. */
+static PyObject *py_hub_client_stop(PyObject *, PyObject *);
 
-}
 
 
 /******************************************************************************
@@ -275,588 +165,6 @@ static PyObject *py_hub_client_stop(PyObject *self, PyObject *args)
 
 /******************************************************************************
 *                                                                             *
-*  Paramètres  : self = client à manipuler.                                   *
-*                args = arguments d'appel non utilisés ici.                   *
-*                                                                             *
-*  Description : Effectue une demande de sauvegarde de l'état courant.        *
-*                                                                             *
-*  Retour      : True si la commande a bien été envoyée, False sinon.         *
-*                                                                             *
-*  Remarques   : -                                                            *
-*                                                                             *
-******************************************************************************/
-
-static PyObject *py_hub_client_save(PyObject *self, PyObject *args)
-{
-    PyObject *result;                       /* Bilan à retourner           */
-    GHubClient *client;                     /* Version native du serveur   */
-    bool status;                            /* Bilan de l'opération        */
-
-#define HUB_CLIENT_SAVE_METHOD PYTHON_METHOD_DEF                            \
-(                                                                           \
-    save, "$self, /",                                                       \
-    METH_NOARGS, py_hub_client,                                             \
-    "Ask the server for saving the current state of the analyzed binary"    \
-    " and returns the status of the request transmission."                  \
-)
-
-    client = G_HUB_CLIENT(pygobject_get(self));
-
-    status = g_hub_client_save(client);
-
-    result = status ? Py_True : Py_False;
-    Py_INCREF(result);
-
-    return result;
-
-}
-
-
-/******************************************************************************
-*                                                                             *
-*  Paramètres  : self = client à manipuler.                                   *
-*                args = arguments d'appel à consulter.                        *
-*                                                                             *
-*  Description : Active les éléments en amont d'un horodatage donné.          *
-*                                                                             *
-*  Retour      : True si la commande a bien été envoyée, False sinon.         *
-*                                                                             *
-*  Remarques   : -                                                            *
-*                                                                             *
-******************************************************************************/
-
-static PyObject *py_hub_client_set_last_active(PyObject *self, PyObject *args)
-{
-    PyObject *result;                       /* Bilan à retourner           */
-    unsigned long long timestamp;           /* Horodatage de limite        */
-    int ret;                                /* Bilan de lecture des args.  */
-    GHubClient *client;                     /* Version native du serveur   */
-    bool status;                            /* Bilan de l'opération        */
-
-#define HUB_CLIENT_SET_LAST_ACTIVE_METHOD PYTHON_METHOD_DEF             \
-(                                                                       \
-    set_last_active, "$self, timestamp, /",                             \
-    METH_VARARGS, py_hub_client,                                        \
-    "Define the timestamp of the last active item in the collection"    \
-    " and returns the status of the request transmission."              \
-    "\n"                                                                \
-    "This method should not be used directly. Prefer calling"           \
-    " pychrysalide.analysis.LoadedBinary.set_last_active() instead,"    \
-    " as some items may be volatile and thus not handled by clients."   \
-)
-
-    ret = PyArg_ParseTuple(args, "K", &timestamp);
-    if (!ret) return NULL;
-
-    client = G_HUB_CLIENT(pygobject_get(self));
-
-    status = g_hub_client_set_last_active(client, timestamp);
-
-    result = status ? Py_True : Py_False;
-    Py_INCREF(result);
-
-    return result;
-
-}
-
-
-/******************************************************************************
-*                                                                             *
-*  Paramètres  : self = client à manipuler.                                   *
-*                args = arguments d'appel à consulter.                        *
-*                                                                             *
-*  Description : Définit la désignation d'un instantané donné.                *
-*                                                                             *
-*  Retour      : True si la commande a bien été envoyée, False sinon.         *
-*                                                                             *
-*  Remarques   : -                                                            *
-*                                                                             *
-******************************************************************************/
-
-static PyObject *py_hub_client_set_snapshot_name(PyObject *self, PyObject *args)
-{
-    PyObject *result;                       /* Bilan à retourner           */
-    const char *raw_id;                     /* Identifiant brut            */
-    const char *text;                       /* Texte fourni à transmettre  */
-    int ret;                                /* Bilan de lecture des args.  */
-    snapshot_id_t id;                       /* Identifiant utilisable      */
-    bool status;                            /* Bilan d'opération           */
-    GHubClient *client;                     /* Version native du serveur   */
-
-#define HUB_CLIENT_SET_SNAPSHOT_NAME_METHOD PYTHON_METHOD_DEF               \
-(                                                                           \
-    set_snapshot_name, "$self, id, name, /",                                \
-    METH_VARARGS, py_hub_client,                                            \
-    "Ask the server for defining a new name of for a snapshot using its"    \
-    " identifier and returns the status of the request transmission."       \
-    "\n"                                                                    \
-    "A 'snapshots-updated' signal is emitted once the request has been"     \
-    " processed with success."                                              \
-)
-
-    ret = PyArg_ParseTuple(args, "ss", &raw_id, &text);
-    if (!ret) return NULL;
-
-    status = init_snapshot_id_from_text(&id, raw_id);
-    if (!status)
-    {
-        PyErr_SetString(PyExc_TypeError, _("provided value is not a valid snapshot identifier."));
-        return NULL;
-    }
-
-    client = G_HUB_CLIENT(pygobject_get(self));
-
-    status = g_hub_client_set_snapshot_name(client, &id, text);
-
-    result = status ? Py_True : Py_False;
-    Py_INCREF(result);
-
-    return result;
-
-}
-
-
-/******************************************************************************
-*                                                                             *
-*  Paramètres  : self = client à manipuler.                                   *
-*                args = arguments d'appel à consulter.                        *
-*                                                                             *
-*  Description : Définit la désignation d'un instantané donné.                *
-*                                                                             *
-*  Retour      : True si la commande a bien été envoyée, False sinon.         *
-*                                                                             *
-*  Remarques   : -                                                            *
-*                                                                             *
-******************************************************************************/
-
-static PyObject *py_hub_client_set_snapshot_desc(PyObject *self, PyObject *args)
-{
-    PyObject *result;                       /* Bilan à retourner           */
-    const char *raw_id;                     /* Identifiant brut            */
-    const char *text;                       /* Texte fourni à transmettre  */
-    int ret;                                /* Bilan de lecture des args.  */
-    snapshot_id_t id;                       /* Identifiant utilisable      */
-    bool status;                            /* Bilan d'opération           */
-    GHubClient *client;                     /* Version native du serveur   */
-
-#define HUB_CLIENT_SET_SNAPSHOT_DESC_METHOD PYTHON_METHOD_DEF               \
-(                                                                           \
-    set_snapshot_desc, "$self, id, desc, /",                                \
-    METH_VARARGS, py_hub_client,                                            \
-    "Ask the server for defining a new description for a snapshot using"    \
-    " its identifier and returns the status of the request transmission."   \
-    "\n"                                                                    \
-    "A 'snapshots-updated' signal is emitted once the request has been"     \
-    " processed with success."                                              \
-)
-
-    ret = PyArg_ParseTuple(args, "ss", &raw_id, &text);
-    if (!ret) return NULL;
-
-    status = init_snapshot_id_from_text(&id, raw_id);
-    if (!status)
-    {
-        PyErr_SetString(PyExc_TypeError, _("provided value is not a valid snapshot identifier."));
-        return NULL;
-    }
-
-    client = G_HUB_CLIENT(pygobject_get(self));
-
-    status = g_hub_client_set_snapshot_desc(client, &id, text);
-
-    result = status ? Py_True : Py_False;
-    Py_INCREF(result);
-
-    return result;
-
-}
-
-
-/******************************************************************************
-*                                                                             *
-*  Paramètres  : self = client à manipuler.                                   *
-*                args = arguments d'appel à consulter.                        *
-*                                                                             *
-*  Description : Restaure un ancien instantané.                               *
-*                                                                             *
-*  Retour      : True si la commande a bien été envoyée, False sinon.         *
-*                                                                             *
-*  Remarques   : -                                                            *
-*                                                                             *
-******************************************************************************/
-
-static PyObject *py_hub_client_restore_snapshot(PyObject *self, PyObject *args)
-{
-    PyObject *result;                       /* Bilan à retourner           */
-    const char *raw_id;                     /* Identifiant brut            */
-    int ret;                                /* Bilan de lecture des args.  */
-    snapshot_id_t id;                       /* Identifiant utilisable      */
-    bool status;                            /* Bilan d'opération           */
-    GHubClient *client;                     /* Version native du serveur   */
-
-#define HUB_CLIENT_RESTORE_SNAPSHOT_METHOD PYTHON_METHOD_DEF                \
-(                                                                           \
-    restore_snapshot, "$self, id, /",                                       \
-    METH_VARARGS, py_hub_client,                                            \
-    "Ask the server for restoring a given snapshot using"                   \
-    " its identifier and returns the status of the request transmission."   \
-    "\n"                                                                    \
-    "A 'snapshot-changed' signal is emitted once the request has been"      \
-    " processed with success."                                              \
-)
-
-    ret = PyArg_ParseTuple(args, "s", &raw_id);
-    if (!ret) return NULL;
-
-    status = init_snapshot_id_from_text(&id, raw_id);
-    if (!status)
-    {
-        PyErr_SetString(PyExc_TypeError, _("provided value is not a valid snapshot identifier."));
-        return NULL;
-    }
-
-    client = G_HUB_CLIENT(pygobject_get(self));
-
-    status = g_hub_client_restore_snapshot(client, &id);
-
-    result = status ? Py_True : Py_False;
-    Py_INCREF(result);
-
-    return result;
-
-}
-
-
-/******************************************************************************
-*                                                                             *
-*  Paramètres  : self = client à manipuler.                                   *
-*                args = arguments d'appel à consulter.                        *
-*                                                                             *
-*  Description : Crée un nouvel instantané à partir d'un autre.               *
-*                                                                             *
-*  Retour      : True si la commande a bien été envoyée, False sinon.         *
-*                                                                             *
-*  Remarques   : -                                                            *
-*                                                                             *
-******************************************************************************/
-
-static PyObject *py_hub_client_create_snapshot(PyObject *self, PyObject *args)
-{
-    PyObject *result;                       /* Bilan à retourner           */
-    GHubClient *client;                     /* Version native du serveur   */
-    bool status;                            /* Bilan d'opération           */
-
-#define HUB_CLIENT_CREATE_SNAPSHOT_METHOD PYTHON_METHOD_DEF                 \
-(                                                                           \
-    create_snapshot, "$self, /",                                            \
-    METH_NOARGS, py_hub_client,                                             \
-    "Ask the server for creating a new snapshot of the current state"       \
-    " and returns the status of the request transmission."                  \
-    "\n"                                                                    \
-    "A 'snapshots-updated' signal is emitted once the request has been"     \
-    " processed with success."                                              \
-)
-
-    client = G_HUB_CLIENT(pygobject_get(self));
-
-    status = g_hub_client_create_snapshot(client);
-
-    result = status ? Py_True : Py_False;
-    Py_INCREF(result);
-
-    return result;
-
-}
-
-
-/******************************************************************************
-*                                                                             *
-*  Paramètres  : self = client à manipuler.                                   *
-*                args = arguments d'appel à consulter.                        *
-*                                                                             *
-*  Description : Supprime un ancien instantané.                               *
-*                                                                             *
-*  Retour      : True si la commande a bien été envoyée, False sinon.         *
-*                                                                             *
-*  Remarques   : -                                                            *
-*                                                                             *
-******************************************************************************/
-
-static PyObject *py_hub_client_remove_snapshot(PyObject *self, PyObject *args)
-{
-    PyObject *result;                       /* Bilan à retourner           */
-    const char *raw_id;                     /* Identifiant brut            */
-    int rec;                                /* Indicateur de récursivité   */
-    int ret;                                /* Bilan de lecture des args.  */
-    snapshot_id_t id;                       /* Identifiant utilisable      */
-    bool status;                            /* Bilan d'opération           */
-    GHubClient *client;                     /* Version native du serveur   */
-
-#define HUB_CLIENT_REMOVE_SNAPSHOT_METHOD PYTHON_METHOD_DEF                 \
-(                                                                           \
-    remove_snapshot, "$self, id, recursive, /",                             \
-    METH_VARARGS, py_hub_client,                                            \
-    "Ask the server for removing a given snapshot using"                    \
-    " its identifier and returns the status of the request transmission."   \
-    "\n"                                                                    \
-    "If this removal has not to be recursive, all children snapshots get"   \
-    " reassigned to the parent snapshot of the target."                     \
-    "\n"                                                                    \
-    "A 'snapshots-updated' signal is emitted once the request has been"     \
-    " processed with success."                                              \
-)
-
-    ret = PyArg_ParseTuple(args, "sp", &raw_id, &rec);
-    if (!ret) return NULL;
-
-    status = init_snapshot_id_from_text(&id, raw_id);
-    if (!status)
-    {
-        PyErr_SetString(PyExc_TypeError, _("provided value is not a valid snapshot identifier."));
-        return NULL;
-    }
-
-    client = G_HUB_CLIENT(pygobject_get(self));
-
-    status = g_hub_client_remove_snapshot(client, &id, rec);
-
-    result = status ? Py_True : Py_False;
-    Py_INCREF(result);
-
-    return result;
-
-}
-
-
-/******************************************************************************
-*                                                                             *
-*  Paramètres  : self    = objet Python concerné par l'appel.                 *
-*                closure = non utilisé ici.                                   *
-*                                                                             *
-*  Description : Fournit la liste des instantanés existants.                  *
-*                                                                             *
-*  Retour      : Liste d'instantanés ou None.                                 *
-*                                                                             *
-*  Remarques   : -                                                            *
-*                                                                             *
-******************************************************************************/
-
-static PyObject *py_hub_client_get_snapshots(PyObject *self, void *closure)
-{
-    PyObject *result;                       /* Valeur à retourner          */
-    GHubClient *client;                     /* Version native du serveur   */
-    snapshot_info_t *info;                  /* Liste d'instantanés présents*/
-    size_t count;                           /* Taille de cette liste       */
-    bool status;                            /* Validité de cet identifiant */
-    PyTypeObject *base;                     /* Modèle d'objet à créer      */
-    size_t i;                               /* Boucle de parcours          */
-    PyObject *item;                         /* Nouvelle description        */
-    char *text;                             /* Valeur textuelle à placer   */
-    PyObject *attrib;                       /* Attribut à constituer       */
-    int ret;                                /* Bilan d'une mise en place   */
-    bool failed;                            /* Détection d'une erreur      */
-
-#define HUB_CLIENT_SNAPSHOTS_ATTRIB PYTHON_GET_DEF_FULL                                 \
-(                                                                                       \
-    snapshots, py_hub_client,                                                           \
-    "List of all existing snapshots, provided as a tuple of pychrysalide.StructObject." \
-    "\n"                                                                                \
-    "Each snapshot is characterised by the following properties :\n"                    \
-    "* parent_id : identifier of the parent snapshot;\n"                                \
-    "* id : identifier of the snapshot;\n"                                              \
-    "* created : timestamp of the creation date;\n"                                     \
-    "* name : name of the snapshot, or None;\n"                                         \
-    "* desc : description of the snapshot, or None."                                    \
-)
-
-    client = G_HUB_CLIENT(pygobject_get(self));
-
-    status = g_hub_client_get_snapshots(client, &info, &count);
-
-    if (status)
-    {
-        result = PyTuple_New(count);
-
-        base = get_python_py_struct_type();
-
-        failed = false;
-
-        for (i = 0; i < count; i++)
-        {
-            item = PyObject_CallFunction((PyObject *)base, NULL);
-            assert(item != NULL);
-
-            text = snapshot_id_as_string(get_snapshot_info_parent_id(&info[i]));
-            attrib = PyUnicode_FromString(text);
-            ret = PyDict_SetItemString(item, "parent_id", attrib);
-            if (ret != 0) break;
-
-            text = snapshot_id_as_string(get_snapshot_info_id(&info[i]));
-            attrib = PyUnicode_FromString(text);
-            ret = PyDict_SetItemString(item, "id", attrib);
-            if (ret != 0) break;
-
-            attrib = PyLong_FromUnsignedLongLong(get_snapshot_info_created(&info[i]));
-            ret = PyDict_SetItemString(item, "created", attrib);
-            if (ret != 0) break;
-
-            text = get_snapshot_info_name(&info[i]);
-
-            if (text != NULL)
-                attrib = PyUnicode_FromString(text);
-            else
-            {
-                attrib = Py_None;
-                Py_INCREF(attrib);
-            }
-
-            ret = PyDict_SetItemString(item, "name", attrib);
-            if (ret != 0) break;
-
-            text = get_snapshot_info_desc(&info[i]);
-
-            if (text != NULL)
-                attrib = PyUnicode_FromString(text);
-            else
-            {
-                attrib = Py_None;
-                Py_INCREF(attrib);
-            }
-
-            ret = PyDict_SetItemString(item, "desc", attrib);
-            if (ret != 0) break;
-
-            PyTuple_SetItem(result, i, item);
-
-        }
-
-        failed = (i < count);
-
-        for (i = 0; i < count; i++)
-            exit_snapshot_info(&info[i]);
-
-        free(info);
-
-        if (failed)
-            goto on_failure;
-
-    }
-
-    else
-    {
-        result = Py_None;
-        Py_INCREF(result);
-    }
-
-    return result;
-
- on_failure:
-
-    Py_DECREF(result);
-
-    return NULL;
-
-}
-
-
-/******************************************************************************
-*                                                                             *
-*  Paramètres  : self    = objet Python concerné par l'appel.                 *
-*                closure = non utilisé ici.                                   *
-*                                                                             *
-*  Description : Fournit l'identifiant de l'instantané courant.               *
-*                                                                             *
-*  Retour      : Identifiant d'instantané ou None.                            *
-*                                                                             *
-*  Remarques   : -                                                            *
-*                                                                             *
-******************************************************************************/
-
-static PyObject *py_hub_client_get_current_snapshot(PyObject *self, void *closure)
-{
-    PyObject *result;                       /* Valeur à retourner          */
-    GHubClient *client;                     /* Version native du serveur   */
-    snapshot_id_t id;                       /* Identifiant à transmettre   */
-    bool status;                            /* Validité de cet identifiant */
-
-#define HUB_CLIENT_CURRENT_SNAPSHOT_ATTRIB PYTHON_GETSET_DEF_FULL   \
-(                                                                   \
-    current_snapshot, py_hub_client,                                \
-    "Identifier of the current snapshot, provided as a string."     \
-    "\n"                                                            \
-    "The returned value is a cached version of the value stored at" \
-    " server side. Thus, defining a new current snapshot is"        \
-    " successful as soon as the request to this server is sent."    \
-)
-
-    client = G_HUB_CLIENT(pygobject_get(self));
-
-    status = g_hub_client_get_current_snapshot(client, &id);
-
-    if (status)
-        result = PyUnicode_FromString(snapshot_id_as_string(&id));
-
-    else
-    {
-        result = Py_None;
-        Py_INCREF(result);
-    }
-
-    return result;
-
-}
-
-
-/******************************************************************************
-*                                                                             *
-*  Paramètres  : self    = objet Python concerné par l'appel.                 *
-*                value   = valeur fournie à intégrer ou prendre en compte.    *
-*                closure = adresse non utilisée ici.                          *
-*                                                                             *
-*  Description : Définit l'identifiant de l'instantané courant.               *
-*                                                                             *
-*  Retour      : Bilan de l'opération.                                        *
-*                                                                             *
-*  Remarques   : -                                                            *
-*                                                                             *
-******************************************************************************/
-
-static int py_hub_client_set_current_snapshot(PyObject *self, PyObject *value, void *closure)
-{
-    int ret;                                /* Bilan d'analyse             */
-    void *raw;                              /* Valeur brute d'identifiant  */
-    snapshot_id_t id;                       /* Identifiant reconnu         */
-    bool status;                            /* Bilan d'une conversion      */
-    GHubClient *client;                     /* Version native du serveur   */
-
-    ret = PyUnicode_Check(value);
-    if (!ret) return -1;
-
-    raw = PyUnicode_DATA(value);
-
-    status = init_snapshot_id_from_text(&id, raw);
-    if (!status)
-    {
-        PyErr_SetString(PyExc_TypeError, _("provided value is not a valid snapshot identifier."));
-        return -1;
-    }
-
-    client = G_HUB_CLIENT(pygobject_get(self));
-
-    status = g_hub_client_set_current_snapshot(client, &id);
-    if (!status)
-    {
-        PyErr_SetString(PyExc_TypeError, "unable to send the provided snapshot identifier");
-        return -1;
-    }
-
-    return 0;
-
-}
-
-
-/******************************************************************************
-*                                                                             *
 *  Paramètres  : -                                                            *
 *                                                                             *
 *  Description : Fournit un accès à une définition de type à diffuser.        *
@@ -872,19 +180,10 @@ PyTypeObject *get_python_hub_client_type(void)
     static PyMethodDef py_hub_client_methods[] = {
         HUB_CLIENT_START_METHOD,
         HUB_CLIENT_STOP_METHOD,
-        HUB_CLIENT_SAVE_METHOD,
-        HUB_CLIENT_SET_LAST_ACTIVE_METHOD,
-        HUB_CLIENT_SET_SNAPSHOT_NAME_METHOD,
-        HUB_CLIENT_SET_SNAPSHOT_DESC_METHOD,
-        HUB_CLIENT_RESTORE_SNAPSHOT_METHOD,
-        HUB_CLIENT_CREATE_SNAPSHOT_METHOD,
-        HUB_CLIENT_REMOVE_SNAPSHOT_METHOD,
         { NULL }
     };
 
     static PyGetSetDef py_hub_client_getseters[] = {
-        HUB_CLIENT_SNAPSHOTS_ATTRIB,
-        HUB_CLIENT_CURRENT_SNAPSHOT_ATTRIB,
         { NULL }
     };
 
@@ -901,7 +200,7 @@ PyTypeObject *get_python_hub_client_type(void)
 
         .tp_methods     = py_hub_client_methods,
         .tp_getset      = py_hub_client_getseters,
-        .tp_new         = py_hub_client_new,
+        .tp_new         = no_python_constructor_allowed,
 
     };
 
diff --git a/plugins/pychrysalide/analysis/db/module.c b/plugins/pychrysalide/analysis/db/module.c
index fa5a139..ddae5a7 100644
--- a/plugins/pychrysalide/analysis/db/module.c
+++ b/plugins/pychrysalide/analysis/db/module.c
@@ -28,6 +28,8 @@
 #include <assert.h>
 
 
+#include "admin.h"
+#include "analyst.h"
 #include "certs.h"
 #include "client.h"
 #include "collection.h"
@@ -98,6 +100,8 @@ bool populate_analysis_db_module(void)
 
     result = true;
 
+    if (result) result = ensure_python_admin_client_is_registered();
+    if (result) result = ensure_python_analyst_client_is_registered();
     if (result) result = ensure_python_certs_is_registered();
     if (result) result = ensure_python_hub_client_is_registered();
     if (result) result = ensure_python_db_collection_is_registered();
diff --git a/plugins/pychrysalide/analysis/db/server.c b/plugins/pychrysalide/analysis/db/server.c
index 80ff4e2..9e4ee61 100644
--- a/plugins/pychrysalide/analysis/db/server.c
+++ b/plugins/pychrysalide/analysis/db/server.c
@@ -83,7 +83,8 @@ static PyObject *py_hub_server_new(PyTypeObject *type, PyObject *args, PyObject
     "\n"                                                                                \
     "Instances can be created using the following constructor:\n"                       \
     "\n"                                                                                \
-    "    HubServer(host=None, port='1337', ipv6=True)"                                  \
+    "    HubServer()"                                                                   \
+    "    HubServer(host='localhost', port='1337', ipv6=True)"                           \
     "\n"                                                                                \
     "Where host and port define the listening properties of the server, and ipv6"       \
     " tries to establish IPv6 connections first."                                       \
diff --git a/src/analysis/binary.c b/src/analysis/binary.c
index 95d2133..19b46a2 100644
--- a/src/analysis/binary.c
+++ b/src/analysis/binary.c
@@ -37,7 +37,6 @@
 
 #include "loaded-int.h"
 #include "routine.h"
-#include "db/client.h"
 #include "disass/disassembler.h"
 #include "../arch/storage.h"
 #include "../common/extstr.h"
@@ -73,8 +72,8 @@ struct _GLoadedBinary
     char *remote_host;                      /* Nom du serveur distant      */
     char *remote_port;                      /* Port du serveur distant     */
 
-    GHubClient *local;                      /* Enregistrements locaux      */
-    GHubClient *remote;                     /* Enregistrements distants    */
+    GAnalystClient *local;                  /* Enregistrements locaux      */
+    GAnalystClient *remote;                 /* Enregistrements distants    */
 
     DBStorage storages[DBF_COUNT];          /* Lieux d'enregistrement      */
     GList *collections;                     /* Ensemble de modifications   */
@@ -830,9 +829,9 @@ static bool g_loaded_binary_connect_internal(GLoadedBinary *binary)
 
     /* Tentative de connexion */
 
-    binary->local = g_hub_client_new(checksum, binary->collections);
+    binary->local = g_analyst_client_new(checksum, binary->collections);
 
-    result = g_hub_client_start_internal(binary->local);
+    result = g_hub_client_start_internal(G_HUB_CLIENT(binary->local));
 
     return result;
 
@@ -867,9 +866,10 @@ static bool g_loaded_binary_connect_remote(GLoadedBinary *binary)
 
     /* Tentative de connexion */
 
-    binary->remote = g_hub_client_new(checksum, binary->collections);
+    binary->remote = g_analyst_client_new(checksum, binary->collections);
 
-    result = g_hub_client_start_remote(binary->local, binary->remote_host, binary->remote_port, true);
+    result = g_hub_client_start_remote(G_HUB_CLIENT(binary->remote),
+                                       binary->remote_host, binary->remote_port, true);
 
     if (!result)
     {
@@ -951,9 +951,9 @@ bool g_loaded_binary_save_cache(const GLoadedBinary *binary)
 *                                                                             *
 ******************************************************************************/
 
-GHubClient *g_loaded_binary_get_client(const GLoadedBinary *binary, bool internal)
+GAnalystClient *g_loaded_binary_get_client(const GLoadedBinary *binary, bool internal)
 {
-    GHubClient *result;                     /* Instance à retourner        */
+    GAnalystClient *result;                 /* Instance à retourner        */
 
     if (internal)
         result = binary->local;
@@ -1056,16 +1056,13 @@ bool g_loaded_binary_add_to_collection(GLoadedBinary *binary, GDbItem *item)
     bool result;                            /* Bilan à faire remonter      */
     DBFeatures feature;                     /* Domaine de fonctionnalité   */
     DBStorage storage;                      /* Forme d'enregistrement      */
-    GHubClient *client;                     /* Liaison à utiliser          */
+    GAnalystClient *client;                 /* Liaison à utiliser          */
 
     feature = g_db_item_get_feature(item);
 
     storage = g_loaded_binary_get_storage(binary, feature);
 
-    if (storage == DBS_ALL_REMOTE)
-        client = binary->remote;
-    else
-        client = binary->local;
+    client = g_loaded_binary_get_client(binary, storage == DBS_ALL_LOCAL);
 
     if (client == NULL)
     {
@@ -1074,7 +1071,10 @@ bool g_loaded_binary_add_to_collection(GLoadedBinary *binary, GDbItem *item)
     }
 
     else
-        result = g_hub_client_add_item(client, item);
+    {
+        result = g_analyst_client_add_item(client, item);
+        g_object_unref(G_OBJECT(client));
+    }
 
     g_object_unref(G_OBJECT(item));
 
@@ -1106,13 +1106,13 @@ bool g_loaded_binary_set_last_active(GLoadedBinary *binary, timestamp_t timestam
 
     if (binary->local != NULL)
     {
-        result = g_hub_client_set_last_active(binary->local, timestamp);
+        result = g_analyst_client_set_last_active(binary->local, timestamp);
         done = true;
     }
 
     if (result && binary->remote != NULL)
     {
-        result = g_hub_client_set_last_active(binary->remote, timestamp);
+        result = g_analyst_client_set_last_active(binary->remote, timestamp);
         done = true;
     }
 
@@ -1546,7 +1546,7 @@ static bool g_loaded_binary_save(GLoadedBinary *binary, xmlDoc *xdoc, xmlXPathCo
     /* Sauvegarde côté serveur */
 
     if (result)
-        g_hub_client_save(binary->local);
+        g_analyst_client_save(binary->local);
 
     return result;
 
diff --git a/src/analysis/binary.h b/src/analysis/binary.h
index 194ccd0..56f21a1 100644
--- a/src/analysis/binary.h
+++ b/src/analysis/binary.h
@@ -31,7 +31,7 @@
 #include "content.h"
 #include "loaded.h"
 #include "db/collection.h"
-#include "db/client.h"
+#include "db/analyst.h"
 #include "db/protocol.h"
 #include "../arch/processor.h"
 #include "../format/debuggable.h"
@@ -114,7 +114,7 @@ bool g_loaded_binary_save_cache(const GLoadedBinary *);
 
 
 /* Fournit un client assurant la liaison avec un serveur. */
-GHubClient *g_loaded_binary_get_client(const GLoadedBinary *, bool);
+GAnalystClient *g_loaded_binary_get_client(const GLoadedBinary *, bool);
 
 /* Fournit l'ensemble des collections utilisées par un binaire. */
 GDbCollection **g_loaded_binary_get_collections(const GLoadedBinary *, size_t *);
diff --git a/src/analysis/db/Makefile.am b/src/analysis/db/Makefile.am
index 3edfb09..8f8242c 100644
--- a/src/analysis/db/Makefile.am
+++ b/src/analysis/db/Makefile.am
@@ -3,12 +3,18 @@ noinst_LTLIBRARIES  = libanalysisdb.la
 
 
 libanalysisdb_la_SOURCES =				\
+	admin.h admin.c						\
+	analyst.h analyst.c					\
 	auth.h auth.c						\
+	backend-int.h						\
+	backend.h backend.c					\
 	cdb.h cdb.c							\
 	certs.h certs.c						\
+	client-int.h						\
 	client.h client.c					\
 	collection-int.h					\
 	collection.h collection.c			\
+	controller.h controller.c			\
 	item-int.h							\
 	item.h item.c						\
 	protocol.h							\
diff --git a/src/analysis/db/admin.c b/src/analysis/db/admin.c
new file mode 100644
index 0000000..355f8c0
--- /dev/null
+++ b/src/analysis/db/admin.c
@@ -0,0 +1,292 @@
+
+/* Chrysalide - Outil d'analyse de fichiers binaires
+ * admin.c - connexion en administrateur à un serveur Chrysalide
+ *
+ * Copyright (C) 2014-2019 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 "admin.h"
+
+
+#include <assert.h>
+#include <poll.h>
+
+
+#include "client-int.h"
+#include "../../core/logs.h"
+
+
+
+/* Description de client à l'écoute (instance) */
+struct _GAdminClient
+{
+    GHubClient parent;                      /* A laisser en premier        */
+
+};
+
+/* Description de client à l'écoute (classe) */
+struct _GAdminClientClass
+{
+    GHubClientClass parent;                 /* A laisser en premier        */
+
+};
+
+
+/* Initialise la classe des descriptions de fichier binaire. */
+static void g_admin_client_class_init(GAdminClientClass *);
+
+/* Initialise une description de fichier binaire. */
+static void g_admin_client_init(GAdminClient *);
+
+/* Supprime toutes les références externes. */
+static void g_admin_client_dispose(GAdminClient *);
+
+/* Procède à la libération totale de la mémoire. */
+static void g_admin_client_finalize(GAdminClient *);
+
+/* Assure l'accueil des nouvelles mises à jour. */
+static void *g_admin_client_update(GAdminClient *);
+
+
+
+/* Indique le type défini pour une description de client à l'écoute. */
+G_DEFINE_TYPE(GAdminClient, g_admin_client, G_TYPE_HUB_CLIENT);
+
+
+/******************************************************************************
+*                                                                             *
+*  Paramètres  : klass = classe à initialiser.                                *
+*                                                                             *
+*  Description : Initialise la classe des descriptions de fichier binaire.    *
+*                                                                             *
+*  Retour      : -                                                            *
+*                                                                             *
+*  Remarques   : -                                                            *
+*                                                                             *
+******************************************************************************/
+
+static void g_admin_client_class_init(GAdminClientClass *klass)
+{
+    GObjectClass *object;                   /* Autre version de la classe  */
+    GHubClientClass *client;                /* Classe parente              */
+
+    object = G_OBJECT_CLASS(klass);
+
+    object->dispose = (GObjectFinalizeFunc/* ! */)g_admin_client_dispose;
+    object->finalize = (GObjectFinalizeFunc)g_admin_client_finalize;
+
+    client = G_HUB_CLIENT_CLASS(klass);
+
+    client->role = CRL_ADMIN;
+    client->recv_func = (GThreadFunc)g_admin_client_update;
+
+}
+
+
+/******************************************************************************
+*                                                                             *
+*  Paramètres  : client = instance à initialiser.                             *
+*                                                                             *
+*  Description : Initialise une description de fichier binaire.               *
+*                                                                             *
+*  Retour      : -                                                            *
+*                                                                             *
+*  Remarques   : -                                                            *
+*                                                                             *
+******************************************************************************/
+
+static void g_admin_client_init(GAdminClient *client)
+{
+
+}
+
+
+/******************************************************************************
+*                                                                             *
+*  Paramètres  : archive = instance d'objet GLib à traiter.                   *
+*                                                                             *
+*  Description : Supprime toutes les références externes.                     *
+*                                                                             *
+*  Retour      : -                                                            *
+*                                                                             *
+*  Remarques   : -                                                            *
+*                                                                             *
+******************************************************************************/
+
+static void g_admin_client_dispose(GAdminClient *client)
+{
+    g_hub_client_stop(G_HUB_CLIENT(client));
+
+    G_OBJECT_CLASS(g_admin_client_parent_class)->dispose(G_OBJECT(client));
+
+}
+
+
+/******************************************************************************
+*                                                                             *
+*  Paramètres  : client = instance d'objet GLib à traiter.                    *
+*                                                                             *
+*  Description : Procède à la libération totale de la mémoire.                *
+*                                                                             *
+*  Retour      : -                                                            *
+*                                                                             *
+*  Remarques   : -                                                            *
+*                                                                             *
+******************************************************************************/
+
+static void g_admin_client_finalize(GAdminClient *client)
+{
+    G_OBJECT_CLASS(g_admin_client_parent_class)->finalize(G_OBJECT(client));
+
+}
+
+
+/******************************************************************************
+*                                                                             *
+*  Paramètres  : -                                                            *
+*                                                                             *
+*  Description : Prépare un client pour une connexion à une BD.               *
+*                                                                             *
+*  Retour      : Structure mise en place ou NULL en cas d'échec.              *
+*                                                                             *
+*  Remarques   : -                                                            *
+*                                                                             *
+******************************************************************************/
+
+GAdminClient *g_admin_client_new(void)
+{
+    GAdminClient *result;                     /* Adresse à retourner         */
+
+    result = g_object_new(G_TYPE_ADMIN_CLIENT, NULL);
+
+    return result;
+
+}
+
+
+/******************************************************************************
+*                                                                             *
+*  Paramètres  : client = client pour les accès distants à manipuler.         *
+*                                                                             *
+*  Description : Assure l'accueil des nouvelles mises à jour.                 *
+*                                                                             *
+*  Retour      : NULL.                                                        *
+*                                                                             *
+*  Remarques   : -                                                            *
+*                                                                             *
+******************************************************************************/
+
+static void *g_admin_client_update(GAdminClient *client)
+{
+    GHubClient *base;                       /* Base de l'instance          */
+    struct pollfd fds[2];                   /* Surveillance des flux       */
+    packed_buffer in_pbuf;                  /* Tampon de réception         */
+    int ret;                                /* Bilan d'un appel            */
+    bool status;                            /* Bilan d'une opération       */
+    uint32_t command;                       /* Commande de la requête      */
+    //packed_buffer out_pbuf;                 /* Tampon d'émission           */
+    char *msg;                              /* Message d'erreur à imprimer */
+
+    base = G_HUB_CLIENT(client);
+
+    /**
+     * Phase d'écoute continue...
+     */
+
+    fds[0].fd = base->stop_ctrl[0];
+    fds[0].events = POLLIN | POLLPRI;
+
+    fds[1].fd = SSL_get_fd(base->tls_fd);
+    fds[1].events = POLLIN | POLLPRI;
+
+    init_packed_buffer(&in_pbuf);
+
+    while (true)
+    {
+        ret = poll(fds, 2, -1);
+        if (ret == -1)
+        {
+            LOG_ERROR_N("poll");
+            break;
+        }
+
+        /* Demande expresse d'arrêt des procédures */
+        if (fds[0].revents)
+            break;
+
+        /* Le canal est fermé, une sortie doit être demandée... */
+        if (fds[1].revents & POLLNVAL)
+            break;
+
+        /**
+         * Même chose, cf. "TCP: When is EPOLLHUP generated?"
+         * https://stackoverflow.com/questions/52976152/tcp-when-is-epollhup-generated/52976327#52976327
+         */
+
+        if (fds[1].revents & (POLLHUP | POLLRDHUP))
+            break;
+
+        if (fds[1].revents & (POLLIN | POLLPRI))
+        {
+            reset_packed_buffer(&in_pbuf);
+
+            status = ssl_recv_packed_buffer(&in_pbuf, base->tls_fd);
+            if (!status) goto bad_exchange;
+
+ next_command:
+
+            status = extract_packed_buffer(&in_pbuf, &command, sizeof(uint32_t), true);
+            if (!status) goto bad_exchange;
+
+            switch (command)
+            {
+                default:
+                    log_variadic_message(LMT_INFO,
+                                         _("This command is not available on this side: 0x%08x"), command);
+                    goto bad_exchange;
+                    break;
+
+            }
+
+            if (has_more_data_in_packed_buffer(&in_pbuf))
+                goto next_command;
+
+            continue;
+
+ bad_exchange:
+
+            asprintf(&msg, _("Bad reception from %s"), base->desc);
+
+            LOG_ERROR(LMT_ERROR, msg);
+
+            free(msg);
+
+            break;
+
+        }
+
+    }
+
+    g_hub_client_stop(G_HUB_CLIENT(client));
+
+    exit_packed_buffer(&in_pbuf);
+
+    return NULL;
+
+}
diff --git a/src/analysis/db/admin.h b/src/analysis/db/admin.h
new file mode 100644
index 0000000..f5a6b5d
--- /dev/null
+++ b/src/analysis/db/admin.h
@@ -0,0 +1,62 @@
+
+/* Chrysalide - Outil d'analyse de fichiers binaires
+ * admin.h - prototypes pour la connexion en administrateur à un serveur Chrysalide
+ *
+ * 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 _ANALYSIS_DB_ADMIN_H
+#define _ANALYSIS_DB_ADMIN_H
+
+
+#include <glib-object.h>
+#include <stdbool.h>
+#include <openssl/ssl.h>
+
+
+#include "client.h"
+#include "collection.h"
+#include "misc/snapshot.h"
+
+
+
+#define G_TYPE_ADMIN_CLIENT            g_admin_client_get_type()
+#define G_ADMIN_CLIENT(obj)            (G_TYPE_CHECK_INSTANCE_CAST((obj), G_TYPE_ADMIN_CLIENT, GAdminClient))
+#define G_IS_ADMIN_CLIENT(obj)         (G_TYPE_CHECK_INSTANCE_TYPE((obj), G_TYPE_ADMIN_CLIENT))
+#define G_ADMIN_CLIENT_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST((klass), G_TYPE_ADMIN_CLIENT, GAdminClientClass))
+#define G_IS_ADMIN_CLIENT_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), G_TYPE_ADMIN_CLIENT))
+#define G_ADMIN_CLIENT_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS((obj), G_TYPE_ADMIN_CLIENT, GAdminClientClass))
+
+
+/* Description de client à l'écoute (instance) */
+typedef struct _GAdminClient GAdminClient;
+
+/* Description de client à l'écoute (classe) */
+typedef struct _GAdminClientClass GAdminClientClass;
+
+
+/* Indique le type défini pour une description de client à l'écoute. */
+GType g_admin_client_get_type(void);
+
+/* Prépare un client pour une connexion à une BD. */
+GAdminClient *g_admin_client_new(void);
+
+
+
+#endif  /* _ANALYSIS_DB_ADMIN_H */
diff --git a/src/analysis/db/analyst.c b/src/analysis/db/analyst.c
new file mode 100644
index 0000000..a828c11
--- /dev/null
+++ b/src/analysis/db/analyst.c
@@ -0,0 +1,1136 @@
+
+/* Chrysalide - Outil d'analyse de fichiers binaires
+ * analyst.c - connexion en analyste à un serveur Chrysalide
+ *
+ * Copyright (C) 2014-2019 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 "analyst.h"
+
+
+#include <assert.h>
+#include <poll.h>
+
+
+#include "client-int.h"
+#include "../../core/logs.h"
+
+
+
+/* Description de client à l'écoute (instance) */
+struct _GAnalystClient
+{
+    GHubClient parent;                      /* A laisser en premier        */
+
+    rle_string hash;                        /* Empreinte du binaire lié    */
+    GList *collections;                     /* Collections d'un binaire    */
+
+    bool can_get_updates;                   /* Réception de maj possibles ?*/
+
+    snapshot_info_t *snapshots;             /* Liste des instantanés       */
+    size_t snap_count;                      /* Taille de cette liste       */
+    GMutex snap_lock;                       /* Concurrence des accès       */
+
+    snapshot_id_t current;                  /* Instantané courant          */
+    bool has_current;                       /* Validité de l'identifiant   */
+    GMutex cur_lock;                        /* Concurrence des accès       */
+
+};
+
+/* Description de client à l'écoute (classe) */
+struct _GAnalystClientClass
+{
+    GHubClientClass parent;                 /* A laisser en premier        */
+
+    /* Signaux */
+
+    void (* snapshots_updated) (GAnalystClient *);
+    void (* snapshot_changed) (GAnalystClient *);
+
+};
+
+
+/* Initialise la classe des descriptions de fichier binaire. */
+static void g_analyst_client_class_init(GAnalystClientClass *);
+
+/* Initialise une description de fichier binaire. */
+static void g_analyst_client_init(GAnalystClient *);
+
+/* Supprime toutes les références externes. */
+static void g_analyst_client_dispose(GAnalystClient *);
+
+/* Procède à la libération totale de la mémoire. */
+static void g_analyst_client_finalize(GAnalystClient *);
+
+/* Assure l'accueil des nouvelles mises à jour. */
+static void *g_analyst_client_update(GAnalystClient *);
+
+/* Met à jour la liste des instantanés courants. */
+static bool g_analyst_client_update_snapshots(GAnalystClient *, packed_buffer *);
+
+/* Met à jour l'identifiant de l'instantané courant. */
+static bool g_analyst_client_update_current_snapshot(GAnalystClient *, packed_buffer *);
+
+
+
+/* Indique le type défini pour une description de client à l'écoute. */
+G_DEFINE_TYPE(GAnalystClient, g_analyst_client, G_TYPE_HUB_CLIENT);
+
+
+/******************************************************************************
+*                                                                             *
+*  Paramètres  : klass = classe à initialiser.                                *
+*                                                                             *
+*  Description : Initialise la classe des descriptions de fichier binaire.    *
+*                                                                             *
+*  Retour      : -                                                            *
+*                                                                             *
+*  Remarques   : -                                                            *
+*                                                                             *
+******************************************************************************/
+
+static void g_analyst_client_class_init(GAnalystClientClass *klass)
+{
+    GObjectClass *object;                   /* Autre version de la classe  */
+    GHubClientClass *client;                /* Classe parente              */
+
+    object = G_OBJECT_CLASS(klass);
+
+    object->dispose = (GObjectFinalizeFunc/* ! */)g_analyst_client_dispose;
+    object->finalize = (GObjectFinalizeFunc)g_analyst_client_finalize;
+
+    client = G_HUB_CLIENT_CLASS(klass);
+
+    client->role = CRL_ANALYST;
+    client->recv_func = (GThreadFunc)g_analyst_client_update;
+
+    g_signal_new("snapshots-updated",
+                 G_TYPE_ANALYST_CLIENT,
+                 G_SIGNAL_RUN_LAST,
+                 G_STRUCT_OFFSET(GAnalystClientClass, snapshots_updated),
+                 NULL, NULL,
+                 g_cclosure_marshal_VOID__VOID,
+                 G_TYPE_NONE, 0);
+
+    g_signal_new("snapshot-changed",
+                 G_TYPE_ANALYST_CLIENT,
+                 G_SIGNAL_RUN_LAST,
+                 G_STRUCT_OFFSET(GAnalystClientClass, snapshot_changed),
+                 NULL, NULL,
+                 g_cclosure_marshal_VOID__VOID,
+                 G_TYPE_NONE, 0);
+
+}
+
+
+/******************************************************************************
+*                                                                             *
+*  Paramètres  : client = instance à initialiser.                             *
+*                                                                             *
+*  Description : Initialise une description de fichier binaire.               *
+*                                                                             *
+*  Retour      : -                                                            *
+*                                                                             *
+*  Remarques   : -                                                            *
+*                                                                             *
+******************************************************************************/
+
+static void g_analyst_client_init(GAnalystClient *client)
+{
+    setup_empty_rle_string(&client->hash);
+    client->collections = NULL;
+
+    client->can_get_updates = false;
+
+    client->snapshots = NULL;
+    client->snap_count = 0;
+    g_mutex_init(&client->snap_lock);
+
+    setup_empty_snapshot_id(&client->current);
+    client->has_current = false;
+    g_mutex_init(&client->cur_lock);
+
+}
+
+
+/******************************************************************************
+*                                                                             *
+*  Paramètres  : archive = instance d'objet GLib à traiter.                   *
+*                                                                             *
+*  Description : Supprime toutes les références externes.                     *
+*                                                                             *
+*  Retour      : -                                                            *
+*                                                                             *
+*  Remarques   : -                                                            *
+*                                                                             *
+******************************************************************************/
+
+static void g_analyst_client_dispose(GAnalystClient *client)
+{
+    g_hub_client_stop(G_HUB_CLIENT(client));
+
+    g_mutex_clear(&client->cur_lock);
+
+    g_mutex_clear(&client->snap_lock);
+
+    G_OBJECT_CLASS(g_analyst_client_parent_class)->dispose(G_OBJECT(client));
+
+}
+
+
+/******************************************************************************
+*                                                                             *
+*  Paramètres  : client = instance d'objet GLib à traiter.                    *
+*                                                                             *
+*  Description : Procède à la libération totale de la mémoire.                *
+*                                                                             *
+*  Retour      : -                                                            *
+*                                                                             *
+*  Remarques   : -                                                            *
+*                                                                             *
+******************************************************************************/
+
+static void g_analyst_client_finalize(GAnalystClient *client)
+{
+    size_t i;                               /* Boucle de parcours          */
+
+    unset_rle_string(&client->hash);
+
+    if (client->snapshots != NULL)
+    {
+        for (i = 0; i < client->snap_count; i++)
+            exit_snapshot_info(&client->snapshots[i]);
+
+        free(client->snapshots);
+
+    }
+
+    G_OBJECT_CLASS(g_analyst_client_parent_class)->finalize(G_OBJECT(client));
+
+}
+
+
+/******************************************************************************
+*                                                                             *
+*  Paramètres  : hash        = empreinte d'un binaire en cours d'analyse.     *
+*                collections = ensemble de collections existantes.            *
+*                                                                             *
+*  Description : Prépare un client pour une connexion à une BD.               *
+*                                                                             *
+*  Retour      : Structure mise en place ou NULL en cas d'échec.              *
+*                                                                             *
+*  Remarques   : -                                                            *
+*                                                                             *
+******************************************************************************/
+
+GAnalystClient *g_analyst_client_new(const char *hash, GList *collections)
+{
+    GAnalystClient *result;                     /* Adresse à retourner         */
+
+    result = g_object_new(G_TYPE_HUB_CLIENT, NULL);
+
+    init_static_rle_string(&result->hash, hash);
+    result->collections = collections;
+
+    return result;
+
+}
+
+
+/******************************************************************************
+*                                                                             *
+*  Paramètres  : client = client pour les accès distants à manipuler.         *
+*                                                                             *
+*  Description : Assure l'accueil des nouvelles mises à jour.                 *
+*                                                                             *
+*  Retour      : NULL.                                                        *
+*                                                                             *
+*  Remarques   : -                                                            *
+*                                                                             *
+******************************************************************************/
+
+static void *g_analyst_client_update(GAnalystClient *client)
+{
+    GHubClient *base;                       /* Base de l'instance          */
+    packed_buffer out_pbuf;                 /* Tampon d'émission           */
+    bool status;                            /* Bilan d'une opération       */
+    struct pollfd fds[2];                   /* Surveillance des flux       */
+    packed_buffer in_pbuf;                  /* Tampon de réception         */
+    int ret;                                /* Bilan d'un appel            */
+    uint32_t tmp32;                         /* Valeur sur 32 bits          */
+    uint32_t command;                       /* Commande de la requête      */
+    DBError error;                          /* Bilan d'une commande passée */
+    GDbCollection *collec;                  /* Collection visée au final   */
+    uint8_t tmp8;                           /* Valeur sur 8 bits           */
+    char *msg;                              /* Message d'erreur à imprimer */
+
+    base = G_HUB_CLIENT(client);
+
+    /**
+     * Avant toute chose, on demande un stage d'actualisation !
+     */
+
+    init_packed_buffer(&out_pbuf);
+
+    status = extend_packed_buffer(&out_pbuf, (uint32_t []) { DBC_GET_SNAPSHOTS }, sizeof(uint32_t), true);
+    if (!status)
+    {
+        exit_packed_buffer(&out_pbuf);
+        goto exit;
+    }
+
+    status = extend_packed_buffer(&out_pbuf, (uint32_t []) { DBC_GET_CUR_SNAPSHOT }, sizeof(uint32_t), true);
+    if (!status)
+    {
+        exit_packed_buffer(&out_pbuf);
+        goto exit;
+    }
+
+    status = extend_packed_buffer(&out_pbuf, (uint32_t []) { DBC_GET_ALL_ITEMS }, sizeof(uint32_t), true);
+    if (!status)
+    {
+        exit_packed_buffer(&out_pbuf);
+        goto exit;
+    }
+
+    status = ssl_send_packed_buffer(&out_pbuf, base->tls_fd);
+    if (!status)
+    {
+        log_simple_message(LMT_INFO, _("Failed to get all updates"));
+        exit_packed_buffer(&out_pbuf);
+        goto exit;
+    }
+
+    exit_packed_buffer(&out_pbuf);
+
+    /**
+     * Phase d'écoute continue...
+     */
+
+    fds[0].fd = base->stop_ctrl[0];
+    fds[0].events = POLLIN | POLLPRI;
+
+    fds[1].fd = SSL_get_fd(base->tls_fd);
+    fds[1].events = POLLIN | POLLPRI;
+
+    init_packed_buffer(&in_pbuf);
+
+    while (true)
+    {
+        ret = poll(fds, 2, -1);
+        if (ret == -1)
+        {
+            LOG_ERROR_N("poll");
+            break;
+        }
+
+        /* Demande expresse d'arrêt des procédures */
+        if (fds[0].revents)
+            break;
+
+        /* Le canal est fermé, une sortie doit être demandée... */
+        if (fds[1].revents & POLLNVAL)
+            break;
+
+        /**
+         * Même chose, cf. "TCP: When is EPOLLHUP generated?"
+         * https://stackoverflow.com/questions/52976152/tcp-when-is-epollhup-generated/52976327#52976327
+         */
+
+        if (fds[1].revents & (POLLHUP | POLLRDHUP))
+            break;
+
+        if (fds[1].revents & (POLLIN | POLLPRI))
+        {
+            reset_packed_buffer(&in_pbuf);
+
+            status = ssl_recv_packed_buffer(&in_pbuf, base->tls_fd);
+            if (!status) goto gdcu_bad_exchange;
+
+ next_command:
+
+            status = extract_packed_buffer(&in_pbuf, &command, sizeof(uint32_t), true);
+            if (!status) goto gdcu_bad_exchange;
+
+            switch (command)
+            {
+                case DBC_SAVE:
+
+                    status = extract_packed_buffer(&in_pbuf, &tmp32, sizeof(uint32_t), true);
+                    if (!status) goto gdcu_bad_exchange;
+
+                    error = tmp32;
+
+                    if (error == DBE_NONE)
+                        log_variadic_message(LMT_INFO, _("Archive saved for binary '%s'"),
+                                             get_rle_string(&client->hash));
+                    else
+                        log_variadic_message(LMT_ERROR, _("Failed to save the archive for binary '%s'"),
+                                             get_rle_string(&client->hash));
+
+                    break;
+
+                case DBC_COLLECTION:
+
+                    status = extract_packed_buffer(&in_pbuf, &tmp32, sizeof(uint32_t), true);
+                    if (!status) goto gdcu_bad_exchange;
+
+                    collec = find_collection_in_list(client->collections, tmp32);
+                    if (collec == NULL) goto gdcu_bad_exchange;
+
+                    if (client->can_get_updates)
+                        status = g_db_collection_unpack(collec, &in_pbuf, NULL);
+                    else
+                        status = _g_db_collection_unpack(collec, &in_pbuf, (DBAction []) { 0 }, NULL);
+
+                    if (!status) goto gdcu_bad_exchange;
+
+                    break;
+
+                case DBC_GET_ALL_ITEMS:
+                    log_variadic_message(LMT_INFO,
+                                         _("This command is not available on this side: 0x%08x"), command);
+                    goto gdcu_bad_exchange;
+                    break;
+
+                case DBC_SET_ALL_ITEMS:
+
+                    status = extract_packed_buffer(&in_pbuf, &tmp8, sizeof(uint8_t), true);
+                    if (!status) goto gdcu_bad_exchange;
+
+                    client->can_get_updates = (tmp8 == 0x1);
+                    break;
+
+                case DBC_GET_SNAPSHOTS:
+                    log_variadic_message(LMT_INFO,
+                                         _("This command is not available on this side: 0x%08x"), command);
+                    goto gdcu_bad_exchange;
+                    break;
+
+                case DBC_SNAPSHOTS_UPDATED:
+
+                    status = g_analyst_client_update_snapshots(client, &in_pbuf);
+                    if (!status) goto gdcu_bad_exchange;
+
+                    break;
+
+                case DBC_GET_CUR_SNAPSHOT:
+                    log_variadic_message(LMT_INFO,
+                                         _("This command is not available on this side: 0x%08x"), command);
+                    goto gdcu_bad_exchange;
+                    break;
+
+                case DBC_CUR_SNAPSHOT_UPDATED:
+
+                    status = g_analyst_client_update_current_snapshot(client, &in_pbuf);
+                    if (!status) goto gdcu_bad_exchange;
+
+                    break;
+
+                case DBC_SET_CUR_SNAPSHOT:
+                case DBC_SET_SNAPSHOT_NAME:
+                case DBC_SET_SNAPSHOT_DESC:
+                case DBC_CREATE_SNAPSHOT:
+                case DBC_REMOVE_SNAPSHOT:
+                    log_variadic_message(LMT_INFO,
+                                         _("This command is not available on this side: 0x%08x"), command);
+                    goto gdcu_bad_exchange;
+                    break;
+
+            }
+
+            if (has_more_data_in_packed_buffer(&in_pbuf))
+                goto next_command;
+
+            client->can_get_updates = true;
+            continue;
+
+ gdcu_bad_exchange:
+
+            asprintf(&msg, _("Bad reception from %s"), base->desc);
+
+            LOG_ERROR(LMT_ERROR, msg);
+
+            free(msg);
+
+            break;
+
+        }
+
+    }
+
+ exit:
+
+    g_hub_client_stop(G_HUB_CLIENT(client));
+
+    exit_packed_buffer(&in_pbuf);
+
+    return NULL;
+
+}
+
+
+/******************************************************************************
+*                                                                             *
+*  Paramètres  : client = client pour les accès distants à manipuler.         *
+*                pbuf   = données présentes à traiter.                        *
+*                                                                             *
+*  Description : Met à jour la liste des instantanés courants.                *
+*                                                                             *
+*  Retour      : true si l'opération s'est déroulée sans encombre, ou false.  *
+*                                                                             *
+*  Remarques   : -                                                            *
+*                                                                             *
+******************************************************************************/
+
+static bool g_analyst_client_update_snapshots(GAnalystClient *client, packed_buffer *pbuf)
+{
+    bool result;                            /* Validité à retourner        */
+    size_t i;                               /* Boucle de parcours          */
+    char id[SNAP_ID_HEX_SZ];                /* Caractères hexadécimaux     */
+    snapshot_info_t info;                   /* Description d'instantané    */
+    snapshot_info_t *dest;                  /* Destination de description  */
+
+    result = true;
+
+    g_mutex_lock(&client->snap_lock);
+
+    if (client->snapshots != NULL)
+    {
+        for (i = 0; i < client->snap_count; i++)
+            exit_snapshot_info(&client->snapshots[i]);
+
+        free(client->snapshots);
+
+        client->snapshots = NULL;
+        client->snap_count = 0;
+
+    }
+
+    do
+    {
+        result = peek_packed_buffer(pbuf, id, SNAP_ID_HEX_SZ, false);
+        if (!result) break;
+
+        if (strncmp(id, SNAPSHOT_END_MARK, SNAP_ID_HEX_SZ) == 0)
+        {
+            advance_packed_buffer(pbuf, SNAP_ID_HEX_SZ);
+            break;
+        }
+
+        else
+        {
+            setup_empty_snapshot_info(&info);
+
+            result = unpack_snapshot_info(&info, pbuf);
+            if (!result) break;
+
+            client->snapshots = realloc(client->snapshots, ++client->snap_count * sizeof(snapshot_info_t));
+
+            dest = &client->snapshots[client->snap_count - 1];
+
+            setup_empty_snapshot_info(dest);
+            copy_snapshot_info(dest, &info);
+
+            exit_snapshot_info(&info);
+
+        }
+
+    }
+    while (true);
+
+    g_mutex_unlock(&client->snap_lock);
+
+    if (result)
+        g_signal_emit_by_name(client, "snapshots-updated");
+
+    return result;
+
+}
+
+
+/******************************************************************************
+*                                                                             *
+*  Paramètres  : client = client pour les accès distants à manipuler.         *
+*                pbuf   = données présentes à traiter.                        *
+*                                                                             *
+*  Description : Met à jour l'identifiant de l'instantané courant.            *
+*                                                                             *
+*  Retour      : true si l'opération s'est déroulée sans encombre, ou false.  *
+*                                                                             *
+*  Remarques   : -                                                            *
+*                                                                             *
+******************************************************************************/
+
+static bool g_analyst_client_update_current_snapshot(GAnalystClient *client, packed_buffer *pbuf)
+{
+    bool result;                            /* Validité à retourner        */
+    snapshot_id_t id;                       /* Identifiant d'instantané    */
+
+    setup_empty_snapshot_id(&id);
+
+    result = unpack_snapshot_id(&id, pbuf);
+
+    if (result)
+    {
+        g_mutex_lock(&client->cur_lock);
+
+        copy_snapshot_id(&client->current, &id);
+        client->has_current = true;
+
+        g_mutex_unlock(&client->cur_lock);
+
+        g_signal_emit_by_name(client, "snapshot-changed");
+
+    }
+
+    return result;
+
+}
+
+
+/******************************************************************************
+*                                                                             *
+*  Paramètres  : client = client pour les accès distants à manipuler.         *
+*                                                                             *
+*  Description : Effectue une demande de sauvegarde de l'état courant.        *
+*                                                                             *
+*  Retour      : true si la commande a bien été envoyée, false sinon.         *
+*                                                                             *
+*  Remarques   : -                                                            *
+*                                                                             *
+******************************************************************************/
+
+bool g_analyst_client_save(GAnalystClient *client)
+{
+    bool result;                            /* Bilan partiel à remonter    */
+    packed_buffer out_pbuf;                 /* Tampon d'émission           */
+    SSL *tls_fd;                            /* Canal de communication SSL  */
+
+    init_packed_buffer(&out_pbuf);
+
+    tls_fd = g_hub_client_get_ssl_fd(G_HUB_CLIENT(client));
+
+    if (tls_fd == NULL)
+        result = false;
+
+    else
+    {
+        result = extend_packed_buffer(&out_pbuf, (uint32_t []) { DBC_SAVE }, sizeof(uint32_t), true);
+
+        if (result)
+            result = ssl_send_packed_buffer(&out_pbuf, tls_fd);
+
+        g_hub_client_put_ssl_fd(G_HUB_CLIENT(client), tls_fd);
+
+    }
+
+    exit_packed_buffer(&out_pbuf);
+
+    return result;
+
+}
+
+
+/******************************************************************************
+*                                                                             *
+*  Paramètres  : client = client pour les accès distants à manipuler.         *
+*                item   = élémnent à pousser vers un serveur de collection.   *
+*                                                                             *
+*  Description : Ajoute un élément à la collection d'un serveur.              *
+*                                                                             *
+*  Retour      : true si la commande a bien été envoyée, false sinon.         *
+*                                                                             *
+*  Remarques   : -                                                            *
+*                                                                             *
+******************************************************************************/
+
+bool g_analyst_client_add_item(GAnalystClient *client, const GDbItem *item)
+{
+    bool result;                            /* Bilan partiel à remonter    */
+    packed_buffer out_pbuf;                 /* Tampon d'émission           */
+    SSL *tls_fd;                            /* Canal de communication SSL  */
+    DBFeatures feature;                     /* Domaine de fonctionnalité   */
+    GDbCollection *collec;                  /* Collection visée au final   */
+
+    init_packed_buffer(&out_pbuf);
+
+    tls_fd = g_hub_client_get_ssl_fd(G_HUB_CLIENT(client));
+
+    if (tls_fd == NULL)
+        result = false;
+
+    else
+    {
+        feature = g_db_item_get_feature(item);
+
+        collec = find_collection_in_list(client->collections, feature);
+        if (collec == NULL)
+        {
+            result = false;
+            goto bad_item_feature;
+        }
+
+        result = g_db_collection_pack(collec, &out_pbuf, DBA_ADD_ITEM, item);
+
+        if (result)
+            result = ssl_send_packed_buffer(&out_pbuf, tls_fd);
+
+ bad_item_feature:
+
+        g_hub_client_put_ssl_fd(G_HUB_CLIENT(client), tls_fd);
+
+    }
+
+    exit_packed_buffer(&out_pbuf);
+
+    return result;
+
+}
+
+
+/******************************************************************************
+*                                                                             *
+*  Paramètres  : client    = client pour les accès distants à manipuler.      *
+*                timestamp = date du dernier élément à garder comme actif.    *
+*                                                                             *
+*  Description : Active les éléments en amont d'un horodatage donné.          *
+*                                                                             *
+*  Retour      : true si la commande a bien été envoyée, false sinon.         *
+*                                                                             *
+*  Remarques   : -                                                            *
+*                                                                             *
+******************************************************************************/
+
+bool g_analyst_client_set_last_active(GAnalystClient *client, timestamp_t timestamp)
+{
+    bool result;                            /* Bilan partiel à remonter    */
+    packed_buffer out_pbuf;                 /* Tampon d'émission           */
+    SSL *tls_fd;                            /* Canal de communication SSL  */
+
+    init_packed_buffer(&out_pbuf);
+
+    tls_fd = g_hub_client_get_ssl_fd(G_HUB_CLIENT(client));
+
+    if (tls_fd == NULL)
+        result = false;
+
+    else
+    {
+        result = extend_packed_buffer(&out_pbuf, (uint32_t []) { DBC_SET_LAST_ACTIVE }, sizeof(uint32_t), true);
+
+        if (result)
+            result = pack_timestamp(&timestamp, &out_pbuf);
+
+        if (result)
+            result = ssl_send_packed_buffer(&out_pbuf, tls_fd);
+
+        g_hub_client_put_ssl_fd(G_HUB_CLIENT(client), tls_fd);
+
+    }
+
+    exit_packed_buffer(&out_pbuf);
+
+    return result;
+
+}
+
+
+/******************************************************************************
+*                                                                             *
+*  Paramètres  : client = client pour les accès distants à manipuler.         *
+*                info   = description des instantanés présents. [OUT]         *
+*                count  = taille de la liste retournée. [OUT]                 *
+*                                                                             *
+*  Description : Fournit la liste des instantanés existants.                  *
+*                                                                             *
+*  Retour      : true si la liste retournée est valide, false sinon.          *
+*                                                                             *
+*  Remarques   : -                                                            *
+*                                                                             *
+******************************************************************************/
+
+bool g_analyst_client_get_snapshots(GAnalystClient *client, snapshot_info_t **info, size_t *count)
+{
+    bool result;                            /* Validité à retourner        */
+    size_t i;                               /* Boucle de parcours          */
+    snapshot_info_t *dest;                  /* Destination de description  */
+
+    g_mutex_lock(&client->snap_lock);
+
+    result = (client->snap_count > 0);
+
+    if (result)
+    {
+        *info = malloc(client->snap_count * sizeof(snapshot_info_t));
+        *count = client->snap_count;
+
+        for (i = 0; i < client->snap_count; i++)
+        {
+            dest = &(*info)[i];
+
+            setup_empty_snapshot_info(dest);
+            copy_snapshot_info(dest, &client->snapshots[i]);
+
+        }
+
+    }
+
+    g_mutex_unlock(&client->snap_lock);
+
+    return result;
+
+}
+
+
+/******************************************************************************
+*                                                                             *
+*  Paramètres  : client = client pour les accès distants à manipuler.         *
+*                id     = identifiant d'instantané à renseigner. [OUT]        *
+*                                                                             *
+*  Description : Fournit l'identifiant de l'instantané courant.               *
+*                                                                             *
+*  Retour      : true si l'identifiant retourné est valide, false sinon.      *
+*                                                                             *
+*  Remarques   : -                                                            *
+*                                                                             *
+******************************************************************************/
+
+bool g_analyst_client_get_current_snapshot(GAnalystClient *client, snapshot_id_t *id)
+{
+    bool result;                            /* Validité à retourner        */
+
+    g_mutex_lock(&client->cur_lock);
+
+    result = client->has_current;
+
+    if (result)
+        copy_snapshot_id(id, &client->current);
+
+    g_mutex_unlock(&client->cur_lock);
+
+    return result;
+
+}
+
+
+/******************************************************************************
+*                                                                             *
+*  Paramètres  : client = client pour les accès distants à manipuler.         *
+*                id     = identifiant d'instantané à activer.                 *
+*                                                                             *
+*  Description : Définit l'identifiant de l'instantané courant.               *
+*                                                                             *
+*  Retour      : true si la commande a bien été envoyée, false sinon.         *
+*                                                                             *
+*  Remarques   : -                                                            *
+*                                                                             *
+******************************************************************************/
+
+bool g_analyst_client_set_current_snapshot(GAnalystClient *client, const snapshot_id_t *id)
+{
+    bool result;                            /* Bilan partiel à remonter    */
+    packed_buffer out_pbuf;                 /* Tampon d'émission           */
+    SSL *tls_fd;                            /* Canal de communication SSL  */
+
+    init_packed_buffer(&out_pbuf);
+
+    tls_fd = g_hub_client_get_ssl_fd(G_HUB_CLIENT(client));
+
+    if (tls_fd == NULL)
+        result = false;
+
+    else
+    {
+        result = extend_packed_buffer(&out_pbuf, (uint32_t []) { DBC_SET_CUR_SNAPSHOT }, sizeof(uint32_t), true);
+
+        if (result)
+            result = pack_snapshot_id(id, &out_pbuf);
+
+        if (result)
+            result = ssl_send_packed_buffer(&out_pbuf, tls_fd);
+
+        g_hub_client_put_ssl_fd(G_HUB_CLIENT(client), tls_fd);
+
+    }
+
+    exit_packed_buffer(&out_pbuf);
+
+    return result;
+
+}
+
+
+/******************************************************************************
+*                                                                             *
+*  Paramètres  : client = client pour les accès distants à manipuler.         *
+*                id     = identifiant d'instantané à traiter.                 *
+*                name   = désignation humaine pour l'instantané.              *
+*                                                                             *
+*  Description : Définit la désignation d'un instantané donné.                *
+*                                                                             *
+*  Retour      : true si la commande a bien été envoyée, false sinon.         *
+*                                                                             *
+*  Remarques   : -                                                            *
+*                                                                             *
+******************************************************************************/
+
+bool g_analyst_client_set_snapshot_name(GAnalystClient *client, const snapshot_id_t *id, const char *name)
+{
+    bool result;                            /* Bilan partiel à remonter    */
+    packed_buffer out_pbuf;                 /* Tampon d'émission           */
+    SSL *tls_fd;                            /* Canal de communication SSL  */
+    rle_string string;                      /* Chaîne à transmettre        */
+
+    init_packed_buffer(&out_pbuf);
+
+    tls_fd = g_hub_client_get_ssl_fd(G_HUB_CLIENT(client));
+
+    if (tls_fd == NULL)
+        result = false;
+
+    else
+    {
+        result = extend_packed_buffer(&out_pbuf, (uint32_t []) { DBC_SET_SNAPSHOT_NAME }, sizeof(uint32_t), true);
+
+        if (result)
+            result = pack_snapshot_id(id, &out_pbuf);
+
+        if (result)
+        {
+            init_static_rle_string(&string, name);
+
+            result = pack_rle_string(&string, &out_pbuf);
+
+            exit_rle_string(&string);
+
+        }
+
+        if (result)
+            result = ssl_send_packed_buffer(&out_pbuf, tls_fd);
+
+        g_hub_client_put_ssl_fd(G_HUB_CLIENT(client), tls_fd);
+
+    }
+
+    exit_packed_buffer(&out_pbuf);
+
+    return result;
+
+}
+
+
+/******************************************************************************
+*                                                                             *
+*  Paramètres  : client = client pour les accès distants à manipuler.         *
+*                id     = identifiant d'instantané à traiter.                 *
+*                desc   = description humaine pour l'instantané.              *
+*                                                                             *
+*  Description : Définit la description d'un instantané donné.                *
+*                                                                             *
+*  Retour      : true si la commande a bien été envoyée, false sinon.         *
+*                                                                             *
+*  Remarques   : -                                                            *
+*                                                                             *
+******************************************************************************/
+
+bool g_analyst_client_set_snapshot_desc(GAnalystClient *client, const snapshot_id_t *id, const char *desc)
+{
+    bool result;                            /* Bilan partiel à remonter    */
+    packed_buffer out_pbuf;                 /* Tampon d'émission           */
+    SSL *tls_fd;                            /* Canal de communication SSL  */
+    rle_string string;                      /* Chaîne à transmettre        */
+
+    init_packed_buffer(&out_pbuf);
+
+    tls_fd = g_hub_client_get_ssl_fd(G_HUB_CLIENT(client));
+
+    if (tls_fd == NULL)
+        result = false;
+
+    else
+    {
+        result = extend_packed_buffer(&out_pbuf, (uint32_t []) { DBC_SET_SNAPSHOT_DESC }, sizeof(uint32_t), true);
+
+        if (result)
+            result = pack_snapshot_id(id, &out_pbuf);
+
+        if (result)
+        {
+            init_static_rle_string(&string, desc);
+
+            result = pack_rle_string(&string, &out_pbuf);
+
+            exit_rle_string(&string);
+
+        }
+
+        if (result)
+            result = ssl_send_packed_buffer(&out_pbuf, tls_fd);
+
+        g_hub_client_put_ssl_fd(G_HUB_CLIENT(client), tls_fd);
+
+    }
+
+    exit_packed_buffer(&out_pbuf);
+
+    return result;
+
+}
+
+
+/******************************************************************************
+*                                                                             *
+*  Paramètres  : client = client pour les accès distants à manipuler.         *
+*                id     = identifiant d'instantané à traiter.                 *
+*                                                                             *
+*  Description : Restaure un ancien instantané.                               *
+*                                                                             *
+*  Retour      : true si la commande a bien été envoyée, false sinon.         *
+*                                                                             *
+*  Remarques   : -                                                            *
+*                                                                             *
+******************************************************************************/
+
+bool g_analyst_client_restore_snapshot(GAnalystClient *client, const snapshot_id_t *id)
+{
+    bool result;                            /* Bilan partiel à remonter    */
+    packed_buffer out_pbuf;                 /* Tampon d'émission           */
+    SSL *tls_fd;                            /* Canal de communication SSL  */
+
+    init_packed_buffer(&out_pbuf);
+
+    tls_fd = g_hub_client_get_ssl_fd(G_HUB_CLIENT(client));
+
+    if (tls_fd == NULL)
+        result = false;
+
+    else
+    {
+        result = extend_packed_buffer(&out_pbuf, (uint32_t []) { DBC_SET_CUR_SNAPSHOT }, sizeof(uint32_t), true);
+
+        if (result)
+            result = pack_snapshot_id(id, &out_pbuf);
+
+        if (result)
+            result = ssl_send_packed_buffer(&out_pbuf, tls_fd);
+
+        g_hub_client_put_ssl_fd(G_HUB_CLIENT(client), tls_fd);
+
+    }
+
+    exit_packed_buffer(&out_pbuf);
+
+    return result;
+
+}
+
+
+/******************************************************************************
+*                                                                             *
+*  Paramètres  : client = client pour les accès distants à manipuler.         *
+*                                                                             *
+*  Description : Crée un nouvel instantané à partir d'un autre.               *
+*                                                                             *
+*  Retour      : true si la commande a bien été envoyée, false sinon.         *
+*                                                                             *
+*  Remarques   : -                                                            *
+*                                                                             *
+******************************************************************************/
+
+bool g_analyst_client_create_snapshot(GAnalystClient *client)
+{
+    bool result;                            /* Bilan partiel à remonter    */
+    packed_buffer out_pbuf;                 /* Tampon d'émission           */
+    SSL *tls_fd;                            /* Canal de communication SSL  */
+
+    init_packed_buffer(&out_pbuf);
+
+    tls_fd = g_hub_client_get_ssl_fd(G_HUB_CLIENT(client));
+
+    if (tls_fd == NULL)
+        result = false;
+
+    else
+    {
+        result = extend_packed_buffer(&out_pbuf, (uint32_t []) { DBC_CREATE_SNAPSHOT }, sizeof(uint32_t), true);
+
+        if (result)
+            result = ssl_send_packed_buffer(&out_pbuf, tls_fd);
+
+        g_hub_client_put_ssl_fd(G_HUB_CLIENT(client), tls_fd);
+
+    }
+
+    exit_packed_buffer(&out_pbuf);
+
+    return result;
+
+}
+
+
+/******************************************************************************
+*                                                                             *
+*  Paramètres  : client = client pour les accès distants à manipuler.         *
+*                id     = identifiant d'instantané à traiter.                 *
+*                rec    = programme une suppression récursive.                *
+*                                                                             *
+*  Description : Supprime un ancien instantané.                               *
+*                                                                             *
+*  Retour      : true si la commande a bien été envoyée, false sinon.         *
+*                                                                             *
+*  Remarques   : -                                                            *
+*                                                                             *
+******************************************************************************/
+
+bool g_analyst_client_remove_snapshot(GAnalystClient *client, const snapshot_id_t *id, bool rec)
+{
+    bool result;                            /* Bilan partiel à remonter    */
+    packed_buffer out_pbuf;                 /* Tampon d'émission           */
+    SSL *tls_fd;                            /* Canal de communication SSL  */
+
+    init_packed_buffer(&out_pbuf);
+
+    tls_fd = g_hub_client_get_ssl_fd(G_HUB_CLIENT(client));
+
+    if (tls_fd == NULL)
+        result = false;
+
+    else
+    {
+        result = extend_packed_buffer(&out_pbuf, (uint32_t []) { DBC_REMOVE_SNAPSHOT }, sizeof(uint32_t), true);
+
+        if (result)
+            result = pack_snapshot_id(id, &out_pbuf);
+
+        if (result)
+            result = extend_packed_buffer(&out_pbuf, (uint8_t []) { rec ? 0x1 : 0x0 }, sizeof(uint8_t), false);
+
+        if (result)
+            result = ssl_send_packed_buffer(&out_pbuf, tls_fd);
+
+        g_hub_client_put_ssl_fd(G_HUB_CLIENT(client), tls_fd);
+
+    }
+
+    exit_packed_buffer(&out_pbuf);
+
+    return result;
+
+}
diff --git a/src/analysis/db/analyst.h b/src/analysis/db/analyst.h
new file mode 100644
index 0000000..d9e90c6
--- /dev/null
+++ b/src/analysis/db/analyst.h
@@ -0,0 +1,95 @@
+
+/* Chrysalide - Outil d'analyse de fichiers binaires
+ * analyst.h - prototypes pour la connexion en analyste à un serveur Chrysalide
+ *
+ * Copyright (C) 2014-2019 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 _ANALYSIS_DB_ANALYST_H
+#define _ANALYSIS_DB_ANALYST_H
+
+
+#include <glib-object.h>
+#include <stdbool.h>
+#include <openssl/ssl.h>
+
+
+#include "client.h"
+#include "collection.h"
+#include "misc/snapshot.h"
+
+
+
+#define G_TYPE_ANALYST_CLIENT            g_analyst_client_get_type()
+#define G_ANALYST_CLIENT(obj)            (G_TYPE_CHECK_INSTANCE_CAST((obj), G_TYPE_ANALYST_CLIENT, GAnalystClient))
+#define G_IS_ANALYST_CLIENT(obj)         (G_TYPE_CHECK_INSTANCE_TYPE((obj), G_TYPE_ANALYST_CLIENT))
+#define G_ANALYST_CLIENT_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST((klass), G_TYPE_ANALYST_CLIENT, GAnalystClientClass))
+#define G_IS_ANALYST_CLIENT_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), G_TYPE_ANALYST_CLIENT))
+#define G_ANALYST_CLIENT_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS((obj), G_TYPE_ANALYST_CLIENT, GAnalystClientClass))
+
+
+/* Description de client à l'écoute (instance) */
+typedef struct _GAnalystClient GAnalystClient;
+
+/* Description de client à l'écoute (classe) */
+typedef struct _GAnalystClientClass GAnalystClientClass;
+
+
+/* Indique le type défini pour une description de client à l'écoute. */
+GType g_analyst_client_get_type(void);
+
+/* Prépare un client pour une connexion à une BD. */
+GAnalystClient *g_analyst_client_new(const char *, GList *);
+
+/* Effectue une demande de sauvegarde de l'état courant. */
+bool g_analyst_client_save(GAnalystClient *);
+
+/* Ajoute un élément à la collection d'un serveur. */
+bool g_analyst_client_add_item(GAnalystClient *, const GDbItem *);
+
+/* Active les éléments en amont d'un horodatage donné. */
+bool g_analyst_client_set_last_active(GAnalystClient *, timestamp_t);
+
+/* Fournit la liste des instantanés existants. */
+bool g_analyst_client_get_snapshots(GAnalystClient *, snapshot_info_t **, size_t *);
+
+/* Fournit l'identifiant de l'instantané courant. */
+bool g_analyst_client_get_current_snapshot(GAnalystClient *, snapshot_id_t *);
+
+/* Définit l'identifiant de l'instantané courant. */
+bool g_analyst_client_set_current_snapshot(GAnalystClient *, const snapshot_id_t *);
+
+/* Définit la désignation d'un instantané donné. */
+bool g_analyst_client_set_snapshot_name(GAnalystClient *, const snapshot_id_t *, const char *);
+
+/* Définit la description d'un instantané donné. */
+bool g_analyst_client_set_snapshot_desc(GAnalystClient *, const snapshot_id_t *, const char *);
+
+/* Restaure un ancien instantané. */
+bool g_analyst_client_restore_snapshot(GAnalystClient *, const snapshot_id_t *);
+
+/* Crée un nouvel instantané à partir d'un autre. */
+bool g_analyst_client_create_snapshot(GAnalystClient *);
+
+/* Supprime un ancien instantané. */
+bool g_analyst_client_remove_snapshot(GAnalystClient *, const snapshot_id_t *, bool);
+
+
+
+#endif  /* _ANALYSIS_DB_ANALYST_H */
diff --git a/src/analysis/db/backend-int.h b/src/analysis/db/backend-int.h
new file mode 100644
index 0000000..c74192c
--- /dev/null
+++ b/src/analysis/db/backend-int.h
@@ -0,0 +1,68 @@
+
+/* Chrysalide - Outil d'analyse de fichiers binaires
+ * backend-int.h - prototypes internes pour le suivi d'une connexion à un serveur
+ *
+ * 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 _ANALYSIS_DB_BACKEND_INT_H
+#define _ANALYSIS_DB_BACKEND_INT_H
+
+
+#include <stdbool.h>
+
+
+#include "backend.h"
+
+
+
+/* Prend en compte une connexion nouvelle d'un utilisateur. */
+typedef void (* add_backend_client_fc) (GServerBackend *, SSL *, const char *, const char *);
+
+
+/* Support pour un suivi de connexion à un serveur (instance) */
+struct _GServerBackend
+{
+    GObject parent;                         /* A laisser en premier        */
+
+    int stop_ctrl[2];                       /* Commande d'arrêt            */
+    int refresh_ctrl[2];                    /* Commande d'actualisation    */
+    GThread *process;                       /* Procédure de traitement     */
+
+};
+
+/* Support pour un suivi de connexion à un serveur (classe) */
+struct _GServerBackendClass
+{
+    GObjectClass parent;                    /* A laisser en premier        */
+
+    const char *thread_name;                /* Désignation de processus    */
+    GThreadFunc thread_func;                /* Traitement des échanges     */
+
+    add_backend_client_fc add_client;       /* Intégration d'un client     */
+
+};
+
+
+/* Met fin à un support de suivi. */
+void g_server_backend_stop(GServerBackend *);
+
+
+
+#endif  /* _ANALYSIS_DB_BACKEND_INT_H */
diff --git a/src/analysis/db/backend.c b/src/analysis/db/backend.c
new file mode 100644
index 0000000..3a5f46e
--- /dev/null
+++ b/src/analysis/db/backend.c
@@ -0,0 +1,267 @@
+
+/* Chrysalide - Outil d'analyse de fichiers binaires
+ * backend.c - suivi d'une connexion à un serveur
+ *
+ * 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 "backend.h"
+
+
+#include <unistd.h>
+
+
+#include "backend-int.h"
+#include "../../core/logs.h"
+
+
+
+/* Initialise la classe des supports pour suivi de connexion. */
+static void g_server_backend_class_init(GServerBackendClass *);
+
+/* Initialise un support pour suivi de connexion. */
+static void g_server_backend_init(GServerBackend *);
+
+/* Supprime toutes les références externes. */
+static void g_server_backend_dispose(GServerBackend *);
+
+/* Procède à la libération totale de la mémoire. */
+static void g_server_backend_finalize(GServerBackend *);
+
+
+
+/* Indique le type défini pour un Support de suivi de connexion. */
+G_DEFINE_TYPE(GServerBackend, g_server_backend, G_TYPE_OBJECT);
+
+
+/******************************************************************************
+*                                                                             *
+*  Paramètres  : klass = classe à initialiser.                                *
+*                                                                             *
+*  Description : Initialise la classe des supports pour suivi de connexion.   *
+*                                                                             *
+*  Retour      : -                                                            *
+*                                                                             *
+*  Remarques   : -                                                            *
+*                                                                             *
+******************************************************************************/
+
+static void g_server_backend_class_init(GServerBackendClass *klass)
+{
+    GObjectClass *object;                   /* Autre version de la classe  */
+
+    object = G_OBJECT_CLASS(klass);
+
+    object->dispose = (GObjectFinalizeFunc/* ! */)g_server_backend_dispose;
+    object->finalize = (GObjectFinalizeFunc)g_server_backend_finalize;
+
+}
+
+
+/******************************************************************************
+*                                                                             *
+*  Paramètres  : backend = instance à initialiser.                            *
+*                                                                             *
+*  Description : Initialise un support pour suivi de connexion.               *
+*                                                                             *
+*  Retour      : -                                                            *
+*                                                                             *
+*  Remarques   : -                                                            *
+*                                                                             *
+******************************************************************************/
+
+static void g_server_backend_init(GServerBackend *backend)
+{
+    backend->stop_ctrl[0] = -1;
+    backend->stop_ctrl[1] = -1;
+    backend->refresh_ctrl[0] = -1;
+    backend->refresh_ctrl[1] = -1;
+    backend->process = NULL;
+
+}
+
+
+/******************************************************************************
+*                                                                             *
+*  Paramètres  : backend = instance d'objet GLib à traiter.                   *
+*                                                                             *
+*  Description : Supprime toutes les références externes.                     *
+*                                                                             *
+*  Retour      : -                                                            *
+*                                                                             *
+*  Remarques   : -                                                            *
+*                                                                             *
+******************************************************************************/
+
+static void g_server_backend_dispose(GServerBackend *backend)
+{
+    G_OBJECT_CLASS(g_server_backend_parent_class)->dispose(G_OBJECT(backend));
+
+}
+
+
+/******************************************************************************
+*                                                                             *
+*  Paramètres  : backend = instance d'objet GLib à traiter.                   *
+*                                                                             *
+*  Description : Procède à la libération totale de la mémoire.                *
+*                                                                             *
+*  Retour      : -                                                            *
+*                                                                             *
+*  Remarques   : -                                                            *
+*                                                                             *
+******************************************************************************/
+
+static void g_server_backend_finalize(GServerBackend *backend)
+{
+    G_OBJECT_CLASS(g_server_backend_parent_class)->finalize(G_OBJECT(backend));
+
+}
+
+
+/******************************************************************************
+*                                                                             *
+*  Paramètres  : backend   = support pour le suivi d'une connexion.           *
+*                fd        = canal de communication réseau ouvert.            *
+*                peer_name = désignation de la connexion.                     *
+*                                                                             *
+*  Description : Prend en compte une connexion nouvelle d'un utilisateur.     *
+*                                                                             *
+*  Retour      : -                                                            *
+*                                                                             *
+*  Remarques   : -                                                            *
+*                                                                             *
+******************************************************************************/
+
+void g_server_backend_add_client(GServerBackend *backend, SSL *fd, const char *peer_name)
+{
+    GServerBackendClass *class;             /* Classe du support manipulé  */
+    X509 *peer_cert;                        /* Certificat présenté         */
+    char *user;                             /* Nom d'utilisateur associé   */
+    int ret;                                /* Bilan d'un appel            */
+    ssize_t sent;                           /* Quantité de données émises  */
+
+    class = G_SERVER_BACKEND_GET_CLASS(backend);
+
+    /* Ajout dans la liste officielle */
+
+    peer_cert = SSL_get_peer_certificate(fd);
+
+    user = X509_NAME_oneline(X509_get_subject_name(peer_cert), NULL, -1);
+
+    X509_free(peer_cert);
+
+    class->add_client(backend, fd, peer_name, user);
+
+    free(user);
+
+    /* Démarrage ou redémarrage du processus d'écoute */
+
+    if (backend->process == NULL)
+    {
+        ret = pipe(backend->stop_ctrl);
+        if (ret != 0)
+        {
+            LOG_ERROR_N("pipe");
+            g_object_unref(G_OBJECT(backend));
+            goto sys_error;
+        }
+
+        ret = pipe(backend->refresh_ctrl);
+        if (ret != 0)
+        {
+            LOG_ERROR_N("pipe");
+            g_object_unref(G_OBJECT(backend));
+            goto sys_error;
+        }
+
+        backend->process = g_thread_new(class->thread_name, class->thread_func, backend);
+
+ sys_error:
+
+        ;
+
+    }
+
+    else
+    {
+        sent = write(backend->refresh_ctrl[1], "\xf0", 1);
+        if (sent != 1) LOG_ERROR_N("write");
+    }
+
+}
+
+
+/******************************************************************************
+*                                                                             *
+*  Paramètres  : backend = support pour le suivi d'une connexion.             *
+*                                                                             *
+*  Description : Met fin à un support de suivi.                               *
+*                                                                             *
+*  Retour      : -                                                            *
+*                                                                             *
+*  Remarques   : -                                                            *
+*                                                                             *
+******************************************************************************/
+
+void g_server_backend_stop(GServerBackend *backend)
+{
+    GThread *process;                       /* Procédure à terminer        */
+    int ret;                                /* Bilan d'un appel            */
+    ssize_t sent;                           /* Quantité de données émises  */
+
+    /* Gestion du double appel */
+
+    if (backend->process == NULL)
+        return;
+
+    process = backend->process;
+
+    backend->process = NULL;
+
+    /* Ordre d'arrêt */
+
+    if (g_thread_self() != process)
+    {
+        sent = write(backend->stop_ctrl[1], "\xf0", 1);
+        if (sent != 1) LOG_ERROR_N("write");
+
+        g_thread_join(process);
+
+    }
+
+    /* Fermeture des flux */
+
+    ret = close(backend->stop_ctrl[0]);
+    if (ret == -1) LOG_ERROR_N("close");
+    backend->stop_ctrl[0] = -1;
+
+    ret = close(backend->stop_ctrl[1]);
+    if (ret == -1) LOG_ERROR_N("close");
+    backend->stop_ctrl[1] = -1;
+
+    ret = close(backend->refresh_ctrl[0]);
+    if (ret == -1) LOG_ERROR_N("close");
+    backend->refresh_ctrl[0] = -1;
+
+    ret = close(backend->refresh_ctrl[1]);
+    if (ret == -1) LOG_ERROR_N("close");
+    backend->refresh_ctrl[1] = -1;
+
+}
diff --git a/src/analysis/db/backend.h b/src/analysis/db/backend.h
new file mode 100644
index 0000000..0aad651
--- /dev/null
+++ b/src/analysis/db/backend.h
@@ -0,0 +1,56 @@
+
+/* Chrysalide - Outil d'analyse de fichiers binaires
+ * backend.h - prototypes pour le suivi d'une connexion à un serveur
+ *
+ * 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 _ANALYSIS_DB_BACKEND_H
+#define _ANALYSIS_DB_BACKEND_H
+
+
+#include <glib-object.h>
+#include <openssl/ssl.h>
+
+
+
+#define G_TYPE_SERVER_BACKEND            g_server_backend_get_type()
+#define G_SERVER_BACKEND(obj)            (G_TYPE_CHECK_INSTANCE_CAST((obj), G_TYPE_SERVER_BACKEND, GServerBackend))
+#define G_IS_SERVER_BACKEND(obj)         (G_TYPE_CHECK_INSTANCE_TYPE((obj), G_TYPE_SERVER_BACKEND))
+#define G_SERVER_BACKEND_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST((klass), G_TYPE_SERVER_BACKEND, GServerBackendClass))
+#define G_IS_SERVER_BACKEND_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), G_TYPE_SERVER_BACKEND))
+#define G_SERVER_BACKEND_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS((obj), G_TYPE_SERVER_BACKEND, GServerBackendClass))
+
+
+/* Support pour un suivi de connexion à un serveur (instance) */
+typedef struct _GServerBackend GServerBackend;
+
+/* Support pour un suivi de connexion à un serveur (classe) */
+typedef struct _GServerBackendClass GServerBackendClass;
+
+
+/* Indique le type défini pour un Support de suivi de connexion. */
+GType g_server_backend_get_type(void);
+
+/* Prend en compte une connexion nouvelle d'un utilisateur. */
+void g_server_backend_add_client(GServerBackend *, SSL *, const char *);
+
+
+
+#endif  /* _ANALYSIS_DB_BACKEND_H */
diff --git a/src/analysis/db/cdb.c b/src/analysis/db/cdb.c
index 9e24f84..8d589b3 100644
--- a/src/analysis/db/cdb.c
+++ b/src/analysis/db/cdb.c
@@ -1,6 +1,6 @@
 
 /* Chrysalide - Outil d'analyse de fichiers binaires
- * cdb.h - prototypes pour la manipulation des archives au format CDB
+ * cdb.c - manipulation des archives au format CDB
  *
  * Copyright (C) 2014-2019 Cyrille Bagard
  *
@@ -29,7 +29,6 @@
 #include <malloc.h>
 #include <poll.h>
 #include <pthread.h>
-#include <signal.h>
 #include <stdint.h>
 #include <stdio.h>
 #include <stdlib.h>
@@ -42,6 +41,7 @@
 #include <config.h>
 
 
+#include "backend-int.h"
 #include "collection.h"
 #include "protocol.h"
 #include "snapshot.h"
@@ -61,7 +61,8 @@
 /* Informations relatives à un client */
 typedef struct _cdb_client
 {
-    SSL *ssl_fd;                            /* Canal de communication      */
+    SSL *tls_fd;                            /* Canal de communication      */
+    char *peer_name;                        /* Désignation du correspondant*/
     char *user;                             /* Utilisateur à l'autre bout  */
 
     uint64_t last_time;                     /* Date de dernier envoi       */
@@ -89,11 +90,6 @@ struct _GCdbArchive
     size_t count;                           /* Quantité de clients         */
     GMutex clients_access;                  /* Verrou pour l'accès         */
 
-    GThread *process;                       /* Procédure de traitement     */
-    GMutex id_access;                       /* Accès à l'identifiant       */
-    GCond id_cond;                          /* Condition d'attente         */
-    pthread_t process_id;                   /* Identifiant de la procédure */
-
 };
 
 /* Description d'une archive d'éléments utilisateur (classe) */
@@ -147,6 +143,15 @@ static void on_collection_extended(GDbCollection *, GDbItem *, GCdbArchive *);
 /* Assure le traitement des requêtes de clients. */
 static void *g_cdb_archive_process(GCdbArchive *);
 
+/* Prend en compte une connexion nouvelle d'un utilisateur. */
+static void g_cdb_archive_add_client(GCdbArchive *, SSL *, const char *, const char *);
+
+/* Dissocie un utilisateur de l'archive. */
+static void _g_cdb_archive_remove_client(GCdbArchive *, size_t);
+
+/* Dissocie un utilisateur de l'archive. */
+static void g_cdb_archive_remove_client(GCdbArchive *, size_t);
+
 /* Envoie un paquet de données constitué à tous les clients. */
 static void g_cdb_archive_send_reply_to_all_clients(GCdbArchive *, packed_buffer *);
 
@@ -156,12 +161,6 @@ static bool g_cdb_archive_send_snapshot_update(GCdbArchive *, packed_buffer *);
 /* Envoie à tous les clients le nouvel instantané courant. */
 static bool g_cdb_archive_send_snapshot_change(GCdbArchive *, packed_buffer *);
 
-/* Dissocie un utilisateur de l'archive. */
-static void _g_cdb_archive_remove_client(GCdbArchive *, size_t);
-
-/* Dissocie un utilisateur de l'archive. */
-static void g_cdb_archive_remove_client(GCdbArchive *, size_t);
-
 
 
 /* ---------------------------------------------------------------------------------- */
@@ -170,7 +169,7 @@ static void g_cdb_archive_remove_client(GCdbArchive *, size_t);
 
 
 /* Indique le type défini pour une une archive d'éléments utilisateur. */
-G_DEFINE_TYPE(GCdbArchive, g_cdb_archive, G_TYPE_OBJECT);
+G_DEFINE_TYPE(GCdbArchive, g_cdb_archive, G_TYPE_SERVER_BACKEND);
 
 
 /******************************************************************************
@@ -188,12 +187,20 @@ G_DEFINE_TYPE(GCdbArchive, g_cdb_archive, G_TYPE_OBJECT);
 static void g_cdb_archive_class_init(GCdbArchiveClass *klass)
 {
     GObjectClass *object;                   /* Autre version de la classe  */
+    GServerBackendClass *backend;           /* Classe parente              */
 
     object = G_OBJECT_CLASS(klass);
 
     object->dispose = (GObjectFinalizeFunc/* ! */)g_cdb_archive_dispose;
     object->finalize = (GObjectFinalizeFunc)g_cdb_archive_finalize;
 
+    backend = G_SERVER_BACKEND_CLASS(klass);
+
+    backend->thread_name = "cdb_archiver";
+    backend->thread_func = (GThreadFunc)g_cdb_archive_process;
+
+    backend->add_client = (add_backend_client_fc)g_cdb_archive_add_client;
+
 }
 
 
@@ -213,6 +220,7 @@ static void g_cdb_archive_init(GCdbArchive *archive)
 {
     setup_empty_rle_string(&archive->hash);
 
+    archive->filename = NULL;
     archive->tmpdir = NULL;
 
     archive->collections = create_collections_list();
@@ -221,9 +229,6 @@ static void g_cdb_archive_init(GCdbArchive *archive)
 
     g_mutex_init(&archive->clients_access);
 
-    g_mutex_init(&archive->id_access);
-    g_cond_init(&archive->id_cond);
-
 }
 
 
@@ -241,10 +246,9 @@ static void g_cdb_archive_init(GCdbArchive *archive)
 
 static void g_cdb_archive_dispose(GCdbArchive *archive)
 {
-    g_clear_object(&archive->snapshot);
+    g_server_backend_stop(G_SERVER_BACKEND(archive));
 
-    g_cond_clear(&archive->id_cond);
-    g_mutex_clear(&archive->id_access);
+    g_clear_object(&archive->snapshot);
 
     g_mutex_clear(&archive->clients_access);
 
@@ -284,7 +288,8 @@ static void g_cdb_archive_finalize(GCdbArchive *archive)
     if (archive->xml_desc != NULL)
         free(archive->xml_desc);
 
-    free(archive->filename);
+    if (archive->filename != NULL)
+        free(archive->filename);
 
     if (archive->tmpdir != NULL)
         free(archive->tmpdir);
@@ -804,7 +809,7 @@ static void on_collection_extended(GDbCollection *collec, GDbItem *item, GCdbArc
 
     for (i = 0; i < archive->count && status; i++)
     {
-        status = ssl_send_packed_buffer(&pbuf, archive->clients[i].ssl_fd);
+        status = ssl_send_packed_buffer(&pbuf, archive->clients[i].tls_fd);
 
         if (!status)
             LOG_ERROR(LMT_ERROR, _("Failed to send some DB update"));
@@ -832,6 +837,7 @@ static void on_collection_extended(GDbCollection *collec, GDbItem *item, GCdbArc
 
 static void *g_cdb_archive_process(GCdbArchive *archive)
 {
+    GServerBackend *base;                   /* Base de l'instance          */
     struct pollfd *fds;                     /* Surveillance des flux       */
     nfds_t nfds;                            /* Quantité de ces flux        */
     nfds_t i;                               /* Boucle de parcours          */
@@ -846,14 +852,7 @@ static void *g_cdb_archive_process(GCdbArchive *archive)
     bool reload;                            /* Besoin de rechargement      */
     char *msg;                              /* Erreur à faire remonter     */
 
-    void interrupt_poll_with_sigusr1(int sig) { };
-
-    signal(SIGUSR1, interrupt_poll_with_sigusr1);
-
-    g_mutex_lock(&archive->id_access);
-    archive->process_id = pthread_self();
-    g_cond_signal(&archive->id_cond);
-    g_mutex_unlock(&archive->id_access);
+    base = G_SERVER_BACKEND(archive);
 
     fds = NULL;
 
@@ -863,35 +862,46 @@ static void *g_cdb_archive_process(GCdbArchive *archive)
 
         g_mutex_lock(&archive->clients_access);
 
-        nfds = archive->count;
+        nfds = archive->count + 2;
         fds = realloc(fds, nfds * sizeof(struct pollfd));
 
-        for (i = 0; i < nfds; i++)
+        for (i = 0; i < (nfds - 2); i++)
         {
-            fds[i].fd = SSL_get_fd(archive->clients[i].ssl_fd);
+            fds[i].fd = SSL_get_fd(archive->clients[i].tls_fd);
             fds[i].events = POLLIN | POLLPRI;
         }
 
         g_mutex_unlock(&archive->clients_access);
 
-        if (nfds == 0)
+        if (nfds == 2)
             goto gcap_no_more_clients;
 
+        fds[nfds - 2].fd = base->stop_ctrl[0];
+        fds[nfds - 2].events = POLLIN | POLLPRI;
+
+        fds[nfds - 1].fd = base->refresh_ctrl[0];
+        fds[nfds - 1].events = POLLIN | POLLPRI;
+
         /* Lancement d'une phase de surveillance */
 
         ret = poll(fds, nfds, -1);
         if (ret == -1)
         {
-            if (errno == EINTR) continue;
-
             LOG_ERROR_N("poll");
             break;
-
         }
 
+        /* Demande expresse d'arrêt des procédures */
+        if (fds[nfds - 2].revents)
+            break;
+
+        /* Demande d'actualisation */
+        if (fds[nfds - 1].revents)
+            continue;
+
         /* Traitement des requêtes reçues */
 
-        for (i = 0; i < nfds; i++)
+        for (i = 0; i < (nfds - 1); i++)
         {
             /* Le canal est fermé, une sortie doit être demandée... */
             if (fds[i].revents & POLLNVAL)
@@ -910,7 +920,7 @@ static void *g_cdb_archive_process(GCdbArchive *archive)
             {
                 init_packed_buffer(&in_pbuf);
 
-                status = ssl_recv_packed_buffer(&in_pbuf, archive->clients[i].ssl_fd);
+                status = ssl_recv_packed_buffer(&in_pbuf, archive->clients[i].tls_fd);
                 if (!status) goto gcap_bad_exchange;
 
  next_command:
@@ -935,7 +945,7 @@ static void *g_cdb_archive_process(GCdbArchive *archive)
                         status = extend_packed_buffer(&out_pbuf, (uint32_t []) { error }, sizeof(uint32_t), true);
                         if (!status) goto gcap_bad_reply;
 
-                        status = ssl_send_packed_buffer(&out_pbuf, archive->clients[i].ssl_fd);
+                        status = ssl_send_packed_buffer(&out_pbuf, archive->clients[i].tls_fd);
                         if (!status) goto gcap_bad_reply;
 
                         exit_packed_buffer(&out_pbuf);
@@ -976,7 +986,7 @@ static void *g_cdb_archive_process(GCdbArchive *archive)
                         status = extend_packed_buffer(&out_pbuf, (uint8_t []) { 0x0 }, sizeof(uint8_t), true);
                         if (!status) goto gcap_bad_reply;
 
-                        status = ssl_send_packed_buffer(&out_pbuf, archive->clients[i].ssl_fd);
+                        status = ssl_send_packed_buffer(&out_pbuf, archive->clients[i].tls_fd);
                         if (!status) goto gcap_bad_reply;
 
                         exit_packed_buffer(&out_pbuf);
@@ -998,7 +1008,7 @@ static void *g_cdb_archive_process(GCdbArchive *archive)
                                                                 &in_pbuf, &out_pbuf, archive->db);
                         if (!status) goto gcap_bad_reply;
 
-                        status = ssl_send_packed_buffer(&out_pbuf, archive->clients[i].ssl_fd);
+                        status = ssl_send_packed_buffer(&out_pbuf, archive->clients[i].tls_fd);
                         if (!status) goto gcap_bad_reply;
 
                         exit_packed_buffer(&out_pbuf);
@@ -1010,7 +1020,7 @@ static void *g_cdb_archive_process(GCdbArchive *archive)
                         if (!g_cdb_archive_send_snapshot_update(archive, &out_pbuf))
                             goto critical_error;
 
-                        status = ssl_send_packed_buffer(&out_pbuf, archive->clients[i].ssl_fd);
+                        status = ssl_send_packed_buffer(&out_pbuf, archive->clients[i].tls_fd);
                         if (!status) goto gcap_bad_reply;
 
                         exit_packed_buffer(&out_pbuf);
@@ -1022,7 +1032,7 @@ static void *g_cdb_archive_process(GCdbArchive *archive)
                         if (!g_cdb_archive_send_snapshot_change(archive, &out_pbuf))
                             goto critical_error;
 
-                        status = ssl_send_packed_buffer(&out_pbuf, archive->clients[i].ssl_fd);
+                        status = ssl_send_packed_buffer(&out_pbuf, archive->clients[i].tls_fd);
                         if (!status) goto gcap_bad_reply;
 
                         exit_packed_buffer(&out_pbuf);
@@ -1185,12 +1195,7 @@ static void *g_cdb_archive_process(GCdbArchive *archive)
 
  gcap_no_more_clients:
 
-    archive->process = NULL;
-
-    g_mutex_lock(&archive->id_access);
-    archive->process_id = 0;
-    g_cond_signal(&archive->id_cond);
-    g_mutex_unlock(&archive->id_access);
+    g_server_backend_stop(G_SERVER_BACKEND(archive));
 
     if (fds != NULL)
         free(fds);
@@ -1202,6 +1207,115 @@ static void *g_cdb_archive_process(GCdbArchive *archive)
 
 /******************************************************************************
 *                                                                             *
+*  Paramètres  : archive   = support pour le suivi d'une connexion.           *
+*                fd        = canal de communication réseau ouvert.            *
+*                peer_name = désignation de la connexion.                     *
+*                user      = désignation de l'utilisateur de la connexion.    *
+*                                                                             *
+*  Description : Prend en compte une connexion nouvelle d'un utilisateur.     *
+*                                                                             *
+*  Retour      : -                                                            *
+*                                                                             *
+*  Remarques   : -                                                            *
+*                                                                             *
+******************************************************************************/
+
+static void g_cdb_archive_add_client(GCdbArchive *archive, SSL *fd, const char *peer_name, const char *user)
+{
+    cdb_client *client;                     /* Nouvelle fiche d'entité     */
+
+    /**
+     * La situation est un peu compliquée lors de l'accueil d'un nouveau client :
+     *
+     *    - soit on envoie tous les éléments à prendre en compte, mais on doit
+     *      bloquer la base de données jusqu'à l'intégration pleine et entière
+     *      du client, afin que ce dernier ne loupe pas l'envoi d'un nouvel
+     *      élément entre temps.
+     *
+     *    - soit on intègre le client et celui ci demande des mises à jour
+     *      collection par collection ; c'est également à lui que revient le rejet
+     *      des éléments envoyés en solitaires avant la réception de la base
+     *      complète.
+     *
+     * On fait le choix du second scenario ici, du fait de la difficulté
+     * de maîtriser facilement la reconstitution d'une liste de clients dans
+     * g_cdb_archive_process() depuis un autre flot d'exécution.
+     */
+
+    g_mutex_lock(&archive->clients_access);
+
+    archive->clients = realloc(archive->clients, ++archive->count * sizeof(cdb_client));
+
+    client = &archive->clients[archive->count - 1];
+
+    client->tls_fd = fd;
+    client->peer_name = strdup(peer_name);
+    client->user = strdup(user);
+
+    g_mutex_unlock(&archive->clients_access);
+
+}
+
+
+/******************************************************************************
+*                                                                             *
+*  Paramètres  : archive = archive à connecter avec un utilisateur.           *
+*                index   = indice de l'utilisateur concerné.                  *
+*                                                                             *
+*  Description : Dissocie un utilisateur de l'archive.                        *
+*                                                                             *
+*  Retour      : -                                                            *
+*                                                                             *
+*  Remarques   : -                                                            *
+*                                                                             *
+******************************************************************************/
+
+static void _g_cdb_archive_remove_client(GCdbArchive *archive, size_t index)
+{
+    cdb_client *client;                     /* Client à traiter            */
+
+    assert(!g_mutex_trylock(&archive->clients_access));
+
+    client = &archive->clients[index];
+
+    SSL_free(client->tls_fd);
+    free(client->user);
+
+    if ((index + 1) < archive->count)
+        memmove(&archive->clients[index], &archive->clients[index + 1],
+                (archive->count - index - 1) * sizeof(cdb_client));
+
+    archive->clients = realloc(archive->clients, --archive->count * sizeof(cdb_client));
+
+}
+
+
+/******************************************************************************
+*                                                                             *
+*  Paramètres  : archive = archive à connecter avec un utilisateur.           *
+*                index   = indice de l'utilisateur concerné.                  *
+*                                                                             *
+*  Description : Dissocie un utilisateur de l'archive.                        *
+*                                                                             *
+*  Retour      : -                                                            *
+*                                                                             *
+*  Remarques   : -                                                            *
+*                                                                             *
+******************************************************************************/
+
+static void g_cdb_archive_remove_client(GCdbArchive *archive, size_t index)
+{
+    g_mutex_lock(&archive->clients_access);
+
+    _g_cdb_archive_remove_client(archive, index);
+
+    g_mutex_unlock(&archive->clients_access);
+
+}
+
+
+/******************************************************************************
+*                                                                             *
 *  Paramètres  : archive = archive à connecter avec un utilisateur.           *
 *                pbuf    = paquet de données à émettre.                       *
 *                                                                             *
@@ -1222,7 +1336,7 @@ static void g_cdb_archive_send_reply_to_all_clients(GCdbArchive *archive, packed
 
     for (i = 0; i < archive->count; i++)
     {
-        status = ssl_send_packed_buffer(pbuf, archive->clients[i].ssl_fd);
+        status = ssl_send_packed_buffer(pbuf, archive->clients[i].tls_fd);
         if (!status)
         {
             log_variadic_message(LMT_ERROR, _("Error while replying to client %zu"), i);
@@ -1337,134 +1451,3 @@ static bool g_cdb_archive_send_snapshot_change(GCdbArchive *archive, packed_buff
     return result;
 
 }
-
-
-/******************************************************************************
-*                                                                             *
-*  Paramètres  : archive = archive à connecter avec un utilisateur.           *
-*                fd      = canal de communication réseau ouvert.              *
-*                                                                             *
-*  Description : Associe un nouvel utilisateur à l'archive.                   *
-*                                                                             *
-*  Retour      : -                                                            *
-*                                                                             *
-*  Remarques   : -                                                            *
-*                                                                             *
-******************************************************************************/
-
-void g_cdb_archive_add_client(GCdbArchive *archive, SSL *fd)
-{
-    X509 *peer_cert;                        /* Certificat présenté         */
-    volatile pthread_t *process_id;         /* Identifiant de la procédure */
-
-    /**
-     * La situation est un peu compliquée lors de l'accueil d'un nouveau client :
-     *
-     *    - soit on envoie tous les éléments à prendre en compte, mais on doit
-     *      bloquer la base de données jusqu'à l'intégration pleine et entière
-     *      du client, afin que ce dernier ne loupe pas l'envoi d'un nouvel
-     *      élément entre temps.
-     *
-     *    - soit on intègre le client et celui ci demande des mises à jour
-     *      collection par collection ; c'est également à lui qui revient le rejet
-     *      des éléments envoyés en solitaires avant la réception de la base
-     *      complète.
-     *
-     * On fait le choix du second scenario ici, du fait de la difficulté
-     * de maîtriser facilement la reconstitution d'une liste de clients dans
-     * g_cdb_archive_process() depuis un autre flot d'exécution.
-     */
-
-    /* Ajout dans la liste officielle */
-
-    g_mutex_lock(&archive->clients_access);
-
-    archive->clients = realloc(archive->clients, ++archive->count * sizeof(cdb_client));
-
-    archive->clients[archive->count - 1].ssl_fd = fd;
-
-    peer_cert = SSL_get_peer_certificate(fd);
-
-    archive->clients[archive->count - 1].user = X509_NAME_oneline(X509_get_subject_name(peer_cert), NULL, -1);
-
-    X509_free(peer_cert);
-
-    /* Démarrage ou redémarrage du processus d'écoute */
-
-    if (archive->process == NULL)
-    {
-        archive->process = g_thread_new("cdb_process", (GThreadFunc)g_cdb_archive_process, archive);
-
-        /* On attend que le processus parallèle soit prêt */
-
-        process_id = &archive->process_id;
-
-        g_mutex_lock(&archive->id_access);
-        while (process_id == 0)
-            g_cond_wait(&archive->id_cond, &archive->id_access);
-        g_mutex_unlock(&archive->id_access);
-
-    }
-    else
-        pthread_kill(archive->process_id, SIGUSR1);
-
-    g_mutex_unlock(&archive->clients_access);
-
-}
-
-
-/******************************************************************************
-*                                                                             *
-*  Paramètres  : archive = archive à connecter avec un utilisateur.           *
-*                index   = indice de l'utilisateur concerné.                  *
-*                                                                             *
-*  Description : Dissocie un utilisateur de l'archive.                        *
-*                                                                             *
-*  Retour      : -                                                            *
-*                                                                             *
-*  Remarques   : -                                                            *
-*                                                                             *
-******************************************************************************/
-
-static void _g_cdb_archive_remove_client(GCdbArchive *archive, size_t index)
-{
-    cdb_client *client;                     /* Client à traiter            */
-
-    assert(!g_mutex_trylock(&archive->clients_access));
-
-    client = &archive->clients[index];
-
-    SSL_free(client->ssl_fd);
-    free(client->user);
-
-    if ((index + 1) < archive->count)
-        memmove(&archive->clients[index], &archive->clients[index + 1],
-                (archive->count - index - 1) * sizeof(cdb_client));
-
-    archive->clients = realloc(archive->clients, --archive->count * sizeof(cdb_client));
-
-}
-
-
-/******************************************************************************
-*                                                                             *
-*  Paramètres  : archive = archive à connecter avec un utilisateur.           *
-*                index   = indice de l'utilisateur concerné.                  *
-*                                                                             *
-*  Description : Dissocie un utilisateur de l'archive.                        *
-*                                                                             *
-*  Retour      : -                                                            *
-*                                                                             *
-*  Remarques   : -                                                            *
-*                                                                             *
-******************************************************************************/
-
-static void g_cdb_archive_remove_client(GCdbArchive *archive, size_t index)
-{
-    g_mutex_lock(&archive->clients_access);
-
-    _g_cdb_archive_remove_client(archive, index);
-
-    g_mutex_unlock(&archive->clients_access);
-
-}
diff --git a/src/analysis/db/cdb.h b/src/analysis/db/cdb.h
index 58bb781..2dd8118 100644
--- a/src/analysis/db/cdb.h
+++ b/src/analysis/db/cdb.h
@@ -64,13 +64,4 @@ int g_cdb_archive_compare_hash(const GCdbArchive *, const rle_string *);
 
 
 
-
-
-/* Associe un nouvel utilisateur à l'archive. */
-void g_cdb_archive_add_client(GCdbArchive *, SSL *);
-
-
-
-
-
 #endif  /* _ANALYSIS_DB_CDB_H */
diff --git a/src/analysis/db/client-int.h b/src/analysis/db/client-int.h
new file mode 100644
index 0000000..83c5039
--- /dev/null
+++ b/src/analysis/db/client-int.h
@@ -0,0 +1,71 @@
+
+/* Chrysalide - Outil d'analyse de fichiers binaires
+ * client.h - prototypes pour la connexion à un serveur Chrysalide
+ *
+ * Copyright (C) 2014-2019 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 _ANALYSIS_DB_CLIENT_INT_H
+#define _ANALYSIS_DB_CLIENT_INT_H
+
+
+#include "client.h"
+
+
+
+/* Description de client à l'écoute (instance) */
+struct _GHubClient
+{
+    GObject parent;                         /* A laisser en premier        */
+
+    char *working;                          /* Répertoire de travail       */
+
+    SSL_CTX *tls_ctx;                       /* Contexte du chiffrement     */
+
+    int fd;                                 /* Canal de communication      */
+    SSL *tls_fd;                            /* Même canal, mais sécurisé   */
+    char *desc;                             /* Description du lien         */
+
+    GMutex sending_lock;                    /* Concurrence des envois      */
+    int stop_ctrl[2];                       /* Commande d'arrêt            */
+    GThread *update;                        /* Procédure de traitement     */
+
+};
+
+/* Description de client à l'écoute (classe) */
+struct _GHubClientClass
+{
+    GObjectClass parent;                    /* A laisser en premier        */
+
+    uint32_t role;                          /* Rôle associé aux clients    */
+    GThreadFunc recv_func;                  /* Réception de données        */
+
+};
+
+
+
+/* Identifie le canal de communication pour envois au serveur. */
+SSL *g_hub_client_get_ssl_fd(GHubClient *);
+
+/* Marque le canal de communication comme disponible. */
+void g_hub_client_put_ssl_fd(GHubClient *, SSL *);
+
+
+
+#endif  /* _ANALYSIS_DB_CLIENT_INT_H */
diff --git a/src/analysis/db/client.c b/src/analysis/db/client.c
index ba0fec8..1f47eea 100644
--- a/src/analysis/db/client.c
+++ b/src/analysis/db/client.c
@@ -37,6 +37,7 @@
 #include <i18n.h>
 
 
+#include "client-int.h"
 #include "auth.h"
 #include "protocol.h"
 #include "misc/rlestr.h"
@@ -47,59 +48,6 @@
 
 
 
-/* Format générique des adresses de connexion */
-typedef union _gen_sockaddr_t
-{
-    struct sockaddr_in inet4_addr;          /* Adresse d'écoute IPv4       */
-    struct sockaddr_in6 inet6_addr;         /* Adresse d'écoute IPv6       */
-    struct sockaddr inet_4_6_addr;          /* Adresse d'écoute IPv4/6     */
-
-} gen_sockaddr_t;
-
-
-/* Description de client à l'écoute (instance) */
-struct _GHubClient
-{
-    GObject parent;                         /* A laisser en premier        */
-
-    rle_string hash;                        /* Empreinte du binaire lié    */
-    GList *collections;                     /* Collections d'un binaire    */
-
-    char *working;                          /* Répertoire de travail       */
-
-    SSL_CTX *tls_ctx;                       /* Contexte du chiffrement     */
-
-    int fd;                                 /* Canal de communication      */
-    SSL *tls_fd;                            /* Même canal, mais sécurisé   */
-    char *desc;                             /* Description du lien         */
-
-    GMutex sending_lock;                    /* Concurrence des envois      */
-    bool can_get_updates;                   /* Réception de maj possibles ?*/
-    GThread *update;                        /* Procédure de traitement     */
-
-    snapshot_info_t *snapshots;             /* Liste des instantanés       */
-    size_t snap_count;                      /* Taille de cette liste       */
-    GMutex snap_lock;                       /* Concurrence des accès       */
-
-    snapshot_id_t current;                  /* Instantané courant          */
-    bool has_current;                       /* Validité de l'identifiant   */
-    GMutex cur_lock;                        /* Concurrence des accès       */
-
-};
-
-/* Description de client à l'écoute (classe) */
-struct _GHubClientClass
-{
-    GObjectClass parent;                    /* A laisser en premier        */
-
-    /* Signaux */
-
-    void (* snapshots_updated) (GHubClient *);
-    void (* snapshot_changed) (GHubClient *);
-
-};
-
-
 /* Initialise la classe des descriptions de fichier binaire. */
 static void g_hub_client_class_init(GHubClientClass *);
 
@@ -112,23 +60,17 @@ static void g_hub_client_dispose(GHubClient *);
 /* Procède à la libération totale de la mémoire. */
 static void g_hub_client_finalize(GHubClient *);
 
-/* Démarre réellement la connexion à la base de données. */
-static bool g_hub_client_start_common(GHubClient *, char *);
-
-/* Assure l'accueil des nouvelles mises à jour. */
-static void *g_hub_client_update(GHubClient *);
-
-/* Met à jour la liste des instantanés courants. */
-static bool g_hub_client_update_snapshots(GHubClient *, packed_buffer *);
-
-/* Met à jour l'identifiant de l'instantané courant. */
-static bool g_hub_client_update_current_snapshot(GHubClient *, packed_buffer *);
+/* Format générique des adresses de connexion */
+typedef union _gen_sockaddr_t
+{
+    struct sockaddr_in inet4_addr;          /* Adresse d'écoute IPv4       */
+    struct sockaddr_in6 inet6_addr;         /* Adresse d'écoute IPv6       */
+    struct sockaddr inet_4_6_addr;          /* Adresse d'écoute IPv4/6     */
 
-/* Identifie le canal de communication pour envois au serveur. */
-static SSL *g_hub_client_get_ssl_fd(GHubClient *);
+} gen_sockaddr_t;
 
-/* Marque le canal de communication comme disponible. */
-static void g_hub_client_put_ssl_fd(GHubClient *, SSL *);
+/* Démarre réellement la connexion à la base de données. */
+static bool g_hub_client_start_common(GHubClient *, char *);
 
 
 
@@ -157,22 +99,6 @@ static void g_hub_client_class_init(GHubClientClass *klass)
     object->dispose = (GObjectFinalizeFunc/* ! */)g_hub_client_dispose;
     object->finalize = (GObjectFinalizeFunc)g_hub_client_finalize;
 
-    g_signal_new("snapshots-updated",
-                 G_TYPE_HUB_CLIENT,
-                 G_SIGNAL_RUN_LAST,
-                 G_STRUCT_OFFSET(GHubClientClass, snapshots_updated),
-                 NULL, NULL,
-                 g_cclosure_marshal_VOID__VOID,
-                 G_TYPE_NONE, 0);
-
-    g_signal_new("snapshot-changed",
-                 G_TYPE_HUB_CLIENT,
-                 G_SIGNAL_RUN_LAST,
-                 G_STRUCT_OFFSET(GHubClientClass, snapshot_changed),
-                 NULL, NULL,
-                 g_cclosure_marshal_VOID__VOID,
-                 G_TYPE_NONE, 0);
-
 }
 
 
@@ -190,9 +116,6 @@ static void g_hub_client_class_init(GHubClientClass *klass)
 
 static void g_hub_client_init(GHubClient *client)
 {
-    setup_empty_rle_string(&client->hash);
-    client->collections = NULL;
-
     client->working = NULL;
 
     client->tls_ctx = NULL;
@@ -202,17 +125,10 @@ static void g_hub_client_init(GHubClient *client)
     client->desc = NULL;
 
     g_mutex_init(&client->sending_lock);
-    client->can_get_updates = false;
+    client->stop_ctrl[0] = -1;
+    client->stop_ctrl[1] = -1;
     client->update = NULL;
 
-    client->snapshots = NULL;
-    client->snap_count = 0;
-    g_mutex_init(&client->snap_lock);
-
-    setup_empty_snapshot_id(&client->current);
-    client->has_current = false;
-    g_mutex_init(&client->cur_lock);
-
 }
 
 
@@ -230,12 +146,6 @@ static void g_hub_client_init(GHubClient *client)
 
 static void g_hub_client_dispose(GHubClient *client)
 {
-    g_hub_client_stop(client);
-
-    g_mutex_clear(&client->cur_lock);
-
-    g_mutex_clear(&client->snap_lock);
-
     g_mutex_clear(&client->sending_lock);
 
     G_OBJECT_CLASS(g_hub_client_parent_class)->dispose(G_OBJECT(client));
@@ -257,10 +167,6 @@ static void g_hub_client_dispose(GHubClient *client)
 
 static void g_hub_client_finalize(GHubClient *client)
 {
-    size_t i;                               /* Boucle de parcours          */
-
-    unset_rle_string(&client->hash);
-
     if (client->working != NULL)
         free(client->working);
 
@@ -270,15 +176,6 @@ static void g_hub_client_finalize(GHubClient *client)
     if (client->desc != NULL)
         free(client->desc);
 
-    if (client->snapshots != NULL)
-    {
-        for (i = 0; i < client->snap_count; i++)
-            exit_snapshot_info(&client->snapshots[i]);
-
-        free(client->snapshots);
-
-    }
-
     G_OBJECT_CLASS(g_hub_client_parent_class)->finalize(G_OBJECT(client));
 
 }
@@ -286,33 +183,6 @@ static void g_hub_client_finalize(GHubClient *client)
 
 /******************************************************************************
 *                                                                             *
-*  Paramètres  : hash        = empreinte d'un binaire en cours d'analyse.     *
-*                collections = ensemble de collections existantes.            *
-*                                                                             *
-*  Description : Prépare un client pour une connexion à une BD.               *
-*                                                                             *
-*  Retour      : Structure mise en place ou NULL en cas d'échec.              *
-*                                                                             *
-*  Remarques   : -                                                            *
-*                                                                             *
-******************************************************************************/
-
-GHubClient *g_hub_client_new(const char *hash, GList *collections)
-{
-    GHubClient *result;                     /* Adresse à retourner         */
-
-    result = g_object_new(G_TYPE_HUB_CLIENT, NULL);
-
-    init_static_rle_string(&result->hash, hash);
-    result->collections = collections;
-
-    return result;
-
-}
-
-
-/******************************************************************************
-*                                                                             *
 *  Paramètres  : client = client pour les accès distants à manipuler.         *
 *                                                                             *
 *  Description : Démarre la connexion à la base de données interne.           *
@@ -522,6 +392,7 @@ static bool g_hub_client_start_common(GHubClient *client, char *desc)
     char *filename;                         /* Fichier PEM à manipuler     */
     int ret;                                /* Bilan d'un appel            */
     char *rootdir;                          /* Racine pour le client       */
+    GHubClientClass *class;                 /* Classe du client connecté   */
     packed_buffer out_pbuf;                 /* Tampon d'émission           */
     bool status;                            /* Bilan d'une opération       */
     packed_buffer in_pbuf;                  /* Tampon de réception         */
@@ -608,11 +479,13 @@ static bool g_hub_client_start_common(GHubClient *client, char *desc)
         goto ssl_error;
     }
 
+    class = G_HUB_CLIENT_GET_CLASS(client);
+
     /**
      * On réalise l'envoi initial ; le premier paquet doit contenir :
-     *    - la commande 'DBC_HELO'.
-     *    - le numéro de version du client.
-     *    - l'empreinte du binaire analysé.
+     *    - la commande 'DBC_HELO' ;
+     *    - le numéro de version du client ;
+     *    - le rôle attendu de la connexion.
      *
      * Tout ceci est à synchroniser avec la fonction g_db_server_listener().
      */
@@ -625,15 +498,19 @@ static bool g_hub_client_start_common(GHubClient *client, char *desc)
     status = extend_packed_buffer(&out_pbuf, (uint32_t []) { CDB_PROTOCOL_VERSION }, sizeof(uint32_t), true);
     if (!status) goto setup_error;
 
-    status = pack_rle_string(&client->hash, &out_pbuf);
+    status = extend_packed_buffer(&out_pbuf, &class->role, sizeof(uint32_t), true);
     if (!status) goto setup_error;
 
+
+
+
+
     status = ssl_send_packed_buffer(&out_pbuf, client->tls_fd);
     if (!status) goto setup_error;
 
     /**
      * Le serveur doit répondre pour un message type :
-     *    - la commande 'DBC_WELCOME'.
+     *    - la commande 'DBC_WELCOME' ;
      *    - un identifiant d'erreur ('DBE_NONE', 'DBE_BAD_EXCHANGE'
      *      ou 'DBE_WRONG_VERSION' ... 'DBE_LOADING_ERROR').
      */
@@ -689,14 +566,19 @@ static bool g_hub_client_start_common(GHubClient *client, char *desc)
 
     }
 
-    client->can_get_updates = false;
+    ret = pipe(client->stop_ctrl);
+    if (ret != 0)
+    {
+        LOG_ERROR_N("pipe");
+        goto sys_error;
+    }
 
-    client->update = g_thread_try_new("cdb_client", (GThreadFunc)g_hub_client_update, client, NULL);
+    client->update = g_thread_try_new("cdb_client", class->recv_func, client, NULL);
     if (client->update == NULL)
     {
         log_variadic_message(LMT_ERROR, _("Failed to start a listening thread for the server '%s'!"),
                              desc);
-        goto comm_error;
+        goto sys_error;
     }
 
     exit_packed_buffer(&out_pbuf);
@@ -704,6 +586,7 @@ static bool g_hub_client_start_common(GHubClient *client, char *desc)
 
     return true;
 
+ sys_error:
  comm_error:
 
     exit_packed_buffer(&in_pbuf);
@@ -733,296 +616,68 @@ static bool g_hub_client_start_common(GHubClient *client, char *desc)
 *                                                                             *
 *  Paramètres  : client = client pour les accès distants à manipuler.         *
 *                                                                             *
-*  Description : Assure l'accueil des nouvelles mises à jour.                 *
+*  Description : Arrête la connexion à la base de données.                    *
 *                                                                             *
-*  Retour      : NULL.                                                        *
+*  Retour      : -                                                            *
 *                                                                             *
 *  Remarques   : -                                                            *
 *                                                                             *
 ******************************************************************************/
 
-static void *g_hub_client_update(GHubClient *client)
+void g_hub_client_stop(GHubClient *client)
 {
-    packed_buffer out_pbuf;                 /* Tampon d'émission           */
-    bool status;                            /* Bilan d'une opération       */
-    struct pollfd fds;                      /* Surveillance des flux       */
-    packed_buffer in_pbuf;                  /* Tampon de réception         */
+    int fd;                                 /* Canal à clôturer            */
     int ret;                                /* Bilan d'un appel            */
-    uint32_t tmp32;                         /* Valeur sur 32 bits          */
-    uint32_t command;                       /* Commande de la requête      */
-    DBError error;                          /* Bilan d'une commande passée */
-    GDbCollection *collec;                  /* Collection visée au final   */
-    uint8_t tmp8;                           /* Valeur sur 8 bits           */
-    char *msg;                              /* Message d'erreur à imprimer */
-
-    /**
-     * Avant toute chose, on demande un stage d'actualisation !
-     */
-
-    init_packed_buffer(&out_pbuf);
-
-    status = extend_packed_buffer(&out_pbuf, (uint32_t []) { DBC_GET_SNAPSHOTS }, sizeof(uint32_t), true);
-    if (!status)
-    {
-        exit_packed_buffer(&out_pbuf);
-        goto exit;
-    }
-
-    status = extend_packed_buffer(&out_pbuf, (uint32_t []) { DBC_GET_CUR_SNAPSHOT }, sizeof(uint32_t), true);
-    if (!status)
-    {
-        exit_packed_buffer(&out_pbuf);
-        goto exit;
-    }
-
-    status = extend_packed_buffer(&out_pbuf, (uint32_t []) { DBC_GET_ALL_ITEMS }, sizeof(uint32_t), true);
-    if (!status)
-    {
-        exit_packed_buffer(&out_pbuf);
-        goto exit;
-    }
-
-    status = ssl_send_packed_buffer(&out_pbuf, client->tls_fd);
-    if (!status)
-    {
-        log_simple_message(LMT_INFO, _("Failed to get all updates"));
-        exit_packed_buffer(&out_pbuf);
-        goto exit;
-    }
-
-    exit_packed_buffer(&out_pbuf);
+    ssize_t sent;                           /* Quantité de données émises  */
 
-    /**
-     * Phase d'écoute continue...
-     */
-
-    fds.fd = client->fd;
-    fds.events = POLLIN | POLLPRI;
-
-    init_packed_buffer(&in_pbuf);
+    /* Gestion du double appel */
 
-    while (client->fd != -1)
+    if (client->fd == -1)
     {
-        ret = poll(&fds, 1, -1);
-        if (ret != 1) continue;
-
-        /* Le canal est fermé, une sortie doit être demandée... */
-        if (fds.revents & POLLNVAL)
-            break;
-
         /**
-         * Même chose, cf. "TCP: When is EPOLLHUP generated?"
-         * https://stackoverflow.com/questions/52976152/tcp-when-is-epollhup-generated/52976327#52976327
+         * Si la fermture est forcée, le thread de traitement va terminer en erreur.
+         * Donc cette fonction sera appelée deux fois. Seule la première va affecter
+         * le contexte, donc on le peut pas s'assurer de la condition suivante dans
+         * tous les cas.
          */
 
-        if (fds.revents & (POLLHUP | POLLRDHUP))
-            break;
-
-        if (fds.revents & (POLLIN | POLLPRI))
-        {
-            reset_packed_buffer(&in_pbuf);
-
-            status = ssl_recv_packed_buffer(&in_pbuf, client->tls_fd);
-            if (!status) goto gdcu_bad_exchange;
-
- next_command:
-
-            status = extract_packed_buffer(&in_pbuf, &command, sizeof(uint32_t), true);
-            if (!status) goto gdcu_bad_exchange;
-
-            switch (command)
-            {
-                case DBC_SAVE:
-
-                    status = extract_packed_buffer(&in_pbuf, &tmp32, sizeof(uint32_t), true);
-                    if (!status) goto gdcu_bad_exchange;
-
-                    error = tmp32;
-
-                    if (error == DBE_NONE)
-                        log_variadic_message(LMT_INFO, _("Archive saved for binary '%s'"),
-                                             get_rle_string(&client->hash));
-                    else
-                        log_variadic_message(LMT_ERROR, _("Failed to save the archive for binary '%s'"),
-                                             get_rle_string(&client->hash));
-
-                    break;
-
-                case DBC_COLLECTION:
-
-                    status = extract_packed_buffer(&in_pbuf, &tmp32, sizeof(uint32_t), true);
-                    if (!status) goto gdcu_bad_exchange;
-
-                    collec = find_collection_in_list(client->collections, tmp32);
-                    if (collec == NULL) goto gdcu_bad_exchange;
-
-                    if (client->can_get_updates)
-                        status = g_db_collection_unpack(collec, &in_pbuf, NULL);
-                    else
-                        status = _g_db_collection_unpack(collec, &in_pbuf, (DBAction []) { 0 }, NULL);
-
-                    if (!status) goto gdcu_bad_exchange;
-
-                    break;
-
-                case DBC_GET_ALL_ITEMS:
-                    log_variadic_message(LMT_INFO,
-                                         _("This command is not available on this side: 0x%08x"), command);
-                    goto gdcu_bad_exchange;
-                    break;
-
-                case DBC_SET_ALL_ITEMS:
-
-                    status = extract_packed_buffer(&in_pbuf, &tmp8, sizeof(uint8_t), true);
-                    if (!status) goto gdcu_bad_exchange;
-
-                    client->can_get_updates = (tmp8 == 0x1);
-                    break;
-
-                case DBC_GET_SNAPSHOTS:
-                    log_variadic_message(LMT_INFO,
-                                         _("This command is not available on this side: 0x%08x"), command);
-                    goto gdcu_bad_exchange;
-                    break;
-
-                case DBC_SNAPSHOTS_UPDATED:
-
-                    status = g_hub_client_update_snapshots(client, &in_pbuf);
-                    if (!status) goto gdcu_bad_exchange;
-
-                    break;
-
-                case DBC_GET_CUR_SNAPSHOT:
-                    log_variadic_message(LMT_INFO,
-                                         _("This command is not available on this side: 0x%08x"), command);
-                    goto gdcu_bad_exchange;
-                    break;
-
-                case DBC_CUR_SNAPSHOT_UPDATED:
-
-                    status = g_hub_client_update_current_snapshot(client, &in_pbuf);
-                    if (!status) goto gdcu_bad_exchange;
-
-                    break;
-
-                case DBC_SET_CUR_SNAPSHOT:
-                case DBC_SET_SNAPSHOT_NAME:
-                case DBC_SET_SNAPSHOT_DESC:
-                case DBC_CREATE_SNAPSHOT:
-                case DBC_REMOVE_SNAPSHOT:
-                    log_variadic_message(LMT_INFO,
-                                         _("This command is not available on this side: 0x%08x"), command);
-                    goto gdcu_bad_exchange;
-                    break;
-
-            }
-
-            if (has_more_data_in_packed_buffer(&in_pbuf))
-                goto next_command;
-
-            client->can_get_updates = true;
-            continue;
-
- gdcu_bad_exchange:
-
-            asprintf(&msg, _("Bad reception from %s"), client->desc);
-
-            LOG_ERROR(LMT_ERROR, msg);
-
-            free(msg);
-
-            break;
-
-        }
-
+        /*assert(client->tls_ctx == NULL);*/
+        return;
     }
 
- exit:
-
-    g_hub_client_stop(client);
-
-    exit_packed_buffer(&in_pbuf);
-
-    return NULL;
-
-}
-
-
-/******************************************************************************
-*                                                                             *
-*  Paramètres  : client = client pour les accès distants à manipuler.         *
-*                pbuf   = données présentes à traiter.                        *
-*                                                                             *
-*  Description : Met à jour la liste des instantanés courants.                *
-*                                                                             *
-*  Retour      : true si l'opération s'est déroulée sans encombre, ou false.  *
-*                                                                             *
-*  Remarques   : -                                                            *
-*                                                                             *
-******************************************************************************/
-
-static bool g_hub_client_update_snapshots(GHubClient *client, packed_buffer *pbuf)
-{
-    bool result;                            /* Validité à retourner        */
-    size_t i;                               /* Boucle de parcours          */
-    char id[SNAP_ID_HEX_SZ];                /* Caractères hexadécimaux     */
-    snapshot_info_t info;                   /* Description d'instantané    */
-    snapshot_info_t *dest;                  /* Destination de description  */
+    fd = client->fd;
 
-    result = true;
+    client->fd = -1;
 
-    g_mutex_lock(&client->snap_lock);
+    /* Ordre d'arrêt */
 
-    if (client->snapshots != NULL)
+    if (g_thread_self() != client->update)
     {
-        for (i = 0; i < client->snap_count; i++)
-            exit_snapshot_info(&client->snapshots[i]);
+        sent = write(client->stop_ctrl[1], "\xf0", 1);
+        if (sent != 1) LOG_ERROR_N("write");
 
-        free(client->snapshots);
-
-        client->snapshots = NULL;
-        client->snap_count = 0;
+        g_thread_join(client->update);
 
     }
 
-    do
-    {
-        result = peek_packed_buffer(pbuf, id, SNAP_ID_HEX_SZ, false);
-        if (!result) break;
-
-        if (strncmp(id, SNAPSHOT_END_MARK, SNAP_ID_HEX_SZ) == 0)
-        {
-            advance_packed_buffer(pbuf, SNAP_ID_HEX_SZ);
-            break;
-        }
-
-        else
-        {
-            setup_empty_snapshot_info(&info);
-
-            result = unpack_snapshot_info(&info, pbuf);
-            if (!result) break;
-
-            client->snapshots = realloc(client->snapshots, ++client->snap_count * sizeof(snapshot_info_t));
-
-            dest = &client->snapshots[client->snap_count - 1];
-
-            setup_empty_snapshot_info(dest);
-            copy_snapshot_info(dest, &info);
+    /* Fermeture des flux */
 
-            exit_snapshot_info(&info);
-
-        }
+    SSL_free(client->tls_fd);
+    client->tls_fd = NULL;
 
-    }
-    while (true);
+    SSL_CTX_free(client->tls_ctx);
+    client->tls_ctx = NULL;
 
-    g_mutex_unlock(&client->snap_lock);
+    ret = close(fd);
+    if (ret == -1) LOG_ERROR_N("close");
 
-    if (result)
-        g_signal_emit_by_name(client, "snapshots-updated");
+    ret = close(client->stop_ctrl[0]);
+    if (ret == -1) LOG_ERROR_N("close");
+    client->stop_ctrl[0] = -1;
 
-    return result;
+    ret = close(client->stop_ctrl[1]);
+    if (ret == -1) LOG_ERROR_N("close");
+    client->stop_ctrl[1] = -1;
 
 }
 
@@ -1030,36 +685,37 @@ static bool g_hub_client_update_snapshots(GHubClient *client, packed_buffer *pbu
 /******************************************************************************
 *                                                                             *
 *  Paramètres  : client = client pour les accès distants à manipuler.         *
-*                pbuf   = données présentes à traiter.                        *
 *                                                                             *
-*  Description : Met à jour l'identifiant de l'instantané courant.            *
+*  Description : Identifie le canal de communication pour envois au serveur.  *
 *                                                                             *
-*  Retour      : true si l'opération s'est déroulée sans encombre, ou false.  *
+*  Retour      : Descripteur de flux normalement ouvert.                      *
 *                                                                             *
 *  Remarques   : -                                                            *
 *                                                                             *
 ******************************************************************************/
 
-static bool g_hub_client_update_current_snapshot(GHubClient *client, packed_buffer *pbuf)
+SSL *g_hub_client_get_ssl_fd(GHubClient *client)
 {
-    bool result;                            /* Validité à retourner        */
-    snapshot_id_t id;                       /* Identifiant d'instantané    */
-
-    setup_empty_snapshot_id(&id);
-
-    result = unpack_snapshot_id(&id, pbuf);
-
-    if (result)
-    {
-        g_mutex_lock(&client->cur_lock);
+    SSL *result;                            /* Canal à retourner           */
+#ifndef NDEBUG
+    int ret;                                /* Validation de transmission  */
+#endif
 
-        copy_snapshot_id(&client->current, &id);
-        client->has_current = true;
+    g_mutex_lock(&client->sending_lock);
 
-        g_mutex_unlock(&client->cur_lock);
+    result = client->tls_fd;
 
-        g_signal_emit_by_name(client, "snapshot-changed");
+    if (result == NULL)
+        g_mutex_unlock(&client->sending_lock);
 
+    else
+    {
+#ifndef NDEBUG
+        ret = SSL_up_ref(result);
+        assert(ret == 1);
+#else
+        SSL_up_ref(result);
+#endif
     }
 
     return result;
@@ -1070,8 +726,9 @@ static bool g_hub_client_update_current_snapshot(GHubClient *client, packed_buff
 /******************************************************************************
 *                                                                             *
 *  Paramètres  : client = client pour les accès distants à manipuler.         *
+*                tls_fd = canal de communication SSL.                         *
 *                                                                             *
-*  Description : Arrête la connexion à la base de données.                    *
+*  Description : Marque le canal de communication comme disponible.           *
 *                                                                             *
 *  Retour      : -                                                            *
 *                                                                             *
@@ -1079,636 +736,10 @@ static bool g_hub_client_update_current_snapshot(GHubClient *client, packed_buff
 *                                                                             *
 ******************************************************************************/
 
-void g_hub_client_stop(GHubClient *client)
+void g_hub_client_put_ssl_fd(GHubClient *client, SSL *tls_fd)
 {
-    int fd;                                 /* Canal à clôturer            */
-    int ret;                                /* Bilan d'un appel            */
-
-    /* Canal de communication */
-
-    if (client->fd == -1)
-    {
-        /**
-         * Si la fermture est forcée, le thread de traitement va terminer en erreur.
-         * Donc cette fonction sera appelée deux fois. Seule la première va affecter
-         * le contexte, donc on le peut pas s'assurer de la condition suivante dans
-         * tous les cas.
-         */
-
-        /*assert(client->tls_ctx == NULL);*/
-        return;
-    }
-
-    fd = client->fd;
-
-    client->fd = -1;
-
-    ret = close(fd);
-    if (ret == -1) LOG_ERROR_N("close");
-
-    if (g_thread_self() != client->update)
-        g_thread_join(client->update);
-
-    /* Environnement TLS */
-
-    SSL_free(client->tls_fd);
-    client->tls_fd = NULL;
-
-    SSL_CTX_free(client->tls_ctx);
-    client->tls_ctx = NULL;
-
-}
-
-
-/******************************************************************************
-*                                                                             *
-*  Paramètres  : client = client pour les accès distants à manipuler.         *
-*                                                                             *
-*  Description : Identifie le canal de communication pour envois au serveur.  *
-*                                                                             *
-*  Retour      : Descripteur de flux normalement ouvert.                      *
-*                                                                             *
-*  Remarques   : -                                                            *
-*                                                                             *
-******************************************************************************/
-
-static SSL *g_hub_client_get_ssl_fd(GHubClient *client)
-{
-    SSL *result;                            /* Canal à retourner           */
-#ifndef NDEBUG
-    int ret;                                /* Validation de transmission  */
-#endif
-
-    g_mutex_lock(&client->sending_lock);
-
-    result = client->tls_fd;
-
-    if (result == NULL)
-        g_mutex_unlock(&client->sending_lock);
-
-    else
-    {
-#ifndef NDEBUG
-        ret = SSL_up_ref(result);
-        assert(ret == 1);
-#else
-        SSL_up_ref(result);
-#endif
-    }
-
-    return result;
-
-}
-
-
-/******************************************************************************
-*                                                                             *
-*  Paramètres  : client = client pour les accès distants à manipuler.         *
-*                tls_fd = canal de communication SSL.                         *
-*                                                                             *
-*  Description : Marque le canal de communication comme disponible.           *
-*                                                                             *
-*  Retour      : -                                                            *
-*                                                                             *
-*  Remarques   : -                                                            *
-*                                                                             *
-******************************************************************************/
-
-static void g_hub_client_put_ssl_fd(GHubClient *client, SSL *tls_fd)
-{
-    g_mutex_unlock(&client->sending_lock);
+    g_mutex_unlock(&client->sending_lock);
 
     SSL_free(tls_fd);
 
 }
-
-
-/******************************************************************************
-*                                                                             *
-*  Paramètres  : client = client pour les accès distants à manipuler.         *
-*                                                                             *
-*  Description : Effectue une demande de sauvegarde de l'état courant.        *
-*                                                                             *
-*  Retour      : true si la commande a bien été envoyée, false sinon.         *
-*                                                                             *
-*  Remarques   : -                                                            *
-*                                                                             *
-******************************************************************************/
-
-bool g_hub_client_save(GHubClient *client)
-{
-    bool result;                            /* Bilan partiel à remonter    */
-    packed_buffer out_pbuf;                 /* Tampon d'émission           */
-    SSL *tls_fd;                            /* Canal de communication SSL  */
-
-    init_packed_buffer(&out_pbuf);
-
-    tls_fd = g_hub_client_get_ssl_fd(client);
-
-    if (tls_fd == NULL)
-        result = false;
-
-    else
-    {
-        result = extend_packed_buffer(&out_pbuf, (uint32_t []) { DBC_SAVE }, sizeof(uint32_t), true);
-
-        if (result)
-            result = ssl_send_packed_buffer(&out_pbuf, tls_fd);
-
-        g_hub_client_put_ssl_fd(client, tls_fd);
-
-    }
-
-    exit_packed_buffer(&out_pbuf);
-
-    return result;
-
-}
-
-
-/******************************************************************************
-*                                                                             *
-*  Paramètres  : client = client pour les accès distants à manipuler.         *
-*                item   = élémnent à pousser vers un serveur de collection.   *
-*                                                                             *
-*  Description : Ajoute un élément à la collection d'un serveur.              *
-*                                                                             *
-*  Retour      : true si la commande a bien été envoyée, false sinon.         *
-*                                                                             *
-*  Remarques   : -                                                            *
-*                                                                             *
-******************************************************************************/
-
-bool g_hub_client_add_item(GHubClient *client, const GDbItem *item)
-{
-    bool result;                            /* Bilan partiel à remonter    */
-    packed_buffer out_pbuf;                 /* Tampon d'émission           */
-    SSL *tls_fd;                            /* Canal de communication SSL  */
-    DBFeatures feature;                     /* Domaine de fonctionnalité   */
-    GDbCollection *collec;                  /* Collection visée au final   */
-
-    init_packed_buffer(&out_pbuf);
-
-    tls_fd = g_hub_client_get_ssl_fd(client);
-
-    if (tls_fd == NULL)
-        result = false;
-
-    else
-    {
-        feature = g_db_item_get_feature(item);
-
-        collec = find_collection_in_list(client->collections, feature);
-        if (collec == NULL)
-        {
-            result = false;
-            goto bad_item_feature;
-        }
-
-        result = g_db_collection_pack(collec, &out_pbuf, DBA_ADD_ITEM, item);
-
-        if (result)
-            result = ssl_send_packed_buffer(&out_pbuf, tls_fd);
-
- bad_item_feature:
-
-        g_hub_client_put_ssl_fd(client, tls_fd);
-
-    }
-
-    exit_packed_buffer(&out_pbuf);
-
-    return result;
-
-}
-
-
-/******************************************************************************
-*                                                                             *
-*  Paramètres  : client    = client pour les accès distants à manipuler.      *
-*                timestamp = date du dernier élément à garder comme actif.    *
-*                                                                             *
-*  Description : Active les éléments en amont d'un horodatage donné.          *
-*                                                                             *
-*  Retour      : true si la commande a bien été envoyée, false sinon.         *
-*                                                                             *
-*  Remarques   : -                                                            *
-*                                                                             *
-******************************************************************************/
-
-bool g_hub_client_set_last_active(GHubClient *client, timestamp_t timestamp)
-{
-    bool result;                            /* Bilan partiel à remonter    */
-    packed_buffer out_pbuf;                 /* Tampon d'émission           */
-    SSL *tls_fd;                            /* Canal de communication SSL  */
-
-    init_packed_buffer(&out_pbuf);
-
-    tls_fd = g_hub_client_get_ssl_fd(client);
-
-    if (tls_fd == NULL)
-        result = false;
-
-    else
-    {
-        result = extend_packed_buffer(&out_pbuf, (uint32_t []) { DBC_SET_LAST_ACTIVE }, sizeof(uint32_t), true);
-
-        if (result)
-            result = pack_timestamp(&timestamp, &out_pbuf);
-
-        if (result)
-            result = ssl_send_packed_buffer(&out_pbuf, tls_fd);
-
-        g_hub_client_put_ssl_fd(client, tls_fd);
-
-    }
-
-    exit_packed_buffer(&out_pbuf);
-
-    return result;
-
-}
-
-
-/******************************************************************************
-*                                                                             *
-*  Paramètres  : client = client pour les accès distants à manipuler.         *
-*                info   = description des instantanés présents. [OUT]         *
-*                count  = taille de la liste retournée. [OUT]                 *
-*                                                                             *
-*  Description : Fournit la liste des instantanés existants.                  *
-*                                                                             *
-*  Retour      : true si la liste retournée est valide, false sinon.          *
-*                                                                             *
-*  Remarques   : -                                                            *
-*                                                                             *
-******************************************************************************/
-
-bool g_hub_client_get_snapshots(GHubClient *client, snapshot_info_t **info, size_t *count)
-{
-    bool result;                            /* Validité à retourner        */
-    size_t i;                               /* Boucle de parcours          */
-    snapshot_info_t *dest;                  /* Destination de description  */
-
-    g_mutex_lock(&client->snap_lock);
-
-    result = (client->snap_count > 0);
-
-    if (result)
-    {
-        *info = malloc(client->snap_count * sizeof(snapshot_info_t));
-        *count = client->snap_count;
-
-        for (i = 0; i < client->snap_count; i++)
-        {
-            dest = &(*info)[i];
-
-            setup_empty_snapshot_info(dest);
-            copy_snapshot_info(dest, &client->snapshots[i]);
-
-        }
-
-    }
-
-    g_mutex_unlock(&client->snap_lock);
-
-    return result;
-
-}
-
-
-/******************************************************************************
-*                                                                             *
-*  Paramètres  : client = client pour les accès distants à manipuler.         *
-*                id     = identifiant d'instantané à renseigner. [OUT]        *
-*                                                                             *
-*  Description : Fournit l'identifiant de l'instantané courant.               *
-*                                                                             *
-*  Retour      : true si l'identifiant retourné est valide, false sinon.      *
-*                                                                             *
-*  Remarques   : -                                                            *
-*                                                                             *
-******************************************************************************/
-
-bool g_hub_client_get_current_snapshot(GHubClient *client, snapshot_id_t *id)
-{
-    bool result;                            /* Validité à retourner        */
-
-    g_mutex_lock(&client->cur_lock);
-
-    result = client->has_current;
-
-    if (result)
-        copy_snapshot_id(id, &client->current);
-
-    g_mutex_unlock(&client->cur_lock);
-
-    return result;
-
-}
-
-
-/******************************************************************************
-*                                                                             *
-*  Paramètres  : client = client pour les accès distants à manipuler.         *
-*                id     = identifiant d'instantané à activer.                 *
-*                                                                             *
-*  Description : Définit l'identifiant de l'instantané courant.               *
-*                                                                             *
-*  Retour      : true si la commande a bien été envoyée, false sinon.         *
-*                                                                             *
-*  Remarques   : -                                                            *
-*                                                                             *
-******************************************************************************/
-
-bool g_hub_client_set_current_snapshot(GHubClient *client, const snapshot_id_t *id)
-{
-    bool result;                            /* Bilan partiel à remonter    */
-    packed_buffer out_pbuf;                 /* Tampon d'émission           */
-    SSL *tls_fd;                            /* Canal de communication SSL  */
-
-    init_packed_buffer(&out_pbuf);
-
-    tls_fd = g_hub_client_get_ssl_fd(client);
-
-    if (tls_fd == NULL)
-        result = false;
-
-    else
-    {
-        result = extend_packed_buffer(&out_pbuf, (uint32_t []) { DBC_SET_CUR_SNAPSHOT }, sizeof(uint32_t), true);
-
-        if (result)
-            result = pack_snapshot_id(id, &out_pbuf);
-
-        if (result)
-            result = ssl_send_packed_buffer(&out_pbuf, tls_fd);
-
-        g_hub_client_put_ssl_fd(client, tls_fd);
-
-    }
-
-    exit_packed_buffer(&out_pbuf);
-
-    return result;
-
-}
-
-
-/******************************************************************************
-*                                                                             *
-*  Paramètres  : client = client pour les accès distants à manipuler.         *
-*                id     = identifiant d'instantané à traiter.                 *
-*                name   = désignation humaine pour l'instantané.              *
-*                                                                             *
-*  Description : Définit la désignation d'un instantané donné.                *
-*                                                                             *
-*  Retour      : true si la commande a bien été envoyée, false sinon.         *
-*                                                                             *
-*  Remarques   : -                                                            *
-*                                                                             *
-******************************************************************************/
-
-bool g_hub_client_set_snapshot_name(GHubClient *client, const snapshot_id_t *id, const char *name)
-{
-    bool result;                            /* Bilan partiel à remonter    */
-    packed_buffer out_pbuf;                 /* Tampon d'émission           */
-    SSL *tls_fd;                            /* Canal de communication SSL  */
-    rle_string string;                      /* Chaîne à transmettre        */
-
-    init_packed_buffer(&out_pbuf);
-
-    tls_fd = g_hub_client_get_ssl_fd(client);
-
-    if (tls_fd == NULL)
-        result = false;
-
-    else
-    {
-        result = extend_packed_buffer(&out_pbuf, (uint32_t []) { DBC_SET_SNAPSHOT_NAME }, sizeof(uint32_t), true);
-
-        if (result)
-            result = pack_snapshot_id(id, &out_pbuf);
-
-        if (result)
-        {
-            init_static_rle_string(&string, name);
-
-            result = pack_rle_string(&string, &out_pbuf);
-
-            exit_rle_string(&string);
-
-        }
-
-        if (result)
-            result = ssl_send_packed_buffer(&out_pbuf, tls_fd);
-
-        g_hub_client_put_ssl_fd(client, tls_fd);
-
-    }
-
-    exit_packed_buffer(&out_pbuf);
-
-    return result;
-
-}
-
-
-/******************************************************************************
-*                                                                             *
-*  Paramètres  : client = client pour les accès distants à manipuler.         *
-*                id     = identifiant d'instantané à traiter.                 *
-*                desc   = description humaine pour l'instantané.              *
-*                                                                             *
-*  Description : Définit la description d'un instantané donné.                *
-*                                                                             *
-*  Retour      : true si la commande a bien été envoyée, false sinon.         *
-*                                                                             *
-*  Remarques   : -                                                            *
-*                                                                             *
-******************************************************************************/
-
-bool g_hub_client_set_snapshot_desc(GHubClient *client, const snapshot_id_t *id, const char *desc)
-{
-    bool result;                            /* Bilan partiel à remonter    */
-    packed_buffer out_pbuf;                 /* Tampon d'émission           */
-    SSL *tls_fd;                            /* Canal de communication SSL  */
-    rle_string string;                      /* Chaîne à transmettre        */
-
-    init_packed_buffer(&out_pbuf);
-
-    tls_fd = g_hub_client_get_ssl_fd(client);
-
-    if (tls_fd == NULL)
-        result = false;
-
-    else
-    {
-        result = extend_packed_buffer(&out_pbuf, (uint32_t []) { DBC_SET_SNAPSHOT_DESC }, sizeof(uint32_t), true);
-
-        if (result)
-            result = pack_snapshot_id(id, &out_pbuf);
-
-        if (result)
-        {
-            init_static_rle_string(&string, desc);
-
-            result = pack_rle_string(&string, &out_pbuf);
-
-            exit_rle_string(&string);
-
-        }
-
-        if (result)
-            result = ssl_send_packed_buffer(&out_pbuf, tls_fd);
-
-        g_hub_client_put_ssl_fd(client, tls_fd);
-
-    }
-
-    exit_packed_buffer(&out_pbuf);
-
-    return result;
-
-}
-
-
-/******************************************************************************
-*                                                                             *
-*  Paramètres  : client = client pour les accès distants à manipuler.         *
-*                id     = identifiant d'instantané à traiter.                 *
-*                                                                             *
-*  Description : Restaure un ancien instantané.                               *
-*                                                                             *
-*  Retour      : true si la commande a bien été envoyée, false sinon.         *
-*                                                                             *
-*  Remarques   : -                                                            *
-*                                                                             *
-******************************************************************************/
-
-bool g_hub_client_restore_snapshot(GHubClient *client, const snapshot_id_t *id)
-{
-    bool result;                            /* Bilan partiel à remonter    */
-    packed_buffer out_pbuf;                 /* Tampon d'émission           */
-    SSL *tls_fd;                            /* Canal de communication SSL  */
-
-    init_packed_buffer(&out_pbuf);
-
-    tls_fd = g_hub_client_get_ssl_fd(client);
-
-    if (tls_fd == NULL)
-        result = false;
-
-    else
-    {
-        result = extend_packed_buffer(&out_pbuf, (uint32_t []) { DBC_SET_CUR_SNAPSHOT }, sizeof(uint32_t), true);
-
-        if (result)
-            result = pack_snapshot_id(id, &out_pbuf);
-
-        if (result)
-            result = ssl_send_packed_buffer(&out_pbuf, tls_fd);
-
-        g_hub_client_put_ssl_fd(client, tls_fd);
-
-    }
-
-    exit_packed_buffer(&out_pbuf);
-
-    return result;
-
-}
-
-
-/******************************************************************************
-*                                                                             *
-*  Paramètres  : client = client pour les accès distants à manipuler.         *
-*                                                                             *
-*  Description : Crée un nouvel instantané à partir d'un autre.               *
-*                                                                             *
-*  Retour      : true si la commande a bien été envoyée, false sinon.         *
-*                                                                             *
-*  Remarques   : -                                                            *
-*                                                                             *
-******************************************************************************/
-
-bool g_hub_client_create_snapshot(GHubClient *client)
-{
-    bool result;                            /* Bilan partiel à remonter    */
-    packed_buffer out_pbuf;                 /* Tampon d'émission           */
-    SSL *tls_fd;                            /* Canal de communication SSL  */
-
-    init_packed_buffer(&out_pbuf);
-
-    tls_fd = g_hub_client_get_ssl_fd(client);
-
-    if (tls_fd == NULL)
-        result = false;
-
-    else
-    {
-        result = extend_packed_buffer(&out_pbuf, (uint32_t []) { DBC_CREATE_SNAPSHOT }, sizeof(uint32_t), true);
-
-        if (result)
-            result = ssl_send_packed_buffer(&out_pbuf, tls_fd);
-
-        g_hub_client_put_ssl_fd(client, tls_fd);
-
-    }
-
-    exit_packed_buffer(&out_pbuf);
-
-    return result;
-
-}
-
-
-/******************************************************************************
-*                                                                             *
-*  Paramètres  : client = client pour les accès distants à manipuler.         *
-*                id     = identifiant d'instantané à traiter.                 *
-*                rec    = programme une suppression récursive.                *
-*                                                                             *
-*  Description : Supprime un ancien instantané.                               *
-*                                                                             *
-*  Retour      : true si la commande a bien été envoyée, false sinon.         *
-*                                                                             *
-*  Remarques   : -                                                            *
-*                                                                             *
-******************************************************************************/
-
-bool g_hub_client_remove_snapshot(GHubClient *client, const snapshot_id_t *id, bool rec)
-{
-    bool result;                            /* Bilan partiel à remonter    */
-    packed_buffer out_pbuf;                 /* Tampon d'émission           */
-    SSL *tls_fd;                            /* Canal de communication SSL  */
-
-    init_packed_buffer(&out_pbuf);
-
-    tls_fd = g_hub_client_get_ssl_fd(client);
-
-    if (tls_fd == NULL)
-        result = false;
-
-    else
-    {
-        result = extend_packed_buffer(&out_pbuf, (uint32_t []) { DBC_REMOVE_SNAPSHOT }, sizeof(uint32_t), true);
-
-        if (result)
-            result = pack_snapshot_id(id, &out_pbuf);
-
-        if (result)
-            result = extend_packed_buffer(&out_pbuf, (uint8_t []) { rec ? 0x1 : 0x0 }, sizeof(uint8_t), false);
-
-        if (result)
-            result = ssl_send_packed_buffer(&out_pbuf, tls_fd);
-
-        g_hub_client_put_ssl_fd(client, tls_fd);
-
-    }
-
-    exit_packed_buffer(&out_pbuf);
-
-    return result;
-
-}
diff --git a/src/analysis/db/client.h b/src/analysis/db/client.h
index 9a95163..0ec40e6 100644
--- a/src/analysis/db/client.h
+++ b/src/analysis/db/client.h
@@ -30,9 +30,6 @@
 #include <openssl/ssl.h>
 
 
-#include "collection.h"
-#include "misc/snapshot.h"
-
 
 
 #define G_TYPE_HUB_CLIENT            g_hub_client_get_type()
@@ -53,9 +50,6 @@ typedef struct _GHubClientClass GHubClientClass;
 /* Indique le type défini pour une description de client à l'écoute. */
 GType g_hub_client_get_type(void);
 
-/* Prépare un client pour une connexion à une BD. */
-GHubClient *g_hub_client_new(const char *, GList *);
-
 /* Démarre la connexion à la base de données interne. */
 bool g_hub_client_start_internal(GHubClient *);
 
@@ -65,39 +59,6 @@ bool g_hub_client_start_remote(GHubClient *, const char *, const char *, bool);
 /* Arrête la connexion à la base de données. */
 void g_hub_client_stop(GHubClient *);
 
-/* Effectue une demande de sauvegarde de l'état courant. */
-bool g_hub_client_save(GHubClient *);
-
-/* Ajoute un élément à la collection d'un serveur. */
-bool g_hub_client_add_item(GHubClient *, const GDbItem *);
-
-/* Active les éléments en amont d'un horodatage donné. */
-bool g_hub_client_set_last_active(GHubClient *, timestamp_t);
-
-/* Fournit la liste des instantanés existants. */
-bool g_hub_client_get_snapshots(GHubClient *, snapshot_info_t **, size_t *);
-
-/* Fournit l'identifiant de l'instantané courant. */
-bool g_hub_client_get_current_snapshot(GHubClient *, snapshot_id_t *);
-
-/* Définit l'identifiant de l'instantané courant. */
-bool g_hub_client_set_current_snapshot(GHubClient *, const snapshot_id_t *);
-
-/* Définit la désignation d'un instantané donné. */
-bool g_hub_client_set_snapshot_name(GHubClient *, const snapshot_id_t *, const char *);
-
-/* Définit la description d'un instantané donné. */
-bool g_hub_client_set_snapshot_desc(GHubClient *, const snapshot_id_t *, const char *);
-
-/* Restaure un ancien instantané. */
-bool g_hub_client_restore_snapshot(GHubClient *, const snapshot_id_t *);
-
-/* Crée un nouvel instantané à partir d'un autre. */
-bool g_hub_client_create_snapshot(GHubClient *);
-
-/* Supprime un ancien instantané. */
-bool g_hub_client_remove_snapshot(GHubClient *, const snapshot_id_t *, bool);
-
 
 
 #endif  /* _ANALYSIS_DB_CLIENT_H */
diff --git a/src/analysis/db/controller.c b/src/analysis/db/controller.c
new file mode 100644
index 0000000..46b628d
--- /dev/null
+++ b/src/analysis/db/controller.c
@@ -0,0 +1,449 @@
+
+/* Chrysalide - Outil d'analyse de fichiers binaires
+ * controller.h - prototypes pour la gestion d'un ensemble d'archives au format CDB
+ *
+ * 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 "controller.h"
+
+
+#include <assert.h>
+#include <errno.h>
+#include <malloc.h>
+#include <poll.h>
+#include <pthread.h>
+#include <string.h>
+
+
+#include <i18n.h>
+
+
+#include "backend-int.h"
+#include "../../common/packed.h"
+#include "../../core/logs.h"
+
+
+
+/* Description d'un contrôleur d'archives (instance) */
+struct _GCdbController
+{
+    GServerBackend parent;                  /* A laisser en premier        */
+
+    char *basedir;                          /* Répertoire du serveur       */
+
+    SSL *tls_fd;                            /* Canal de communication      */
+    char *peer_name;                        /* Désignation du correspondant*/
+    char *user;                             /* Utilisateur connecté        */
+
+};
+
+/* Description d'un contrôleur d'archives (classe) */
+struct _GCdbControllerClass
+{
+    GServerBackendClass parent;             /* A laisser en premier        */
+
+};
+
+
+/* Initialise la classe des contrôleurs d'archives. */
+static void g_cdb_controller_class_init(GCdbControllerClass *);
+
+/* Initialise un contrôleur d'archives. */
+static void g_cdb_controller_init(GCdbController *);
+
+/* Supprime toutes les références externes. */
+static void g_cdb_controller_dispose(GCdbController *);
+
+/* Procède à la libération totale de la mémoire. */
+static void g_cdb_controller_finalize(GCdbController *);
+
+/* Assure le traitement des requêtes de contrôle. */
+static void *g_cdb_controller_process(GCdbController *);
+
+/* Prend en compte une connexion nouvelle d'un utilisateur. */
+static void g_cdb_controller_add_client(GCdbController *, SSL *, const char *, const char *);
+
+
+
+/* Indique le type défini pour une gestion d'archives. */
+G_DEFINE_TYPE(GCdbController, g_cdb_controller, G_TYPE_SERVER_BACKEND);
+
+
+/******************************************************************************
+*                                                                             *
+*  Paramètres  : klass = classe à initialiser.                                *
+*                                                                             *
+*  Description : Initialise la classe des contrôleurs d'archives.             *
+*                                                                             *
+*  Retour      : -                                                            *
+*                                                                             *
+*  Remarques   : -                                                            *
+*                                                                             *
+******************************************************************************/
+
+static void g_cdb_controller_class_init(GCdbControllerClass *klass)
+{
+    GObjectClass *object;                   /* Autre version de la classe  */
+    GServerBackendClass *backend;           /* Classe parente              */
+
+    object = G_OBJECT_CLASS(klass);
+
+    object->dispose = (GObjectFinalizeFunc/* ! */)g_cdb_controller_dispose;
+    object->finalize = (GObjectFinalizeFunc)g_cdb_controller_finalize;
+
+    backend = G_SERVER_BACKEND_CLASS(klass);
+
+    backend->thread_name = "cdb_controller";
+    backend->thread_func = (GThreadFunc)g_cdb_controller_process;
+
+    backend->add_client = (add_backend_client_fc)g_cdb_controller_add_client;
+
+}
+
+
+/******************************************************************************
+*                                                                             *
+*  Paramètres  : controller = instance à initialiser.                         *
+*                                                                             *
+*  Description : Initialise un contrôleur d'archives.                         *
+*                                                                             *
+*  Retour      : -                                                            *
+*                                                                             *
+*  Remarques   : -                                                            *
+*                                                                             *
+******************************************************************************/
+
+static void g_cdb_controller_init(GCdbController *controller)
+{
+    controller->basedir = NULL;
+
+    controller->tls_fd = NULL;
+    controller->peer_name = NULL;
+    controller->user = NULL;
+
+}
+
+
+/******************************************************************************
+*                                                                             *
+*  Paramètres  : controller = instance d'objet GLib à traiter.                *
+*                                                                             *
+*  Description : Supprime toutes les références externes.                     *
+*                                                                             *
+*  Retour      : -                                                            *
+*                                                                             *
+*  Remarques   : -                                                            *
+*                                                                             *
+******************************************************************************/
+
+static void g_cdb_controller_dispose(GCdbController *controller)
+{
+    g_server_backend_stop(G_SERVER_BACKEND(controller));
+
+    G_OBJECT_CLASS(g_cdb_controller_parent_class)->dispose(G_OBJECT(controller));
+
+}
+
+
+/******************************************************************************
+*                                                                             *
+*  Paramètres  : controller = instance d'objet GLib à traiter.                *
+*                                                                             *
+*  Description : Procède à la libération totale de la mémoire.                *
+*                                                                             *
+*  Retour      : -                                                            *
+*                                                                             *
+*  Remarques   : -                                                            *
+*                                                                             *
+******************************************************************************/
+
+static void g_cdb_controller_finalize(GCdbController *controller)
+{
+    if (controller->basedir != NULL)
+        free(controller->basedir);
+
+    if (controller->tls_fd != NULL)
+        SSL_free(controller->tls_fd);
+
+    if (controller->peer_name != NULL)
+        free(controller->peer_name);
+
+    if (controller->user != NULL)
+        free(controller->user);
+
+    G_OBJECT_CLASS(g_cdb_controller_parent_class)->finalize(G_OBJECT(controller));
+
+}
+
+
+/******************************************************************************
+*                                                                             *
+*  Paramètres  : basedir = répertoire de stockage des enregistrements.        *
+*                error   = indication éventuelle en cas d'échec. [OUT]        *
+*                                                                             *
+*  Description : Définit ou ouvre une archive d'éléments utilisateur.         *
+*                                                                             *
+*  Retour      : Structure mise en plae ou NULL en cas d'échec.               *
+*                                                                             *
+*  Remarques   : -                                                            *
+*                                                                             *
+******************************************************************************/
+
+GCdbController *g_cdb_controller_new(const char *basedir, DBError *error)
+{
+    GCdbController *result;                 /* Adresse à retourner         */
+
+    result = g_object_new(G_TYPE_CDB_CONTROLLER, NULL);
+
+    result->basedir = strdup(basedir);
+
+    *error = DBE_NONE;
+
+    return result;
+
+}
+
+
+/******************************************************************************
+*                                                                             *
+*  Paramètres  : controller = centralisation de tous les savoirs.             *
+*                                                                             *
+*  Description : Assure le traitement des requêtes de contrôle.               *
+*                                                                             *
+*  Retour      : NULL.                                                        *
+*                                                                             *
+*  Remarques   : -                                                            *
+*                                                                             *
+******************************************************************************/
+
+static void *g_cdb_controller_process(GCdbController *controller)
+{
+    GServerBackend *base;                   /* Base de l'instance          */
+    struct pollfd fds[3];                   /* Surveillance des flux       */
+    int ret;                                /* Bilan d'un appel            */
+    packed_buffer in_pbuf;                  /* Tampon de réception         */
+    uint32_t tmp32;                         /* Valeur sur 32 bits          */
+    bool status;                            /* Bilan de lecture initiale   */
+    uint32_t command;                       /* Commande de la requête      */
+    DBError error;                          /* Bilan d'une opération       */
+    packed_buffer out_pbuf;                 /* Tampon d'émission           */
+    char *msg;                              /* Erreur à faire remonter     */
+
+    base = G_SERVER_BACKEND(controller);
+
+    fds[0].fd = base->stop_ctrl[0];
+    fds[0].events = POLLIN | POLLPRI;
+
+    fds[1].fd = base->refresh_ctrl[0];
+    fds[1].events = POLLIN | POLLPRI;
+
+    fds[2].fd = SSL_get_fd(controller->tls_fd);
+    fds[2].events = POLLIN | POLLPRI;
+
+    while (1)
+    {
+        /* Lancement d'une phase de surveillance */
+
+        ret = poll(fds, 3, -1);
+        if (ret == -1)
+        {
+            if (errno == EINTR) continue;
+
+            LOG_ERROR_N("poll");
+            break;
+
+        }
+
+        /* Demande expresse d'arrêt des procédures */
+        if (fds[0].revents)
+            break;
+
+        /* Demande d'actualisation ?! */
+        assert(fds[1].revents == 0);
+
+        /* Le canal est fermé, une sortie doit être demandée... */
+        if (fds[2].revents & POLLNVAL)
+            goto closed_exchange;
+
+        /**
+         * Même chose, cf. "TCP: When is EPOLLHUP generated?"
+         * https://stackoverflow.com/questions/52976152/tcp-when-is-epollhup-generated/52976327#52976327
+         */
+
+        if (fds[2].revents & (POLLHUP | POLLRDHUP))
+            goto closed_exchange;
+
+        /* Données présentes en entrée */
+        if (fds[2].revents & (POLLIN | POLLPRI))
+        {
+            init_packed_buffer(&in_pbuf);
+
+            status = ssl_recv_packed_buffer(&in_pbuf, controller->tls_fd);
+            if (!status) goto bad_exchange;
+
+ next_command:
+
+            status = extract_packed_buffer(&in_pbuf, &command, sizeof(uint32_t), true);
+            if (!status) goto bad_exchange;
+
+            switch (command)
+            {
+                case DBC_LIST_ARCHIVES:
+
+                    /*
+                    error = g_cdb_controller_write(archive);
+
+                        init_packed_buffer(&out_pbuf);
+
+                        status = extend_packed_buffer(&out_pbuf, (uint32_t []) { DBC_SAVE },
+                                                      sizeof(uint32_t), true);
+                        if (!status) goto reply_error;
+
+                        status = extend_packed_buffer(&out_pbuf, (uint32_t []) { error }, sizeof(uint32_t), true);
+                        if (!status) goto reply_error;
+
+                        status = ssl_send_packed_buffer(&out_pbuf, controller->tls_fd);
+                        if (!status) goto reply_error;
+                        */
+
+                        exit_packed_buffer(&out_pbuf);
+
+                        break;
+
+                default:
+                    asprintf(&msg, _("Bad protocol command: 0x%08x"), command);
+                    LOG_ERROR(LMT_ERROR, msg);
+                    free(msg);
+                    goto bad_exchange;
+                    break;
+
+            }
+
+            if (has_more_data_in_packed_buffer(&in_pbuf))
+                goto next_command;
+
+            exit_packed_buffer(&in_pbuf);
+
+            continue;
+
+ reply_error:
+
+            exit_packed_buffer(&out_pbuf);
+
+ bad_exchange:
+
+            LOG_ERROR(LMT_ERROR, _("Bad exchange"));
+
+            assert(0);
+
+            exit_packed_buffer(&in_pbuf);
+
+ closed_exchange:
+
+            break;
+
+        }
+
+    }
+
+    /* On disparaît des écrans... */
+
+    g_server_backend_stop(G_SERVER_BACKEND(controller));
+
+    return NULL;
+
+}
+
+
+/******************************************************************************
+*                                                                             *
+*  Paramètres  : controller = support pour le suivi d'une connexion.          *
+*                fd         = canal de communication réseau ouvert.           *
+*                peer_name  = désignation de la connexion.                    *
+*                user       = désignation de l'utilisateur de la connexion.   *
+*                                                                             *
+*  Description : Prend en compte une connexion nouvelle d'un utilisateur.     *
+*                                                                             *
+*  Retour      : -                                                            *
+*                                                                             *
+*  Remarques   : -                                                            *
+*                                                                             *
+******************************************************************************/
+
+static void g_cdb_controller_add_client(GCdbController *controller, SSL *fd, const char *peer_name, const char *user)
+{
+    controller->tls_fd = fd;
+
+    controller->peer_name = strdup(peer_name);
+    controller->user = strdup(user);
+
+}
+
+
+
+
+
+#if 0
+/******************************************************************************
+*                                                                             *
+*  Paramètres  : controller = archive à connecter avec un utilisateur.           *
+*                pbuf    = paquet à consituer pour un envoi unique. [OUT]     *
+*                                                                             *
+*  Description : Envoie à tous les clients la nouvelle liste d'instantanés.   *
+*                                                                             *
+*  Retour      : Bilan de constitution de la réponse.                         *
+*                                                                             *
+*  Remarques   : -                                                            *
+*                                                                             *
+******************************************************************************/
+
+static bool g_cdb_controller_send_snapshot_update(GCdbController *controller, packed_buffer *pbuf)
+{
+    bool result;                            /* Bilan à retourner           */
+    bool do_send;                           /* Réalisation de l'émission   */
+    packed_buffer out_pbuf;                 /* Tampon d'émission           */
+
+    do_send = (pbuf == NULL);
+
+    if (pbuf == NULL)
+        pbuf = &out_pbuf;
+
+    init_packed_buffer(pbuf);
+
+    result = extend_packed_buffer(pbuf, (uint32_t []) { DBC_SNAPSHOTS_UPDATED },
+                                  sizeof(uint32_t), true);
+    if (!result) goto bad_reply;
+
+    result = g_db_snapshot_pack_all(archive->snapshot, pbuf);
+    if (!result) goto bad_reply;
+
+    result = extend_packed_buffer(pbuf, SNAPSHOT_END_MARK, SNAP_ID_HEX_SZ, false);
+    if (!result) goto bad_reply;
+
+ bad_reply:
+
+    if (do_send || !result)
+        exit_packed_buffer(pbuf);
+
+    return result;
+
+}
+
+#endif
diff --git a/src/analysis/db/controller.h b/src/analysis/db/controller.h
new file mode 100644
index 0000000..adeee2b
--- /dev/null
+++ b/src/analysis/db/controller.h
@@ -0,0 +1,59 @@
+
+/* Chrysalide - Outil d'analyse de fichiers binaires
+ * controller.h - prototypes pour la gestion d'un ensemble d'archives au format CDB
+ *
+ * Copyright (C) 2014-2019 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 _ANALYSIS_DB_CONTROLLER_H
+#define _ANALYSIS_DB_CONTROLLER_H
+
+
+#include <glib-object.h>
+#include <stdbool.h>
+
+
+#include "protocol.h"
+
+
+
+#define G_TYPE_CDB_CONTROLLER            g_cdb_controller_get_type()
+#define G_CDB_CONTROLLER(obj)            (G_TYPE_CHECK_INSTANCE_CAST((obj), G_TYPE_CDB_CONTROLLER, GCdbController))
+#define G_IS_CDB_CONTROLLER(obj)         (G_TYPE_CHECK_INSTANCE_TYPE((obj), G_TYPE_CDB_CONTROLLER))
+#define G_CDB_CONTROLLER_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST((klass), G_TYPE_CDB_CONTROLLER, GCdbControllerClass))
+#define G_IS_CDB_CONTROLLER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), G_TYPE_CDB_CONTROLLER))
+#define G_CDB_CONTROLLER_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS((obj), G_TYPE_CDB_CONTROLLER, GCdbControllerClass))
+
+
+/* Description d'un contrôleur d'archives (instance) */
+typedef struct _GCdbController GCdbController;
+
+/* Description d'un contrôleur d'archives (classe) */
+typedef struct _GCdbControllerClass GCdbControllerClass;
+
+
+/* Indique le type défini pour une gestion d'archives. */
+GType g_cdb_controller_get_type(void);
+
+/* Prépare un client pour une connexion à une BD. */
+GCdbController *g_cdb_controller_new(const char *, DBError *);
+
+
+
+#endif  /* _ANALYSIS_DB_CONTROLLER_H */
diff --git a/src/analysis/db/protocol.h b/src/analysis/db/protocol.h
index f673c4b..41e3ae7 100644
--- a/src/analysis/db/protocol.h
+++ b/src/analysis/db/protocol.h
@@ -29,8 +29,12 @@
 /**
  * Version de la définition courante du protocole.
  */
-#define CDB_PROTOCOL_VERSION 0xc0de0004
+#define CDB_PROTOCOL_VERSION 0xc0de0005
 
+/**
+ * 0xc0de0005 :
+ *   - création des rôles d'aministrateur et d'analyste
+ */
 
 
 
@@ -58,7 +62,14 @@ typedef enum _DBStorage
 
 
 
+/* Rôle à envoyer lors des présentations */
+typedef enum _ClientRole
+{
+    CRL_UNDEFINED = 0,                      /* Rôle non défini             */
+    CRL_ADMIN     = 1,                      /* Rôle d'administrateur       */
+    CRL_ANALYST   = 2,                      /* Rôle d'analyste             */
 
+} ClientRole;
 
 
 
@@ -114,9 +125,38 @@ typedef enum _DBAction
  */
 typedef enum _DBCommand
 {
+    /**
+     * Gestion des commandes 'DBC_HELO' et 'DBC_WELCOME'.
+     *
+     * Le client envoie un tout premier paquet de la forme suivante :
+     *
+     *    [ Ordre de sauvegarde : DBC_HELO            ]
+     *    [ Protocole supporté : CDB_PROTOCOL_VERSION ]
+     *    [ Rôle visé ; cf ClientRole                 ]
+     *    [ Compléments selon le rôle visé            ]
+     *
+     * Le serveur effectue les validations et renvoie un bilan :
+     *
+     *    [ Ordre de sauvegarde : DBC_WELCOME         ]
+     *    [ Statut d'exécution ; cf. DBError          ]
+     *
+     */
+
     DBC_HELO,                               /* Connexion initiale C -> S   */
     DBC_WELCOME,                            /* Réponse initiale S -> C     */
 
+
+    /* ------------------------ Commandes pour administrateur ------------------------ */
+
+
+
+    DBC_LIST_ARCHIVES,                      /* Fourniture des identifiants */
+
+
+
+
+    /* ------------------------ Commandes pour analyste ------------------------ */
+
     /**
      * Gestion de la commande 'DBC_SAVE'.
      *
diff --git a/src/analysis/db/server.c b/src/analysis/db/server.c
index 258a66c..ad7929f 100644
--- a/src/analysis/db/server.c
+++ b/src/analysis/db/server.c
@@ -42,7 +42,9 @@
 
 
 #include "auth.h"
+#include "backend.h"
 #include "cdb.h"
+#include "controller.h"
 #include "protocol.h"
 #include "misc/rlestr.h"
 #include "../../common/extstr.h"
@@ -92,8 +94,10 @@ struct _GHubServer
 
     GThread *listener;                      /* Procédure de traitement     */
 
-    GList *archives;                        /* Liste des binaires ouverts  */
-    GMutex mutex;                           /* Verrou pour l'accès         */
+    GList *controllers;                     /* Liste des administrateurs   */
+    GMutex ctrl_mutex;                      /* Verrou pour l'accès         */
+    GList *archives;                        /* Liste des aanlystes         */
+    GMutex ar_mutex;                        /* Verrou pour l'accès         */
 
     GMutex wait_mutex;                      /* Accès à la condition        */
     GCond wait_cond;                        /* Attente de signal           */
@@ -136,6 +140,18 @@ static int g_hub_server_verify(int, X509_STORE_CTX *);
 /* Assure l'accueil des nouveaux clients. */
 static void *g_hub_server_listener(GHubServer *);
 
+/* Assure l'accueil des nouveaux clients administrateurs. */
+static GServerBackend *g_hub_server_handle_admin(GHubServer *, packed_buffer *, const char *, DBError *, bool *);
+
+/* Assure l'accueil des nouveaux clients analystes. */
+static GServerBackend *g_hub_server_handle_analyst(GHubServer *, packed_buffer *, const char *, DBError *, bool *);
+
+/* Enregistre dans une liste interne un support de suivi. */
+static void g_hub_server_register_backend(GHubServer *, GServerBackend *);
+
+/* Suit les variations du compteur de références d'un greffon. */
+static void on_backend_ref_toggle(GHubServer *, GServerBackend *, gboolean);
+
 
 
 /* Indique le type défini pour une description de serveur à l'écoute. */
@@ -190,7 +206,10 @@ static void g_hub_server_init(GHubServer *server)
     server->unlock_socket = NULL;
     server->lock_fd = -1;
 
-    g_mutex_init(&server->mutex);
+    server->controllers = NULL;
+    g_mutex_init(&server->ctrl_mutex);
+    server->archives = NULL;
+    g_mutex_init(&server->ar_mutex);
 
     g_mutex_init(&server->wait_mutex);
     g_cond_init(&server->wait_cond);
@@ -216,15 +235,23 @@ static void g_hub_server_dispose(GHubServer *server)
 
     g_hub_server_stop(server);
 
+    for (iter = g_list_first(server->controllers);
+         iter != NULL;
+         iter = g_list_first(server->controllers))
+    {
+        g_object_unref(G_OBJECT(iter->data));
+    }
+
+    g_mutex_clear(&server->ctrl_mutex);
+
     for (iter = g_list_first(server->archives);
          iter != NULL;
          iter = g_list_first(server->archives))
     {
         g_object_unref(G_OBJECT(iter->data));
-        server->archives = g_list_delete_link(server->archives, iter);
     }
 
-    g_mutex_clear(&server->mutex);
+    g_mutex_clear(&server->ar_mutex);
 
     g_mutex_clear(&server->wait_mutex);
     g_cond_clear(&server->wait_cond);
@@ -721,18 +748,16 @@ static void *g_hub_server_listener(GHubServer *server)
     gen_sockaddr_t peer;                    /* Adresse cliente             */
     int fd;                                 /* Canal établi vers un client */
     SSL *tls_fd;                            /* Même canal, mais sécurisé   */
-    rle_string hash;                        /* Empreinte du binaire visé   */
+    GServerBackend *backend;                /* Support de suivi créé       */
     const char *ip;                         /* Statut de la conversion     */
     char *peer_name;                        /* Désignation du correspondant*/
     DBError error;                          /* Validation de la connexion  */
-    GCdbArchive *archive;                   /* Destinataire final du client*/
-    GList *iter;                            /* Boucle de parcours          */
     packed_buffer in_pbuf;                  /* Tampon de réception         */
     bool status;                            /* Bilan d'une opération       */
     uint32_t cmd;                           /* Commande initiale lue       */
     uint32_t version;                       /* Version du client lue       */
-    char *basedir;                          /* Répertoire de stockage      */
-    char *tmpdir;                           /* Répertoire de travail       */
+    uint32_t role;                          /* Rôle visé par le client     */
+    bool new;                               /* Besoin d'ajout à une liste  */
     packed_buffer out_pbuf;                 /* Tampon d'émission           */
 
     fds.fd = server->fd;
@@ -782,12 +807,10 @@ static void *g_hub_server_listener(GHubServer *server)
                 goto invalid_conn;
             }
 
-            /* Initialisation à vide pour les sorties en erreur */
-
-            setup_empty_rle_string(&hash);
-
             /* Construction d'une représentation */
 
+            backend = NULL;
+
             if (*((sa_family_t *)&peer) == AF_UNIX)
                 peer_name = strdup(server->desc);
 
@@ -799,7 +822,7 @@ static void *g_hub_server_listener(GHubServer *server)
                 if (ip == NULL)
                 {
                     LOG_ERROR_N("inet_ntop");
-                    goto id_error;
+                    goto ip_error;
                 }
 
                 snprintf(peer_name + strlen(ip), 1 + 5, ":%hu", ntohs(peer.inet4_addr.sin_port));
@@ -814,7 +837,7 @@ static void *g_hub_server_listener(GHubServer *server)
                 if (ip == NULL)
                 {
                     LOG_ERROR_N("inet_ntop");
-                    goto id_error;
+                    goto ip_error;
                 }
 
                 snprintf(peer_name + strlen(ip), 1 + 5, ":%hu", ntohs(peer.inet6_addr.sin6_port));
@@ -825,14 +848,12 @@ static void *g_hub_server_listener(GHubServer *server)
                 goto invalid_conn;
 
             error = DBE_NONE;
-            archive = NULL;
-
-            iter = NULL;
 
             /**
              * Le premier "paquet" reçu de la part d'un client doit contenir les informations suivantes :
-             *    - la commande 'DBC_HELO'.
-             *    - le numéro de version du client.
+             *    - la commande 'DBC_HELO' ;
+             *    - le numéro de version du client ;
+             *    - le rôle attendu.
              *    - l'empreinte du binaire analysé.
              *
              * Tout ceci est à synchroniser avec la fonction g_db_client_start().
@@ -846,7 +867,7 @@ static void *g_hub_server_listener(GHubServer *server)
                 log_variadic_message(LMT_ERROR, _("Error while getting the initial packet from '%s'..."),
                                      peer_name);
                 error = DBE_BAD_EXCHANGE;
-                goto error_sending;
+                goto error_receiving;
             }
 
             status = extract_packed_buffer(&in_pbuf, &cmd, sizeof(uint32_t), true);
@@ -855,7 +876,7 @@ static void *g_hub_server_listener(GHubServer *server)
                 log_variadic_message(LMT_ERROR, _("Error while getting the initial command from '%s'..."),
                                      peer_name);
                 error = DBE_BAD_EXCHANGE;
-                goto error_sending;
+                goto error_receiving;
             }
 
             status = extract_packed_buffer(&in_pbuf, &version, sizeof(uint32_t), true);
@@ -864,16 +885,16 @@ static void *g_hub_server_listener(GHubServer *server)
                 log_variadic_message(LMT_ERROR, _("Error while getting the protocol version from '%s'..."),
                                      peer_name);
                 error = DBE_BAD_EXCHANGE;
-                goto error_sending;
+                goto error_receiving;
             }
 
-            status = unpack_rle_string(&hash, &in_pbuf);
+            status = extract_packed_buffer(&in_pbuf, &role, sizeof(uint32_t), true);
             if (!status)
             {
-                log_variadic_message(LMT_ERROR, _("Error while getting the binary hash from '%s'..."),
+                log_variadic_message(LMT_ERROR, _("Error while getting the expected role from '%s'..."),
                                      peer_name);
                 error = DBE_BAD_EXCHANGE;
-                goto error_sending;
+                goto error_receiving;
             }
 
             if (cmd != DBC_HELO)
@@ -881,65 +902,48 @@ static void *g_hub_server_listener(GHubServer *server)
                 log_variadic_message(LMT_ERROR, _("The client from '%s' did not introduce itself!"),
                                      peer_name);
                 error = DBE_BAD_EXCHANGE;
-                goto error_sending;
+                goto error_receiving;
             }
 
             if (version != CDB_PROTOCOL_VERSION)
             {
-                log_variadic_message(LMT_ERROR, _("The client from '%s' does not use the same protocol: 0x%08x vs 0x%08x..."),
+                log_variadic_message(LMT_ERROR,
+                                     _("The client from '%s' does not use the same protocol: 0x%08x vs 0x%08x..."),
                                      peer_name, be32toh(version), CDB_PROTOCOL_VERSION);
                 error = DBE_WRONG_VERSION;
-                goto error_sending;
+                goto error_receiving;
             }
 
-            if (is_rle_string_empty(&hash))
+            switch (role)
             {
-                log_variadic_message(LMT_ERROR, _("The submitted binary hash from '%s' is empty!"),
-                                     peer_name);
-                error = DBE_BAD_EXCHANGE;
-                goto error_sending;
-            }
-
-            /**
-             * On met en place le maximum ici, de manière à pouvoir indiquer une erreur
-             * en cas d'échec, et être le plus précis possible dans la courte réponse.
-             */
-
-            assert(error == DBE_NONE);
-
-            for (iter = g_list_first(server->archives);
-                 iter != NULL;
-                 iter = g_list_next(iter))
-            {
-                archive = G_CDB_ARCHIVE(iter->data);
-                if (g_cdb_archive_compare_hash(archive, &hash) == 0)
+                case CRL_ADMIN:
+                    backend = g_hub_server_handle_admin(server, &in_pbuf, peer_name, &error, &new);
                     break;
-            }
 
-            if (iter == NULL)
-            {
-                basedir = strdup(server->working);
-                basedir = stradd(basedir, "cdbs" G_DIR_SEPARATOR_S);
+                case CRL_ANALYST:
+                    backend = g_hub_server_handle_analyst(server, &in_pbuf, peer_name, &error, &new);
+                    break;
 
-                tmpdir = strdup(server->working);
-                tmpdir = stradd(tmpdir, "tmp" G_DIR_SEPARATOR_S);
+                default:
+                    log_variadic_message(LMT_ERROR, _("Unknown client role requested by '%s'"),
+                                         peer_name);
+                    backend = NULL;
+                    error = DBE_BAD_EXCHANGE;
+                    new = false;
+                    break;
 
-                archive = g_cdb_archive_new(basedir, tmpdir, &hash, &error);
+            }
 
-                free(tmpdir);
-                free(basedir);
+            assert((backend == NULL && error != DBE_NONE) || (backend != NULL && error == DBE_NONE));
 
-            }
+ error_receiving:
 
             /**
              * Le serveur doit répondre pour un message type :
-             *    - la commande 'DBC_WELCOME'.
-             *    - un identifiant d'erreur ('DBE_NONE', 'DBE_BAD_EXCHANGE'
-             *      ou 'DBE_WRONG_VERSION' ... 'DBE_LOADING_ERROR').
+             *    - la commande 'DBC_WELCOME' ;
+             *    - un identifiant d'erreur.
              */
 
- error_sending:
-
             exit_packed_buffer(&in_pbuf);
 
             init_packed_buffer(&out_pbuf);
@@ -959,41 +963,28 @@ static void *g_hub_server_listener(GHubServer *server)
              * lors des échanges initiaux, car ces derniers seraient alors précédés des mises à jour...
              */
 
-            if (archive != NULL)
+            if (backend != NULL)
             {
-                assert(error == DBE_NONE);
-
-                /* Si l'archive a été créée pour l'occasion... */
-                if (iter == NULL)
-                    server->archives = g_list_append(server->archives, archive);
-
-                g_cdb_archive_add_client(archive, tls_fd);
+                if (new)
+                    g_hub_server_register_backend(server, backend);
 
-                exit_packed_buffer(&out_pbuf);
-
-                free(peer_name);
-
-                exit_rle_string(&hash);
-
-                continue;
+                g_server_backend_add_client(backend, tls_fd, peer_name);
 
             }
 
-            assert(error != DBE_NONE);
-
  out_error:
 
-            exit_packed_buffer(&out_pbuf);
+            if (backend != NULL)
+                g_object_unref(G_OBJECT(backend));
 
-            /* Si l'archive a été créée pour l'occasion... */
-            if (iter == NULL && archive != NULL)
-                g_object_unref(G_OBJECT(archive));
+            exit_packed_buffer(&out_pbuf);
 
- id_error:
+ ip_error:
 
             free(peer_name);
 
-            exit_rle_string(&hash);
+            if (backend != NULL)
+                continue;
 
  invalid_conn:
 
@@ -1016,6 +1007,261 @@ static void *g_hub_server_listener(GHubServer *server)
 
 /******************************************************************************
 *                                                                             *
+*  Paramètres  : server    = serveur pour les accès distants à manipuler.     *
+*                in_pbuf   = reste des premières données reçues.              *
+*                peer_name = désignation de la connexion entrante.            *
+*                error     = code d'erreur issu du traitement. [OUT]          *
+*                new       = indique si le résultat doit être ajouté. [OUT]   *
+*                                                                             *
+*  Description : Assure l'accueil des nouveaux clients administrateurs.       *
+*                                                                             *
+*  Retour      : Instance de support de suivi mise en place.                  *
+*                                                                             *
+*  Remarques   : -                                                            *
+*                                                                             *
+******************************************************************************/
+
+static GServerBackend *g_hub_server_handle_admin(GHubServer *server, packed_buffer *in_pbuf, const char *peer_name, DBError *error, bool *new)
+{
+    GCdbController *result;                 /* Support de suivi à retourner*/
+    char *basedir;                          /* Répertoire de stockage      */
+
+    if (has_more_data_in_packed_buffer(in_pbuf))
+    {
+        log_variadic_message(LMT_ERROR, _("The client from '%s' provided to much data!"), peer_name);
+
+        result = NULL;
+
+        *error = DBE_BAD_EXCHANGE;
+        *new = false;
+
+    }
+    else
+    {
+        basedir = strdup(server->working);
+        basedir = stradd(basedir, "cdbs" G_DIR_SEPARATOR_S);
+
+        result = g_cdb_controller_new(basedir, error);
+
+        free(basedir);
+
+        *new = true;
+
+    }
+
+    return G_SERVER_BACKEND(result);
+
+}
+
+
+/******************************************************************************
+*                                                                             *
+*  Paramètres  : server    = serveur pour les accès distants à manipuler.     *
+*                in_pbuf   = reste des premières données reçues.              *
+*                peer_name = désignation de la connexion entrante.            *
+*                error     = code d'erreur issu du traitement. [OUT]          *
+*                new       = indique si le résultat doit être ajouté. [OUT]   *
+*                                                                             *
+*  Description : Assure l'accueil des nouveaux clients analystes.             *
+*                                                                             *
+*  Retour      : Instance de support de suivi mise en place.                  *
+*                                                                             *
+*  Remarques   : -                                                            *
+*                                                                             *
+******************************************************************************/
+
+static GServerBackend *g_hub_server_handle_analyst(GHubServer *server, packed_buffer *in_pbuf, const char *peer_name, DBError *error, bool *new)
+{
+    GCdbArchive *result;                    /* Support de suivi à retourner*/
+    rle_string hash;                        /* Empreinte du binaire visé   */
+    bool status;                            /* Bilan d'une opération       */
+    GList *iter;                            /* Boucle de parcours          */
+    GCdbArchive *archive;                   /* Destinataire final du client*/
+    char *basedir;                          /* Répertoire de stockage      */
+    char *tmpdir;                           /* Répertoire de travail       */
+
+    result = NULL;
+
+    *error = DBE_BAD_EXCHANGE;
+    *new = false;
+
+    /* Fin de réception des données envoyées */
+
+    status = unpack_rle_string(&hash, in_pbuf);
+    if (!status)
+    {
+        log_variadic_message(LMT_ERROR, _("Error while getting the binary hash from '%s'..."), peer_name);
+        goto error_receiving;
+    }
+
+    if (is_rle_string_empty(&hash))
+    {
+        log_variadic_message(LMT_ERROR, _("The submitted binary hash from '%s' is empty!"), peer_name);
+        goto wrong_receiving;
+    }
+
+    if (has_more_data_in_packed_buffer(in_pbuf))
+    {
+        log_variadic_message(LMT_ERROR, _("The client from '%s' provided to much data!"), peer_name);
+        goto wrong_receiving;
+    }
+
+    /* Recherche d'un support existant adapté */
+
+    g_mutex_lock(&server->ar_mutex);
+
+    for (iter = g_list_first(server->archives); iter != NULL; iter = g_list_next(iter))
+    {
+        archive = G_CDB_ARCHIVE(iter->data);
+
+        if (g_cdb_archive_compare_hash(archive, &hash) == 0)
+            break;
+
+    }
+
+    if (iter != NULL)
+    {
+        result = archive;
+        g_object_ref(G_OBJECT(result));
+    }
+
+    g_mutex_unlock(&server->ar_mutex);
+
+    /* Nouvelle création au besoin */
+
+    if (result == NULL)
+    {
+        basedir = strdup(server->working);
+        basedir = stradd(basedir, "cdbs" G_DIR_SEPARATOR_S);
+
+        tmpdir = strdup(server->working);
+        tmpdir = stradd(tmpdir, "tmp" G_DIR_SEPARATOR_S);
+
+        result = g_cdb_archive_new(basedir, tmpdir, &hash, error);
+
+        free(tmpdir);
+        free(basedir);
+
+        *new = true;
+
+    }
+
+ wrong_receiving:
+
+    exit_rle_string(&hash);
+
+ error_receiving:
+
+    return (result != NULL ? G_SERVER_BACKEND(result) : NULL);
+
+}
+
+
+/******************************************************************************
+*                                                                             *
+*  Paramètres  : server  = serveur pour les accès distants à manipuler.       *
+*                backend = support de suivi de connexion.                     *
+*                                                                             *
+*  Description : Enregistre dans une liste interne un support de suivi.       *
+*                                                                             *
+*  Retour      : -                                                            *
+*                                                                             *
+*  Remarques   : -                                                            *
+*                                                                             *
+******************************************************************************/
+
+static void g_hub_server_register_backend(GHubServer *server, GServerBackend *backend)
+{
+    GList **list;                           /* Liste à parcourir           */
+    GMutex *mutex;                          /* Verrou à manipuler          */
+
+    /* Sélection des éléments concernés */
+
+    if (G_IS_CDB_CONTROLLER(backend))
+    {
+        list = &server->controllers;
+        mutex = &server->ctrl_mutex;
+    }
+    else if (G_IS_CDB_ARCHIVE(backend))
+    {
+        list = &server->archives;
+        mutex = &server->ar_mutex;
+    }
+    else
+        assert(false);
+
+    /* Retrait de l'élément inutilisé */
+
+    g_mutex_lock(mutex);
+
+    g_object_ref(G_OBJECT(backend));
+
+    *list = g_list_append(*list, backend);
+
+    g_object_add_toggle_ref(G_OBJECT(backend), (GToggleNotify)on_backend_ref_toggle, server);
+
+    g_mutex_unlock(mutex);
+
+}
+
+
+/******************************************************************************
+*                                                                             *
+*  Paramètres  : server  = serveur pour les accès distants à manipuler.       *
+*                backend = support de suivi de connexion.                     *
+*                last    = indication sur la valeur du compteur de références.*
+*                                                                             *
+*  Description : Suit les variations du compteur de références d'un greffon.  *
+*                                                                             *
+*  Retour      : -                                                            *
+*                                                                             *
+*  Remarques   : -                                                            *
+*                                                                             *
+******************************************************************************/
+
+static void on_backend_ref_toggle(GHubServer *server, GServerBackend *backend, gboolean last)
+{
+    GList **list;                           /* Liste à parcourir           */
+    GMutex *mutex;                          /* Verrou à manipuler          */
+    GList *iter;                            /* Boucle de parcours          */
+
+    if (last)
+    {
+        /* Sélection des éléments concernés */
+
+        if (G_IS_CDB_CONTROLLER(backend))
+        {
+            list = &server->controllers;
+            mutex = &server->ctrl_mutex;
+        }
+        else if (G_IS_CDB_ARCHIVE(backend))
+        {
+            list = &server->archives;
+            mutex = &server->ar_mutex;
+        }
+        else
+            assert(false);
+
+        /* Retrait de l'élément inutilisé */
+
+        g_mutex_lock(mutex);
+
+        for (iter = g_list_first(*list); iter != NULL; iter = g_list_first(*list))
+        {
+            *list = g_list_delete_link(*list, iter);
+        }
+
+        g_object_remove_toggle_ref(G_OBJECT(backend), (GToggleNotify)on_backend_ref_toggle, server);
+
+        g_mutex_unlock(mutex);
+
+    }
+
+}
+
+
+/******************************************************************************
+*                                                                             *
 *  Paramètres  : server  = serveur pour les accès distants à manipuler.       *
 *                backlog = nombre de connexions maximal.                      *
 *                keep    = conservation du serveur en avant plan.             *
diff --git a/src/gui/dialogs/snapshots.c b/src/gui/dialogs/snapshots.c
index 8abbdf2..53fc7b0 100644
--- a/src/gui/dialogs/snapshots.c
+++ b/src/gui/dialogs/snapshots.c
@@ -60,7 +60,7 @@ static void on_server_selection_changed(GtkComboBox *, GtkBuilder *);
 static bool find_suitable_parent(GtkTreeModel *, GtkTreeIter *, const char *, GtkTreeIter *);
 
 /* Met à jour l'affichage avec une nouvelle liste d'instantanés. */
-static void on_snapshots_updated(GHubClient *, GtkBuilder *);
+static void on_snapshots_updated(GAnalystClient *, GtkBuilder *);
 
 /* Réinitialise la zone d'affichage des informations. */
 static void reset_information_area(GtkBuilder *);
@@ -181,11 +181,11 @@ GtkWidget *create_snapshots_dialog(GLoadedBinary *binary, GtkWindow *parent, Gtk
 
 static void on_dialog_destroy(GtkWidget *dialog, GtkBuilder *builder)
 {
-    GHubClient *client;                     /* Cible des interactions      */
+    GAnalystClient *client;                 /* Cible des interactions      */
 
     /* Déconnexion de l'ancien */
 
-    client = G_HUB_CLIENT(g_object_get_data(G_OBJECT(builder), "client"));
+    client = G_ANALYST_CLIENT(g_object_get_data(G_OBJECT(builder), "client"));
 
     if (client != NULL)
         g_signal_handlers_disconnect_by_func(client, on_snapshots_updated, builder);
@@ -208,14 +208,14 @@ static void on_dialog_destroy(GtkWidget *dialog, GtkBuilder *builder)
 
 static void on_server_selection_changed(GtkComboBox *combo, GtkBuilder *builder)
 {
-    GHubClient *client;                     /* Cible des interactions      */
+    GAnalystClient *client;                 /* Cible des interactions      */
     gint active;                            /* Indice du serveur retenu    */
     GLoadedBinary *binary;                  /* Binaire en cours d'étude    */
     GtkTreeStore *store;                    /* Modèle de gestion           */
 
     /* Déconnexion de l'ancien */
 
-    client = G_HUB_CLIENT(g_object_get_data(G_OBJECT(builder), "client"));
+    client = G_ANALYST_CLIENT(g_object_get_data(G_OBJECT(builder), "client"));
 
     if (client != NULL)
         g_signal_handlers_disconnect_by_func(client, on_snapshots_updated, builder);
@@ -327,7 +327,7 @@ static bool find_suitable_parent(GtkTreeModel *model, GtkTreeIter *iter, const c
 *                                                                             *
 ******************************************************************************/
 
-static void on_snapshots_updated(GHubClient *client, GtkBuilder *builder)
+static void on_snapshots_updated(GAnalystClient *client, GtkBuilder *builder)
 {
     GtkTreeStore *store;                    /* Modèle de gestion           */
     snapshot_info_t *info;                  /* Liste d'instantanés présents*/
@@ -351,7 +351,7 @@ static void on_snapshots_updated(GHubClient *client, GtkBuilder *builder)
 
     gtk_tree_store_clear(store);
 
-    status = g_hub_client_get_snapshots(client, &info, &count);
+    status = g_analyst_client_get_snapshots(client, &info, &count);
 
     if (!status)
     {
@@ -409,7 +409,7 @@ static void on_snapshots_updated(GHubClient *client, GtkBuilder *builder)
 
         /* Ajout de l'instantané courant */
 
-        status = g_hub_client_get_current_snapshot(client, &current);
+        status = g_analyst_client_get_current_snapshot(client, &current);
 
         if (status)
         {
@@ -692,7 +692,7 @@ static void restore_old_snapshot(GtkToolButton *button, GtkBuilder *builder)
     const char *raw;                        /* Identifiant brut            */
     snapshot_id_t id;                       /* Identifiant utilisable      */
     bool status;                            /* Bilan d'une conversion      */
-    GHubClient *client;                     /* Cible des interactions      */
+    GAnalystClient *client;                 /* Cible des interactions      */
 
     raw = g_object_get_data(G_OBJECT(builder), "selected");
 
@@ -700,9 +700,9 @@ static void restore_old_snapshot(GtkToolButton *button, GtkBuilder *builder)
 
     if (status)
     {
-        client = G_HUB_CLIENT(g_object_get_data(G_OBJECT(builder), "client"));
+        client = G_ANALYST_CLIENT(g_object_get_data(G_OBJECT(builder), "client"));
 
-        g_hub_client_restore_snapshot(client, &id);
+        g_analyst_client_restore_snapshot(client, &id);
 
     }
 
@@ -724,11 +724,11 @@ static void restore_old_snapshot(GtkToolButton *button, GtkBuilder *builder)
 
 static void create_new_snapshot(GtkToolButton *button, GtkBuilder *builder)
 {
-    GHubClient *client;                     /* Cible des interactions      */
+    GAnalystClient *client;                 /* Cible des interactions      */
 
-    client = G_HUB_CLIENT(g_object_get_data(G_OBJECT(builder), "client"));
+    client = G_ANALYST_CLIENT(g_object_get_data(G_OBJECT(builder), "client"));
 
-    g_hub_client_create_snapshot(client);
+    g_analyst_client_create_snapshot(client);
 
 }
 
@@ -750,7 +750,7 @@ static void remove_old_snapshot(GtkToolButton *button, GtkBuilder *builder)
     const char *raw;                        /* Identifiant brut            */
     snapshot_id_t id;                       /* Identifiant utilisable      */
     bool status;                            /* Bilan d'une conversion      */
-    GHubClient *client;                     /* Cible des interactions      */
+    GAnalystClient *client;                 /* Cible des interactions      */
 
     raw = g_object_get_data(G_OBJECT(builder), "selected");
 
@@ -758,9 +758,9 @@ static void remove_old_snapshot(GtkToolButton *button, GtkBuilder *builder)
 
     if (status)
     {
-        client = G_HUB_CLIENT(g_object_get_data(G_OBJECT(builder), "client"));
+        client = G_ANALYST_CLIENT(g_object_get_data(G_OBJECT(builder), "client"));
 
-        g_hub_client_remove_snapshot(client, &id, false);
+        g_analyst_client_remove_snapshot(client, &id, false);
 
     }
 
@@ -785,7 +785,7 @@ static void delete_old_snapshot(GtkToolButton *button, GtkBuilder *builder)
     const char *raw;                        /* Identifiant brut            */
     snapshot_id_t id;                       /* Identifiant utilisable      */
     bool status;                            /* Bilan d'une conversion      */
-    GHubClient *client;                     /* Cible des interactions      */
+    GAnalystClient *client;                 /* Cible des interactions      */
 
     raw = g_object_get_data(G_OBJECT(builder), "selected");
 
@@ -793,9 +793,9 @@ static void delete_old_snapshot(GtkToolButton *button, GtkBuilder *builder)
 
     if (status)
     {
-        client = G_HUB_CLIENT(g_object_get_data(G_OBJECT(builder), "client"));
+        client = G_ANALYST_CLIENT(g_object_get_data(G_OBJECT(builder), "client"));
 
-        g_hub_client_remove_snapshot(client, &id, true);
+        g_analyst_client_remove_snapshot(client, &id, true);
 
     }
 
@@ -820,7 +820,7 @@ static void apply_new_snapshot_info(GtkButton *button, GtkBuilder *builder)
     const char *raw;                        /* Identifiant brut            */
     snapshot_id_t id;                       /* Identifiant utilisable      */
     bool status;                            /* Bilan d'une conversion      */
-    GHubClient *client;                     /* Cible des interactions      */
+    GAnalystClient *client;                 /* Cible des interactions      */
     GtkEntry *entry;                        /* Zone de saisie à actualiser */
 
     raw = g_object_get_data(G_OBJECT(builder), "selected");
@@ -829,13 +829,13 @@ static void apply_new_snapshot_info(GtkButton *button, GtkBuilder *builder)
 
     if (status)
     {
-        client = G_HUB_CLIENT(g_object_get_data(G_OBJECT(builder), "client"));
+        client = G_ANALYST_CLIENT(g_object_get_data(G_OBJECT(builder), "client"));
 
         entry = GTK_ENTRY(gtk_builder_get_object(builder, "name"));
-        g_hub_client_set_snapshot_name(client, &id, gtk_entry_get_text(entry));
+        g_analyst_client_set_snapshot_name(client, &id, gtk_entry_get_text(entry));
 
         entry = GTK_ENTRY(gtk_builder_get_object(builder, "description"));
-        g_hub_client_set_snapshot_desc(client, &id, gtk_entry_get_text(entry));
+        g_analyst_client_set_snapshot_desc(client, &id, gtk_entry_get_text(entry));
 
     }
 
diff --git a/src/gui/panels/history.c b/src/gui/panels/history.c
index 79be0dd..d6ca678 100644
--- a/src/gui/panels/history.c
+++ b/src/gui/panels/history.c
@@ -676,7 +676,7 @@ static void do_history_undo(GtkButton *button, GHistoryPanel *panel)
     GtkTreeModel *model;                    /* Modèle de gestion de données*/
     GtkTreeIter iter;                       /* Pointeur vers la ligne visée*/
     GDbItem *item;                          /* Elément de collection       */
-    GHubClient *client;                     /* Connexion vers la base      */
+    GAnalystClient *client;                 /* Connexion vers la base      */
 
     builder = gtk_built_named_widget_get_builder(GTK_BUILT_NAMED_WIDGET(G_PANEL_ITEM(panel)->widget));
 
@@ -691,7 +691,7 @@ static void do_history_undo(GtkButton *button, GHistoryPanel *panel)
             gtk_tree_model_get(model, &iter, HTC_ITEM, &item, -1);
 
             client = g_loaded_binary_get_client(panel->binary, true);
-            g_hub_client_set_last_active(client, g_db_item_get_timestamp(item));
+            g_analyst_client_set_last_active(client, g_db_item_get_timestamp(item));
             g_object_unref(G_OBJECT(client));
 
             g_object_unref(G_OBJECT(item));
@@ -726,7 +726,7 @@ static void do_history_redo(GtkButton *button, GHistoryPanel *panel)
     GtkTreeModel *model;                    /* Modèle de gestion de données*/
     GtkTreeIter iter;                       /* Pointeur vers la ligne visée*/
     GDbItem *item;                          /* Elément de collection       */
-    GHubClient *client;                     /* Connexion vers la base      */
+    GAnalystClient *client;                 /* Connexion vers la base      */
 
     builder = gtk_built_named_widget_get_builder(GTK_BUILT_NAMED_WIDGET(G_PANEL_ITEM(panel)->widget));
 
@@ -739,7 +739,7 @@ static void do_history_redo(GtkButton *button, GHistoryPanel *panel)
         gtk_tree_model_get(model, &iter, HTC_ITEM, &item, -1);
 
         client = g_loaded_binary_get_client(panel->binary, true);
-        g_hub_client_set_last_active(client, g_db_item_get_timestamp(item));
+        g_analyst_client_set_last_active(client, g_db_item_get_timestamp(item));
         g_object_unref(G_OBJECT(client));
 
         g_object_unref(G_OBJECT(item));
diff --git a/tests/analysis/db/conn.py b/tests/analysis/db/conn.py
new file mode 100644
index 0000000..39c660a
--- /dev/null
+++ b/tests/analysis/db/conn.py
@@ -0,0 +1,125 @@
+
+from chrysacase import ChrysalideTestCase
+from pychrysalide.analysis.db import certs
+from pychrysalide.analysis.db import AdminClient
+from pychrysalide.analysis.db import HubServer
+import os
+import shutil
+import tempfile
+
+
+class TestDbConnection(ChrysalideTestCase):
+    """TestCase for analysis.db."""
+
+    @classmethod
+    def setUpClass(cls):
+
+        super(TestDbConnection, cls).setUpClass()
+
+        cls._tmp_path = tempfile.mkdtemp()
+
+        cls._server_path = '%s/.chrysalide/servers/localhost-9999/' % cls._tmp_path
+        os.makedirs(cls._server_path)
+
+        cls._server_authorized_path = '%s/authorized/' % cls._server_path
+        os.makedirs(cls._server_authorized_path)
+
+        cls._client_path = '%s/.chrysalide/clients/' % cls._tmp_path
+        os.makedirs(cls._client_path)
+
+        cls._client_cert_path = '%s/localhost-9999/' % cls._client_path
+        os.makedirs(cls._client_cert_path)
+
+        cls.log('Using temporary directory "%s"' % cls._tmp_path)
+
+
+    @classmethod
+    def tearDownClass(cls):
+
+        super(TestDbConnection, cls).tearDownClass()
+
+        os.system('ls -laihR %s' % cls._tmp_path)
+
+        cls.log('Delete directory "%s"' % cls._tmp_path)
+
+        shutil.rmtree(cls._tmp_path)
+
+
+    def testServerListening(self):
+        """List binaries available from server."""
+
+
+        from pychrysalide import core
+        #core.set_verbosity(0)
+
+
+
+        identity = {
+
+            'C': 'FR',
+            'CN': 'Test authority'
+
+        }
+
+        ret = certs.build_keys_and_ca(self._server_path, 'ca', 3650 * 24 * 60 * 60, identity)
+        self.assertTrue(ret)
+
+        identity = {
+
+            'C': 'FR',
+            'CN': 'Test server'
+
+        }
+
+        ret = certs.build_keys_and_request(self._server_path, 'server', identity);
+        self.assertTrue(ret)
+
+
+        ret = certs.sign_cert('%s/server-csr.pem' % self._server_path, '%s/ca-cert.pem' % self._server_path, \
+                              '%s/ca-key.pem' % self._server_path, '%s/server-cert.pem' % self._server_path, \
+                              3650 * 24 * 60 * 60)
+        self.assertTrue(ret)
+
+        identity = {
+
+            'C': 'FR',
+            'CN': 'Test admin'
+
+        }
+
+        ret = certs.build_keys_and_request(self._client_path, 'client', identity);
+        self.assertTrue(ret)
+
+        ret = certs.sign_cert('%s/client-csr.pem' % self._client_path, '%s/ca-cert.pem' % self._server_path, \
+                              '%s/ca-key.pem' % self._server_path, '%s/client-cert.pem' % self._client_cert_path, \
+                              3650 * 24 * 60 * 60)
+        self.assertTrue(ret)
+
+        shutil.copy('%s/ca-cert.pem' % self._server_path,
+                    '%s/ca-cert.pem' % self._client_cert_path)
+
+        shutil.copy('%s/client-cert.pem' % self._client_cert_path,
+                    '%s/client-cert.pem' % self._server_authorized_path)
+
+
+        os.environ['XDG_CONFIG_HOME'] = self._tmp_path
+        os.environ['HOME'] = self._tmp_path
+
+        server = HubServer('localhost', '9999')
+
+        #print(server)
+
+        ret = server.start()
+
+        #print(ret)
+
+        admin = AdminClient()
+
+        #print(admin)
+
+
+        ret = admin.start('localhost', '9999')
+
+        #print('FINAL::', ret)
+
+        #print(server)
-- 
cgit v0.11.2-87-g4458