/* Chrysalide - Outil d'analyse de fichiers binaires * attribute.c - spécification d'un attribut 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 "attribute.h" #include #include #include #include #include #include "attribute-int.h" #include "../expression.h" #include "../scope.h" #include "../records/bits.h" #include "../records/empty.h" #include "../records/item.h" #include "../records/list.h" /* -------------------- CORRESPONDANCE ENTRE ATTRIBUT ET BINAIRE -------------------- */ /* Initialise la classe des attributs de spécification Kaitai. */ static void g_kaitai_attribute_class_init(GKaitaiAttributeClass *); /* Initialise un attribut de spécification Kaitai. */ static void g_kaitai_attribute_init(GKaitaiAttribute *); /* Supprime toutes les références externes. */ static void g_kaitai_attribute_dispose(GKaitaiAttribute *); /* Procède à la libération totale de la mémoire. */ static void g_kaitai_attribute_finalize(GKaitaiAttribute *); /* Traduit en champ de bits une chaîne de caractères. */ static bool g_kaitai_attribute_resolve_bit_field(GKaitaiAttribute *, const char *); /* Traduit en type concret une chaîne de caractères. */ static bool g_kaitai_attribute_resolve_type(GKaitaiAttribute *, const char *); /* Valide la cohérence des informations portées par l'attribut. */ static bool g_kaitai_attribute_check(const GKaitaiAttribute *); /* Copie le coeur de la définition d'un lecteur d'attribut. */ static GKaitaiAttribute *g_kaitai_attribute_dup_for(const GKaitaiAttribute *); /* --------------------- IMPLEMENTATION DES FONCTIONS DE CLASSE --------------------- */ /* Parcourt un contenu binaire selon des spécifications Kaitai. */ static bool _g_kaitai_attribute_parse_content(GKaitaiAttribute *, kaitai_scope_t *, GBinContent *, ext_vmpa_t *, GMatchRecord **); /* Extrait d'un contenu une série d'octets avec terminaison. */ static bool g_kaitai_attribute_parse_terminated_bytes(GKaitaiAttribute *, const kaitai_scope_t *, GBinContent *, vmpa2t *, GMatchRecord **); /* Détermine la zone de couverture finale d'une correspondance. */ static bool g_kaitai_attribute_compute_maybe_terminated_range(const GKaitaiAttribute *, const kaitai_scope_t *, const GBinContent *, const vmpa2t *, phys_t *, mrange_t *); /* Parcourt un contenu binaire selon des spécifications Kaitai. */ static bool g_kaitai_attribute_parse_content(GKaitaiAttribute *, kaitai_scope_t *, GBinContent *, ext_vmpa_t *, GMatchRecord **); /* ---------------------------------------------------------------------------------- */ /* CORRESPONDANCE ENTRE ATTRIBUT ET BINAIRE */ /* ---------------------------------------------------------------------------------- */ /* Indique le type défini pour un attribut de la spécification Kaitai. */ G_DEFINE_TYPE(GKaitaiAttribute, g_kaitai_attribute, G_TYPE_KAITAI_PARSER); /****************************************************************************** * * * Paramètres : klass = classe à initialiser. * * * * Description : Initialise la classe des attributs de spécification Kaitai. * * * * Retour : - * * * * Remarques : - * * * ******************************************************************************/ static void g_kaitai_attribute_class_init(GKaitaiAttributeClass *klass) { GObjectClass *object; /* Autre version de la classe */ GKaitaiParserClass *parser; /* Version parente de la classe*/ object = G_OBJECT_CLASS(klass); object->dispose = (GObjectFinalizeFunc/* ! */)g_kaitai_attribute_dispose; object->finalize = (GObjectFinalizeFunc)g_kaitai_attribute_finalize; parser = G_KAITAI_PARSER_CLASS(klass); parser->parse = (parse_kaitai_fc)g_kaitai_attribute_parse_content; klass->get_label = g_kaitai_attribute_get_raw_id; } /****************************************************************************** * * * Paramètres : attrib = instance à initialiser. * * * * Description : Initialise un attribut de spécification Kaitai. * * * * Retour : - * * * * Remarques : - * * * ******************************************************************************/ static void g_kaitai_attribute_init(GKaitaiAttribute *attrib) { attrib->raw_id = NULL; attrib->orig_id = NULL; attrib->doc = NULL; attrib->payload = KAP_UNINITIALIZED; attrib->repetition = KAR_NO_REPETITION; attrib->repeat_controller = NULL; attrib->condition = NULL; init_szstr(&attrib->terminator); attrib->consume = true; attrib->include = false; attrib->eos_error = true; } /****************************************************************************** * * * Paramètres : attrib = instance d'objet GLib à traiter. * * * * Description : Supprime toutes les références externes. * * * * Retour : - * * * * Remarques : - * * * ******************************************************************************/ static void g_kaitai_attribute_dispose(GKaitaiAttribute *attrib) { if (attrib->payload & KAP_DYNAMIC_TYPE) g_clear_object(&attrib->switchon); G_OBJECT_CLASS(g_kaitai_attribute_parent_class)->dispose(G_OBJECT(attrib)); } /****************************************************************************** * * * Paramètres : attrib = instance d'objet GLib à traiter. * * * * Description : Procède à la libération totale de la mémoire. * * * * Retour : - * * * * Remarques : - * * * ******************************************************************************/ static void g_kaitai_attribute_finalize(GKaitaiAttribute *attrib) { if (attrib->raw_id != NULL) free(attrib->raw_id); if (attrib->orig_id != NULL) free(attrib->orig_id); if (attrib->doc != NULL) free(attrib->doc); if (attrib->payload & KAP_FIXED_CONTENT) exit_szstr(&attrib->fixed_content); else if (attrib->payload & KAP_USER_TYPE) free(attrib->named_type); if (attrib->fixed_size != NULL) free(attrib->fixed_size); if (attrib->repeat_controller != NULL) free(attrib->repeat_controller); if (attrib->condition != NULL) free(attrib->condition); exit_szstr(&attrib->terminator); G_OBJECT_CLASS(g_kaitai_attribute_parent_class)->finalize(G_OBJECT(attrib)); } /****************************************************************************** * * * Paramètres : parent = noeud Yaml contenant l'attribut à constituer. * * * * Description : Construit un lecteur d'attribut Kaitai. * * * * Retour : Instance mise en place ou NULL en cas d'échec. * * * * Remarques : - * * * ******************************************************************************/ GKaitaiAttribute *g_kaitai_attribute_new(GYamlNode *parent) { GKaitaiAttribute *result; /* Structure à retourner */ result = g_object_new(G_TYPE_KAITAI_ATTRIBUTE, NULL); if (!g_kaitai_attribute_create(result, parent, false /* TODO : REMME ? */)) g_clear_object(&result); return result; } /****************************************************************************** * * * Paramètres : attrib = lecteur d'attribut Kaitai à initialiser pleinement.* * parent = noeud Yaml contenant l'attribut à constituer. * * need_id = encadre la présence d'un champ "id". * * * * Description : Met en place un lecteur d'attribut Kaitai. * * * * Retour : Bilan de l'opération. * * * * Remarques : - * * * ******************************************************************************/ bool g_kaitai_attribute_create(GKaitaiAttribute *attrib, GYamlNode *parent, bool need_id) { bool result; /* Bilan à retourner */ GYamlNode *node; /* Noeud particulier présent */ const char *value; /* Valeur Yaml particulière */ char *rebuilt_value; /* Valeur Yaml rassemblée */ kaitai_scope_t fake; /* Contexte de circonstance */ resolved_value_t bytes; /* Données brutes obtenues */ GYamlNode *other_node; /* Autre noeud nécessaire */ result = false; /* Identifiant obligatoire */ node = g_yaml_node_find_first_by_path(parent, "/id"); if (node != NULL) { assert(G_IS_YAML_PAIR(node)); value = g_yaml_pair_get_value(G_YAML_PAIR(node)); if (value == NULL) { g_object_unref(G_OBJECT(node)); goto bad_id; } attrib->raw_id = strdup(value); g_object_unref(G_OBJECT(node)); } else if (need_id) goto bad_id; /* Identifiant facultatif */ node = g_yaml_node_find_first_by_path(parent, "/-orig-id"); if (node != NULL) { assert(G_IS_YAML_PAIR(node)); value = g_yaml_pair_get_value(G_YAML_PAIR(node)); if (value == NULL) { g_object_unref(G_OBJECT(node)); goto bad_id; } attrib->orig_id = strdup(value); g_object_unref(G_OBJECT(node)); } /* Eventuelle documentation */ node = g_yaml_node_find_first_by_path(parent, "/doc"); if (node != NULL) { assert(G_IS_YAML_PAIR(node)); value = g_yaml_pair_get_value(G_YAML_PAIR(node)); if (value == NULL) { g_object_unref(G_OBJECT(node)); goto bad_doc; } attrib->doc = strdup(value); g_object_unref(G_OBJECT(node)); } /* Champ contents */ node = g_yaml_node_find_first_by_path(parent, "/contents"); if (node != NULL) { assert(G_IS_YAML_PAIR(node)); rebuilt_value = g_yaml_pair_aggregate_value(G_YAML_PAIR(node)); if (rebuilt_value == NULL) { g_object_unref(G_OBJECT(node)); goto bad_content; } fake.meta = NULL; fake.root = NULL; fake.parent = NULL; fake.last = NULL; if (!resolve_kaitai_expression_as_bytes(&fake, rebuilt_value, strlen(rebuilt_value), &bytes)) { free(rebuilt_value); g_object_unref(G_OBJECT(node)); goto bad_content; } free(rebuilt_value); attrib->fixed_content = bytes.bytes; g_object_unref(G_OBJECT(node)); attrib->payload |= KAP_FIXED_CONTENT; } /* Charge portée par un type */ node = g_yaml_node_find_first_by_path(parent, "/type"); if (node != NULL) { if (attrib->payload & KAP_FIXED_CONTENT) { printf("Can not handle fixed content and type definition at the same time for an attribute.\n"); goto bad_definition; } assert(G_IS_YAML_PAIR(node)); value = g_yaml_pair_get_value(G_YAML_PAIR(node)); if (value != NULL) { if (g_kaitai_attribute_resolve_bit_field(attrib, value)) attrib->payload |= KAP_BIT_FIELD_TYPE; else if (g_kaitai_attribute_resolve_type(attrib, value)) attrib->payload |= KAP_BASIC_TYPE; else { attrib->named_type = strdup(value); attrib->payload |= KAP_USER_TYPE; } } else { attrib->switchon = g_kaitai_switch_new(parent, attrib); if (attrib->switchon == NULL) goto bad_definition; attrib->payload |= KAP_DYNAMIC_TYPE; } g_object_unref(G_OBJECT(node)); } /* Répétitions contrôlées ? */ node = g_yaml_node_find_first_by_path(parent, "/repeat"); if (node != NULL) { assert(G_IS_YAML_PAIR(node)); value = g_yaml_pair_get_value(G_YAML_PAIR(node)); if (value != NULL) { if (strcmp(value, "eos") == 0) attrib->repetition = KAR_END_OF_STREAM; else if (strcmp(value, "expr") == 0) { other_node = g_yaml_node_find_first_by_path(parent, "/repeat-expr"); if (other_node != NULL) { if (G_IS_YAML_PAIR(other_node)) { value = g_yaml_pair_get_value(G_YAML_PAIR(other_node)); if (value != NULL) { attrib->repetition = KAR_EXPRESSION; attrib->repeat_controller = strdup(value); } else printf("Expected repeat expression\n"); } g_object_unref(G_OBJECT(other_node)); } } else if (strcmp(value, "until") == 0) { other_node = g_yaml_node_find_first_by_path(parent, "/repeat-until"); if (other_node != NULL) { assert(G_IS_YAML_PAIR(other_node)); value = g_yaml_pair_get_value(G_YAML_PAIR(other_node)); if (value != NULL) { attrib->repetition = KAR_UNTIL; attrib->repeat_controller = strdup(value); } else printf("Expected repeat expression\n"); } g_object_unref(G_OBJECT(other_node)); } } g_object_unref(G_OBJECT(node)); } /* Intégration sous condition ? */ node = g_yaml_node_find_first_by_path(parent, "/if"); if (node != NULL) { assert(G_IS_YAML_PAIR(node)); value = g_yaml_pair_get_value(G_YAML_PAIR(node)); if (value != NULL) attrib->condition = strdup(value); g_object_unref(G_OBJECT(node)); } /* Taille fixée ? */ node = g_yaml_node_find_first_by_path(parent, "/size"); if (node != NULL) { assert(G_IS_YAML_PAIR(node)); value = g_yaml_pair_get_value(G_YAML_PAIR(node)); if (value != NULL) { attrib->fixed_size = strdup(value); attrib->payload |= KAP_SIZED; } g_object_unref(G_OBJECT(node)); if ((attrib->payload & KAP_SIZED) == 0) goto bad_content; } /* Prise en considération d'une taille maximale */ node = g_yaml_node_find_first_by_path(parent, "/size-eos"); if (node != NULL) { assert(G_IS_YAML_PAIR(node)); value = g_yaml_pair_get_value(G_YAML_PAIR(node)); if (value != NULL && strcmp(value, "true") == 0) { if (attrib->payload != KAP_UNINITIALIZED) /* printf warning */; attrib->payload |= KAP_SIZED_EOS; } g_object_unref(G_OBJECT(node)); } /* Champ terminator */ node = g_yaml_node_find_first_by_path(parent, "/terminator"); if (node != NULL) { assert(G_IS_YAML_PAIR(node)); rebuilt_value = g_yaml_pair_aggregate_value(G_YAML_PAIR(node)); if (rebuilt_value == NULL) { g_object_unref(G_OBJECT(node)); goto bad_content; } fake.meta = NULL; fake.root = NULL; fake.parent = NULL; fake.last = NULL; if (!resolve_kaitai_expression_as_bytes(&fake, rebuilt_value, strlen(rebuilt_value), &bytes)) { free(rebuilt_value); g_object_unref(G_OBJECT(node)); goto bad_content; } free(rebuilt_value); if (attrib->terminator.data != NULL) printf("A ending content has already been specified (implicitly by the strz type)"); else { attrib->terminator.data = bytes.bytes.data; attrib->terminator.len = bytes.bytes.len; } g_object_unref(G_OBJECT(node)); } /* Champ consume */ node = g_yaml_node_find_first_by_path(parent, "/consume"); if (node != NULL) { assert(G_IS_YAML_PAIR(node)); value = g_yaml_pair_get_value(G_YAML_PAIR(node)); if (value != NULL) { if (strcmp(value, "true") == 0) attrib->consume = true; else if (strcmp(value, "false") == 0) attrib->consume = false; else printf("Unsupported value for the 'consume' property (expecting true of false)"); } g_object_unref(G_OBJECT(node)); } /* Champ include */ node = g_yaml_node_find_first_by_path(parent, "/include"); if (node != NULL) { assert(G_IS_YAML_PAIR(node)); value = g_yaml_pair_get_value(G_YAML_PAIR(node)); if (value != NULL) { if (strcmp(value, "true") == 0) attrib->include = true; else if (strcmp(value, "false") == 0) attrib->include = false; else printf("Unsupported value for the 'include' property (expecting true of false)"); } g_object_unref(G_OBJECT(node)); } /* Champ eos-error */ node = g_yaml_node_find_first_by_path(parent, "/eos-error"); if (node != NULL) { assert(G_IS_YAML_PAIR(node)); value = g_yaml_pair_get_value(G_YAML_PAIR(node)); if (value != NULL) { if (strcmp(value, "true") == 0) attrib->eos_error = true; if (strcmp(value, "false") == 0) attrib->eos_error = false; else printf("Unsupported value for the 'eos_error' property (expecting true of false)"); } g_object_unref(G_OBJECT(node)); } /* Validation finale */ result = g_kaitai_attribute_check(attrib); bad_definition: bad_doc: bad_id: bad_content: return result; } /****************************************************************************** * * * Paramètres : attrib = attribut Kaitai en cours de constitution. * * desc = chaîne de caractère à interpréter en type. * * * * Description : Traduit en champ de bits une chaîne de caractères. * * * * Retour : Bilan de l'opération. * * * * Remarques : - * * * ******************************************************************************/ static bool g_kaitai_attribute_resolve_bit_field(GKaitaiAttribute *attrib, const char *desc) { bool result; /* Bilan à retourner */ size_t len; /* Taille de la chaîne à lire */ char *end; /* Prochain caractère à lire */ unsigned long size; /* Taille du champ de bits */ result = false; if (desc[0] == 'b') { len = strlen(desc); size = strtoul(&desc[1], &end, 10); if (size > 64) { printf("Unsupported size for bit field: %lu\n", size); goto exit; } result = ((desc + len) == end); if (result) attrib->bf_size = size; } exit: return result; } /****************************************************************************** * * * Paramètres : attrib = attribut Kaitai en cours de constitution. * * desc = chaîne de caractère à interpréter en type. * * * * Description : Traduit en type concret une chaîne de caractères. * * * * Retour : Bilan de l'opération. * * * * Remarques : - * * * ******************************************************************************/ static bool g_kaitai_attribute_resolve_type(GKaitaiAttribute *attrib, const char *desc) { bool result; /* Bilan à retourner */ result = true; attrib->basic = BTP_INVALID; attrib->has_endian = false; /** * Cf. définition des types de base existants : * http://doc.kaitai.io/user_guide.html#_fixed_size_structures */ #define RESOLVE_ENDIAN \ if (desc[2] == 'l') \ { \ if (desc[3] == 'e') \ { \ attrib->endian = SRE_LITTLE; \ attrib->has_endian = true; \ } \ } \ else if (desc[2] == 'b') \ { \ if (desc[3] == 'e') \ { \ attrib->endian = SRE_BIG; \ attrib->has_endian = true; \ } \ } \ /* Analyse de la chaîne fournie */ switch (desc[0]) { case 'f': switch (desc[1]) { case '4': attrib->basic = BTP_754R_32; RESOLVE_ENDIAN; break; case '8': attrib->basic = BTP_754R_64; RESOLVE_ENDIAN; break; default: result = false; break; } break; case 's': switch (desc[1]) { case '1': attrib->basic = BTP_CHAR; RESOLVE_ENDIAN; break; case '2': attrib->basic = BTP_SHORT; RESOLVE_ENDIAN; break; case '4': attrib->basic = BTP_INT; RESOLVE_ENDIAN; break; case '8': attrib->basic = BTP_LONG_LONG; RESOLVE_ENDIAN; break; case 't': if (desc[2] == 'r') { attrib->basic = BTP_CHAR; attrib->is_string = true; if (desc[3] == 'z') { attrib->terminator.data = strdup(""); attrib->terminator.len = 1; } } else result = false; break; default: result = false; break; } break; case 'u': switch (desc[1]) { case '1': attrib->basic = BTP_UCHAR; RESOLVE_ENDIAN; break; case '2': attrib->basic = BTP_USHORT; RESOLVE_ENDIAN; break; case '4': attrib->basic = BTP_UINT; RESOLVE_ENDIAN; break; case '8': attrib->basic = BTP_ULONG_LONG; RESOLVE_ENDIAN; break; default: result = false; break; } break; default: result = false; break; } /* Vérification d'une comparaison complète */ if (result) switch (attrib->basic) { case BTP_CHAR: if (attrib->is_string) { if (attrib->terminator.data != NULL) result = (desc[4] == 0); else result = (desc[3] == 0); } else { if (attrib->has_endian) result = (desc[4] == 0); else result = (desc[2] == 0); } break; default: if (attrib->has_endian) result = (desc[4] == 0); else result = (desc[2] == 0); break; } return result; } /****************************************************************************** * * * Paramètres : attrib = attribut Kaitai à valider. * * * * Description : Valide la cohérence des informations portées par l'attribut. * * * * Retour : Bilan de l'opération. * * * * Remarques : - * * * ******************************************************************************/ static bool g_kaitai_attribute_check(const GKaitaiAttribute *attrib) { bool result; /* Bilan à retourner */ result = true; /** * Une lecture de tous les octets restants ne doit correspondre qu'à des octets bruts. */ if (attrib->payload & KAP_SIZED_EOS && attrib->payload != KAP_SIZED_EOS) { result = (attrib->payload & KAP_BASIC_TYPE) && attrib->is_string; if (!result) { printf("Reading all the remaining bytes should only produce bytes."); result = true; } } /** * Une chaîne (type str[z]) doit comporter une séquence de terminaison. */ if ((attrib->payload & KAP_BASIC_TYPE) && attrib->is_string) { result = (attrib->terminator.data != NULL) || (attrib->payload & (KAP_SIZED | KAP_SIZED_EOS)); if (!result) { printf("An unsized string (str type with no size attribute) has to be link to a terminator sequence."); goto exit; } } /** * Si une séquence d'octets finaux est spécifiée, alors l'attribut * doit correspondre à un type str[z] (lecture) ou de taille fixée * (validation post-lecture). */ if (attrib->terminator.data != NULL) { result = ((attrib->payload & ~(KAP_FIXED_CONTENT | KAP_BASIC_TYPE | KAP_SIZED)) == 0); if (result && (attrib->payload & KAP_BASIC_TYPE)) result = attrib->is_string; if (!result) { printf("A useless terminator is specified."); result = true; goto exit; } } /** * Il n'est pas possible d'inclure un marqueur de fin sans le consommer. */ if (!attrib->consume && attrib->include) { result = false; printf("It is not possible to include a terminator without consuming it."); } exit: return result; } /****************************************************************************** * * * Paramètres : attrib = lecteur d'attribut Kaitai à dupliquer. * * type = type utilisateur à associer au nouvel attribut. * * * * Description : Dérive un lecteur d'attribut Kaitai pour un type utilisateur.* * * * Retour : Instance mise en place ou NULL en cas d'échec. * * * * Remarques : - * * * ******************************************************************************/ GKaitaiAttribute *g_kaitai_attribute_dup_for_user_type(const GKaitaiAttribute *attrib, const char *type) { GKaitaiAttribute *result; /* Structure à retourner */ result = g_kaitai_attribute_dup_for(attrib); result->payload = KAP_USER_TYPE; result->named_type = strdup(type); return result; } /****************************************************************************** * * * Paramètres : attrib = lecteur d'attribut Kaitai à dupliquer. * * * * Description : Copie le coeur de la définition d'un lecteur d'attribut. * * * * Retour : Nouvelle instance à compléter. * * * * Remarques : - * * * ******************************************************************************/ static GKaitaiAttribute *g_kaitai_attribute_dup_for(const GKaitaiAttribute *attrib) { GKaitaiAttribute *result; /* Structure à retourner */ result = g_object_new(G_TYPE_KAITAI_ATTRIBUTE, NULL); /** * Il n'y a rien à copier dans la structure parente. * * Les travaux de copie ne portent ainsi que sur le présent attribut. */ if (attrib->raw_id != NULL) result->raw_id = strdup(attrib->raw_id); if (attrib->orig_id != NULL) result->orig_id = strdup(attrib->orig_id); if (attrib->doc != NULL) result->doc = strdup(attrib->doc); if (attrib->fixed_size != NULL) result->fixed_size = strdup(attrib->fixed_size); result->repetition = attrib->repetition; if (attrib->repeat_controller != NULL) result->repeat_controller = strdup(attrib->repeat_controller); if (attrib->condition != NULL) result->condition = strdup(attrib->condition); return result; } /****************************************************************************** * * * Paramètres : attrib = lecteur d'attribut Kaitai à consulter. * * * * Description : Indique l'étiquette à utiliser pour identifier un attribut. * * * * Retour : Valeur brute de l'identifiant. * * * * Remarques : - * * * ******************************************************************************/ const char *g_kaitai_attribute_get_label(const GKaitaiAttribute *attrib) { const char *result; /* Valeur à renvoyer */ GKaitaiAttributeClass *class; /* Classe de l'instance */ class = G_KAITAI_ATTRIBUTE_GET_CLASS(attrib); result = class->get_label(attrib); return result; } /****************************************************************************** * * * Paramètres : attrib = lecteur d'attribut Kaitai à consulter. * * * * Description : Indique la désignation brute d'un identifiant Kaitai. * * * * Retour : Valeur brute de l'identifiant. * * * * Remarques : - * * * ******************************************************************************/ const char *g_kaitai_attribute_get_raw_id(const GKaitaiAttribute *attrib) { char *result; /* Valeur à renvoyer */ result = attrib->raw_id; return result; } /****************************************************************************** * * * Paramètres : attrib = lecteur d'attribut Kaitai à consulter. * * * * Description : Indique la désignation originelle d'un identifiant Kaitai. * * * * Retour : Valeur originelle de l'identifiant. * * * * Remarques : - * * * ******************************************************************************/ const char *g_kaitai_attribute_get_original_id(const GKaitaiAttribute *attrib) { char *result; /* Valeur à renvoyer */ result = attrib->orig_id; return result; } /****************************************************************************** * * * Paramètres : attrib = lecteur d'attribut Kaitai à consulter. * * * * Description : Fournit une éventuelle documentation concernant l'attribut. * * * * Retour : Description enregistrée ou NULL si absente. * * * * Remarques : - * * * ******************************************************************************/ const char *g_kaitai_attribute_get_doc(const GKaitaiAttribute *attrib) { char *result; /* Valeur à renvoyer */ result = attrib->doc; return result; } /****************************************************************************** * * * Paramètres : attrib = lecteur d'attribut Kaitai à consulter. * * * * Description : Indique la nature de la charge représentée par l'attribut. * * * * Retour : Forme de contenu représenté par le lecteur d'attribut. * * * * Remarques : - * * * ******************************************************************************/ KaitaiAttributePayload g_kaitai_attribute_get_payload(const GKaitaiAttribute *attrib) { KaitaiAttributePayload result; /* Type de charge à renvoyer */ result = attrib->payload; return result; } /****************************************************************************** * * * Paramètres : attrib = lecteur d'attribut Kaitai à consulter. * * basic = type de base Kaitai reconnu par le lecteur. [OUT]* * is_string = nature du type BTP_CHAR en sortie. [OUT] * * * * Description : Précise un éventuel type de base reconnu par le lecteur. * * * * Retour : Validité du type renseigné en argument. * * * * Remarques : - * * * ******************************************************************************/ bool g_kaitai_attribute_get_basic_type(const GKaitaiAttribute *attrib, BaseType *basic, bool *is_string) { bool result; /* Validité à retourner */ result = (attrib->payload & KAP_BASIC_TYPE); if (result) { *basic = attrib->basic; *is_string = attrib->is_string; } return result; } /****************************************************************************** * * * Paramètres : attrib = lecteur d'attribut Kaitai à consulter. * * content = contenu binaire à venir lire. * * range = espace disponible pour la lecture. * * out = tableau d'octets retournés. [OUT] * * len = taille de ce tableau alloué. [OUT] * * * * Description : Lit les octets d'une chaîne représentée. * * * * Retour : Bilan de l'opération. * * * * Remarques : - * * * ******************************************************************************/ bool g_kaitai_attribute_read_truncated_bytes(const GKaitaiAttribute *attrib, const GBinContent *content, const mrange_t *range, bin_t **out, size_t *len) { bool result; /* Bilan à retourner */ vmpa2t tmppos; /* Localisation modifiable */ const bin_t *data; /* Accès aux données brutes */ result = false; if ((attrib->payload & KAP_SIZED) == 0) goto bad_type; copy_vmpa(&tmppos, get_mrange_addr(range)); *len = get_mrange_length(range); data = g_binary_content_get_raw_access(content, &tmppos, *len); *out = malloc(sizeof(bin_t) * (*len + 1)); memcpy(*out, data, *len); (*out)[*len] = '\0'; result = true; bad_type: return result; } /****************************************************************************** * * * Paramètres : attrib = lecteur d'attribut Kaitai à consulter. * * * * Description : Détermine si l'attribue porte une valeur entière signée. * * * * Retour : Bilan de la consultation : true si un entier signé est visé. * * * * Remarques : - * * * ******************************************************************************/ bool g_kaitai_attribute_handle_signed_integer(const GKaitaiAttribute *attrib) { bool result; /* Bilan à retourner */ result = false; if ((attrib->payload & KAP_BASIC_TYPE) == 0) goto bad_type; switch (attrib->basic) { case BTP_CHAR: case BTP_SHORT: case BTP_INT: case BTP_LONG_LONG: result = true; break; default: break; } bad_type: return result; } /****************************************************************************** * * * Paramètres : attrib = lecteur d'attribut Kaitai à consulter. * * content = contenu binaire à venir lire. * * range = espace de lecture. * * endian = boustime des données à respecter. * * out = valeur à sauvegarder sous une forme générique.[OUT]* * * * Description : Lit la valeur d'un élément Kaitai entier représenté. * * * * Retour : Bilan de l'opération. * * * * Remarques : - * * * ******************************************************************************/ bool g_kaitai_attribute_read_value(const GKaitaiAttribute *attrib, const GBinContent *content, const mrange_t *range, SourceEndian endian, resolved_value_t *out) { bool result; /* Bilan à retourner */ vmpa2t tmppos; /* Localisation modifiable */ const bin_t *data; /* Données brutes restituées */ int8_t stmp8; /* Valeur de 8 bits lue */ uint8_t tmp8; /* Valeur de 8 bits lue */ int16_t stmp16; /* Valeur de 16 bits lue */ uint16_t tmp16; /* Valeur de 16 bits lue */ int32_t stmp32; /* Valeur de 32 bits lue */ uint32_t tmp32; /* Valeur de 32 bits lue */ int64_t stmp64; /* Valeur de 64 bits lue */ uint64_t tmp64; /* Valeur de 64 bits lue */ result = false; if (attrib->payload & (KAP_FIXED_CONTENT | KAP_SIZED | KAP_SIZED_EOS)) { copy_vmpa(&tmppos, get_mrange_addr(range)); data = g_binary_content_get_raw_access(content, &tmppos, get_mrange_length(range)); result = (data != NULL); if (result) { out->type = GVT_BYTES; out->bytes.len = get_mrange_length(range); out->bytes.data = malloc(out->bytes.len); memcpy(out->bytes.data, data, out->bytes.len); } } else if (attrib->payload & KAP_BASIC_TYPE) { copy_vmpa(&tmppos, get_mrange_addr(range)); switch (attrib->basic) { case BTP_CHAR: if (attrib->is_string) { copy_vmpa(&tmppos, get_mrange_addr(range)); data = g_binary_content_get_raw_access(content, &tmppos, get_mrange_length(range)); result = (data != NULL); if (result) { out->type = GVT_BYTES; out->bytes.len = get_mrange_length(range); out->bytes.data = malloc(out->bytes.len); memcpy(out->bytes.data, data, out->bytes.len); } } else { assert(get_mrange_length(range) == 1); result = g_binary_content_read_s8(content, &tmppos, &stmp8); out->type = GVT_SIGNED_INTEGER; out->signed_integer = stmp8; } break; case BTP_UCHAR: assert(get_mrange_length(range) == 1); result = g_binary_content_read_u8(content, &tmppos, &tmp8); out->type = GVT_UNSIGNED_INTEGER; out->unsigned_integer = tmp8; break; case BTP_SHORT: assert(get_mrange_length(range) == 2); result = g_binary_content_read_s16(content, &tmppos, endian, &stmp16); out->type = GVT_SIGNED_INTEGER; out->signed_integer = stmp16; break; case BTP_USHORT: assert(get_mrange_length(range) == 2); result = g_binary_content_read_u16(content, &tmppos, endian, &tmp16); out->type = GVT_UNSIGNED_INTEGER; out->unsigned_integer = tmp16; break; case BTP_INT: assert(get_mrange_length(range) == 4); result = g_binary_content_read_s32(content, &tmppos, endian, &stmp32); out->type = GVT_SIGNED_INTEGER; out->signed_integer = stmp32; break; case BTP_UINT: assert(get_mrange_length(range) == 4); result = g_binary_content_read_u32(content, &tmppos, endian, &tmp32); out->type = GVT_UNSIGNED_INTEGER; out->unsigned_integer = tmp32; break; case BTP_LONG_LONG: assert(get_mrange_length(range) == 8); result = g_binary_content_read_s64(content, &tmppos, endian, &stmp64); out->type = GVT_SIGNED_INTEGER; out->signed_integer = stmp64; break; case BTP_ULONG_LONG: assert(get_mrange_length(range) == 8); result = g_binary_content_read_u64(content, &tmppos, endian, &tmp64); out->type = GVT_UNSIGNED_INTEGER; out->unsigned_integer = tmp64; break; default: break; } } return result; } /****************************************************************************** * * * Paramètres : attrib = lecteur d'attribut Kaitai à consulter. * * content = contenu binaire à venir lire. * * epos = tête de lecture avec granularité en bits. * * size = quantité de bits à prendre en compte. * * endian = boustime des données à respecter. * * out = valeur à sauvegarder sous une forme générique.[OUT]* * * * Description : Lit la valeur d'un champ de bits Kaitai représenté. * * * * Retour : Bilan de l'opération. * * * * Remarques : - * * * ******************************************************************************/ bool g_kaitai_attribute_read_bit_field_value(const GKaitaiAttribute *attrib, const GBinContent *content, const ext_vmpa_t *epos, uint8_t size, SourceEndian endian, resolved_value_t *out) { bool result; /* Bilan à retourner */ ext_vmpa_t tmpepos; /* Localisation modifiable */ uint64_t tmp64; /* Valeur de 64 bits lue */ result = false; if (attrib->payload & KAP_BIT_FIELD_TYPE) { copy_evmpa(&tmpepos, epos); result = g_binary_content_read_bits(content, &tmpepos, size, endian, &tmp64); if (result) { out->type = GVT_UNSIGNED_INTEGER; out->unsigned_integer = tmp64; } } return result; } /* ---------------------------------------------------------------------------------- */ /* IMPLEMENTATION DES FONCTIONS DE CLASSE */ /* ---------------------------------------------------------------------------------- */ /****************************************************************************** * * * Paramètres : attrib = structure Kaitai en cours de parcours. * * locals = variables locales pour les résolutions de types. * * content = données binaires à analyser et traduire. * * epos = tête de lecture courante. [OUT] * * record = noeud d'arborescence d'éléments rencontrés. [OUT] * * * * Description : Parcourt un contenu binaire selon des spécifications Kaitai. * * * * Retour : Bilan de l'opératon : true pour continuer, false sinon. * * * * Remarques : - * * * ******************************************************************************/ static bool _g_kaitai_attribute_parse_content(GKaitaiAttribute *attrib, kaitai_scope_t *locals, GBinContent *content, ext_vmpa_t *epos, GMatchRecord **record) { bool result; /* Bilan à retourner */ resolved_value_t authorized; /* Validation des traitements */ mrange_t work_range; /* Définition de cette aire */ GBinContent *work_area; /* Aire de travail */ bool has_empty_size; /* Mémorise une taille nulle */ //unsigned long long value; /* Valeur entière finale */ //bool status; /* Bilan d'une conversion */ vmpa2t tmp; /* Position de travail */ phys_t diff; /* Différentiel de positions */ resolved_value_t resolved; /* Valeur entière obtenue */ phys_t max_size; /* Taille maximale imposée */ const bin_t *data; /* Données à comparer */ GKaitaiType *user_type; /* Définition particulière */ mrange_t range; /* Couverture appliquée */ SourceEndian endian; /* Boutisme à observer */ phys_t cur_diff; /* Avancée de lecture courante */ result = false; *record = NULL; /* Lecture soumise à condition ? */ if (attrib->condition != NULL) { result = resolve_kaitai_expression_as_boolean(locals, attrib->condition, strlen(attrib->condition), &authorized); if (!result || !authorized.status) goto exit; } /* Zone de travail restreinte */ g_binary_content_compute_end_pos(content, &tmp); diff = compute_vmpa_diff(&epos->base, &tmp); if (epos->consumed_extra_bits > 0 && diff > 0) diff--; if (attrib->payload & KAP_SIZED) { result = resolve_kaitai_expression_as_integer(locals, attrib->fixed_size, strlen(attrib->fixed_size), &resolved); if (result) { if (resolved.type == GVT_UNSIGNED_INTEGER) max_size = resolved.unsigned_integer; else { assert(resolved.type == GVT_SIGNED_INTEGER); if (resolved.signed_integer < 0) result = false; else max_size = resolved.signed_integer; } if (result) result = (diff >= max_size); if (!result) printf("Need more data!\n"); if (result && max_size < diff) diff = max_size; } if (!result) goto exit; align_evmpa_on_byte(epos); init_mrange(&work_range, &epos->base, diff); work_area = g_restricted_content_new(content, &work_range); has_empty_size = (diff == 0); } else { work_area = content; has_empty_size = false; } /* Etablissement d'une zone de correspondance */ if (attrib->payload == KAP_UNINITIALIZED) assert(false); else if (attrib->payload & KAP_SIZED_EOS) { align_evmpa_on_byte(epos); result = true; } else if (attrib->payload & KAP_FIXED_CONTENT) { if (diff >= attrib->fixed_content.len) { align_evmpa_on_byte(epos); copy_vmpa(&tmp, &epos->base); data = g_binary_content_get_raw_access(work_area, &tmp, attrib->fixed_content.len); assert(data != NULL); result = (memcmp(data, attrib->fixed_content.data, attrib->fixed_content.len) == 0); if (result) diff = attrib->fixed_content.len; } } else if (attrib->payload & KAP_BIT_FIELD_TYPE) { if (attrib->has_endian) endian = attrib->endian; else endian = g_kaitai_meta_get_endian(locals->meta); *record = g_record_bit_field_new(attrib, work_area, epos, attrib->bf_size, endian); result = (*record != NULL); if (result) advance_evmpa_bits(epos, attrib->bf_size); } else if (attrib->payload & KAP_BASIC_TYPE) { align_evmpa_on_byte(epos); switch (attrib->basic) { case BTP_CHAR: case BTP_UCHAR: if (attrib->is_string) { if ((attrib->payload & KAP_SIZED) == 0) result = g_kaitai_attribute_parse_terminated_bytes(attrib, locals, work_area, &epos->base, record); } else { result = (diff >= 1); diff = 1; } break; case BTP_SHORT: case BTP_USHORT: result = (diff >= 2); diff = 2; break; case BTP_INT: case BTP_UINT: case BTP_754R_32: result = (diff >= 4); diff = 4; break; case BTP_LONG_LONG: case BTP_ULONG_LONG: case BTP_754R_64: result = (diff >= 8); diff = 8; break; default: break; } } else if (attrib->payload & KAP_USER_TYPE) { user_type = find_sub_type(locals, attrib->named_type); if (user_type != NULL) { result = g_kaitai_parser_parse_content(G_KAITAI_PARSER(user_type), locals, work_area, epos, record); if (result) /** * Le type utilisateur dérive du type GKaitaiStruct, qui ne possède pas * d'identifiant propre. La correspondance produite est ainsi nominalement * anonyme, ce qui empêche toute résolution. * * Le rattachement de l'étiquette de l'attribut d'origine est donc forcée ici. */ g_match_record_fix_creator(*record, G_KAITAI_PARSER(attrib)); g_object_unref(G_OBJECT(user_type)); } } else if (attrib->payload & KAP_DYNAMIC_TYPE) result = g_kaitai_parser_parse_content(G_KAITAI_PARSER(attrib->switchon), locals, work_area, epos, record); else if (attrib->payload & KAP_SIZED) { /* Cas déjà traité en début de fonction */ } /* Enregistrement de la correspondance */ if (result && *record == NULL) { /** * A ce stade, la granularité des travaux est l'octet. */ assert(epos->consumed_extra_bits == 0); /** * On choisit de laisser la création de correspondances nulles. * * Cela permet de disposer de la présence de champs valides, même vides * (cf. "4.10.3. Repeat until condition is met") */ /* if (diff > 0) */ { result = g_kaitai_attribute_compute_maybe_terminated_range(attrib, locals, content, &epos->base, &diff, &range); if (result) { if (has_empty_size) *record = G_MATCH_RECORD(g_record_empty_new(G_KAITAI_PARSER(attrib), content, &epos->base)); else { if (attrib->has_endian) endian = attrib->endian; else endian = g_kaitai_meta_get_endian(locals->meta); *record = G_MATCH_RECORD(g_record_item_new(attrib, work_area, &range, endian)); if (*record != NULL) advance_vmpa(&epos->base, diff); else result = false; } } } } /* Libération de zone de travail restreinte ? */ if (attrib->payload & KAP_SIZED) { /* Réalignement éventuel suite aux lectures dans la zone périmétrée... */ align_evmpa_on_byte(epos); cur_diff = compute_vmpa_diff(get_mrange_addr(&work_range), &epos->base); /* Pour GCC... */ max_size = get_mrange_length(&work_range); if (cur_diff < max_size) advance_vmpa(&epos->base, max_size - cur_diff); assert(work_area != content); g_object_unref(G_OBJECT(work_area)); } exit: return result; } /****************************************************************************** * * * Paramètres : attrib = structure Kaitai en cours de parcours. * * locals = variables locales pour les résolutions de types. * * content = données binaires à analyser et traduire. * * pos = tête de lecture courante. [OUT] * * record = noeud d'arborescence d'éléments rencontrés. [OUT] * * * * Description : Extrait d'un contenu une série d'octets avec terminaison. * * * * Retour : Bilan de l'opératon : true pour continuer, false sinon. * * * * Remarques : - * * * ******************************************************************************/ static bool g_kaitai_attribute_parse_terminated_bytes(GKaitaiAttribute *attrib, const kaitai_scope_t *locals, GBinContent *content, vmpa2t *pos, GMatchRecord **record) { bool result; /* Bilan à retourner */ sized_string_t marker; /* Marqueur potentiel à tester */ vmpa2t iter; /* Tête de lecture courante */ vmpa2t end; /* Fin du parcours possible */ vmpa2t tmp; /* Position à mouvante */ phys_t diff; /* Avancée de lecture courante */ mrange_t range; /* Couverture appliquée */ SourceEndian endian; /* Boutisme à observer */ result = false; /* Recherche du marqueur de fin */ marker.len = attrib->terminator.len; copy_vmpa(&iter, pos); g_binary_content_compute_end_pos(content, &end); while (cmp_vmpa_by_phy(&iter, &end) < 0) { copy_vmpa(&tmp, &iter); marker.data = (char *)g_binary_content_get_raw_access(content, &tmp, marker.len); if (marker.data == NULL) break; if (szmemcmp(&marker, &attrib->terminator) == 0) { result = true; break; } advance_vmpa(&iter, 1); } /* Si la recherche a abouti */ if (result) { diff = compute_vmpa_diff(pos, &iter); if (attrib->include) diff += marker.len; init_mrange(&range, pos, diff); if (attrib->has_endian) endian = attrib->endian; else endian = g_kaitai_meta_get_endian(locals->meta); *record = G_MATCH_RECORD(g_record_item_new(attrib, content, &range, endian)); copy_vmpa(pos, &iter); if (attrib->consume) advance_vmpa(pos, marker.len); } /* Sinon l'absence de marqueur est-elle tolérée ? */ else if (!attrib->eos_error) { diff = compute_vmpa_diff(pos, &end); init_mrange(&range, pos, diff); if (attrib->has_endian) endian = attrib->endian; else endian = g_kaitai_meta_get_endian(locals->meta); *record = G_MATCH_RECORD(g_record_item_new(attrib, content, &range, endian)); copy_vmpa(pos, &end); result = true; } return result; } /****************************************************************************** * * * Paramètres : attrib = structure Kaitai en cours de parcours. * * locals = variables locales pour les résolutions de types. * * content = données binaires à analyser et traduire. * * pos = tête de lecture courante. * * maxsize = taille maximale de la zone de correspondance. [OUT]* * range = zone de couverture à officialiser. [OUT] * * * * Description : Détermine la zone de couverture finale d'une correspondance. * * * * Retour : Bilan de l'opératon : true pour continuer, false sinon. * * * * Remarques : - * * * ******************************************************************************/ static bool g_kaitai_attribute_compute_maybe_terminated_range(const GKaitaiAttribute *attrib, const kaitai_scope_t *locals, const GBinContent *content, const vmpa2t *pos, phys_t *maxsize, mrange_t *range) { bool result; /* Bilan à retourner */ sized_string_t marker; /* Marqueur potentiel à tester */ vmpa2t iter; /* Tête de lecture courante */ vmpa2t end; /* Fin du parcours possible */ vmpa2t tmp; /* Position à mouvante */ phys_t diff; /* Avancée de lecture courante */ if (attrib->terminator.data == NULL) { init_mrange(range, pos, *maxsize); result = true; } else { result = false; if (attrib->terminator.len > *maxsize) goto exit; /* Recherche du marqueur de fin */ marker.len = attrib->terminator.len; copy_vmpa(&iter, pos); copy_vmpa(&tmp, pos); advance_vmpa(&tmp, *maxsize - marker.len); while (cmp_vmpa_by_phy(&iter, &end) <= 0) { copy_vmpa(&tmp, &iter); marker.data = (char *)g_binary_content_get_raw_access(content, &tmp, marker.len); if (marker.data == NULL) break; if (szmemcmp(&marker, &attrib->terminator) == 0) { result = true; break; } advance_vmpa(&iter, 1); } /* Si la recherche a abouti */ if (result) { diff = compute_vmpa_diff(pos, &iter); if (attrib->include) init_mrange(range, pos, diff + marker.len); else init_mrange(range, pos, diff); assert((diff + marker.len) <= *maxsize); if (attrib->consume) *maxsize = diff + marker.len; else *maxsize = diff; } /* Sinon l'absence de marqueur est-elle tolérée ? */ else if (!attrib->eos_error) { init_mrange(range, pos, *maxsize); result = true; } } exit: return result; } /****************************************************************************** * * * Paramètres : attrib = structure Kaitai en cours de parcours. * * locals = variables locales pour les résolutions de types. * * content = données binaires à analyser et traduire. * * epos = tête de lecture courante. [OUT] * * record = noeud d'arborescence d'éléments rencontrés. [OUT] * * * * Description : Parcourt un contenu binaire selon des spécifications Kaitai. * * * * Retour : Bilan de l'opératon : true pour continuer, false sinon. * * * * Remarques : - * * * ******************************************************************************/ static bool g_kaitai_attribute_parse_content(GKaitaiAttribute *attrib, kaitai_scope_t *locals, GBinContent *content, ext_vmpa_t *epos, GMatchRecord **record) { bool result; /* Bilan à retourner */ resolved_value_t authorized; /* Validation des traitements */ GRecordList *list; /* Constitution d'une liste */ vmpa2t end; /* Position maximale du flux */ phys_t diff; /* Différentiel de positions */ GMatchRecord *child; /* Element de liste à intégrer */ resolved_value_t resolved; /* Valeur entière obtenue */ unsigned long long count; /* Nombre d'itérations à mener */ unsigned long long i; /* Boucle de parcours */ resolved_value_t loop; /* Poursuite des lectures ? */ if (attrib->repetition == KAR_NO_REPETITION) result = _g_kaitai_attribute_parse_content(attrib, locals, content, epos, record); else { /* Lecture soumise à condition ? */ if (attrib->condition != NULL) { result = resolve_kaitai_expression_as_boolean(locals, attrib->condition, strlen(attrib->condition), &authorized); if (!result || !authorized.status) goto exit; } list = g_record_list_new(attrib, content, &epos->base); switch (attrib->repetition) { case KAR_END_OF_STREAM: result = true; g_binary_content_compute_end_pos(content, &end); diff = compute_vmpa_diff(&epos->base, &end); while (diff > 0) { result = _g_kaitai_attribute_parse_content(attrib, locals, content, epos, &child); if (!result) break; g_record_list_add_record(list, child); remember_last_record(locals, child); diff = compute_vmpa_diff(&epos->base, &end); } break; case KAR_EXPRESSION: result = resolve_kaitai_expression_as_integer(locals, attrib->repeat_controller, strlen(attrib->repeat_controller), &resolved); if (resolved.type == GVT_UNSIGNED_INTEGER) count = resolved.unsigned_integer; else { assert(resolved.type == GVT_SIGNED_INTEGER); if (resolved.signed_integer < 0) { result = false; break; } count = resolved.signed_integer; } for (i = 0; i < count; i++) { result = _g_kaitai_attribute_parse_content(attrib, locals, content, epos, &child); if (!result) break; g_record_list_add_record(list, child); remember_last_record(locals, child); } break; case KAR_UNTIL: do { result = _g_kaitai_attribute_parse_content(attrib, locals, content, epos, &child); if (!result) break; g_record_list_add_record(list, child); remember_last_record(locals, child); result = resolve_kaitai_expression_as_boolean(locals, attrib->repeat_controller, strlen(attrib->repeat_controller), &loop); if (!result) break; } while (!loop.status); break; default: break; } if (!result) g_clear_object(&list); *record = G_MATCH_RECORD(list); } exit: return result; }