pycrate/pycrate_asn1c/generator.py

1096 lines
45 KiB
Python

# -*- coding: UTF-8 -*-
#/**
# * Software Name : pycrate
# * Version : 0.4
# *
# * Copyright 2016. Benoit Michau. ANSSI.
# *
# * This library is free software; you can redistribute it and/or
# * modify it under the terms of the GNU Lesser General Public
# * License as published by the Free Software Foundation; either
# * version 2.1 of the License, or (at your option) any later version.
# *
# * This library is distributed in the hope that it will be useful,
# * but WITHOUT ANY WARRANTY; without even the implied warranty of
# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# * Lesser General Public License for more details.
# *
# * You should have received a copy of the GNU Lesser General Public
# * License along with this library; if not, write to the Free Software
# * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
# * MA 02110-1301 USA
# *
# *--------------------------------------------------------
# * File Name : pycrate_asn1c/gen_pycrate_asn1rt.py
# * Created : 2016-12-21
# * Authors : Benoit Michau
# *--------------------------------------------------------
#*/
import copy
from .utils import *
from .glob import *
from .setobj import *
from .refobj import *
from .asnobj import get_asnobj, ASN1Obj, INT, OID
class _Generator(object):
def __init__(self, dest='/tmp/dst.txt'):
self.dest = dest
self.fd = open(self.dest, 'w')
self.indent = 0
self.gen()
self.fd.close()
def wrl(self, s):
self.fd.write('{0}{1}\n'.format(self.indent * ' ', s))
def gen(self):
pass
#------------------------------------------------------------------------------#
# Python source code generator
#------------------------------------------------------------------------------#
# generate Python source code for the ASN1 runtime of pycrate
# (located in pycrate_asn1rt/ directory)
_mode_lut = {
'TYPE' : 'MODE_TYPE',
'SET' : 'MODE_SET',
'VALUE' : 'MODE_VALUE'
}
_tag_lut = {
'IMPLICIT' : 'TAG_IMPLICIT',
'EXPLICIT' : 'TAG_EXPLICIT',
'CONTEXT-SPECIFIC' : 'TAG_CONTEXT_SPEC',
'PRIVATE' : 'TAG_PRIVATE',
'APPLICATION' : 'TAG_APPLICATION',
'UNIVERSAL' : 'TAG_UNIVERSAL',
'AUTOMATIC' : 'TAG_AUTOMATIC'
}
_type_lut = {
'NULL' : 'TYPE_NULL',
'BOOLEAN' : 'TYPE_BOOL',
'INTEGER' : 'TYPE_INT',
'REAL' : 'TYPE_REAL',
'ENUMERATED' : 'TYPE_ENUM',
'BIT STRING' : 'TYPE_BIT_STR',
'OCTET STRING' : 'TYPE_OCT_STR',
'OBJECT IDENTIFIER' : 'TYPE_OID',
'REALTIVE-OID' : 'TYPE_REL_OID',
#
'IA5String' : 'TYPE_STR_IA5',
'PrintableString' : 'TYPE_STR_PRINT',
'NumericString' : 'TYPE_STR_NUM',
'VisibleString' : 'TYPE_STR_VIS',
'BMPString' : 'TYPE_STR_BMP',
'UTF8String' : 'TYPE_STR_UTF8',
'ISO646String' : 'TYPE_STR_ISO646',
'TeletexString' : 'TYPE_STR_TELE',
'VideotexString' : 'TYPE_STR_VID',
'GraphicString' : 'TYPE_STR_GRAPH',
'T61String' : 'TYPE_STR_T61',
'GeneralString' : 'TYPE_STR_GENE',
'UniversalString' : 'TYPE_STR_UNIV',
'ObjectDescriptor' : 'TYPE_OBJ_DESC',
#
'GeneralizedTime' : 'TYPE_TIME_GEN',
'UTCTime' : 'TYPE_TIME_UTC',
#
'CHOICE' : 'TYPE_CHOICE',
'SEQUENCE' : 'TYPE_SEQ',
'SEQUENCE OF' : 'TYPE_SEQ_OF',
'SET' : 'TYPE_SET',
'SET OF' : 'TYPE_SET_OF',
#
'OPEN_TYPE' : 'TYPE_OPEN',
'ANY' : 'TYPE_OPEN',
'EXTERNAL' : 'TYPE_EXT',
'EMBEDDED PDV' : 'TYPE_EMB_PDV',
'CHARACTER STRING' : 'TYPE_CHAR_STR',
#
'CLASS' : 'TYPE_CLASS',
'INSTANCE OF' : 'TYPE_INSTOF'
}
def value_to_defin(v, Obj=None, Gen=None, ind=None):
if Obj.TYPE == TYPE_NULL:
# '0'
return '0'
elif Obj.TYPE == TYPE_BOOL:
# bool
return repr(v)
elif Obj.TYPE == TYPE_INT:
# int (/long)
rv = repr(v)
if rv[-1] == 'L':
# python2 useless long notation
return rv[:-1]
else:
return rv
elif Obj.TYPE == TYPE_REAL:
# list of 3 int -> convert to 3-tuple
mant, ex = repr(v[0]), repr(v[2])
if mant[-1] == 'L':
# again, python2 useless long notation
mant = mant[:-1]
if ex[-1] == 'L':
ex = ex[:-1]
return '(%s, %r, %s)' % (mant, v[1], ex)
elif Obj.TYPE == TYPE_ENUM:
# string
return repr(v)
elif Obj.TYPE == TYPE_BIT_STR:
# list of 2 int -> convert to tuple
vv, vl = repr(v[0]), repr(v[1])
if vv[-1] == 'L':
vv = vv[:-1]
if vl[-1] == 'L':
vl = vl[:-1]
return '(%s, %s)' % (vv, vl)
elif Obj.TYPE == TYPE_OCT_STR:
# byte-string
if python_version > 2:
return repr(v)
else:
return 'b%s' % repr(v)
elif Obj.TYPE == TYPE_OID:
# list of int -> convert to tuple
return repr(tuple(v))
elif Obj.TYPE == TYPE_REL_OID:
# list of int -> convert to tuple
return repr(tuple(v))
elif Obj.TYPE in TYPE_STRINGS:
# char string
return repr(v)
elif Obj.TYPE in (TYPE_TIME_UTC, TYPE_TIME_GEN):
# tuple of {int, None}
return repr(v)
elif Obj.TYPE == TYPE_CHOICE:
# list of name and single value -> convert to tuple
return '(' + repr(v[0]) + ', ' + value_to_defin(v[1], Obj.get_cont()[v[0]], Gen) + ')'
elif Obj.TYPE in (TYPE_SEQ_OF, TYPE_SET_OF):
# list of single value
return '[' + ', '.join([value_to_defin(i, Obj.get_cont(), Gen) for i in v]) + ']'
elif Obj.TYPE in (TYPE_SEQ, TYPE_SET):
# Python dict of {name: single value}
#return 'ASN1Dict([' + \
return 'dict([' + \
', '.join(['({0}, {1})'.format(repr(name), value_to_defin(val, Obj.get_cont()[name], Gen)) \
for (name, val) in v.items()]) + \
'])'
elif Obj.TYPE == TYPE_CLASS:
# Python dict of {name: single value or set of values or type object}
r = []
for (name, value) in v.items():
ObjVal = Obj.get_cont()[name]
if not hasattr(ObjVal, '_pyname'):
ObjVal._pyname = '_{0}_{1}'.format(Obj._pyname, name_to_defin(ObjVal._name))
if ObjVal._mode == MODE_VALUE:
if ObjVal.TYPE == TYPE_OPEN and isinstance(ObjVal._typeref, ASN1RefClassIntern):
# get the typeref object defined in v, and use it to define the ObjVal value
ObjValTr = v[ObjVal._typeref.ced_path[0]]
for p in ObjVal._typeref.ced_path[1:]:
ObjValTr = ObjValTr.get_cont()[p]
r.append('({0}, {1})'.format(repr(name), value_to_defin(value, ObjValTr, Gen)))
else:
r.append('({0}, {1})'.format(repr(name), value_to_defin(value, ObjVal, Gen)))
elif ObjVal._mode == MODE_SET:
if ObjVal.TYPE == TYPE_OPEN and isinstance(ObjVal._typeref, ASN1RefClassIntern):
# get the typeref object defined in v, and use it to define the ObjVal value
ObjValTr = v[ObjVal._typeref.ced_path[0]]
for p in ObjVal._typeref.ced_path[1:]:
ObjValTr = ObjValTr.get_cont()[p]
r.append('({0}, {1})'.format(repr(name), set_to_defin(value, ObjValTr, Gen)))
else:
r.append('({0}, {1})'.format(repr(name), set_to_defin(ASN1Set(value), ObjVal, Gen)))
else:
#ObjVal._mode == MODE_TYPE
# value is an ASN1 object, create it first
# WNG: no name unicity for value, use ind for this purpose
if ind is not None:
value._pyname = '_{0}_val_{1}_{2!r}'.format(Obj._pyname, name_to_defin(value._name), ind)
else:
value._pyname = '_{0}_val_{1}'.format(Obj._pyname, name_to_defin(value._name))
Gen.gen_type(value, compts=False)
# object to be linked in the ASN1Dict value
r.append('({0}, {1})'.format(repr(name), value._pyname))
#return 'ASN1Dict([' + ', '.join(r) + '])'
return 'dict([' + ', '.join(r) + '])'
elif Obj.TYPE in (TYPE_OPEN, TYPE_ANY):
# list with [object definition, single value]
# changing it to a 2-tuple
v[0]._pyname = '_{0}_val'.format(name_to_defin(v[0]._name))
Gen.gen_type(v[0], compts=False)
return '({0}, {1})'.format(v[0]._pyname, value_to_defin(v[1], v[0], Gen))
elif Obj.TYPE == TYPE_EXT:
# TODO
assert()
elif Obj.TYPE == TYPE_EMB_PDV:
# TODO
assert()
elif Obj.TYPE == TYPE_CHAR_STR:
# TODO
assert()
else:
assert()
def range_to_defin(r, Obj=None):
# ASN1Range only applied to TYPE_INT, TYPE_REAL and TYPE_STR_*
if Obj.TYPE == TYPE_INT:
return 'ASN1RangeInt(lb={0}, ub={1})'\
.format(value_to_defin(r.lb, Obj), value_to_defin(r.ub, Obj))
elif Obj.TYPE == TYPE_REAL:
return 'ASN1RangeReal(lb={0}, ub={1}, lb_incl={2!r}, ub_incl={3!r})'\
.format(value_to_defin(r.lb, Obj), value_to_defin(r.ub, Obj), r.lb_incl, r.ub_incl)
elif Obj.TYPE in ASN1Range._TYPE_STR:
return 'ASN1RangeStr(lb={0!r}, ub={1!r})'.format(r.lb, r.ub)
else:
assert()
def set_to_defin(S, Obj=None, Gen=None):
# ASN1Set(rv, rr, ev, er)
# ind: value index, required especially for distinguishing sets of CLASS values
ind = 0
# root part
rv, rr = [], []
for v in S._rv:
rv.append( value_to_defin(v, Obj, Gen, ind) )
ind += 1
rv = '[' + ', '.join(rv) + ']'
for vr in S._rr:
rr.append( range_to_defin(vr, Obj) )
rr = '[' + ', '.join(rr) + ']'
# extension part
if S._ev is None:
ev, er = 'None', '[]'
else:
ev, er = [], []
for v in S._ev:
ev.append( value_to_defin(v, Obj, Gen, ind) )
ind += 1
ev = '[' + ', '.join(ev) + ']'
for vr in S._er:
er.append( range_to_defin(vr, Obj) )
er = '[' + ', '.join(er) + ']'
#
return 'ASN1Set(rv={0}, rr={1}, ev={2}, er={3})'.format(rv, rr, ev, er)
def tag_to_defin(t):
return '({0!r}, {1}, {2})'.format(t[0], _tag_lut[t[1]], _tag_lut[t[2]])
def typeref_to_defin(Obj):
if isinstance(Obj._typeref, ASN1RefClassIntern):
return 'ASN1RefClassIntern(None, {0!r})'.format(Obj._typeref.ced_path)
elif hasattr(Obj._typeref, 'called') and \
Obj._typeref.called[1] in ('TYPE-IDENTIFIER', 'ABSTRACT-SYNTAX'):
# special process for those types which are injected in all modules by proc.py
return '{0}({1!r}, {2!r})'.format(Obj._typeref.__class__.__name__,
Obj._typeref.called,
Obj._typeref.ced_path)
# otherwise, we ensure to provide the correct module name (and not the imported one,
# which be misleading due to IMPORTS chain)
else:
(modname, objname) = Obj._typeref.called
while objname in GLOBAL.MOD[modname]['_imp_']:
modname = GLOBAL.MOD[modname]['_imp_'][objname]
if isinstance(Obj._typeref, ASN1RefType):
return 'ASN1RefType((\'{0}\', \'{1}\'))'.format(modname, objname)
elif isinstance(Obj._typeref, ASN1RefClassField):
return 'ASN1RefClassField((\'{0}\', \'{1}\'), {2!r})'\
.format(modname, objname, Obj._typeref.ced_path)
elif isinstance(Obj._typeref, ASN1RefClassValField):
return 'ASN1RefClassValField((\'{0}\', \'{1}\'), {2!r})'\
.format(modname, objname, Obj._typeref.ced_path)
elif isinstance(Obj._typeref, ASN1RefChoiceComp):
return 'ASN1RefChoiceComp((\'{0}\', \'{1}\'), {2!r})'\
.format(modname, objname, Obj._typeref.ced_path)
elif isinstance(Obj._typeref, ASN1RefInstOf):
return 'ASN1RefInstOf((\'{0}\', \'{1}\'))'.format(modname, objname)
else:
assert()
class PycrateGenerator(_Generator):
"""
PycrateGenerator generates Python source code to be loaded into the pycrate
ASN.1 runtime, located in pycrate_asn1rt
"""
_impl = 0
def gen(self):
#
self.wrl('# -*- coding: UTF-8 -*-')
self.wrl('# Code automatically generated by pycrate_asn1c')
self.wrl('')
self.wrl('from pycrate_asn1rt.utils import *')
self.wrl('from pycrate_asn1rt.err import *')
self.wrl('from pycrate_asn1rt.glob import make_GLOBAL, GLOBAL')
self.wrl('from pycrate_asn1rt.dictobj import ASN1Dict')
self.wrl('from pycrate_asn1rt.refobj import *')
self.wrl('from pycrate_asn1rt.setobj import *')
self.wrl('from pycrate_asn1rt.asnobj_basic import *')
self.wrl('from pycrate_asn1rt.asnobj_str import *')
self.wrl('from pycrate_asn1rt.asnobj_construct import *')
self.wrl('from pycrate_asn1rt.asnobj_class import *')
self.wrl('from pycrate_asn1rt.asnobj_ext import *')
self.wrl('from pycrate_asn1rt.init import init_modules')
self.wrl('')
#
modlist = []
#
for mod_name in [mn for mn in GLOBAL.MOD if mn[:1] != '_']:
self._mod_name = mod_name
Mod = GLOBAL.MOD[mod_name]
pymodname = name_to_defin(mod_name)
#
self.wrl('class {0}:\n'.format(pymodname))
self.indent = 4
#
self.wrl('_name_ = {0!r}'.format(Mod['_name_']))
self.wrl('_oid_ = {0!r}'.format(Mod['_oid_']))
#self.wrl('_tag_ = {0}'.format(_tag_lut[Mod['_tag_']]))
self.wrl('')
for attr in ('_obj_', '_type_', '_set_', '_val_', '_class_', '_param_'):
self.wrl('{0} = ['.format(attr))
self.indent += 4
for name in Mod[attr]:
self.wrl('{0},'.format(repr(name)))
self.wrl(']')
self.indent -= 4
self.wrl('')
#
self._all_ = []
self._allobj_ = {}
self.gen_mod(GLOBAL.MOD[mod_name])
self.wrl('_all_ = [')
for pyobjname in self._all_:
self.wrl(' {0},'.format(pyobjname))
self.wrl(']')
modlist.append(pymodname)
#
self.indent = 0
self.wrl('')
#
# create the _IMPL_ class if required
if self._impl:
self.wrl('class _IMPL_:\n')
self.indent = 4
#
self.wrl('_name_ = \'_IMPL_\'')
self.wrl('_oid_ = []')
self.wrl('_obj_ = {0!r}'.format(GLOBAL.MOD['_IMPL_']['_obj_']))
self.wrl('')
#
self._all_ = []
self._allobj_ = {}
self.gen_mod(GLOBAL.MOD['_IMPL_'])
self.wrl('_all_ = [')
for pyobjname in self._all_:
self.wrl(' {0},'.format(pyobjname))
self.wrl(']')
modlist.append('_IMPL_')
#
self.indent = 0
self.wrl('')
#
self.wrl('init_modules(' + ', '.join(modlist) + ')')
def gen_mod(self, Mod):
obj_names = [obj_name for obj_name in Mod.keys() if obj_name[0:1] != '_']
for obj_name in obj_names:
Obj = Mod[obj_name]
self.wrl('#-----< {0} >-----#'.format(Obj._name))
if Obj._mode == MODE_TYPE:
self.gen_type(Obj)
elif Obj._mode == MODE_SET:
self.gen_set(Obj)
elif Obj._mode == MODE_VALUE:
self.gen_val(Obj)
# delete potential table constraints caches
if hasattr(self, '_const_tabs'):
del self._const_tabs
self.wrl('')
def _handle_dup(self, Obj):
if Obj._pyname in self._all_:
# a similar object was already generated (this is mainly due to a
# certain usage of parameterization)
ObjDef = self._allobj_[Obj._pyname]
if Obj == ObjDef or \
Obj._name == ObjDef._name and Obj._type == ObjDef._type and \
Obj._mode == ObjDef._mode and Obj._typeref == ObjDef._typeref and \
Obj._cont == ObjDef._cont and Obj._const == ObjDef._const and \
Obj._tag == ObjDef._tag and Obj._flag == ObjDef._flag and \
Obj._val == ObjDef._val:
# new object seems broadly equal to the already defined one
return True
else:
# change _pyname attribute, until an unused _pyname is found
ext = 0
Obj._pyname = Obj._pyname + '_{0!r}'.format(ext)
while Obj._pyname in self._all_:
ObjDef = self._allobj_[Obj._pyname]
if Obj == ObjDef or \
Obj._name == ObjDef._name and Obj._type == ObjDef._type and \
Obj._mode == ObjDef._mode and Obj._typeref == ObjDef._typeref and \
Obj._cont == ObjDef._cont and Obj._const == ObjDef._const and \
Obj._tag == ObjDef._tag and Obj._flag == ObjDef._flag and \
Obj._val == ObjDef._val:
return True
else:
ext += 1
Obj._pyname = Obj._pyname[:-2] + '_{0!r}'.format(ext)
return False
else:
return False
def gen_type(self, Obj, compts=False):
#
# 1) create a python-compliant name if not already done
if not hasattr(Obj, '_pyname'):
Obj._pyname = name_to_defin(Obj._name)
#
# 2) check to not duplicate object
if self._handle_dup(Obj):
return
#
# 3) early process INSTANCE OF, as it translates to a special
# SEQUENCE object
if Obj.TYPE == TYPE_INSTOF:
InstSeq = Obj.get_typeref()
if hasattr(Obj, '_pyname'):
InstSeq._pyname = Obj._pyname
self.gen_type(InstSeq)
if Obj._const:
# TODO
assert()
self.wrl('{0}.TAG = 8'.format(InstSeq._pyname))
self._all_.append(InstSeq._pyname)
self._allobj_[InstSeq._pyname] = InstSeq
return
#
# 3bis) in case of parameterized object with no referrers
# need to consider it as a standard object
if Obj._param and \
sum([len(param['ref']) for param in Obj._param.values()]) == 0:
Obj._param = None
#
# 4) initialize the object Python instance
self.wrl('{0} = {1}({2})'.format(Obj._pyname,
Obj.__class__.__name__,
self._gen_type_init_attr(Obj, compts)))
#
# 5) check if the _IMPL_ module is required
if Obj._typeref and isinstance(Obj._typeref.called, tuple) and \
Obj._typeref.called[0] == '_IMPL_':
self._impl += 1
#
# 6) in case this is a parameterized object, no need to defined
# anything more
if Obj._param:
return
#
# 7) generate the content and specific constraints
if Obj.TYPE == TYPE_NULL : pass
elif Obj.TYPE == TYPE_BOOL : self.gen_type_bool(Obj)
elif Obj.TYPE == TYPE_INT : self.gen_type_int(Obj)
elif Obj.TYPE == TYPE_REAL : self.gen_type_real(Obj)
elif Obj.TYPE == TYPE_ENUM : self.gen_type_enum(Obj)
elif Obj.TYPE == TYPE_BIT_STR : self.gen_type_bitstr(Obj)
elif Obj.TYPE == TYPE_OCT_STR : self.gen_type_octstr(Obj)
elif Obj.TYPE in (TYPE_OID, TYPE_REL_OID) : self.gen_type_oid(Obj)
elif Obj.TYPE in TYPE_STRINGS : self.gen_type_str(Obj)
elif Obj.TYPE in (TYPE_TIME_UTC, TYPE_TIME_GEN) : self.gen_type_time(Obj)
elif Obj.TYPE == TYPE_CHOICE : self.gen_type_choice(Obj)
elif Obj.TYPE in (TYPE_SEQ_OF, TYPE_SET_OF) : self.gen_type_seqof(Obj)
elif Obj.TYPE in (TYPE_SEQ, TYPE_SET) : self.gen_type_seq(Obj)
elif Obj.TYPE == TYPE_CLASS : self.gen_type_class(Obj)
elif Obj.TYPE in (TYPE_OPEN, TYPE_ANY) : self.gen_type_open(Obj)
elif Obj.TYPE == TYPE_EXT : self.gen_type_ext(Obj)
elif Obj.TYPE == TYPE_EMB_PDV : self.gen_type_embpdv(Obj)
elif Obj.TYPE == TYPE_CHAR_STR : self.gen_type_charstr(Obj)
else:
raise(ASN1Err('{0}.{1}: unknown ASN.1 type, {2}'\
.format(self._mod_name, Obj._name, Obj.TYPE)))
#
# 8) generate the table constraint
self.gen_const_table(Obj)
#
# 9) keep track of the generated object
self._all_.append(Obj._pyname)
self._allobj_[Obj._pyname] = Obj
def gen_set(self, Obj):
#
# generate the type first
self.gen_type(Obj, compts=False)
#
# in case this is a parameterized object, no need to defined anything more
if Obj._param:
return
#
# now generate the set of values
self.wrl('{0}._val = {1}'.format(Obj._pyname, set_to_defin(ASN1Set(Obj._val), Obj, self)))
def gen_val(self, Obj):
#
# generate the type first
self.gen_type(Obj, compts=False)
#
# in case this is a parameterized object, no need to defined anything more
if Obj._param:
return
#
# now generate the set of values
self.wrl('{0}._val = {1}'.format(Obj._pyname, value_to_defin(Obj._val, Obj, self)))
def _gen_type_init_attr(self, Obj, compts):
attr = ['name={0}'.format(repr(Obj._name)),
'mode={0}'.format(_mode_lut[Obj._mode])]
# WNG: tag or typeref at the root of the object definition could be parameterized too
if Obj._tag and isinstance(Obj._tag[0], integer_types):
attr.append('tag={0}'.format(tag_to_defin(Obj._tag)))
if Obj._typeref and isinstance(Obj._typeref.called, tuple):
attr.append('typeref={0}'.format(typeref_to_defin(Obj)))
if Obj._param:
attr.append('param=True')
if compts:
# this is for constructed objects components only
if Obj._flag and FLAG_OPT in Obj._flag:
attr.append('opt=True')
elif Obj._flag and FLAG_DEF in Obj._flag:
if Obj._mode == MODE_SET:
# this can happen with CLASS fields
attr.append('default={0}'.format(set_to_defin(ASN1Set(Obj._flag[FLAG_DEF]), Obj, self)))
else:
if Obj._mode == MODE_TYPE:
par = Obj._parent
while par is not None:
if par.TYPE == TYPE_CLASS:
assert()
par = par._parent
attr.append('default={0}'.format(value_to_defin(Obj._flag[FLAG_DEF], Obj, self)))
if Obj.TYPE == TYPE_ANY and Obj._flag and FLAG_DEFBY in Obj._flag:
attr.append('defby={0!r}'.format(Obj._flag[FLAG_DEFBY]))
if Obj._flag and FLAG_UNIQ in Obj._flag:
attr.append('uniq=True')
if Obj._group is not None:
attr.append('group={0!r}'.format(Obj._group))
return ', '.join(attr)
#--------------------------------------------------------------------------#
# specific types
#--------------------------------------------------------------------------#
def gen_type_bool(self, Obj):
# value constraint
self.gen_const_val(Obj)
def gen_type_int(self, Obj):
# named integer values
if Obj._cont:
# Cont is an ASN1Dict with {str: int}
self.wrl('{0}._cont = ASN1Dict({1!r})'.format(Obj._pyname, list(Obj._cont.items())))
# value constraint
self.gen_const_val(Obj)
def gen_type_real(self, Obj):
# components constraint
self.gen_const_comps(Obj)
# value constraint
self.gen_const_val(Obj)
def gen_type_enum(self, Obj):
# enum content
if Obj._cont:
self.wrl('{0}._cont = ASN1Dict({1!r})'.format(Obj._pyname, list(Obj._cont.items())))
if Obj._ext is None:
self.wrl('{0}._ext = None'.format(Obj._pyname))
else:
self.wrl('{0}._ext = {1!r}'.format(Obj._pyname, Obj._ext))
# value constraint
self.gen_const_val(Obj)
def gen_type_bitstr(self, Obj):
# content: named bit offsets
if Obj._cont:
# Cont is an ASN1Dict with {str: int}
self.wrl('{0}._cont = ASN1Dict({1!r})'.format(Obj._pyname, list(Obj._cont.items())))
# value constraint
self.gen_const_val(Obj)
# size constraint
self.gen_const_sz(Obj)
# containing constraint
self.gen_const_contain(Obj)
def gen_type_octstr(self, Obj):
# value constraint
self.gen_const_val(Obj)
# size constraint
self.gen_const_sz(Obj)
# containing constraint
self.gen_const_contain(Obj)
def gen_type_oid(self, Obj):
# value constraint
self.gen_const_val(Obj)
def gen_type_str(self, Obj):
# value constraint
self.gen_const_val(Obj)
# size constraint
self.gen_const_sz(Obj)
# alphabet constraint
Consts_alpha = [C for C in Obj.get_const() if C['type'] == CONST_ALPHABET]
if Consts_alpha:
Salpha = reduce_setdicts(Consts_alpha)
self.wrl('{0}._const_alpha = {1}'.format(Obj._pyname, set_to_defin(Salpha, Obj, self)))
def gen_type_time(self, Obj):
# value constraint
self.gen_const_val(Obj)
def gen_type_choice(self, Obj):
# components constraint
self.gen_const_comps(Obj)
# content: ASN1Dict of {name: ASN1Obj}
if Obj._cont is not None:
# create all objects of the content first
links = ASN1Dict()
for name in Obj._cont:
Cont = Obj._cont[name]
Cont._pyname = '_{0}_{1}'.format(Obj._pyname, name_to_defin(Cont._name))
self.gen_type(Cont, compts=True)
links[name] = Cont._pyname
# now link all of them in an ASN1Dict into the Obj content
self.wrl('{0}._cont = ASN1Dict(['.format(Obj._pyname))
for name in links:
self.wrl(' ({0!r}, {1}),'.format(name, links[name]))
self.wrl(' ])')
# extension
if Obj._ext is None:
self.wrl('{0}._ext = None'.format(Obj._pyname))
else:
self.wrl('{0}._ext = {1!r}'.format(Obj._pyname, Obj._ext))
# value constraint
self.gen_const_val(Obj)
def gen_type_seq(self, Obj):
# components constraint
self.gen_const_comps(Obj)
# content: ASN1Dict of {name: ASN1Obj}
if Obj._cont is not None:
# create all objects of the content first
links = ASN1Dict()
for name in Obj._cont:
Cont = Obj._cont[name]
Cont._pyname = '_{0}_{1}'.format(Obj._pyname, name_to_defin(Cont._name))
self.gen_type(Cont, compts=True)
links[name] = Cont._pyname
# now link all of them in an ASN1Dict into the Obj content
self.wrl('{0}._cont = ASN1Dict(['.format(Obj._pyname))
for name in links:
self.wrl(' ({0!r}, {1}),'.format(name, links[name]))
self.wrl(' ])')
# extension
if Obj._ext is None:
self.wrl('{0}._ext = None'.format(Obj._pyname))
else:
self.wrl('{0}._ext = {1!r}'.format(Obj._pyname, Obj._ext))
# value constraint
self.gen_const_val(Obj)
def gen_type_seqof(self, Obj):
# component constraint
self.gen_const_comp(Obj)
# content: ASN1Obj
if Obj._cont is not None:
# create the object of the content first
Cont = Obj._cont
Cont._pyname = '_{0}_{1}'.format(Obj._pyname, name_to_defin(Cont._name))
self.gen_type(Cont)
# now link it to the Obj content
self.wrl('{0}._cont = {1}'.format(Obj._pyname, Cont._pyname))
# value constraint
self.gen_const_val(Obj)
# size constraint
self.gen_const_sz(Obj)
def gen_type_class(self, Obj):
# content: ASN1Dict of {name: ASN1Obj}
if Obj._cont is not None:
# create all objects of the content first
links = ASN1Dict()
for name in Obj._cont:
Cont = Obj._cont[name]
Cont._pyname = '_{0}_{1}'.format(Obj._pyname, name_to_defin(Cont._name))
self.gen_type(Cont, compts=True)
links[name] = Cont._pyname
# now link all of them in an ASN1Dict into the Obj content
self.wrl('{0}._cont = ASN1Dict(['.format(Obj._pyname))
for name in links:
self.wrl(' ({0!r}, {1}),'.format(name, links[name]))
self.wrl(' ])')
def gen_type_open(self, Obj):
if Obj._cont:
assert()
# value constraint for OPEN types are actually other ASN.1 types...
Consts_val = [C for C in Obj._const if C['type'] == CONST_VAL]
if Consts_val:
if len(Consts_val) > 1:
asnlog('WNG: {0}.{1}: multiple OPEN type value constraints, compiling only '\
'the first'.format(self._mod_name, Obj._name))
Const = Consts_val[0]
# process the root part of the constraint
ind = 0
if Const['root']:
root = []
for O in Const['root']:
O._pyname = '_{0}_val_{1!r}'.format(Obj._pyname, ind)
self.gen_type(O)
ind += 1
root.append(O._pyname)
root = '[{0}]'.format(', '.join(root))
else:
root = '[]'
# process the extended part of the constraint
if Const['ext'] is not None:
ext = []
for O in Const['ext']:
O._pyname = '_{0}_val_{1!r}'.format(Obj._pyname, ind)
self.gen_type(O)
ind += 1
ext.append(O._pyname)
ext = '[{0}]'.format(', '.join(ext))
else:
ext = 'None'
# creates the ASN1Set which links to the object
self.wrl('{0}._const_val = ASN1Set(rv={1}, ev={2})'.format(Obj._pyname, root, ext))
if [C for C in Obj.get_const() if C['type'] not in \
(CONST_TABLE, CONST_VAL, CONST_CONSTRAIN_BY)]:
assert()
def gen_type_ext(self, Obj):
# TODO: apply CONST_COMPS if exists
pass
def gen_type_embpdv(self, Obj):
# TODO: apply CONST_COMPS if exists
pass
def gen_type_charstr(self, Obj):
# TODO: apply CONST_COMPS if exists
pass
#--------------------------------------------------------------------------#
# specific constraints
#--------------------------------------------------------------------------#
def gen_const_sz(self, Obj):
# if no local size constraint, just return
if not [C for C in Obj._const if C['type'] == CONST_SIZE]:
return
# size constraint: reducing all size constraint to a single one
Consts_size = [C for C in Obj.get_const() if C['type'] == CONST_SIZE]
if Consts_size:
# size is a set of INTEGER values
SzProxy = INT()
Ssz = reduce_setdicts(Consts_size)
# SIZE has always an lb of 0 for range, even if they are defined as (MIN..XYZ)
for rr in Ssz._rr:
if rr.lb is None:
rr.lb = 0
for er in Ssz._er:
if er.lb is None:
er.lb = 0
self.wrl('{0}._const_sz = {1}'.format(Obj._pyname, set_to_defin(Ssz, SzProxy, self)))
def gen_const_val(self, Obj):
# if no local value constraint, just return
if not [C for C in Obj._const if C['type'] == CONST_VAL]:
return
# value constraint: reducing all value constraint to a single one
Consts_val = [C for C in Obj.get_const() if C['type'] == CONST_VAL \
and ('excl' not in C or C['excl'] == False)]
Consts_val_excl = [C for C in Obj.get_const() if C['type'] == CONST_VAL \
and ('excl' in C and C['excl'] == True)]
if Consts_val:
Sval = reduce_setdicts(Consts_val)
if Consts_val_excl:
# TODO: support removing excluded values
pass
self.wrl('{0}._const_val = {1}'.format(Obj._pyname, set_to_defin(Sval, Obj, self)))
def gen_const_table(self, Obj):
# table constraint: processing only a local and single constraint
Consts_tab = [C for C in Obj._const if C['type'] == CONST_TABLE]
if Consts_tab:
if len(Consts_tab) > 1:
asnlog('WNG: {0}.{1}: multiple table constraint, but compiling only the first'\
.format(self._mod_name, Obj._name))
Const = Consts_tab[0]
link_name = None
# check if the same constraint was already defined somewhere in the root object
if hasattr(self, '_const_tabs'):
ConstTabVal = Const['tab'].get_val()
if len(ConstTabVal['root']) \
or ConstTabVal['ext'] is not None and len(ConstTabVal['ext']):
# ensure the table as actual value(s) inside
for ct in self._const_tabs:
# HOLLY PYTHON: comparing damned complex dict values...
if ConstTabVal == ct[1]:
# the table of values get already compiled, just need to link it
link_name = ct[0]
break
if link_name is None:
# create the table set object
Const['tab']._pyname = '_{0}_tab'.format(Obj._pyname)
self.gen_set(Const['tab'])
link_name = Const['tab']._pyname
if not hasattr(self, '_const_tabs'):
self._const_tabs = [(link_name, Const['tab'].get_val())]
else:
self._const_tabs.append( (link_name, Const['tab'].get_val()) )
# now link it to the Obj constraint
self.wrl('{0}._const_tab = {1}'.format(Obj._pyname, link_name))
# define the @ identifier
if Const['at'] is None:
self.wrl('{0}._const_tab_at = None'.format(Obj._pyname))
else:
self.wrl('{0}._const_tab_at = {1!r}'.format(Obj._pyname, tuple(Const['at'])))
# define the table object identifier
try:
self.wrl('{0}._const_tab_id = {1}'.format(Obj._pyname, repr(Obj._typeref.ced_path[-1])))
except:
asnlog('WNG: {0}.{1}: unavailable table constraint ident, not compiling it'\
.format(self._mod_name, Obj._name))
self.wrl('{0}._const_tab_id = None')
def gen_const_contain(self, Obj):
# CONTAINING constraint: processing only a local and single constraint
Consts_contain = [C for C in Obj._const if C['type'] == CONST_CONTAINING]
if Consts_contain:
if len(Consts_contain) > 1:
asnlog('WNG: {0}.{1}: multiple CONTAINING constraint, compiling only '\
'the first'.format(self._mod_name, Obj._name))
Const = Consts_contain[0]
if Const['enc'] is not None:
# Const['enc'] is an OID value
EncProxy = OID()
self.wrl('{0}._const_cont_enc = {1}'\
.format(Obj._pyname, value_to_defin(Const['enc'], EncProxy, self)))
else:
# create the contained object first
Const['obj']._pyname = '_{0}_contain'.format(Obj._pyname)
self.gen_type(Const['obj'])
# now link it to the Obj constraint
self.wrl('{0}._const_cont = {1}'.format(Obj._pyname, Const['obj']._pyname))
def gen_const_comp(self, Obj):
# WITH COMPONENT constraint
Consts_comp = [C for C in Obj._const if C['type'] == CONST_COMP]
if Consts_comp:
# TODO: need to add support for WITH COMPONENT in the compiler
pass
def gen_const_comps(self, Obj):
# WITH COMPONENTS constraint: processing only a single constraint
Consts_comps = [C for C in Obj._const if C['type'] == CONST_COMPS]
if Consts_comps:
if len(Consts_comps) > 1:
asnlog('WNG: {0}.{1}: multiple WITH COMPONENTS constraints, '\
'generating only the first'.format(self._mod_name, Obj._name))
if Consts_comps[0]['ext'] is not None:
asnlog('INF: {0}.{1}: extensible WITH COMPONENTS constraint, '\
'not generating extension'.format(self._mod_name, Obj._name))
if not Consts_comps[0]['root']:
return
'''
if len(Consts_comps[0]['root']) > 1:
asnlog('WNG: {0}.{1}: multiple root parts in WITH COMPONENTS constraint, '\
'processing only the common components'.format(self._mod_name, Obj._name))
'''
#
'''
# this is not required as both step 2 and 3 are commented out
#
# 1) duplicate the content structure of the object
if not Obj._cont:
cont, Obj._ext = Obj.get_cont(wext=True)
Obj._cont = cont.copy()
else:
Obj._cont = Obj._cont.copy()
for ident, Comp in Obj._cont.items():
Obj._cont[ident] = Comp.__class__(Comp)
'''
#
'''
# TODO: components need actually to stay there, and be kept OPTIONAL
# this is required for proper encoding (mainly PER, OER)
# These COMPONENTS constraints need actually to be checked at runtime
# in addition to existing ones, without impacting the structure
# of objects content.
#
# 2) handle absent / present components
# gathering present / absent components from the potentially
# multiple possibilities, and keeping only the ones in common
pres, abse = set(Consts_comps[0]['root'][0]['_pre']), set(Consts_comps[0]['root'][0]['_abs'])
for Const in Consts_comps[0]['root'][1:]:
pres.intersection_update(Const['_pre'])
abse.intersection_update(Const['_abs'])
for ident in abse:
# remove the component
del Obj._cont[ident]
if Obj.TYPE != TYPE_CHOICE:
# for CHOICE, the constraint parsing takes care to set in _abs
# all components to be removed, when a component is set in _pre
# therefore, present components only need to be set as non optional
# for SEQUENCE
for ident in pres:
if FLAG_OPT in Obj._cont[ident]._flag:
del Obj._cont[ident]._flag[FLAG_OPT]
#
if len(Consts_comps[0]['root']) > 1:
asnlog('WNG: {0}.{1}: multiple root parts in WITH COMPONENTS constraint, '\
'unable to compile them'.format(self._mod_name, Obj._name))
return
'''
#
'''
# TODO: additional constraints provided through WITH COMPONENTS are not
# PER visible and must not be mixed with existing constraints which are
# PER visible
#
# 3) apply additional constraint on components
# only if we have a single root component in the constraint
Const = Consts_comps[0]['root'][0]
Const_kw = set(Const.keys())
Const_kw.remove('_pre')
Const_kw.remove('_abs')
for ident in Const_kw:
Obj._cont[ident]._const = list(Obj._cont[ident]._const)
Obj._cont[ident]._const.extend(Const[ident]['const'])
#print('%s.%s: %r' % (Obj._name, ident, Obj._cont[ident]._const))
'''
#------------------------------------------------------------------------------#
# JSON graph dependency generator
#------------------------------------------------------------------------------#
# generate a JSON file listing all ASN.1 objects dependencies
# then usable with the force-directed graph at https://bl.ocks.org/mbostock/4062045
# node.group is the module which hosts the object
# link.value is a fixed value
# WNG: for large ASN.1 specification, the web browser dies...
def asnmod_build_dep(mods):
"""
Returns two dicts:
- one listing all {caller ASN1Obj: list of called ASN1Obj}
- one listing all {called ASN1Obj: list of caller ASN1Obj}
"""
# build two dicts:
# 1 of {called_object: list of caller objects}
# 1 of {caller_object: list of called objects}
CallerDict, CalledDict = {}, {}
for mod in mods:
Mod = mods[mod]
for name in Mod:
if name[0] != '_':
Obj = Mod[name]
objname = (mod, Obj._name)
for Ref in Obj._ref:
if Ref.called in CalledDict:
if objname not in CalledDict[Ref.called]:
CalledDict[Ref.called].append( objname )
else:
CalledDict[Ref.called] = [ objname ]
if objname in CallerDict:
if Ref not in CallerDict[objname]:
CallerDict[objname].append( Ref )
else:
CallerDict[objname] = [ Ref ]
return CallerDict, CalledDict
class JSONDepGraphGenerator(_Generator):
"""
JSONDepGraphGenerator generates a JSON file that enables to produce a directed
graph representing all ASN.1 caller objects linked to all other called objects
The JSON file can be used directly with https://bl.ocks.org/mbostock/4062045
to be visualized
"""
# the link force is fixed for all linked between caller / callee objects
LINK_FORCE = 1
def gen(self):
#
groups = list(GLOBAL.MOD.keys())
#
# building 2 dicts: 1 with referrers objects, 1 with referred objects
obj_refr, obj_refd = asnmod_build_dep(GLOBAL.MOD)
#
self.wrl('{')
self.indent += 2
self.wrl('"_comment": "code automatically generated by pycrate_asn1c",')
#
self.wrl('"nodes": [')
self.indent += 2
nodes = []
for mod in GLOBAL.MOD:
if '_obj_' in GLOBAL.MOD[mod]:
for name in GLOBAL.MOD[mod]['_obj_']:
nodes.append('{"id": "%s.%s", "group": %i},'\
% (mod, name, groups.index(mod)))
# remove last coma
#nodes[-1] = nodes[-1][:-1]
last_node = nodes[-1]
#print('last_node:', last_node)
del nodes[-1]
nodes.append( last_node[:-1] )
#
for l in nodes:
self.wrl(l)
#
self.indent -= 2
self.wrl('],')
#
self.wrl('"links": [')
self.indent += 2
links = []
for (mod, name) in obj_refr:
for tgt in obj_refr[(mod, name)]:
if isinstance(tgt.called, tuple) and \
len(tgt.called) == 2 and tgt.called[1]:
tgt = list(tgt.called)
# we must ensure that tgt is really defined within tgt.called[0]
# and not imported from another module
while tgt[1] not in GLOBAL.MOD[tgt[0]]:
tgt[0] = GLOBAL.MOD[tgt[0]]['_imp_'][tgt[1]]
links.append('{"source": "%s.%s", "target": "%s.%s", "value": %i},'\
% (mod, name, tgt[0], tgt[1], self.LINK_FORCE))
# remove last coma
#links[-1] = links[-1][:-1]
last_link = links[-1]
#print('last_link:', last_link)
del links[-1]
links.append( last_link[:-1] )
#
for l in links:
self.wrl(l)
#
self.indent -= 2
self.wrl(']')
#
self.indent -= 2
self.wrl('}')