[英]asn.1 parser in C/Python
我正在尋找一個解析asn.1規范文件並從中生成解碼器的解決方案。
理想情況下,我想使用Python模塊,但如果沒有可用的話,我會使用C / C ++庫並將它們與Python接口,並提供大量的解決方案。
在過去,我一直在使用pyasn1並手工構建所有東西,但這已經變得過於笨拙。
我對libtasn1和asn1c看起來也很表面。 第一個解析甚至最簡單的文件都有問題。 第二個有一個很好的解析器但是生成用於解碼的C代碼似乎太復雜了; 該解決方案適用於簡單的規范,但在復雜的規則上窒息。
我可能忽略了其他任何好的選擇嗎?
幾年前我寫過這樣的解析器。 它為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的作者,一個用Python編寫的解析器,你想要做的就是我的“TODO”列表中的一個東西。
我不會很快這樣做,但你可能會考慮使用LEPL來構建你的解決方案,因為:
1 - 這是一個純Python解決方案(讓生活更簡單)
2 - 它已經可以解析二進制數據和文本,因此您只需要使用一個工具 - 您將用於解析ASN1規范的相同解析器將用於解析二進制數據
主要缺點是:
1 - 這是一個相當新的包,所以它可能比一些人更吵,而且支持社區並不那么大
2 - 它僅限於Python 2.6及更高版本(二進制解析器僅適用於Python 3及更高版本)。
有關詳細信息,請參閱 http://www.acooke.org/lepl - 特別是,對於二進制解析,請參閱手冊的相關部分(我無法直接鏈接到那個,因為Stack Overflow似乎認為我是垃圾郵件)
安德魯
PS這不是我已經開始的主要原因是ASN 1規格不是免費提供的,據我所知。 如果你有權訪問它們,並且它不是非法的(!),那么副本將不勝感激(不幸的是我目前正在開發另一個項目,所以這仍然需要時間來實現,但它可以幫助我更快地完成這項工作...)。
我使用asn1c做了類似的工作,並圍繞它構建Pyrex擴展。 包裹的結構在3GPP TS 32.401中描述。
使用Pyrex,您可以編寫一個足夠厚的包裝器,以便在本機Python數據類型和正確的ASN.1表示之間進行轉換(包裝器生成器,例如SWIG,往往不會對該類型執行復雜的操作)。 我寫的包裝器還跟蹤了底層C數據結構的所有權(例如,訪問子結構,返回了一個Python對象,但沒有底層數據的副本,只有參考共享)。
包裝器最終以一種半自動的方式編寫,但因為這是我在ASN.1中唯一的工作,所以我從來沒有完全自動化代碼生成的步驟。
您可以嘗試使用其他Python-C包裝器並執行完全自動的轉換:作業會更少,但是您會將復雜性(以及重復的容易出錯的操作)移動到結構用戶:因此我更喜歡Pyrex方式。 asn1c絕對是個不錯的選擇。
我最近創建了名為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?'}
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.