문제

asn.1 사양 파일을 구문 분석하고 해당 파일에서 디코더를 생성하는 솔루션을 찾고 있습니다.

이상적으로는 Python 모듈로 작업하고 싶지만, 사용할 수 있는 것이 없으면 C/C++ 라이브러리를 사용하고 수많은 솔루션이 있는 Python과 인터페이스합니다.

과거에는 pyasn1을 사용하고 모든 것을 손으로 구축했지만 너무 다루기 어려워졌습니다.

나는 또한 libtasn1과 asn1c를 표면적으로 살펴보았습니다.첫 번째는 가장 간단한 파일조차도 구문 분석하는 데 문제가 있었습니다.두 번째에는 좋은 파서가 있지만 디코딩을 위한 C 코드를 생성하는 것은 너무 복잡해 보입니다.이 솔루션은 간단한 사양에서는 잘 작동했지만 복잡한 사양에서는 제대로 작동하지 않았습니다.

내가 간과했을 수 있는 다른 좋은 대안이 있습니까?

도움이 되었습니까?

해결책

절대 시도하지는 않지만 :

둘 다 당신이 원하는 것을하는 것 같습니다 (C, Python이 아닌).

다른 팁

나는 몇 년 전에 그런 파서를 썼습니다. PYASN1 라이브러리 용 Python 클래스를 생성합니다. 나는 Ericsson Doc에서 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

여전히 관심이 있다면 코드를 어딘가에 넣을 것입니다. 사실, 나는 어딘가에 어딘가에 넣을 것입니다. 그러나 관심이 있으시면 저에게 알려 주시면 당신을 지적 할 것입니다.

있습니다 antlr asn.1 문법; Antlr을 사용하면 ASN.1 파서를 만들 수 있어야합니다. pyasn1에 대한 코드 생성 코드 생성은 포스터의 연습으로 남겨 둡니다 :-)

나는 경험이 있습니다 pyasn1 꽤 복잡한 문법을 ​​분석하는 것만으로도 충분합니다.문법은 파이썬 구조로 표현되므로 코드 생성기를 실행할 필요가 없습니다.

저는 Python으로 작성된 파서 인 Lepl의 저자이며, 당신이하고 싶은 것은 내 "todo"목록의 것들 중 하나입니다.

곧이 작업을 수행하지는 않지만 LEPL을 사용하여 솔루션을 구성하는 것을 고려할 수 있습니다.

1- 순수한 파이썬 솔루션입니다 (삶을 더 간단하게 만듭니다)

2- 이미 바이너리 데이터와 텍스트를 구문 분석 할 수 있으므로 단일 도구 만 사용하면됩니다. ASN1 사양을 구문 분석하는 데 사용하는 것과 동일한 소포는 이진 데이터를 구문 분석하는 데 사용됩니다.

주요 단점은 다음과 같습니다.

1- 상당히 새로운 패키지이므로 일부보다 더 멍청 할 수 있으며 지원 커뮤니티는 그다지 크지 않습니다.

2- 파이썬 2.6 이상으로 제한됩니다 (이진 파서는 Python 3 이상에서만 작동합니다).

자세한 내용은 참조하십시오 http://www.acooke.org/lepl - 이진 구문 분석의 경우 매뉴얼의 관련 섹션을 참조하십시오 (스택 오버플로가 스팸을하고 있다고 생각하는 것처럼 보이기 때문에 직접 연결할 수 없습니다).

앤드류

추신 : 이것이 내가 이미 시작한 것이 아닌 주된 이유는 내가 아는 한 ASN 1 사양을 자유롭게 사용할 수 없기 때문입니다. 당신이 그들에게 액세스 할 수 있고 불법이 아닌 경우 (!), 사본은 크게 감사하겠습니다 (불행히도 나는 현재 다른 프로젝트를 진행하고 있기 때문에 여전히 구현하는 데 시간이 걸릴 것이지만 더 빨리 작동하는 데 도움이 될 것입니다. ...).

나는 ASN1C를 사용하여 비슷한 작업을 수행하고 그 주위에 Pyrex 확장을 만들었습니다. 래핑 된 구조는 다음에 설명되어 있습니다 3GPP TS 32.401.

Pyrex를 사용하면 기본 Python 데이터 유형과 올바른 ASN을 변환 할 수있을 정도로 두껍게 랩퍼를 작성할 수 있습니다. 내가 쓴 래퍼는 또한 기본 C 데이터 구조의 소유권을 추적했습니다 (예 : 하위 구조에 대한 액세스, 파이썬 객체에 대한 액세스가 반환되었지만 기본 데이터의 사본은 없었으며 참조 공유 만 없었습니다).

래퍼는 결국 일종의 반자동 방식으로 쓰여졌지만 ASN과의 유일한 일 이었기 때문에 코드 생성을 완전히 자동화하는 단계는 없었습니다.

다른 Python-C 래퍼를 사용하고 완전히 자동 변환을 수행하려고 시도 할 수 있습니다. 작업은 적지 만 복잡성 (및 반복적 인 오류가 발생하기 쉬운 작업)을 구조 사용자로 이동시킬 수 있습니다.이 이유로 Pyrex Way를 선호했습니다. . ASN1C는 확실히 좋은 선택이었습니다.

최근에 호출 된 파이썬 패키지를 만들었습니다 asn1tools 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?'}
라이센스 : CC-BY-SA ~와 함께 속성
제휴하지 않습니다 StackOverflow
scroll top