19 janv. 2011

[Code source] ekogen v0.4

La librairie de chiffrement basée sur OpenSLL présentée dans un billet récent évolue et deviens plus utilisable. La version 0.4 est disponible en téléchargement sur developpez.com (ainsi que sur ce blog, attachée à ce billet).

Les changements en bref

  • La chaine d'outils CMake est utilisée pour construire la librairie, ce qui permet de la compiler sur différentes plateformes en utilisant le compilateur natif (g++ sous *nix, Visual C++ sous Windows).
  • Le code a été réorganisé, et des tests unitaires ont été créés (le framework microtest a été implémenté : vous pouvez le trouver dans test/utest.h ; ce framework a de grandes chances d'évoluer par la suite pour refléter mes besoins en termes de tests unitaires, avec notamment une gestion de sortie en XML pour le rendre compatible avec certains systèmes d'intégration continue).
  • Les pipeline font leur apparition.

Pipelines

Un pipeline P est une chaine de fonctions F1, ..., Fn tel que P(x) = (Fn ° ... ° F1)(x). Par conséquent, si la fonction Fi définie par Fi : Ei → Si[1], alors Ei+1 = Si et Si-1 = Ei. Autrement dit, la sortie de Fi est l'entrée de Fi+1.

En termes de C++, le challenge est de composer les fonctions via une syntaxe simple - un DSEL spécifique. Joel Falcou[2] a proposé une première version intéressante utilisant la librairie Boost.Proto qui met bien en évidence le fait que c'est bien un DSEL dont nous avons besoin. De sa solution, j'ai dérivé une implémentation sans référence à Boost (disponible dans l'archive liée).

Le fonctionnement est relativement simple :

#include "ekogen/core/pipeline.h"
int f3(float in) { ... } float f2(double in) { ... } double f1(const std::string& in) { ... }
int main() { ekogen::pipeline<int(const std::string&)> p(stage(f1) | f2 | f3);
int result = p("test"); }

Ce code construit une fonction P(x) = (f3 ° f2 ° f1)(x). On notera le type de P, qui ne dépends que du type en entrée de f1 et du type en sortie de f3. Le pipeline est copiable, possède un constructeur par défaut et un constructeur par copie, donc peut être stocké dans n'importe quel conteneur de la librairie standard si besoin (encore qu'un tel besoin me paraisse tout à fait étrange).

L'intérêt du pipeline n'est pas nécessairement situé dans l'écriture de fonctions mathématiques composées complexes. Dans core/adapters.h, j'ai prévu différentes adapteurs permettant d'utiliser les classes de la librairie de chiffrement de manière à créer simplement un pipeline de chiffrement et de déchiffrement. Un exemple d'un tel pipeline se trouve dans test/pipeline.cpp, dans la fonction test_encryption_decryption reproduite ici:

void test_encryption_decryption(utest::logger& log)
{
  ekogen::security::blowfish_cryptograph cryptograph;
  ekogen::security::blowfish_key key = cryptograph.generate_key();
ekogen::pipeline<std::string(const std::string&)> encrypter( ekogen::stage( ekogen::string_to_ucv(), ekogen::make_symetric_encrypter(cryptograph, key) ) | ekogen::encoder_adapter<ekogen::encoder::hex>() );
ekogen::pipeline<std::string(const std::string&)> decrypter( ekogen::stage( ekogen::decoder_adapter<ekogen::encoder::hex>(), ekogen::make_symetric_decrypter(cryptograph, key) ) | ekogen::ucv_to_string() );
std::string input = "this is an encryption test using blowfish, but any other can do the job"; std::string temp = encrypter(input); std::string output = decrypter(temp);
log(std::string("input = ") + input); log(std::string("encrypted = ") + temp); log(std::string("decrypted = ") + output);
utest::check_equal(input, output, "encoding failed"); }

On construit un pipeline encrypter<string(string)> qui applique successivement les fonctions suivantes :

  • string_to_ucv : transformation d'une chaine de caractères (std::string) en vecteur de caractères non signés (std::vector<unsigned char>)
  • make_symetric_encrypter() : chiffrement symétrique
  • encoder_adapter(): encodage du résultat chiffré.

En appliquant ce pipeline, on peut chiffrer une chaine de caractère en une unique opération de chiffrement. Le principe est similaire pour le déchiffrement.

Implémentation des pipelines

Il y a deux choses à voir, histoire de bien comprendre. En premier, ekogen::bits::function<R(A)> est un objet fonction polymorphique (PFO) qui a pur particularité d'accepter toute instance F appelable comme une fonction, prenant en paramètre un argument de type A et renvoyant une valeur de type R. Cet objet est à la base de ekogen::pipeline. Le principe de fonctionnement est similaire à celui de Boost.Function, à ceci près que je ne gère que les fonctions unaires renvoyant un résultat, tandis que Boost.Function gère tout type de fonctions (jusqu'à 50 arguments, avec ou sans type de retour). Pour parvenir à ce résultat, une instance de ekogen::bits::function<> possède une référence sur un objet de type ekogen::bits::basic_caller<>, dont on va dériver plusieurs classes ekogen::bits::caller<>. De fait, l'utilisation d'un objet fonction suppose qu'avant que la fonction soit évaluée, un appel de méthode virtuel est effectué - ce n'est pas nécessairement une bonne chose, mais c'est plus ou moins nécessaire (Boost.Function remplace cet appel par un appel de fonction libre ; en termes de performance pure, Boost.Function va faire jusqu'à deux appels de fonction, et va n'en faire qu'un seul dans la majorité des cas, tandis que ekogen::bits::function<> va faire au moins un appel de méthode virtuelle, puis un appel de fonction dans le cas ou l'instance appelable est une fonction ou un pointeur sur une fonction membre).

