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 :
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.
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.
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.
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.
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.
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
addDataBase() : Cette méthode statique permet de
créer l'objet représentant le serveur de la base de données
utilisée.
setDataBaseName() : Spécifie le nom de cette base de
données à créer où à atteindre.
open() : Nous établissons la connexion effective à
l'aide de cette méthode qui renvoie une valeur booléenne suivant si
l'opération d'ouverture s'est bien déroulée ou non.
Méthodes de la classe QSqlQuery
QSqlQuery(requête) : Exécute immédiatement la
requête exprimée en argument du constructeur, dès la phase de
création de l'objet.
exec(requête) : Exécute la requête exprimée
en argument de la méthode après que l'objet ait été créé.
next() : Cette méthode nous permet de parcourir
l'ensemble des enregistrements issus de la requête demandée.
prepare(requête) : Exécute une requête contenant
des emplacements réservés liés par des valeurs qui seront insérées
par la suite.
bindValue(clé, valeur): Après avoir
spécifié la requête préparée à l'aide de la méthode prepare()
dans laquelle vous devez désigner les variables à prendre en compte,
à l'aide du préfixe : , vous les enregistrez ensuite
successivement au travers de cette méthode. Lorsque toutes les
variables sont correctement renseignées, il suffit de faire appel
ensuite à la méthode exec() sans paramètre.
value() : Cette méthode retourne la valeur d'un champ
en tant que QVariant. La classe QVariant peut
contenir de nombreux types Qt et C++, dont int et QString.
Les différents types de données susceptibles d'être stockés dans une
base de données sont transformés en types Qt et C++ correspondants
et stockés en tant que QVariant. Par exemple, un VARCHAR
est représenté en tant que QString et un DATETIME en
tant que QDateTime.
numRowsAffected() : Permet de connaître le nombre de
lignes affectées par la requête SQL.
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.
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 :
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.
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.
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.
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.
#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;
}
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.
L'intérêt des flux ici, c'est que nous travaillons directement
avec des données de très haut niveau, ainsi qu'avec les types
primitifs du langage C++, qui sont traduit automatiquement en une
suite d'octets qui est par contre parfaitement adapté à un
enregistrement dans un fichier ou pour être envoyée au travers d'un
réseau.
Pour s'y retrouver, les flux utilisent pour chaque variables, un
en-tête de décompte d'octets qui sera bien utile par la suite pour
permettre la lecture correcte des entités enregistrées.
Ce flux d'octets peut naturellement être correctement interprété
si nous utilisons la même version de flux, donc ici de nouveau un QDataStream,
et surtout si nous lisons les données à récupérer dans le même ordre
que lors de l'enregistrement. Utilisez, par contre, cette fois-ci
l'opérateur >>.
QDataStream peut aussi être utilisé pour lire et écrire des
octets bruts, sans en-tête de décompte d'octets, au moyen des
méthodes readRawBytes() et writeRawBytes().
Lorsque nous utilisons QDataStream, Qt se
charge de lire et d'écrire chaque type, dont les conteneurs avec un
nombre arbitraire d'éléments. Cette caractéristique nous évite de
structurer ce que nous écrivons et d'appliquer une conversion à ce
que nous lisons. Notre seule obligation consiste à nous assurer que
nous lisons tous les types dans leur ordre d'écriture, en laissant à
Qt le soin de gérer tous les détails.
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.
#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;
}
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.
#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;
}
#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;
}
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.
#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;
}
#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;
}