15

我正在寻找一种解决方案来解析 asn.1 规范文件并从中生成解码器。

理想情况下,我想使用 Python 模块,但如果没有可用的模块,我会使用 C/C++ 库并将它们与 Python 接口,并提供大量的解决方案。

过去我一直在使用 pyasn1 并手动构建所有东西,但这变得太笨拙了。

我也从表面上看了一下 libtasn1 和 asn1c。第一个即使解析最简单的文件也有问题。第二个有一个很好的解析器,但生成用于解码的 C 代码似乎太复杂了;该解决方案适用于简单的规格,但在复杂的规格上却令人窒息。

我可能忽略了任何其他好的选择?

4

7 回答 7

13

几年前我写了这样的解析器。它为 pyasn1 库生成 python 类。我在爱立信文档中使用过为他们的 CDR 制作解析器。

我现在将尝试在此处发布代码。

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]

这将采用一个语法文件,类似于这个:

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

您需要在生成的文件顶部添加这一行:

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

并将结果命名为 defs.py。然后,我在 defs 上附加了一堆漂亮的打印机(如果你没有跳过它)

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)

然后,归结为:

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

如果您仍然感兴趣,我会将代码放在某处。事实上,无论如何我都会把它放在某个地方 - 但如果你有兴趣,请告诉我,我会指出你。

于 2010-06-09T14:20:12.757 回答
6

从未尝试过,但是:

两者似乎都在做你想做的事(C,而不是 Python)。

于 2009-09-29T20:40:49.173 回答
2

有一个ANTLR ASN.1 语法;使用 ANTLR,您应该能够使用它制作 ASN.1 解析器。为 pyasn1 生成代码作为练习留给海报:-)

于 2009-09-29T19:30:35.247 回答
2

我有pyasn1的经验,它足以解析相当复杂的语法。语法是用python结构表示的,所以不需要运行代码生成器。

于 2009-09-30T04:00:19.163 回答
2

我是 LEPL 的作者,这是一个用 Python 编写的解析器,你想做的就是我的“TODO”列表中的一件事。

我不会很快这样做,但您可能会考虑使用 LEPL 来构建您的解决方案,因为:

1 - 这是一个纯 Python 解决方案(让生活更简单)

2 - 它已经可以解析二进制数据和文本,因此您只需要使用一个工具 - 用于解析 ASN1 规范的同一个解析器将用于解析二进制数据

主要缺点是:

1 - 它是一个相当新的包,所以它可能比某些包有问题,而且支持社区不是那么大

2 - 仅限于 Python 2.6 及更高版本(二进制解析器仅适用于 Python 3 及更高版本)。

欲了解更多信息,请参阅http://www.acooke.org/lepl - 特别是,对于二进制解析,请参阅手册的相关部分(我无法直接链接到该部分,因为 Stack Overflow 似乎认为我在发送垃圾邮件)

安德鲁

PS 这不是我已经开始做的事情的主要原因是,据我所知,ASN 1 规范不是免费提供的。如果您可以访问它们,并且它不是非法的(!),将不胜感激(不幸的是,我目前正在从事另一个项目,所以这仍然需要时间来实施,但它会帮助我更快地完成这项工作...)。

于 2009-09-30T12:01:58.270 回答
1

我使用 asn1c 完成了类似的工作,并围绕它构建了一个 Pyrex 扩展。包装结构在3GPP TS 32.401中描述。

使用 Pyrex,您可以编写一个足够厚的包装器,以便在原生 Python 数据类型和正确的 ASN.1 表示之间进行转换(包装器生成器,例如 SWIG,往往不对类型执行复杂的操作)。我编写的包装器还跟踪了底层 C 数据结构的所有权(例如访问子结构,返回一个 Python 对象,但没有底层数据的副本,只有引用共享)。

包装器最终是以一种半自动的方式编写的,但因为这是我在 ASN.1 中唯一的工作,所以我从未做过完全自动化代码生成的步骤。

您可以尝试使用其他 Python-C 包装器并执行完全自动的转换:工作会更少,但是您会将复杂性(和重复的容易出错的操作)转移给结构用户:因此我更喜欢 Pyrex 方式. asn1c 绝对是一个不错的选择。

于 2009-09-29T23:18:32.023 回答
1

我最近创建了名为asn1tools的 Python 包,它将 ASN.1 规范编译为 Python 对象,可用于对消息进行编码和解码。

>>> 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?'}
于 2017-08-27T12:38:45.343 回答