Polymorphisme

Le terme polymorphisme décrit la caractéristique d’un élément qui peut prendre plusieurs formes, comme l’eau qui se trouve à l’état solide, liquide ou gazeux. En informatique, le polymorphisme désigne un concept de la théorie des types, selon lequel un nom d’objet peut désigner des instances de classes différentes issues d’une même arborescence.

Liste des travaux pratiques

Cryptage des messages envoyés sur le réseau

Durant les derniers travaux pratiques, nous avons beaucoup travaillés avec les websockets. Cette technologie est vraiment intéressante puisqu'elle nous permet de communiquer entre deux systèmes numériques sur l'intranet et même depuis Internet.  Jusqu'à présent, nous ne nous sommes pas préoccupés de savoir comment transitaient les informations. Si nous prenions un analyseur de trame, nous remarquerions que les messages envoyés et reçus apparaîtraient en clair. Pour certaines données, cela ne pose pas de réels problèmes, mais pour d'autres, notamment pour la circulation de mots de passe, il serait souhaitable que l'information soit systématiquement criptée. C'est l'objet de de ce travaux pratique.

Mise en place d'un projet qui va nous permettre de valider le cryptage des informations.

Nous allons mettre en oeuvre un projet qui va nous permettre de réaliser le criptage d'un message quelconque et de le décripter par la suite, tout ceci sur une application simple en mode fenêtrée nous placerons ensuite ce système sur des applications client-serveur. Lorsque nous réalisons du criptage, souvent nous devons posséder une clef qui nous précisera comment décripter par la suite. Cette clef doit être présente côté serveur et côté client. Dans ce cas de figure, le criptage d'un message sera toujours le même pour un message identique.

Une autre approche consiste à réaliser une clef de criptage pour chaque message individuel. Ainsi, comme vous pouvez le remarquer sur les deux vues ci-dessus, à chaque fois que vous cliquez sur le bouton , un nouveau cryptage est constitué avec le même message original. Remarquez également que la taille du message crypté n'est pas identique cela rend la tâche du hacker potentiel plus difficile à décripter un tel texte.

Si vous désirez utiliser cette deuxième approche, la clef doit être impérativement intégrée à chacun des messages.
Structure de cryptage la clef de cryptage

Je vous propose une clef de cryptage pour réaliser ce projet. Bien d'autres sont bien entendu possibles. La seule limite est notre imagination :

  1. La première lettre du message crypté constitue la lettre de référence pour la suite de la description de la clef. Cette lettre est un caractère variable compris entre ! et ) du code ASCII.
  2. La deuxième lettre représente un chiffre de référence qui servira pour l'algorithme de cryptage pour le message lui-même. Ce chiffre a une valeur comprise entre 1 et 9, et il est représenté à l'aide de la lettre de référence décalage dans le code ASCII de la valeur du chiffre par rapport à la lettre de référence.
  3. La troisième lettre nous donne le nombre de chiffres supplémentaires constituant la clef de cryptage. Ce nombre de chiffres est compris également entre 1 et 9, et il est toujours représenté à l'aide de la lettre de référence.
  4. Suivent ensuite l'ensemble des chiffres qui vont servir à l'algorithme de cryptage. Comme précédemment, chacun de ces chiffres est compris entre 1 et 9, et il est représenté à l'aide de la lettre de référence.
Algorithme de cryptage

Pour chacune des lettres qui constituent le message à cripter, nous allons soustraire son code ASCII à l'aide à la fois du chiffre de référence et d'un des chiffres supplémentaires proposés par la clef de cryptage. Comme la valeur des chiffres qui constitue l'ensemble donné par la clef ne possède pas toujours la même valeur, le décalage négatif sera systématiquement différent. Par contre, nous aurons une certaine périodicité puisque la longueur du message à crypter sera certainement plus longue que le nombre de chiffres proposé par la clef de cryptage.

Mise en oeuvre du codage

cryptage.pro
QT       += core gui
greaterThan(QT_MAJOR_VERSION, 4): QT += widgets

TARGET = cryptage
TEMPLATE = app

CONFIG  += c++11
SOURCES += main.cpp principal.cpp cryptage.cpp
HEADERS  += principal.h cryptage.h
FORMS    += principal.ui
cryptage.h
#ifndef CRYPTAGE_H
#define CRYPTAGE_H

#include <string>
#include <ctime>
using namespace std;

class Cryptage
{
  static int random() { return (clock() % 9) + 1; }
public:
  static string crypter(const string &message);
  static string decrypter(const string &message);
};

#endif // CRYPTAGE_H
cryptage.cpp
#include "cryptage.h"

string Cryptage::crypter(const string &message)
{
  string encodage;
  char lettre = ' ' + random(); // lettre référence pour la clef
  encodage += lettre;
  int chiffre = random(); // chiffre référence pour le cryptage
  encodage += lettre + chiffre;
  int nombre = random();  // nombre d'éléments supplémentaires dans la clef
  encodage += lettre + nombre;
  int decalages[nombre]; // suite de nombre servant au calcul de chaque lettre du message
  for (int i=0; i<nombre; i++)
  {
    decalages[i] = random();
    encodage += lettre + decalages[i];
  }
  for (int i=0; i<message.size(); i++) encodage += message[i] - chiffre - decalages[i%nombre];
  return encodage;
}

string Cryptage::decrypter(const string &message)
{
  string decodage;
  int clef = 0;
  char lettre = message[clef++]; // lettre référence pour la clef
  int chiffre = message[clef++] - lettre; // chiffre référence pour le décryptage
  int nombre = message[clef++] - lettre;  // nombre d'éléments dans la clef
  int decalages[nombre];
  for (int i=0; i<nombre; i++)  decalages[i] = message[clef++] - lettre;
  for (int i=clef; i<message.size(); i++)  decodage += message[i] + chiffre + decalages[(i-clef)%nombre];
  return decodage;
}
principal.h
#ifndef PRINCIPAL_H
#define PRINCIPAL_H

#include "ui_principal.h"

class Principal : public QMainWindow, private Ui::Principal
{
  Q_OBJECT

public:
  explicit Principal(QWidget *parent = 0);
private slots:
  void lancerCryptage();
  void lancerDecryptage();
};

#endif // PRINCIPAL_H
principal.cpp
#include "principal.h"
#include "cryptage.h"

Principal::Principal(QWidget *parent) : QMainWindow(parent)
{
  setupUi(this);
}

void Principal::lancerCryptage()
{
  code->setText(Cryptage::crypter(original->text().toStdString()).c_str());
}

void Principal::lancerDecryptage()
{
  decode->setText(Cryptage::decrypter(code->text().toStdString()).c_str());
}

Communication cryptée entre la raspberry et un PC

Nous allons maintenant utiliser cette classe utilitaire de cryptage afin de vérifier la communication entre deux systèmes numériques, avec par exemple une rasberry pi d'un côté et un PC classique de l'autre. Les échanges se feront au travers du protocole websocket et la communication entre les deux systèmes sera cryptée si nous envoyons des messages de type binaire, et non criptée dans le cas de messages au format textuel.

Mise en oeuvre du codage côté raspberry

Nous allons reprendre la classe Service que nous avons déjà élaborée lors des projets précédents. Nous allons toutefois rajouter un certain nombre de fonctionnalités supplémentaires qui vont nous permettre d'intégrer la partie criptage des informations qui sera alors très utiles pour nombre de projets ultérieurs.

ServicePI.pro
QT       += core websockets
QT       -= gui

TARGET = ServicePI
target.path = /home/pi/Travail
INSTALLS += target

CONFIG   += console c++11
CONFIG   -= app_bundle

TEMPLATE = app

SOURCES += main.cpp service.cpp traitement.cpp criptage.cpp
HEADERS += service.h traitement.h criptage.h
cryptage.h
#ifndef CRYPTAGE_H
#define CRYPTAGE_H

#include <string>
#include <ctime>
using namespace std;

class Cryptage
{
  static int random() { return (clock() % 9) + 1; }
public:
  static string crypter(const string &message);
  static string decrypter(const string &message);
};

#endif // CRYPTAGE_H
cryptage.cpp
#include "cryptage.h"

string Cryptage::crypter(const string &message)
{
  string encodage;
  char lettre = ' ' + random(); // lettre référence pour la clef
  encodage += lettre;
  int chiffre = random(); // chiffre référence pour le criptage
  encodage += lettre + chiffre;
  int nombre = random();  // nombre d'éléments supplémentaires dans la clef
  encodage += lettre + nombre;
  int decalages[nombre]; // suite de nombre servant au calcul de chaque lettre du message
  for (int i=0; i<nombre; i++)
  {
    decalages[i] = random();
    encodage += lettre + decalages[i];
  }
  for (int i=0; i<message.size(); i++) encodage += message[i] - chiffre - decalages[i%nombre];
  return encodage;
}

string Cryptage::decrypter(const string &message)
{
  string decodage;
  int clef = 0;
  char lettre = message[clef++]; // lettre référence pour la clef
  int chiffre = message[clef++] - lettre; // chiffre référence pour le décriptage
  int nombre = message[clef++] - lettre;  // nombre d'éléments dans la clef
  int decalages[nombre];
  for (int i=0; i<nombre; i++)  decalages[i] = message[clef++] - lettre;
  for (int i=clef; i<message.size(); i++)  decodage += message[i] + chiffre + decalages[(i-clef)%nombre];
  return decodage;
}
service.h
#ifndef SERVICE_H
#define SERVICE_H

#include "cryptage.h"
#include <QObject>
#include <QWebSocketServer>
#include <QWebSocket>
#include <vector>
using namespace std;

class Service : public QObject
{
  Q_OBJECT
public:
  explicit Service(QObject *parent, const char *nom, int port);
protected:
  vector<QWebSocket *> connectes;
  QWebSocket *client;
  bool cryptage = false;
  string message;
private:
  QWebSocketServer service;
signals:
  void arreter();
protected:
  void crypter(const QByteArray &octets)   { message = Cryptage::crypter(octets.data()); }
  void decrypter(const QByteArray &octets) { message = Cryptage::decrypter(octets.data()); }
public slots:
  virtual void nouvelleConnexion();
  virtual void receptionMessage(const QString &texte);
  virtual void receptionOctets(const QByteArray &octets);
  virtual void deconnexion();
};

#endif // SERVICE_H
service.cpp
#include "service.h"

Service::Service(QObject *parent, const char *nom, int port) : QObject(parent), service(nom, QWebSocketServer::NonSecureMode, this)
{
  if (service.listen(QHostAddress::Any, port))
    connect(&service, SIGNAL(newConnection()), this, SLOT(nouvelleConnexion()));
  else arreter();
}

void Service::nouvelleConnexion()
{
  client = service.nextPendingConnection();
  connectes.push_back(client);
  client->sendTextMessage("Bienvenue !");
  connect(client, SIGNAL(textMessageReceived(QString)), this, SLOT(receptionMessage(QString)));
  connect(client, SIGNAL(binaryMessageReceived(QByteArray)), this, SLOT(receptionOctets(QByteArray)));
  connect(client, SIGNAL(disconnected()), this, SLOT(deconnexion()));
}

void Service::receptionMessage(const QString &texte)
{
  if (texte=="stop") { arreter(); return; }
  client = (QWebSocket *) sender();
}

void Service::receptionOctets(const QByteArray &octets)
{
  if (cryptage) decrypter(octets);
  client = (QWebSocket *) sender();
}

void Service::deconnexion()
{
  QWebSocket *client = (QWebSocket *) sender();
  client->deleteLater();
  connectes.erase(find(connectes.begin(), connectes.end(), client));
  disconnect(client, SIGNAL(textMessageReceived(QString)), this, SLOT(receptionMessage(QString)));
  disconnect(client, SIGNAL(binaryMessageReceived(QByteArray)), this, SLOT(receptionOctets(QByteArray)));
  disconnect(client, SIGNAL(disconnected()), this, SLOT(deconnexion()));
}
traitement.h
#ifndef TRAITEMENT_H
#define TRAITEMENT_H

#include "service.h"

class Traitement : public Service
{
public:
  Traitement() : Service(nullptr, "service", 8080) { cryptage = true; }
  void nouvelleConnexion() override;
  void receptionMessage(const QString &texte) override;
  void receptionOctets(const QByteArray &octets) override;
  void deconnexion() override;
};

#endif // TRAITEMENT_H
traitement.cpp
#include "traitement.h"
#include <iostream>

void Traitement::nouvelleConnexion()
{
  Service::nouvelleConnexion();
  cout << client->peerAddress().toString().toStdString() << endl;
  cout << client->requestUrl().toString().toStdString()  << endl;
}

void Traitement::receptionMessage(const QString &texte)
{
  Service::receptionMessage(texte);
  cout << texte.toStdString() << endl;
}

void Traitement::receptionOctets(const QByteArray &octets)
{
  cout << octets.data() << endl;
  Service::receptionOctets(octets);
  cout << message << endl;
}

void Traitement::deconnexion()
{
  cout << "client déconnecté" << endl;
  Service::deconnexion();
}
Mise en oeuvre du codage côté PC

ClientPI.pro
QT       += core gui websockets
greaterThan(QT_MAJOR_VERSION, 4): QT += widgets

TARGET = ClientPI
TEMPLATE = app
CONFIG  += c++11

SOURCES += main.cpp principal.cpp criptage.cpp
HEADERS  += principal.h criptage.h
FORMS    += principal.ui
criptage.h
#ifndef CRYPTAGE_H
#define CRYPTAGE_H

#include <string>
#include <ctime>
using namespace std;

class Cryptage
{
  static int random() { return (clock() % 9) + 1; }
public:
  static string crypter(const string &message);
  static string decrypter(const string &message);
};

#endif // CRIPTAGE_H
cryptage.cpp
#include "cryptage.h"

string Cryptage::crypter(const string &message)
{
  string encodage;
  char lettre = ' ' + random(); // lettre référence pour la clef
  encodage += lettre;
  int chiffre = random(); // chiffre référence pour le criptage
  encodage += lettre + chiffre;
  int nombre = random();  // nombre d'éléments supplémentaires dans la clef
  encodage += lettre + nombre;
  int decalages[nombre]; // suite de nombre servant au calcul de chaque lettre du message
  for (int i=0; i<nombre; i++)
  {
    decalages[i] = random();
    encodage += lettre + decalages[i];
  }
  for (int i=0; i<message.size(); i++) encodage += message[i] - chiffre - decalages[i%nombre];
  return encodage;
}

string Cryptage::decrypter(const string &message)
{
  string decodage;
  int clef = 0;
  char lettre = message[clef++]; // lettre référence pour la clef
  int chiffre = message[clef++] - lettre; // chiffre référence pour le décriptage
  int nombre = message[clef++] - lettre;  // nombre d'éléments dans la clef
  int decalages[nombre];
  for (int i=0; i<nombre; i++)  decalages[i] = message[clef++] - lettre;
  for (int i=clef; i<message.size(); i++)  decodage += message[i] + chiffre + decalages[(i-clef)%nombre];
  return decodage;
}
principal.h
#ifndef PRINCIPAL_H
#define PRINCIPAL_H

#include "ui_principal.h"
#include <QWebSocket>

class Principal : public QMainWindow, private Ui::Principal
{
  Q_OBJECT

public:
  explicit Principal(QWidget *parent = 0);
private slots:
  void seConnecter(bool etat);
  void soumettreChaine();
  void soumettreOctets();
private:
  QWebSocket ws;
};

#endif // PRINCIPAL_H
principal.cpp
#include "principal.h"
#include "cryptage.h"

Principal::Principal(QWidget *parent) : QMainWindow(parent)
{
  setupUi(this);
}

void Principal::seConnecter(bool etat)
{
  if (etat)
  {
    QString url = "ws://";
    url+=adresse->text();
    url+=":8080/";
    ws.open(QUrl(url));
  }
  else ws.close();
}

void Principal::soumettreChaine()
{
  ws.sendTextMessage(message->text());
}

void Principal::soumettreOctets()
{
  ws.sendBinaryMessage(Cryptage::crypter(message->text().toStdString()).c_str());
}

Base de données SQLITE

Il est souvent très utiles de pouvoir sauvegarder des informations pour les rendre persistantes. Nous pensons tout de suite aux bases de données. Généralement, les données à sauvegarder ont très peu de volume et il ne serait pas approprié d'utiliser pour cela un serveur de bases de données surtout pour les systèmes embarqués comme les raspberry pi. Heureusement, il existe pour cela le système SQLITE qui permet d'enregistrer les données dans un fichier persistance et de pouvoir ensuite les retrouver quand nous le désirons, tout ceci en utilisant des requête SQL comme nous le ferions avec un véritable serveur de bases de données. La base de données gérée par SQLITE existe uniquement pour le projet dans laquelle elle a été créée. Grâce à ce mécanisme, vous n'avez pas besoin de de sécurisation et les transactions sont très rapides puisque tout se situe en local pas de communication réseau et seule l'application qui la créée l'utilise.

