Forum Programmation.c Manipulation rapide et légère de données structurées binaires

Posté par  (site web personnel) .
Étiquettes : aucune
1
29
avr.
2009
Cher journal, imaginons que j'ai un programme qui doivent exploiter des données binaires structurées. Genre des paquets avec un header structuré, des données arbitraires de taille variable spécifiée dans le header, et un footer structuré. Structures connues et tout. Imaginons que je veuille accéder (lecture/écriture) aux différents champs contenus dans les différentes structures. "Facile" qu'on peut répondre : il suffit de définir les struct { }; qui vont bien en C et le compilateur fait le reste...

Certes, mais ce n'est pas suffisant. Car il faut compter sur les indiens, qui ne sont pas gentils quand les champs font plus de 8 bits de large. Bref, en pratique on se retrouve avec un programme tout beau tout simple mais qui interprète les indiens de travers. Existe-t'il un "__attribute__" gcc qui permet de spécifier l'endianness d'une structure ? Ou un système qui génère du code C ou C++ permettant d'accéder aux champs des structures (décrites elles-mêmes sous forme de struct C ou dans un langage spécifique) en prenant soin de s'occuper de l'endianness ?

En ce qui me concerne, la limitation principale n'est pas vraiment cette histoire d'indiens sur des mots simples (ntoh*/hton*, après tout ce n'est pas la mer à boire). C'est surtout que j'ai des mots qui contiennent des champs de bits, et ça c'est un peu casse-pied puisqu'il faut "cuisiner" et c'est pas très joli.

Pour l'instant, j'ai choisi le générateur de code http://morcandel.free.fr/mimine pour le C. Mais cela ne satisfait qu'à moitié mes exigences de flemmardise. J'ai aussi eu vent de NetPDL (http://www.nbee.org/doku.php?id=netpdl:index) mais est-ce bien utilisable en mode génération de code ? J'avais aussi un peu suivi DEVIL (http://phoenix.labri.fr/software/devil/) mais je ne suis pas convaincu que ça corresponde à ce qui m'intéresse, ni que ce soit encore très actif. J'ai aussi vu http://nmedit.sourceforge.net/subprojects/libpdl.html mais j'ai cru comprendre que l'overhead CPU pouvait être important (utilisation de dictionnaires pour caser les champs, parsing en-ligne de la description, etc.).

Des idées pour éclairer ma lanterne ? Comment font les gens de wireshark ?
  • # buffer

    Posté par  . Évalué à 1.

    salut,

    je suppose qu'il faut que tu remplisses ton buffer local avec les ntoh*, et ensuite tu travailles sur ton buffer local en parcourant les champs de bits.

    en résumé : en entrée, avant toute autre manipulation, tu convertis tout ton buffer avec ntoh*.
    • [^] # Re: buffer

      Posté par  (site web personnel) . Évalué à 1.

      C'est en effet une solution sur laquelle je pensais partir. Mais je préfère devoir faire les ntoh*/hton* à chaque accès à un champ (ça se traduit par un opcode [bswap] sur x86) que d'avoir à dupliquer chaque paquet pour le ntoh*-er.
      • [^] # Re: buffer

        Posté par  . Évalué à 1.

        Si tu fais du C++, tu peux commencer par regarder du côté de boost.endian (ça ne fait pas encore partie de boost mais ça ne devrait pas tarder). Voir http://mysite.verizon.net/beman/endian-0.10/libs/integer/doc(...) et http://svn.boost.org/svn/boost/sandbox/endian pour les sources.

        Le principe est assez simple, tu a une classe endian templatée par le type d'endian, le type C représenté et la taille du champ. La donnée stockée sera toujours dans le format de l'endian que tu as fourni.
        Ensuite les différents opérateurs sont implémentés pour fournir une interface identique à un entier mais qui fait la conversion dans le format de ta machine à chaque fois.


        Étienne
        • [^] # Re: buffer

          Posté par  (site web personnel) . Évalué à 1.

          Merci, c'est intéressant. Par contre j'ai l'impression que ça ne gère pas les bitfields "en standard". Il faut sans doute passer par la définition de classes supplémentaires. C'est probablement une proposition qu'il serait intéressant de soumettre aux auteurs de ce package. Mais intéressant quand même.
  • # Ca devient un beau foutoir le C ...

    Posté par  . Évalué à 3.

    A l'origine ce langage était un "assembleur amélioré" qui permettait de faire des manips "bas niveau" assez facilement (manipulations de bits, etc).

    Depuis quelques années, avec les "optimisations" des CPU ou des compilateurs, le code compilé ne correspond absolument plus au code généré. On se retrouve avec des octets "de bourage" dans les structures pour que celles ci soient alignées sur 32/64 bits ...

    Je pense que, de nos jours, il est possible via une directive fourni au compilo, de ne pas activer ces "optimisations" et de travailler "à l'ancienne". Un man de GCC devrait t'en dire plus (il me semble avoir cherché il y a quelques mois sur ce sujet).
    • [^] # Re: Ca devient un beau foutoir le C ...

      Posté par  (site web personnel) . Évalué à 3.

      En effet. C'est __attribute__ ((packed)) et autres __attribute__ ((aligned(X)))... qu'on est bien obligés d'utiliser pour ce genre de choses.

      Je ne suis pas convaincu que ce soit une tare du C (enfin : de gcc en l'espèce) que de réarranger la structure effective et l'alignement des choses en mémoire afin d'"optimiser" ou tout simplement afin de s'adapter aux contraintes d'alignement des CPU.

      Après tout, la notion de structure est davantage sémantique dans un langage "de haut niveau", que véritablement physique. Parlez-en à un électronicien, pour lui le C est bien un langage de haut niveau ;)
      • [^] # Re: Ca devient un beau foutoir le C ...

        Posté par  . Évalué à 2.

        Après tout, la notion de structure est davantage sémantique dans un langage "de haut niveau", que véritablement physique. Parlez-en à un électronicien, pour lui le C est bien un langage de haut niveau ;)

        Le C d' origine n'était rien de mieux qu'un assembleur amélioré .... Et ce n'est qu'au fur et à mesure de ses diverses évolutions qu'il es devenu un réel langage de haut niveau.

        Est-ce bien ou non ? Peut-être, peut-être pas. Toujurs est-il qu'aujourd'hui ce n'est pas évident de s'y retrouver ....

        Pour info je ne suis pas électronicien de métier mais je fais de l'électronique .... Et pour moi le C est un assembleur amélioré (en tout cas le C d'origine).

        Je ne suis pas convaincu que ce soit une tare du C (enfin : de gcc en l'espèce) que de réarranger la structure effective et l'alignement des choses en mémoire afin d'"optimiser" ou tout simplement afin de s'adapter aux contraintes d'alignement des CPU.

        Les compilateurs que j'utilisais il y a 12 ans ne le faisaient pas systématiquement. c'était pratique quand on faisait de la programmation hardware, on ne se posait pas trop de questions, et dans ce domaine l'optimisation systématique peut être problématique.
  • # Mini-framework

    Posté par  . Évalué à 2.

    Classe abstraite Field qui declare une methode RESULT Read(const unsigned char* pBuffer, const size_t stBufferSize, size_t &stSizeRead)

    IntegerField: Classe derivee de Field -> Integer qui implemente support pour les petits et grands indiens et offre un parametre pour decider
    Classe derivee de IntegerField (tu fais un template en 5 minutes) pour chaque entier de 8 a 64bit

    Classe String derivee de Field qui gere Unicode/UTF8/ANSI

    Classe "Buffer" derivee de Field qui gere des buffers quelconques

    Classe Container derivee de Field qui est un container(permet d'y ajouter des champs a l'interieur, et la methode read va de l'un a l'autre)

    Classe BitField derivee de Container qui permet d'inclure des IntegerField, specifier combien de bits sont assignes a chaque IntegerField, et dont la methode Read s'occupe de passer la valeur qu'il faut a coup de masque a chaque champ

    Une fois que t'as fait ca, ben c'est fini, t'as plus qu'a construire ta structure avec ces objets et c'est regle. Tu peux meme t'amuser a ajouter les dependances entre champs apres (genre tu specifies que la longueur de ce champs texte est la valeur du champs d'avant, les checksums, etc...) voire la compression/encryption des champs.
    Le jour ou t'as un champs totalement different, suffit de rajouter l'objet qui le represente.

    Je t'assures que ca fonctionne tres tres bien, j'ai cree un systeme de ce type il y a des annees, et je l'utilises quasiment tous les jours, il represente sans problemes des formats plutot complexes(mp3, PNG, etc...) et cela assez facilement
    • [^] # Re: Mini-framework

      Posté par  (site web personnel) . Évalué à 1.

      Oui, c'est aussi une solution. Assez déclarative, mais pourquoi pas.

      Ce que je verrais bien, c'est qu'après avoir décrit la structure de tes données sous la forme d'objets "champs" comme tu présentes, tu utilises cette description pour générer le code C++ "optimisé" qui définit de nouvelles classes coquilles (genre beans avec get_/set_ sur chacun des champs que tu as décrits). Ensuite tu utilises ces objets coquille pour travailler avec tes données brutes.

      C'est un peu cette dernière approche "génération de code légère" que je recherche et je me demandais surtout si qqch comme cela existait déjà et était répandu.
      • [^] # Re: Mini-framework

        Posté par  . Évalué à 1.

        Ben justement, il est tres facile de definir les methode set/get sur ces objets directement, tu rajoutes une methode "Generate" qui te genere le buffer final (en concatenant les buffers des objets fils pour les containers, en gerant little/big endian et l'encodage pour les strings pour les autres objets, en compressant le contenu pour les objets s'occupant de compression, calculant les checksums, ...) et c'est regle.

        Une fois cela fait, tu peux lire tes structs, et tu peux les generer en y mettant les valeurs que tu veux et t'as meme pas besoin de t'occuper de rafraichir les champs de longueur/checksum/compression ... quand tu changes les donnees avec un systeme pareil, l'appel a Generate sur l'objet pere se charge de tout.

Suivre le flux des commentaires

Note : les commentaires appartiennent à celles et ceux qui les ont postés. Nous n’en sommes pas responsables.