2009-09-29 20 views
10

Estoy buscando una solución para analizar archivos de especificaciones asn.1 y generar un decodificador a partir de ellos.analizador asn.1 en C/Python

Idealmente, me gustaría trabajar con módulos de Python, pero si no hay nada disponible, usaría las bibliotecas C/C++ y las conectaría con Python con la gran cantidad de soluciones disponibles.

En el pasado he estado usando pyasn1 y construyendo todo a mano, pero eso se ha vuelto demasiado difícil de manejar.

También he mirado superficialmente a libtasn1 y asn1c. El primero tenía problemas para analizar incluso los archivos más simples. El segundo tiene un buen analizador, pero la generación de código C para la decodificación parece demasiado compleja; la solución funcionó bien con especificaciones sencillas pero se atragantó con las complejas.

¿Alguna otra buena alternativa que pueda haber pasado por alto?

Respuesta

4

Nunca les intentado, pero:

Ambos parece hacer lo que quiere (C , no Python).

+0

snacc se me pasó por el radar. Lo revisaré. ¡Gracias! – elventear

+0

asn1c ha sido la mejor opción hasta ahora. – elventear

2

Hay un ANTLR ASN.1 grammar; usando ANTLR, debería poder hacer un analizador ASN.1. La generación de código para pyasn1 se deja como ejercicio para el cartel :-)

+0

La cita del enlace: "La gramática es por lejos incompleta. No tengo experiencia en ANTLR". – jfs

+0

enlace muerto ahora! – monojohnny

1

He hecho un trabajo similar usando asn1c y construyendo a su alrededor una extensión Pyrex. La estructura envuelta se describe en 3GPP TS 32.401.

Con Pyrex puede escribir un envoltorio lo suficientemente grueso como para convertir entre tipos de datos nativos de Python y las representaciones ASN.1 correctas (los generadores de envoltura, tales como SWIG, tienden a no realizar operaciones complejas en el tipo). El contenedor que escribí también rastreó la propiedad de las estructuras de datos C subyacentes (por ejemplo, al acceder a una subestructura, se devolvió un objeto Python, pero no hubo copia de los datos subyacentes, solo se compartió la referencia).

La envoltura se escribió finalmente de una manera semiautomática, pero como ese ha sido mi único trabajo con ASN.1, nunca hice el paso de automatizar por completo la generación de código.

Puede intentar utilizar otras envolturas de Python-C y realizar una conversión completamente automática: el trabajo sería menor, pero luego trasladaría la complejidad (y operaciones repetitivas propensas a errores) a los usuarios de la estructura: por esta razón, preferido el camino Pyrex. asn1c definitivamente fue una buena elección.

+0

La definición de asn1 que tengo es bastante larga y parece compleja. El código que genera asn1c parece tener problemas con este archivo y la idea es no tener que depurar otra herramienta. – elventear

2

Tengo experiencia con pyasn1 y es suficiente para analizar gramáticas bastante complejas. Una gramática se expresa con estructura python, por lo que no es necesario ejecutar el generador de código.

+1

El problema con pyasn1 es que las estructuras de datos deben escribirse manualmente. Esto está bien para pequeñas definiciones asn.1. Pero no para los grandes. – elventear

2

Soy el autor de LEPL, un analizador sintáctico escrito en Python, y lo que quiero hacer es una de las cosas en mi lista de tareas "TODO".

no voy a estar haciendo esto antes, pero es posible considerar el uso de LEPL para construir su solución porque:

1 - es una solución Python puro (que hace la vida más simple)

2 - ya puede analizar datos binarios y texto, por lo que solo necesitaría usar una sola herramienta; el mismo analizador que usaría para analizar la especificación ASN1 se usaría para analizar los datos binarios

Los inconvenientes principales son los siguientes:

1 - es un paquete bastante nuevo, por lo que puede ser más complicado que algunos, y la comunidad de soporte no es tan grande

2 - está restringido a Python 2.6 y superior (y el analizador binario solo funciona con Python 3 y superior)

Para obtener más información, consulte http://www.acooke.org/lepl - en particular, para el análisis binario ver la sección correspondiente del manual (no puedo enlazar directamente a que, como desbordamiento de pila parece pensar que estoy Spam a)

Andrew

PD La razón principal por la que esto no es algo que ya he comenzado es que las especificaciones de ASN 1 no están disponibles libremente, hasta donde yo sé. Si tiene acceso a ellos, y no es ilegal (!), Una copia sería muy apreciada (desafortunadamente, actualmente estoy trabajando en otro proyecto, por lo que aún tomaría tiempo implementarlo, pero me ayudaría a que esto funcione antes). ...).

+0

LEPL parece ciertamente interesante, pero a corto plazo parte de mis necesidades son la compatibilidad con Python 2.4. – elventear

11

Escribí tal analizador hace algunos años. Genera clases de python para la biblioteca pyasn1. Utilicé el documento ericsson para hacer un analizador para sus CDR.

Intentaré publicar el código aquí ahora.

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) 

OctetStringBody = DefineTypeDef("OctetString", Regex("OCTET STRING"), Constraints) 
IA5StringBody = DefineTypeDef("IA5String", 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) 

BitStringBody = DefineTypeDef("BitString", 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] 

Esto tomará un archivo con una sintaxis similar a la siguiente:

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)) 

Usted tendrá que añadir esta línea en la parte superior del archivo generado:

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

Y el nombre el resultado defs.py. A continuación, os adjunto un montón de prettyprinters a los defs (si usted no tiene la omitida)

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) 

Entonces, hay que bajar a:

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 usted todavía está interesado I' Pondré el código en alguna parte. De hecho, lo pondré en algún lugar, en cualquier caso, pero si estás interesado solo házmelo saber y te señalaré allí.

+4

Me podría interesar usar esto en un proyecto de código abierto (http://packages.python.org/DendroPy/). ¿Es este código de dominio público o está disponible bajo una licencia de fuente abierta relativamente relajada como BSD, MIT, etc.? – Jeet

0

Recientemente creé el paquete de Python llamado asn1tools que compila una especificación ASN.1 en objetos de Python, que se puede usar para codificar y decodificar mensajes.

>>> import asn1tools 
>>> foo = asn1tools.compile_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?'} 
Cuestiones relacionadas