Pregunta

Estoy buscando una solución para analizar los archivos de especificaciones ASN.1 y generar un decodificador de aquellos.

Tu lugar ideal para trabajar con módulos de Python, pero si no hay nada disponible que usaría C / C ++ y bibliotecas interactuar con Python con la plétora de soluciones por ahí.

En el pasado he estado usando pyasn1 y construir todo a mano, sino que se ha vuelto demasiado difícil de manejar.

También he mirado superficialmente a libtasn1 y asn1c. La primera tenía los problemas al procesar incluso el más simple de archivos. El segundo tiene una buena analizador pero la generación de código C para la decodificación parece demasiado complejo; la solución ha funcionado bien con especificaciones sencillas, pero se atragantó con los complejos.

Las demás buenas alternativas que pueden haber pasado por alto?

¿Fue útil?

Solución

Nunca ellos intentó pero:

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

Otros consejos

escribí tales analizador hace unos años. Genera clases de Python para la biblioteca pyasn1. He utilizado en el doc Ericsson para hacer analizador por sus CDR.

Voy a tratar de 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 del 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)

A continuación, 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 voy a poner el código en algún lugar. De hecho, voy a poner en algún lugar, en todo caso - pero si usted está interesado sólo házmelo saber y te punto no

.

Hay un antlr ASN.1 gramática ; utilizando antlr, usted debería ser capaz de hacer un analizador ASN.1 fuera de él. código para generar pyasn1 se deja como ejercicio para el cartel: -)

Tengo experiencia con pyasn1 y es suficiente con analizar gramáticas bastante complejos. Una gramática se expresa con la estructura de pitón, así que no hay necesidad de correr generador de código.

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

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

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

2 - ya puede analizar los datos binarios, así como texto, por lo que sólo tendría que usar una sola herramienta - el mismo analizador que se usaría para analizar la especificación ASN1 se utilizaría entonces para analizar los datos binarios

Las principales desventajas son que:

1 - es bastante un nuevo paquete, por lo que puede ser buggier que algunos, y la comunidad de apoyo no es tan grande

2 - se limita a Python 2.6 y hasta (y el analizador sintáctico binario sólo funciona con Python 3 y hasta)

.

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

Andrew

PS La razón principal de esto no es algo que ya he empezado es que las especificaciones ASN 1 no son de libre acceso, por lo que yo sé. Si usted tiene acceso a ellos, y no es ilegal (!), Una copia sería muy apreciado (por desgracia, actualmente estoy trabajando en otro proyecto, por lo que este aun así, tener tiempo para poner en práctica, pero me ayudaría a conseguir este trabajo antes ...).

He hecho un trabajo similar usando asn1c y la construcción de alrededor de él una extensión de Pyrex. La estructura de envuelta se describe en 3GPP TS 32.401 .

Con Pyrex se puede escribir un envoltorio suficientemente gruesa como para convertir entre tipos de datos nativos de Python y las representaciones correctas ASN.1 (generadores de envoltura, tales TRAGO, tienden a no realizar operaciones complejas del tipo). La envoltura escribí también seguimiento de la propiedad de las estructuras de datos C subyacentes (por ejemplo, el acceso a una sub-estructura, se devuelve un objeto Python, pero no había ninguna copia de los datos subyacentes, única referencia compartir).

La envoltura finalmente fue escrito en una especie de forma semiautomática, pero debido a que ha sido mi único trabajo con ASN.1 nunca hizo 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 totalmente automática: el trabajo sería menor, pero entonces sería mover la complejidad (y las operaciones propensos a errores repetitivos) a los usuarios de la estructura: por esta razón preferí la forma de Pyrex. asn1c fue sin duda una buena opción.

He creado recientemente el paquete Python llamada asn1tools que compila en una especificación ASN.1 objetos de Python, que pueden ser utilizados 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?'}
Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top