Classes utilisées par QT pour la gestion de SQLITE

La première classe QSqlDataBase s'occupe ce créer la base de données en choisissant les bons pilotes pour être en connexion avec n'importe quel serveur de bases de données. La deuxième classe QSqlQuery représente un bon moyen d'exécuter directement des instructions SQL et de gérer leurs résultats. Avant d'exécuter des requêtes SQL, nous devons tout d'abord établir une connexion avec une base de données à l'aide de la classe QSqlDataBase.

Méthodes de la classe QSqlDataBase
Méthodes de la classe QSqlQuery
Mise en oeuvre du codage

Afin de prendre en compte la gestion des bases de données, vous êtes obligés d'intégrer le module sql dans votre fichier de projet.

comptes.pro
QT       += core gui sql

greaterThan(QT_MAJOR_VERSION, 4): QT += widgets

TARGET = comptes
TEMPLATE = app
CONFIG  += c++11

SOURCES += main.cpp principal.cpp
HEADERS  += principal.h
FORMS    += principal.ui
principal.h
#ifndef PRINCIPAL_H
#define PRINCIPAL_H

#include "ui_principal.h"

class Principal : public QMainWindow, private Ui::Principal
{
  Q_OBJECT

public:
  explicit Principal(QWidget *parent = 0);
private:
  void ouvertureBD();
  void creationTable();
  void listeEnregistrements();
private slots:
  void nouvelEnregistrement();
  void modifierEnregistrement();
  void supprimerEnregistrement();
  void rechercherEnregistrement();
  void toutEffacer();
  void rendreVisible(bool etat);
  void changerEnregistrement(QString identifiant);
};

#endif // PRINCIPAL_H
principal.cpp
#include "principal.h"
#include <QSqlDatabase>
#include <QSqlQuery>
#include <QSqlError>

Principal::Principal(QWidget *parent) : QMainWindow(parent)
{
  setupUi(this);
  ouvertureBD();
  listeEnregistrements();
}

void Principal::ouvertureBD()
{
  QSqlDatabase bd = QSqlDatabase::addDatabase("QSQLITE");
  bd.setDatabaseName("comptes.bd");
  if (bd.open()) creationTable();
  else barreEtat->showMessage("BD inaccessible");
}

void Principal::creationTable()
{
  QSqlQuery requete;
  if (requete.exec("CREATE TABLE IF NOT EXISTS COMPTES"
          "(compte VARCHAR(15) PRIMARY KEY,"
          "pseudo VARCHAR(15),"
          "mdp VARCHAR(15),"
          "messagerie VARCHAR(30),"
          "internet VARCHAR(40),"
          "commentaire VARCHAR(80))")) barreEtat->showMessage("Base de données disponible");
  else barreEtat->showMessage(requete.lastError().text());
}

void Principal::listeEnregistrements()
{
  liste->clear();
  QSqlQuery requete("SELECT compte FROM COMPTES ORDER BY compte");
  while (requete.next()) liste->addItem(requete.value("compte").toString());
}

void Principal::changerEnregistrement(QString identifiant)
{
  compte->setText(identifiant);
  rechercherEnregistrement();
}

void Principal::nouvelEnregistrement()
{
  QSqlQuery requete;
  requete.prepare("INSERT INTO COMPTES (compte, pseudo, mdp, messagerie, internet, commentaire)"
                  " VALUES (:compte, :pseudo, :mdp, :messagerie, :internet, :commentaire)");
  requete.bindValue(":compte", compte->text());
  requete.bindValue(":pseudo", pseudo->text());
  requete.bindValue(":mdp", mdp->text());
  requete.bindValue(":messagerie", messagerie->text());
  requete.bindValue(":internet", internet->text());
  requete.bindValue(":commentaire", commentaire->text());
  if (requete.exec()) barreEtat->showMessage("Nouveau compte enregistré");
  else barreEtat->showMessage(requete.lastError().text());
  listeEnregistrements();
}

void Principal::modifierEnregistrement()
{
  QSqlQuery requete;
  requete.prepare("UPDATE COMPTES "
                  "SET pseudo=:pseudo, mdp=:mdp, messagerie=:messagerie, internet=:internet, commentaire=:commentaire "
                  "WHERE compte=:compte");
  requete.bindValue(":compte", compte->text());
  requete.bindValue(":pseudo", pseudo->text());
  requete.bindValue(":mdp", mdp->text());
  requete.bindValue(":messagerie", messagerie->text());
  requete.bindValue(":internet", internet->text());
  requete.bindValue(":commentaire", commentaire->text());
  if (requete.exec()) barreEtat->showMessage("Compte modifié");
  else barreEtat->showMessage(requete.lastError().text());
}

void Principal::supprimerEnregistrement()
{
  QSqlQuery requete;
  requete.prepare("DELETE FROM COMPTES WHERE compte = :compte");
  requete.bindValue(":compte", compte->text());
  if (requete.exec()) { barreEtat->showMessage("Compte supprimer"); listeEnregistrements(); }
  else barreEtat->showMessage(requete.lastError().text());
}

void Principal::rechercherEnregistrement()
{
  QSqlQuery requete;
  requete.prepare("SELECT * FROM COMPTES WHERE UPPER(compte) = UPPER(:compte)");
  requete.bindValue(":compte", compte->text());
  if (requete.exec()) {
    barreEtat->showMessage("Recherche aboutie");
    if (requete.next())
    {
       pseudo->setText(requete.value("pseudo").toString());
       mdp->setText(requete.value("mdp").toString());
       messagerie->setText(requete.value("messagerie").toString());
       internet->setText(requete.value("internet").toString());
       commentaire->setText(requete.value("commentaire").toString());
    }
    else barreEtat->showMessage("Cet enregistrement n'existe pas");
  }
  else barreEtat->showMessage(requete.lastError().text());
}

void Principal::toutEffacer()
{
  compte->clear();
  pseudo->clear();
  mdp->clear();
  messagerie->clear();
  internet->clear();
  commentaire->clear();
  barreEtat->clearMessage();
}

void Principal::rendreVisible(bool etat)
{
  mdp->setEchoMode(etat ? QLineEdit::Password : QLineEdit::Normal);
}

Client-serveur avec base de données et criptage

Pour ce projet, nous allons fusionner les deux études précédentes afin de permettre la communication entre la rasberry pi d'un côté et le PC classique de l'autre. Les échanges se feront toujours au travers du protocole websocket et la communication entre les deux systèmes sera criptée. Le service à réaliser sera la gestion de comptes que nous avons élaborés dans le projet précédent et qui sera enregistrée dans une base de données SQLITE dans la rasberry pi. Enfin, toutes les informations qui transiterons au travers du réseau concernant la création, la modification et la suppression de compte seront envoyées au format JSON.

Format JSON

JSON est un format léger pour l'échange de données structurées complexes. Il est à l'image des documents XML en moins verbeux. Il est très utile lorsque vous devez transférer toutes les informations relatives à une structure ou à un objet persistant par exemple. Voici ci-dessous un exemple de document JSON représentant un objet de type Personne qui peut disposer de plusieurs numéros de téléphones :

{
   "id" : 51,
   "nom" : "DURAND",
   "prénom" : "Paul",
   "naissance" : 18/11/1969,
   "téléphones" : ["04-45-18-99-77", "06-89-89-87-23", "04-72-33-55-84"]
}
L'ossature du document ressemble à une structure dont le début et la fin sont désignés par des accolades. Chaque élément du document possède une clé et une valeur associée. L'ensemble des éléments sont séparés par des virgules. Pour la définition de la clé et de la valeur, vous devez l'écrire entre guillemets, sauf éventuellement pour les valeurs numériques. Enfin, si une clé possède plusieurs valeurs, vous devez les spécifier entre des crochets séparées par des virgules et toujours écrites entre guillemets. La librairie QT dispose de classes toutes prètes pour générer ou lire des documents au format JSON. Vous avez la classe QJsonDocument qui permet de prendre en compte l'ensemble du document pour sa génération ou sa lecture, la classe QJsonObject qui permettra de créer ou lire la structure globale de l'entité et la classe QJsonArray qui sera capable de générer ou de lire un ensemble de valeurs pour une même clé. La librairie QT stocke l'ensemble des éléments de l'objet de type QJsonObject dans une collection de type map, ce qui correspond bien au principe de l'association clé/valeur. Grâce à ce type de collection, il est très facile de retrouver chacun des éléments en utilisant les crochets. Les valeurs retournées ou prises en compte des éléments sont de type QVariant. Cette classe peut ensuite convertir directement vos valeurs vers des types correspond au langage C++ ou propres à la librairie QT, grâce à des méthodes associées comme toString(), toInt(), toDouble(), toDate(), etc.
Mise en oeuvre du codage côté raspberry

Dans le code source, nous avons rajouter quelques lignes de code qui affichent sur le terminal les documents JSON reçus et envoyés afin de bien contrôler le format après décriptage et avant criptage.

comptesPI.pro
QT       += core websockets sql
QT       -= gui

TARGET = comptes
target.path = /home/pi/Travail
INSTALLS += target

CONFIG   += console c++11
CONFIG   -= app_bundle

TEMPLATE = app

SOURCES += main.cpp service.cpp traitement.cpp criptage.cpp
HEADERS += service.h traitement.h criptage.h
criptage.h
#ifndef CRIPTAGE_H
#define CRIPTAGE_H

#include <string>
#include <ctime>
using namespace std;

class Criptage
{
  static int random() { return (clock() % 9) + 1; }
public:
  static string cripter(const string &message);
  static string decripter(const string &message);
};

#endif // CRIPTAGE_H
criptage.cpp
#include "criptage.h"

string Criptage::cripter(const string &message)
{
  string encodage;
  char lettre = ' ' + random(); // lettre référence pour la clef
  encodage += lettre;
  int chiffre = random(); // chiffre référence pour le criptage
  encodage += lettre + chiffre;
  int nombre = random();  // nombre d'éléments supplémentaires dans la clef
  encodage += lettre + nombre;
  int decalages[nombre]; // suite de nombre servant au calcul de chaque lettre du message
  for (int i=0; i<nombre; i++)
  {
    decalages[i] = random();
    encodage += lettre + decalages[i];
  }
  for (int i=0; i<message.size(); i++) encodage += message[i] - chiffre - decalages[i%nombre];
  return encodage;
}

string Criptage::decripter(const string &message)
{
  string decodage;
  int clef = 0;
  char lettre = message[clef++]; // lettre référence pour la clef
  int chiffre = message[clef++] - lettre; // chiffre référence pour le décriptage
  int nombre = message[clef++] - lettre;  // nombre d'éléments dans la clef
  int decalages[nombre];
  for (int i=0; i<nombre; i++)  decalages[i] = message[clef++] - lettre;
  for (int i=clef; i<message.size(); i++)  decodage += message[i] + chiffre + decalages[(i-clef)%nombre];
  return decodage;
}
service.h
#ifndef SERVICE_H
#define SERVICE_H

#include "criptage.h"
#include <QObject>
#include <QWebSocketServer>
#include <QWebSocket>
#include <vector>
using namespace std;

class Service : public QObject
{
  Q_OBJECT
public:
  explicit Service(QObject *parent, const char *nom, int port);
protected:
  vector<QWebSocket *> connectes;
  QWebSocket *client;
  bool criptage = false;
  string message;
private:
  QWebSocketServer service;
signals:
  void arreter();
protected:
  void cripter(const QByteArray &octets)   { message = Criptage::cripter(octets.data()); }
  void decripter(const QByteArray &octets) { message = Criptage::decripter(octets.data()); }
public slots:
  virtual void nouvelleConnexion();
  virtual void receptionMessage(const QString &texte);
  virtual void receptionOctets(const QByteArray &octets);
  virtual void deconnexion();
};

#endif // SERVICE_H
service.cpp
#include "service.h"

Service::Service(QObject *parent, const char *nom, int port) : QObject(parent), service(nom, QWebSocketServer::NonSecureMode, this)
{
  if (service.listen(QHostAddress::Any, port))
    connect(&service, SIGNAL(newConnection()), this, SLOT(nouvelleConnexion()));
  else arreter();
}

void Service::nouvelleConnexion()
{
  client = service.nextPendingConnection();
  connectes.push_back(client);
  client->sendTextMessage("Bienvenue !");
  connect(client, SIGNAL(textMessageReceived(QString)), this, SLOT(receptionMessage(QString)));
  connect(client, SIGNAL(binaryMessageReceived(QByteArray)), this, SLOT(receptionOctets(QByteArray)));
  connect(client, SIGNAL(disconnected()), this, SLOT(deconnexion()));
}

void Service::receptionMessage(const QString &texte)
{
  if (texte=="stop") { arreter(); return; }
  client = (QWebSocket *) sender();
}

void Service::receptionOctets(const QByteArray &octets)
{
  if (criptage) decripter(octets);
  client = (QWebSocket *) sender();
}

void Service::deconnexion()
{
  QWebSocket *client = (QWebSocket *) sender();
  client->deleteLater();
  connectes.erase(find(connectes.begin(), connectes.end(), client));
  disconnect(client, SIGNAL(textMessageReceived(QString)), this, SLOT(receptionMessage(QString)));
  disconnect(client, SIGNAL(binaryMessageReceived(QByteArray)), this, SLOT(receptionOctets(QByteArray)));
  disconnect(client, SIGNAL(disconnected()), this, SLOT(deconnexion()));
}
traitement.h
#ifndef TRAITEMENT_H
#define TRAITEMENT_H

#include "service.h"

class Traitement : public Service
{
public:
  Traitement() : Service(nullptr, "service", 7777) { criptage = true; bdd(); }
  void nouvelleConnexion() override;
  void receptionOctets(const QByteArray &octets) override;
private:
  void bdd();
  void table();
  void liste();
  void nouveau(const QJsonObject &json);
  void modifier(const QJsonObject &json);
void recuperer(const QJsonObject &json, const QString &sql);
 void supprimer(const QString &compte); void rechercher(const QString &compte); }; #endif // TRAITEMENT_H
traitement.cpp
#include "traitement.h"
#include <QSqlDatabase>
#include <QSqlQuery>
#include <QSqlError>
#include <QJsonObject>
#include <QJsonArray>
#include <QJsonDocument>
#include <iostream>

void Traitement::nouvelleConnexion()
{
  Service::nouvelleConnexion();
  liste();
}

void Traitement::bdd()
{
  QSqlDatabase bd = QSqlDatabase::addDatabase("QSQLITE");
  bd.setDatabaseName("comptes.bd");
  if (bd.open()) table();
  else cout << "Base de données inaccessible !" << endl;
}

void Traitement::table()
{
  QSqlQuery requete;
  if (requete.exec("CREATE TABLE IF NOT EXISTS COMPTES"
          "(compte VARCHAR(30) PRIMARY KEY,"
          "pseudo VARCHAR(30),"
          "mdp VARCHAR(15),"
          "messagerie VARCHAR(40),"
          "internet VARCHAR(80),"
          "commentaire VARCHAR(80))")) cout << "Base de données disponible" << endl;
  else cout << requete.lastError().text().toStdString() << endl;
}

void Traitement::receptionOctets(const QByteArray &octets)
{
  Service::receptionOctets(octets);
  cout << "Réception => " << message << endl << endl;
  QJsonDocument document = QJsonDocument::fromJson(message.c_str());
  QJsonObject json = document.object();
  QString commande = json["commande"].toString();
  if      (commande=="liste")       liste();
  else if (commande=="nouveau")     nouveau(json);
  else if (commande=="modifier")    modifier(json);
  else if (commande=="supprimer")   supprimer(json["compte"].toString());
  else if (commande=="rechercher")  rechercher(json["compte"].toString());
}

void Traitement::liste()
{
  QJsonArray comptes;
  QSqlQuery requete("SELECT compte FROM COMPTES ORDER BY compte");
  while (requete.next()) comptes.append(requete.value("compte").toString());
  QJsonObject json;
  json["commande"] = "liste";
  json["comptes"] = comptes;
  QJsonDocument document(json);
  cout << "Envoi => " << document.toJson(QJsonDocument::Compact).data() << endl << endl;
  cripter(document.toJson(QJsonDocument::Compact));
  client->sendBinaryMessage(message.c_str());
}

