Analyseur asn.1 en C / Python

Je cherche une solution pour parsingr les fichiers de spécifications asn.1 et générer un décodeur à partir de ceux-ci.

Idéalement, j’aimerais travailler avec des modules Python, mais si rien n’est disponible, j’utiliserais les bibliothèques C / C ++ et les interfacerais avec Python avec la pléthore de solutions disponibles.

Dans le passé, j’utilisais pyasn1 et construisais tout à la main, mais c’est devenu trop lourd.

J’ai aussi superficiellement regardé libtasn1 et asn1c. Le premier avait des problèmes pour parsingr même les fichiers les plus simples. Le second a un bon parsingur mais générer du code C pour le décodage semble trop complexe; la solution a bien fonctionné avec des spécifications simples, mais étranglée avec des spécifications complexes.

Toute autre bonne alternative que j’ai peut-être négligée?

Je ne les ai jamais essayées mais:

  • asn1c
  • snacc

Les deux semblent faire ce que vous voulez (C, pas Python).

J’ai écrit un tel parsingur il y a quelques années. Il génère des classes python pour la bibliothèque pyasn1. J’ai utilisé dans ericsson doc pour créer un parsingur syntaxique pour leurs CDR.

Je vais essayer de poster le code ici maintenant.

import sys from pyparsing import * OpenBracket = Regex("[({]").suppress() CloseBracket = Regex("[)}]").suppress() def Enclose(val): return OpenBracket + val + CloseBracket def SetDefType(typekw): def f(a, b, c): c["defType"] = typekw return f def NoDashes(a, b, c): return c[0].replace("-", "_") def DefineTypeDef(typekw, typename, typedef): return typename.addParseAction(SetDefType(typekw)).setResultsName("definitionType") - \ Optional(Enclose(typedef).setResultsName("definition")) SizeConstraintBodyOpt = Word(nums).setResultsName("minSize") - \ Optional(Suppress(Literal("..")) - Word(nums + "n").setResultsName("maxSize")) SizeConstraint = Group(Keyword("SIZE").suppress() - Enclose(SizeConstraintBodyOpt)).setResultsName("sizeConstraint") Constraints = Group(delimitedList(SizeConstraint)).setResultsName("constraints") DefinitionBody = Forward() TagPrefix = Enclose(Word(nums).setResultsName("tagID")) - Keyword("IMPLICIT").setResultsName("tagFormat") OptionalSuffix = Optional(Keyword("OPTIONAL").setResultsName("isOptional")) JunkPrefix = Optional("--F--").suppress() AName = Word(alphanums + "-").setParseAction(NoDashes).setResultsName("name") SingleElement = Group(JunkPrefix - AName - Optional(TagPrefix) - DefinitionBody.setResultsName("typedef") - OptionalSuffix) NamedTypes = Dict(delimitedList(SingleElement)).setResultsName("namedTypes") SetBody = DefineTypeDef("Set", Keyword("SET"), NamedTypes) SequenceBody = DefineTypeDef("Sequence", Keyword("SEQUENCE"), NamedTypes) ChoiceBody = DefineTypeDef("Choice", Keyword("CHOICE"), NamedTypes) SetOfBody = (Keyword("SET") + Optional(SizeConstraint) + Keyword("OF")).setParseAction(SetDefType("SetOf")) + Group(DefinitionBody).setResultsName("typedef") SequenceOfBody = (Keyword("SEQUENCE") + Optional(SizeConstraint) + Keyword("OF")).setParseAction(SetDefType("SequenceOf")) + Group(DefinitionBody).setResultsName("typedef") CustomBody = DefineTypeDef("constructed", Word(alphanums + "-").setParseAction(NoDashes), Constraints) NullBody = DefineTypeDef("Null", Keyword("NULL"), Constraints) OctetSsortingngBody = DefineTypeDef("OctetSsortingng", Regex("OCTET STRING"), Constraints) IA5SsortingngBody = DefineTypeDef("IA5Ssortingng", Keyword("IA5STRING"), Constraints) EnumElement = Group(Word(printables).setResultsName("name") - Enclose(Word(nums).setResultsName("value"))) NamedValues = Dict(delimitedList(EnumElement)).setResultsName("namedValues") EnumBody = DefineTypeDef("Enum", Keyword("ENUMERATED"), NamedValues) BitSsortingngBody = DefineTypeDef("BitSsortingng", Keyword("BIT") + Keyword("STRING"), NamedValues) DefinitionBody << (OctetStringBody | SetOfBody | SetBody | ChoiceBody | SequenceOfBody | SequenceBody | EnumBody | BitStringBody | IA5StringBody | NullBody | CustomBody) Definition = AName - Literal("::=").suppress() - Optional(TagPrefix) - DefinitionBody Definitions = Dict(ZeroOrMore(Group(Definition))) pf = Definitions.parseFile(sys.argv[1]) TypeDeps = {} TypeDefs = {} def SizeConstraintHelper(size): s2 = s1 = size.get("minSize") s2 = size.get("maxSize", s2) try: return("constraint.ValueSizeConstraint(%s, %s)" % (int(s1), int(s2))) except ValueError: pass ConstraintMap = { 'sizeConstraint' : SizeConstraintHelper, } def ConstraintHelper(c): result = [] for key, value in c.items(): r = ConstraintMap[key](value) if r: result.append(r) return result def GenerateConstraints(c, ancestor, element, level=1): result = ConstraintHelper(c) if result: return [ "subtypeSpec = %s" % " + ".join(["%s.subtypeSpec" % ancestor] + result) ] return [] def GenerateNamedValues(definitions, ancestor, element, level=1): result = [ "namedValues = namedval.NamedValues(" ] for kw in definitions: result.append(" ('%s', %s)," % (kw["name"], kw["value"])) result.append(")") return result OptMap = { False: "", True: "Optional", } def GenerateNamedTypesList(definitions, element, level=1): result = [] for val in definitions: name = val["name"] typename = None isOptional = bool(val.get("isOptional")) subtype = [] constraints = val.get("constraints") if constraints: cg = ConstraintHelper(constraints) subtype.append("subtypeSpec=%s" % " + ".join(cg)) tagId = val.get("tagID") if tagId: subtype.append("implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, %s)" % tagId) if subtype: subtype = ".subtype(%s)" % ", ".join(subtype) else: subtype = "" cbody = [] if val["defType"] == "constructed": typename = val["typedef"] element["_d"].append(typename) elif val["defType"] == "Null": typename = "univ.Null" elif val["defType"] == "SequenceOf": typename = "univ.SequenceOf" print val.items() cbody = [ " componentType=%s()" % val["typedef"]["definitionType"] ] elif val["defType"] == "Choice": typename = "univ.Choice" indef = val.get("definition") if indef: cbody = [ " %s" % x for x in GenerateClassDefinition(indef, name, typename, element) ] construct = [ "namedtype.%sNamedType('%s', %s(" % (OptMap[isOptional], name, typename), ")%s)," % subtype ] if not cbody: result.append("%s%s%s" % (" " * level, construct[0], construct[1])) else: result.append(" %s" % construct[0]) result.extend(cbody) result.append(" %s" % construct[1]) return result def GenerateNamedTypes(definitions, ancestor, element, level=1): result = [ "componentType = namedtype.NamedTypes(" ] result.extend(GenerateNamedTypesList(definitions, element)) result.append(")") return result defmap = { 'constraints' : GenerateConstraints, 'namedValues' : GenerateNamedValues, 'namedTypes' : GenerateNamedTypes, } def GenerateClassDefinition(definition, name, ancestor, element, level=1): result = [] for defkey, defval in definition.items(): if defval: fn = defmap.get(defkey) if fn: result.extend(fn(defval, ancestor, element, level)) return [" %s" % x for x in result] def GenerateClass(element, ancestor): name = element["name"] top = "class %s(%s):" % (name, ancestor) definition = element.get("definition") body = [] if definition: body = GenerateClassDefinition(definition, name, ancestor, element) else: typedef = element.get("typedef") if typedef: element["_d"].append(typedef["definitionType"]) body.append(" componentType = %s()" % typedef["definitionType"]) szc = element.get('sizeConstraint') if szc: body.extend(GenerateConstraints({ 'sizeConstraint' : szc }, ancestor, element)) if not body: body.append(" pass") TypeDeps[name] = list(frozenset(element["_d"])) return "\n".join([top] + body) StaticMap = { "Null" : "univ.Null", "Enum" : "univ.Enumerated", "OctetString" : "univ.OctetString", "IA5String" : "char.IA5String", "Set" : "univ.Set", "Sequence" : "univ.Sequence", "Choice" : "univ.Choice", "SetOf" : "univ.SetOf", "BitString" : "univ.BitString", "SequenceOf" : "univ.SequenceOf", } def StaticConstructor(x): x["_d"] = [] if x["defType"] == "constructed": dt = x["definitionType"] x["_d"].append(dt) else: dt = StaticMap[x["defType"]] return GenerateClass(x, dt) for element in pf: TypeDefs[element["name"]] = StaticConstructor(element) while TypeDefs: ready = [ k for k, v in TypeDeps.items() if len(v) == 0 ] if not ready: x = list() for a in TypeDeps.values(): x.extend(a) x = frozenset(x) - frozenset(TypeDeps.keys()) print TypeDefs raise ValueError, sorted(x) for t in ready: for v in TypeDeps.values(): try: v.remove(t) except ValueError: pass del TypeDeps[t] print TypeDefs[t] print print del TypeDefs[t] 