La seconde chose à étudier, c'est ekogen::bits;;pipeline_stage<>. C'est grâce à cette classe que sont construits récursivément les étages du pipeline. Un pipeline à 4 étages F1,F2,F3,F4 sera composé grâce à pipeline_stage<pipeline_stage<pipeline_stage<F1,F2>,F3>,F4>. Il est évident qu'on ne peut pas demander à un utilisateur d'écrire une telle horreur (c'est en fait la raison d'exister de l'objet pipeline). Toutefois, construire un pipeline_stage complexe sans l'attacher à un pipeline a un intérêt - car contrairement au pipeline qui stocke un objet ekogen::bits::function<>, le pipeline_stage ne rajoute aucune indiréction au niveau de l'appel ; cela signifie que si les différents étages sont faits de foncteurs inlinés, l'ensemble sera inliné. Du coup, on peut l'utiliser - par exemple - avec les algorithmes de la librairie standard :

// transformation d'une suite de points en 3D
// local_transform, local_to_world et world_to_screen sont des foncteurs
std::transform(points.begin(), points.end(), screenpoints.begin(), 
  (stage(local_transform(localmat)) | local_to_world(worldmat) | world_to_screen(projmat)));

Le dernier paramètre de std::transform construit une expression qui sera évaluée pour chaque point du vecteur d'entrée. Ceux qui ont des connaissances solides en C++ reconnaissent maintenant la technique utilisée, qui fera l'objet d'un billet ultérieur : nous voilà avec un patron d'expression (expression template).

ChangeLog

2011-01-17		Emmanuel Deloget			<logout@free.fr>
* VERSION 0.4
* added LICENCE, as well as a licence header to each source code file
* added src/core/bits/stdint_types.h to import standard integer types in a portable way.
* moved pipeline_stage<> from src/core/pipeline.h to src/core/bits/pipeline_stage.h
* CMakeLists.txt: check for <cstdint> or <tr1/ctsdint> presence, as well as for the namespace in which standard integer types are declared. Added _SECURE_SCL_NO_WARNINGS define for MSVC builds.
* src/core/bits/pipeline_stage.h: added bits::concatener<> and bits::extracter<> to optimize the construction of pipeline stages.
* src/core/CMakeLists.txt: added header files to the source file list; added the install() commands to install the library.
* src/core/pipeline.h: stageify1/2 have been renamed stage(). They now construct the pipeline_stage<> using bits::concatener<> and bits::extracter<> BREAKING CHANGE
* src/core/security_sha_digest.h: empty collections may implement iterators that cannot be dereferenced (depending on the library vendor) BUG CORRECTION
* src/ekogen_config.h.in: added the macros to support the use of src/core/bits/stdint_types.h
* test/pipeline.cpp: added tests to verify the construction of optimized pipeline_stage objects.
* test/security.cpp: logging the ekogen library version in the corresponding unit test.

Prochaine version

A partir de la prochaine version, la numérotation des versions change : elle sera sous la forme MAJOR.MINOR.BUILD, où BUILD est plus ou moins arbitraire (en fait, c'est le nombre d'itérations entre deux releases MINOR, tandis que MINOR est le nombre d'itérations entre deux releases MAJOR).

La prochaine version sera nommée 0.5.x et intégrera des corrections de bugs (déjà effectuées à l'heure actuelle). Les fichiers utilisés par CMake ont été modifiés pour supporter complètement Windows (Visual C++ 2010 ; les versions précédentes ne supportent par l'ensemble du TR1, et en particulier le header <cstdint> est manquant). De plus, une pré-version de la librairie ekonet sera incluse dans ekogen 0.5.x, ainsi que des programmes d'exemples pour cette librairie.

La date de sortie n'est pas prévue pour l'instant, mais vous pouvez compter sur la semaine prochaine[3].

Notes

[1] on a la relation ∀ x ∊ Ei, Fi(x) ∊ Si

[2] maître de conférence au LRI, et membre éminent du forum de developpez.com

[3] bien évidemment, ce code n'est pas encore prêt pour la production - il faudra attendre la première release majeure, qui signifiera que l'interface ne bougera plus (jusqu'à la prochaine release majeure au moins) et que le code a été estimé stable et efficace.

Annexes

Ajouter un commentaire

Les commentaires peuvent être formatés en utilisant une syntaxe wiki simplifiée.

Fil des commentaires de ce billet