void Traitement::nouveau(const QJsonObject &json)
{
  QString requete = "INSERT INTO COMPTES (compte, pseudo, mdp, messagerie, internet, commentaire)"
                   " VALUES (:compte, :pseudo, :mdp, :messagerie, :internet, :commentaire)";
  recuperer(json, requete);
}

void Traitement::modifier(const QJsonObject &json)
{
  QString requete = "UPDATE COMPTES "
                   "SET pseudo=:pseudo, mdp=:mdp, messagerie=:messagerie, internet=:internet, commentaire=:commentaire "
                   "WHERE compte=:compte";
  recuperer(json, requete);
}

void Traitement::recuperer(const QJsonObject &json, const QString &sql)
{
  QSqlQuery requete;
  requete.prepare(sql);
  requete.bindValue(":compte", json["compte"].toString());
  requete.bindValue(":pseudo", json["pseudo"].toString());
  requete.bindValue(":mdp", json["mdp"].toString());
  requete.bindValue(":messagerie", json["messagerie"].toString());
  requete.bindValue(":internet", json["internet"].toString());
  requete.bindValue(":commentaire", json["commentaire"].toString());
  if (requete.exec()) client->sendTextMessage("Compte enregistré");
  else client->sendTextMessage(requete.lastError().text());
  liste();
}

void Traitement::supprimer(const QString &compte)
{
  QSqlQuery requete;
  requete.prepare("DELETE FROM COMPTES WHERE compte = :compte");
  requete.bindValue(":compte", compte);
  if (requete.exec()) { client->sendTextMessage("Compte supprimer"); liste(); }
  else client->sendTextMessage(requete.lastError().text());
}

void Traitement::rechercher(const QString &compte)
{
  QSqlQuery requete;
  requete.prepare("SELECT * FROM COMPTES WHERE UPPER(compte) = UPPER(:compte)");
  requete.bindValue(":compte", compte);
  if (requete.exec()) {
    if (requete.next())
    {
       QJsonObject json;
       json["commande"] = "rechercher";
       json["compte"] = requete.value("compte").toString();
       json["pseudo"] = requete.value("pseudo").toString();
       json["mdp"] = requete.value("mdp").toString();
       json["messagerie"] = requete.value("messagerie").toString();
       json["internet"] = requete.value("internet").toString();
       json["commentaire"] = requete.value("commentaire").toString();
       QJsonDocument document(json);
       cout << "Envoi => " << document.toJson(QJsonDocument::Compact).data() << endl << endl;
       cripter(document.toJson(QJsonDocument::Compact));
       client->sendBinaryMessage(message.c_str());
    }
    else client->sendTextMessage("Cet enregistrement n'existe pas");
  }
  else client->sendTextMessage(requete.lastError().text());
}
Mise en oeuvre du codage côté PC

comptesPC.pro
QT       += core gui websockets
greaterThan(QT_MAJOR_VERSION, 4): QT += widgets

TARGET = comptes
TEMPLATE = app
CONFIG  += c++11

SOURCES += main.cpp principal.cpp criptage.cpp
HEADERS  += principal.h criptage.h
FORMS    += principal.ui
cryptage.h
#ifndef CRYPTAGE_H
#define CRYPTAGE_H

#include <string>
#include <ctime>
using namespace std;

class Cryptage
{
  static int random() { return (clock() % 9) + 1; }
public:
  static string crypter(const string &message);
  static string decrypter(const string &message);
};

#endif // CRIPTAGE_H
cryptage.cpp
#include "cryptage.h"

string Cryptage::crypter(const string &message)
{
  string encodage;
  char lettre = ' ' + random(); // lettre référence pour la clef
  encodage += lettre;
  int chiffre = random(); // chiffre référence pour le criptage
  encodage += lettre + chiffre;
  int nombre = random();  // nombre d'éléments supplémentaires dans la clef
  encodage += lettre + nombre;
  int decalages[nombre]; // suite de nombre servant au calcul de chaque lettre du message
  for (int i=0; i<nombre; i++)
  {
    decalages[i] = random();
    encodage += lettre + decalages[i];
  }
  for (int i=0; i<message.size(); i++) encodage += message[i] - chiffre - decalages[i%nombre];
  return encodage;
}

string Cryptage::decrypter(const string &message)
{
  string decodage;
  int clef = 0;
  char lettre = message[clef++]; // lettre référence pour la clef
  int chiffre = message[clef++] - lettre; // chiffre référence pour le décriptage
  int nombre = message[clef++] - lettre;  // nombre d'éléments dans la clef
  int decalages[nombre];
  for (int i=0; i<nombre; i++)  decalages[i] = message[clef++] - lettre;
  for (int i=clef; i<message.size(); i++)  decodage += message[i] + chiffre + decalages[(i-clef)%nombre];
  return decodage;
}
principal.h
#ifndef PRINCIPAL_H
#define PRINCIPAL_H

#include "ui_principal.h"
#include <QWebSocket>

class Principal : public QMainWindow, private Ui::Principal
{
  Q_OBJECT

public:
  explicit Principal(QWidget *parent = 0);
private slots:
  void nouveauCompte();
  void modifierCompte();
  void supprimerCompte();
  void rechercherCompte();
  void toutEffacer();
  void rendreVisible(bool etat);
  void changerCompte(QString identifiant);
  void receptionOctets(const QByteArray &octets);
private:
  void envoiJSON(const QJsonObject &json);
private:
  QWebSocket ws;
};

#endif // PRINCIPAL_H
principal.cpp
#include "principal.h"
#include "cryptage.h"
#include <QJsonObject>
#include <QJsonArray>
#include <QJsonDocument>
#include <iostream>

Principal::Principal(QWidget *parent) : QMainWindow(parent)
{
  setupUi(this);
  ws.open(QUrl("ws://192.168.1.33:7777/"));
  connect(&ws, SIGNAL(textMessageReceived(QString)), barreEtat, SLOT(showMessage(QString)));
  connect(&ws, SIGNAL(binaryMessageReceived(QByteArray)), this, SLOT(receptionOctets(QByteArray)));
}

void Principal::changerCompte(QString identifiant)
{
  compte->setText(identifiant);
  if (!identifiant.isEmpty()) rechercherCompte();
}

void Principal::envoiJSON(const QJsonObject &json)
{
  QJsonDocument document(json);
  string message = Cryptage::crypter(document.toJson(QJsonDocument::Compact).data());
  ws.sendBinaryMessage(message.c_str());
}

void Principal::nouveauCompte()
{
  QJsonObject json;
  json["commande"] = "nouveau";
  json["compte"] = compte->text();
  json["pseudo"] = pseudo->text();
  json["mdp"] = mdp->text();
  json["messagerie"] = messagerie->text();
  json["internet"] = internet->text();
  json["commentaire"] = commentaire->text();
  envoiJSON(json);
}

void Principal::modifierCompte()
{
  QJsonObject json;
  json["commande"] = "modifier";
  json["compte"] = compte->text();
  json["pseudo"] = pseudo->text();
  json["mdp"] = mdp->text();
  json["messagerie"] = messagerie->text();
  json["internet"] = internet->text();
  json["commentaire"] = commentaire->text();
  envoiJSON(json);
}

void Principal::supprimerCompte()
{
  QJsonObject json;
  json["commande"] = "supprimer";
  json["compte"] = compte->text();
  envoiJSON(json);
}

void Principal::rechercherCompte()
{
  QJsonObject json;
  json["commande"] = "rechercher";
  json["compte"] = compte->text();
  envoiJSON(json);
}

void Principal::toutEffacer()
{
  compte->clear();
  pseudo->clear();
  mdp->clear();
  messagerie->clear();
  internet->clear();
  commentaire->clear();
  barreEtat->clearMessage();
}

void Principal::rendreVisible(bool etat)
{
  mdp->setEchoMode(etat ? QLineEdit::Password : QLineEdit::Normal);
}

void Principal::receptionOctets(const QByteArray &octets)
{
  string message = Cryptage::decrypter(octets.data());
  QJsonDocument document = QJsonDocument::fromJson(message.c_str());
  QJsonObject json = document.object();
  QString commande = json["commande"].toString();
  if (commande=="liste")
  {
    QJsonArray comptes = json["comptes"].toArray();
    liste->clear();
    for (auto item : comptes) liste->addItem(item.toString());
  }
  else if (commande=="rechercher")
  {
    compte->setText(json["compte"].toString());
    pseudo->setText(json["pseudo"].toString());
    mdp->setText(json["mdp"].toString());
    messagerie->setText(json["messagerie"].toString());
    internet->setText(json["internet"].toString());
    commentaire->setText(json["commentaire"].toString());
  }
}

Système de notification de rendez-vous enregistrés

Nous allons élaborrer un projet qui nous permet d'enregistrer des rendez-vous afin que par la suite nous ayons des alertes sur chacun des postes de travail. Comme précédemment, le service de notification se fait sur la rapberry pi. Chaque poste interroge ensuite à distance le service afin que chacun puisse recevoir les notifications sur sa propre barre des tâches. Les clients pourront utiliser le service aussi bien en réseau local que depuis Intenet proposition automatique de la bonne adresse IP.

Mise en oeuvre du codage côté raspberry

Dans ce projet aussi, dans le code source, nous avons rajouter quelques lignes de code qui sera possible d'enlever par la suite qui affichent sur le terminal les documents JSON reçus et envoyés afin de bien contrôler le format respectivement après le décriptage et avant le criptage.

comptesPI.pro
QT       += core websockets sql
QT       -= gui

TARGET = agenda
target.path = /home/pi/Travail
INSTALLS += target

CONFIG   += console c++11
CONFIG   -= app_bundle

TEMPLATE = app

SOURCES += main.cpp service.cpp traitement.cpp criptage.cpp
HEADERS += service.h traitement.h criptage.h
cryptage.h
#ifndef CRYPTAGE_H
#define CRYPTAGE_H

#include <string>
#include <ctime>
using namespace std;

class Cryptage
{
  static int random() { return (clock() % 9) + 1; }
public:
  static string crypter(const string &message);
  static string decrypter(const string &message);
};

#endif // CRYPTAGE_H
cryptage.cpp
#include "cryptage.h"

string Cryptage::crypter(const string &message)
{
  string encodage;
  char lettre = ' ' + random(); // lettre référence pour la clef
  encodage += lettre;
  int chiffre = random(); // chiffre référence pour le criptage
  encodage += lettre + chiffre;
  int nombre = random();  // nombre d'éléments supplémentaires dans la clef
  encodage += lettre + nombre;
  int decalages[nombre]; // suite de nombre servant au calcul de chaque lettre du message
  for (int i=0; i<nombre; i++)
  {
    decalages[i] = random();
    encodage += lettre + decalages[i];
  }
  for (int i=0; i<message.size(); i++) encodage += message[i] - chiffre - decalages[i%nombre];
  return encodage;
}

string Cryptage::decrypter(const string &message)
{
  string decodage;
  int clef = 0;
  char lettre = message[clef++]; // lettre référence pour la clef
  int chiffre = message[clef++] - lettre; // chiffre référence pour le décriptage
  int nombre = message[clef++] - lettre;  // nombre d'éléments dans la clef
  int decalages[nombre];
  for (int i=0; i<nombre; i++)  decalages[i] = message[clef++] - lettre;
  for (int i=clef; i<message.size(); i++)  decodage += message[i] + chiffre + decalages[(i-clef)%nombre];
  return decodage;
}
service.h
#ifndef SERVICE_H
#define SERVICE_H

#include "cryptage.h"
#include <QObject>
#include <QWebSocketServer>
#include <QWebSocket>
#include <vector>
using namespace std;

class Service : public QObject
{
  Q_OBJECT
public:
  explicit Service(QObject *parent, const char *nom, int port);
protected:
  vector<QWebSocket *> connectes;
  QWebSocket *client;
  bool cryptage = false;
  string message;
private:
  QWebSocketServer service;
signals:
  void arreter();
protected:
  void crypter(const QByteArray &octets)   { message = Cryptage::crypter(octets.data()); }
  void decrypter(const QByteArray &octets) { message = Cryptage::decrypter(octets.data()); }
public slots:
  virtual void nouvelleConnexion();
  virtual void receptionMessage(const QString &texte);
  virtual void receptionOctets(const QByteArray &octets);
  virtual void deconnexion();
};

#endif // SERVICE_H
service.cpp
#include "service.h"

Service::Service(QObject *parent, const char *nom, int port) : QObject(parent), service(nom, QWebSocketServer::NonSecureMode, this)
{
  if (service.listen(QHostAddress::Any, port))
    connect(&service, SIGNAL(newConnection()), this, SLOT(nouvelleConnexion()));
  else arreter();
}

void Service::nouvelleConnexion()
{
  client = service.nextPendingConnection();
  connectes.push_back(client);
  client->sendTextMessage("Bienvenue !");
  connect(client, SIGNAL(textMessageReceived(QString)), this, SLOT(receptionMessage(QString)));
  connect(client, SIGNAL(binaryMessageReceived(QByteArray)), this, SLOT(receptionOctets(QByteArray)));
  connect(client, SIGNAL(disconnected()), this, SLOT(deconnexion()));
}

void Service::receptionMessage(const QString &texte)
{
  if (texte=="stop") { arreter(); return; }
  client = (QWebSocket *) sender();
}

void Service::receptionOctets(const QByteArray &octets)
{
  if (criptage) decrypter(octets);
  client = (QWebSocket *) sender();
}

void Service::deconnexion()
{
  QWebSocket *client = (QWebSocket *) sender();
  client->deleteLater();
  connectes.erase(find(connectes.begin(), connectes.end(), client));
  disconnect(client, SIGNAL(textMessageReceived(QString)), this, SLOT(receptionMessage(QString)));
  disconnect(client, SIGNAL(binaryMessageReceived(QByteArray)), this, SLOT(receptionOctets(QByteArray)));
  disconnect(client, SIGNAL(disconnected()), this, SLOT(deconnexion()));
}
traitement.h
#ifndef TRAITEMENT_H
#define TRAITEMENT_H

#include "service.h"
#include <QSqlQuery>

class Traitement : public Service { public: Traitement() : Service(nullptr, "service", 7788) { criptage = true; bdd(); } void receptionOctets(const QByteArray &octets) override; void nouvelleConnexion() override; private: void bdd(); void table(); void liste(); void nouveau(const QJsonObject &json); void modifier(const QJsonObject &json);
void recuperer(const QJsonObject &json, QSqlQuery &requete);
void supprimer(int id); void rechercher(int id); }; #endif // TRAITEMENT_H
traitement.cpp
#include "traitement.h"
#include <QSqlDatabase>
#include <QSqlError>
#include <QJsonObject>
#include <QJsonArray>
#include <QJsonDocument>
#include <QTime>
#include <QDate>
#include <iostream>

