From 8bf77ba6e5ef40d8bb936dc952ac2c8cc30aab3e Mon Sep 17 00:00:00 2001
From: Cyrille Bagard <nocbos@gmail.com>
Date: Mon, 21 Feb 2022 07:54:10 +0100
Subject: Make the server drive the network exchanges.

---
 plugins/pychrysalide/analysis/db/analyst.c   |   4 +
 plugins/pychrysalide/analysis/db/constants.c |  43 ++++++
 plugins/pychrysalide/analysis/db/constants.h |   3 +
 src/analysis/db/analyst.c                    | 140 ++++++++++++++++++++
 src/analysis/db/analyst.h                    |  14 ++
 src/analysis/db/cdb.c                        | 133 +++++++++++++++----
 src/analysis/db/protocol.h                   |  31 ++++-
 src/analysis/db/server.c                     |   4 +
 tests/analysis/db/analyst.py                 | 188 +++++++++++++++++++++++++++
 tests/analysis/db/conn.py                    |  43 ++++--
 10 files changed, 559 insertions(+), 44 deletions(-)
 create mode 100644 tests/analysis/db/analyst.py

diff --git a/plugins/pychrysalide/analysis/db/analyst.c b/plugins/pychrysalide/analysis/db/analyst.c
index 289db31..c55f34a 100644
--- a/plugins/pychrysalide/analysis/db/analyst.c
+++ b/plugins/pychrysalide/analysis/db/analyst.c
@@ -36,6 +36,7 @@
 
 #include "client.h"
 #include "collection.h"
+#include "constants.h"
 #include "../content.h"
 #include "../loaded.h"
 #include "../../access.h"
@@ -901,6 +902,9 @@ bool ensure_python_analyst_client_is_registered(void)
         if (!register_class_for_pygobject(dict, G_TYPE_ANALYST_CLIENT, type, get_python_hub_client_type()))
             return false;
 
+        if (!define_loading_status_hint_constants(type))
+            return false;
+
     }
 
     return true;
diff --git a/plugins/pychrysalide/analysis/db/constants.c b/plugins/pychrysalide/analysis/db/constants.c
index 8e7765a..5e8c20d 100644
--- a/plugins/pychrysalide/analysis/db/constants.c
+++ b/plugins/pychrysalide/analysis/db/constants.c
@@ -25,6 +25,7 @@
 #include "constants.h"
 
 
+#include <analysis/db/analyst.h>
 #include <analysis/db/item.h>
 #include <analysis/db/server.h>
 
@@ -152,3 +153,45 @@ bool define_hub_server_constants(PyTypeObject *type)
     return result;
 
 }
+
+
+/******************************************************************************
+*                                                                             *
+*  Paramètres  : type = type dont le dictionnaire est à compléter.            *
+*                                                                             *
+*  Description : Définit les constantes pour les indications de chargement.   *
+*                                                                             *
+*  Retour      : true en cas de succès de l'opération, false sinon.           *
+*                                                                             *
+*  Remarques   : -                                                            *
+*                                                                             *
+******************************************************************************/
+
+bool define_loading_status_hint_constants(PyTypeObject *type)
+{
+    bool result;                            /* Bilan à retourner           */
+    PyObject *values;                       /* Groupe de valeurs à établir */
+
+    values = PyDict_New();
+
+    result = add_const_to_group(values, "READY", LSH_READY);
+    if (result) result = add_const_to_group(values, "ON_WAIT_LIST", LSH_ON_WAIT_LIST);
+    if (result) result = add_const_to_group(values, "NEED_CONTENT", LSH_NEED_CONTENT);
+    if (result) result = add_const_to_group(values, "NEED_FORMAT", LSH_NEED_FORMAT);
+    if (result) result = add_const_to_group(values, "NEED_ARCH", LSH_NEED_ARCH);
+
+    if (!result)
+    {
+        Py_DECREF(values);
+        goto exit;
+    }
+
+    result = attach_constants_group_to_type_with_pyg_enum(type, false, "LoadingStatusHint", values,
+                                                          "Indication about a loading process state.",
+                                                          G_TYPE_LOADING_STATUS_HINT);
+
+ exit:
+
+    return result;
+
+}
diff --git a/plugins/pychrysalide/analysis/db/constants.h b/plugins/pychrysalide/analysis/db/constants.h
index 5f0afeb..ea7a0c0 100644
--- a/plugins/pychrysalide/analysis/db/constants.h
+++ b/plugins/pychrysalide/analysis/db/constants.h
@@ -39,6 +39,9 @@ bool define_db_item_constants(PyTypeObject *);
 /* Définit les constantes pour les serveurs de données. */
 bool define_hub_server_constants(PyTypeObject *);
 
