2009-09-29 4 views
10

나는 asn.1 spec 파일을 파싱하고 그것들로부터 디코더를 생성하는 솔루션을 찾고있다.C/Python의 asn.1 파서

이상적으로 저는 파이썬 모듈로 작업하고 싶습니다만, 사용할 수있는 것이 없다면 C/C++ 라이브러리를 사용하고 파이썬과 인터페이스 할 수있는 해결책이 많습니다.

과거에 저는 pyasn1을 사용하여 손으로 모든 것을 만들었지 만, 너무 쉽게 들리지 않게되었습니다.

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

다른 좋은 대안으로 간과했을 수 있습니까?

답변

4

:-) 포스터에 연습 문제로 남겨 생성을 시도하지하지만 결코 :

모두 (당신이 원하는 것을 할 C 보인다 , 파이썬이 아님).

+0

snacc가 나를 위해 레이더 아래로 갔다. 제가 확인하겠습니다. 감사! – elventear

+0

asn1c가 지금까지 최선의 선택이었습니다. – elventear

2

ANTLR ASN.1 grammar; ANTLR을 사용하면 ASN.1 구문 분석기를 만들 수 있어야합니다. pyasn1에 대한 코드가

+0

링크의 인용문 : "문법은 아직 완전하지 않습니다. ANTLR에 대한 경험이 없습니다." – jfs

+0

죽은 링크 now! – monojohnny

1

필자는 asn1c를 사용하여 Pyrex 확장을 사용하여 유사한 작업을 수행했습니다. 래핑 된 구조는 3GPP TS 32.401에 설명되어 있습니다.

Pyrex를 사용하면 원시 Python 데이터 유형과 올바른 ASN.1 표현 (SWIG와 같은 래퍼 생성자)간에 변환 할 수있을만큼 두꺼운 래퍼를 작성할 수있다. 내가 작성한 래퍼는 기본 C 데이터 구조 (예 : 하위 구조에 액세스, Python 객체가 반환되었지만 참조 데이터 만 복사 했음)도 소유권을 추적했습니다.

랩퍼는 결국 일종의 반자동 방식으로 작성되었지만 ASN.1 만 수행 한 유일한 작업 이었기 때문에 코드 생성을 완전히 자동화하는 단계를 수행하지 못했습니다.

다른 Python-C 래퍼를 사용하고 완전 자동 변환을 수행 할 수 있습니다. 작업은 적지 만 복잡성 (반복적 인 오류가 발생하기 쉬운 작업)은 구조 사용자로 이동하게됩니다. 파이렉스 (Pyrex) 방식을 선호했다. asn1c는 확실히 좋은 선택이었습니다.

+0

내가 가지고있는 asn1 정의는 꽤 길며 복잡해 보입니다. asn1c가 생성하는 코드는이 파일에 문제가있는 것 같고 아이디어는 다른 도구를 디버깅 할 필요가 없다는 것입니다. – elventear

2

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

+1

pyasn1의 문제점은 데이터 구조를 수동으로 작성해야한다는 것입니다. 이것은 작은 asn.1 정의에서는 괜찮습니다. 그러나 거대한 것들은 아닙니다. – elventear

2

저는 파이썬으로 작성된 파서 인 LEPL의 저자입니다. 당신이하고 싶은 것은 "TODO"목록에있는 것들 중 하나입니다.

곧이 일을하지 않을 것이다, 그러나 당신이 있기 때문에 솔루션을 구축 LEPL 사용을 고려할 수 :

1 - 그것은

2 (단순한 삶을 만드는) 순수한 파이썬 솔루션 - 그것은 이미 수 텍스트뿐만 아니라 이진 데이터도 구문 분석하므로 단일 도구 만 사용해야합니다. ASN1 사양을 구문 분석하는 데 사용할 동일한 파서가 이진 데이터를 구문 분석하는 데 사용됩니다.

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

1 - 상당히 새로운 패키지이므로 일부 커뮤니티에 비해 버그가 많을 수 있으며 지원 커뮤니티가 그다지 크지 않습니다

2 - 이진 파서는 Python 3 이상에서만 작동합니다). (스택 오버플로 내가 스팸하고 생각하는 것 같이 내가 그에게 직접 링크 할 수 없습니다)

앤드류

특히, 바이너리 분석에 대한 매뉴얼의 관련 섹션을 참조하십시오 -

자세한 내용은 http://www.acooke.org/lepl은 참조하십시오

추신 : 이것은 내가 이미 시작한 것이 아니라는 주된 이유는 ASN 1 스펙이 내가 아는 한 자유롭게 사용할 수 없다는 것입니다. 액세스 권한이 있고 불법이 아닌 경우 (!) 복사가 크게 도움이 될 것입니다 (불행히도 현재 다른 프로젝트에서 작업 중이기 때문에 구현하는 데는 아직 시간이 걸리지 만 더 빨리이 작업을 수행 할 수 있습니다. ...).

+0

LEPL은 확실히 흥미로워 보이지만 단기적으로는 Python 2.4와의 호환성이 필요합니다. – elventear

11

나는 몇 년 전에 그런 파서를 썼다. 그것은 pyasn1 라이브러리를위한 파이썬 클래스를 생성합니다. 나는 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. 당신은 내가 '여전히 관심이 있다면

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

: 그럼, 그것은 아래의 (당신이없는 경우 그냥 건너 뛸) 다음

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) 

인증 된 정의에 prettyprinters의 무리를 부착 코드를 어딘가에 넣을 것입니다. 사실, 어딘가에 넣을 것입니다.하지만 관심이 있다면 알려주면 바로 알려 드리겠습니다.

+4

오픈 소스 프로젝트 (http://packages.python.org/DendroPy/)에서이 기능을 사용하고 싶습니다. 이 코드는 공개 도메인입니까, 아니면 BSD, MIT 등과 같은 비교적 개방 된 오픈 소스 라이선스에 따라 사용 가능합니까? – Jeet

0

나는 최근에 메시지를 인코딩하고 디코딩하는 데 사용할 수있는 Python 객체로 ASN.1 사양을 컴파일하는 asn1tools이라는 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?'}