void Traitement::nouvelleConnexion()
{
  Service::nouvelleConnexion();
QSqlQuery requete; if (requete.exec("DELETE FROM AGENDA WHERE date < CURRENT_DATE")) client->sendTextMessage("Anciens rendez-vous supprimés"); else client->sendTextMessage(requete.lastError().text());
 liste(); } void Traitement::bdd() { QSqlDatabase bd = QSqlDatabase::addDatabase("QSQLITE"); bd.setDatabaseName("agenda.bd"); if (bd.open()) table(); else cout << "Base de données inaccessible !" << endl; } void Traitement::table() { QSqlQuery requete; if (requete.exec("CREATE TABLE IF NOT EXISTS AGENDA" "(id INTEGER PRIMARY KEY," "sujet VARCHAR(40)," "pour VARCHAR(15)," "qui VARCHAR(30)," "tel VARCHAR(15)," "heure TIME," "date DATE," "lieu VARCHAR(40)," "note VARCHAR(80))")) cout << "Base de données disponible" << endl; else cout << requete.lastError().text().toStdString() << endl; } void Traitement::receptionOctets(const QByteArray &octets) { Service::receptionOctets(octets); cout << "Réception => " << message << endl << endl; QJsonDocument document = QJsonDocument::fromJson(message.c_str()); QJsonObject json = document.object(); QString commande = json["commande"].toString(); if (commande=="liste") liste(); else if (commande=="nouveau") nouveau(json); else if (commande=="modifier") modifier(json); else if (commande=="supprimer") supprimer(json["id"].toInt()); else if (commande=="rechercher") rechercher(json["id"].toInt()); } void Traitement::liste() { QJsonArray ids; QSqlQuery requete("SELECT id FROM AGENDA ORDER BY date, heure"); while (requete.next()) ids.append(requete.value("id").toInt()); QJsonObject json; json["commande"] = "liste"; json["ids"] = ids; QJsonDocument document(json); cout << "Envoi => " << document.toJson(QJsonDocument::Compact).data() << endl << endl; cripter(document.toJson(QJsonDocument::Compact)); client->sendBinaryMessage(message.c_str()); } void Traitement::nouveau(const QJsonObject &json) { QSqlQuery requete; requete.prepare("INSERT INTO AGENDA (sujet, pour, qui, tel, heure, date, lieu, note)" " VALUES (:sujet, :pour, :qui, :tel, :heure, :date, :lieu, :note)"); recuperer(json, requete); } void Traitement::modifier(const QJsonObject &json) { QSqlQuery requete; requete.prepare("UPDATE AGENDA " "SET sujet=:sujet, pour=:pour, qui=:qui, tel=:tel, heure=:heure, date=:date, lieu=:lieu, note=:note " "WHERE id=:id"); requete.bindValue(":id", json["id"].toInt()); recuperer(json, requete); } void Traitement::recuperer(const QJsonObject &json, QSqlQuery &requete) { requete.bindValue(":sujet", json["sujet"].toString()); requete.bindValue(":pour", json["pour"].toString()); requete.bindValue(":qui", json["qui"].toString()); requete.bindValue(":tel", json["tel"].toString()); requete.bindValue(":heure", QTime::fromString(json["heure"].toString())); requete.bindValue(":date", QDate::fromString(json["date"].toString())); requete.bindValue(":lieu", json["lieu"].toString()); requete.bindValue(":note", json["note"].toString()); if (requete.exec()) client->sendTextMessage("Compte enregistré"); else client->sendTextMessage(requete.lastError().text()); liste(); }
void Traitement::supprimer(int id) { QSqlQuery requete; requete.prepare("DELETE FROM AGENDA WHERE id = :id"); requete.bindValue(":id", id); if (requete.exec()) { client->sendTextMessage("Compte supprimer"); liste(); } else client->sendTextMessage(requete.lastError().text()); } void Traitement::rechercher(int id) { QSqlQuery requete; requete.prepare("SELECT * FROM AGENDA WHERE id = :id"); requete.bindValue(":id", id); if (requete.exec()) { if (requete.next()) { QJsonObject json; json["commande"] = "rechercher"; json["id"] = requete.value("id").toInt(); json["sujet"] = requete.value("sujet").toString(); json["pour"] = requete.value("pour").toString(); json["qui"] = requete.value("qui").toString(); json["tel"] = requete.value("tel").toString(); json["heure"] = requete.value("heure").toTime().toString(); json["date"] = requete.value("date").toDate().toString(); json["lieu"] = requete.value("lieu").toString(); json["note"] = requete.value("note").toString(); QJsonDocument document(json); cout << "Envoi => " << document.toJson(QJsonDocument::Compact).data() << endl << endl; cripter(document.toJson(QJsonDocument::Compact)); client->sendBinaryMessage(message.c_str()); } else client->sendTextMessage("Ce rendez-vous n'existe pas"); } else client->sendTextMessage(requete.lastError().text()); }
Mise en oeuvre du codage côté PC

Par rapport au projet précédent, cette application doit pouvoir communiquer avec le service qu'elle soit en réseau local ou depuis Internet. Deux adresses sont alors fournies. La première tentative de connexion se fait d'abord en se considérant dans le réseau local. Si cette tentative échoue, après un time-out de 1mn environ, c'est la deuxième adresse qui est alors proposée pour une nouvelle connexion.

comptesPC.pro
QT       += core gui websockets
greaterThan(QT_MAJOR_VERSION, 4): QT += widgets

TARGET = agenda
TEMPLATE = app
CONFIG  += c++11

SOURCES += main.cpp principal.cpp criptage.cpp
HEADERS  += principal.h criptage.h
FORMS    += principal.ui

RESOURCES += icones.qrc
cryptage.h
#ifndef CRYPTAGE_H
#define CRYPTAGE_H

#include <string>
#include <ctime>
using namespace std;

class Cryptage
{
  static int random() { return (clock() % 9) + 1; }
public:
  static string crypter(const string &message);
  static string decrypter(const string &message);
};

#endif // CRYPTAGE_H
cryptage.cpp
#include "cryptage.h"

string Cryptage::crypter(const string &message)
{
  string encodage;
  char lettre = ' ' + random(); // lettre référence pour la clef
  encodage += lettre;
  int chiffre = random(); // chiffre référence pour le criptage
  encodage += lettre + chiffre;
  int nombre = random();  // nombre d'éléments supplémentaires dans la clef
  encodage += lettre + nombre;
  int decalages[nombre]; // suite de nombre servant au calcul de chaque lettre du message
  for (int i=0; i<nombre; i++)
  {
    decalages[i] = random();
    encodage += lettre + decalages[i];
  }
  for (int i=0; i<message.size(); i++) encodage += message[i] - chiffre - decalages[i%nombre];
  return encodage;
}

string Cryptage::decrypter(const string &message)
{
  string decodage;
  int clef = 0;
  char lettre = message[clef++]; // lettre référence pour la clef
  int chiffre = message[clef++] - lettre; // chiffre référence pour le décriptage
  int nombre = message[clef++] - lettre;  // nombre d'éléments dans la clef
  int decalages[nombre];
  for (int i=0; i<nombre; i++)  decalages[i] = message[clef++] - lettre;
  for (int i=clef; i<message.size(); i++)  decodage += message[i] + chiffre + decalages[(i-clef)%nombre];
  return decodage;
}
principal.h
#ifndef PRINCIPAL_H
#define PRINCIPAL_H

#include "ui_principal.h"
#include <QWebSocket>
#include <vector>
#include <QSystemTrayIcon>
using namespace std;

class Principal : public QMainWindow, private Ui::Principal
{
  Q_OBJECT

public:
  explicit Principal(QWidget *parent = 0);
private slots:
  void nouvelAgenda();
  void modifierAgenda();
  void supprimerAgenda();
  void rechercherAgenda();
  void toutEffacer();  
  void suivant();
  void precedent();
  void receptionOctets(const QByteArray &octets);  
  void arreter();
  void alerte();
  void connecte();
  void nonconnecte();
private:
void soumettre(QJsonObject &json);
 void envoiJSON(const QJsonObject &json); void setNotification(); protected: void closeEvent(QCloseEvent *); private: QWebSocket ws; int identifiant, index; vector<int> identifiants; bool quitter = false; QSystemTrayIcon *notification; }; #endif // PRINCIPAL_H
principal.cpp
#include "principal.h"
#include "criptage.h"
#include <QJsonObject>
#include <QJsonArray>
#include <QJsonDocument>
#include <QMenu>
#include <QCloseEvent>

Principal::Principal(QWidget *parent) : QMainWindow(parent)
{
  setupUi(this);
  setNotification();
  ws.open(QUrl("ws://192.168.1.33:7788/"));
  connect(&ws, SIGNAL(connected()), this, SLOT(connecte()));
  connect(&ws, SIGNAL(disconnected()), this, SLOT(nonconnecte()));
}

void Principal::connecte()
{
  connect(&ws, SIGNAL(textMessageReceived(QString)), barreEtat, SLOT(showMessage(QString)));
  connect(&ws, SIGNAL(binaryMessageReceived(QByteArray)), this, SLOT(receptionOctets(QByteArray)));
}

void Principal::nonconnecte()
{
  ws.open(QUrl("ws://remy-manu.no-ip.biz:7788/"));
}

void Principal::nouvelAgenda()
{
  QJsonObject json;
  json["commande"] = "nouveau";
  soumettre(json);
}

void Principal::modifierAgenda()
{
  QJsonObject json;
  json["commande"] = "modifier";
  json["id"] = identifiant;
  soumettre(json);
}

void Principal::soumettre(QJsonObject &json)
{
  json["sujet"] = sujet->text();
  json["pour"] = pour->text();
  json["qui"] = qui->text();
  json["tel"] = tel->text();
  json["heure"] = heure->time().toString();
  json["date"] = date->selectedDate().toString();
  json["lieu"] = lieu->text();
  json["note"] = note->text();
  envoiJSON(json);
}

void Principal::envoiJSON(const QJsonObject &json)
{
  QJsonDocument document(json);
  string message = Cryptage::crypter(document.toJson(QJsonDocument::Compact).data());
  ws.sendBinaryMessage(message.c_str());
}

void Principal::supprimerAgenda()
{
  QJsonObject json;
  json["commande"] = "supprimer";
  json["id"] = identifiant;
  envoiJSON(json);
}

void Principal::rechercherAgenda()
{
  QJsonObject json;
  json["commande"] = "rechercher";
  json["id"] = identifiant;
  envoiJSON(json);
}

void Principal::toutEffacer()
{
  sujet->clear();
  pour->clear();
  qui->clear();
  tel->clear();
  lieu->clear();
  note->clear();
  barreEtat->clearMessage();
}

void Principal::receptionOctets(const QByteArray &octets)
{
  string message = Cryptage::decrypter(octets.data());
  QJsonDocument document = QJsonDocument::fromJson(message.c_str());
  QJsonObject json = document.object();
  QString commande = json["commande"].toString();
  if (commande=="liste")
  {
    QJsonArray ids = json["ids"].toArray();
    identifiants.clear();
    for (auto id : ids) identifiants.push_back(id.toInt());
    if (!identifiants.empty())
    {
      identifiant = identifiants[index = 0];
      actionPrecedent->setEnabled(false);
      actionSuivant->setEnabled(identifiants.size()>1);
      rechercherAgenda();
    }
  }
  else if (commande=="rechercher")
  {
    identifiant = json["id"].toInt();
    sujet->setText(json["sujet"].toString());
    pour->setText(json["pour"].toString());
    qui->setText(json["qui"].toString());
    tel->setText(json["tel"].toString());
    heure->setTime(QTime::fromString(json["heure"].toString()));
    jour->setDate(QDate::fromString(json["date"].toString()));
    date->setSelectedDate(QDate::fromString(json["date"].toString()));  
    lieu->setText(json["lieu"].toString());
    note->setText(json["note"].toString());
    alerte();
actionSuivant->setEnabled(index!=identifiants.size()-1); // Rétablir les boutons de navigation actionPrecedent->setEnabled(index!=0); // une fois la réception des données acquise
 } } void Principal::alerte() { QString texte = "Sujet : "; texte += sujet->text(); texte += "\nNote : "; texte += note->text(); texte += "\nPour : "; texte += pour->text(); texte += "\nAvec : "; texte += qui->text(); texte += "\n"; texte += jour->text(); texte += "\n"; texte += heure->text(); notification->showMessage("Rendez-vous", texte); } void Principal::suivant() { if (index<(identifiants.size()-1)) { identifiant = identifiants[++index]; actionSuivant->setEnabled(false); // Bloquer la navigation tant que les données demandées actionPrecedent->setEnabled(false); // ne sont pas encore réceptionnées rechercherAgenda(); } } void Principal::precedent() { if (index>0) { identifiant = identifiants[--index]; actionSuivant->setEnabled(false); // Bloquer la navigation tant que les données demandées actionPrecedent->setEnabled(false); // ne sont pas encore arrivées rechercherAgenda(); } } void Principal::closeEvent(QCloseEvent *evt) { if (!quitter) { hide(); evt->ignore(); } } void Principal::arreter() { quitter = true; close(); } void Principal::setNotification() { QMenu *menu = new QMenu(this); menu->addAction(actionAfficher); menu->addSeparator(); menu->addAction(actionRevoir); menu->addAction(actionSuivant); menu->addAction(actionPrecedent); menu->addSeparator(); menu->addAction(actionQuitter); notification = new QSystemTrayIcon(QIcon(":/icones/calendar.png"), this); notification->setContextMenu(menu); notification->show(); }

Lancement automatique d'un service au démarrage de la Raspberry PI

Dans ce tout petit chapitre, nous allons voir comment faire en sorte que les services que nous venons de mettre en oeuvre soient automatiquement opérationnels dès que nous branchons la Raspberry PI, sans que nous ayons besoin systématiquement de nous connecter et de les activer manuellement. Il faut savoir que tout système d'exploitation lancent un certain nombre de services au démarrage de la machine afin qu'elle soit pleinement opérationnelle. C'est grâce à ce mécanisme que nous allons pouvoir intégrer nos propres services.

Réalisation d'un script personnalisé

Au démarrage du système, c'est systématiquement le programme init qui est activé en premier init est l'ancêtre de tous les processus. Ce processus ancêtre s'occupe de lancer tous les RC qui sont les scripts de démarrage des processus. Tous ces scripts particuliers sont spécifiés dans le répertoire /etc/init.d. Si vous devez élaborrer vos propres services qui peuvent être activés automatiquement, c'est dans ce répertoire que vous allez écrire un script prototypé.

Voici d'ailleurs le canevas que devra suivre votre script afin que ce dernier soit correctement interprété :
# Fichier "XYZ" texte à placer dans le répertoire "/etc/init.d"

# Après cette ligne toutes les commandes seront exécutées systematiquement
# ...

# Apres cette ligne les commandes seront exécutées suivant le paramètre passé en ligne de commande case "$1" in start) # Commandes exécutées avec le paramètre start (celui activé lors du démarrage du système) ;; stop) # Commandes exécutées avec le parametre stop (celui activé lors de la demande d'arrêt du systeme) ;; reload|restart) $0 stop $0 start ;; *) echo "Utilisation: $0 start|stop|restart|reload" exit 1 esac exit 0
N’oubliez pas de rendre le script exécutable à l'aide de la commande chmod :
chmod 755 /etc/init.d/XYZ
Vous pouvez ensuite tester votre script avec la commande service. Vous passez en paramètre l’argument que vous souhaitez. Par exemple pour démarrer votre service, vous pouvez placer l'argument start :
service XYZ start
Après ce test, si cela fonctionne correctement, vous pouvez alors activer le lancement automatique du script au démarrage du système grâce à la commande update-rc.d :
update-rc.d XYZ defaults
Cette commande ajoute le script dans la séquence de démarrage de votre Raspberry. Vous pouvez utiliser d’autres valeurs à la place de defaults :
  • remove : pour supprimer le script de la séquence de démarrage
  • disable : pour désactiver le script
  • enable : pour activer le script
Script de démarrage personnalisé

Pour conclure ce petit chapitre, je vous propose de visualiser le script services qui va permettre de lancer automatiquement au démarrage de la Raspberry PI les deux services que nous avons mis en oeuvre précédemment, savoir compte et agenda.

services
# démarrage automatiques de mes services personnels

case "$1" in
  start)
    cd /home/pi/logiciels 
    ./agenda &
    ./comptes &
    ;;
  stop)
    cd /home/pi/logiciels
    killall agenda
    killall comptes
    ;;
  reload|restart)
    $0 stop
    $0 start
    ;;
  *)
    echo "Utiliser : $0 start|stop|restart|reload"
    exit 1
esac

exit 0

Fichier de configuration et accès à une date spécifique d'un rendez-vous

Reprenons le projet sur la gestion des rendez-vous. Nous allons rajouter quelques spécificités supplémentaires qui vont nous permettre d'avoir une meilleure ergonomie. Actuellement, si vous avez beaucoup de rendez-vous enregistrés, vous êtes obligé de parcourrir la liste un par un. Nous pourrons dès lors accéder à un rendez-vous en spécifiant une date. Par ailleurs, dorénavant, nous pourront demander une mise à jour de la liste complète des rendez-vous déjà enregistrés au préalable chaque ajout ou modification nous redonnait automatiquement la liste des rendez-vous mais en se plaçant systématiquement sur le premier enregistrement. Enfin, notre application cliente doit pouvoir fonctionner aussi bien en réseau local que depuis Internet. Les adresses ip de ces deux situations pouvant changer, il est souhaitable de prévoir un fichier de configuration qui précisera ces adresses ainsi que le numéro de port utilisé qui pourront facilement être modifiés par la suite.

Mise en oeuvre du codage côté raspberry

Au niveau du service, nous devons rajouter une requête qui va nous permettre de retrouver un rendez-vous à partir d'une date soumise par le client.

comptesPI.pro
QT       += core websockets sql
QT       -= gui

TARGET = agenda
target.path = /home/pi/Travail
INSTALLS += target

CONFIG   += console c++11
CONFIG   -= app_bundle

TEMPLATE = app

SOURCES += main.cpp service.cpp traitement.cpp criptage.cpp
HEADERS += service.h traitement.h criptage.h
cryptage.h
#ifndef CRYPTAGE_H
#define CRYPTAGE_H