+/* Définit les constantes pour les indications de chargement. */
+bool define_loading_status_hint_constants(PyTypeObject *);
+
 
 
 #endif  /* _PLUGINS_PYCHRYSALIDE_ANALYSIS_DB_CONSTANTS_H */
diff --git a/src/analysis/db/analyst.c b/src/analysis/db/analyst.c
index ab12cc1..2e1cc23 100644
--- a/src/analysis/db/analyst.c
+++ b/src/analysis/db/analyst.c
@@ -35,6 +35,17 @@
 
 
 
+
+
+
+
+/* ------------------------- PRISES EN COMPTE DES COMMANDES ------------------------- */
+
+
+
+
+
+
 /* Description de client à l'écoute (instance) */
 struct _GAnalystClient
 {
@@ -65,6 +76,8 @@ struct _GAnalystClientClass
 
     /* Signaux */
 
+    void (* ready) (GAnalystClient *);
+    void (* server_status_changed) (GAnalystClient *, LoadingStatusHint);
     void (* snapshots_updated) (GAnalystClient *);
     void (* snapshot_changed) (GAnalystClient *);
 
@@ -97,6 +110,56 @@ static bool g_analyst_client_update_current_snapshot(GAnalystClient *, packed_bu
 
 
 
+/* ------------------------- PRISES EN COMPTE DES COMMANDES ------------------------- */
+
+
+/* Prend en compte une évolution du statut côté serveur. */
+static bool g_analyst_client_handle_loading_hints(GAnalystClient *, packed_buffer_t *);
+
+
+
+
+/* ---------------------------------------------------------------------------------- */
+/*                                 GLUES POUR LA GLIB                                 */
+/* ---------------------------------------------------------------------------------- */
+
+
+/******************************************************************************
+*                                                                             *
+*  Paramètres  : -                                                            *
+*                                                                             *
+*  Description : Définit un type GLib pour l'énumération "LoadingStatusHint". *
+*                                                                             *
+*  Retour      : Type GLib enregistré.                                        *
+*                                                                             *
+*  Remarques   : -                                                            *
+*                                                                             *
+******************************************************************************/
+
+GType g_loading_status_hint_type(void)
+{
+    static GType result = 0;
+
+    static const GEnumValue values[] = {
+        { LSH_READY, "LSH_READY", "ready" },
+        { LSH_ON_WAIT_LIST, "LSH_ON_WAIT_LIST", "on_wait_list" },
+        { LSH_NEED_CONTENT, "LSH_NEED_CONTENT", "need_content" },
+        { LSH_NEED_FORMAT, "LSH_NEED_FORMAT", "need_format" },
+        { LSH_NEED_ARCH, "LSH_NEED_ARCH", "need_arch" },
+        { 0, NULL, NULL }
+    };
+
+    if (result == 0)
+        result = g_enum_register_static(g_intern_static_string("LoadingStatusHint"), values);
+
+    return result;
+
+}
+
+
+
+
+
 /* Indique le type défini pour une description de client à l'écoute. */
 G_DEFINE_TYPE(GAnalystClient, g_analyst_client, G_TYPE_HUB_CLIENT);
 
@@ -129,6 +192,22 @@ static void g_analyst_client_class_init(GAnalystClientClass *klass)
     client->complete_hello = (complete_client_hello_fc)g_analyst_client_complete_hello;
     client->recv_func = (GThreadFunc)g_analyst_client_update;
 
+    g_signal_new("ready",
+                 G_TYPE_ANALYST_CLIENT,
+                 G_SIGNAL_RUN_LAST,
+                 G_STRUCT_OFFSET(GAnalystClientClass, ready),
+                 NULL, NULL,
+                 g_cclosure_marshal_VOID__VOID,
+                 G_TYPE_NONE, 0);
+
+    g_signal_new("server-status-changed",
+                 G_TYPE_ANALYST_CLIENT,
+                 G_SIGNAL_RUN_LAST,
+                 G_STRUCT_OFFSET(GAnalystClientClass, server_status_changed),
+                 NULL, NULL,
+                 g_cclosure_marshal_VOID__ENUM,
+                 G_TYPE_NONE, 1, G_TYPE_LOADING_STATUS_HINT);
+
     g_signal_new("snapshots-updated",
                  G_TYPE_ANALYST_CLIENT,
                  G_SIGNAL_RUN_LAST,
@@ -430,6 +509,11 @@ static void *g_analyst_client_update(GAnalystClient *client)
 
             switch (command)
             {
+                case DBC_LOADING_STATUS:
+                    status = g_analyst_client_handle_loading_hints(client, &in_pbuf);
+                    if (!status) goto gdcu_bad_exchange;
+                    break;
+
                 case DBC_SAVE:
 
                     status = extract_packed_buffer(&in_pbuf, &tmp32, sizeof(uint32_t), true);
@@ -1285,3 +1369,59 @@ bool g_analyst_client_remove_snapshot(GAnalystClient *client, const snapshot_id_
     return result;
 
 }
+
+
+
+/* ---------------------------------------------------------------------------------- */
+/*                           PRISES EN COMPTE DES COMMANDES                           */
+/* ---------------------------------------------------------------------------------- */
+
+
+/******************************************************************************
+*                                                                             *
+*  Paramètres  : archive  = archive à connecter avec un utilisateur.          *
+*                in_pbuf  = paquet à consulter.                               *
+*                                                                             *
+*  Description : Prend en compte une évolution du statut côté serveur.        *
+*                                                                             *
+*  Retour      : Indication pour le maintien de la communication.             *
+*                                                                             *
+*  Remarques   : -                                                            *
+*                                                                             *
+******************************************************************************/
+
+static bool g_analyst_client_handle_loading_hints(GAnalystClient *client, packed_buffer_t *in_pbuf)
+{
+    bool result;                            /* Bilan à retourner           */
+    uleb128_t hint;                         /* Indication du serveur       */
+
+    result = unpack_uleb128(&hint, in_pbuf);
+
+    switch (hint)
+    {
+        case LSH_READY:
+            g_signal_emit_by_name(client, "ready");
+            break;
+
+        case LSH_ON_WAIT_LIST:
+            log_simple_message(LMT_INFO, _("Waiting for content from server..."));
+            break;
+
+        case LSH_NEED_CONTENT:
+        case LSH_NEED_FORMAT:
+        case LSH_NEED_ARCH:
+            g_signal_emit_by_name(client, "server-status-changed", hint);
+            break;
+
+        default:
+            log_variadic_message(LMT_ERROR,
+                                 _("Unknown loaded hint received (%x); unsupported newer protocol?"),
+                                 hint);
+            result = false;
+            break;
+
+    }
+
+    return result;
+
+}
diff --git a/src/analysis/db/analyst.h b/src/analysis/db/analyst.h
index 9f7b32b..459034e 100644
--- a/src/analysis/db/analyst.h
+++ b/src/analysis/db/analyst.h
@@ -38,6 +38,20 @@
 
 
 
+
+/* ------------------------------- GLUES POUR LA GLIB ------------------------------- */
+
+
+#define G_TYPE_LOADING_STATUS_HINT g_loading_status_hint_type()
+
+
+/* Définit un type GLib pour l'énumération "LoadingStatusHint". */
+GType g_loading_status_hint_type(void);
+
+
+
+
+
 #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))
diff --git a/src/analysis/db/cdb.c b/src/analysis/db/cdb.c
index c5d3af7..b1e47bc 100644
--- a/src/analysis/db/cdb.c
+++ b/src/analysis/db/cdb.c
@@ -101,9 +101,13 @@ struct _GCdbArchive
 
     char *filename;                         /* Chemin d'accès à l'archive  */
     char *tmpdir;                           /* Répertoire de travail       */
-    char *cnt_file;                         /* Fichier de contenu binaire  */
     char *xml_desc;                         /* Fichier de description      */
 
+    char *cnt_file;                         /* Fichier de contenu binaire  */
+
+    GMutex loading_access;                  /* Verrou pour l'accès         */
+
+
     GList *collections;                     /* Ensemble de modifications   */
 
     GDbSnapshot *snapshot;                  /* Instantanés de bases SQL    */
@@ -189,8 +193,8 @@ static bool g_cdb_archive_send_snapshot_change(GCdbArchive *, packed_buffer_t *)
 /* ------------------------- PRISES EN COMPTE DES COMMANDES ------------------------- */
 
 
-/* Prépare la réponse à envoyer à un client connecté. */
-static bool setup_server_answer(DBCommand, DBError, packed_buffer_t *);
+/* Prépare une courte réponse à envoyer à un client connecté. */
+static bool craft_server_short_answer(DBCommand, uleb128_t, packed_buffer_t *);
 
 /* Enregistre le contenu binaire lié à une analyse. */
 static bool g_cdb_archive_set_content(GCdbArchive *, packed_buffer_t *, packed_buffer_t *);
@@ -218,7 +222,7 @@ static cdb_client *create_cdb_client(SSL *fd, const char *peer_name, const char
 {
     cdb_client *result;                     /* Fiche d'entité à retourner  */
 
-    result = malloc(sizeof(cdb_client *));
+    result = malloc(sizeof(cdb_client));
 
     result->tls_fd = fd;
 
@@ -358,9 +362,11 @@ static void g_cdb_archive_init(GCdbArchive *archive)
 
     archive->filename = NULL;
     archive->tmpdir = NULL;
-    archive->cnt_file = NULL;
     archive->xml_desc = NULL;
 
+    archive->cnt_file = NULL;
+    g_mutex_init(&archive->loading_access);
+
     archive->collections = create_collections_list();
 
     archive->snapshot = NULL;
@@ -387,9 +393,11 @@ static void g_cdb_archive_dispose(GCdbArchive *archive)
 {
     g_server_backend_stop(G_SERVER_BACKEND(archive));
 
+    g_mutex_clear(&archive->clients_access);
+
     g_clear_object(&archive->snapshot);
 
-    g_mutex_clear(&archive->clients_access);
+    g_mutex_clear(&archive->loading_access);
 
     G_OBJECT_CLASS(g_cdb_archive_parent_class)->dispose(G_OBJECT(archive));
 
@@ -1459,38 +1467,100 @@ static void *g_cdb_archive_process(GCdbArchive *archive)
 *                                                                             *
 ******************************************************************************/
 
+static LoadingStatusHint g_cdb_archive_compute_loading_hint(GCdbArchive *archive)
+{
+    LoadingStatusHint result;               /* Statut à retourner          */
+
+
+    // Try
+    //    g_mutex_lock(&archive->loading_access);
+
+
+
+    // cnt_file
+
+    if (archive->cnt_file == NULL)
+        result = LSH_NEED_CONTENT;
+
+    else
+        result = LSH_NEED_FORMAT;
+
+
+
+
+
+    return result;
+
+}
+
+
+/******************************************************************************
+*                                                                             *
+*  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é     */
+    packed_buffer_t out_pbuf;               /* Tampon d'émission           */
+    LoadingStatusHint hint;                 /* Statut de chargement        */
+    bool status;                            /* Bilan de lecture initiale   */
+
+    client = create_cdb_client(fd, peer_name, user);
 
     /**
-     * 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.
+     * Le verrou encadrant les évolutions des contenus initiaux doit englober
+     * l'extension de la liste des clients.
      *
-     *    - 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.
+     * En effet, une évolution partielle peut intervenir dans la fonction
+     * g_cdb_archive_process(), à un moment au seul le verrou dans les
+     * évolutions sera posé (g_cdb_archive_set_content() par exemple).
      *
-     * 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.
+     * Or g_cdb_archive_compute_loading_hint() doit fournir ici un état qui ne
+     *  varie pas entre le calcul et l'envoi. Donc verrous sur les clients et
+     * l'état de l'archive doivent englover l'ensemble des traitements ci-après.
      */
 
-    client = create_cdb_client(fd, peer_name, user);
+    g_mutex_lock(&archive->loading_access);
 
     g_mutex_lock(&archive->clients_access);
 
-    archive->clients = realloc(archive->clients, ++archive->count * sizeof(cdb_client *));
+    hint = g_cdb_archive_compute_loading_hint(archive);
+
+    if (hint != LSH_READY)
+        hint = (archive->count == 0 ? hint : LSH_ON_WAIT_LIST);
+
+    init_packed_buffer(&out_pbuf);
+
+    status = craft_server_short_answer(DBC_LOADING_STATUS, hint, &out_pbuf);
+
+    if (status)
+        status = ssl_send_packed_buffer(&out_pbuf, fd);
+
+    exit_packed_buffer(&out_pbuf);
+
+    if (status)
+    {
+        archive->clients = realloc(archive->clients, ++archive->count * sizeof(cdb_client *));
+
+        archive->clients[archive->count - 1] = client;
 
-    archive->clients[archive->count - 1] = client;
+    }
 
     g_mutex_unlock(&archive->clients_access);
 
+    g_mutex_unlock(&archive->loading_access);
+
 }
 
 
@@ -1694,10 +1764,10 @@ static bool g_cdb_archive_send_snapshot_change(GCdbArchive *archive, packed_buff
 /******************************************************************************
 *                                                                             *
 *  Paramètres  : cmd      = commande à l'origine d'un traitement.             *
-*                error    = bilan de traitement à communiquer.                *
+*                value    = valeur à communiquer.                             *
 *                out_pbuf = paquet à consituer pour un retour au client. [OUT]*
 *                                                                             *
-*  Description : Prépare la réponse à envoyer à un client connecté.           *
+*  Description : Prépare une courte réponse à envoyer à un client connecté.   *
 *                                                                             *
 *  Retour      : Indication pour le maintien de la communication.             *
 *                                                                             *
@@ -1705,7 +1775,7 @@ static bool g_cdb_archive_send_snapshot_change(GCdbArchive *archive, packed_buff
 *                                                                             *
 ******************************************************************************/
 
-static bool setup_server_answer(DBCommand cmd, DBError error, packed_buffer_t *out_pbuf)
+static bool craft_server_short_answer(DBCommand cmd, uleb128_t value, packed_buffer_t *out_pbuf)
 {
     bool result;                            /* Bilan à retourner           */
 
@@ -1714,7 +1784,7 @@ static bool setup_server_answer(DBCommand cmd, DBError error, packed_buffer_t *o
     result = extend_packed_buffer(out_pbuf, (uint32_t []) { cmd }, sizeof(uint32_t), true);
 
     if (result)
-        result = extend_packed_buffer(out_pbuf, (uint32_t []) { error }, sizeof(uint32_t), true);
+        result = pack_uleb128((uleb128_t []){ value }, out_pbuf);
 
     return result;
 
@@ -1748,6 +1818,7 @@ static bool g_cdb_archive_set_content(GCdbArchive *archive, packed_buffer_t *in_
     const gchar *hash;                      /* Empreinte de ce contenu     */
     int fd;                                 /* Flux ouvert en écriture     */
     bool status;                            /* Bilan d'une écriture        */
+    LoadingStatusHint hint;                 /* Statut de chargement        */
 
     result = true;
     error = DBE_NONE;
@@ -1848,7 +1919,15 @@ static bool g_cdb_archive_set_content(GCdbArchive *archive, packed_buffer_t *in_
 
     /* Formulation de la réponse */
 
-    result = setup_server_answer(DBC_SET_CONTENT, error, out_pbuf);
+    result = craft_server_short_answer(DBC_SET_CONTENT, error, out_pbuf);
+
+    if (result && error == DBE_NONE)
+    {
+        hint = g_cdb_archive_compute_loading_hint(archive);
+
+        result = craft_server_short_answer(DBC_LOADING_STATUS, hint, out_pbuf);
+
+    }
 
  free_and_exit:
 
diff --git a/src/analysis/db/protocol.h b/src/analysis/db/protocol.h
index 17263c8..7707058 100644
--- a/src/analysis/db/protocol.h
+++ b/src/analysis/db/protocol.h
@@ -76,14 +76,21 @@ typedef enum _ServerPrivLevels
 
 
 
+/**
+ * Précisions pour la commande DBC_LOADING_STATUS.
+ */
+
+
 /* Eléments de base nécessaires */
-typedef enum _RequiredBasics
+typedef enum _LoadingStatusHint
 {
-    RBS_NONE    = 0x0,                      /* (Plus) rien n'est requis    */
-    RBS_CONTENT = 0x1,                      /* Contenu binaire à analyser  */
-    RBS_LOADED  = 0x2,                      /* Contenu binaire analysé     */
+    LSH_READY        = 0,                   /* (Plus) rien n'est requis    */
+    LSH_ON_WAIT_LIST = 1,                   /* Concurrence des connexions  */
+    LSH_NEED_CONTENT = 2,                   /* Suppléments nécessaires     */
+    LSH_NEED_FORMAT  = 3,                   /* Suppléments nécessaires     */
+    LSH_NEED_ARCH    = 4,                   /* Suppléments nécessaires     */
 
-} RequiredBasics;
+} LoadingStatusHint;
 
 
 
@@ -135,6 +142,8 @@ typedef enum _DBAction
  */
 typedef enum _DBCommand
 {
+    /* ------------------------- Commandes à portée générale ------------------------- */
+
     /**
      * Le client envoie un tout premier paquet de la forme suivante :
      *
@@ -176,6 +185,18 @@ typedef enum _DBCommand
     /* ------------------------ Commandes pour analyste ------------------------ */
 
     /**
+     * Gestion de la commande 'DBC_LOADING_STATUS'.
+     *
+     * Le serveur envoie un statut de prise en charge au début d'une connexion :
+     *
+     *    [ Indication du serveur : DBC_LOADING_STATUS]
+     *    [ Statut courant ; cf. LoadingStatusHint    ]
+     *
+     */
+
+    DBC_LOADING_STATUS,                     /* Indications initiales       */
+
+    /**
      * Gestion de la commande 'DBC_SET_CONTENT'.
      *
      * Le client connecté envoie un paquet de la forme suivante :
diff --git a/src/analysis/db/server.c b/src/analysis/db/server.c
index 5c6fd18..79d5df1 100644
--- a/src/analysis/db/server.c
+++ b/src/analysis/db/server.c
@@ -1088,6 +1088,8 @@ static GServerBackend *g_hub_server_handle_analyst(GHubServer *server, packed_bu
 
     /* Fin de réception des données envoyées */
 
+    setup_empty_rle_string(&hash);
+
     status = unpack_rle_string(&hash, in_pbuf);
     if (!status)
     {
@@ -1101,6 +1103,8 @@ static GServerBackend *g_hub_server_handle_analyst(GHubServer *server, packed_bu
         goto wrong_receiving_0;
     }
 
+    setup_empty_rle_string(&class);
+
     status = unpack_rle_string(&class, in_pbuf);
     if (!status)
     {
diff --git a/tests/analysis/db/analyst.py b/tests/analysis/db/analyst.py
new file mode 100644
index 0000000..7347810
--- /dev/null
+++ b/tests/analysis/db/analyst.py
@@ -0,0 +1,188 @@
+
+from chrysacase import ChrysalideTestCase
+import pychrysalide
+from pychrysalide.analysis.contents import MemoryContent
+from pychrysalide.analysis.db import certs
+from pychrysalide.analysis.db import AdminClient, AnalystClient
+from pychrysalide.analysis.db import HubServer
+import os
+import shutil
+import sys
+import tempfile
+import threading
+
+
+class TestDbConnection(ChrysalideTestCase):
+    """TestCase for analysis.db."""
+
+    @classmethod
+    def setUpClass(cls):
+
+        super(TestDbConnection, cls).setUpClass()
+
+        cls.log('Compile binary "strings" if needed...')
+
+        fullname = sys.modules[cls.__module__].__file__
+        dirpath = os.path.dirname(fullname)
+
+        cls._bin_path = os.path.realpath(dirpath + '/../../format/elf/')
+
+        os.system('make -C %s strings > /dev/null 2>&1' % cls._bin_path)
+
+        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()
+
+        cls.log('Delete built binaries...')
+
+        os.system('make -C %s clean > /dev/null 2>&1' % cls._bin_path)
+
+        ## 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()
+
+        # ret = admin.start('localhost', '9999')
+        # self.assertTrue(ret)
+
+        # def _on_existing_binaries_updated(adm, evt):
+        #     evt.set()
+
+        # event = threading.Event()
+
+        # admin.connect('existing-binaries-updated', _on_existing_binaries_updated, event)
+
+        # ret = admin.request_existing_binaries()
+        # self.assertTrue(ret)
+
+        # event.wait()
+
+        # self.assertEqual(len(admin.existing_binaries), 0)
+
+
+
+        cnt = MemoryContent(b'A' * 400 * 1024)
+
+
+
+        analyst = AnalystClient(cnt.checksum, "elf", [])
+
+
+
+        def _on_server_status_changed(analyst, hint, evt):
+            print(hint)
+            evt.set()
+
+
+        event = threading.Event()
+
+        analyst.connect('server-status-changed', _on_server_status_changed, event)
+
+
+
+        ret = analyst.start('localhost', '9999')
+        self.assertTrue(ret)
+
+
+        event.wait()
+
+        event.clear()
+
+        ret = analyst.send_content(cnt)
+        self.assertTrue(ret)
+
+
+        event.wait()
diff --git a/tests/analysis/db/conn.py b/tests/analysis/db/conn.py
index f388f60..248a036 100644
--- a/tests/analysis/db/conn.py
+++ b/tests/analysis/db/conn.py
@@ -1,7 +1,8 @@
 
 from chrysacase import ChrysalideTestCase
+from pychrysalide.analysis.contents import MemoryContent
 from pychrysalide.analysis.db import certs
-from pychrysalide.analysis.db import AdminClient
+from pychrysalide.analysis.db import AdminClient, AnalystClient
 from pychrysalide.analysis.db import HubServer
 import os
 import shutil
@@ -51,7 +52,7 @@ class TestDbConnection(ChrysalideTestCase):
 
 
         from pychrysalide import core
-        #core.set_verbosity(0)
+        core.set_verbosity(0)
 
 
 
@@ -117,21 +118,39 @@ class TestDbConnection(ChrysalideTestCase):
 
 
 
-        admin = AdminClient()
+        # admin = AdminClient()
 
-        ret = admin.start('localhost', '9999')
-        self.assertTrue(ret)
+        # ret = admin.start('localhost', '9999')
+        # self.assertTrue(ret)
+
+        # def _on_existing_binaries_updated(adm, evt):
+        #     evt.set()
+
+        # event = threading.Event()
+
+        # admin.connect('existing-binaries-updated', _on_existing_binaries_updated, event)
+
+        # ret = admin.request_existing_binaries()
+        # self.assertTrue(ret)
+
+        # event.wait()
 
-        def _on_existing_binaries_updated(adm, evt):
-            evt.set()
+        # self.assertEqual(len(admin.existing_binaries), 0)
 
-        event = threading.Event()
 
-        admin.connect('existing-binaries-updated', _on_existing_binaries_updated, event)
 
-        ret = admin.request_existing_binaries()
+        cnt = MemoryContent(b'A' * 400 * 1024)
+
+        print(cnt)
+
+        print(len(cnt.data))
+
+
+        analyst = AnalystClient(cnt.checksum, [])
+
+        ret = analyst.start('localhost', '9999')
         self.assertTrue(ret)
 
-        event.wait()
 
-        self.assertEqual(len(admin.existing_binaries), 0)
+        ret = analyst.send_content(cnt)
+        self.assertTrue(ret)
-- 
cgit v0.11.2-87-g4458