/* Chrysalide - Outil d'analyse de fichiers binaires * enum.h - gestion des énumérations Kaitai * * Copyright (C) 2019 Cyrille Bagard * * This file is part of Chrysalide. * * Chrysalide is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * Chrysalide is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with Chrysalide. If not, see . */ #include "enum.h" #include #include #include #include #include #include #include #include #include "enum-int.h" /* ------------------------- MANIPULATION D'UNE ENUMERATION ------------------------- */ /* Construit une valeur d'énumération à partir d'indications. */ static enum_value_t *build_enum_value(GYamlNode *, bool *); /* Supprime de la mémoire une valeur d'énumération. */ static void delete_enum_value(enum_value_t *); /* Etablit la comparaison entre deux valeurs d'énumération. */ static int compare_enum_values_by_value(const enum_value_t **, const enum_value_t **); /* Etablit la comparaison entre deux noms d'énumération. */ static int compare_enum_values_by_label(const enum_value_t **, const enum_value_t **); /* Etablit la comparaison entre deux noms d'énumération. */ static int compare_enum_values_by_sized_label(const sized_string_t *, const enum_value_t **); /* ----------------------- GESTION D'UN GROUPE D'ENUMERATIONS ----------------------- */ /* Initialise la classe des groupes d'énumérations Kaitai. */ static void g_kaitai_enum_class_init(GKaitaiEnumClass *); /* Initialise un groupe d'énumérations Kaitai. */ static void g_kaitai_enum_init(GKaitaiEnum *); /* Supprime toutes les références externes. */ static void g_kaitai_enum_dispose(GKaitaiEnum *); /* Procède à la libération totale de la mémoire. */ static void g_kaitai_enum_finalize(GKaitaiEnum *); /* ---------------------------------------------------------------------------------- */ /* MANIPULATION D'UNE ENUMERATION */ /* ---------------------------------------------------------------------------------- */ /****************************************************************************** * * * Paramètres : node = noeud Yaml à venir lire. * * defcase = indique si une valeur par défaut est visée. [OUT] * * * * Description : Construit une valeur d'énumération à partir d'indications. * * * * Retour : Structure de valeur mise en place. * * * * Remarques : - * * * ******************************************************************************/ static enum_value_t *build_enum_value(GYamlNode *node, bool *defcase) { enum_value_t *result; /* Valeur à retourner */ const char *key; /* Clef d'une énumération */ kaitai_scope_t fake; /* Contexte de circonstance */ resolved_value_t kval; /* Valeur à indexer */ const char *value; /* Valeur Yaml particulière */ char *path; /* Chemin d'accès suivant */ GYamlNode *children; /* Sous-noeuds rattachés */ GYamlNode *sub; /* Sous-noeud à considérer */ result = NULL; *defcase = false; if (!G_IS_YAML_PAIR(node)) goto bad_node; /* Identification de la valeur énumérative */ key = g_yaml_pair_get_key(G_YAML_PAIR(node)); if (strcmp(key, "_") == 0) { /** * Exemple de choix par défaut : * http://doc.kaitai.io/user_guide.html#tlv */ kval.type = GVT_UNSIGNED_INTEGER; kval.unsigned_integer = ~0llu; *defcase = true; } else { fake.meta = NULL; fake.root = NULL; fake.parent = NULL; fake.last = NULL; if (!resolve_kaitai_expression_as_integer(&fake, key, strlen(key), &kval)) goto bad_node; } /* Récupération des éléments associés à la valeur */ value = g_yaml_pair_get_value(G_YAML_PAIR(node)); if (value != NULL) { result = malloc(sizeof(enum_value_t)); result->value = kval; result->label = strdup(value); result->doc = NULL; } else { /** * Les énumérations peuvent comporter un commentaire associé * sous forme d'un élément de documentation complémentaire. * * Cf. http://doc.kaitai.io/user_guide.html#verbose-enums */ asprintf(&path, "/%s/", key); children = g_yaml_node_find_first_by_path(node, path); free(path); if (!G_IS_YAML_COLLEC(children)) goto bad_value; /* Identifiant */ sub = g_yaml_node_find_first_by_path(children, "/id"); if (!G_IS_YAML_PAIR(sub)) goto bad_sub_value; value = g_yaml_pair_get_value(G_YAML_PAIR(sub)); if (value == NULL) goto bad_sub_value; result = malloc(sizeof(enum_value_t)); result->value = kval; result->label = strdup(value); result->doc = NULL; g_object_unref(G_OBJECT(sub)); /* Documentation */ sub = g_yaml_node_find_first_by_path(children, "/doc"); if (!G_IS_YAML_PAIR(sub)) goto bad_sub_value; value = g_yaml_pair_get_value(G_YAML_PAIR(sub)); if (value == NULL) goto bad_sub_value; result->doc = strdup(value); bad_sub_value: g_clear_object(&sub); g_object_unref(G_OBJECT(children)); bad_value: ; } bad_node: return result; } /****************************************************************************** * * * Paramètres : value = valeur à traiter. * * * * Description : Supprime de la mémoire une valeur d'énumération. * * * * Retour : - * * * * Remarques : - * * * ******************************************************************************/ static void delete_enum_value(enum_value_t *value) { EXIT_RESOLVED_VALUE(value->value); free(value->label); if (value->doc != NULL) free(value->doc); free(value); } /****************************************************************************** * * * Paramètres : a = premières informations à consulter. * * b = secondes informations à consulter. * * * * Description : Etablit la comparaison entre deux valeurs d'énumération. * * * * Retour : Bilan : -1 (a < b), 0 (a == b) ou 1 (a > b). * * * * Remarques : - * * * ******************************************************************************/ static int compare_enum_values_by_value(const enum_value_t **a, const enum_value_t **b) { int result; /* Bilan à retourner */ const resolved_value_t *value_a; /* Raccouri d'accès pour a */ const resolved_value_t *value_b; /* Raccouri d'accès pour b */ value_a = &(*a)->value; value_b = &(*b)->value; if (value_a->type == GVT_UNSIGNED_INTEGER && value_b->type == GVT_UNSIGNED_INTEGER) result = sort_unsigned_long_long(value_a->unsigned_integer, value_b->unsigned_integer); else if (value_a->type == GVT_UNSIGNED_INTEGER && value_b->type == GVT_UNSIGNED_INTEGER) result = sort_signed_long_long(value_a->signed_integer, value_b->signed_integer); else { /** * Le code Python a deux options : soit fournir un équivalent à la * structure resolved_value_t lors de l'appel correspondant à cette * fonction compare_enum_values_by_value(), soit fournir un nombre * directement. * * Comme PyArg_ParseTuple() est obligée de trancher entre non-signé * et signé, le parti est pris de considérer le non-signé coté Python. * On s'adapte en conséquence ici. * * La structure resolved_value_t est une union, donc les valeurs * sont potientiellement au mauvais format mais bien présentes. */ /** * result = sort_unsigned_long_long(value_a->type, value_b->type); */ result = sort_unsigned_long_long(value_a->unsigned_integer, value_b->unsigned_integer); } return result; } /****************************************************************************** * * * Paramètres : a = premières informations à consulter. * * b = secondes informations à consulter. * * * * Description : Etablit la comparaison entre deux noms d'énumération. * * * * Retour : Bilan : -1 (a < b), 0 (a == b) ou 1 (a > b). * * * * Remarques : - * * * ******************************************************************************/ static int compare_enum_values_by_label(const enum_value_t **a, const enum_value_t **b) { int result; /* Bilan à retourner */ result = strcmp((*a)->label, (*b)->label); return result; } /****************************************************************************** * * * Paramètres : l = premières informations à consulter. * * b = secondes informations à consulter. * * * * Description : Etablit la comparaison entre deux noms d'énumération. * * * * Retour : Bilan : -1 (a < b), 0 (a == b) ou 1 (a > b). * * * * Remarques : - * * * ******************************************************************************/ static int compare_enum_values_by_sized_label(const sized_string_t *l, const enum_value_t **b) { int result; /* Bilan à retourner */ result = strncmp(l->data, (*b)->label, l->len); // FIXME return result; } /* ---------------------------------------------------------------------------------- */ /* GESTION D'UN GROUPE D'ENUMERATIONS */ /* ---------------------------------------------------------------------------------- */ /* Indique le type défini pour un ensemble d'énumérations Kaitai. */ G_DEFINE_TYPE(GKaitaiEnum, g_kaitai_enum, G_TYPE_OBJECT); /****************************************************************************** * * * Paramètres : klass = classe à initialiser. * * * * Description : Initialise la classe des groupes d'énumérations Kaitai. * * * * Retour : - * * * * Remarques : - * * * ******************************************************************************/ static void g_kaitai_enum_class_init(GKaitaiEnumClass *klass) { GObjectClass *object; /* Autre version de la classe */ object = G_OBJECT_CLASS(klass); object->dispose = (GObjectFinalizeFunc/* ! */)g_kaitai_enum_dispose; object->finalize = (GObjectFinalizeFunc)g_kaitai_enum_finalize; } /****************************************************************************** * * * Paramètres : kenum = instance à initialiser. * * * * Description : Initialise un groupe d'énumérations Kaitai. * * * * Retour : - * * * * Remarques : - * * * ******************************************************************************/ static void g_kaitai_enum_init(GKaitaiEnum *kenum) { kenum->name = NULL; kenum->cases_v2l = NULL; kenum->cases_v2l_count = 0; kenum->cases_l2v = NULL; kenum->cases_l2v_count = 0; kenum->defcase = NULL; } /****************************************************************************** * * * Paramètres : kenum = instance d'objet GLib à traiter. * * * * Description : Supprime toutes les références externes. * * * * Retour : - * * * * Remarques : - * * * ******************************************************************************/ static void g_kaitai_enum_dispose(GKaitaiEnum *kenum) { G_OBJECT_CLASS(g_kaitai_enum_parent_class)->dispose(G_OBJECT(kenum)); } /****************************************************************************** * * * Paramètres : kenum = instance d'objet GLib à traiter. * * * * Description : Procède à la libération totale de la mémoire. * * * * Retour : - * * * * Remarques : - * * * ******************************************************************************/ static void g_kaitai_enum_finalize(GKaitaiEnum *kenum) { size_t i; /* Boucle de parcours */ if (kenum->name != NULL) free(kenum->name); for (i = 0; i < kenum->cases_v2l_count; i++) delete_enum_value(kenum->cases_v2l[i]); if (kenum->cases_v2l != NULL) free(kenum->cases_v2l); if (kenum->cases_l2v != NULL) free(kenum->cases_l2v); if (kenum->defcase != NULL) delete_enum_value(kenum->defcase); G_OBJECT_CLASS(g_kaitai_enum_parent_class)->finalize(G_OBJECT(kenum)); } /****************************************************************************** * * * Paramètres : parent = noeud Yaml contenant l'attribut à constituer. * * * * Description : Construit un groupe d'énumérations Kaitai. * * * * Retour : Instance mise en place ou NULL en cas d'échec. * * * * Remarques : - * * * ******************************************************************************/ GKaitaiEnum *g_kaitai_enum_new(GYamlNode *parent) { GKaitaiEnum *result; /* Identifiant à retourner */ result = g_object_new(G_TYPE_KAITAI_ENUM, NULL); if (!g_kaitai_enum_create(result, parent)) g_clear_object(&result); return result; } /****************************************************************************** * * * Paramètres : kenum = groupe d'énumérations à initialiser pleinement. * * parent = noeud Yaml contenant l'attribut à constituer. * * * * Description : Met en place un groupe d'énumérations Kaitai. * * * * Retour : Bilan de l'opération. * * * * Remarques : - * * * ******************************************************************************/ bool g_kaitai_enum_create(GKaitaiEnum *kenum, GYamlNode *parent) { bool result; /* Bilan à retourner */ char *path; /* Chemin des valeurs */ GYamlNode *collec; /* Liste de noeuds à traiter */ GYamlNode **nodes; /* Eventuels noeuds trouvés */ size_t count; /* Quantité de ces noeuds */ size_t i; /* Boucle de parcours */ bool defcase; /* Définition par défaut ? */ enum_value_t *value; /* Valeur énumérative nouvelle */ bool found; /* Présence de partage existant*/ size_t index; /* Indice du point d'insertion */ result = false; /* Récupération du nom */ if (!G_IS_YAML_PAIR(parent)) goto exit; kenum->name = strdup(g_yaml_pair_get_key(G_YAML_PAIR(parent))); /* Association de valeurs */ path = strdup("/"); path = stradd(path, kenum->name); path = stradd(path, "/"); collec = g_yaml_node_find_first_by_path(parent, path); free(path); if (collec != NULL) { if (G_IS_YAML_COLLEC(collec)) nodes = g_yaml_collection_get_nodes(G_YAML_COLLEC(collec), &count); else count = 0; if (count > 0) { for (i = 0; i < count; i++) { value = build_enum_value(nodes[i], &defcase); if (value == NULL) break; if (defcase) { if (kenum->defcase != NULL) { log_variadic_message(LMT_WARNING, _("Multiple definition of the defaut value for the enumeration '%s'"), kenum->name); delete_enum_value(value); break; } /** * Exemple de choix par défaut : * http://doc.kaitai.io/user_guide.html#tlv */ kenum->defcase = value; } else { kenum->cases_v2l = qinsert(kenum->cases_v2l, &kenum->cases_v2l_count, sizeof(enum_value_t *), (__compar_fn_t)compare_enum_values_by_value, &value); found = bsearch_index(&value, kenum->cases_l2v, kenum->cases_l2v_count, sizeof(enum_value_t *), (__compar_fn_t)compare_enum_values_by_label, &index); if (found) log_variadic_message(LMT_WARNING, _("Multiple occurrence of the label %s in the enumeration '%s'"), value->label, kenum->name); else kenum->cases_l2v = _qinsert(kenum->cases_l2v, &kenum->cases_l2v_count, sizeof(enum_value_t *), &value, index); } g_object_unref(G_OBJECT(nodes[i])); } result = (i == count); for (; i < count; i++) g_object_unref(G_OBJECT(nodes[i])); free(nodes); } g_object_unref(G_OBJECT(collec)); } exit: return result; } /****************************************************************************** * * * Paramètres : kenum = groupe d'énumérations à consulter. * * * * Description : Fournit le nom principal d'une énumération. * * * * Retour : Désignation de l'énumération. * * * * Remarques : - * * * ******************************************************************************/ const char *g_kaitai_enum_get_name(const GKaitaiEnum *kenum) { const char *result; /* Chaîne à retourner */ result = kenum->name; return result; } /****************************************************************************** * * * Paramètres : kenum = groupe d'énumérations à consulter. * * label = étiquette de l'élément constant à traduire. * * value = valeur concrète correspondante. [OUT] * * * * Description : Traduit une étiquette brute en constante d'énumération. * * * * Retour : Bilan de la conversion. * * * * Remarques : - * * * ******************************************************************************/ bool g_kaitai_enum_find_value(const GKaitaiEnum *kenum, const sized_string_t *label, resolved_value_t *value) { bool result; /* Présence à retourner */ size_t index; /* Indice du point d'insertion */ result = bsearch_index(label, kenum->cases_l2v, kenum->cases_l2v_count, sizeof(enum_value_t *), (__compar_fn_t)compare_enum_values_by_sized_label, &index); if (result) *value = kenum->cases_l2v[index]->value; return result; } /****************************************************************************** * * * Paramètres : kenum = groupe d'énumérations à consulter. * * value = valeur concrète à transformer. * * prefix = détermine l'ajout d'un préfixe éventuel. * * * * Description : Traduit une constante d'énumération en étiquette brute. * * * * Retour : Désignation ou NULL en cas d'échec. * * * * Remarques : - * * * ******************************************************************************/ char *g_kaitai_enum_find_label(const GKaitaiEnum *kenum, const resolved_value_t *value, bool prefix) { char *result; /* Etiquette à retourner */ enum_value_t faked; /* Copie d'élément recherché */ bool found; /* Présence de partage existant*/ size_t index; /* Indice du point d'insertion */ const enum_value_t *item; /* Elément retrouvé par valeur */ faked.value = *value; found = bsearch_index(&faked, kenum->cases_v2l, kenum->cases_v2l_count, sizeof(enum_value_t *), (__compar_fn_t)compare_enum_values_by_value, &index); if (found) item = kenum->cases_l2v[index]; else item = kenum->defcase; if (item != NULL) { if (prefix) asprintf(&result, "%s::%s", kenum->name, item->label); else result = strdup(item->label); } else result = NULL; return result; } /****************************************************************************** * * * Paramètres : kenum = groupe d'énumérations à consulter. * * value = valeur concrète à transformer. * * * * Description : Traduit une constante d'énumération en documentation. * * * * Retour : Documentation associée à la valeur indiquée ou NULL. * * * * Remarques : - * * * ******************************************************************************/ char *g_kaitai_enum_find_documentation(const GKaitaiEnum *kenum, const resolved_value_t *value) { char *result; /* Documentation à retourner */ enum_value_t faked; /* Copie d'élément recherché */ bool found; /* Présence de partage existant*/ size_t index; /* Indice du point d'insertion */ const enum_value_t *item; /* Elément retrouvé par valeur */ faked.value = *value; found = bsearch_index(&faked, kenum->cases_v2l, kenum->cases_v2l_count, sizeof(enum_value_t *), (__compar_fn_t)compare_enum_values_by_value, &index); if (found) item = kenum->cases_l2v[index]; else item = kenum->defcase; if (item != NULL) result = strdup(item->doc); else result = NULL; return result; }