#include <string>
#include <ctime>
using namespace std;

class Cryptage
{
  static int random() { return (clock() % 9) + 1; }
public:
  static string crypter(const string &message);
  static string decrypter(const string &texte);
};

#endif // CRYPTAGE_H
cryptage.cpp
#include "cryptage.h"

string Cryptage::crypter(const string &message)
{
  string encodage;
  char lettre = ' ' + random(); // lettre référence pour la clef
  encodage += lettre;
  int chiffre = random(); // chiffre référence pour le criptage
  encodage += lettre + chiffre;
  int nombre = random();  // nombre d'éléments supplémentaires dans la clef
  encodage += lettre + nombre;
  int decalages[nombre]; // suite de nombre servant au calcul de chaque lettre du message
  for (int i=0; i<nombre; i++)
  {
    decalages[i] = random();
    encodage += lettre + decalages[i];
  }
  for (int i=0; i<message.size(); i++) encodage += message[i] - chiffre - decalages[i%nombre];
  // Brassage des lettres afin de mélanger la clé de criptage avec son message cripté
  string melange;
  nombre = encodage.size();
  if (nombre%3 == 0) encodage+=' ';
  for (int i=0, indice=2; i<nombre; i++, indice+=3) melange += encodage[indice%encodage.size()];
  return melange;
}

