2009-09-29 15 views
10

私は、asn.1仕様ファイルを解析し、それらからデコーダを生成するソリューションを探しています。C/Pythonのasn.1パーサ

理想的には私はPythonモジュールで作業したいと思いますが、何も利用できない場合は、C/C++ライブラリを使用して、Pythonとそれらのインターフェイスの豊富なソリューションを使用します。

私は過去にpyasn1を使用していましたが、すべてを手作業で構築していましたが、それはあまりにも扱いにくくなっています。

また、私はlibtasn1とasn1cも表面的に見てきました。最初のものは、最も単純なファイルであっても解析に問題がありました。 2番目のパーサは良いパーサを持っていますが、デコードのためにCコードを生成するのは複雑すぎるようです。このソリューションは簡単な仕様ではうまく機能しましたが、複雑なものでは機能しませんでした。

私が見落としている可能性のある他の良い選択肢はありますか?

答えて

4

がそれらを試していないが、決して:-)ポスターへの課題として残されています、Pythonではなく)。

+0

snaccは私のためにレーダーの下に行きました。確認してみるよ。ありがとう! – elventear

+0

asn1cはこれまで最高の選択肢でした。 – elventear

2

ANTLR ASN.1 grammarがあります。 ANTLRを使用すると、ASN.1パーサを作成することができます。

どちらのあなたは(Cやりたいようだ:pyasn1ためのコードを生成することは

+0

リンクからの引用:「文法はまだ完全ではありません。私はANTLRでの経験はありません」 – jfs

+0

今すぐデッドリンク! – 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の経験があり、かなり複雑な文法を解析するだけで十分です。文法はPython構造で表現されているので、コードジェネレータを実行する必要はありません。

+1

pyasn1の問題は、データ構造を手動で書き込む必要があることです。これは、小さなasn.1定義では問題ありません。しかし、巨大なものではありません。 – elventear

2

私はPythonで書かれたパーサであるLEPLの著者です。あなたがやりたいことは、私の "TODO"リストの一つです。

私はすぐにこれを行うことはありませんが、あなたはあなたのソリューションを構築するためにLEPLを使用して検討するかもしれないので:

1 - それは(人生は簡単になる)、純粋なPythonのソリューション

2だ - それができるすでにASN1仕様を解析するのに使用するのと同じパーサーを使用してバイナリデータを解析します。

主な欠点は次のとおりです。

1 - それはかなり新しいパッケージなので、いくつかよりもバグがあり、サポートコミュニティはそれほど大きくはない

2 - これはPython 2.6以降に制限されています(バイナリパーサーはPython 3以降でのみ動作します)。 (スタックオーバーフローが、私はスパムだと思っているようだと、私はそれに直接リンクすることはできません)

アンドリュー

特に、バイナリ解析のためのマニュアルの該当するセクションを参照してください -

詳細については、http://www.acooke.org/leplは参照してください。

PSこれは私がすでに始めたことではない主な理由は、私が知る限り、ASN 1の仕様は自由に利用できないということです。もしあなたがそれらにアクセスして、それが違法ではないなら(!)、コピーは大いに感謝されるでしょう(残念ながら私は現在別のプロジェクトに取り組んでいますので、これはまだ実装に時間がかかるでしょう。 ...)。

+0

LEPLは確かに興味深いようですが、私のニーズの短期的な部分はPython 2.4との互換性です。 – elventear

11

私はこのようなパーサーを数年前に書いています。 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] 

これは、この1に似た構文を持つファイルを、かかります:

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) 

DEFSにprettyprintersの束を添付しましたコードをどこかに置きます。実際には、私はどこかのどこかに置くでしょう - しかし、あなたが興味を持っているなら私に知らせてください、そして私はそこにあなたを指摘します。

+4

オープンソースプロジェクト(http://packages.python.org/DendroPy/)でこれを使用することに興味があります。このコードはパブリックドメインですか、BSD、MITなどの比較的緩やかなオープンソースライセンスの下で利用できますか? – Jeet

0

私は最近、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?'} 
関連する問題