Cela prendra un fichier avec une syntaxe similaire à celle-ci:

 CarrierInfo ::= OCTET STRING (SIZE(2..3)) ChargeAreaCode ::= OCTET STRING (SIZE(3)) ChargeInformation ::= OCTET STRING (SIZE(2..33)) ChargedParty ::= ENUMERATED (chargingOfCallingSubscriber (0), chargingOfCalledSubscriber (1), noCharging (2)) ChargingOrigin ::= OCTET STRING (SIZE(1)) Counter ::= OCTET STRING (SIZE(1..4)) Date ::= OCTET STRING (SIZE(3..4)) 

Vous devrez append cette ligne au-dessus du fichier généré:

 from pyasn1.type import univ, namedtype, namedval, constraint, tag, char 

Et nommez le résultat defs.py. Ensuite, j'ai attaché un groupe de jolies imprimantes aux defs (si vous n'avez pas à le sauter)

 import defs, parsers def rplPrettyOut(self, value): return repr(self.decval(value)) for name in dir(parsers): if (not name.startswith("_")) and hasattr(defs, name): target = getattr(defs, name) target.prettyOut = rplPrettyOut target.decval = getattr(parsers, name) 

Ensuite, c'est à:

  def ParseBlock(self, block): while block and block[0] != '\x00': result, block = pyasn1.codec.ber.decoder.decode(block, asn1Spec=parserimp.defs.CallDataRecord()) yield result 