string Cryptage::decrypter(const string &texte)
{
// Remettre en place les lettres mélangées (clé de criptage suivi du message cripté) string melange = texte; string message = melange; int nombre = melange.size(); if (nombre%3 == 0) melange+=' '; for (int i=0, indice=2; i<nombre; i++, indice+=3) message[indice%melange.size()] = melange[i]; // Phase de décriptage
string decodage; int clef = 0; char lettre = message[clef++]; // lettre référence pour la clef int chiffre = message[clef++] - lettre; // chiffre référence pour le décriptage int nombre = message[clef++] - lettre; // nombre d'éléments dans la clef int decalages[nombre]; for (int i=0; i<nombre; i++) decalages[i] = message[clef++] - lettre; for (int i=clef; i<message.size(); i++) decodage += message[i] + chiffre + decalages[(i-clef)%nombre]; return decodage; }
service.h
#ifndef SERVICE_H
#define SERVICE_H

#include "cryptage.h"
#include <QObject>
#include <QWebSocketServer>
#include <QWebSocket>
#include <vector>
using namespace std;

class Service : public QObject
{
  Q_OBJECT
public:
  explicit Service(QObject *parent, const char *nom, int port);
protected:
  vector<QWebSocket *> connectes;
  QWebSocket *client;
  bool cryptage = false;
  string message;
private:
  QWebSocketServer service;
signals:
  void arreter();
protected:
  void crypter(const QByteArray &octets)   { message = Cryptage::crypter(octets.data()); }
  void decrypter(const QByteArray &octets) { message = Cryptage::decrypter(octets.data()); }
public slots:
  virtual void nouvelleConnexion();
  virtual void receptionMessage(const QString &texte);
  virtual void receptionOctets(const QByteArray &octets);
  virtual void deconnexion();
};

#endif // SERVICE_H
service.cpp
#include "service.h"

Service::Service(QObject *parent, const char *nom, int port) : QObject(parent), service(nom, QWebSocketServer::NonSecureMode, this)
{
  if (service.listen(QHostAddress::Any, port))
    connect(&service, SIGNAL(newConnection()), this, SLOT(nouvelleConnexion()));
  else arreter();
}

void Service::nouvelleConnexion()
{
  client = service.nextPendingConnection();
  connectes.push_back(client);
  client->sendTextMessage("Bienvenue !");
  connect(client, SIGNAL(textMessageReceived(QString)), this, SLOT(receptionMessage(QString)));
  connect(client, SIGNAL(binaryMessageReceived(QByteArray)), this, SLOT(receptionOctets(QByteArray)));
  connect(client, SIGNAL(disconnected()), this, SLOT(deconnexion()));
}

void Service::receptionMessage(const QString &texte)
{
  if (texte=="stop") { arreter(); return; }
  client = (QWebSocket *) sender();
}

void Service::receptionOctets(const QByteArray &octets)
{
  if (criptage) decrypter(octets);
  client = (QWebSocket *) sender();
}

void Service::deconnexion()
{
  QWebSocket *client = (QWebSocket *) sender();
  client->deleteLater();
  connectes.erase(find(connectes.begin(), connectes.end(), client));
  disconnect(client, SIGNAL(textMessageReceived(QString)), this, SLOT(receptionMessage(QString)));
  disconnect(client, SIGNAL(binaryMessageReceived(QByteArray)), this, SLOT(receptionOctets(QByteArray)));
  disconnect(client, SIGNAL(disconnected()), this, SLOT(deconnexion()));
}
traitement.h
#ifndef TRAITEMENT_H
#define TRAITEMENT_H

#include "service.h"
#include <QSqlQuery>

class Traitement : public Service { public: Traitement() : Service(nullptr, "service", 7788) { cryptage = true; bdd(); } void receptionOctets(const QByteArray &octets) override; void nouvelleConnexion() override; private: void bdd(); void table(); void liste(); void nouveau(const QJsonObject &json); void modifier(const QJsonObject &json); void recuperer(const QJsonObject &json, QSqlQuery &requete); void supprimer(int id); void rechercher(int id); void position(const QString &date); void soumettre(QJsonObject &json, QSqlQuery &requete); }; #endif // TRAITEMENT_H
traitement.cpp
#include "traitement.h"
#include <QSqlDatabase>
#include <QSqlError>
#include <QJsonObject>
#include <QJsonArray>
#include <QJsonDocument>
#include <QTime>
#include <QDate>
#include <iostream>

void Traitement::nouvelleConnexion()
{
  Service::nouvelleConnexion();
QSqlQuery requete; if (requete.exec("DELETE FROM AGENDA WHERE date < CURRENT_DATE")) client->sendTextMessage("Anciens rendez-vous supprimés"); else client->sendTextMessage(requete.lastError().text());
 liste(); } void Traitement::bdd() { QSqlDatabase bd = QSqlDatabase::addDatabase("QSQLITE"); bd.setDatabaseName("agenda.bd"); if (bd.open()) table(); else cout << "Base de données inaccessible !" << endl; } void Traitement::table() { QSqlQuery requete; if (requete.exec("CREATE TABLE IF NOT EXISTS AGENDA" "(id INTEGER PRIMARY KEY," "sujet VARCHAR(40)," "pour VARCHAR(15)," "qui VARCHAR(30)," "tel VARCHAR(15)," "heure TIME," "date DATE," "lieu VARCHAR(40)," "note VARCHAR(80))")) cout << "Base de données disponible" << endl; else cout << requete.lastError().text().toStdString() << endl; } void Traitement::receptionOctets(const QByteArray &octets) { Service::receptionOctets(octets); cout << "Réception => " << message << endl << endl; QJsonDocument document = QJsonDocument::fromJson(message.c_str()); QJsonObject json = document.object(); QString commande = json["commande"].toString(); if (commande=="liste") liste(); else if (commande=="nouveau") nouveau(json); else if (commande=="modifier") modifier(json); else if (commande=="supprimer") supprimer(json["id"].toInt()); else if (commande=="rechercher") rechercher(json["id"].toInt());
else if (commande=="position") position(json["date"].toString());
} void Traitement::liste() { QJsonArray ids; QSqlQuery requete("SELECT id FROM AGENDA ORDER BY date, heure"); while (requete.next()) ids.append(requete.value("id").toInt()); QJsonObject json; json["commande"] = "liste"; json["ids"] = ids; QJsonDocument document(json); cout << "Envoi => " << document.toJson(QJsonDocument::Compact).data() << endl << endl; cripter(document.toJson(QJsonDocument::Compact)); client->sendBinaryMessage(message.c_str()); } void Traitement::nouveau(const QJsonObject &json) { QSqlQuery requete; requete.prepare("INSERT INTO AGENDA (sujet, pour, qui, tel, heure, date, lieu, note)" " VALUES (:sujet, :pour, :qui, :tel, :heure, :date, :lieu, :note)"); recuperer(json, requete); } void Traitement::modifier(const QJsonObject &json) { QSqlQuery requete; requete.prepare("UPDATE AGENDA " "SET sujet=:sujet, pour=:pour, qui=:qui, tel=:tel, heure=:heure, date=:date, lieu=:lieu, note=:note " "WHERE id=:id"); requete.bindValue(":id", json["id"].toInt()); recuperer(json, requete); } void Traitement::recuperer(const QJsonObject &json, QSqlQuery &requete) { requete.bindValue(":sujet", json["sujet"].toString()); requete.bindValue(":pour", json["pour"].toString()); requete.bindValue(":qui", json["qui"].toString()); requete.bindValue(":tel", json["tel"].toString()); requete.bindValue(":heure", QTime::fromString(json["heure"].toString())); requete.bindValue(":date", QDate::fromString(json["date"].toString())); requete.bindValue(":lieu", json["lieu"].toString()); requete.bindValue(":note", json["note"].toString()); if (requete.exec()) client->sendTextMessage("Rendez-vous enregistré"); else client->sendTextMessage(requete.lastError().text()); liste(); }
void Traitement::supprimer(int id) { QSqlQuery requete; requete.prepare("DELETE FROM AGENDA WHERE id = :id"); requete.bindValue(":id", id); if (requete.exec()) { client->sendTextMessage("Rendez-vous supprimer"); liste(); } else client->sendTextMessage(requete.lastError().text()); } void Traitement::rechercher(int id) { QSqlQuery requete; requete.prepare("SELECT * FROM AGENDA WHERE id = :id"); requete.bindValue(":id", id); QJsonObject json; json["commande"] = "rechercher"; soumettre(json, requete); }
void Traitement::position(const QDate &jour) { QSqlQuery requete("SELECT * FROM AGENDA WHERE date = (?) ORDER BY heure"); requete.addBindValue(jour); QJsonObject json; json["commande"] = "position"; soumettre(json, requete); }

void Traitement::soumettre(QJsonObject &json, QSqlQuery &requete) { if (requete.exec()) { if (requete.next()) { json["id"] = requete.value("id").toInt(); json["sujet"] = requete.value("sujet").toString(); json["pour"] = requete.value("pour").toString(); json["qui"] = requete.value("qui").toString(); json["tel"] = requete.value("tel").toString(); json["heure"] = requete.value("heure").toTime().toString(); json["date"] = requete.value("date").toDate().toString(); json["lieu"] = requete.value("lieu").toString(); json["note"] = requete.value("note").toString(); QJsonDocument document(json); cout << "Envoi => " << document.toJson(QJsonDocument::Compact).data() << endl << endl; cripter(document.toJson(QJsonDocument::Compact)); client->sendBinaryMessage(message.c_str());

client->sendTextMessage("Ok");
  } else client->sendTextMessage("Aucun rendez-vous."); } else client->sendTextMessage(requete.lastError().text()); }
Attention, les requêtes SQL nécessaires pour tester l'égalité entre dates est toujours problématique. Afin d'éviter ces problèmes, j'ai préféré utiliser la méthode addBindValue() en lieu et place de la méthode bindValue() de la classe QSqlQuery. Dans ce cas de figure, vous devez alors proposer le symbole ? dans votre requête à l'endroit où vous souhaitez ajouter votre valeur. Dans le cas d'une date, pensez à rajouter les parenthèses dans votre requête, comme suit (?). Finalement, la requête globale qui permet de retrouver tous les rendez-vous d'une date précise est la suivante :
SELECT * FROM AGENDA WHERE date = (?) ORDER BY heure
Intégration des ressources dans un projet Qt

Jusqu'à présent, nous avons étudié l'accès aux données dans des fichiers externes, mais avec Qt, il est également possible d'intégrer du texte ou des données binaires dans l'exécutable de l'application. Pour ce faire, il convient d'utiliser le système de ressources de Qt.

Une ressource est un fichier XML, dont l'extension est *.qrc qui répertorie les fichiers à intégrer dans l'exécutable. Les ressources sont ensuite converties en code C++ par l'utilitaire standard intégré rcc le compilateur de ressources de Qt. Depuis l'application, les ressources sont identifiées par le préfixe de chemin ainsi que le fichier concené, par exemple :/icones/calendar.png ou bien :/configuration/agenda.config.
ressources.qrc

Avantages
L'intégration de données dans l'exécutable présente plusieurs avantages : les données ne peuvent être perdues et cette opération permet la création d'exécutables véritablement autonomes.
Inconvénients
Les inconvénients sont les suivants : si les données intégrées doivent être changées, il est impératif de remplacer l'exécutable entier, et la taille de ce dernier sera plus importante car il doit s'adapter aux données intégrées.
La classe QFile

Un objet de la classe QFile représente un fichier spécifique du système de fichier local quelque soit la plate-forme utilisée. Vous spécifiez le nom du fichier désiré au moment de la construction. Par la suite, pour exploiter le contenu du fichier, vous devez l'ouvrir en spécifiant le mode d'ouverture requis, lecture ou écriture, tout ceci au moyen de la méthode open().

Méthodes utiles
QFile(nom du fichier)
Construit un objet représentant le fichier spécifié en argument. Si vous spécifier un fichier au travers d'un répertoire, vous utilisez le séparateur quelque soit le système de fichier, même pour Windows. ATTENTION, le symbole \ n'est pas du tout supporté.
copy(nom du fichier source, nom du fichier destination)
Copie un fichier et en crée un autre. Si l'opération s'est bien déroulée, la fonction retourne true, sinon false dans le cas contraire.
exists()
Permet de savoir si le fichier représenté par QFile existe vraiment.
fileName() - setFileName(nom du fichier)
Permet de connaître le nom du fichier représenté par QFile. Il est également possible de représenter un autre fichier au moyen du même objet QFile.
open(mode d'ouverture)
Ouvre réellement le fichier représenté par QFile. Nous pouvons ouvrir le fichier en lecture seule QIODevice::ReadOnly, en écriture seule QIODevice::WriteOnly, en lecture et écriture QIODevice::ReadWrite. Vous pouvez rajoutez des spécifications supplémentaires pour indiquer par exemple que le fichier doit être un fichier texte au moyen de la constante suivante QIODevice::Text.
permissions() – setPermissions(permissions)
Il est possible de connaître ou de régler les permissions accordées pour chaque fichier. Ces permissions sont de la même nature que celles que vous rencontrez sous les systèmes Unix. La classe QFile possède en interne une énumération dénommée Permission qui propose l'ensemble des constantes correspondant à ce système de fichier. Nous retrouvons les quatre types d'utilisateurs, respectivement : Propriétaire, Utilisateur, Groupe et Autre. enum Permission {ReadOwner, WriteOwner, ExeOwner, ReadUser, WriteUser, ExeUser, ReadGroup, WriteGroup, ExeGroup, ReadOther, WriteOther, ExeOther};
remove()
Supprime définitivement le fichier en cours et renvoie true si l'opération s'est déroulée correctement.
rename(nouveau nom)
Permet de changer le nom du fichier.
close()
Clôture le fichier si ce dernier est ouvert. Permet ainsi d'éviter de perdre des données. Ceci-dit, cette méthode est automatiquement appelée lorsque l'objet QFile est détruit, notamment lorsque nous sortons de la portée de la déclaration de l'objet.
size()
Retourne la taille du fichier en octets.
Lire et écrire des données binaires

La façon la plus simple de charger et d'enregistrer des données binaires avec Qt consiste à prendre un objet de type QFile, à ouvrir le fichier et à y accéder par le biais d'un objet QDataStream. Ce dernier fournit un format de stockage indépendant de la plate-forme, qui supporte les types C++ de base tels que les int et double, de nombreux types de données Qt, dont QByteArray, QFont, QImage, QPixmap et QString ainsi que des classes conteneur telles que QList<T>. Un QByteArray est un simple tableau d'octets représenté sous la forme d'un décompte d'octets, suivi des octets eux-mêmes.

Si le fichier s'ouvre avec succès, nous créons un flux QDataStream dans lequel nous plaçons successivement l'ensemble des informations de natures totalement différentes à l'aide du simple opérateur que nous connaissons bien <<. Ces informations sont bien enregistrées sous forme de suite d'octets et finalement stockées dans le fichier correspondant. Si nous souhaitons lire ou écrire dans un fichier en une seule fois, nous pouvons éviter l'utilisation de QDataStream et recourir à la place aux méthodes write() et readAll() de QIODevice et donc de QFile.
Lire et écrire du texte

Les formats de fichiers binaires sont généralement plus compacts que ceux basés sur le texte, mais ils ne sont pas lisibles ou modifiables par l'homme. Si vous désirez pouvoir consulter les données avec un simple éditeur, il est possible d'utiliser à la place les formats texte. Qt fournit la classe QTextStream pour lire et écrire des fichiers de texte brut, mais également d'autres formats de texte comme le HTML ou le XML ainsi que du code source.

QTextStream se charge automatiquement de la conversion entre Unicode et le codage local prévu par le système de fichier en cours, et gère de façon transparente les conventions de fin de ligne utilisées par les différents systèmes d'exploitation \r\n sur Windows et \n sur Unix et Mac OS. En plus des caractères et des chaînes, QTextStream prend en charge les types numériques de base du C++, qu'il convertit alors automatiquement en chaînes. L'écriture du texte est très facile, mais sa lecture peut représenter un véritable défit, car les données textuelles, contrairement aux données binaires écrites au moyen de QDataStream, sont fondamentalement ambiguës. Afin d'éviter ce genre d'inconvénient, il serait peut-être souhaitable d'enregistrer le texte ligne par ligne au moyen du terminateur endl et de faire de même lors de la lecture au moyen de la méthode readLine() de QTextStream qui récupère à chaque occurrence une seule ligne du texte enregistré. Il est également possible de traiter le texte en entier. Nous pouvons ainsi lire le fichier complet en une seule fois à l'aide de la méthode readAll() de QTextStream, si nous ne nous préoccupons pas bien sûr de l'utilisation de la mémoire, ou si nous savons que le fichier est petit.
Mise en oeuvre du codage côté PC

Par rapport au projet précédent, nous prévoyons un fichier de configuration description au format JSON qui sera pris en compte par le fichier de ressource intégré à l'application dans lequel nous rajoutons l'ensemble des icônes nécessaires au projet. L'IHM change afin de proposer la navigation des rendez-vous en spécifiant la date requise. Enfin, un nouveau bouton apparaît afin de pouvoir récupérer, quand nous le désirons, la liste complète de l'ensemble des rendez-vous.

comptesPC.pro
QT       += core gui websockets
greaterThan(QT_MAJOR_VERSION, 4): QT += widgets

TARGET = agenda
TEMPLATE = app
CONFIG  += c++11

SOURCES += main.cpp principal.cpp criptage.cpp
HEADERS  += principal.h criptage.h
FORMS    += principal.ui

RESOURCES += ressources.qrc
cryptage.h
#ifndef CRYPTAGE_H
#define CRYPTAGE_H

#include <string>
#include <ctime>
using namespace std;

class Cryptage
{
  static int random() { return (clock() % 9) + 1; }
public:
  static string crypter(const string &message);
  static string decrypter(const string &texte);
};

#endif // CRIPTAGE_H
cryptage.cpp
#include "criptage.h"

string Cryptage::crypter(const string &message)
{
  string encodage;
  char lettre = ' ' + random(); // lettre référence pour la clef
  encodage += lettre;
  int chiffre = random(); // chiffre référence pour le criptage
  encodage += lettre + chiffre;
  int nombre = random();  // nombre d'éléments supplémentaires dans la clef
  encodage += lettre + nombre;
  int decalages[nombre]; // suite de nombre servant au calcul de chaque lettre du message
  for (int i=0; i<nombre; i++)
  {
    decalages[i] = random();
    encodage += lettre + decalages[i];
  }
  for (int i=0; i<message.size(); i++) encodage += message[i] - chiffre - decalages[i%nombre];
  // Brassage des lettres afin de mélanger la clé de criptage avec son message cripté
  string melange;
  nombre = encodage.size();
  if (nombre%3 == 0) encodage+=' ';
  for (int i=0, indice=2; i<nombre; i++, indice+=3) melange += encodage[indice%encodage.size()];
  return melange;
}

string Cryptage::decrypter(const string &texte)
{
// Remettre en place les lettres mélangées (clé de criptage suivi du message cripté) string melange = texte; string message = melange; int nombre = melange.size(); if (nombre%3 == 0) melange+=' '; for (int i=0, indice=2; i<nombre; i++, indice+=3) message[indice%melange.size()] = melange[i]; // Phase de décriptage
string decodage; int clef = 0; char lettre = message[clef++]; // lettre référence pour la clef int chiffre = message[clef++] - lettre; // chiffre référence pour le décriptage int nombre = message[clef++] - lettre; // nombre d'éléments dans la clef int decalages[nombre]; for (int i=0; i<nombre; i++) decalages[i] = message[clef++] - lettre; for (int i=clef; i<message.size(); i++) decodage += message[i] + chiffre + decalages[(i-clef)%nombre]; return decodage; }
principal.h
#ifndef PRINCIPAL_H
#define PRINCIPAL_H

#include "ui_principal.h"
#include <QWebSocket>
#include <vector>
#include <QSystemTrayIcon>
using namespace std;

class Principal : public QMainWindow, private Ui::Principal
{
  Q_OBJECT

public:
  explicit Principal(QWidget *parent = 0);
private slots:
  void nouvelAgenda();
  void modifierAgenda();
  void supprimerAgenda();
  void rechercherAgenda();
  void liste();
  void position(const QDate &date);
  void toutEffacer();  
  void receptionOctets(const QByteArray &octets);  
  void recuperer(const QJsonObject &json);
  void arreter();
  void alerte();
  void suivant();
  void precedent();
  void connecte();
  void nonconnecte();
void activerRechercheParDate(bool action);
private:
void soumettre(QJsonObject &json);
 void envoiJSON(const QJsonObject &json); void setNotification();
void lireConfig(); protected: void closeEvent(QCloseEvent *);
void timerEvent(QTimerEvent *) { suivant(); if (index==identifiants.size()) index=0; }
private: QWebSocket ws; int identifiant, index, nombre; vector<int> identifiants; bool quitter = false; QSystemTrayIcon *notification;
QString adresseLocale, adresseInternet;
}; #endif // PRINCIPAL_H
principal.cpp
#include "principal.h"
#include "cryptage.h"
#include <QJsonObject>
#include <QJsonArray>
#include <QJsonDocument>
#include <QMenu>
#include <QCloseEvent>
#include <QFile>

Principal::Principal(QWidget *parent) : QMainWindow(parent)
{
  setupUi(this);
  setNotification();
  lireConfig();
  ws.open(QUrl(adresseLocale));
  connect(&ws, SIGNAL(connected()), this, SLOT(connecte()));
  connect(&ws, SIGNAL(disconnected()), this, SLOT(nonconnecte()));
startTimer(600000); // alerte toutes dix minutes
} void Principal::setNotification() { QMenu *menu = new QMenu(this); menu->addAction(actionAfficher); menu->addSeparator(); menu->addAction(actionRevoir); menu->addAction(actionSuivant); menu->addAction(actionPrecedent); menu->addSeparator(); menu->addAction(actionQuitter); notification = new QSystemTrayIcon(QIcon(":/icones/calendar.png"), this); notification->setContextMenu(menu); notification->show(); } void Principal::lireConfig() { QFile fichier(":/configuration/agenda.config"); if (fichier.open(QIODevice::ReadOnly)) { QTextStream lire(&fichier); QJsonDocument document = QJsonDocument::fromJson(lire.readAll().toUtf8()); QJsonObject json = document.object(); adresseLocale = QString("ws://%1:%2/").arg(json["ipLocal"].toString()).arg(json["port"].toInt()); adresseInternet = QString("ws://%1:%2/").arg(json["ipInternet"].toString()).arg(json["port"].toInt()); } } void Principal::connecte() { connect(&ws, SIGNAL(textMessageReceived(QString)), barreEtat, SLOT(showMessage(QString))); connect(&ws, SIGNAL(binaryMessageReceived(QByteArray)), this, SLOT(receptionOctets(QByteArray))); } void Principal::nonconnecte() { ws.open(QUrl(adresseInternet)); } void Principal::activerRechercheParDate(bool action) { if (action) connect(date, SIGNAL(clicked(QDate)), this, SLOT(position(QDate))); else disconnect(date, SIGNAL(clicked(QDate)), this, SLOT(position(QDate))); }

void Principal::nouvelAgenda() { QJsonObject json; json["commande"] = "nouveau"; soumettre(json); } void Principal::modifierAgenda() { QJsonObject json; json["commande"] = "modifier"; json["id"] = identifiant; soumettre(json); } void Principal::soumettre(QJsonObject &json) { json["sujet"] = sujet->text(); json["pour"] = pour->text(); json["qui"] = qui->text(); json["tel"] = tel->text(); json["heure"] = heure->time().toString(); json["date"] = date->selectedDate().toString(); json["lieu"] = lieu->text(); json["note"] = note->text(); envoiJSON(json); } void Principal::envoiJSON(const QJsonObject &json) { QJsonDocument document(json); string message = Cryptage::crypter(document.toJson(QJsonDocument::Compact).data()); ws.sendBinaryMessage(message.c_str()); } void Principal::supprimerAgenda() { QJsonObject json; json["commande"] = "supprimer"; json["id"] = identifiant; envoiJSON(json); } void Principal::rechercherAgenda() { QJsonObject json; json["commande"] = "rechercher"; json["id"] = identifiant; envoiJSON(json); } void Principal::liste() { QJsonObject json; json["commande"] = "liste"; envoiJSON(json); } void Principal::position(const QDate &date) { QJsonObject json; json["commande"] = "position"; json["date"] = date.toString(); envoiJSON(json); } void Principal::toutEffacer() { sujet->clear(); pour->clear(); qui->clear(); tel->clear(); lieu->clear(); note->clear(); barreEtat->clearMessage(); } void Principal::receptionOctets(const QByteArray &octets) { string message = Criptage::decripter(octets.data()); QJsonDocument document = QJsonDocument::fromJson(message.c_str()); QJsonObject json = document.object(); QString commande = json["commande"].toString(); if (commande=="liste") { QJsonArray ids = json["ids"].toArray(); identifiants.clear(); for (auto id : ids) identifiants.push_back(id.toInt()); if (!identifiants.empty()) { identifiant = identifiants[index = 0]; actionPrecedent->setEnabled(false); actionSuivant->setEnabled(identifiants.size()>1); rechercherAgenda(); } }
else if (commande=="nombre") { nombre = json["nombre"].toInt(); }
 else if (commande=="rechercher") { recuperer(json); actionSuivant->setEnabled(index!=identifiants.size()-1); actionPrecedent->setEnabled(index!=0); } else if (commande=="position") { recuperer(json); index = 0; while (identifiants[index]!=identifiant) index++; actionSuivant->setEnabled(index!=identifiants.size()-1); actionPrecedent->setEnabled(index!=0); } } void Principal::recuperer(const QJsonObject &json) { identifiant = json["id"].toInt(); sujet->setText(json["sujet"].toString()); pour->setText(json["pour"].toString()); qui->setText(json["qui"].toString()); tel->setText(json["tel"].toString()); heure->setTime(QTime::fromString(json["heure"].toString())); jour->setDate(QDate::fromString(json["date"].toString())); date->setSelectedDate(QDate::fromString(json["date"].toString())); lieu->setText(json["lieu"].toString()); note->setText(json["note"].toString()); alerte(); } void Principal::alerte() { if (isHidden()) { QString texte = "Sujet : "; texte += sujet->text(); texte += "\nNote : "; texte += note->text(); texte += "\nPour : "; texte += pour->text(); texte += "\nAvec : "; texte += qui->text(); texte += "\n"; texte += jour->text(); texte += "\n"; texte += heure->text(); if (index<nombre) notification->showMessage("ATTENTION, c'est pour aujourd'hui", texte); else notification->showMessage("Rendez-vous", texte); } } void Principal::suivant() { if (index<(identifiants.size()-1)) { identifiant = identifiants[++index]; actionSuivant->setEnabled(false); actionPrecedent->setEnabled(false); rechercherAgenda(); } } void Principal::precedent() { if (index>0) { identifiant = identifiants[--index]; actionSuivant->setEnabled(false); actionPrecedent->setEnabled(false); rechercherAgenda(); } } void Principal::closeEvent(QCloseEvent *evt) { if (!quitter) { hide(); evt->ignore(); } } void Principal::arreter() { quitter = true; close(); }

Répertoire téléphonique avec base de données dans la Raspberry

Je vous propose un autre projet qui utilise les mêmes techniques que nous venons déjà de développer. Il s'agit de construire une application et un service qui permet d'enregistrer les numéros de téléphone d'un ensemble de personnes, bref un répertoire téléphonique. Au niveau de l'application cliente, l'ergonomie doit être soigneusement pensée. Il serait judicieux, d'avoir dès le départ l'ensemble des noms des personnes déjà enregistrées et de pouvoir choisir celui qui nous intéresse à l'aide d'une ComboBox. Une fois que le nom est sélectionné, une deuxième ComboBox nous permet de choisir ensuite le prénom associé dans une liste réduite tous les prénoms possibles correspondants au nom choisi. Une fois que nous disposons de ces deux éléments, l'ensemble des numéros est alors affiché avec en plus éventuellement, l'adresse mail et des commentaires associés.

Mise en oeuvre du codage côté raspberry

Au niveau du service, encore une fois nous devons juste construite une base de données qui comporte une seule table. Le principe de communication est totalement identique aux projets précédents.

RepertoirePI.pro
QT       += core websockets sql
QT       -= gui

TARGET = repertoire
target.path = /home/pi/Travail
INSTALLS += target

CONFIG   += console c++11
CONFIG   -= app_bundle

TEMPLATE = app

SOURCES += main.cpp service.cpp traitement.cpp criptage.cpp
HEADERS += service.h traitement.h criptage.h
cryptage.h
#ifndef CRYPTAGE_H
#define CRYPTAGE_H

#include <string>
#include <ctime>
using namespace std;

class Cryptage
{
  static int random() { return (clock() % 9) + 1; }
public:
  static string crypter(const string &message);
  static string decrypter(const string &texte);
};

#endif // CRIPTAGE_H
cryptage.cpp
#include "criptage.h"

string Cryptage::crypter(const string &message)
{
  string encodage;
  char lettre = ' ' + random(); // lettre référence pour la clef
  encodage += lettre;
  int chiffre = random(); // chiffre référence pour le criptage
  encodage += lettre + chiffre;
  int nombre = random();  // nombre d'éléments supplémentaires dans la clef
  encodage += lettre + nombre;
  int decalages[nombre]; // suite de nombre servant au calcul de chaque lettre du message
  for (int i=0; i<nombre; i++)
  {
    decalages[i] = random();
    encodage += lettre + decalages[i];
  }
  for (int i=0; i<message.size(); i++) encodage += message[i] - chiffre - decalages[i%nombre];
  // Brassage des lettres afin de mélanger la clé de criptage avec son message cripté
  string melange;
  nombre = encodage.size();
  if (nombre%3 == 0) encodage+=' ';
  for (int i=0, indice=2; i<nombre; i++, indice+=3) melange += encodage[indice%encodage.size()];
  return melange;
}

string Cryptage::decrypter(const string &texte)
{
// Remettre en place les lettres mélangées (clé de criptage suivi du message cripté) string melange = texte; string message = melange; int nombre = melange.size(); if (nombre%3 == 0) melange+=' '; for (int i=0, indice=2; i<nombre; i++, indice+=3) message[indice%melange.size()] = melange[i]; // Phase de décriptage
string decodage; int clef = 0; char lettre = message[clef++]; // lettre référence pour la clef int chiffre = message[clef++] - lettre; // chiffre référence pour le décriptage int nombre = message[clef++] - lettre; // nombre d'éléments dans la clef int decalages[nombre]; for (int i=0; i<nombre; i++) decalages[i] = message[clef++] - lettre; for (int i=clef; i<message.size(); i++) decodage += message[i] + chiffre + decalages[(i-clef)%nombre]; return decodage; }
service.h
#ifndef SERVICE_H
#define SERVICE_H

#include "cryptage.h"
#include <QObject>
#include <QWebSocketServer>
#include <QWebSocket>
#include <vector>
using namespace std;

class Service : public QObject
{
  Q_OBJECT
public:
  explicit Service(QObject *parent, const char *nom, int port);
protected:
  vector<QWebSocket *> connectes;
  QWebSocket *client;
  bool cryptage = false;
  string message;
private:
  QWebSocketServer service;
signals:
  void arreter();
protected:
  void cripter(const QByteArray &octets)   { message = Cryptage::crypter(octets.data()); }
  void decripter(const QByteArray &octets) { message = Cryptage::decrypter(octets.data()); }
public slots:
  virtual void nouvelleConnexion();
  virtual void receptionMessage(const QString &texte);
  virtual void receptionOctets(const QByteArray &octets);
  virtual void deconnexion();
};

#endif // SERVICE_H
service.cpp
#include "service.h"

Service::Service(QObject *parent, const char *nom, int port) : QObject(parent), service(nom, QWebSocketServer::NonSecureMode, this)
{
  if (service.listen(QHostAddress::Any, port))
    connect(&service, SIGNAL(newConnection()), this, SLOT(nouvelleConnexion()));
  else arreter();
}

void Service::nouvelleConnexion()
{
  client = service.nextPendingConnection();
  connectes.push_back(client);
  client->sendTextMessage("Bienvenue !");
  connect(client, SIGNAL(textMessageReceived(QString)), this, SLOT(receptionMessage(QString)));
  connect(client, SIGNAL(binaryMessageReceived(QByteArray)), this, SLOT(receptionOctets(QByteArray)));
  connect(client, SIGNAL(disconnected()), this, SLOT(deconnexion()));
}

void Service::receptionMessage(const QString &texte)
{
  if (texte=="stop") { arreter(); return; }
  client = (QWebSocket *) sender();
}

void Service::receptionOctets(const QByteArray &octets)
{
  if (criptage) decripter(octets);
  client = (QWebSocket *) sender();
}

void Service::deconnexion()
{
  QWebSocket *client = (QWebSocket *) sender();
  client->deleteLater();
  connectes.erase(find(connectes.begin(), connectes.end(), client));
  disconnect(client, SIGNAL(textMessageReceived(QString)), this, SLOT(receptionMessage(QString)));
  disconnect(client, SIGNAL(binaryMessageReceived(QByteArray)), this, SLOT(receptionOctets(QByteArray)));
  disconnect(client, SIGNAL(disconnected()), this, SLOT(deconnexion()));
}
traitement.h
#ifndef TRAITEMENT_H
#define TRAITEMENT_H

#include "service.h"

class Traitement : public Service
{
public:
  Traitement() : Service(nullptr, "service", 7755) { criptage = true; }
public:
  void receptionOctets(const QByteArray &octets) override;
  void nouvelleConnexion() override;
  void deconnexion() override;
private:
  void envoyerATous(QJsonObject &json);
};

#endif // TRAITEMENT_H
traitement.cpp
#include "traitement.h"
#include <QSqlDatabase>
#include <QSqlError>
#include <QJsonObject>
#include <QJsonArray>
#include <QJsonDocument>
#include <iostream>

void Traitement::nouvelleConnexion()
{
  Service::nouvelleConnexion();
  listeNoms();
}

void Traitement::bdd()
{
  QSqlDatabase bd = QSqlDatabase::addDatabase("QSQLITE");
  bd.setDatabaseName("repertoire.bd");
  if (bd.open()) table();
  else cout << "Base de données inaccessible !" << endl;
}

void Traitement::table()
{
  QSqlQuery requete;
  if (requete.exec("CREATE TABLE IF NOT EXISTS REPERTOIRE"
          "(id INTEGER PRIMARY KEY,"
          "nom VARCHAR(30),"
          "prénom VARCHAR(30),"
          "messagerie VARCHAR(40),"
          "note VARCHAR(60),"
          "domicile VARCHAR(15),"
          "box VARCHAR(15),"
          "mobile VARCHAR(15),"
          "bureau VARCHAR(15))")) cout << "Table REPERTOIRE disponible" << endl;
  else cout << requete.lastError().text().toStdString() << endl;
}

void Traitement::receptionOctets(const QByteArray &octets)
{
  Service::receptionOctets(octets);
  cout << "Réception => " << message << endl << endl;
  QJsonDocument document = QJsonDocument::fromJson(message.c_str());
  QJsonObject json = document.object();
  QString commande = json["commande"].toString();
  if      (commande=="noms")        listeNoms();
  else if (commande=="prénoms")     listePrenoms(json["nom"].toString());
  else if (commande=="nouveau")     nouveau(json);
  else if (commande=="modifier")    modifier(json);
  else if (commande=="supprimer")   supprimer(json["id"].toInt());
  else if (commande=="rechercher")  rechercher(json);
  else if (commande=="aqui")        aqui(json["téléphone"].toString());
}

void Traitement::listeNoms()
{
  QJsonArray noms;
  QSqlQuery requete("SELECT DISTINCT nom FROM REPERTOIRE ORDER BY nom");
  while (requete.next()) noms.append(requete.value("nom").toString());
  QJsonObject json;
  json["commande"] = "noms";
  json["noms"] = noms;
  QJsonDocument document(json);
  cout << "Envoi => " << document.toJson(QJsonDocument::Compact).data() << endl << endl;
  cripter(document.toJson(QJsonDocument::Compact));
  client->sendBinaryMessage(message.c_str());
}

void Traitement::listePrenoms(const QString &nom)
{
  QJsonArray prenoms;
  QSqlQuery requete;
  requete.prepare("SELECT prénom FROM REPERTOIRE WHERE nom=? ORDER BY prénom");
  requete.addBindValue(nom);
  if (requete.exec()) while (requete.next()) prenoms.append(requete.value("prénom").toString());
  else client->sendTextMessage(requete.lastError().text());
  QJsonObject json;
  json["commande"] = "prénoms";
  json["prénoms"] = prenoms;
  QJsonDocument document(json);
  cout << "Envoi => " << document.toJson(QJsonDocument::Compact).data() << endl << endl;
  cripter(document.toJson(QJsonDocument::Compact));
  client->sendBinaryMessage(message.c_str());
}

void Traitement::nouveau(const QJsonObject &json)
{
  QSqlQuery requete;
  requete.prepare("INSERT INTO REPERTOIRE (nom, prénom, messagerie, note, domicile, box, mobile, bureau)"
                  " VALUES (?, ?, ?, ?, ?, ?, ?, ?)");
  requete.addBindValue(json["nom"].toString());
  requete.addBindValue(json["prénom"].toString());
  requete.addBindValue(json["messagerie"].toString());
  requete.addBindValue(json["note"].toString());
  requete.addBindValue(json["domicile"].toString());
  requete.addBindValue(json["box"].toString());
  requete.addBindValue(json["mobile"].toString());
  requete.addBindValue(json["bureau"].toString());
  if (requete.exec()) client->sendTextMessage("Répertoire enregistré");
  else client->sendTextMessage(requete.lastError().text());
  listeNoms();
}

void Traitement::modifier(const QJsonObject &json)
{
  QSqlQuery requete;
  requete.prepare("UPDATE REPERTOIRE "
                  "SET messagerie=:messagerie, note=:note, domicile=:domicile, box=:box, mobile=:mobile, bureau=:bureau "
                  "WHERE id=:id");
  requete.bindValue(":id", json["id"].toInt());
  requete.bindValue(":messagerie", json["messagerie"].toString());
  requete.bindValue(":note", json["note"].toString());
  requete.bindValue(":domicile", json["domicile"].toString());
  requete.bindValue(":box", json["box"].toString());
  requete.bindValue(":mobile", json["mobile"].toString());
  requete.bindValue(":bureau", json["bureau"].toString());
  if (requete.exec()) client->sendTextMessage("Répertoire modifié");
  else client->sendTextMessage(requete.lastError().text());
}

void Traitement::supprimer(int id)
{
  QSqlQuery requete;
  requete.prepare("DELETE FROM REPERTOIRE WHERE id=?");
  requete.addBindValue(id);
  if (requete.exec()) { client->sendTextMessage("Répertoire supprimé"); listeNoms(); }
  else client->sendTextMessage(requete.lastError().text());
}

void Traitement::rechercher(const QJsonObject &json)
{
  QSqlQuery requete;
  requete.prepare("SELECT * FROM REPERTOIRE WHERE nom=? AND prénom=?");
  requete.addBindValue(json["nom"].toString());
  requete.addBindValue(json["prénom"].toString());
  QJsonObject commande;
  commande["commande"] = "rechercher";
  soumettre(commande, requete);
}

void Traitement::aqui(const QString &telephone)
{
  QSqlQuery requete("SELECT * FROM REPERTOIRE WHERE domicile=? OR box=? OR mobile=? OR bureau=?");
  requete.addBindValue(telephone);
  requete.addBindValue(telephone);
  requete.addBindValue(telephone);
  requete.addBindValue(telephone);
  QJsonObject commande;
  commande["commande"] = "aqui";
  soumettre(commande, requete);
}

void Traitement::soumettre(QJsonObject &json, QSqlQuery &requete)
{
  if (requete.exec()) {
    if (requete.next())
    {
      json["id"] = requete.value("id").toInt();
      json["nom"] = requete.value("nom").toString();
      json["prénom"] = requete.value("prénom").toString();
      json["note"] = requete.value("note").toString();
      json["messagerie"] = requete.value("messagerie").toString();
      json["domicile"] = requete.value("domicile").toString();
      json["box"] = requete.value("box").toString();
      json["mobile"] = requete.value("mobile").toString();
      json["bureau"] = requete.value("bureau").toString();
      QJsonDocument document(json);
      cout << "Envoi => " << document.toJson(QJsonDocument::Compact).data() << endl << endl;
      cripter(document.toJson(QJsonDocument::Compact));
      client->sendBinaryMessage(message.c_str());
      client->sendTextMessage("Ok");
    }
    else client->sendTextMessage("Aucune personne.");
  }
  else client->sendTextMessage(requete.lastError().text());
}
Mise en oeuvre du codage côté PC

repertoire.pro
QT       += core gui websockets
greaterThan(QT_MAJOR_VERSION, 4): QT += widgets

TARGET = repertoire
TEMPLATE = app
CONFIG  += c++11

SOURCES += main.cpp principal.cpp criptage.cpp
HEADERS  += principal.h criptage.h
FORMS    += principal.ui

RESOURCES += ressources.qrc
cryptage.h
#ifndef CRYPTAGE_H
#define CRYPTAGE_H

#include <string>
#include <ctime>
using namespace std;

class Cryptage
{
  static int random() { return (clock() % 9) + 1; }
public:
  static string crypter(const string &message);
  static string decrypter(const string &texte);
};

#endif // CRYPTAGE_H
cryptage.cpp
#include "cryptage.h"

string Cryptage::crypter(const string &message)
{
  string encodage;
  char lettre = ' ' + random(); // lettre référence pour la clef
  encodage += lettre;
  int chiffre = random(); // chiffre référence pour le criptage
  encodage += lettre + chiffre;
  int nombre = random();  // nombre d'éléments supplémentaires dans la clef
  encodage += lettre + nombre;
  int decalages[nombre]; // suite de nombre servant au calcul de chaque lettre du message
  for (int i=0; i<nombre; i++)
  {
    decalages[i] = random();
    encodage += lettre + decalages[i];
  }
  for (int i=0; i<message.size(); i++) encodage += message[i] - chiffre - decalages[i%nombre];
  // Brassage des lettres afin de mélanger la clé de cryptage avec son message crypté
  string melange;
  nombre = encodage.size();
  if (nombre%3 == 0) encodage+=' ';
  for (int i=0, indice=2; i<nombre; i++, indice+=3) melange += encodage[indice%encodage.size()];
  return melange;
}

string Cryptage::decrypter(const string &texte)
{
// Remettre en place les lettres mélangées (clé de cryptage suivi du message crypté) string melange = texte; string message = melange; int nombre = melange.size(); if (nombre%3 == 0) melange+=' '; for (int i=0, indice=2; i<nombre; i++, indice+=3) message[indice%melange.size()] = melange[i]; // Phase de décryptage
string decodage; int clef = 0; char lettre = message[clef++]; // lettre référence pour la clef int chiffre = message[clef++] - lettre; // chiffre référence pour le décryptage int nombre = message[clef++] - lettre; // nombre d'éléments dans la clef int decalages[nombre]; for (int i=0; i<nombre; i++) decalages[i] = message[clef++] - lettre; for (int i=clef; i<message.size(); i++) decodage += message[i] + chiffre + decalages[(i-clef)%nombre]; return decodage; }
principal.h
#ifndef PRINCIPAL_H
#define PRINCIPAL_H

#include "ui_principal.h"
#include <QWebSocket>
#include <vector>
using namespace std;

class Principal : public QMainWindow, private Ui::Principal
{
  Q_OBJECT

public:
  explicit Principal(QWidget *parent = 0);
private slots:
  void nouvelEnregistrement();
  void modifierEnregistrement();
  void supprimerEnregistrement();
  void rechercherNoms();
  void rechercherPrenoms(const QString &nom);
  void rechercherPersonne(const QString &prenom);
  void rechercherPersonne();
  void toutEffacer();  
  void receptionOctets(const QByteArray &octets);  
  void connecte();
  void nonconnecte();
private:
  void soumettre(QJsonObject &json);
  void envoiJSON(const QJsonObject &json);
  void lireConfig();
private:
  QWebSocket ws;
  int identifiant;
  QString adresseLocale, adresseInternet;
};

#endif // PRINCIPAL_H
principal.cpp
#include "principal.h"
#include "criptage.h"
#include <QJsonObject>
#include <QJsonArray>
#include <QJsonDocument>
#include <QMenu>
#include <QCloseEvent>
#include <QFile>

Principal::Principal(QWidget *parent) : QMainWindow(parent)
{
  setupUi(this);
  lireConfig();
  ws.open(QUrl(adresseLocale));
  connect(&ws, SIGNAL(connected()), this, SLOT(connecte()));
  connect(&ws, SIGNAL(disconnected()), this, SLOT(nonconnecte()));
}

void Principal::lireConfig()
{
  QFile fichier(":/configuration/repertoire.config");
  if (fichier.open(QIODevice::ReadOnly))
  {
    QTextStream lire(&fichier);
    QJsonDocument document = QJsonDocument::fromJson(lire.readAll().toUtf8());
    QJsonObject json = document.object();
    adresseLocale = QString("ws://%1:%2/").arg(json["ipLocal"].toString()).arg(json["port"].toInt());
    adresseInternet = QString("ws://%1:%2/").arg(json["ipInternet"].toString()).arg(json["port"].toInt());
  }
}

void Principal::connecte()
{
  connect(&ws, SIGNAL(textMessageReceived(QString)), barreEtat, SLOT(showMessage(QString)));
  connect(&ws, SIGNAL(binaryMessageReceived(QByteArray)), this, SLOT(receptionOctets(QByteArray)));
}

void Principal::nonconnecte()
{
  ws.open(QUrl(adresseInternet));
}

void Principal::nouvelEnregistrement()
{
  QJsonObject json;
  json["commande"] = "nouveau";
  soumettre(json);
}

void Principal::modifierEnregistrement()
{
  QJsonObject json;
  json["commande"] = "modifier";
  json["id"] = identifiant;
  soumettre(json);
}

void Principal::soumettre(QJsonObject &json)
{
  json["nom"] = nom->currentText();
  json["prénom"] = prenom->currentText();
  json["messagerie"] = messagerie->text();
  json["note"] = note->text();
  json["domicile"] = telDomicile->text();
  json["box"] = telBox->text();
  json["mobile"] = telMobile->text();
  json["bureau"] = telBureau->text();
  envoiJSON(json);
}

void Principal::envoiJSON(const QJsonObject &json)
{
  QJsonDocument document(json);
  string message = Criptage::cripter(document.toJson(QJsonDocument::Compact).data());
  ws.sendBinaryMessage(message.c_str());
}

void Principal::supprimerEnregistrement()
{
  QJsonObject json;
  json["commande"] = "supprimer";
  json["id"] = identifiant;
  envoiJSON(json);
}

void Principal::rechercherNoms()
{
  QJsonObject json;
  json["commande"] = "noms";
  envoiJSON(json);
}

void Principal::rechercherPrenoms(const QString &nom)
{
  QJsonObject json;
  json["commande"] = "prénoms";
  json["nom"] = nom;
  envoiJSON(json);
}

void Principal::rechercherPersonne(const QString &prenom)
{
  QJsonObject json;
  json["commande"] = "rechercher";
  json["nom"] = nom->currentText();
  json["prénom"] = prenom;
  envoiJSON(json);
}

void Principal::rechercherPersonne()
{
  QJsonObject json;
  json["commande"] = "aqui";
  json["téléphone"] = telDomicile->text();
  envoiJSON(json);
}

void Principal::toutEffacer()
{
  nom->clearEditText();
  prenom->clearEditText();
  messagerie->clear();
  note->clear();
  telDomicile->clear();
  telBox->clear();
  telMobile->clear();
  telBureau->clear();
  barreEtat->clearMessage();
}

void Principal::receptionOctets(const QByteArray &octets)
{
  string message = Cryptage::decrypter(octets.data());
  QJsonDocument document = QJsonDocument::fromJson(message.c_str());
  QJsonObject json = document.object();
  QString commande = json["commande"].toString();
  if (commande=="noms")
  {
    QJsonArray noms = json["noms"].toArray();
    nom->clear();
    for (auto nomPersonne : noms) nom->addItem(nomPersonne.toString());
  }
  else if (commande=="prénoms")
  {
      QJsonArray prenoms = json["prénoms"].toArray();
      prenom->clear();
      for (auto prenomPersonne : prenoms) prenom->addItem(prenomPersonne.toString());
  }
  else if (commande=="rechercher")
  {
      identifiant = json["id"].toInt();
      note->setText(json["note"].toString());
      messagerie->setText(json["messagerie"].toString());
      telDomicile->setText(json["domicile"].toString());
      telBox->setText(json["box"].toString());
      telMobile->setText(json["mobile"].toString());
      telBureau->setText(json["bureau"].toString());
  }
  else if (commande=="aqui")
  {
      toutEffacer();
      identifiant = json["id"].toInt();
      nom->setCurrentText(json["nom"].toString());
      prenom->setCurrentText(json["prénom"].toString());
      note->setText(json["note"].toString());
      messagerie->setText(json["messagerie"].toString());
      telDomicile->setText(json["domicile"].toString());
      telBox->setText(json["box"].toString());
      telMobile->setText(json["mobile"].toString());
      telBureau->setText(json["bureau"].toString());
  }
}

Messagerie instantanée

Pour clôturer l'ensemble de ces projets qui nous ont permis de communiquer à l'aide du protocole websocket et de ce fait d'échanger des informations par Internet, je vous propose de réaliser une messagerie instantanée, ce que nous appelons couramment un chat. Cette fois-ci, l'échange se fait au travers de plusieurs clients connectés, ce qui est une nouveauté par rapport au projets précédents.

Mise en oeuvre du codage côté raspberry

Pour ce projet, nous n'avons pas besoin de constituer une base de données côté serveur. Il suffit juste de mémoriser l'ensemble des clients actuellement connectés, ceci en temps réel. Chaque client reçoit une notification sur le dernier client qui se connecte ou se déconnecte avec en plus la liste de tous les présents.  Le principe de communication cripté est totalement identique aux projets précédents.

RepertoirePI.pro
QT       += core websockets
QT       -= gui

TARGET = messagerie
target.path = /home/pi/Travail
INSTALLS += target

CONFIG   += console c++11
CONFIG   -= app_bundle

TEMPLATE = app

SOURCES += main.cpp service.cpp traitement.cpp criptage.cpp
HEADERS += service.h traitement.h criptage.h
cryptage.h
#ifndef CRYPTAGE_H
#define CRYPTAGE_H

#include <string>
#include <ctime>
using namespace std;

class Cryptage
{
  static int random() { return (clock() % 9) + 1; }
public:
  static string crypter(const string &message);
  static string decrypter(const string &texte);
};

#endif // CRYPTAGE_H
cryptage.cpp
#include "cryptage.h"

string Cryptage::crypter(const string &message)
{
  string encodage;
  char lettre = ' ' + random(); // lettre référence pour la clef
  encodage += lettre;
  int chiffre = random(); // chiffre référence pour le criptage
  encodage += lettre + chiffre;
  int nombre = random();  // nombre d'éléments supplémentaires dans la clef
  encodage += lettre + nombre;
  int decalages[nombre]; // suite de nombre servant au calcul de chaque lettre du message
  for (int i=0; i<nombre; i++)
  {
    decalages[i] = random();
    encodage += lettre + decalages[i];
  }
  for (int i=0; i<message.size(); i++) encodage += message[i] - chiffre - decalages[i%nombre];
  // Brassage des lettres afin de mélanger la clé de cryptage avec son message crypté
  string melange;
  nombre = encodage.size();
  if (nombre%3 == 0) encodage+=' ';
  for (int i=0, indice=2; i<nombre; i++, indice+=3) melange += encodage[indice%encodage.size()];
  return melange;
}

string Cryptage::decrypter(const string &texte)
{
// Remettre en place les lettres mélangées (clé de cryptage suivi du message crypté) string melange = texte; string message = melange; int nombre = melange.size(); if (nombre%3 == 0) melange+=' '; for (int i=0, indice=2; i<nombre; i++, indice+=3) message[indice%melange.size()] = melange[i]; // Phase de décryptage
string decodage; int clef = 0; char lettre = message[clef++]; // lettre référence pour la clef int chiffre = message[clef++] - lettre; // chiffre référence pour le décryptage int nombre = message[clef++] - lettre; // nombre d'éléments dans la clef int decalages[nombre]; for (int i=0; i<nombre; i++) decalages[i] = message[clef++] - lettre; for (int i=clef; i<message.size(); i++) decodage += message[i] + chiffre + decalages[(i-clef)%nombre]; return decodage; }
service.h
#ifndef SERVICE_H
#define SERVICE_H

#include "cryptage.h"
#include <QObject>
#include <QWebSocketServer>
#include <QWebSocket>
#include <map>
using namespace std;

class Service : public QObject
{
  Q_OBJECT
public:
  explicit Service(QObject *parent, const char *nom, int port);
protected:
  map<QWebSocket *, QString> connectes;
  QWebSocket *client;
  bool cryptage = false;
  string message;
private:
  QWebSocketServer service;
signals:
  void arreter();
protected:
  void crypter(const QByteArray &octets)   { message = Cryptage::crypter(octets.data()); }
  void decrypter(const QByteArray &octets) { message = Cryptage::decrypter(octets.data()); }
public slots:
  virtual void nouvelleConnexion();
  virtual void receptionMessage(const QString &texte);
  virtual void receptionOctets(const QByteArray &octets);
  virtual void deconnexion();
};

#endif // SERVICE_H
service.cpp
#include "service.h"

Service::Service(QObject *parent, const char *nom, int port) : QObject(parent), service(nom, QWebSocketServer::NonSecureMode, this)
{
  if (service.listen(QHostAddress::Any, port))
    connect(&service, SIGNAL(newConnection()), this, SLOT(nouvelleConnexion()));
  else arreter();
}

void Service::nouvelleConnexion()
{
  client = service.nextPendingConnection();
  QString nom = client->requestUrl().path();
  nom.remove('/');
  connectes[client] = nom;
  connect(client, SIGNAL(textMessageReceived(QString)), this, SLOT(receptionMessage(QString)));
  connect(client, SIGNAL(binaryMessageReceived(QByteArray)), this, SLOT(receptionOctets(QByteArray)));
  connect(client, SIGNAL(disconnected()), this, SLOT(deconnexion()));
}

void Service::receptionMessage(const QString &texte)
{
  if (texte=="stop") { arreter(); return; }
  client = (QWebSocket *) sender();
}

void Service::receptionOctets(const QByteArray &octets)
{
  if (criptage) decrypter(octets);
  client = (QWebSocket *) sender();
}

void Service::deconnexion()
{
  QWebSocket *client = (QWebSocket *) sender();
  client->deleteLater();
  disconnect(client, SIGNAL(textMessageReceived(QString)), this, SLOT(receptionMessage(QString)));
  disconnect(client, SIGNAL(binaryMessageReceived(QByteArray)), this, SLOT(receptionOctets(QByteArray)));
  disconnect(client, SIGNAL(disconnected()), this, SLOT(deconnexion()));
}
traitement.h
#ifndef TRAITEMENT_H
#define TRAITEMENT_H

#include "service.h"

class Traitement : public Service
{
public:
  Traitement() : Service(nullptr, "service", 7755) { cryptage = true; }
public:
  void receptionOctets(const QByteArray &octets) override;
  void nouvelleConnexion() override;
  void deconnexion() override;
private:
  void envoyerATous(QJsonObject &json);
};

#endif // TRAITEMENT_H
traitement.cpp
#include "traitement.h"
#include <QJsonObject>
#include <QJsonArray>
#include <QJsonDocument>
#include <iostream>

void Traitement::nouvelleConnexion()
{
  Service::nouvelleConnexion();
  QJsonObject json;  
  json["commande"] = "ouverture";
  json["nom"] = connectes[client];
  envoyerATous(json);
}

void Traitement::deconnexion()
{
  Service::deconnexion();
  QJsonObject json;
  json["commande"] = "fermeture";
  json["nom"] = connectes[client];
  connectes.erase(client);
  envoyerATous(json);
}

void Traitement::envoyerATous(QJsonObject &json)
{
  QJsonArray liste;
  for (auto &chacun : connectes) liste.append(chacun.second);
  json["connectés"] = liste;
  QJsonDocument document(json);
  cout << "Envoi => " << document.toJson(QJsonDocument::Compact).data() << endl << endl;
  cripter(document.toJson(QJsonDocument::Compact));
  for (auto &chacun : connectes) chacun.first->sendBinaryMessage(message.c_str());
}

void Traitement::receptionOctets(const QByteArray &octets)
{
  Service::receptionOctets(octets);
  cout << "Réception => " << message << endl << endl;
  QJsonDocument document = QJsonDocument::fromJson(message.c_str());
  QJsonObject json = document.object();
  QString commande = json["commande"].toString();
  if (commande=="message") {
    bool existe = false;
    QWebSocket *destinataire;
    for (auto &chacun : connectes)
      if (chacun.second==json["destinataire"].toString()) { existe=true; destinataire = chacun.first; break; }
    if (existe) destinataire->sendBinaryMessage(octets);
  }
}
Mise en oeuvre du codage côté PC

repertoire.pro
QT       += core gui websockets
greaterThan(QT_MAJOR_VERSION, 4): QT += widgets

TARGET = messagerie
TEMPLATE = app
CONFIG  += c++11

SOURCES += main.cpp principal.cpp criptage.cpp
HEADERS  += principal.h criptage.h
FORMS    += principal.ui

RESOURCES += ressources.qrc
cryptage.h
#ifndef CRYPTAGE_H
#define CRYPTAGE_H

#include <string>
#include <ctime>
using namespace std;

class Cryptage
{
  static int random() { return (clock() % 9) + 1; }
public:
  static string crypter(const string &message);
  static string decrypter(const string &texte);
};

#endif // CRYPTAGE_H
cryptage.cpp
#include "cryptage.h"

string Cryptage::crypter(const string &message)
{
  string encodage;
  char lettre = ' ' + random(); // lettre référence pour la clef
  encodage += lettre;
  int chiffre = random(); // chiffre référence pour le criptage
  encodage += lettre + chiffre;
  int nombre = random();  // nombre d'éléments supplémentaires dans la clef
  encodage += lettre + nombre;
  int decalages[nombre]; // suite de nombre servant au calcul de chaque lettre du message
  for (int i=0; i<nombre; i++)
  {
    decalages[i] = random();
    encodage += lettre + decalages[i];
  }
  for (int i=0; i<message.size(); i++) encodage += message[i] - chiffre - decalages[i%nombre];
  // Brassage des lettres afin de mélanger la clé de criptage avec son message cripté
  string melange;
  nombre = encodage.size();
  if (nombre%3 == 0) encodage+=' ';
  for (int i=0, indice=2; i<nombre; i++, indice+=3) melange += encodage[indice%encodage.size()];
  return melange;
}

string Cryptage::decrypter(const string &texte)
{
// Remettre en place les lettres mélangées (clé de cryptage suivi du message crypté) string melange = texte; string message = melange; int nombre = melange.size(); if (nombre%3 == 0) melange+=' '; for (int i=0, indice=2; i<nombre; i++, indice+=3) message[indice%melange.size()] = melange[i]; // Phase de décryptage
string decodage; int clef = 0; char lettre = message[clef++]; // lettre référence pour la clef int chiffre = message[clef++] - lettre; // chiffre référence pour le décryptage int nombre = message[clef++] - lettre; // nombre d'éléments dans la clef int decalages[nombre]; for (int i=0; i<nombre; i++) decalages[i] = message[clef++] - lettre; for (int i=clef; i<message.size(); i++) decodage += message[i] + chiffre + decalages[(i-clef)%nombre]; return decodage; }
principal.h
#ifndef PRINCIPAL_H
#define PRINCIPAL_H

#include "ui_principal.h"
#include <QWebSocket>
#include <QSystemTrayIcon>
using namespace std;

class Principal : public QMainWindow, private Ui::Principal
{
  Q_OBJECT

public:
  explicit Principal(QWidget *parent = 0);
private slots:
  void connecte();
  void nonconnecte();
  void envoyerMessage();
  void arreter();
  void receptionOctets(const QByteArray &octets);
private:
  void setNotification();  
  void lireConfig();
protected:
  void closeEvent(QCloseEvent *);
private:
  QWebSocket ws;
  bool quitter = false;
  QSystemTrayIcon *notification;
  QString adresseLocale, adresseInternet, login;
};

#endif // PRINCIPAL_H
principal.cpp
#include "principal.h"
#include "cryptage.h"
#include <QJsonObject>
#include <QJsonArray>
#include <QJsonDocument>
#include <QMenu>
#include <QCloseEvent>
#include <QFile>
#include <QTime>

Principal::Principal(QWidget *parent) : QMainWindow(parent)
{
  setupUi(this);
  setNotification();
  lireConfig();
  ws.open(QUrl(adresseLocale));
  connect(&ws, SIGNAL(connected()), this, SLOT(connecte()));
  connect(&ws, SIGNAL(disconnected()), this, SLOT(nonconnecte()));
}

void Principal::setNotification()
{
  QMenu *menu = new QMenu(this);
  menu->addAction(actionAfficher);
  menu->addSeparator();
  menu->addAction(actionQuitter);
  notification = new QSystemTrayIcon(QIcon(":/icones/journal.png"), this);
  notification->setContextMenu(menu);
  notification->show();
}

void Principal::lireConfig()
{
  QFile fichier(":/configuration/messagerie.config");
  if (fichier.open(QIODevice::ReadOnly))
  {
    QTextStream lire(&fichier);
    QJsonDocument document = QJsonDocument::fromJson(lire.readAll().toUtf8());
    QJsonObject json = document.object();
    int port = json["port"].toInt();
    login = json["login"].toString();
    adresseLocale = QString("ws://%1:%2/%3").arg(json["ipLocal"].toString()).arg(port).arg(login);
    adresseInternet = QString("ws://%1:%2/%3").arg(json["ipInternet"].toString()).arg(port).arg(login);
  }
}

void Principal::connecte()
{
  connect(&ws, SIGNAL(binaryMessageReceived(QByteArray)), this, SLOT(receptionOctets(QByteArray)));
}

void Principal::nonconnecte()
{
  ws.open(QUrl(adresseInternet));
}

void Principal::envoyerMessage()
{
  QJsonObject json;
  json["commande"] = "message";
  json["expéditeur"] = login;
  json["destinataire"] = destinataire->text();
  json["message"] = message->text();
  QJsonDocument document(json);
  string message = Cryptage::crypter(document.toJson(QJsonDocument::Compact).data());
  ws.sendBinaryMessage(message.c_str());
  QString heure = QTime::currentTime().toString();
  reception->append(QString("[%1] moi > %2").arg(heure).arg(this->message->text()));
  this->message->selectAll();
  this->message->setFocus();
}

void Principal::receptionOctets(const QByteArray &octets)
{
  string message = Cryptage::decrypter(octets.data());
  QJsonDocument document = QJsonDocument::fromJson(message.c_str());
  QJsonObject json = document.object();
  QString commande = json["commande"].toString();
  if (commande=="message")
  {
    QString expediteur = json["expéditeur"].toString();
    QString texte = json["message"].toString();
    QString heure = QTime::currentTime().toString();
    reception->append(QString("[%1] %2 > %3").arg(heure).arg(expediteur).arg(texte));
    notification->showMessage(expediteur, texte);
  }
  else
  {
    connectes->clear();
    QJsonArray liste = json["connectés"].toArray();
    for (auto present : liste) connectes->addItem(present.toString());
    QString nom = json["nom"].toString();
    QString mode = json["commande"].toString();
    QString alerte = mode == "ouverture" ? "vient de se connecter" : "vient de se déconnecter";
    notification->showMessage(nom, alerte);
    barreEtat->showMessage(QString("%1 : %2").arg(nom).arg(alerte), 5);
  }
}

void Principal::closeEvent(QCloseEvent *evt)
{
  if (!quitter) { hide(); evt->ignore(); }
}

void Principal::arreter()
{
  quitter = true;
  close();
}