Si vous êtes toujours intéressé, je mets le code quelque part. En fait, je vais le mettre quelque part de toute façon - mais si cela vous intéresse, faites le moi savoir et je vous indiquerai le chemin.

Il existe une grammaire ANTLR ASN.1 ; En utilisant ANTLR, vous devriez pouvoir en faire un parsingur syntaxique ASN.1. La génération de code pour pyasn1 est laissée comme exercice à l’affiche 🙂

J’ai de l’expérience avec pyasn1 et il suffit d’parsingr des grammaires assez complexes. Une grammaire est exprimée avec une structure python, il n’est donc pas nécessaire d’exécuter le générateur de code.

Je suis l’auteur de LEPL, un parsingur syntaxique écrit en Python, et ce que vous voulez faire est l’une des choses de ma liste “TODO”.

Je ne le ferai pas bientôt, mais vous pourriez envisager d’utiliser LEPL pour construire votre solution pour les raisons suivantes:

1 – c’est une solution pure Python (qui simplifie la vie)

2 – il peut déjà parsingr des données binarys ainsi que du texte, de sorte que vous n’auriez besoin que d’un seul outil – le même parsingur que vous utiliseriez pour parsingr la spécification ASN1 serait ensuite utilisé pour parsingr les données binarys

Les principaux inconvénients sont les suivants:

1 – c’est un paquet assez récent, il est donc peut-être plus gênant que d’autres, et la communauté de support n’est pas très nombreuse

2 – il est limité à Python 2.6 et supérieur (et l’parsingur binary ne fonctionne qu’avec Python 3 et supérieur).

Pour plus d’informations, veuillez consulter http://www.acooke.org/lepl – en particulier, pour une parsing syntaxique binary, voir la section correspondante du manuel (je ne peux pas créer de lien direct avec cela, car Stack Overflow semble penser que je suis un spam)

Andrew

PS La raison principale pour laquelle ce n’est pas quelque chose que j’ai déjà commencé est que les spécifications de l’ASN 1 ne sont pas librement disponibles, pour autant que je sache. Si vous y avez access et que ce n’est pas illégal (!), Une copie serait grandement appréciée (malheureusement, je travaille actuellement sur un autre projet. Il faudra donc encore du temps pour le mettre en œuvre, mais cela m’aiderait à le faire fonctionner plus rapidement. …)

J’ai fait un travail similaire en utilisant asn1c et en construisant autour de lui une extension Pyrex. La structure enveloppée est décrite dans 3GPP TS 32.401 .

Avec Pyrex, vous pouvez écrire un wrapper suffisamment épais pour effectuer la conversion entre les types de données Python natifs et les représentations ASN.1 correctes (les générateurs de wrapper, tels que SWIG, ont tendance à ne pas effectuer d’opérations complexes sur le type). Le wrapper que j’ai écrit suivait également la propriété des structures de données C sous-jacentes (par exemple, l’access à une sous-structure, un object Python avait été renvoyé, mais il n’y avait pas de copie des données sous-jacentes, mais un partage de références).

Le wrapper a finalement été écrit de manière semi-automatique, mais comme cela a été mon seul travail avec ASN.1, je n’ai jamais procédé à l’automatisation complète de la génération de code.

Vous pouvez essayer d’utiliser d’autres wrappers Python-C et d’effectuer une conversion entièrement automatique: le travail serait moins volumineux, mais vous déplaceriez alors la complexité (et les opérations sujettes aux erreurs répétitives) aux utilisateurs de la structure: c’est pourquoi j’ai préféré la méthode Pyrex. . asn1c était vraiment un bon choix.

J’ai récemment créé le paquet Python appelé asn1tools, qui comstack une spécification ASN.1 en objects Python, qui peut être utilisé pour coder et décoder des messages.

 >>> import asn1tools >>> foo = asn1tools.comstack_file('tests/files/foo.asn') >>> encoded = foo.encode('Question', {'id': 1, 'question': 'Is 1+1=3?'}) >>> encoded bytearray(b'0\x0e\x02\x01\x01\x16\x09Is 1+1=3?') >>> foo.decode('Question', encoded) {'id': 1, 'question': 'Is 1+1=3?'}