# -*- coding: UTF-8 -*- #/** # * Software Name : pycrate # * Version : 0.3 # * # * 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/asnobj.py # * Created : 2016-05-12 # * Authors : Benoit Michau # *-------------------------------------------------------- #*/ from binascii import hexlify, unhexlify from .utils import * from .utils import _RE_IDENT, _RE_TYPEREF, _RE_WORD from .err import * from .glob import * from .refobj import * from .setobj import * from .dictobj import * #///////////////////////////////////////\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\# #------------------------------------------------------------------------------# # debugging directives #------------------------------------------------------------------------------# #\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\///////////////////////////////////////# _DEBUG_PARAM = False _DEBUG_PARAM_PT = False _DEBUG_PARAM_VAL = False _DEBUG_PARAM_SET = False _DEBUG_PARAM_TYPE = False _DEBUG_SYNTAX_GRP = False _DEBUG_SYNTAX_OGRP = False # method tracer, to be used as a decorator _TRACE_NAME = ['chained', 'chainedRead', ] #_TRACE_NAME = None def tracemethod(meth, *args, **kwargs): def wrapper(*args, **kwargs): if _TRACE_NAME is None or GLOBAL.COMP['NS']['name'] in _TRACE_NAME: asnlog('---------------------------TRACE (%s)---------------------------'\ % GLOBAL.COMP['NS']['name']) asnlog('{0}.{1} :: {2}.{3}()'.format( GLOBAL.COMP['NS']['mod'], GLOBAL.COMP['NS']['name'], args[0].fullname(), meth.__name__)) asnlog(' args : {0!r}'.format(args[1:])) asnlog(' kwargs : {0!r}'.format(kwargs)) asnlog(' NS path: {0!r}'.format(GLOBAL.COMP['NS']['path'])) asnlog(' NS set : setdisp {0!r}, setpar {1!r}'.format( GLOBAL.COMP['NS']['setdisp'], GLOBAL.COMP['NS']['setpar'])) ret = meth(*args, **kwargs) if _TRACE_NAME is None or GLOBAL.COMP['NS']['name'] in _TRACE_NAME: asnlog(' ret : {0!r}'.format(ret)) return ret return wrapper #///////////////////////////////////////\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\# #------------------------------------------------------------------------------# # functions for processing paths inside ASN1Obj #------------------------------------------------------------------------------# #\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\///////////////////////////////////////# def get_asnobj(mod_name, obj_name): """ returns the ASN1Obj instance with the given obj_name in the module with the given mod_name """ try: mod = GLOBAL.MOD[mod_name] except KeyError: raise(ASN1Err('module {0}, undefined'.format(mod_name))) # it is possible to import an object from a module which itsel imports it... while obj_name in mod['_imp_']: try: mod = GLOBAL.MOD[mod['_imp_'][obj_name]] except KeyError: raise(ASN1Err('module {0}, undefined'.format(mod_name))) try: obj = mod[obj_name] except KeyError: raise(ASN1Err('object {0} in module {1}, undefined'.format(obj_name, mod_name))) else: return obj def _get_path_objs(Obj, path=[]): """ returns the list of objects along the path, starting from Obj """ if not path: return [] L = [] item = Obj for step in path: try: item = item[step] except Exception as err: raise(ASN1Err('_get_path_objs: {0}'.format(err))) else: L.append(item) return L def _get_path_last_obj(Obj, path=[]): """ returns the last object at the end of the path, starting from Obj """ if not path: return Obj item = Obj for step in path: try: item = item[step] except Exception as err: raise(ASN1Err('_get_path_last_obj: {0}'.format(err))) return item def _get_copy(Obj): """ returns a copy of the Python object Obj, but with identical content """ # copy a mutable / instance object into a new one but with the same # referred content if isinstance(Obj, list): return Obj[:] elif isinstance(Obj, dict): return dict(Obj) elif isinstance(Obj, (ASN1Dict, ASN1Range, ASN1Ref, ASN1Obj)): return Obj.copy() else: raise(ASN1Err('_asncopy: unsupported object, {0}'.format(type(Obj)))) def _get_path_copy(Obj, path=[]): """ returns a copy of all objects along the path, except the last one, starting from Obj """ path_objs = _get_path_objs(Obj, path) ind = len(path)-1 item = path_objs[ind] for obj in reversed(path_objs[:-1]): objcopy = _get_copy(obj) objcopy[path[ind]] = item item = objcopy ind -= 1 return item #///////////////////////////////////////\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\# #------------------------------------------------------------------------------# # functions for processing the global current path #------------------------------------------------------------------------------# #\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\///////////////////////////////////////# def _path_ext(ext): # append an extension to the root and current path if different GLOBAL.COMP['NS']['path'][0].extend(ext) if len(GLOBAL.COMP['NS']['path']) > 1: GLOBAL.COMP['NS']['path'][-1].extend(ext) # #asnlog('[DBG] _path_ext({0!r}), {1}.{2}'\ # .format(ext, GLOBAL.COMP['NS']['mod'], GLOBAL.COMP['NS']['name'])) #asnlog(' {0!r}'.format(GLOBAL.COMP['NS']['path'])) def _path_trunc(depth): assert( depth >= 0 ) # truncate for the given depth the root and current path if different assert( len(GLOBAL.COMP['NS']['path'][0]) >= depth ) del GLOBAL.COMP['NS']['path'][0][-depth:] if len(GLOBAL.COMP['NS']['path']) > 1: assert( len(GLOBAL.COMP['NS']['path'][-1]) >= depth ) del GLOBAL.COMP['NS']['path'][-1][-depth:] # #asnlog('[DBG] _path_trunc({0!r}), {1}.{2}'\ # .format(depth, GLOBAL.COMP['NS']['mod'], GLOBAL.COMP['NS']['name'])) #asnlog(' {0!r}'.format(GLOBAL.COMP['NS']['path'])) def _path_stack(new_path): GLOBAL.COMP['NS']['path'].append( new_path ) # #asnlog('[DBG] _path_stack({0!r}), {1}.{2}'\ # .format(new_path, GLOBAL.COMP['NS']['mod'], GLOBAL.COMP['NS']['name'])) #asnlog(' {0!r}'.format(GLOBAL.COMP['NS']['path'])) def _path_pop(): assert( len(GLOBAL.COMP['NS']['path']) > 0 ) # #asnlog('[DBG] _path_pop(), {0}.{1}'\ # .format(GLOBAL.COMP['NS']['mod'], GLOBAL.COMP['NS']['name'])) #asnlog(' {0!r}'.format(GLOBAL.COMP['NS']['path'])) # return GLOBAL.COMP['NS']['path'].pop() def _path_root(): return GLOBAL.COMP['NS']['path'][0] def _path_cur(): return GLOBAL.COMP['NS']['path'][-1] def _path_all(): return GLOBAL.COMP['NS']['path'] #///////////////////////////////////////\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\# #------------------------------------------------------------------------------# # ASN1Obj, parent Python class to parse and compile any ASN.1 object #------------------------------------------------------------------------------# #\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\///////////////////////////////////////# ASN1Obj_docstring = """ Attributes: - name: str, the identifier of the object - mode: str (MODE_*), the mode of the object after compilation. - MODE_TYPE : ASN.1 subtype or subclass - MODE_VALUE: ASN.1 value - MODE_SET : ASN.1 set of values - type: str (TYPE_*), the native ASN.1 type of the object. - param: None or ASN1Dict, lists the formal parameters of the object. If defined, each item has the following format: {str (parameter name) : {'obj': None or ASN1Obj (parameter governor), 'ref': list of referrers}} Each referrer is the path from the root object to where the parameter has to be set. - tag: None or list, contains the explicit tagging of the ASN.1 type. If defined, it has the following format: [int (tag value), str (tag class, TAG_CONTEXT_SPEC / TAG_PRIVATE / TAG_APPLICATION / TAG_UNIVERSAL), str (tag mode, TAG_IMPLICIT / TAG_EXPLICIT)] - typeref: None or ASN1Ref, provides the subtype of the object in case it derives from another user-defined one (and not a native one). - cont: type-dependent. - root: None or list of str, provides the identifiers of the root content if defined. - ext: None or list of str, provides the identifiers of the extended content if defined. - const: list of dict, lists all the constraints of the object; list is empty by default. Each constraint dict has the following format: {'text': str, 'type': str, 'keys': list of str, ... $key from keys: depends of $type} - val: None for MODE_TYPE, single_value (type-dependent) for MODE_VALUE, dict for MODE_SET. For set of values, the dict has the following format: {'root': list of single_value, 'ext' : None or empty list or list of single_value} Attributes required when defined as component of a constructed or CLASS type: - parent: None or ASN1Obj, indicates the parent ASN.1 object container. - flag: None or dict, lists the specific behavior of components. The following dict items can be set: - FLAG_OPT : None, means OPTIONAL when present - FLAG_DEF : int, provides a DEFAULT single_value when present - FLAG_UNIQ: None, means UNIQUE when present - FLAG_DEFBY: ASN1RefAnyDefBy, provides an identifier - group: None or int, indicates the extension group index of components. Attributes used during compilation and linking time: - ref: list of ASN1Ref, lists all the references to other ASN.1 user-defined ASN1Obj used in the object. It is used to track cross-references between objects. - cache: dict, hosts the cached fully defined internal content of the objects. It is enabled through the class attribute _CACHE_ENABLED. """ ASN1SyntaxForm_docstring = """ ASN.1 definitions syntax format: 1) MODE_TYPE object: # global object: Name [param] ::= [tag] type [cont, ext] [const] Name [param] ::= [tag] typeref [arg] [const] # local object: [name] [tag] type [cont, ext] [const] [flag] [name] [tag] typeref [arg] [const] [flag] # except within CLASS objects, for OPEN TYPE: &Name [flag] 2) MODE_VALUE and MODE_SET object # global object: name [param] [tag] type [cont, ext] [const] ::= value or { set } name [param] [tag] typeref [arg] [const] ::= value or { set } # local MODE_VALUE object within CLASS object: &name [tag] type [cont, ext] [const] [flag] &name [tag] typeref [arg] [const] [flag] # local MODE_SET object within CLASS object: &Name [tag] type [cont, ext] [const] [flag] &Name [tag] typeref [arg] [const] [flag] 3) When typeref is used to define an ASN.1 object, the following attributes of typeref are "inherited": - param - tag, if not overriden by any local tag declared - type - cont / ext - const, which are complemented by any other const in the refchain and locally declared """ class ASN1Obj(object): __doc__ = """ ASN1Obj is the parent class for all ASN.1 objects (type, value, set, class, class_value, class_set, but also constructed types' components and classes' fields, parameters, type-inclusion constraints, ...). It is also used as intermediate object during the compilation stage, when user-defined ASN.1 subtypes are by definition not known in advance. %s Additional attributes for SEQUENCE, SET and CLASS objects: - root_mand: list of str, lists all non-optional identifiers in the root content - root_opt: list of str, lists all identifiers from the root content that are optional or have a default value Additional attributes for SEQUENCE and SET objects: - ext_ident: dict with identifier (str) : extended group index (int), for extended component - ext_group: dict with extended group index (int) : list of identifiers (str), for extended component - cont_tags: dict with tag (int, str -tag class-): identifier Additional attribute for the CLASS object: - syntax: ASN1Dict with identifier (str) : 3-tuple (pre-keyword, post-keyword, opt-group-id) %s """ % (ASN1Obj_docstring, ASN1SyntaxForm_docstring) # to cache internal structures, enable it only after the compilation stage # has ended _CACHE_ENABLED = False # content parser dispatcher _PARSE_CONT_DISPATCH = { TYPE_INT : '_parse_cont_int', TYPE_BIT_STR : '_parse_cont_int', TYPE_ENUM : '_parse_cont_enum', TYPE_CHOICE : '_parse_cont_choice', TYPE_SEQ_OF : '_parse_cont_seqof', TYPE_SET_OF : '_parse_cont_seqof', TYPE_SEQ : '_parse_cont_seq', TYPE_SET : '_parse_cont_seq', TYPE_CLASS : '_parse_cont_class' } # value parser dispatcher _PARSE_VALUE_DISPATCH = { TYPE_NULL : '_parse_value_null', TYPE_BOOL : '_parse_value_bool', TYPE_INT : '_parse_value_int', TYPE_REAL : '_parse_value_real', TYPE_ENUM : '_parse_value_enum', TYPE_BIT_STR : '_parse_value_bitstr', TYPE_OCT_STR : '_parse_value_octstr', TYPE_OID : '_parse_value_oid', TYPE_REL_OID : '_parse_value_oid', TYPE_STR_IA5 : '_parse_value_str', TYPE_STR_PRINT : '_parse_value_str', TYPE_STR_NUM : '_parse_value_str', TYPE_STR_VIS : '_parse_value_str', TYPE_STR_BMP : '_parse_value_str', TYPE_STR_UTF8 : '_parse_value_str', TYPE_STR_ISO646 : '_parse_value_str', TYPE_STR_TELE : '_parse_value_str', TYPE_STR_VID : '_parse_value_str', TYPE_STR_GRAPH : '_parse_value_str', TYPE_STR_T61 : '_parse_value_str', TYPE_STR_GENE : '_parse_value_str', TYPE_STR_UNIV : '_parse_value_str', TYPE_OBJ_DESC : '_parse_value_str', TYPE_TIME_GEN : '_parse_value_timegen', TYPE_TIME_UTC : '_parse_value_timeutc', TYPE_CHOICE : '_parse_value_choice', TYPE_SEQ : '_parse_value_seq', TYPE_SEQ_OF : '_parse_value_seqof', TYPE_SET : '_parse_value_set', TYPE_SET_OF : '_parse_value_seqof', TYPE_OPEN : '_parse_value_open', TYPE_ANY : '_parse_value_open', TYPE_EXT : '_parse_value_seq', TYPE_EMB_PDV : '_parse_value_seq', TYPE_CHAR_STR : '_parse_value_seq', TYPE_CLASS : '_parse_value_class', TYPE_TYPEIDENT : '_parse_value_class', TYPE_ABSSYNT : '_parse_value_class' } # lookup shortcuts for basic objects specific values _VALUE_BOOL = {'TRUE': True, 'FALSE': False} _VALUE_REAL = {'MINUS-INFINITY': (-1, None, None), 'PLUS-INFINITY' : ( 1, None, None), 'NOT-A-NUMBER' : ( 0, None, None)} # ASN.1 types supporting the definition of range of values # WNG: for _String types, only ascii characters are supported _RANGE_TYPE_STR = (TYPE_STR_IA5, TYPE_STR_PRINT, TYPE_STR_VIS) _RANGE_TYPE = (TYPE_INT, TYPE_REAL) + _RANGE_TYPE_STR # the following keywords are used to identify all ASN.1 object attributes KW = ('name', 'mode', 'parnum', 'param', 'tag', 'type', 'typeref', 'cont', 'ext', 'const', 'val', 'ref', 'parent', 'flag', 'group', 'msg') # error reporting def _raise(self, error, msg=''): raise(error('{0}: {1}'.format(self.fullname(), msg))) #--------------------------------------------------------------------------# # initialization and generation methods #--------------------------------------------------------------------------# def __init__(self, name='', mode=MODE_TYPE, type=None, parent=None): self._name = name self._mode = mode self._type = type self._typeref = None self._parent = parent self.init_common_attr() def init_common_attr(self): # for all user-defined ASN.1 objects self._parnum = None self._param = None self._tag = None self._cont = None self._root = None self._ext = None self._const = [] self._val = None # for ASN.1 objects included in constructed types self._flag = None self._group = None # for tracking references self._ref = set() # for storing any transfer syntax self._msg = None # for caching temporary results self._cache = {} def init_cache(self): self._cache = {} def _init_from_obj(self, Obj): # this is used by all ASN.1 objects defined at the end of this file # in order to initialize from an existing ASN1Obj instance if Obj is None: self._name = '' self._mode = MODE_TYPE self._type = self.TYPE self._typeref = None self.init_common_attr() self._parent = None self._text_def = '' if self.TYPE in (TYPE_CLASS, TYPE_TYPEIDENT, TYPE_ABSSYNT): self._syntax = None elif isinstance(Obj, ASN1Obj) and Obj._type == self.TYPE: self._name = Obj._name self._mode = Obj._mode self._typeref = Obj._typeref self._parent = None # this must be set manually after init # common attributes self._parnum = Obj._parnum self._param = Obj._param self._tag = Obj._tag self._cont = Obj._cont self._root = Obj._root self._ext = Obj._ext self._const = Obj._const self._val = Obj._val self._flag = Obj._flag self._group = Obj._group self._ref = Obj._ref self._msg = None self._cache = {} if hasattr(Obj, '_root_mand') and hasattr(Obj, '_root_opt'): assert(Obj._type in (TYPE_SEQ, TYPE_SET, TYPE_CHOICE, TYPE_CLASS, TYPE_REAL, TYPE_EXT, TYPE_EMB_PDV, TYPE_CHAR_STR)) self._root_mand = Obj._root_mand self._root_opt = Obj._root_opt if hasattr(Obj, '_ext_ident') and hasattr(Obj, '_ext_group'): assert(Obj._type in (TYPE_SEQ, TYPE_SET, TYPE_CHOICE, TYPE_REAL, TYPE_EXT, TYPE_EMB_PDV, TYPE_CHAR_STR)) self._ext_ident = Obj._ext_ident self._ext_group = Obj._ext_group if hasattr(Obj, '_syntax'): assert(Obj._type in (TYPE_CLASS, TYPE_TYPEIDENT, TYPE_ABSSYNT)) self._syntax = Obj._syntax elif Obj._type in (TYPE_CLASS, TYPE_TYPEIDENT, TYPE_ABSSYNT): self._syntax = None # textual definition self._text_def = Obj._text_def if hasattr(self, '_text_decl'): self._text_decl = Obj._text_decl else: raise(ASN1ObjErr('{0}: invalid initializer'.format(self.TYPE))) def gen(self, **kwargs): for kw in self.KW: if kw in kwargs: setattr(self, '_%s' % kw, kwargs[kw]) return self.resolve() #--------------------------------------------------------------------------# # user-friendly generic representation #--------------------------------------------------------------------------# def __repr__(self): if self._typeref is not None: if isinstance(self._typeref, (ASN1RefType, ASN1RefInstOf)): try: typeref = self._typeref.called[1] except AttributeError: # .called is ASN1RefParam typeref = repr(self._typeref.called) elif isinstance(self._typeref, ASN1RefChoiceComp): try: typeref = '%s<' % self._typeref.called[1] + \ '<'.join(self._typeref.ced_path) except AttributeError: # .called is ASN1RefParam typeref = '%s<' % repr(self._typeref.called) + \ '<'.join(self._typeref.ced_path) elif isinstance(self._typeref, ASN1RefClassField): try: typeref = '%s.&' % self._typeref.called[1] + \ '.&'.join(self._typeref.ced_path) except AttributeError: # .called is ASN1RefParam typeref = '%s.&' % repr(self._typeref.called) + \ '.&'.join(self._typeref.ced_path) elif isinstance(self._typeref, ASN1RefClassIntern): typeref = '&%s' % '.&'.join(self._typeref.ced_path) elif isinstance(self._typeref, ASN1RefClassValField): try: typeref = '%s.&%s' % (self._typeref.called[1], '.&'.join(self._typeref.ced_path)) except AttributeError: # .called is ASN1RefParam typeref = '%s.&%s' % (repr(self._typeref.called), '.&'.join(self._typeref.ced_path)) else: assert() else: typeref = None # if self._mode == MODE_TYPE: if typeref is not None: return '<%s ([%s] %s)>' % (self._name, typeref, self._type) else: return '<%s (%s)>' % (self._name, self._type) elif self._mode == MODE_VALUE: if self._val is not None: val = self._val else: val = ' ' if typeref is not None: return '<%s ([%s] %s): %s>' % (self._name, typeref, self._type, val) else: return '<%s (%s): %s>' % (self._name, self._type, val) elif self._mode == MODE_SET: if self._val is not None: if self._val['ext'] is not None: ext = ', ...' elif self._val['ext']: ext += ', %s' % repr(self._val['ext'])[1:-1] else: ext = '' val = repr(self._val['root'])[1:-1] + ext else: val = ' ' if typeref is not None: return '<%s ([%s] %s): %s>' % (self._name, typeref, self._type, val) else: return '<%s (%s): %s>' % (self._name, self._type, val) def __call__(self): return self._val #--------------------------------------------------------------------------# # type resolution and generation #--------------------------------------------------------------------------# def copy(self): # this is used to create a new ASN1Obj instance which has its internal # attributes bounded to the ones of self # WNG: this only works with definitive objects (which have a .TYPE # attribute) # clone._parent is not bounded clone = self.__class__(self) clone._val = self._val return clone def get_typeref(self): """ returns the ASN1Obj corresponding to the typeref of self, or None when self inherits directly from an ASN.1 native type """ if self._typeref is None: return None elif self._CACHE_ENABLED and 'typeref' in self._cache: return self._cache['typeref'] ref = self._typeref # # if isinstance(ref, ASN1RefType): assert( ref.ced_path == [] ) if isinstance(ref.called, ASN1RefParam): tr = GLOBAL.COMP['NS']['par'][ref.called.name]['gov'] else: try: tr = get_asnobj(ref.called[0], ref.called[1]) except ASN1Err as Err: raise(ASN1ProcTextErr('{0}: {1}'\ .format(self.fullname(), Err))) if tr._type is None or tr._mode is None: # type or mode not yet compiled raise(ASN1ProcLinkErr('{0}: {1!r}'\ .format(self.fullname(), ref))) # # elif isinstance(ref, ASN1RefClassField): assert( len(ref.ced_path) >= 1 ) if isinstance(ref.called, ASN1RefParam): cla = GLOBAL.COMP['NS']['par'][ref.called.name]['gov'] if cla.get_cont() is None: # this means the governor is solely a MODE_TYPE, TYPE_CLASS # without defined content until parameterization # hence, typeref resolution will only happen at parameterization # WNG: here, we return only a "virtual" object tr = ASN1Obj(name='{0}.&{1}'.format(ref.called.name, '.&'.join(ref.ced_path)), type=TYPE_OPEN) tr._text_def = '' tr = tr.resolve() return tr else: try: cla = get_asnobj(ref.called[0], ref.called[1]) except ASN1Err as Err: raise(ASN1ProcTextErr('{0}: {1}'\ .format(self.fullname(), Err))) if cla._type is None or cla._mode is None: # type or mode not yet compiled raise(ASN1ProcLinkErr('{0}: {1!r}'\ .format(self.fullname(), ref))) elif cla._type not in (TYPE_CLASS, TYPE_TYPEIDENT, TYPE_ABSSYNT) \ or cla._mode not in (MODE_TYPE, MODE_SET): raise(ASN1ProcTextErr('{0}: {1!r}, invalid object'\ .format(self.fullname(), ref))) # support chained field references: # e.g. MeLesCasse.&Un.&Gros.&Emmerdement classpath = ref.ced_path[:] cp_ok = [cla._name] while len(classpath) > 1: cla_cont = cla.get_cont() if cla_cont is None: # cont not yet compiled raise(ASN1ProcLinkErr('{0}: ASN1RefClassField into {1}'\ .format(self.fullname(), '.&'.join(cp_ok)))) cp_ok.append(classpath[0]) try: tr = cla_cont[classpath[0]] except KeyError: raise(ASN1ProcTextErr('{0}: ASN1RefClassField into {1}, undefined'\ .format(self.fullname(), '.&'.join(cp_ok)))) if tr._typeref is None: # CLASS locally defined within CLASS cla = tr else: try: cla = tr.get_typeref() except ASN1ProcLinkErr as LinkErr: # enrich the linking error with local info LinkErr.args = ('{0}: {1}'.format(self.fullname(), LinkErr.args[0]), ) raise(LinkErr) except ASN1Err as Err: # enrich any other error with local info Err.args = ('{0}: {1}'.format(self.fullname(), Err.args[0]), ) raise(Err) del classpath[0] if cla._type is None or cla._mode is None: # type or mode not yet compiled raise(ASN1ProcLinkErr('{0}: ASN1RefClassField into {1}'\ .format(self.fullname(), '.&'.join(cp_ok)))) elif cla._type not in (TYPE_CLASS, TYPE_TYPEIDENT, TYPE_ABSSYNT) \ or cla._mode not in (MODE_TYPE, MODE_SET): raise(ASN1ProcTextErr('{0}: ASN1RefClassField into {1}, invalid object'\ .format(self.fullname(), '.&'.join(cp_ok)))) cla_cont = cla.get_cont() if cla_cont is None: # cont not yet compiled raise(ASN1ProcLinkErr('{0}: ASN1RefClassField into {1}'\ .format(self.fullname(), '.&'.join(cp_ok)))) cp_ok.append(classpath[0]) try: tr = cla_cont[classpath[0]] except KeyError: raise(ASN1ProcTextErr('{0}: {1!r} undefined'\ .format(self.fullname(), ref))) # # elif isinstance(ref, ASN1RefClassIntern): assert( self._parent is not None and self._parent._type == TYPE_CLASS ) assert( len(ref.ced_path) >= 1 ) cla = self._parent classpath = ref.ced_path[:] cp_ok = [cla._name] while len(classpath) > 1: cla_cont = cla.get_cont() if cla_cont is None: # cont not yet compiled raise(ASN1ProcLinkErr('{0}: ASN1RefClassIntern into {1}'\ .format(self.fullname(), '.&'.join(cp_ok)))) cp_ok.append(classpath[0]) try: tr = cla_cont[classpath[0]] except KeyError: raise(ASN1ProcTextErr('{0}: ASN1RefClassIntern into {1}, undefined'\ .format(self.fullname(), '.&'.join(cp_ok)))) if tr._typeref is None: # CLASS locally defined within CLASS cla = tr else: try: cla = tr.get_typeref() except ASN1ProcLinkErr as LinkErr: # enrich the linking error with local info LinkErr.args = ('{0}: {1}'.format(self.fullname(), LinkErr.args[0]), ) raise(LinkErr) except ASN1Err as Err: # enrich any other error with local info Err.args = ('{0}: {1}'.format(self.fullname(), Err.args[0]), ) raise(Err) del classpath[0] if cla._type is None or cla._mode is None: # type or mode not yet compiled raise(ASN1ProcLinkErr('{0}: ASN1RefClassIntern into {1}'\ .format(self.fullname(), '.&'.join(cp_ok)))) elif cla._type != TYPE_CLASS or cla._mode not in (MODE_TYPE, MODE_SET): raise(ASN1ProcTextErr('{0}: ASN1RefClassIntern into {1}, invalid object'\ .format(self.fullname(), '.&'.join(cp_ok)))) cla_cont = cla.get_cont() if cla_cont is None: # cont not yet compiled raise(ASN1ProcLinkErr('{0}: ASN1RefClassIntern into {1}'\ .format(self.fullname(), '.&'.join(cp_ok)))) cp_ok.append(classpath[0]) try: tr = cla_cont[classpath[0]] except KeyError: raise(ASN1ProcTextErr('{0}: {1!r} undefined'\ .format(self.fullname(), ref))) # # elif isinstance(ref, ASN1RefClassValField): assert( len(ref.ced_path) >= 1 ) if isinstance(ref.called, ASN1RefParam): # this means the governor is MODE_VALUE, TYPE_CLASS # without defined value until parameterization # hence, typeref resolution will only happen during parameterization tr = ASN1Obj(name='{0}.&{1}'.format(ref.called.name, '.&'.join(ref.ced_path)), type=TYPE_OPEN) return tr else: try: cla = get_asnobj(ref.called[0], ref.called[1]) except ASN1Err as Err: raise(ASN1ProcTextErr('{0}: {1}'\ .format(self.fullname(), Err))) if cla._type is None or cla._mode is None or cla._val is None: # type or mode or value not yet compiled raise(ASN1ProcLinkErr('{0}: {1!r}'\ .format(self.fullname(), ref))) elif cla._type not in (TYPE_CLASS, TYPE_TYPEIDENT, TYPE_ABSSYNT) \ or cla._mode != MODE_VALUE: raise(ASN1ProcTextErr('{0}: {1!r}, invalid object'\ .format(self.fullname(), ref))) claval = cla._val # support chained field references: # e.g. meLesCasse.&un.&gros.&Emmerdement classpath = ref.ced_path[:] cp_ok = [cla._name] while len(classpath) > 0: cp_ok.append(classpath[0]) try: claval = claval[classpath[0]] except KeyError: raise(ASN1ProcTextErr('{0}: ASN1RefClassValField into {1}, undefined'\ .format(self.fullname(), '.&'.join(cp_ok)))) del classpath[0] tr = claval if not isinstance(tr, ASN1Obj): raise(ASN1ProcTextErr('{0}: {1!r}, invalid object'\ .format(self.fullname(), ref))) # # elif isinstance(ref, ASN1RefChoiceComp): assert( len(ref.ced_path) >= 1 ) if isinstance(ref.called, ASN1RefParam): cho = GLOBAL.COMP['NS']['par'][ref.called.name]['gov'] else: try: cho = get_asnobj(ref.called[0], ref.called[1]) except ASN1Err as Err: raise(ASN1ProcTextErr('{0}: {1}'\ .format(self.fullname(), Err))) if cho._type is None or cho._mode is None: # type or mode not yet compiled raise(ASN1ProcLinkErr('{0}: {1!r}'\ .format(self.fullname(), ref))) elif cho._type != TYPE_CHOICE or cho._mode not in (MODE_TYPE, MODE_SET): raise(ASN1ProcTextErr('{0}: {1!r}, invalid object'\ .format(self.fullname(), ref))) # support chained CHOICE references: # e.g. emmerdement < gros < un < CasseLesBonbons choicepath = ref.ced_path[:] cp_ok = [cho._name] while len(choicepath) > 1: cho_cont = cho.get_cont() if cho_cont is None: # cont not yet compiled raise(ASN1ProcLinkErr('{0}: ASN1RefChoiceComp into {1}'\ .format(self.fullname(), '<'.join(cp_ok)))) cp_ok.append(choicepath[0]) try: tr = cho_cont[choicepath[0]] except KeyError: raise(ASN1ProcTextErr('{0}: ASN1RefChoiceComp into {1}, undefined'\ .format(self.fullname(), '<'.join(cp_ok)))) if tr._typeref is None: # CHOICE locally defined within CHOICE cho = tr else: try: cho = tr.get_typeref() except ASN1ProcLinkErr as LinkErr: # enrich the linking error with local info LinkErr.args = ('{0}: {1}'.format(self.fullname(), LinkErr.args[0]), ) raise(LinkErr) except ASN1Err as Err: # enrich any other error with local info Err.args = ('{0}: {1}'.format(self.fullname(), Err.args[0]), ) raise(Err) del choicepath[0] if cho._type is None or cho._mode is None: # type or mode not yet compiled raise(ASN1ProcLinkErr('{0}: ASN1RefChoiceComp into {1}'\ .format(self.fullname(), '<'.join(cp_ok)))) elif cho._type != TYPE_CHOICE or cho._mode not in (MODE_TYPE, MODE_SET): raise(ASN1ProcTextErr('{0}: ASN1RefChoiceComp into {1}, invalid object'\ .format(self.fullname(), '<'.join(cp_ok)))) cho_cont = cho.get_cont() if cho_cont is None: # cont not yet compiled raise(ASN1ProcLinkErr('{0}: ASN1RefChoiceComp into {1}'\ .format(self.fullname(), '<'.join(cp_ok)))) cp_ok.append(choicepath[0]) try: tr = cho_cont[choicepath[0]] except KeyError: raise(ASN1ProcTextErr('{0}: {1!r}, undefined'\ .format(self.fullname(), ' < '.join(cp_ok)))) # # elif isinstance(ref, ASN1RefInstOf): # we need to get the special TYPE-IDENTIFIER (sub)class referenced within _typeref # and build a SEQUENCE from it try: tid = get_asnobj(ref.called[0], ref.called[1]) except ASN1Err as Err: raise(ASN1ProcTextErr('{0}: {1}'.format(self.fullname(), Err))) seq = ASN1Obj(name=self._name, mode=MODE_TYPE, type=TYPE_SEQ) seq._text_def = self._text_def seq._cont = ASN1Dict() seq._cont['type-id'] = ASN1Obj(name='type-id', mode=MODE_TYPE) seq._cont['type-id']._text_def = '' seq._cont['type-id']._typeref = ASN1RefClassField(called=ref.called, ced_path=['id']) seq._cont['type-id'] = seq._cont['type-id'].resolve() seq._cont['value'] = ASN1Obj(name='value', mode=MODE_TYPE) seq._cont['value']._text_def = '' seq._cont['value']._typeref = ASN1RefClassField(called=ref.called, ced_path=['Type']) seq._cont['value']._tag = [0, TAG_CONTEXT_SPEC, TAG_EXPLICIT] seq._cont['value'] = seq._cont['value'].resolve() tr = seq.resolve() # # # in case typeref has formal parameters not yet compiled # just raise if tr._parnum and tr._param is None: raise(ASN1ProcLinkErr('{0}: typeref {1} need to be compiled'\ .format(self.fullname(), tr._name))) self._cache['typeref'] = tr return tr def get_refchain(self): """ returns the list of ASN1Obj instance(s) leading to the ultimate user-defined object which inherits directly form a native ASN.1 type """ if self._typeref is None: return [] elif self._CACHE_ENABLED and 'refchain' in self._cache: return self._cache['refchain'] # # 1) initialize the iterative process refchain = [self.get_typeref()] if self.fullname() == refchain[0].fullname(): raise(ASN1ProcTextErr('{0}: self type reference'\ .format(self.fullname()))) # # 2) iterate until we find the utlimate object with a native type while refchain[-1]._typeref is not None: refchain.append( refchain[-1].get_typeref() ) if refchain[-1] in refchain[:-1]: raise(ASN1ProcTextErr('{0}: circular type reference, {1}'\ .format(self.fullname(), [obj.fullname() for obj in refchain]))) # if refchain[-1]._type is None: raise(ASN1ProcTextErr('{0}: no native type defined for {1}'\ .format(self.fullname(), refchain[-1].fullname()))) # self._cache['refchain'] = refchain return refchain def resolve(self): """ returns a new instance of self using the fully defined Python class (including TYPE and TAG attributes) """ assert( self._type or self._typeref ) if self._type is None: # if subtype of a user-defined type, get the refchain and the ASN.1 # native type of the ultimate user-defined type refchain = self.get_refchain() native = refchain[-1] self._type = native._type New = ASN1ObjLUT[self._type](self) # # update self._parent if exists if self._parent is not None: New._parent = self._parent # # update the parent of objects in the content, if needed if New.TYPE in (TYPE_SEQ_OF, TYPE_SET_OF): if New._cont: if New._cont._parent == self: # WNG: in certain cases of parameterization, # this is not always true New._cont._parent = New # and in case the component has a CONTAINING constraint # it needs to be updated too for const in New._cont._const: if const['type'] == CONST_CONTAINING: if const['obj']._parent == self: const['obj']._parent = New elif New.TYPE in (TYPE_CHOICE, TYPE_SEQ, TYPE_SET, TYPE_CLASS): if New._cont: for Comp in New._cont.values(): if Comp._parent == self: # WNG: in certain cases of parameterization, # this is not always true Comp._parent = New # and in case components have a CONTAINING constraint # it needs to be updated too for const in Comp._const: if const['type'] == CONST_CONTAINING: if const['obj']._parent == self: const['obj']._parent = New # return New def get_classref(self): """ returns the ASN1Obj corresponding to the (parent) CLASS object referenced by self in case its typeref is ASN1RefClassField, ASN1RefClassIntern, ASN1RefClassValField or ASN1RefType to a CLASS, None otherwise """ if self._typeref is None: return None elif self._CACHE_ENABLED and 'classref' in self._cache: return self._cache['classref'] # # get_classref() will always happen after get_typeref() # there is no need to rerun all the checks ref = self._typeref # if isinstance(ref, ASN1RefType): tr = self.get_typeref() if tr._type in (TYPE_CLASS, TYPE_TYPEIDENT, TYPE_ABSSYNT): cr = tr else: cr = None elif isinstance(ref, (ASN1RefClassField, ASN1RefClassValField)): if isinstance(ref.called, ASN1RefParam): cr = self.get_param()[ref.called.name]['gov'] else: cr = get_asnobj(ref.called[0], ref.called[1]) elif isinstance(self._typeref, ASN1RefClassIntern): cr = self._parent else: cr = None # self._cache['classref'] = cr return cr def _verif_typeref(self): # check if the typeref is compiled, otherwise raise() a link error tr = self.get_typeref() if tr is not None and not hasattr(tr, 'TYPE'): raise(ASN1ProcLinkErr('{0}: typeref not yet compiled'\ .format(self.fullname()))) #--------------------------------------------------------------------------# # methods for emulating a dictionnary #--------------------------------------------------------------------------# # this enables to reference path within ASN.1 objects in the form of # list of str or int; e.g. path = ['cont', 'choiceAlternative'] def __getitem__(self, kw): #if kw in self.KW_TR: # return getattr(self, 'get_%s' % kw)() #elif kw in self.KW: if kw in self.KW: return getattr(self, '_%s' % kw) else: return object.__getitem__(self, kw) # select_set() should be preferred over __setitem__() def __setitem__(self, kw, arg): if kw in self.KW: return setattr(self, '_%s' % kw, arg) else: return object.__setitem__(self, kw, arg) def select(self, path=[]): """ returns the value of an attribute of self, by selecting its path """ # this method is similar to _get_path_last_obj() if not path: return self else: obj = self path_ok = [] for p in path: try: obj = obj[p] except Exception as err: raise(ASN1ObjErr('{0}: invalid path {1}, after {2}'\ .format(self.fullname(), path, path_ok))) else: path_ok.append(p) return obj def select_set(self, path=[], val=None): """ sets a value to an attribute of self, by selecting its path WNG: in case the path leads to an attribute of a typeref, this is the typeref object that will be updated """ if not path: return # select path elements until the last one obj = self.select( path[:-1] ) try: if isinstance(obj, list) and path[-1] == len(obj): # appending a new element to an existing list obj.append(val) else: obj[path[-1]] = val except Exception as err: raise(ASN1ObjErr('{0}: invalid path {1}, last element'\ .format(self.fullname(), path))) # methods for transferring from / to a dictionnary def get_internals(self): """ returns a dictionnary containing all attributes' value of self """ ASN1ObjDict = {} for kw in self.KW: ASN1ObjDict[kw] = getattr(self, '_%s' % kw) return ASN1ObjDict def set_internals(self, ASN1ObjDict): """ sets attributes' value(s) of self from a dictionnary """ for kw in self.KW: try: setattr(self, '_%s' % kw, ASN1ObjDict[kw]) except KeyError: pass #--------------------------------------------------------------------------# # methods for accessing internal attributes #--------------------------------------------------------------------------# def fullname(self): """ returns the full name of self, including parent's name when self is a component of a constructed type or a field of a CLASS type """ if self._parent is None: return self._name elif self._CACHE_ENABLED and 'fullname' in self._cache: return self._cache['fullname'] else: fn = '%s.%s' % (self._parent.fullname(), self._name) self._cache['fullname'] = fn return fn def get_parent_root(self): """ returns the root parent (ASN1Obj) of self when it is a component of a constructed type or a field of a CLASS type """ # returns the ultimate parent of the ASN1Obj if self._parent is None: return self elif self._CACHE_ENABLED and 'parent_root' in self._cache: return self._cache['parent_root'] Obj = self._parent while Obj is not None: parent = Obj._parent if parent is None: self._cache['parent_root'] = Obj return Obj else: Obj = parent def get_parent_path(self): """ returns a list with the selection path from the root parent to self """ # returns the selection path from the parent root to the ASN1Obj if self._CACHE_ENABLED and 'parent_path' in self._cache: return self._cache['parent_path'] path = [] son = self parent = son._parent while parent: if parent._type in (TYPE_SEQ_OF, TYPE_SET_OF): path.append( 'cont' ) else: path.extend( [son._name, 'cont'] ) son = parent parent = son._parent path.reverse() self._cache['parent_path'] = path return path def get_param(self): """ returns the ASN1Dict with the formal parameters associated to self, or None when the type is not parameterized """ # in case of component / field of a constructed objects, parameters are # those of the root parent if self._parent is None: return self._param elif self._CACHE_ENABLED and 'param' in self._cache: return self._cache['param'] else: p = self.get_parent_root()._param self._cache['param'] = p return p def get_tag(self): """ returns the nearest specific tag (not universal) of self """ if self._tag is not None: # 1) get local tag in priority, if exists return self._tag elif self._CACHE_ENABLED and 'tag' in self._cache: return self._cache['tag'] else: # 2) or inherited tag if exists tr = self.get_typeref() if tr is None: tag = None else: self._verif_typeref() # 3) get the tag from typeref tag = tr.get_tag() self._cache['tag'] = tag return tag def get_tag_univ(self): """ returns the UNIVERSAL tag of self """ if self.TAG is None: return None else: return [self.TAG, TAG_UNIVERSAL, TAG_IMPLICIT] def get_tag_chain(self): """ returns the list of tags of self, up to the UNIVERSAL one """ raise(ASN1NotSuppErr()) def get_cont(self): """ returns the content of self """ if self._cont is not None: # 1) get local content if exists return self._cont elif self._CACHE_ENABLED and 'cont' in self._cache: return self._cache['cont'] else: # 2) or inherited content if exists tr = self.get_typeref() if tr is None: cont = None else: self._verif_typeref() # 3) get the content from typeref cont = tr.get_cont() self._cache['cont'] = cont return cont def get_cont_tags(self): """ returns the list of tuple(tag, ident) for components within the content of self except for CHOICE, returns the ASN1Dict of ident: tag """ if self._type not in (TYPE_CHOICE, TYPE_SEQ, TYPE_SET, TYPE_REAL, TYPE_EXT, TYPE_EMB_PDV, TYPE_CHAR_STR): return None elif self._CACHE_ENABLED and 'cont_tags' in self._cache: return self._cache['cont_tags'] else: cont_tags = [] for ident, Comp in self.get_cont().items(): tag = None # check the local tag if Comp._tag: tag = Comp._tag cont_tags.append( (tuple(tag[0:2]), ident) ) else: # check into the chain of type reference if one of it has # a specific tag refchain, ok = Comp.get_refchain(), False for tr in refchain: if tr._tag: tag = tr._tag cont_tags.append( (tuple(tag[0:2]), ident) ) ok = True break if not ok: # get the universal tag tag = Comp.get_tag_univ() # if tag is None: assert( Comp._type in (TYPE_CHOICE, TYPE_OPEN, TYPE_ANY) ) if Comp._type == TYPE_CHOICE: # need to check the tags of the content of CHOICE # WNG, this is an ASN1Dict Comp_tags = Comp.get_cont_tags() for t in Comp_tags.keys(): cont_tags.append( (t[0:2], tuple([ident] + list(Comp_tags[t]))) ) elif not ok: cont_tags.append( (tuple(tag[0:2]), ident) ) # if self._type == TYPE_CHOICE: # sudo make me an ASN1Dict ! choice_cont_tags = ASN1Dict(cont_tags) if len(choice_cont_tags) < len(cont_tags): raise(ASN1ProcTextErr('{0}: duplicate tags within CHOICE content'\ .format(self.fullname()))) cont_tags = choice_cont_tags self._cache['cont_tags'] = cont_tags return cont_tags def get_root(self): """ returns the list of identifiers in the root part of the content of self """ if self._root is not None: # 1) get local root content if exists return self._root elif self._CACHE_ENABLED and 'root' in self._cache: return self._cache['root'] else: # 2) or inherited root content if exists tr = self.get_typeref() if tr is None: root = None else: self._verif_typeref() # 3) get the root content from typeref root = tr.get_root() self._cache['root'] = root return root def get_root_mand(self): """ returns the list of mandatory identifiers in the root part of the content of self """ if hasattr(self, '_root_mand'): # 1) get local root content if exists return self._root_mand elif self._CACHE_ENABLED and 'root_mand' in self._cache: return self._cache['root_mand'] else: # 2) or inherited root content if exists tr = self.get_typeref() if tr is None: root_mand = None else: self._verif_typeref() # 3) get the mandatory root content from typeref root_mand = tr.get_root_mand() self._cache['root_mand'] = root_mand return root_mand def get_root_opt(self): """ returns the list of optional identifiers in the root part of the content of self """ if hasattr(self, '_root_opt'): # 1) get local root content if exists return self._root_opt elif self._CACHE_ENABLED and 'root_opt' in self._cache: return self._cache['root_opt'] else: # 2) or inherited root content if exists tr = self.get_typeref() if tr is None: root_opt = None else: self._verif_typeref() # 3) get the optional root content from typeref root_opt = tr.get_root_opt() self._cache['root_opt'] = root_opt return root_opt def get_ext(self): """ returns the list of identifiers of the extended part of the content of self """ if self._ext is not None: # 1) get local extended content if exists return self._ext elif self._CACHE_ENABLED and 'ext' in self._cache: return self._cache['ext'] else: # 2) or inherited extended content if exists tr = self.get_typeref() if tr is None: ext = None else: self._verif_typeref() # 3) get the extended content from typeref ext = tr.get_ext() self._cache['ext'] = ext return ext def get_ext_ident(self): """ returns the dict of extended identifiers and associated optional group for the content of self """ if hasattr(self, '_ext_ident'): return self._ext_ident elif self._CACHE_ENABLED and 'ext_ident' in self._cache: return self._cache['ext_ident'] else: tr = self.get_typeref() if tr is None: ei = None else: self._verif_typeref() ei = tr.get_ext_ident() self._cache['ext_ident'] = ei return ei def get_ext_group(self): """ returns the dict of optional group and associated extended identifiers for the content of self """ if hasattr(self, '_ext_group'): return self._ext_group elif self._CACHE_ENABLED and 'ext_group' in self._cache: return self._cache['ext_group'] else: tr = self.get_typeref() if tr is None: eg = None else: self._verif_typeref() eg = tr.get_ext_group() self._cache['ext_group'] = eg return eg def get_const(self): """ returns the list of constraints applied to self """ if self._CACHE_ENABLED and 'const' in self._cache: return self._cache['const'] # build a list of constraints: const = [] tr = self.get_typeref() # 1) add local constraints if self._const: const.extend(self._const) # 2) add inherited constraints, without their extended part, # only from non-parameterized objects # (because constraints are duplicated from parameterized objects) if tr is not None and tr.get_param() is None: self._verif_typeref() for c in tr.get_const(): # remove extension from a copy of the constraint if 'ext' in c and c['ext']: c = dict(c) # TODO: ensure the alternative below is correct # keep it extensible c['ext'] = [] # remove entirely its extensibility #c['ext'] = None const.append(c) self._cache['const'] = const return const def get_syntax(self): """ returns the syntax ASN1Dict for CLASS ASN.1 object """ if not hasattr(self, '_syntax'): return None elif self._syntax is not None: return self._syntax elif self._CACHE_ENABLED and 'syntax' in self._cache: return self._cache['syntax'] else: tr = self.get_typeref() if tr is None: synt = None else: self._verif_typeref() synt = tr.get_syntax() self._cache['syntax'] = synt return synt def get_val(self): """ returns the single value or ASN1Dict with the set of values of self """ if self._val is not None: return self._val elif self._CACHE_ENABLED and 'val' in self._cache: return self._cache['val'] else: tr = self.get_typeref() if tr is None: val = None else: self._verif_typeref() val = tr.get_val() self._cache['val'] = val return val # methods specific to constructed components or CLASS fields def is_opt(self): if self._flag: return FLAG_OPT in self._flag or FLAG_DEF in self._flag else: return False # method to test the compliance of constructed or CLASS objects' values def is_value_ok(self, value): if self._type in (TYPE_SEQ, TYPE_SET): # ensure all mandatory root components are in val if not all([ident in value for ident in self.get_root_mand()]): return False # ensure grouped extension are all presents when at least 1 is # in value if any([ident in self.get_ext_ident() for ident in value]): for gid in self.get_ext_group(): idents = self.get_ext_group()[gid] ok = [ident in value for ident in idents] if any(ok) and not all(ok): return False elif self._type == TYPE_CLASS: # ensure all mandatory root components are in val if not all([ident in value for ident in self.get_root_mand()]): return False return True #--------------------------------------------------------------------------# # ASN.1 syntactic parser for formal parameters #--------------------------------------------------------------------------# def parse_param(self, text): """ parses the text corresponding to formal parameters within "{" and "}" sets self._param with an ASN1Dict containing formal parameters: parameter_name (str): {'obj': parameter_governor (ASN1Obj or None), 'ref': list of referrers (list of pathes)} returns the rest of the text """ # 1) extract the textual definitions of parameters rest, params = extract_multi(text) if params is None: self._param = None return rest if not params: raise(ASN1ProcTextErr('{0}: empty formal parameters'\ .format(self.fullname()))) # self._param = ASN1Dict() GLOBAL.COMP['NS']['par'] = self._param for param in params: # # parse each formal parameter: split the 2 parts, name and governor offset = search_top_lvl_sep(param, ':') if not offset: # # 2.1) no param_governor, param_name alone (-> type for an open-type) m = match_typeref(param) if not m: raise(ASN1ProcTextErr('{0}: invalid formal parameter, {1}'\ .format(self._name, param))) name = m.group(1) # # 2.2) create an empty / open object if name.isupper(): # CLASS object, with undefined content Gov = ASN1Obj(name=name, type=TYPE_CLASS, mode=MODE_TYPE) else: # OPEN type Gov = ASN1Obj(name=name, type=TYPE_OPEN, mode=MODE_TYPE) Gov._text_def = '' if param[m.end():]: raise(ASN1ProcTextErr('{0}: invalid formal parameter, {1}'\ .format(self._name, param))) # elif len(offset) == 1: # # 3.1) param_governor : param_name (lo-case -> val, up-case -> set) text_gov, text_name = param[:offset[0]].strip(), \ param[offset[0]+1:].strip() m = re.match(_RE_WORD, text_name) if not m: raise(ASN1ProcTextErr('{0}: invalid formal parameter name, {1}'\ .format(self._name, text_name))) # # 3.2) parse the object definition Gov = ASN1Obj(name=m.group()) if Gov._name[0].isupper(): Gov._mode = MODE_SET else: Gov._mode = MODE_VALUE Gov._text_def = text_gov # if text_name[m.end():]: raise(ASN1ProcTextErr('{0}: invalid formal parameter name, {1}'\ .format(self._name, text_name))) # _path_ext([Gov._name, 'gov']) _path_stack([]) try: text_gov = Gov.parse_def(text_gov) except ASN1Err as Err: # enrich the exception that can happen when parsing the governor # definition, with the fullname of self Err.args = ('{0}: {1}'.format(self.fullname(), Err.args[0]), ) raise(Err) _path_pop() _path_trunc(2) # if text_gov: raise(ASN1ProcTextErr('{0}, governor {1}: remaining textual definition, {2}'\ .format(self._name, Gov._name, text_gov))) # # 3.3) keep track of references made by governor in self if Gov._ref: self._ref.update( Gov._ref ) else: raise(ASN1ProcTextErr('{0}: invalid formal parameter, {1}'\ .format(self._name, param))) # if Gov._name in self._param: raise(ASN1ProcTextErr('{0}: duplicated formal parameter name, {1}'\ .format(self._name, Gov._name))) self._param[Gov._name] = {'gov': Gov.resolve(), 'ref': []} # # the GLOBAL namespace is updated in proc.asnobj_compile() return rest #--------------------------------------------------------------------------# # ASN.1 syntactic parser for object definition # parses: tag, type, content and constraints #--------------------------------------------------------------------------# def parse_def(self, text): """ parses the text corresponding to the ASN.1 object definition sets: - self._tag - self._type and / or self._typeref - self._cont for native INTEGER, BIT STRING, ENUMERATED, constructed and CLASS (also self._root and self._ext), or - self._const returns the rest of the text """ text = self._parse_tag(text) text = self._parse_type(text) if text: text = self._parse_cont(text) # if self._type == TYPE_CLASS: if text: # extract SYNTAX content text = self._parse_class_syntax(text) else: self._syntax = None else: while text[0:1] == '(': # constraint rest = self._parse_const(text) if text == rest: raise(ASN1ProcTextErr('{0}: missing closing parenthesis, {1}'\ .format(self.fullname(), text))) text = rest return text #--------------------------------------------------------------------------# # ASN.1 syntactic parser for tag #--------------------------------------------------------------------------# def _parse_tag(self, text): """ parses the text corresponding to a tag within "[" and "]" with tagging class (CONTEXT-SPECIFIC, PRIVATE, APPLICATION) and tagging mode (IMPLICIT / EXPLICIT) sets a list [tag_value, tag_class, tag_mode] in self._tag tag_value can be an ASN1RefPAram if referring to a parameter; returns the rest of the text """ m = SYNT_RE_TAG.match(text) if not m: # no tag specified self._tag = None return text cla, valnum, valref = m.group(1), m.group(2), m.group(3) # 1) get TAG class if cla is None: cla = TAG_CONTEXT_SPEC # 2) get TAG value if valref: param = GLOBAL.COMP['NS']['par'] if param and valref in param: # 2.1) valref corresponds to a formal parameter Gov = param[valref]['gov'] self.__parse_value_ref_typechk(Gov, valref, TYPE_INT) val = ASN1RefValue(ASN1RefParam(valref)) # add the referrer path to the formal parameter param[valref]['ref'].append( _path_root() + ['tag', 0] ) else: # 2.2) valref corresponds to a global value try: valmod = GLOBAL.COMP['NS']['obj'][valref] except KeyError: raise(ASN1ProcTextErr('{0}: tag value {1}, undefined'\ .format(self.fullname(), valref))) try: valobj = get_asnobj(valmod, valref) except ASN1Err as Err: raise(ASN1ProcTextErr('{0}: {1}'\ .format(self.fullname(), Err))) if valobj._val is None: # val not yet compiled raise(ASN1ProcLinkErr('{0}: tag value {1}'\ .format(self.fullname(), valref))) self.__parse_value_ref_typechk(valobj, valref, TYPE_INT) val = valobj._val # keep track of the reference self._ref.add( ASN1RefValue((valmod, valref)) ) else: # 2.3) valnum is a straight integer val = int(valnum) text = text[m.end():].strip() # 3) get TAG mode m = re.match('(IMPLICIT|EXPLICIT)(?:\s)', text) if m: self._tag = [val, cla, m.group(1)] text = text[m.end():].strip() else: # get the mode from the module-wide options if GLOBAL.COMP['NS']['tag'] == TAG_AUTO: mode = TAG_IMPLICIT else: mode = GLOBAL.COMP['NS']['tag'] self._tag = [val, cla, mode] # return text #--------------------------------------------------------------------------# # ASN.1 syntactic parser for type / typeref #--------------------------------------------------------------------------# def _parse_type(self, text): """ parses the text corresponding to the ASN.1 type (native or user-defined) if a native type is used, sets it in self._type if a reference to a user-defined type is used, sets the corresponding reference in self._typeref and copy its _type into self._type if the type refers to a parameter, the parameter ref is updated with the referrer's path for SEQUENCE OF and SET OF native types, parse the SIZE and other constraints early with _parse_const() returns the rest of the text """ # An ASN.1 type can be noted as a: # 1) ASN.1 native type -> 1 # in case of INSTANCE OF declaration, another type follows # (TYPE-IDENTIFIER or subtype of it) # 2) reference to a user-defined ASN.1 type: # Typeref -> 2.2.2 # Typeref can reference a formal parameter too -> 2.2.1 # 3) reference to a user-defined ASN.1 class: # CLASSREF -> 2.2.2 # CLASSREF can reference a formal parameter too -> 2.2.1 # 4) reference to a user-defined type field within a user-defined ASN.1 class: # CLASSREF.&[fF]ield -> 2.1.2 # CLASSREF can reference a formal parameter too -> 2.1.1 # 5) reference to an open-type field within a user-defined ASN.1 class value: # classvalref.&Type -> 5.2 # classvalref can reference a formal paramater too -> 5.1 # 6) reference to a type within a user-defined CHOICE object: # cho1 < ChoiceObj -> 4.2 # ChoiceObj can reference a formal parameter too -> 4.1 # 7) when inside a native CLASS type, reference to an internal open-type field: # &Type -> 3 # 1) reference to an ASN.1 native type rest, const = self._parse_type_native(text) if rest is not None: # parse constraint for SEQUENCE OF / SET OF if const is not None: assert(self._type in (TYPE_SEQ_OF, TYPE_SET_OF)) for text_const in const: void = self._parse_const(text_const) return rest # # param = GLOBAL.COMP['NS']['par'] # # # 2) reference to a user-defined Typeref or CLASSREF m = match_typeref(text) if m: typeref = m.group(1) text = text[len(typeref):].strip() # if typeref.isupper() and text[0:2] == '.&': # 2.1) typeref is actually a CLASSREF, with (chained) class # field(s) reference: CLASSREF(.&[fF]ield){1,} classpath = [] while text[0:2] == '.&': m = SYNT_RE_CLASSFIELDIDENT.match(text[1:]) try: classpath.append(m.group(1)) text = text[1+m.end():].strip() except AttributeError: raise(ASN1ProcTextErr('{0}: invalid CLASS field reference, {1}'\ .format(self.fullname(), text))) if param and typeref in param: # 2.1.1) CLASSREF is a reference to a local formal parameter # type may not be known until CLASSREF gets its actual parameter # hence it will be resolved at parameterization self._typeref = ASN1RefClassField(ASN1RefParam(typeref), classpath) param[typeref]['ref'].append( _path_root() + ['typeref'] ) self._type = TYPE_OPEN else: # 2.1.2) CLASSREF is a global reference try: refmod = GLOBAL.COMP['NS']['obj'][typeref] except KeyError: raise(ASN1ProcTextErr('{0}: ASN1RefClassField into {1}, undefined'\ .format(self.fullname(), typeref))) self._typeref = ASN1RefClassField((refmod, typeref), classpath) tr = self.get_typeref() self._type = tr._type # if tr.get_param(): raise(ASN1NotSuppErr('{0}: parameterized CLASS field {1!r}'\ .format(self.fullname(), self._typeref))) # # keep track of the type reference and return self._ref.add( self._typeref ) # elif text[0:1] == '.': # 2.2) typeref is actually a reference to an imported module, # and the subsequent token should be a typeref within this module refmod = typeref text = text[1:].strip() m = match_typeref(text) if not m: raise(ASN1ProcTextErr('{0}: ASN1RefType to {1}., missing typeref'\ .format(self.fullname(), refmod))) typeref = m.group(1) text = text[len(typeref):].strip() self._typeref = ASN1RefType((refmod, typeref)) tr = self.get_typeref() self._type = tr._type # # keep track of the type reference and return self._ref.add( self._typeref ) # else: # 2.3) local type / class reference only if param and typeref in param: # 2.3.1) typeref is a reference to a local formal parameter # if the formal parameter is MODE_TYPE: it is an OPEN_TYPE or TYPE_CLASS # if the formal parameter is MODE_SET: it can be any defined type self._typeref = ASN1RefType(ASN1RefParam(typeref)) param[typeref]['ref'].append( _path_root() + ['typeref'] ) self._type = param[typeref]['gov']._type else: # 2.3.2) typeref is a global reference try: refmod = GLOBAL.COMP['NS']['obj'][typeref] except KeyError: raise(ASN1ProcTextErr('{0}: ASN1RefType to {1}, undefined'\ .format(self.fullname(), typeref))) self._typeref = ASN1RefType((refmod, typeref)) tr = self.get_typeref() self._type = tr._type # # keep track of the type reference and return self._ref.add( self._typeref ) # return text # # # 3) internal reference to another user-defined field within a CLASS # &ClassField # or worse: &ClassField.&ClassSubField.&... if self._parent and self._parent._type == TYPE_CLASS: m = SYNT_RE_CLASSFIELDREFINT.match(text) if m: classpath = [m.group(1)] text = text[m.end():].strip() while text[0:2] == '.&': m = SYNT_RE_CLASSFIELDIDENT.match(text[1:]) try: classpath.append(m.group(1)) text = text[1+m.end():].strip() except AttributeError: raise(ASN1ProcTextErr('{0}: invalid CLASS field reference, {1}'\ .format(self.fullname(), text))) self._typeref = ASN1RefClassIntern(None, classpath) # ensure the last field name is upper case (MODE_TYPE or MODE_SET) if classpath and classpath[-1][0].islower(): raise(ASN1ProcTextErr('{0}: invalid mode for CLASS field reference, {1!r}'\ .format(self.fullname(), self._typeref))) tr = self.get_typeref() self._type = tr._type # if tr.get_param(): raise(ASN1NotSuppErr('{0}: parameterized CLASS field {1!r}'\ .format(self.fullname(), self._typeref))) # # keep track of the type reference and return self._ref.add( self._typeref ) return text # # # 4) reference to a CHOICE alternative # e.g. cho251 < cho25 < cho2 < ChoiceType m = SYNT_RE_CHOICEALT.match(text) if m: choicepath = list(reversed(list(map(strip, m.group().split('<'))))) # if param and choicepath[0] in param: # 4.1) ChoiceType is a reference to a local formal parameter # set to OPEN_TYPE, # type resolution will happen in parameterized objects only if param[choicepath[0]]['gov']._type != TYPE_OPEN: # it could actually be a TYPE_CLASS raise(ASN1ProcTextErr('{0}: invalid type for parameter {1}'\ .format(self.fullname(), choicepath[0]))) self._typeref = ASN1RefChoiceComp(ASN1RefParam(choicepath[0]), choicepath[1:]) param[choicepath[0]]['ref'].append( _path_root() + ['typeref'] ) self._type = TYPE_OPEN # else: # 4.2) ChoiceType is a global reference try: choicemod = GLOBAL.COMP['NS']['obj'][choicepath[0]] except KeyError: raise(ASN1ProcTextErr('{0}: ASN1RefChoiceComp into {1}, undefined'\ .format(self.fullname(), choicepath[0]))) self._typeref = ASN1RefChoiceComp((choicemod, choicepath[0]), choicepath[1:]) tr = self.get_typeref() self._type = tr._type # # keep track of the type reference and return self._ref.add( self._typeref ) # return text[m.end():].strip() # # # 5) reference to a field within a CLASS value # e.g. myClassValue.&MyType m = SYNT_RE_IDENT.match(text) if m: classvalref = m.group(1) classpath = [] text = text[m.end():].strip() while text[:2] == '.&': m = SYNT_RE_CLASSFIELDIDENT.match(text[1:]) try: classpath.append(m.group(1)) text = text[1+m.end():].strip() except AttributeError: raise(ASN1ProcTextErr('{0}: invalid CLASS value field reference, {1}'\ .format(self.fullname(), text))) # if param and classvalref in param: # 5.1) myClassValue is a reference to a local formal parameter # type may not be known until myClassValue gets its actual parameter # hence it will be resolved at parameterization self._typeref = ASN1RefClassValField(ASN1RefParam(classvalref), classpath) # ensure the last field name is upper case (MODE_TYPE or MODE_SET) if classpath and classpath[-1][0].islower(): raise(ASN1ProcTextErr('{0}: invalid mode for CLASS value field reference, {1!r}'\ .format(self.fullname(), self._typeref))) param[classvalref]['ref'].append( _path_root() + ['typeref'] ) self._type = TYPE_OPEN # else: # 5.2) myClassValue is a global reference try: classvalmod = GLOBAL.COMP['NS']['obj'][classvalref] except KeyError: raise(ASN1ProcTextErr('{0}: ASN1ClassValField into {1}, undefined'\ .format(self.fullname(), classvalref))) self._typeref = ASN1RefClassValField((classvalmod, classvalref), classpath) # ensure the last field name is upper case (MODE_TYPE or MODE_SET) if classpath and classpath[-1][0].islower(): raise(ASN1ProcTextErr('{0}: invalid mode for CLASS value field reference, {1!r}'\ .format(self.fullname(), self._typeref))) tr = self.get_typeref() self._type = tr._type # if tr.get_param(): raise(ASN1NotSuppErr('{0}: parameterized CLASS value field {1!r}'\ .format(self.fullname(), self._typeref))) # # keep track of the type reference and return self._ref.add( self._typeref ) # return text # raise(ASN1ProcTextErr('{0}: invalid ASN.1 type definition, {1}'\ .format(self.fullname(), text))) def _parse_type_native(self, text): m = SYNT_RE_TYPE.match(text) if m: self._type = m.group(1) text = text[len(self._type):].strip() const = None # if self._type in (TYPE_SEQ, TYPE_SET): # SEQUENCE OF / SET OF: get potential SIZE constraint and OF keyword m = SYNT_RE_SIZEOF.match(text) if m and m.group(1) is not None: # get SIZE constraint text, const = self.__extract_const_seqof(text) if text[:2] != 'OF': # SIZE constraint only for SEQ OF / SET OF raise(ASN1ProcTextErr('{0}: invalid use of SIZE keyword: {1}'\ .format(self.fullname(), text))) # add OF to the native type self._type = '%s OF' % self._type text = text[2:].strip() elif m and m.group(2): # no SIZE constraint, just OF keyword self._type = '%s OF' % self._type text = text[2:].strip() # elif self._type == TYPE_ANY: # ANY type can have a reference to another component of a SEQUENCE # with the DEFINED BY declaration if text[:10] == 'DEFINED BY': text = text[10:].strip() m = SYNT_RE_IDENT.match(text) if not m: raise(ASN1ProcTextErr('{0}: no identifier for ANY DEFINED BY, {1}'\ .format(self.fullname(), text))) if self._flag is None: self._flag = {FLAG_DEFBY: m.group(1)} else: self._flag[FLAG_DEFBY] = m.group(1) text = text[m.end():].strip() # elif self._type == TYPE_INSTOF: # in case of INSTANCE OF declaration, another type follows # (TYPE-IDENTIFIER or subtype of it) # get the ObjectClass reference following m = match_typeref(text) try: classref = m.group(1) except AttributeError: raise(ASN1ProcTextErr('{0}: no ObjectClass found, {1}'\ .format(self.fullname(), text))) try: classmod = GLOBAL.COMP['NS']['obj'][classref] except KeyError: raise(ASN1ProcTextErr('{0}: ASN1RefInstOf to {1}, undefined'\ .format(self.fullname(), classref))) self._typeref = ASN1RefInstOf((classmod, classref)) text = text[len(classref):].strip() # elif self._type in (TYPE_TYPEIDENT, TYPE_ABSSYNT): # TYPE-IDENTIFIER and ABSTRACT-SYNTAX are actually standard CLASS if text[0:2] == '.&': # an inner field can follow m = SYNT_RE_CLASSFIELDIDENT.match(text[1:]) try: self._typeref = ASN1RefClassField(('_IMPL_', self._type), [m.group(1)]) text = text[1+m.end():].strip() except AttributeError: raise(ASN1ProcTextErr('{0}: invalid {1} field reference, {2}'\ .format(self.fullname(), self._type, text))) else: self._typeref = ASN1RefType(('_IMPL_', self._type)) # overwrite the original type of the object self._type = self.get_typeref()._type # elif self._type in GLOBAL.MOD['_IMPL_']: # WNG: those types in _IMPL_ module are defined as SEQUENCE, # but native type must be kept # REAL, EXTERNAL, EMBEDDED PDV, CHARACTER STRING self._typeref = ASN1RefType(('_IMPL_', self._type)) # return text, const else: return None, None def __extract_const_seqof(self, text): if text[:4] == 'SIZE': # no outer parenthesis: e.g. SEQUENCE SIZE (2) OF text = text[4:].strip() text, text_const = extract_parenth(text) if not text_const: # no size specification raise(ASN1ProcTextErr('{0}: invalid SIZE constraint, {1}'\ .format(self.fullname(), text))) return text, ['(SIZE (%s))' % text_const] else: # any kind of constraint can happen otherwise const = [] while text[0:1] == '(': text, text_const = extract_parenth(text) const.append('(%s)' % text_const) return text, const #--------------------------------------------------------------------------# # ASN.1 syntactic parser for content #--------------------------------------------------------------------------# def _parse_cont(self, text): """ parses the text corresponding to any native content of an ASN.1 object calls special handler for ASN.1 native objects, in case of typeref referencement, process arguments as actual parameters returns the rest of the text """ if self._typeref and self._type != TYPE_INSTOF: tr = self.get_typeref() params_form = tr.get_param() if params_form: # # 1) handle parameterized standard ASN.1 types if isinstance(self._typeref, ASN1RefType): # # 1.1) keep track locally of the formal parameters from typeref self._params_form = list(params_form.values()) # # 1.2) bind the content of typeref into self if self._tag is None: self._tag = tr._tag self._cont = tr._cont self._root = tr._root self._ext = tr._ext self._const = tr._const #self._const = tr.get_const() self._val = tr._val # # 1.3) duplicate the parameterized pathes for param in self._params_form: for path in param['ref']: #if path[-2:] == ['tag', 0]: # the tag is parameterized, but the list storing it # should not exist yet, # self.select_set(path[0:-1], []) # if path[0] == 'param': # a subsequent formal parameter governor is itself # parameterized by this one assert( len(path) > 3 ) p_ind = params_form.index(path[1]) # decorrelate the local governor self._params_form[p_ind]['gov'] = \ self._params_form[p_ind]['gov'].copy() # keep track of the parameter index if hasattr(param['gov'], '_p_ind'): param['gov']._p_ind[path[1]] = p_ind else: param['gov']._p_ind = {path[1]: p_ind} else: self.select_set(path[0:1], _get_path_copy(self, path)) # # 2) handle parameterized CHOICE component elif isinstance(self._typeref, ASN1RefChoiceComp): # # 2.1) make a local copy of the original formal parameters, # and truncate the beggining of all referrers parpath = tr.get_parent_path() parpath_len = len(parpath) self._params_form = [] for param in params_form.values(): self._params_form.append( {'gov': param['gov'], 'ref': []} ) for path in param['ref']: assert( path[0] != 'param' ) if path[:parpath_len] == parpath: # the formal parameter impacts the CHOICE alternative # and requires processing self._params_form[-1]['ref'].append(path[parpath_len:]) # # 2.2) bind the content of typeref into self if self._tag is None: self._tag = tr._tag self._cont = tr._cont self._root = tr._root self._ext = tr._ext self._const = tr._const #self._const = tr.get_const() self._val = tr._val # # 2.3) duplicate the parameterized pathes for param in self._params_form: for path in param['ref']: if path[-2:] == ['tag', 0]: # the tag is parameterized, but the list storing it # should not exist yet self.select_set(path[0:-1], []) # self.select_set(path[0:1], _get_path_copy(self, path)) # else: assert() # text = self._parameterize(text) del self._params_form # # 3) handle native content for specific ASN.1 types elif self._type in self._PARSE_CONT_DISPATCH: text = getattr(self, self._PARSE_CONT_DISPATCH[self._type])(text) # return text def _parameterize(self, text): """ parses the text corresponding to the actual parameters of an ASN.1 object sets the actual parameters at all referrers path(es) for each formal parameter returns the rest of the text """ # # 0) extact actual parameters and verifies them against the number of # formal parameters text, params_act = extract_multi(text) if not params_act: raise(ASN1ProcTextErr('{0}: missing actual parameters'\ .format(self.fullname()))) #asnlog('WNG: {0}.{1}, missing actual parameters'\ # .format(GLOBAL.COMP['NS']['mod'], self.fullname())) return text # if len(params_act) != len(self._params_form): raise(ASN1ProcTextErr('{0}: invalid number of parameters, {1} instead of {2}'\ .format(self.fullname(), len(params_act), len(self._params_form)))) # # 1) get potential local formal parameters to be passed-through self._params_loc = GLOBAL.COMP['NS']['par'] # # create an empty list for keeping track of parameters pass-through # for set of values # this will be re-initialized at the end of parse_set() GLOBAL.COMP['NS']['setpar'] = [] # # 2) iterate over each actual parameter to set them properly in self for i in range(len(params_act)): # # 2.1) get formal param governor and referrers' path self._Gov = self._params_form[i]['gov'] self._pathes = self._params_form[i]['ref'] param_act = params_act[i] # # 2.2) extract MODE_SET parameter if param_act[0:1] == '{' and param_act[-1:] == '}' and \ self._Gov._mode == MODE_SET: # param_act is a set object # WNG: any SEQUENCE / SET / OID value would have curlybrackets, too param_act = param_act[1:-1].strip() self.__parameterize_set(extract_set(param_act)) # # 2.3) extract MODE_SET parameter reference elif self._Gov._mode == MODE_SET: self.__parameterize_set_comp(param_act) # # 2.4) extract MODE_VALUE parameter elif self._Gov._mode == MODE_VALUE: self.__parameterize_val(param_act) # # 2.5) extract MODE_TYPE / OPEN_TYPE parameter else: self.__parameterize_type(param_act) # if _DEBUG_PARAM: asnlog('[DBG] _parameterize({0}): {1}.{2}'.format( text, GLOBAL.COMP['NS']['mod'], GLOBAL.COMP['NS']['name'])) asnlog(' param_act: {0}'.format(param_act)) asnlog(' NS path : {0!r}'.format(GLOBAL.COMP['NS']['path'])) asnlog(' NS set : setdisp {0!r}, setpar {1!r}'.format( GLOBAL.COMP['NS']['setdisp'], GLOBAL.COMP['NS']['setpar'])) asnlog(' governor : name {0}, type {1}, typeref {2!r}, mode {3}'\ .format(self._Gov._name, self._Gov._type, self._Gov._typeref, self._Gov._mode)) asnlog(' referrers: {0!r}'.format(self._pathes)) # # 2.6) clean-up temporary attributes del self._Gov del self._pathes # # 3) clean-up temporary attributes del self._params_loc # # 4) return the rest of the textual object definition return text def __parameterize_set(self, valset): # dispatch valset, which is a root / ext dict, to the parameterization # applying to each component of the set if valset == {'root': [], 'ext': None}: self.__parameterize_set_comp_empty(ext=False) elif valset == {'root': [], 'ext': []}: self.__parameterize_set_comp_empty(ext=True) else: for comp in valset['root']: self.__parameterize_set_comp(comp) if valset['ext']: for comp in valset['ext']: self.__parameterize_set_comp(comp, dom='ext') if hasattr(self, '_ced_path'): del self._ced_path def __parameterize_set_comp_empty(self, ext=False): for path in self._pathes: # if path[0] == 'param': raise(ASN1NotSuppErr('{0}: formal parameter set parameterization, {1}'\ .format(self.fullname(), path))) # # 1) ensure the last part of path is the domain (root / ext) # and the index in the list of root / ext values assert( len(path)>=3 ) assert( path[-2] in ('root', 'ext') ) dom = path[-2] ind = path[-1] assert( isinstance(ind, integer_types) and ind >= 0 ) # # 2) select the root / ext dict containing the set of values val = self.select(path[:-2]) # # 3) keep track of the ASN1RefSet when __parameterize_set_comp() # is called for the 1st time from __parameterize_set() # and remove it from self ref = val[path[-2]][path[-1]] if isinstance(ref, ASN1RefSet): assert( isinstance(ref.called, ASN1RefParam) ) del val[path[-2]][path[-1]] # if ext and val['ext'] is None: val['ext'] = [] def __parameterize_set_comp(self, comp, dom='root'): # in case Gov is a CLASS, comp can be a field selected within # this class, e.g. Set{CLA:ClaSet} ::= {AnotherSet{{ClaSet.&Field}}} # hence, it is needed to split comp m = SYNT_RE_CLASSINSTFIELDREF.match(comp) if m and m.group(2): comp_spl = list(map(strip, comp[:m.end()].split('.&'))) if m.end() < len(comp): comp_spl.append( comp[m.end():] ) else: comp_spl = [comp] # # 1) parameter pass-through if self._params_loc and comp_spl[0] in self._params_loc: self.__parameterize_pt(comp_spl) return # _objval = [] # # 2) set of values parameter resolution # use the governor to parse the set of values for each given path # take care of the root / ext domains for values for path in self._pathes: # if path[0] == 'param': raise(ASN1NotSuppErr('{0}: formal parameter set parameterization, {1}'\ .format(self.fullname(), path))) # # 2.1) ensure the last part of path is the domain (root / ext) # and the index in the list of root / ext values assert( len(path)>=3 ) assert( path[-2] in ('root', 'ext') ) if dom == 'root': dom = path[-2] ind = path[-1] assert( isinstance(ind, integer_types) and ind >= 0 ) # # 2.2) select the root / ext dict containing the set of values val = self.select(path[:-2]) # # 2.3) keep track of the ASN1RefSet when __parameterize_set_comp() # is called for the 1st time from __parameterize_set() # and remove it from self ref = val[path[-2]][path[-1]] if isinstance(ref, ASN1RefSet): assert( isinstance(ref.called, ASN1RefParam) ) if ref.ced_path: # it will be required to select internal field inside the # actual parameter, which is a set of values of CLASS self._ced_path = ref.ced_path[:] else: self._ced_path = [] del val[path[-2]][path[-1]] # # 2.4) create a proxy object that will parse the textual set component # of values ObjProxy = self._Gov.copy() ObjProxy._ref = set() # # 2.5) set the global path to the root / ext dict and parse the set # of values _path_ext(path[:-2]) _path_stack(['val']) ObjProxy.parse_set(comp, dom=dom) _path_pop() _path_trunc(len(path)-2) # # 2.6) this creates a root / ext dict of values to dispatch into self objval = ObjProxy._val assert( isinstance(objval, dict) ) # for v in objval['root']: if self._ced_path: # need to select an internal field within the value if isinstance(v, ASN1RefSet) and isinstance(v.called, ASN1RefParam): # this is easy to handle val[dom].append( ASN1RefSet(ASN1RefParam(v.called.name), v.ced_path + self._ced_path) ) else: # try to select subfield into the value o = ObjProxy for cp in self._ced_path: try: o = o.get_cont()[cp] except KeyError: raise(ASN1ProcTextErr('{0}: invalid subfield from {1!r}'\ .format(self.fullname(), ref))) if cp not in v: if not o.is_opt(): raise(ASN1ProcTextErr( '{0}: missing value for mandatory field from {1!r}'\ .format(self.fullname(), ref))) else: v = None break else: v = v[cp] if v is not None: if o._mode == MODE_SET: assert( 'root' in v and 'ext' in v ) for vr in v['root']: val[dom].append(vr) if v['ext']: if val['ext'] is None: val['ext'] = [] for ve in v['ext']: val['ext'] = ve elif o._mode == MODE_VALUE: val[dom].append(v) else: assert() else: # just append the value as is val[dom].append(v) # if objval['ext']: if val['ext'] is None: val['ext'] = [] for v in objval['ext']: if self._ced_path: # need to select an internal field within the value if isinstance(v, ASN1RefSet) and isinstance(v.called, ASN1RefParam): # this is easy to handle val['ext'].append( ASN1RefSet(ASN1RefParam(v.called.name), c.ced_path + self._ced_path) ) else: # try to select subfield into the value o = ObjProxy for cp in self._ced_path: try: o = o.get_cont()[cp] except KeyError: raise(ASN1ProcTextErr('{0}: invalid subfield from {1!r}'\ .format(self.fullname(), ref))) if cp not in v: if not o.is_opt(): raise(ASN1ProcTextErr( '{0}: missing value for mandatory field from {1!r}'\ .format(self.fullname(), ref))) else: v = None break else: v = v[cp] if v is not None: if o._mode == MODE_SET: assert( 'root' in v and 'ext' in v ) if val['ext'] is None: val['ext'] = [] for vr in v['root']: val['ext'].append(vr) if v['ext']: for ve in v['ext']: val['ext'] = ve elif o._mode == MODE_VALUE: val['ext'].append(v) else: assert() else: # just append the value as is val['ext'].append(v) # # 2.7) transfer references from ObjProxy to self if ObjProxy._ref: self._ref.update( ObjProxy._ref ) _objval.append(objval) # if _DEBUG_PARAM_SET: asnlog('DBG: __parameterize_set_comp({0!r}, {1}), {2}.{3}'.format( comp, dom, GLOBAL.COMP['NS']['mod'], GLOBAL.COMP['NS']['name'])) asnlog(' NS path : {0!r}'.format(GLOBAL.COMP['NS']['path'])) asnlog(' NS set : setdisp {0!r}, setpar {1!r}'.format( GLOBAL.COMP['NS']['setdisp'], GLOBAL.COMP['NS']['setpar'])) asnlog(' referrers: {0!r}'.format(self._pathes)) asnlog(' _objval : {0!r}'.format(_objval)) def __parameterize_pt(self, comp_spl): # # 1) select the local formal parameter that corresponds to comp param_loc = self._params_loc[comp_spl[0]] Gov_loc = param_loc['gov'] # # 2) in case the actual parameter is actually a field selected within # the local governor, and not directly the local governor itself classpath = comp_spl[1:] while classpath: try: Gov_loc = Gov_loc.get_cont()[classpath[0]] except KeyError: raise(ASN1ProcTextErr('{0}: invalid field in parameter pass-through, {1}'\ .format(self.fullname(), classpath[0]))) else: del classpath[0] # # 3) ensure the local governor and typeref governor are compatible if Gov_loc._type != self._Gov._type or \ (self._Gov._mode in (MODE_TYPE, MODE_VALUE) and Gov_loc._mode != self._Gov._mode): #if Gov_loc._type != self._Gov._type or Gov_loc._mode != self._Gov._mode: raise(ASN1ProcTextErr('{0}: incompatible parameter pass-through, {1!r}'\ .format(self.fullname(), comp_spl))) if Gov_loc._typeref is not None: if self._Gov._typeref is None or \ Gov_loc._typeref.__hash__() != self._Gov._typeref.__hash__(): asnlog('WNG: {0}.{1}, parameter pass-through may be incompatible, {1!r}'\ .format(GLOBAL.COMP['NS']['mod'], self.fullname(), comp_spl)) # path_root = _path_root() path_cur = _path_cur() # # 4) build the local referrers for path in self._pathes: # if path[0] == 'param': raise(ASN1NotSuppErr('{0}: formal parameter passthrough parameterization, {1}'\ .format(self.fullname(), path))) # # 4.1) take potential current path into account (especially when # we handle ASN.1 value) if path_cur: #asnlog('{0}.{1}, path_cur: {2!r}, path: {3!r}'.format( # GLOBAL.COMP['NS']['mod'], GLOBAL.COMP['NS']['name'], # GLOBAL.COMP['NS']['path'], path)) assert( path_cur[0] == 'val' ) path = path_cur + path[1:] # # 4.2) keep track locally of the ASN1RefSet and rename it according # to the local formal parameter ref = self.select(path) assert( isinstance(ref, (ASN1RefSet, ASN1RefValue, ASN1RefClassValField)) ) assert( isinstance(ref.called, ASN1RefParam) ) if isinstance(ref, ASN1RefSet) and Gov_loc._mode == MODE_VALUE and \ not ref.ced_path: # casting the set of values to a single value newref = ASN1RefValue(called=ASN1RefParam(comp_spl[0])) else: newref = ref.__class__(called=ASN1RefParam(comp_spl[0])) if comp_spl[1:]: if ref.ced_path: newref.ced_path = comp_spl[1:] + ref.ced_path else: newref.ced_path = comp_spl[1:] elif ref.ced_path: newref.ced_path = ref.ced_path[:] self.select_set(path, newref) # # 4.3) extend the referrer from the local param to the object # pointed by path, taking potential current path into account # WNG: in case of nested parse_set() calls, the path needs to be # flattened (understand who can...) # if path[0] == 'val': path = path[1:] if path[0] in ('root', 'ext') and GLOBAL.COMP['NS']['setdisp']: assert( len(path) >= 2 ) if path_cur: path_full = path_root[:1-len(path_cur)] + path[2:] else: path_full = path_root + path[2:] GLOBAL.COMP['NS']['setpar'].append(path_full) else: if path_cur: path_full = path_root[:1-len(path_cur)] + path else: path_full = path_root + path param_loc['ref'].append( path_full ) # if _DEBUG_PARAM_PT: asnlog('[DBG] __parameterize_pt({0!r}), {1}.{2}'.format( comp_spl, GLOBAL.COMP['NS']['mod'], GLOBAL.COMP['NS']['name'])) asnlog(' NS path : {0!r}'.format(GLOBAL.COMP['NS']['path'])) asnlog(' NS set : setdisp {0!r}, setpar {1!r}'.format( GLOBAL.COMP['NS']['setdisp'], GLOBAL.COMP['NS']['setpar'])) asnlog(' formal referrers: {0!r}'.format(self._pathes)) asnlog(' local referrers : {0!r}'.format(param_loc['ref'])) def __parameterize_val(self, val): # in case Gov is a CLASS, comp can be a field selected within # this class, e.g. val{CLA:claVal} ::= anotherVal{{claVal.&Field}} # hence, it is needed to split val m = SYNT_RE_CLASSINSTFIELDREF.match(val) if m and m.group(2): val_spl = list(map(strip, val[:m.end()].split('.&'))) if m.end() < len(val): val_spl.append( val[m.end():] ) else: val_spl = [val] # # 1) parameter pass-through if self._params_loc and val_spl[0] in self._params_loc: self.__parameterize_pt(val_spl) return # # 2) create a proxy object that will parse the textual value ObjProxy = self._Gov.copy() ObjProxy._ref = set() _objval = [] # path_root = _path_root() path_cur = _path_cur() # # 3) go over each path for path in self._pathes: # if path[0] == 'param': raise(ASN1NotSuppErr('{0}: formal parameter value parameterization, {1}'\ .format(self.fullname(), path))) # # 3.1) take potential current path into account (especially when # we handle ASN.1 value) if path_cur: #asnlog('{0}.{1}, path_cur: {2!r}, path: {3!r}'.format( # GLOBAL.COMP['NS']['mod'], GLOBAL.COMP['NS']['name'], # GLOBAL.COMP['NS']['path'], path)) assert( path_cur[0] == 'val' ) path = path_cur + path[1:] # # 3.2) select the ASN1Ref that needs to be replaced by the actual # parameter and remove it from self ref = self.select(path) assert( isinstance(ref, ASN1Ref) ) assert( isinstance(ref.called, ASN1RefParam) ) if ref.ced_path: # we will need to select internal field inside the actual # parameter ced_path = ref.ced_path[:] else: ced_path = [] # # 3.3) set the global path and parse the value _path_ext(path) _path_stack(['val']) rest = ObjProxy.parse_value(val) _path_pop() _path_trunc(len(path)) # if rest: raise(ASN1ProcTextErr('{0}: remaining textual value definition, {1}'\ .format(self.fullname(), rest))) # objval = ObjProxy._val assert( objval is not None ) # # 3.4) select internal field inside the ObjProxy value if required o = ObjProxy if ced_path: for cp in ced_path: try: o = o.get_cont()[cp] except KeyError: raise(ASN1ProcTextErr('{0}: invalid subfield from {1!r}'\ .format(self.fullname(), ref))) if cp not in objval: if not o.is_opt(): raise(ASN1ProcTextErr( '{0}: missing value for mandatory subfield from {1!r}'\ .format(self.fullname(), ref))) else: objval = None break else: objval = objval[cp] # # 3.5) transfer the parsed value from ObjProxy to self if o._mode == MODE_SET: assert( path[-2] in ('root', 'ext') ) dom, ind = path[-2], path[-1] # delete the potential ASN1Ref and dispatch the content of objval # into the dict at path[:-2] dest = self.select(path[:-2]) assert( isinstance(dest, dict) ) del dest[dom][ind] if objval is not None: assert( 'root' in objval and 'ext' in objval ) for vr in objval['root']: dest[dom].append( vr ) if objval['ext']: if dest['ext'] is None: dest['ext'] = [] for ve in objval['ext']: dest['ext'].append(ve) elif o._mode == MODE_TYPE: assert( path[-1] == 'typeref' ) # decorrelate objval from the original object OldObj = self.select(path[:-1]) objval = self.__parameterize_transfer_obj(OldObj, objval, clone=True, ClassRef=None) self.select_set(path[:-1], objval) else: # o._mode == MODE_VALUE self.select_set(path, objval) # # 3.6) transfer references from ObjProxy to self if ObjProxy._ref: self._ref.update( ObjProxy._ref ) # cleanup ObjProxy references ObjProxy._ref = set() # # cleanup ObjProxy value ObjProxy._val = None _objval.append(objval) # if _DEBUG_PARAM_VAL: asnlog('DBG: __parameterize_val({0}), {2}.{3}'.format( val, GLOBAL.COMP['NS']['mod'], GLOBAL.COMP['NS']['name'])) asnlog(' NS path : {0!r}'.format(GLOBAL.COMP['NS']['path'])) asnlog(' NS set : setdisp {0!r}, setpar {1!r}'.format( GLOBAL.COMP['NS']['setdisp'], GLOBAL.COMP['NS']['setpar'])) asnlog(' referrers: {0!r}'.format(self._pathes)) asnlog(' _objval : {0!r}'.format(_objval)) def __parameterize_type(self, typedef): # Gov is a MODE_TYPE / TYPE_OPEN ASN1Obj objects = [] for path in self._pathes: # # 1) ensure the last part of the path is a typeref assert( path[-1] == 'typeref' ) # if path[0] == 'param': # 2.1) in case a subsequent formal parameter gets parameterized if len(path) != 4 or path[2:4] != ['gov', 'typeref']: raise(ASN1NotSuppErr('{0}: formal parameter type parameterization, {1}'\ .format(self.fullname(), path))) # # 2.2) update the governor in the local record of the formal parameters p_ind = self._Gov._p_ind[path[1]] ObjOpen = self._params_form[p_ind]['gov'] assert( ObjOpen._type in (TYPE_OPEN, TYPE_CLASS) ) # else: # 3) in case a standard part of the current object is to be parameterized # just select it ObjOpen = self.select(path[:-1]) assert( isinstance(ObjOpen, ASN1Obj) ) assert( ObjOpen._type == TYPE_OPEN ) # # 4) parse the new object Obj = ASN1Obj(name=ObjOpen._name, mode=ObjOpen._mode) Obj._text_def = typedef # _path_ext(path[:-1]) _path_stack([]) rest = Obj.parse_def(typedef) _path_pop() _path_trunc(len(path)-1) # if rest: raise(ASN1ProcTextErr('{0}: remaining textual definition, {1}'\ .format(self.fullname(), rest))) # ClassRef = None # 5) select internal part of Obj if required if isinstance(Obj._typeref, ASN1RefType) and Obj._type == TYPE_CLASS and \ isinstance(ObjOpen._typeref, ASN1RefClassField): Obj._typeref = ASN1RefClassField(Obj._typeref.called, ObjOpen._typeref.ced_path) # clean-up CLASS specifics attributes from Obj Obj._type = None del Obj._syntax # track the CLASS reference for potential table constraint ClassRef = Obj.get_classref() elif not isinstance(ObjOpen._typeref, ASN1RefType): raise(ASN1NotSuppErr('{0}: type parameterization with {1!r}'\ .format(self.fullname(), ObjOpen._typeref))) # # 6) transfer existing flags, constraints and tag to the new object Obj = self.__parameterize_transfer_obj(ObjOpen, Obj, clone=False, ClassRef=ClassRef) # if path[0] == 'param': # 7) replace the local governor by the new object, by rewriting # the gov / ref dict self._params_form[p_ind] = {'gov': Obj.resolve(), 'ref': self._params_form[p_ind]['ref']} # else: # 8) replace ObjOpen by Obj in self # e.g. MyType {CustomType} ::= CustomType if len(path) > 1: self.select_set(path[:-1], Obj.resolve()) else: # this is a special case, were the new object will be assigned # during the proc.asnobj_compile() function self._new = Obj.resolve() Obj._parent = ObjOpen._parent # # 9) transfer references from Obj to self if Obj._ref: self._ref.update( Obj._ref ) objects.append( Obj ) # if _DEBUG_PARAM_TYPE: asnlog('DBG: __parameterize_type({0}), {2}.{3}'.format( typedef, GLOBAL.COMP['NS']['mod'], GLOBAL.COMP['NS']['name'])) asnlog(' NS path : {0!r}'.format(GLOBAL.COMP['NS']['path'])) asnlog(' NS set : setdisp {0!r}, setpar {1!r}'.format( GLOBAL.COMP['NS']['setdisp'], GLOBAL.COMP['NS']['setpar'])) asnlog(' referrers: {0!r}'.format(self._pathes)) asnlog(' objects : {0!r}'.format(objval)) def __parameterize_transfer_obj(self, OldObj, NewObj, clone=False, ClassRef=None): # transfer existing flags, constraints and tag from OldObj to NewObj cloned = False if OldObj._flag: assert(NewObj._flag is None) if clone: NewObj = NewObj.copy() cloned = True NewObj._flag = OldObj._flag # if OldObj._tag: assert(NewObj._tag is None) if clone and not cloned: NewObj = NewObj.copy() cloned = True NewObj._tag = OldObj._tag # if OldObj._const: if clone: if not cloned: NewObj = NewObj.copy() cloned = True NewObj._const = NewObj._const[:] for const in reversed(OldObj._const): # in case of table constraint, need to regenerate ClassRef if ClassRef is not None and const['type'] == CONST_TABLE: TabSetOpen = const['tab'] # new TabSet according to the parameterized CLASS ref if hasattr(ClassRef, '_mod') and ClassRef._mod: _TabSet = ASN1Obj(name='_tab_{0}'.format(ClassRef._name), mode=MODE_SET, type=TYPE_CLASS) _TabSet._typeref = ASN1RefType((ClassRef._mod, ClassRef._name)) _TabSet._text_def = ClassRef._text_def TabSet = CLASS(_TabSet) else: # WNG: ClassRef._mod is not always defined, # e.g. when ClassRef is a parameter TabSet = ClassRef.copy() TabSet._mode = MODE_SET TabSet._ref = set() # transfer set values from TabSetOpen to TabSet TabSet._val = {'root': [], 'ext': None} for val in TabSetOpen._val['root']: if isinstance(val, ASN1RefSet) and \ isinstance(val.called, ASN1RefParam): # only supporting the transfer of parameterized set TabSet._val['root'].append(val) else: raise(ASN1NotSuppErr( '{0}: type parameterization with table constraint, {1!r}'\ .format(self.fullname(), val))) # recreate a new const dict const_new = dict(const) const_new['tab'] = TabSet.resolve() NewObj._const.insert(0, const_new) else: NewObj._const.insert(0, const) # return NewObj def _parse_cont_int(self, text): """ parses the text corresponding to the content of an ASN.1 INTEGER (named values) or BIT STRING (named offsets) object sets an ASN1Dict with names and named values / offsets in self._cont returns the rest of the text """ # list of name (value / offset), coma-separated # eg: alpha(1), beta (deux), trois(dreI-3), four( 4 ), five ( fifthVal) rest, text = extract_curlybrack(text) if not text: return rest named_val = map(strip, text.split(',')) self._cont = ASN1Dict() for nv in named_val: m = SYNT_RE_INT_ID.match(nv) if not m: raise(ASN1ProcTextErr('{0}: invalid named value in {1}, {2}'\ .format(self.fullname(), self._type, nv))) name = m.group(1) if name in self._cont: # duplicated identifier raise(ASN1ProcTextErr('{0}: duplicated named value in {1}, {2}'\ .format(self.fullname(), self._type, name))) elif m.group(4): # named offset is a reference to an INTEGER self._cont[name] = None _path_ext(['cont', name]) self._parse_value_ref(m.group(4), type_expected=TYPE_INT) _path_trunc(2) else: # named offset is an integer value self._cont[name] = int(m.group(3)) return rest def _parse_cont_enum(self, text): """ parses the text corresponding to the content of an ASN.1 ENUMERATED object sets an ASN1Dict with names and provided indexes in self._cont, sets also self._ext if the content is extended returns the rest of the text """ # list of enumeration strings and integer values, coma-separated # eg: butter (-1), cheese (1), cake, apple (three), ..., orange (4) rest, text = extract_curlybrack(text) if not text: raise(ASN1ProcTextErr('{0}: empty ENUMERATED'\ .format(self.fullname()))) enums = map(strip, text.split(',')) self._cont = ASN1Dict() self._ext = None for enum in enums: m = SYNT_RE_ENUM.match(enum) if not m: raise(ASN1ProcTextErr('{0}: invalid identifier in ENUMERATED, {1}'\ .format(self.fullname(), enum))) name = m.group(1) if name == '...': # extension marker if self._ext is not None: raise(ASN1ProcTextErr('{0}: invalid extension marker in ENUMERATED, {1}'\ .format(self.fullname(), text))) self._ext = [] elif name in self._cont: # duplicated identifier raise(ASN1ProcTextErr('{0}: duplicated identifier in ENUMERATED, {1}'\ .format(self.fullname(), name))) elif m.group(3): # enum value is an integer self._cont[name] = int(m.group(3)) elif m.group(4): # enum value is a reference to an integer self._cont[name] = None _path_ext(['cont', name]) self._parse_value_ref(m.group(4), type_expected=TYPE_INT) _path_trunc(2) elif m.group(2) is None: # no explicit value for the enum self._cont[name] = None # if name != '...': if self._ext is not None: # identifier in the extension list self._ext.append(name) # if not self._cont: # empty ENUM are invalid raise(ASN1ProcTextErr('{0}: empty ENUMERATED'.format(self.fullname()))) self.__parse_cont_gen_root() self.__parse_cont_gen_ext() self.__parse_cont_enum_autonum() self.__parse_cont_enum_reorder() return rest def __parse_cont_gen_root(self): self._root = [] if self._ext is None: ext = [] else: ext = self._ext for ident in self._cont.keys(): if ident in ext: break else: self._root.append(ident) # for SEQUENCE, SET and CLASS (and also REAL, EXTERNAL and EMBEDDED PDV # which have hidden content), build the list of optional content in # the root part if self._type in (TYPE_SEQ, TYPE_SET, TYPE_CLASS, TYPE_EXT, TYPE_EMB_PDV, TYPE_CHAR_STR): self._root_mand = [] self._root_opt = [] for ident in self._root: Cont = self._cont[ident] if Cont.is_opt(): self._root_opt.append(ident) else: self._root_mand.append(ident) def __parse_cont_gen_ext(self): # in case the module forces extensibility globally if self._ext is None and GLOBAL.COMP['NS']['ext']: self._ext = [] # for CHOICE, SEQUENCE and SET, build 2 dicts for grouped extension if self._type in (TYPE_CHOICE, TYPE_SEQ, TYPE_SET): self._ext_ident,self._ext_group = {}, {} if self._ext: for comp_name in self._ext: Comp = self._cont[comp_name] self._ext_ident[comp_name] = Comp._group if Comp._group in self._ext_group: self._ext_group[Comp._group].append(comp_name) else: self._ext_group[Comp._group] = [comp_name] def __parse_cont_enum_autonum(self): # applies automatic numbering # 1) on the root part indused = [self._cont[ident] for ident in self._root if \ self._cont[ident] is not None] ind = 0 while ind in indused: ind += 1 for ident in self._root: if self._cont[ident] is None: self._cont[ident] = ind indused.append(ind) ind += 1 while ind in indused: ind += 1 # 2) on the ext part if self._ext: ind = 0 while ind in indused: ind += 1 for ident in self._ext: if self._cont[ident] is None: self._cont[ident] = ind indused.append(ind) ind += 1 while ind in indused: ind += 1 elif self._cont[ident] in indused: raise(ASN1ProcTextErr( '{0}: invalid numbering of extension {1}, {2}'\ .format(self.fullname(), ident, self._cont[ident]))) def __parse_cont_enum_reorder(self): # reorder the _cont ASN1Dict according to the enum numbering ind = list(self._cont.values()) Cont = ASN1Dict() while ind: for (ident, val) in self._cont.items(): if not ind: break if val == min(ind): Cont[ident] = val ind.remove(val) self._cont = Cont # reorder the _ext list similarly if self._ext: ext = [] for ident in self._cont: if ident in self._ext: ext.append(ident) self._ext = ext def _parse_cont_seqof(self, text): """ parses the text corresponding to the content of an ASN.1 SEQUENCE OF or SET OF object sets an ASN1Obj directly in self._cont returns the rest of the text """ m = SYNT_RE_IDENT.match(text) if m: name = m.group(1) text = text[m.end():].strip() else: name = '_item_' # Comp = ASN1Obj(name=name, mode=MODE_TYPE, parent=self) Comp._text_def = text # _path_ext(['cont']) _path_stack([]) text = Comp.parse_def(text) _path_pop() _path_trunc(1) # self._cont = Comp.resolve() self._cont.__comp_chk() # # transfer ref from component to self self._ref.update( Comp._ref ) # return text def _parse_cont_choice(self, text): """ parses the text corresponding to the content of an ASN.1 CHOICE object sets an ASN1Dict with components in self._cont each self._cont item has the following format: component_name (str): ASN1Obj sets a (nested) list of extended component_name in self._ext returns the rest of the text """ rest, text_comps = extract_multi(text) if not text_comps: raise(ASN1ProcTextErr('{0}: empty CHOICE'\ .format(self.fullname()))) # initialize content and parse components self._cont = ASN1Dict() self._ext = None self.__parse_cont_comps(text_comps, self._type, False) # if not self._cont: # empty CHOICE are invalid raise(ASN1ProcTextErr('{0}: empty CHOICE'.format(self.fullname()))) # # transfer ref from components to self for Comp in self._cont.values(): self._ref.update( Comp._ref ) # self.__parse_cont_gen_root() self.__parse_cont_gen_ext() self.__parse_cont_gen_tag() # return rest def __parse_cont_gen_tag(self): # # (try to) apply automatic tagging first if GLOBAL.COMP['NS']['tag'] == TAG_AUTO: # if self is a SEQUENCE / SET / CHOICE # and at least 1 of the 3 first components is manually tagged, # then automatic tag does not apply... not_auto = False if self._type in (TYPE_SEQ, TYPE_SET, TYPE_CHOICE): for Comp in list(self._cont.values())[:3]: if Comp._tag is not None: not_auto = True break # if not not_auto: # automatic tagging from 0 t = 0 t_used = [] for Comp in self._cont.values(): if Comp._tag is not None: # manually tagged t_used.append(Comp._tag[0]) while t in t_used: t += 1 else: if Comp._type in (TYPE_CHOICE, TYPE_OPEN): Comp._tag = [t, TAG_CONTEXT_SPEC, TAG_EXPLICIT] else: Comp._tag = [t, TAG_CONTEXT_SPEC, TAG_IMPLICIT] t += 1 # # then check tag canonicity if self._type in (TYPE_CHOICE, TYPE_SET): # for CHOICE and SET, verifies that all components have distinct tags tag_db = set() for ident in self._cont: Comp = self._cont[ident] tag = Comp.get_tag() if tag is None: tag = Comp.get_tag_univ() # if tag is None: # untagged CHOICE / OPEN / ANY if Comp._type == TYPE_CHOICE: cho_tag_db = Comp.__choice_expand_tags() inter = tag_db.intersection( cho_tag_db ) if inter: raise(ASN1ProcTextErr('{0}: duplicate tags in CHOICE / SET with {1}, {2!r}'\ .format(self.fullname(), ident, inter))) else: asnlog('WNG: {0}.{1}, untagged OPEN / ANY in CHOICE / SET with {2}'\ .format(GLOBAL.COMP['NS']['mod'], self.fullname(), ident)) else: tag = tuple(tag) if tag in tag_db: raise(ASN1ProcTextErr('{0}: duplicate tag in CHOICE / SET with {1}, {2!r}'\ .format(self.fullname(), ident, tag))) else: tag_db.add( tag ) # elif self._type == TYPE_SEQ: # for SEQUENCE, verifies that all optional successive root components # have distinct tags prev_tag = None for ident in self._cont: Comp, err = self._cont[ident], False if Comp.is_opt() or prev_tag is not None: tag = Comp.get_tag() if tag is None: tag = Comp.get_tag_univ() if tag is None: # untagged CHOICE / OPEN / ANY if Comp._type == TYPE_CHOICE: tag = Comp.__choice_expand_tags() else: asnlog('WNG: {0}.{1}, untagged OPEN / ANY in SEQUENCE with {2}'\ .format(GLOBAL.COMP['NS']['mod'], self.fullname(), ident)) # if prev_tag is not None: if isinstance(prev_tag, set): if isinstance(tag, set): inter = prev_tag.intersection(tag) if inter: err = True elif isinstance(tag, list): if tuple(tag) in prev_tag: err = True elif isinstance(prev_tag, list): if isinstance(tag, set): inter = tag.intersection(tuple(prev_tag)) if inter: err = True elif isinstance(tag, list): if tag == prev_tag: err = True if err: raise(ASN1ProcTextErr('{0}: duplicate tag in SEQUENCE with {1}, {2!r}'\ .format(self.fullname(), ident, tag))) else: prev_tag = tag else: prev_tag = tag # if not Comp.is_opt(): prev_tag = None def __choice_expand_tags(self): tag_db = set() cho_cont = self.get_cont() for ident in cho_cont: Cho = cho_cont[ident] tag = Cho.get_tag() if tag is None: tag = Cho.get_tag_univ() if tag is None: # untagged CHOICE / OPEN / ANY if Cho._type == TYPE_CHOICE: tag_db.update( Cho.__choice_expand_tags() ) else: asnlog('WNG: {0}.{1}, untagged OPEN / ANY component in {2}'\ .format(GLOBAL.COMP['NS']['mod'], self.fullname(), ident)) else: tag_db.add( tuple(tag) ) return tag_db def _parse_cont_seq(self, text): """ parses the text corresponding to the content of an ASN.1 SEQUENCE object sets an ASN1Dict with components in self._cont each self._cont item has the following format: component_name (str): ASN1Obj sets a (nested) list of extended component_name in self._ext returns the rest of the text """ rest, text_comps = extract_multi(text) if text_comps is None: raise(ASN1ProcTextErr('{0}: no {1} content found'\ .format(self.fullname(), self._type))) # initialize content and parse components self._cont = ASN1Dict() self._ext = None self.__parse_cont_comps(text_comps, self._type, False) # # transfer ref from components to self for Comp in self._cont.values(): self._ref.update( Comp._ref ) # self.__parse_cont_gen_root() self.__parse_cont_gen_ext() self.__parse_cont_gen_tag() # return rest def __parse_cont_comps(self, comps, parent_type, extgroup=False): if not hasattr(self, '_compof_id'): self._compof_id = 0 groupid = 0 for comp in comps: # 1) get standard ASN.1 component name m = SYNT_RE_IDENT.match(comp) if m: name = m.group(1) if name in self._cont: raise(ASN1ProcTextErr('{0}: duplicated component name, {1}'\ .format(self.fullname(), name))) comp_t = comp[m.end():].strip() Comp = ASN1Obj(name=name, mode=MODE_TYPE, parent=self) Comp._text_def = comp_t self._cont[name] = Comp # _path_ext(['cont', name]) _path_stack([]) comp_t = Comp.parse_def(comp_t) comp_t = Comp._parse_cont_comp_flag(comp_t) _path_pop() _path_trunc(2) # self._cont[name] = Comp.resolve() if self._ext is not None: # extended component self._ext.append(name) # # 2) get COMPONENTS OF construction within SEQUENCE / SET elif parent_type != TYPE_CHOICE and comp[:13] == 'COMPONENTS OF': # lookup typeref and copy its components into C cont_len = len(self._cont) comp_t = self.__parse_cont_expand_compof(comp[13:].strip()) if self._ext is not None: self._ext.extend(\ [name for name in list(self._cont.keys())[cont_len:]]) # # 3) get extension marker elif not extgroup and comp[:3] == '...': name = '...' if self._ext is None: self._ext = [] else: # this multiple extension markers actually exist in some # ASN.1 spec (e.g. in ITU-T X series specs) #raise(ASN1ProcTextErr('{0}: duplicated extension marker'\ # .format(self.fullname()))) #asnlog('WNG: {0}.{1}, multiple extension markers'\ # .format(GLOBAL.COMP['NS']['mod'], self.fullname())) pass comp_t = comp[3:].strip() # # 4) get group of options elif not extgroup and self._ext is not None and \ comp[:2] == '[[' and comp[-2:] == ']]': og_text = comp[2:-2] # some specs have a dummy integer to identify the spec version # in the beginning of the group, we just remove it if we find one m = SYNT_RE_GROUPVERS.match(og_text) if m: #group_vers = int(m.group().split(':')[0]) og_text = og_text[m.end():].strip() # split optional components og_coma_off = [-1] + search_top_lvl_sep(og_text, ',') + \ [len(og_text)] og_comps = map(strip, [og_text[og_coma_off[i]+1:og_coma_off[i+1]] \ for i in range(len(og_coma_off)-1)]) # re-parse it cont_len = len(self._cont) self.__parse_cont_comps(og_comps, self._type, True) for (comp_name, Comp) in list(self._cont.items())[cont_len:]: Comp._group = groupid groupid += 1 comp_t = '' # else: raise(ASN1ProcTextErr( '{0}: no valid component identifier found, {1}'\ .format(self.fullname(), comp))) # # 5) ensure all the text has been processed if comp_t: raise(ASN1ProcTextErr( '{0}, component {1}: remaining textual definition, {2}'\ .format(self.fullname(), name, comp_t))) # if not extgroup: del self._compof_id def __parse_cont_expand_compof(self, text): # # 1) create a new object, even if we generally only get a typeref here CompOf = ASN1Obj(name='_compof_{0}'.format(self._compof_id), mode=MODE_TYPE, parent=self) CompOf._text_def = text self._compof_id += 1 self._cont[CompOf._name] = CompOf # # 2) ensure the SEQUENCE / SET to be expanded is not referencing a # formal parameter if GLOBAL.COMP['NS']['par']: parrefnum = sum([len(par['ref']) for \ par in GLOBAL.COMP['NS']['par'].values()]) # _path_ext(['cont', CompOf._name]) _path_stack([]) text = CompOf.parse_def(text) _path_pop() _path_trunc(2) # if GLOBAL.COMP['NS']['par'] and \ parrefnum != sum([len(par['ref']) for \ par in GLOBAL.COMP['NS']['par'].values()]): raise(ASN1NotSuppErr('{0}: COMPONENT OF with parameterization'\ .format(self.fullname()))) # del self._cont[CompOf._name] # # 3) ensure the SEQUENCE / SET to be expanded has the correct type and mode if CompOf._type != self._type or CompOf._mode != MODE_TYPE: raise(ASN1ProcTextErr('{0}: invalid COMPONENT OF definition, {1}'\ .format(self.fullname(), Comp._text_def))) # # 4) insert copies of components of CompOf into the content for (compof_name, CompOfObj) in CompOf.get_cont().items(): if compof_name in self._cont: raise(ASN1ProcTextErr('{0}: duplicated component name, {1}'\ .format(self.fullname(), compof_name))) assert( CompOfObj._type is not None ) self._cont[compof_name] = CompOfObj.copy() if GLOBAL.COMP['NS']['tag'] == TAG_AUTO and \ self._cont[compof_name]._tag is not None and \ self._cont[compof_name]._tag[1] == TAG_CONTEXT_SPEC: # delete context-specific tags of the components, # as they will be retagged automatically with this class in self self._cont[compof_name]._tag = None self._cont[compof_name]._parent = self # delete all subtype constraints self._cont[compof_name]._const = [] # return text def _parse_cont_comp_flag(self, text): """ parses the text corresponding to the potential specific behaviour of the ASN.1 constructed component: OPTIONAL or DEFAULT $abcd_gros_merdier. sets one item in self._flag the item has one of the following format: FLAG_OPT: None for OPTIONAL behaviour FLAG_DEF_STR: str for DEFAULT value (value unparsed) returns the rest of the text """ if text[:8] == 'OPTIONAL': if self._flag is None: self._flag = {FLAG_OPT: None} else: self._flag[FLAG_OPT] = None text = text[8:].strip() return text # elif text[:7] == 'DEFAULT': if self._flag is None: self._flag = {FLAG_DEF: None} else: self._flag[FLAG_DEF] = None _path_ext(['flag', FLAG_DEF]) text = self.parse_value(text[7:].strip()) _path_trunc(2) return text # else: return text def __comp_chk(self): # verify there is no CLASS object defined / referenced within ASN.1 # standard constructed types assert( self._parent is not None ) if self._parent._type in (TYPE_SEQ, TYPE_SET, TYPE_SEQ_OF, TYPE_SET_OF, TYPE_CHOICE): if self._type in (TYPE_CLASS, TYPE_TYPEIDENT, TYPE_ABSSYNT): raise(ASN1ProcTextErr('{0}: CLASS object within ASN.1 constructed object'\ .format(self.fullname()))) elif self._mode != MODE_TYPE: raise(ASN1ProcTextErr('{0}: mode {1} object within ASN.1 constructed object'\ .format(self.fullname()))) def _parse_cont_set(self, text): """ parses the text corresponding to the content of an ASN.1 SET object sets an ASN1Dict with components in self._cont each self._cont item has the following format: component_name (str): ASN1Obj all components are put in their canonical tag order sets a (nested) list of extended component_name in self._ext returns the rest of the text """ rest, text_comps = extract_multi(text) if text_comps is None: raise(ASN1ProcTextErr('{0}: no {1} content found'\ .format(self.fullname(), self._type))) # initialize content and parse components self._cont = ASN1Dict() self._ext = None self.__parse_cont_comps(text_comps, self._type, False) # # transfer ref from components to self for Comp in self._cont.values(): self._ref.update( Comp._ref ) # self.__parse_cont_gen_root() self.__parse_cont_gen_ext() self.__parse_cont_gen_tag() # return rest def _parse_cont_class(self, text): """ parses the text corresponding to the content of an ASN.1 CLASS object sets an ASN1Dict with CLASS fields in self._cont each self._cont item has the following format: field_name (str): ASN1Obj returns the rest of the text """ rest, fields = extract_multi(text) if not fields: raise(ASN1ProcTextErr('{0}: CLASS content cannot be empty'\ .format(self.fullname()))) # self._cont = ASN1Dict() self._ext = None ref_class_intern = [] # # 1) do a 1st iteration to compile each field properly, except for those # referring to another local field (ASN1RefClassIntern) to be placed # in ref_class_intern for field in fields: # 1.1) get name m = SYNT_RE_CLASSFIELDIDENT.match(field) if not m: raise(ASN1ProcTextErr('{0}: invalid CLASS field name, {1}'\ .format(self.fullname(), field))) name = m.group(1) field = field[m.end():].strip() # # 1.2) create an empty ASN1Obj instance and place it in the content # with the current CLASS object `self' as parent Field = ASN1Obj(name=name, parent=self) Field._text_def = field self._cont[name] = Field # # 1.3) set the path in the namespace for processing Field _path_ext(['cont', name]) # we need to stack a new path, because the following processing is a # variant of the standard parse_def() method _path_stack([]) # # 1.4) get potential tag (a priori useless for CLASS object) field = Field._parse_tag(Field._text_def) # # 1.5) filter out ASN1RefClassIntern and ASN1RefClassField into the # same CLASS m1 = SYNT_RE_CLASSFIELDREFINT.match(field) m2 = SYNT_RE_CLASSFIELDREF.match(field) if m1 or (m2 and m2.group(2) == self._name): ref_class_intern.append( (name, Field, field) ) else: # 1.6) get potential type or typeref if name[0].isupper() and (not field or \ re.match('(UNIQUE|OPTIONAL|DEFAULT)(\s{1,}|$)', field)): # no type is provided, so this is must be an OPEN TYPE Field._mode = MODE_TYPE Field._type = TYPE_OPEN else: field = Field._parse_type(field) # 1.7) mode and type determination for defined fields if name[0].isupper(): Field._mode = MODE_SET elif isinstance(Field._typeref, ASN1RefType) and \ str(Field._typeref.called).isupper(): # some ASN.1 inconsistency here (not the single one) #Field._type = TYPE_CLASS # will be resolved afterwards Field._mode = MODE_TYPE else: Field._mode = MODE_VALUE # # 1.8) continue processing the content, constraints, flags field = Field._parse_cont(field) while field and field[0:1] == '(': # mutliple constraints can be specified field = Field._parse_const(field) # # 1.9) overwrite the content with the compiled Field self._cont[name] = Field.resolve() # # 1.10) finally process the potential flag field = self._cont[name]._parse_cont_field_flag(field) if field: raise(ASN1ProcTextErr('{0}.&{1}: unprocessed definition, {2}'\ .format(self.fullname(), name, field))) # # 1.11) restore the path in the namespace _path_pop() _path_trunc(2) # # 2) do a 2nd iteration over fields referring to another local one for (name, Field, field) in ref_class_intern: # # 2.1) set the path in the namespace _path_ext(['cont', name]) _path_stack([]) # # 2.2) get potential type or typeref field = Field._parse_type(field) # # 2.3) mode and type determination if name[0].isupper(): Field._mode = MODE_SET else: Field._mode = MODE_VALUE # # 2.4) continue processing the content, constraints, flags field = Field._parse_cont(field) while field and field[0:1] == '(': # mutliple constraints can be specified field = Field._parse_const(field) # # 2.5) overwrite the content with the compiled Field self._cont[name] = Field.resolve() # # 2.6) process the potential flag field = self._cont[name]._parse_cont_field_flag(field) if field: raise(ASN1ProcTextErr('{0}.&{1}: unprocessed definition, {2}'\ .format(self.fullname(), name, field))) # # 2.7) restore the path in the namespace _path_pop() _path_trunc(2) # # 3) transfer ref from components to self for Comp in self._cont.values(): self._ref.update( Comp._ref ) # # 4) generate the root mandatory / optional lists self.__parse_cont_gen_root() # return rest def _parse_cont_field_flag(self, text): """ parses the text corresponding to the potential specific behaviour of the ASN.1 CLASS field: OPTIONAL, DEFAULT $gros_merdier_degueu or UNIQUE. sets one or two item(s) from the following in self._flag each item has the following format: FLAG_UNIQ: None for UNIQUE FLAG_OPT: None for OPTIONAL FLAG_DEF_STR: str for DEFAULT value (value unparsed) returns the rest of the text """ if text[:6] == 'UNIQUE': if self._flag is None: self._flag = {FLAG_UNIQ: None} else: self._flag[FLAG_UNIQ] = None text = text[6:].strip() if text[:8] == 'OPTIONAL': self._flag[FLAG_OPT] = None text = text[8:].strip() return text else: return text # elif text[:8] == 'OPTIONAL': if self._flag is None: self._flag = {FLAG_OPT: None} else: self._flag[FLAG_OPT] = None text = text[8:].strip() if text[:6] == 'UNIQUE': self._flag[FLAG_UNIQ] = None else: return text # elif text[:7] == 'DEFAULT': if self._flag is None: self._flag = {FLAG_DEF: None} else: self._flag[FLAG_DEF] = None _path_ext(['flag', FLAG_DEF]) # if self._mode == MODE_VALUE: text = self.parse_value(text[7:].strip()) # elif self._mode == MODE_SET: text, settext = extract_curlybrack(text[7:].strip()) if settext is None: raise(ASN1ProcTextErr('{0}: invalid set, {1}'\ .format(self.fullname(), text[7:].strip()))) self.parse_set(settext) # else: # MODE_TYPE object definition Obj = ASN1Obj(name=self._name, mode=MODE_TYPE) Obj._text_def = text[7:].strip() self._flag[FLAG_DEF] = Obj _path_stack([]) text = Obj.parse_def(Obj._text_def) _path_pop() self._flag[FLAG_DEF] = Obj.resolve() # _path_trunc(2) return text # else: return text #--------------------------------------------------------------------------# # ASN.1 syntactic parser for CLASS syntax #--------------------------------------------------------------------------# def _parse_class_syntax(self, text): if text[:11] != 'WITH SYNTAX': return text text = text[11:].strip() rest, text = extract_curlybrack(text) if not text: raise(ASN1ProcTextErr('{0}: WITH SYNTAX without actual content'\ .format(self.fullname()))) # remove comas, as they are just here for beauty ! text = text.replace(',', '') # replace any kind of space(s) with single white space text = re.subn('\s{1,}', ' ', text)[0] # self._syntax = [] # self._synt_text = text self._synt_off = 0 self._synt_cur = self._syntax self._synt_depth = 0 self._synt_fn = [] # self.__parse_class_syntax_grp() # if any([fn not in self._synt_fn for fn in self._root_mand]): #raise(ASN1ProcTextErr('{0}: some mandatory field(s) not in SYNTAX'\ # .format(self.fullname()))) pass # del self._synt_text, self._synt_off, self._synt_cur, self._synt_depth, self._synt_fn # return rest def __parse_class_syntax_grp(self): # cap = [] # while self._synt_off < len(self._synt_text): m = SYNT_RE_CLASSSYNTAX.match(self._synt_text[self._synt_off:]) if not m or not m.group(1): raise(ASN1ProcTextErr('{0}: invalid WITH SYNTAX expression, {1}'\ .format(self.fullname(), self._synt_text[self._synt_off:]))) # elif m.group(2): # [ : opt_open self._synt_off += m.end() if cap: self._synt_cur.append(' '.join(cap)) cap = [] synt_cur = self._synt_cur self._synt_cur.append([]) self._synt_cur = self._synt_cur[-1] self._synt_depth += 1 self.__parse_class_syntax_grp() self._synt_depth -= 1 self._synt_cur = synt_cur # elif m.group(3): # ] : opt_close self._synt_off += m.end() if cap: self._synt_cur.append(' '.join(cap)) cap = [] return # elif m.group(4): # capital word self._synt_off += m.end() cap.append(m.group(4)) # elif m.group(6): # &[fF]ieldName self._synt_off += m.end() if cap: self._synt_cur.append(' '.join(cap)) cap = [] elif self._synt_depth: asnlog('WNG: {0}.{1}, no starting SYNTAX keyword for optional field {1}'\ .format(GLOBAL.COMP['NS']['mod'], self.fullname(), m.group(6))) # do some control on the corresponding field fn = m.group(6) if fn not in self._cont: raise(ASN1ProcTextErr('{0}: SYNTAX field name not in content, {1}'\ .format(self.fullname(), fn))) #pass elif self._synt_depth > 0 and fn not in self._root_opt: raise(ASN1ProcTextErr('{0}: field {1}, optional in SYNTAX but not in content'\ .format(self.fullname(), fn))) #pass elif self._synt_depth == 0 and fn in self._root_opt: asnlog('WNG: {0}.{1}, field {1}, optional in content but not in SYNTAX'\ .format(GLOBAL.COMP['NS']['mod'], self.fullname(), fn)) #pass self._synt_fn.append(fn) # self._synt_cur.append('&' +fn) # else: assert() # if cap: self._synt_cur.append(' '.join(cap)) #--------------------------------------------------------------------------# # ASN.1 syntactic parser for constraints #--------------------------------------------------------------------------# def _parse_const(self, text): """ parses the next ASN.1 object constraint in text, between parenthesis appends a dict in self._const with at least the following keywords: text, type, keys returns the rest of the text """ text, text_const = extract_parenth(text) if text_const is None: return text const = {'text': text_const} m = SYNT_RE_CONST_DISPATCH.match(text_const) if m is None: if not text_const: raise(ASN1ProcTextErr('{0}: invalid empty constraint'\ .format(self.fullname()))) elif self._typeref and \ isinstance(self._typeref, ASN1RefClassField) and \ const['text'][0:1] == '{': # table constraint for CLASS.&field self._parse_const_table(const) else: # (set of) value(s) self._parse_const_val(const) elif m.group(1): # INCLUDES: (set of) value(s) self._parse_const_val(const) elif m.group(2): # SIZE: (set of) INTEGER value(s) self._parse_const_size(const) elif m.group(3): # FROM self._parse_const_alphabet(const) elif m.group(4): # WITH COMPONENTS self._parse_const_withcomps(const) elif m.group(5): # WITH COMPONENT self._parse_const_withcomp(const) elif m.group(6): # PATTERN self._parse_const_regexp(const) elif m.group(7): # SETTINGS # TODO: check which ASN.1 type can get such a constraint self._parse_const_property(const) elif m.group(8): # CONTAINING self._parse_const_containing(const) elif m.group(9): # ENCODED BY self._parse_const_encodedby(const) elif m.group(10): # CONSTRAINED BY self._parse_const_userconst(const) else: assert() return text def _parse_const_val(self, const): # single value and type inclusion constraints can be mixed together # within a single constraint syntactic definition # multiple single value(s) and/or type inclusion(s) are separated # with "|" const_index = len(self._const) self._const.append(const) const['type'] = CONST_VAL const['keys'] = ['root', 'ext', 'excl'] const['root'] = [] const['ext'] = None const['excl'] = False # # simply use parse_set() to parse the textual values _path_ext(['const', const_index]) if const['text'][:10] == 'ALL EXCEPT': # for the ALL EXCEPT case, 2 variants (like with the SIZE constraint) # the value can be between parenthesis or not... const['excl'] = True text = const['text'][10:].strip() if text[0] == '(': rest, text = extract_parenth(const['text'][10:]) if text: self.parse_set(text) else: raise(ASN1ProcTextErr('{0}: invalid ALL EXCEPT syntax, {1}'\ .format(self.fullname(), const['text']))) if rest: raise(ASN1ProcTextErr('{0}: invalid ALL EXCEPT syntax, {1}'\ .format(self.fullname(), const['text']))) else: self.parse_set(text) else: self.parse_set(const['text']) _path_trunc(2) def _parse_const_table(self, const): const_index = len(self._const) self._const.append(const) const['type'] = CONST_TABLE const['keys'] = ['tab', 'at', 'exc'] const['tab'] = None const['at'] = None const['exc'] = None # the 1st part of the constraint is a (serie of) set(s) defined in curlybrackets, # the 2nd optional part is after "@", an identifier defined in curlybrackets again, # the 3rd optional part is after "!", the exception part # e.g.: {ValSet1|ValSet2, ..., ValSet3|Val4}{@identifier}!exceptionCase # # we create a generic ASN1Obj instance of type classref, which will # get all values from value(s) and set(s) of values thanks to parse_set() # # 1) get the CLASS object set(s) text, text_set = extract_curlybrack(const['text']) if not text_set: raise(ASN1ProcTextErr( '{0}: invalid table constraint, no set defined, {1}'\ .format(self.fullname(), const['text']))) # # 2) create a new CLASS object, to host a set of CLASS values to be looked-up in, # and referencing the ClassRef ClassRef = self.get_classref() assert( ClassRef is not None ) # use the the ClassRef CLASS as a typeref for an new CLASS set object if hasattr(ClassRef, '_mod') and ClassRef._mod: _TabSet = ASN1Obj(name='_tab_{0}'.format(ClassRef._name), mode=MODE_SET, type=TYPE_CLASS) _TabSet._typeref = ASN1RefType((ClassRef._mod, ClassRef._name)) _TabSet._text_def = ClassRef._text_def TabSet = CLASS(_TabSet) else: # WNG: ClassRef._mod is not always defined, e.g. when ClassRef is a parameter TabSet = ClassRef.copy() TabSet._mode = MODE_SET TabSet._ref = set() const['tab'] = TabSet # _path_ext(['const', const_index, 'tab', 'val']) _path_stack(['val']) TabSet.parse_set(text_set) _path_pop() _path_trunc(4) # # 3) transfer any reference(s) from TabSet to self if TabSet._ref: self._ref.update( TabSet._ref ) # # 4) get at "@" optionally text, at_name = extract_curlybrack(text) if at_name and at_name[0:1] == '@': at_name = at_name[1:].split('.') # ensure the chain of at_name items matches with the content if at_name[0] == '': # path starting with . is relative const['at'] = ['..'] + at_name[1:] parent = self._parent at_name = at_name[1:] else: # complete path starting from the root object: # count the number of parent until the root object lvl = 0 obj = self while obj._parent is not None: lvl += 1 obj = obj._parent const['at'] = ['..'] * lvl + at_name parent = obj for atn in at_name: if parent is None or atn not in parent._cont: raise(ASN1ProcTextErr( '{0}: undefined field reference for table constraint, {1}'\ .format(self.fullname(), at_name))) else: parent = parent._cont[atn] # # 5) get exception case "!" optionally if text[0:1] == '!': # TODO: parse exception case const['exc'] = text[1:].strip() asnlog('INFO: {0}.{1}, unprocessed table constraint exception'\ .format(GLOBAL.COMP['NS']['mod'], self.fullname())) elif text: raise(ASN1ProcTextErr('{0}: remaining text for table constraint, {1}'\ .format(self.fullname(), text))) def _parse_const_size(self, const): const_index = len(self._const) self._const.append(const) const['type'] = CONST_SIZE const['keys'] = ['root', 'ext'] const['root'] = [] const['ext'] = None # # 1) remove the SIZE identifier text = const['text'][4:].strip() rest, text = extract_parenth(text) if not text: raise(ASN1ProcTextErr('{0}: invalid SIZE constraint, no size defined, {1}'\ .format(self.fullname(), const['text']))) # # 2) create a proxy INTEGER object that will parse the textual value(s) ObjProxy = ASN1Obj(name='_size_{0}'.format(self._name), mode=MODE_SET, type=TYPE_INT) # _path_ext(['const', const_index]) _path_stack(['val']) ObjProxy.parse_set(text) assert( isinstance(ObjProxy._val, dict) ) _path_pop() _path_trunc(2) # # 3) transfer the parsed set of values from ObjProxy to self if ObjProxy._val['root']: self.select_set(['const', const_index, 'root'], ObjProxy._val['root']) if ObjProxy._val['ext'] is not None: self.select_set(['const', const_index, 'ext'], ObjProxy._val['ext']) # # 4) transfer references from ObjProxy to self if ObjProxy._ref: self._ref.update( ObjProxy._ref ) def _parse_const_alphabet(self, const): const_index = len(self._const) self._const.append(const) const['type'] = CONST_ALPHABET const['keys'] = ['root', 'ext'] const['root'] = [] const['ext'] = None # # 1) remove the FROM identifier text = const['text'][4:].strip() rest, text = extract_parenth(text) if not text: raise(ASN1ProcTextErr('{0}: invalid ALPHABET constraint, {1}'\ .format(self.fullname(), const['text']))) # # 2) create a proxy object that will parse the textual value(s) ObjProxy = ASN1Obj(name='_alpha_{0}'.format(self._name), mode=MODE_SET, type=self._type) # _path_ext(['const', const_index]) _path_stack(['val']) ObjProxy.parse_set(text) assert( isinstance(ObjProxy._val, dict) ) _path_pop() _path_trunc(2) # # WNG: some specs use FROM ("abcd"), some other FROM ("a"|"b"|"c"|"d")... # 3) transfer the parsed set of values from ObjProxy to self if ObjProxy._val['root']: rv = [] for v in ObjProxy._val['root']: if isinstance(v, ASN1Range) or len(v) == 1: rv.append(v) else: # decompose the string into single char if needed [rv.append(c) for c in v] self.select_set(['const', const_index, 'root'], rv) if ObjProxy._val['ext'] is not None: ev = [] for v in ObjProxy._val['ext']: if isinstance(v, ASN1Range) or len(v) == 1: ev.append(v) else: # decompose the string into single char if needed [ev.append(c) for c in v] self.select_set(['const', const_index, 'ext'], ev) # # 4) transfer references from ObjProxy to self if ObjProxy._ref: self._ref.update( ObjProxy._ref ) def _parse_const_withcomp(self, const): const_index = len(self._const) self._const.append(const) const['type'] = CONST_COMP const['keys'] = [] # TODO asnlog('INFO: {0}.{1}, unprocessed WITH COMPONENT constraint'\ .format(GLOBAL.COMP['NS']['mod'], self.fullname())) def _parse_const_withcomps(self, const): const_index = len(self._const) self._const.append(const) const['type'] = CONST_COMPS const['keys'] = ['root', 'ext'] const['root'] = [] const['ext'] = None # # 1) split potential mutliple WITH COMPONENTS constraints, # OR-ed and / or extended # sometimes, the 2nd, 3rd, ... comes within additional parenthesis try: text_comps = extract_set(const['text']) except Exception as err: raise(ASN1ProcTextErr('{0}: {1}'.format(self.fullname(), err))) # # 2) collect comps in the root domain for rc in text_comps['root']: if rc[0:1] == '(' and rc[-1:] == ')': rc = rc[1:-1].strip() # initialize the container to store constraint present / absent list # of identifier, and identifier with additional constraints const_comp = {'_pre': [], '_abs': []} const['root'].append(const_comp) # parse each component _path_ext( ['const', const_index, 'root', len(const['root'])-1] ) self.__parse_const_withcomps_comp(rc, const_comp) _path_trunc(4) # # 3) collect comps in the ext domain if text_comps['ext'] == []: const['ext'] = [] elif text_comps['ext']: const['ext'] = [] for ec in text_comps['ext']: if ec[0:1] == '(' and ec[-1:] == ')': ec = ec[1:-1].strip() # initialize the container to store constraint present / absent list # of identifier, and identifier with additional constraints const_comp = {'_pre': [], '_abs': []} const['ext'].append(const_comp) # parse each extended component _path_ext( ['const', const_index, 'ext', len(const['ext'])-1] ) self.__parse_const_withcomps_comp(ec, const_comp) _path_trunc(4) def __parse_const_withcomps_comp(self, text, const): # 1) check the WITH COMPONENTS identifier if text[:15] != 'WITH COMPONENTS': raise(ASN1ProcTextErr('{0}: invalid WITH COMPONENTS constraint, {1}'\ .format(self.fullname(), text))) text = text[15:].strip() # # 2) extract the components' parts rest, comps = extract_multi(text) if not comps: raise(ASN1ProcTextErr('{0}: empty WITH COMPONENTS constraint'\ .format(self.fullname()))) # # 3) get the content of the original constructed object cont = self.get_cont() # # 4) check for partial components if comps[0] == '...': partial = True comps = comps[1:] else: partial = False # # 5) initialize presence lists present = [] potent = [] absent = [] opt = [] done = [] # # 6) check all components for comp in comps: ident = comp.split(' ', 1)[0] if ident not in cont: raise(ASN1ProcTextErr( '{0}: invalid ident in WITH COMPONENTS constraint, {1}'\ .format(self.fullname(), ident))) elif ident in done: raise(ASN1ProcTextErr( '{0}: duplicated ident in WITH COMPONENTS constraint, {1}'\ .format(self.fullname(), ident))) comp = comp[len(ident):].strip() if not comp: # 6.1) presence-only format potent.append(ident) else: # 6.2) use the constructed Component object to parse additional # constraint(s) Comp = cont[ident] # # remove already existing constraints and references from the Component CompConst = Comp._const CompRef = Comp._ref Comp._const = [] Comp._ref = set() # _path_ext([ident]) const_ind = 0 while comp[:1] == '(': _path_stack([]) try: comp = Comp._parse_const(comp) except ASN1ProcLinkErr as err: Comp._const = CompConst Comp._ref = CompRef raise(err) _path_pop() const_ind += 1 _path_trunc(1) # # 6.3) transfer any additional constraint(s) to the local const dict if Comp._const: # The is needed to transfer the additional constraints # into a dict with a single key 'const', in order to comply # with the way pathes are handled into _parse_const() methods const[ident] = {} const[ident]['const'] = Comp._const # # 6.4) transfer any new reference(s) from Comp to the local object if Comp._ref: self._ref.update( Comp._ref ) # # 6.5) restore original constraints and references from the Component Comp._const = CompConst Comp._ref = CompRef # # 6.6) handle PRESENT / ABSENT / OPTIONAL keyword if not comp: potent.append(ident) elif comp[:7] == 'PRESENT': present.append(ident) comp = comp[7:].strip() elif comp[:6] == 'ABSENT': absent.append(ident) comp = comp[6:].strip() elif comp[:8] == 'OPTIONAL': opt.append(ident) comp = comp[8:].strip() if comp: raise(ASN1ProcTextErr( '{0}: invalid text in WITH COMPONENTS constraint, {1}'\ .format(self.fullname(), comp))) done.append(ident) # # 7) determine the PRESENCE / ABSENCE context depending of the # constructed type if self._type == TYPE_CHOICE: if opt: raise(ASN1ProcTextErr( '{0}: invalid OPTIONAL marker for components, {1}'\ .format(self.fullname(), opt))) self.__parse_const_withcomps_choice(const, present, potent, absent) else: # SEQUENCE, SET self.__parse_const_withcomps_seq(const, partial, present, potent, absent, done) # # 8) ensure no more text exists if rest: raise(ASN1ProcTextErr('{0}: remaining text for WITH COMPONENTS constraint, {1}'\ .format(self.fullname(), rest))) def __parse_const_withcomps_choice(self, const, present, potent, absent): if len(present) > 1: raise(ASN1ProcTextErr( '{0}: invalid multiple PRESENT components, {1}'\ .format(self.fullname(), present))) elif len(present) == 1: const['_pre'].append(present[0]) # all other components must be marked ABSENT for comp_name in self.get_cont(): if comp_name != present[0]: const['_abs'].append(comp_name) elif potent: # all other components must be marked ABSENT for comp_name in self.get_cont(): if comp_name not in potent: const['_abs'].append(comp_name) elif absent: for comp_name in self.get_cont(): if comp_name in absent: const['_abs'].append(comp_name) def __parse_const_withcomps_seq(self, const, partial, present, potent, absent, done): cont = self.get_cont() if self._type in (TYPE_SEQ, TYPE_REAL, TYPE_EXT, TYPE_EMB_PDV, TYPE_CHAR_STR): # ensure the correct order of components has been respected idents = list(cont.keys()) ind_prev = -1 for ident in done: ind = idents.index(ident) if ind < ind_prev: raise(ASN1ProcTextErr( '{0}: invalid order of components in WITH COMPONENTS'\ .format(self.fullname()))) else: ind_prev = ind if not partial: # ensure all mandatory components have been constrained if not all([ident in done for ident in self.get_root_mand()]): asnlog('WNG: {0}.{1}, missing mandatory components in WITH COMPONENTS'\ .format(GLOBAL.COMP['NS']['mod'], self.fullname())) raise(ASN1ProcTextErr( '{0}: missing mandatory components in WITH COMPONENTS'\ .format(self.fullname()))) for ident in self.get_root_opt(): # all OPTIONAL / DEFAULT components not listed in done are # considered ABSENT Comp = cont[ident] if ident not in done: const['_abs'].append(ident) # all OPTIONAL / DEFAULT components listed in potent are # considered PRESENT if ident in potent: const['_pre'].append(ident) for ident in present: if cont[ident].is_opt(): const['_pre'].append(ident) for ident in absent: if FLAG_OPT not in cont[ident]._flag or FLAG_DEF in cont[ident]._flag: raise(ASN1ProcTextErr('{0}: invalid ABSENT component, {1}'\ .format(self.fullname(), ident))) else: const['_abs'].append(ident) def _parse_const_regexp(self, const): const_index = len(self._const) self._const.append(const) const['type'] = CONST_REGEXP const['keys'] = [] # TODO asnlog('INFO: {0}.{1}, unprocessed PATTERN constraint'\ .format(GLOBAL.COMP['NS']['mod'], self.fullname())) def _parse_const_property(self, const): const_index = len(self._const) self._const.append(const) const['type'] = CONST_PROPERTY const['keys'] = [] # TODO asnlog('INFO: {0}.{1}, unprocessed SETTINGS constraint'\ .format(GLOBAL.COMP['NS']['mod'], self.fullname())) def _parse_const_containing(self, const): const_index = len(self._const) self._const.append(const) const['type'] = CONST_CONTAINING const['keys'] = ['obj', 'enc'] const['obj'] = None const['enc'] = None # content of the constraint contain an unnamed ASN.1 object definition # # 1) extract potential encoding textual directive and object textual # description m = re.search('ENCODED BY', const['text']) if m: text_obj = const['text'][10:m.start()].strip() text_enc = const['text'][m.end():].strip() # 2) create a proxy OID object that will parse the encoding value ObjProxy = ASN1Obj(name='_enc_{0}'.format(self._name), mode=MODE_VALUE, type=TYPE_OID) # _path_ext(['const', const_index, 'enc']) _path_stack(['val']) ObjProxy.parse_value(text_enc) assert( ObjProxy._val is not None ) _path_pop() _path_trunc(2) # # 3) transfer the parsed OID value from ObjProxy to self const['enc'] = ObjProxy._val # # 4) transfer references from ObjProxy to self if ObjProxy._ref: self._ref.update( ObjProxy._ref ) else: text_obj = const['text'][10:].strip() # # 5) create an object and parse its textual definition # CONTAINING objects may need to reference the parent of self Obj = ASN1Obj(name='_cont_{0}'.format(self._name), mode=MODE_TYPE, parent=self._parent) Obj._text_def = text_obj const['obj'] = Obj # _path_ext(['const', const_index, 'obj']) _path_stack([]) rest = Obj.parse_def(text_obj) _path_pop() _path_trunc(3) # if rest: raise(ASN1ProcTextErr( '{0}, CONTAINING constraint: remaining textual definition, {1}'\ .format(self.fullname(), rest))) const['obj'] = Obj.resolve() # # 3) copy references from Obj in self if Obj._ref: self._ref.update( Obj._ref ) def _parse_const_encodeby(self, const): const_index = len(self._const) self._const.append(const) const['type'] = CONST_ENCODEBY const['keys'] = ['enc'] const['enc'] = None # TODO asnlog('INFO: {0}.{1}, unprocessed ENCODE BY constraint'\ .format(GLOBAL.COMP['NS']['mod'], self.fullname())) def _parse_const_userconst(self, const): const_index = len(self._const) self._const.append(const) const['type'] = CONST_CONSTRAIN_BY const['keys'] = ['user', 'exc'] const['user'] = None const['exc'] = None # TODO asnlog('INFO: {0}.{1}, unprocessed CONSTRAINED BY constraint'\ .format(GLOBAL.COMP['NS']['mod'], self.fullname())) #--------------------------------------------------------------------------# # ASN.1 syntactic parser for values #--------------------------------------------------------------------------# def parse_value(self, text): """ parses the text corresponding to an ASN.1 value for self and put the result in the current last path (GLOBAL.COMP['NS']['path'][-1]) """ if self._type in self._PARSE_VALUE_DISPATCH: return getattr(self, self._PARSE_VALUE_DISPATCH[self._type])(text) else: raise(ASN1ObjErr('{0}: undefined type, {1}'\ .format(self.fullname(), self._type))) def _parse_value_ref(self, text, type_expected=None): # when text, the textual value, is a reference to a formal identifier or # a global identifier # type_expected: None or TYPE_* or list of TYPE_* # # 0) get identifier m = SYNT_RE_IDENT.match(text) if not m: m = SYNT_RE_IDENTEXT.match(text) if not m: raise(ASN1ProcTextErr('{0}: invalid value reference for {1}, {2}'\ .format(self.fullname(), self._type, text))) else: # get the ref to the external module and identifier valmod = m.group(2) ident = m.group(3) else: valmod = None ident = m.group(1) # # 1) get remaining text and potential formal param text = text[m.end():].strip() param = GLOBAL.COMP['NS']['par'] # # 2) identifier is a reference to a local formal parameter if param and ident in param and valmod is None: Gov = param[ident]['gov'] idents = [ident] # # 2.1) in case the formal param is a CLASS value, the text can reference # fields (iteratively) within this CLASS value # myParamValue {MYCLASS:myClass} ::= myClass.&myValue.&mySubValue while text[:2] == '.&': # ensure the parameter governor (and inner field, when iterating) # is a CLASS value self.__parse_value_ref_typechk(Gov, '.&'.join(idents), TYPE_CLASS) # get the field name to be selected within the param gov text = text[2:] m = SYNT_RE_WORD.match(text) if not m: raise(ASN1ProcTextErr( '{0}: invalid value field reference within parameter {1}, {2}'\ .format(self.fullname(), '.&'.join(idents), text))) idents.append( m.group(1) ) text = text[m.end():].strip() try: Gov = Gov.get_cont()[idents[-1]] except KeyError: raise(ASN1ProcTextErr( '{0}: undefined value field reference within parameter {1}, {2}'\ .format(self.fullname(), '.&'.join(idents[:-1]), idents[-1]))) # # 2.2) ensure the parameter gov has the same type as self self.__parse_value_ref_typechk(Gov, '.&'.join(idents), type_expected) # # 2.3) build the path selected in the called reference in case of # CLASS internal field selection ced_path = [] if len(idents) > 1: ced_path.extend(idents[1:]) # # 2.4) create a reference for the value ref = ASN1RefValue(ASN1RefParam(ident), ced_path) # # 2.5) set the ref as the value self.select_set(_path_cur(), ref) # # 2.6) update the list of the parameter referrers with a copy of the # current path param[ident]['ref'].append( _path_root()[:] ) # # 3) identifier is a reference to a global identifier else: # 3.1) get the module and value object from GLOBAL if valmod is None: try: valmod = GLOBAL.COMP['NS']['obj'][ident] except KeyError: raise(ASN1ProcTextErr('{0}: value {1}, undefined'\ .format(self.fullname(), ident))) try: valobj = get_asnobj(valmod, ident) except ASN1Err as Err: raise(ASN1ProcTextErr('{0}: {1}'\ .format(self.fullname(), Err))) if valobj._val is None: # value object not yet compiled raise(ASN1ProcLinkErr('{0}: value {1}'\ .format(self.fullname(), ident))) # # 3.2) check if valobj is a parameterized value if valobj._param: # # 3.2.1) rebuild the list of formal parameters self._params_form = list(valobj._param.values()) # # 3.2.2) bind valobj into self # and duplicate the parameterized part of valobj path_cur = _path_cur() self.select_set(path_cur, valobj._val) for param in self._params_form: for path in param['ref']: assert( path[0] == 'val' ) path = path_cur + path[1:] self.select_set(path[0:1], _get_path_copy(self, path)) # # 3.2.3) do the parameterization text = self._parameterize(text) # # 3.2.4) some clean-up del self._params_form # # 3.3) valobj is just a referenced value else: val = valobj._val # # 3.3.1) in case the text is a CLASS value, the text can reference # fields (iteratively) within this CLASS value # myValue ::= myClass.&myValue.&mySubValue idents = [ident] while text[:2] == '.&': # ensure the selected global object (and inner field, when iterating) # is a CLASS value self.__parse_value_ref_typechk(valobj, '.&'.join(idents), TYPE_CLASS) # get the field name to be selected within valobj text = text[2:] m = SYNT_RE_WORD.match(text) if not m: raise(ASN1ProcTextErr( '{0}: invalid value field reference within {2}, {3}'\ .format(self.fullname(), ident, text))) idents.append( m.group(1) ) text = text[m.end():].strip() try: valobj = valobj.get_cont()[idents[-1]] val = val[idents[-1]] except KeyError: raise(ASN1ProcTextErr( '{0}: undefined value field reference within {2}, {3}'\ .format(self.fullname(), ident, ident_field))) # # 3.3.2) ensure valobj has the same type as self self.__parse_value_ref_typechk(valobj, '.&'.join(idents), type_expected) # self.select_set(_path_cur(), val) # # 3.3.3) keep track of the value reference if len(idents) > 1: self._ref.add( ASN1RefValue((valmod, ident), idents[1:]) ) else: self._ref.add( ASN1RefValue((valmod, ident)) ) # return text def __parse_value_ref_typechk(self, obj, ident, type_exp=None): if type_exp is None: type_exp = self._type if obj._mode != MODE_VALUE: raise(ASN1ProcTextErr( '{0}: value identifier {1}, mode mismatch, {2} instead of VALUE'\ .format(self.fullname(), ident, obj._mode))) if type_exp == TYPE_ANY and obj._type != type_exp: # ASN.1 1988 old-school construction raise(ASN1NotSuppErr( '{0}: ASN.1 1988 ANY type assigned with another type\'s value, '\ '{1} of type {2} and value {3}'\ .format(self.fullname(), ident, obj._type, obj._val))) elif (isinstance(type_exp, str_types) and obj._type != type_exp) or \ (isinstance(type_exp, (tuple, list)) and obj._type not in type_exp): raise(ASN1ProcTextErr( '{0}: value identifier {1}, type mismatch, {2} instead of {3}'\ .format(self.fullname(), ident, obj._type, type_exp))) def _parse_value_null(self, text): """ parses the NULL value value is the integer 0 """ # test NULL ::= NULL m = re.match('(?:^|\s{1})(NULL)', text) if not m: # reference to a local formal parameter identifier # or a global identifier return self._parse_value_ref(text) else: # raw NULL value self.select_set(_path_cur(), 0) return text[m.end():].strip() def _parse_value_bool(self, text): """ parses the BOOLEAN value value is a boolean (Python bool) """ # test BOOLEAN ::= TRUE (/ FALSE) m = re.match('(?:^|\s{1})(TRUE|FALSE)', text) if not m: # reference to a local formal parameter identifier # or a global identifier return self._parse_value_ref(text) else: # raw BOOLEAN value self.select_set(_path_cur(), self._VALUE_BOOL[m.group(1)]) return text[m.end():].strip() def _parse_value_int(self, text): """ parses the INTEGER value value is an integer (Python int) """ # positive, null or negative integer # test INTEGER ::= -10 (or 0 or 100 or ...) m = SYNT_RE_INTVAL.match(text) if not m: m = SYNT_RE_CLASSVALREF.match(text) if m: # CLASS value field # reference to a local formal parameter identifier # or a global identifier return self._parse_value_ref(text) m = SYNT_RE_IDENTEXT.match(text) if m: # reference to an external module value return self._parse_value_ref(text) m = SYNT_RE_IDENT.match(text) if m: ref = m.group(1) # check if some content identifier are defined cont = self.get_cont() if cont is not None and ref in cont: self.select_set(_path_cur(), cont[ref]) return text[m.end():].strip() else: # reference to a local formal parameter identifier # or a global identifier return self._parse_value_ref(text) else: raise(ASN1ProcTextErr('{0}: invalid value for INTEGER, {1}'\ .format(self.fullname(), text))) else: # raw INTEGER value self.select_set(_path_cur(), int(m.group(1))) return text[m.end():].strip() def _parse_value_real(self, text): """ parses the REAL value value is a list of 3 integers, with the integral, decimal and exponent parts """ m = SYNT_RE_REALNUM.match(text) if not m: m = SYNT_RE_REALSEQ.match(text) if not m: m = SYNT_RE_REALSPEC.match(text) if m: # special value self.select_set(_path_cur(), self._VALUE_REAL[m.group(1)]) return text[m.end():].strip() else: # reference to a local formal parameter identifier # or a global identifier return self._parse_value_ref(text) else: # sequence representation self.select_set(_path_cur(), list(map(int, m.groups()))) return text[m.end():].strip() else: # real representation self.__parse_value_realsci(m.groups()) return text[m.end():].strip() def __parse_value_realsci(self, val): # integral, decimal, base 10 exponent parts i, d, e = val if not d: if not e: ret = [int(i), 10, 0] else: ret = [int(i), 10, int(e)] else: # decimal part present # need to adjust the base 10 exponent if not e: e = 0 else: e = int(e) e -= len(d) ret = [int(i+d), 10, e] self.select_set(_path_cur(), ret) def _parse_value_enum(self, text): """ parses the ENUMERATED value value is a string (Python str) """ m = SYNT_RE_IDENT.match(text) if not m: raise(ASN1ProcTextErr('{0}: invalid ENUMERATED value, {1}'\ .format(self.fullname(), text))) name = m.group(1) cont = self.get_cont() if name not in cont: raise(ASN1ProcTextErr('{0}: undefined ENUMERATED value, {1}'\ .format(self.fullname(), name))) self.select_set(_path_cur(), name) return text[m.end():].strip() def _parse_value_bitstr(self, text): """ parses the BIT STRING value value is a list of 2 integers, with the integral value and length in bits """ # bstring, hstring, set of named offsets between curly-brackets, or # reference to another BIT STRING value m = SYNT_RE_BSTRING.match(text) if m: # bstring bs = re.subn('\s{1,}', '', m.group(1))[0] if not bs: # null length bit string val = [0, 0] else: val = [int(bs, 2), len(bs)] self.select_set(_path_cur(), val) return text[m.end():].strip() # m = SYNT_RE_HSTRING.match(text) if m: # hstring hs = re.subn('\s{1,}', '', m.group(1))[0] if not hs: # null length bit string val = [0, 0] else: val = [int(hs, 16), 4*len(hs)] self.select_set(_path_cur(), val) return text[m.end():].strip() # m = SYNT_RE_IDENT.match(text) if m: # reference to a local formal parameter identifier # or a global identifier return self._parse_value_ref(text) m = SYNT_RE_IDENTEXT.match(text) if m: # reference to an external module identifier return self._parse_value_ref(text) # if text[0] == '{': # set of named offsets rest, text = extract_curlybrack(text) if text is None: raise(ASN1ProcTextErr('{0}: invalid BIT STRING value, {1}'\ .format(self.fullname(), rest))) if not text: # empty set of offsets self.__parse_value_bitstr_offsets(None) return rest # named_offs = map(strip, text.split(',')) # reference to local content identifiers cont = self.get_cont() # references to be put in a python set set_no = set() for no in named_offs: m = SYNT_RE_IDENT.match(no) if not m: raise(ASN1ProcTextErr( '{0}: invalid BIT STRING named offset, {1}'\ .format(self.fullname(), no))) ref = m.group(1) if cont is None or ref not in cont: raise(ASN1ProcTextErr( '{0}: undefined BIT STRING named offset, {1}'\ .format(self.fullname(), ref))) off = cont[ref] set_no.add( off ) rest_no = no[m.end():].strip() if rest_no: raise(ASN1ProcTextErr( '{0}: remaining named offset text, {1}'\ .format(self.fullname(), rest_no))) self.__parse_value_bitstr_offsets(set_no) return rest # raise(ASN1ProcTextErr( '{0}: invalid BIT STRING value, {1}'\ .format(self.fullname(), text))) def __parse_value_bitstr_offsets(self, val): # returns the integral value and the minimum number of bits # for the set of offsets # TODO: take SIZE constraint into account ? if val is None: # null length bit string val = [0, 0] else: blen = max(val) val = [sum([1<<(blen-i) for i in val]), blen] self.select_set(_path_cur(), val) def _parse_value_octstr(self, text): """ parses the OCTET STRING value value is a bytestream (Python bytes) """ m = SYNT_RE_BSTRING.match(text) if m: # bstring bs = re.subn('\s{1,}', '', m.group(1))[0] if not bs: # null length octet string val = b'' else: val = uint_to_bytes(int(bs, 2), len(bs)) self.select_set(_path_cur(), val) return text[m.end():].strip() else: m = SYNT_RE_HSTRING.match(text) if m: # hstring hs = re.subn('\s{1,}', '', m.group(1))[0] if len(hs)%2: val = unhexlify(hs + '0') else: val = unhexlify(hs) self.select_set(_path_cur(), val) return text[m.end():].strip() else: # reference to a local formal parameter identifier # or a global identifier of another BIT STRING value return self._parse_value_ref(text) def _parse_value_oid(self, text): """ parses the OBJECT IDENTIFIER or RELATIVE-OID value value is a list of integers """ # id-dsa OBJECT IDENTIFIER ::= { iso(1) member-body(2) us(840) # x9-57(10040) x9algorithm(4) 1 } rest, text = extract_curlybrack(text) if text is None: # reference to a local formal parameter identifier # or a global identifier for the whole OID return self._parse_value_ref(rest) elif text == '': raise(ASN1ProcTextErr('{0}: invalid empty OBJECT IDENTIFIER value'\ .format(self.fullname()))) else: val = [] self.select_set(_path_cur(), val) m = SYNT_RE_OID_COMP.match(text) while m: if m.group(1): # NumberForm val.append(int(m.group(1))) elif m.group(4): # NameAndNumberForm val.append(int(m.group(4))) elif m.group(3): # NameForm ident = tuple(val) + (m.group(3), ) if self._type == TYPE_OID and ident in ASN1_OID_ISO: val.append(int(ASN1_OID_ISO[ident])) else: # reference to another OID or integer as component of # our current OID value self.__parse_value_oid_ref(m.group(3), val) text = text[m.end():].strip() m = SYNT_RE_OID_COMP.match(text) if text: raise(ASN1ProcTextErr('{0}: invalid remaining text in {1} value, {2}'\ .format(self.fullname(), self._type, text))) return rest def __parse_value_oid_ref(self, ident, val): # when ident is a reference to a local formal parameter identifier # or a global identifier for a component of an OID param = GLOBAL.COMP['NS']['par'] if param and ident in param: # # 1) reference to a local parameter # type verification self.__parse_value_ref_typechk(param[ident]['gov'], ident, [self._type, TYPE_INT]) # # append a reference for the value val.append(ASN1RefValue(ASN1RefParam(ident))) # update the list of the parameter referrers param[ident]['ref'].append( _path_root() + [len(val)-1] ) # else: # # 2) reference to a global identifier try: valmod = GLOBAL.COMP['NS']['obj'][ident] except KeyError: raise(ASN1ProcTextErr('{0}: OID reference {1}, undefined'\ .format(self.fullname(), ident))) try: valobj = get_asnobj(valmod, ident) except ASN1Err as Err: raise(ASN1ProcTextErr('{0}: {1}'\ .format(self.fullname(), Err))) if valobj._val is None: raise(ASN1ProcLinkErr('{0}: OID value {1}'\ .format(self.fullname(), ident))) self.__parse_value_ref_typechk(valobj, ident, [self._type, TYPE_INT]) if valobj._type == TYPE_INT: val.append(valobj._val) else: val.extend(valobj._val) # # 3) keep track of the reference self._ref.add( ASN1RefValue((valmod, ident)) ) def _parse_value_str(self, text): """ parses any *String object value value is a string (Python str) """ m = SYNT_RE_IDENT.match(text) if m: return self._parse_value_ref(text) m = SYNT_RE_IDENTEXT.match(text) if m: # reference to an external module value return self._parse_value_ref(text) else: text, val = extract_charstr(text) if val is None: raise(ASN1ProcTextErr('{0}: invalid {1} value, {2}'\ .format(self.fullname(), self._type, text))) self.select_set(_path_cur(), val) return text def _parse_value_timeutc(self, text): """ parses the UTCTime value value is a tuple of length 7, with str (digits) or None """ # (AA, MM, DD, HH, MM, [SS,] Z) m = SYNT_RE_TIMEUTC.match(text) if m: self.select_set(_path_cur(), m.groups()) return text[m.end():].strip() else: # reference to a local formal parameter identifier # or a global identifier of another UTCTime value return self._parse_value_ref(text) def _parse_value_timegen(self, text): """ parses the GeneralizedTime value value is a tuple of length 8, with str (digits) or None """ # (AAAA, MM, DD, HH, [MM, [SS,]] [{.,}F*,] [Z]) m = SYNT_RE_TIMEGENE.match(text) if m: self.select_set(_path_cur(), m.groups()) return text[m.end():].strip() else: # reference to a local formal parameter identifier # or a global identifier of another GeneralizedTime value return self._parse_value_ref(text) def _parse_value_choice(self, text): """ parses the CHOICE value value is a list of length 2, with the chosen component identifier and single value """ # identifier: ASN1Obj single value # # 1) get the identifier and textual value m = SYNT_RE_IDENT.match(text) if not m: m = SYNT_RE_IDENTEXT.match(text) if m: # reference to an external module value return self._parse_value_ref(text) else: raise(ASN1ProcTextErr('{0}: invalid CHOICE value, {1}'\ .format(self.fullname(), text))) name = m.group(1) cont = self.get_cont() rest = text[m.end():].strip() if rest[0:1] != ':': # if we only have an identifier return self._parse_value_ref(text) elif name not in cont: raise(ASN1ProcTextErr('{0}: undefined CHOICE identifier, {1}'\ .format(self.fullname(), name))) text = rest[1:].strip() # # 2) prepare the container for receiving the value of the chosen object # with the ident of the chosen object in the 1st place val = [name, None] self.select_set(_path_cur(), val) # # 3) create a proxy object with the chosen one that will parse the textual value ObjProxy = cont[name].copy() ObjProxy._ref = set() # _path_ext([1]) _path_stack(['val']) text = ObjProxy.parse_value(text) assert( ObjProxy._val is not None ) _path_pop() _path_trunc(1) # # 4) transfer the parsed value from ObjProxy to self val[1] = ObjProxy._val # # 5) transfer references from ObjProxy to self if ObjProxy._ref: self._ref.update( ObjProxy._ref ) # return text def _parse_value_seq(self, text): """ parses the SEQUENCE value value is an ASN1Dict made of component identifier, single value """ # 1) extract all identifiers / values between "{" and "}" # and coma-separated rest, values = extract_multi(text) if values is None: return self._parse_value_ref(text) values = list(values) # # 2) get the SEQUENCE root / ext / optional content and identifiers cont = self.get_cont() root_opt = self.get_root_opt() ext = self.get_ext() ext_ident = self.get_ext_ident() ext_group = self.get_ext_group() idents = list(cont.keys()) # # 3) prepare a container for receiving all values vals = ASN1Dict() self.select_set(_path_cur(), vals) # # 4) parse all values according to the root and extended content # iterate over all content identifiers and provided values ind_cont = 0 ind_values = 0 while ind_cont < len(cont) and ind_values < len(values): ident = idents[ind_cont] value = values[ind_values] m = re.match(ident, value) if not m: if ident in root_opt: # 4.1) optional / default value component, value not present ind_cont += 1 elif ext and ident in ext: # 4.2) extended component, value not present # if ident is in a group of extended components, # jump over all idents in the same group if ident in ext_ident: gid = ext_ident[ident] ind_cont += len(ext_group[gid]) else: ind_cont += 1 else: raise(ASN1ProcTextErr('{0}, component {1}: invalid SEQUENCE value, {2}'\ .format(self.fullname(), ident, value))) else: # 4.3) value present value = value[m.end():].strip() vals[ident] = None # # 4.4) create a proxy object with the selected field that will parse # the textual value ObjProxy = cont[ident].copy() ObjProxy._ref = set() # _path_ext([ident]) _path_stack(['val']) restval = ObjProxy.parse_value(value) assert( ObjProxy._val is not None ) _path_pop() _path_trunc(1) # if restval: raise(ASN1ProcTextErr( '{0}, component {1}: remaining textual value definition, {2}'\ .format(self.fullname(), ident, restval))) # # 4.5) transfer the parsed value from ObjProxy to self vals[ident] = ObjProxy._val # # 4.6) transfer references from ObjProxy to self if ObjProxy._ref: self._ref.update( ObjProxy._ref ) # ind_cont += 1 ind_values += 1 # # 5) check if any value remains, that would be invalid if values[ind_values:]: raise(ASN1ProcTextErr('{0}: undefined SEQUENCE value, {1}'\ .format(self.fullname(), values[ind_values:]))) # check if the provided value conforms to the optional and extended # part of the content if not self.is_value_ok(vals): raise(ASN1ProcTextErr( '{0}: invalid SEQUENCE value, incorrect non-optional root or grouped extension, {1}'\ .format(self.fullname(), values))) # return rest def _parse_value_set(self, text): """ parses the SET value value is an ASN1Dict made of component identifier, single value """ # 1) extract all identifiers / values between "{" and "}" # and coma-separated rest, values = extract_multi(text) if values is None: return self._parse_value_ref(text) values = list(values) # # 2) get the SET content cont = self.get_cont() # # 3) prepare a container for receiving all values vals = ASN1Dict() self.select_set(_path_cur(), vals) # # 4) parse all provided values for value in values: m = SYNT_RE_IDENT.match(value) if not m: raise(ASN1ProcTextErr('{0}: invalid SET value, {1}'\ .format(self.fullname(), value))) ident = m.group(1) if ident not in cont: raise(ASN1ProcTextErr('{0}: invalid SET value identifier, {1}'\ .format(self.fullname(), ident))) elif ident in vals: raise(ASN1ProcTextErr('{0}: duplicated SET value, {1}'\ .format(self.fullname(), ident))) # # 4.1) value present value = value[m.end():].strip() vals[ident] = None # # 4.2) create a proxy object with the selected field that will parse the # textual value ObjProxy = cont[ident].copy() ObjProxy._ref = set() # _path_ext([ident]) _path_stack(['val']) restval = ObjProxy.parse_value(value) assert( ObjProxy._val is not None ) _path_pop() _path_trunc(1) # if restval: raise(ASN1ProcTextErr( '{0}, component {1}: remaining textual value definition, {2}'\ .format(self.fullname(), ident, restval))) # # 4.3) transfer the parsed value from ObjProxy to self vals[ident] = ObjProxy._val # # 4.4) transfer references from ObjProxy to self if ObjProxy._ref: self._ref.update( ObjProxy._ref ) # # 5) check if the provided value conforms to the optional and extended # part of the content if not self.is_value_ok(vals): raise(ASN1ProcTextErr('{0}: invalid SET value, '\ 'incorrect non-optional root or grouped extension, {1}'\ .format(self.fullname(), values))) # return rest def _parse_value_seqof(self, text): """ parses the SEQUENCE OF or SET OF value value is a list of single values """ # TODO: verify the number of values against any SIZE constraint # 1) extract all identifiers / values between "{" and "}" # and coma-separated rest, values = extract_multi(text) if values is None: return self._parse_value_ref(text) # # 2) create a proxy object with the SEQUENCE OF component that will parse # the textual value ObjProxy = self.get_cont().copy() ObjProxy._ref = set() # # 3) prepare a container for receiving all values vals = [] self.select_set(_path_cur(), vals) # # 4) parse all provided values for value in values: # # 4.1) use ObjProxy to parse the textual value vals.append(None) # _path_ext([len(vals)-1]) _path_stack(['val']) restval = ObjProxy.parse_value(value) assert( ObjProxy._val is not None ) _path_pop() _path_trunc(1) # if restval: raise(ASN1ProcTextErr('{0}: remaining textual value definition, {1}'\ .format(self.fullname(), restval))) # # 4.2) transfer the parsed value from ObjProxy to self vals[-1] = ObjProxy._val # # 4.3) cleanup ObjProxy value ObjProxy._val = None # # 5) transfer references from ObjProxy to self if ObjProxy._ref: self._ref.update( ObjProxy._ref ) # return rest def _parse_value_open(self, text): """ parses the OPEN TYPE value value is a list of length 2, with an ASN1Obj instance and the corresponding single value """ # ASN1Obj definition: ASN1Obj single value # # 1) extract the ASN.1 object definition and its value colon_offset = search_top_lvl_sep(text, ':') if len(colon_offset) == 0: return self._parse_value_ref(text) elif len(colon_offset) > 1: raise(ASN1ProcTextErr('{0}: invalid OPEN value, {1}'\ .format(self.fullname(), text))) textobj, textval = text[:colon_offset[0]].strip(), \ text[1+colon_offset[0]:].strip() # # 2) setup the container that will receive the value val = [None, None] self.select_set(_path_cur(), val) # # 3) parse the object definition and put it in val Obj = ASN1Obj(name=self._name, mode=MODE_TYPE) Obj._text_def = textobj val[0] = Obj # _path_ext([0]) _path_stack([]) rest = Obj.parse_def(textobj) _path_pop() _path_trunc(1) # if rest: raise(ASN1ProcTextErr('{0}: remaining textual definition, {1}'\ .format(self.fullname(), rest))) val[0] = Obj.resolve() # # 5) parse the value according to the object definition and put it in val _path_ext([1]) _path_stack(['val']) textval = Obj.parse_value(textval) assert( Obj._val is not None ) _path_pop() _path_trunc(1) # # 6) transfer the parsed value from Obj to self val[1] = Obj._val Obj._val = None # # 7) transfer references from Obj to self if Obj._ref: self._ref.update( Obj._ref ) # return textval def _parse_value_ext(self, text): # TODO # would parse an OID first # then call the corresponding object within GLOBAL.MOD # and finally call Obj.parse_def() on the value text raise(ASN1NotSuppErr('{0}: _parse_value_ext()'.format(self.fullname()))) def _parse_value_class(self, text): """ parses the CLASS object value, according to its syntax value is an ASN1Dict made of {field identifier: single value} """ # 1) extract the value text, values = extract_curlybrack(text) if values is None: return self._parse_value_ref(text) # # 2) prepare a container for receiving all values vals = ASN1Dict() self.select_set(_path_cur(), vals) # # 3) parse the values according to the CLASS definition if not self.get_syntax(): # extract all values according to the content identifiers self._parse_value_class_ident(vals, values) else: # extract all values according to the syntax self._parse_value_class_syntax(vals, values) # # 4) ensure all the required values were provided if not self.is_value_ok(vals): # ensure all mandatory fields are there raise(ASN1ProcTextErr( '{0}: invalid CLASS value, missing non-optional field'\ .format(self.fullname(), values))) # return text def _parse_value_class_ident(self, vals, text): # 1) extract value syntax separated with identifiers and coma # split each coma-separated field coma_offsets = [-1] + search_top_lvl_sep(text, ',') + [len(text)] values = list(map(strip, [text[coma_offsets[i]+1:coma_offsets[i+1]] \ for i in range(len(coma_offsets)-1)])) cont = self.get_cont() # # 2) iterate over each field and parse the associated 'value' # can be a value, a set of values, or an object definition ind = 0 for field_name in cont: Field = cont[field_name] text = values[ind] m = re.match('&%s' % field_name, text) # if not m: # 2.1) value not there if not Field.is_opt(): # field is not optional raise(ASN1ProcTextErr('{0}: missing mandatory value for {1}'\ .format(self.fullname(), field_name))) # else: # 2.2) value there text = text[m.end():].strip() text = self.__parse_value_class_field(Field, text, vals) if text: raise(ASN1ProcTextErr( '{0}, field {1}: remaining textual definition, {2}'\ .format(self.fullname(), field_name, text))) # ind += 1 if ind > len(values): break def __parse_value_class_field(self, Field, text, vals): if Field._mode != MODE_TYPE: # 1) Field._mode in (MODE_VALUE, MODE_SET) # text is a true value or set of values # 1.1) create a proxy object with the selected field that will parse # the textual value(s) if isinstance(Field._typeref, ASN1RefClassIntern): # Field type is a local reference to a MODE_TYPE object try: ObjProxy = vals[Field._typeref.ced_path[-1]].copy() except KeyError: raise(ASN1ProcTextErr( '{0}, field {1}: requires OPEN type field {2}, not yet defined'\ .format(self.fullname(), Field._name, Field._typeref.ced_path[-1]))) else: ObjProxy = Field.copy() ObjProxy._ref = set() # # 1.2) parse the value or set of values _path_ext([Field._name]) _path_stack(['val']) # if Field._mode == MODE_VALUE: # 1.3) true value text = ObjProxy.parse_value(text) assert( ObjProxy._val is not None ) # else: # 1.4) set of values text, settext = extract_curlybrack(text) if settext is None: raise(ASN1ProcTextErr('{0}, field {1}: invalid set, {2}'\ .format(self.fullname(), Field._name, text))) ObjProxy.parse_set(settext) assert( isinstance(ObjProxy._val, dict) ) # _path_pop() _path_trunc(1) # # 1.5) transfer the parsed value from ObjProxy to self vals[Field._name] = ObjProxy._val # else: # 2) Field._mode == MODE_TYPE # 2.1) value present and is actually an object definition Obj = ASN1Obj(name=Field._name, mode=MODE_TYPE) Obj._text_def = text vals[Field._name] = Obj # # 2.2) parse object definition _path_ext([Field._name]) _path_stack([]) text = Obj.parse_def(text) _path_pop() _path_trunc(1) # # 2.3) truncate Obj._text_def, this is a bit dirty... off = Obj._text_def.find(text) assert( off >= 0 ) Obj._text_def = Obj._text_def[:off].strip() # vals[Field._name] = Obj.resolve() # # 2.4) transfer references from Obj to self if Obj._ref: self._ref.update( Obj._ref ) # return text.strip() def _parse_value_class_syntax(self, vals, text): # # prepare temporary attributes self._text = text self._synt = self.get_syntax() self._synt_in = False self._depth = 0 # # this works recursively over SYNTAX groups in self._synt self.__parse_value_class_syntax_grp(vals) # # clean-up temp attr text = self._text del self._text, self._synt, self._synt_in, self._depth # # ensure nothing remains if text: raise(ASN1ProcTextErr('{0}: remaining textual definition, {1}'\ .format(self.fullname(), text))) def __parse_value_class_syntax_grp(self, vals): for grp in self._synt: # # old-school ASN.1 notation made use of coma between groups... if self._text[0:1] == ',': self._text = self._text[1:].strip() # if isinstance(grp, str_types) and grp[0] == '&': # 1) field identifier: parse the associated value self._synt_in = True Field = self.get_cont()[grp[1:]] self._text = self.__parse_value_class_field(Field, self._text, vals) # elif isinstance(grp, str_types) and grp.isupper(): # 2) SYNTAX word(s) group: check if we have it m = re.match(grp, self._text) if m: self._synt_in = True self._text = self._text[m.end():].strip() elif self._synt_in: # we already started parsing some part of this group raise(ASN1ProcTextErr('{0}: missing SYNTAX keyword, {1}'\ .format(self.fullname(), grp))) elif self._depth == 0: # this group is a mandatory part of the SYNTAX raise(ASN1ProcTextErr('{0}: missing mandatory SYNTAX keyword, {1}'\ .format(self.fullname(), grp))) else: # this (optional) group is not present return # elif isinstance(grp, list): # 3) optional inner group, do it recursively synt = self._synt synt_in = self._synt_in self._synt = grp self._synt_in = False self._depth += 1 self.__parse_value_class_syntax_grp(vals) self._depth -= 1 self._synt_in = synt_in self._synt = synt # else: assert() #--------------------------------------------------------------------------# # ASN.1 syntactic parser for set of values #--------------------------------------------------------------------------# def parse_set(self, text, dom='root'): # WNG: textual set of values must come extracted from curlybrackets already # # a set of values has the following format: # root_set, ext_marker , ext_set # where each group is optional # ext_marker: "...", it is the extension marker # root_set, ext_set: one or multiple value(s) and/or set(s) of value(s), # each separated with "|" # # dom: if 'ext', indicates that every values will be put in the extension # domain, including values in the root component # # 1) split the textual values try: text_val = extract_set(text) except ASN1Err as err: raise(ASN1ProcTextErr('{0}: {1}'.format(self.fullname(), err))) # # 2) initialize root / ext dict for storing values val = self.select(_path_cur()) if val is None: val = {'root': [], 'ext': None} self.select_set(_path_cur(), val) else: # there is already a dict at the current path (e.g. in a const) assert( isinstance(val, dict) ) assert( val['root'] == [] ) assert( val['ext'] is None ) pass #val['root'], val['ext'] = [], None # # 3) collect values in the root domain if dom == 'ext': val['ext'] = [] for rv in text_val['root']: # configure the current path self.__parse_set_comp_path_config(val, dom) # parse each root component if self._type == TYPE_OPEN: rest = self.__parse_set_comp_open(rv, val, dom) else: rest = self._parse_set_comp(rv, val, dom) self.__parse_set_comp_path_unconfig() val = self.__parse_set_track_val(val) # if rest[:6] == 'EXCEPT': asnlog('WNG: {0}.{1}, ignoring set exclusion, {2}'\ .format(GLOBAL.COMP['NS']['mod'], self.fullname(), rest)) rest = '' elif rest: raise(ASN1ProcTextErr('{0}: remaining textual set definition, {1}'\ .format(self.fullname(), rest))) # # 4) collect values in the extension domain if text_val['ext'] is None: return dom = 'ext' if val['ext'] is None: val['ext'] = [] for ev in text_val['ext']: # configure the current path self.__parse_set_comp_path_config(val, dom) # parse each extended component if self._type == TYPE_OPEN: rest = self.__parse_set_comp_open(ev, val, dom) else: rest = self._parse_set_comp(ev, val, dom) self.__parse_set_comp_path_unconfig() val = self.__parse_set_track_val(val) # if rest[:6] == 'EXCEPT': asnlog('WNG: {0}.{1}, ignoring set exclusion, {2}'\ .format(GLOBAL.COMP['NS']['mod'], self.fullname(), rest)) rest = '' elif rest: raise(ASN1ProcTextErr('{0}: remaining textual set definition, {1}'\ .format(self.fullname(), rest))) def __parse_set_comp_path_config(self, val, dom): _path = [dom, len(val[dom])] if GLOBAL.COMP['NS']['setdisp']: # if setdisp is set, this means we need to dispatch a set of values # inside the current path which is already configured with the right depth, # only the domain (root / ext) and indexing needs to be updated _path_trunc(2) _path_ext(_path) else: _path_ext(_path) def __parse_set_comp_path_unconfig(self): if not GLOBAL.COMP['NS']['setdisp']: _path_trunc(2) def __parse_set_track_val(self, val): # in case of set component parameterization, it can happen that the # root / ext val dict is overwritten with a new dict instance with the # same content # here, we ensure that we keep track of the last current version of this # root / ext dict newval = self.select(_path_cur()) if id(newval) == id(val): return val else: return newval def _parse_set_comp(self, text, val, dom): # text: textual definition of a single value (or reference to a single value) # or reference to a set of values # or ASN.1 object defined inline with specific constraints # # val: root / ext dict for receiving the result when parsing text # dom: root / ext domain # # 0) check if we are using the exclusion formal if text[:10] == 'ALL EXCEPT': val['excl'] = True text = text[10:].strip() # # 1) check if we have an identifier + potential .&[fF]ield selection m = SYNT_RE_CLASSINSTFIELDREF.match(text) if m: ident_first = m.group(1) ident_last = m.group(2) if ident_last is None: # # 1.1) NULL and BOOLEAN have single values which are # uppercase text, hence need to be processed first # The special ALL EXCEPT case is also handled here, in a very dirty way if ident_first == 'NULL': assert( self._type == TYPE_NULL ) return self.parse_value(text) elif ident_first in ('FALSE', 'TRUE'): assert( self._type == TYPE_BOOL ) return self.parse_value(text) elif ident_first in ('MIN', 'MAX', 'MINUS-INFINITY', 'PLUS-INFINITY'): assert( self._type in self._RANGE_TYPE ) return self._parse_value_or_range(text) else: ident_last = ident_first # if ident_last[0:1].isupper(): # # 1.2) object textual description is uppercase: # this is an ASN.1 object which must define a set of values # compatible with self # those values will be dispatched into val GLOBAL.COMP['NS']['setdisp'] += 1 param = GLOBAL.COMP['NS']['par'] # if param and ident_first in param: # # 3.3) reference to a local formal parameter text = text[len(ident_first):].strip() Gov = param[ident_first]['gov'] idents = [ident_first] while text[:2] == '.&': # # 3.3.1) parameter can be a CLASS set, and the reference can # select fields (iteratively) within this CLASS set, e.g. # MyParamSet {MYCLASS:MyClass} ::= MyClass.&MySet.&MySubSet # # ensure the parameter governor (and inner field, when iterating) # is a CLASS set self.__parse_set_ref_typechk(Gov, '.&'.join(idents), TYPE_CLASS, (MODE_VALUE, MODE_SET)) # get the field name to be selected within the param gov text = text[2:] m = SYNT_RE_TYPEREF.match(text) if not m: raise(ASN1ProcTextErr( '{0}: invalid set field reference within parameter {1}, {2}'\ .format(self.fullname(), '.&'.join(idents), text))) idents.append( m.group(1) ) text = text[m.end():].strip() try: Gov = Gov.get_cont()[idents[-1]] except KeyError: raise(ASN1ProcTextErr( '{0}: undefined set field reference within parameter {1}, {2}'\ .format(self.fullname(), '.&'.join(idents[:-1]), idents[-1]))) assert( idents[-1] == ident_last ) # # 3.3.2) ensure the parameter gov has the same type as self self.__parse_set_ref_typechk(Gov, '.&'.join(idents)) # # 3.3.3) build the path selected in the called reference in case of # MyClass.&MySet.&MySubSet ced_path = [] if len(idents) > 1: ced_path.extend(idents[1:]) # # 3.3.4) create a reference for the set ref = ASN1RefSet(ASN1RefParam(ident_first), ced_path) # set the ref in the val dict with all other values val[dom].append(ref) # # 3.3.5) add the current path to the referrers of the formal parameter param[ident_first]['ref'].append( _path_root()[:] ) # # 3.4) reference to a MODE_SET object or MODE_TYPE object with constraints # or MODE_TYPE object with constraints defined inline else: # # 3.4.1) we create an empty object that will parse the definition # It can be a single identifier in case of a simple object reference ObjProxy = ASN1Obj(name='_set_{0}'.format(self._name), mode=MODE_SET) ObjProxy._text_def = text # # 3.4.2) parse the object definition _path_stack([]) text = ObjProxy.parse_def(text) _path_pop() ObjProxy = ObjProxy.resolve() # # 3.4.3) extract its set of values ObjProxy_val = ObjProxy.get_val() if ObjProxy_val is None: # in this case, the set of values is actually defined from # a value constraint, not from straight values # WNG: this is currently only supported for non-parameterized # TYPE_INT object objval = ObjProxy.__parse_set_from_const() else: objval = ObjProxy_val # # 3.4.4) dispatch the root / ext values from objval into self within val if objval['root']: self.__parse_set_insert(val[dom], objval['root'], dom) if objval['ext']: if val['ext'] is None: val['ext'] = [] self.__parse_set_insert(val['ext'], objval['ext'], 'ext') # # 3.4.5) transfer references from ObjProxy to self # WNG: in case the set is only a typeref, we don't want to # get all ref made by the typeref if ObjProxy_val is not None and ObjProxy._typeref is not None: self._ref.add( ASN1RefSet(ObjProxy._typeref.called, ObjProxy._typeref.ced_path) ) elif ObjProxy._ref: self._ref.update( ObjProxy._ref ) # GLOBAL.COMP['NS']['setdisp'] -= 1 return text # # 4) object textual description is lower case or not an identifier at all: # in all remaining cases, this is a single value or a ref to a single value if self._type in self._RANGE_TYPE: return self._parse_value_or_range(text) else: return self.parse_value(text) def __parse_set_ref_typechk(self, obj, ident, type_exp=None, mode_exp=MODE_SET): if type_exp is None: type_exp = self._type if (isinstance(mode_exp, str_types) and obj._mode != mode_exp) or \ (isinstance(mode_exp, (tuple, list)) and obj._mode not in mode_exp): raise(ASN1ProcTextErr( '{0}: set identifier {1}, mode mismatch, {2} instead of {3!r}'\ .format(self.fullname(), ident, mode_exp))) elif (isinstance(type_exp, str_types) and obj._type != type_exp) or \ (isinstance(type_exp, (tuple, list)) and obj._type not in type_exp): raise(ASN1ProcTextErr( '{0}: set identifier {1}, type mismatch, {2} instead of {3!r}'\ .format(self.fullname(), ident, obj._type, type_exp))) def __parse_set_insert(self, val, objval, dom): # In case of parameter pass-through, referrers re-indexing may be required # 1) get all values that are an ASN1RefSet referring to a formal parameter ref = [v for v in objval if isinstance(v, ASN1RefSet) and \ isinstance(v.called, ASN1RefParam)] assert( len(ref) == len(GLOBAL.COMP['NS']['setpar']) ) # # 2) insert all values 1 by 1 from objval into val (self) # and rewrite the root / ext domain and indexing into the val set for v in objval: val.append(v) if v in ref: ind = ref.index(v) GLOBAL.COMP['NS']['setpar'][ind][-2] = dom GLOBAL.COMP['NS']['setpar'][ind][-1] = len(val)-1 GLOBAL.COMP['NS']['setpar'] = [] def _parse_value_or_range(self, text): # check for the range marker ".." m = re.search('[^.]\.\.[^.]', text) if m: if self._type == TYPE_INT: ra = ASN1RangeInt() elif self._type == TYPE_REAL: ra = ASN1RangeReal() else: assert( self._type in ASN1Range._TYPE_STR ) ra = ASN1RangeStr() # parse the range text_lb = text[:1+m.start()].strip() text_ub = text[m.end()-1:].strip() if text_lb[-1:] == '<': lb_incl = False text_lb = text_lb[:-1] else: lb_incl = True if text_ub[:1] == '<': ub_incl = False text_ub = text_ub[1:] else: ub_incl = True self.select_set(_path_cur(), ra) # set value to boundaries if self._type != TYPE_INT or text_lb != 'MIN': # keeps None for lb for TYPE_INT _path_ext(['lb']) rest = self.parse_value(text_lb) _path_trunc(1) if rest: raise(ASN1ProcTextErr('{0}: invalid range lower bound, {1}'\ .format(self.fullname(), text_lb))) if self._type != TYPE_INT or text_ub != 'MAX': # keeps None for ub for TYPE_INT _path_ext(['ub']) text = self.parse_value(text_ub) _path_trunc(1) elif text_ub == 'MAX': text = '' # handle boundary inclusion if self._type == TYPE_REAL: if not lb_incl: ra.lb_incl = False if not ub_incl: ra.ub_uncl = False else: if not lb_incl: # increment lower bound if ra.lb is not None: ra.lb += 1 if not ub_incl: # decrement higher bound if ra.ub is not None: ra.ub -= 1 else: text = self.parse_value(text) # return text def __parse_set_from_const(self): # extraction of set of values from constraints of MODE_TYPE object # # 1) set extraction from INTEGER constraints if self._type == TYPE_INT: # we go over all constraints of the object and return a root / ext dict # with integral values root, ext = set(), None const = self.get_const() for C in const: if C['type'] == CONST_VAL and not C['excl']: # get root values into a set for val in C['root']: self.__parse_set_from_const_comp(val, root) if C['ext']: if ext is None: ext = set() for val in C['ext']: self.__parse_set_from_const_comp(val, ext) #if not root: # raise(ASN1ProcTextErr('{0}: no constraint with root values to defined a set'\ # .format(self.fullname()))) if ext is not None: return {'root': list(root), 'ext': list(ext)} else: return {'root': list(root), 'ext': None} # # 2) otherwise unsupported, but stays nice, do not raise... else: asnlog('WNG: {0}.{1}, unprocessed set of values from constraint, {1}'\ .format(GLOBAL.COMP['NS']['mod'], self.fullname(), self._text_def)) return {'root': [], 'ext': None} def __parse_set_from_const_comp(self, comp, values): if isinstance(comp, ASN1Range): try: comp_exp = comp.expand() except ASN1Err as err: raise(ASN1NotSuppErr('{0}: unable to expand range, {1}'\ .format(self.fullname(), err))) else: values.update(comp_exp) elif isinstance(comp, ASN1Ref) and isinstance(comp.called, ASN1RefParam): raise(ASN1NotSuppErr('{0}: parameterized constraint, unable to expand'\ .format(self.fullname()))) else: values.add(comp) def __parse_set_comp_open(self, text, val, dom): # specific case for OPEN TYPE: an ASN.1 set defined between { and } # is defined by a set of ASN.1 object types, and not a set of ASN.1 values # text: textual definition of an ASN1 object # val: root / ext dict for receiving the result when parsing text # ext: True when in the extension domain # # parse the new object Obj = ASN1Obj(name=self._name, mode=MODE_TYPE) Obj._text_def = text val[dom].append( Obj ) # _path_stack([]) text = Obj.parse_def(text) _path_pop() # val[dom][-1] = Obj.resolve() # # copy references from Obj to self if Obj._ref: self._ref.update( Obj._ref ) # return text #///////////////////////////////////////\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\# #------------------------------------------------------------------------------# # Python classes for ASN.1 native basic objects #------------------------------------------------------------------------------# #\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\///////////////////////////////////////# class NULL(ASN1Obj): __doc__ = """ ASN.1 basic type NULL object single value: int 0 %s """ % ASN1Obj_docstring TYPE = TYPE_NULL TAG = 5 _type = TYPE CONST = [CONST_VAL] def __init__(self, Obj=None): self._init_from_obj(Obj) def _to_asn1(self, val): return 'NULL' class BOOL(ASN1Obj): __doc__ = """ ASN.1 basic type BOOLEAN object single value: Python bool %s """ % ASN1Obj_docstring TYPE = TYPE_BOOL TAG = 1 _type = TYPE CONST = [CONST_VAL] def __init__(self, Obj=None): self._init_from_obj(Obj) def _to_asn1(self, val): return {True: 'TRUE', False: 'FALSE'}[val] class INT(ASN1Obj): __doc__ = """ ASN.1 basic type INTEGER object single value: Python int %s """ % ASN1Obj_docstring TYPE = TYPE_INT TAG = 2 _type = TYPE CONST = [CONST_VAL] def __init__(self, Obj=None): self._init_from_obj(Obj) def _to_asn1(self, val): # to be applied to an internal single value `val' to get # an ASN.1 compliant value return str(val) class REAL(ASN1Obj): __doc__ = """ ASN.1 basic type REAL object single value: Python list of 3 int 1st int is the mantissa, 2nd is the base, 3rd is the exponent Special values are: [-1, None, None]: MINUS-INFINITY [1, None, None]: PLUS-INFINITY [0, None, None]: NOT-A-NUMBER %s """ % ASN1Obj_docstring REPR_VAL = b'S' # b'S': sequential repr, b'N': numeric repr if exp base 10 # # SEQUENCE-like definition of REAL: # # REAL ::= SEQUENCE { # mantissa INTEGER, # base INTEGER (2|10), # exponent INTEGER } # TYPE = TYPE_REAL TAG = 9 _type = TYPE CONST = [CONST_VAL] def __init__(self, Obj=None): self._init_from_obj(Obj) def _to_asn1(self, val): # to be applied to an internal single value `val' to get # an ASN.1 compliant value if val[1:3] == [None, None]: return {-1: 'MINUS-INFINITY', 1: 'PLUS-INFINITY', 0: 'NOT-A-NUMBER'}[val[0]] elif self.REPR_VAL == b'N' and val[1] == 10: return '%iE%i' % (val[0], val[2]) else: return '{mantissa %i, base %i, exponent %i}'.format(*val) class ENUM(ASN1Obj): __doc__ = """ ASN.1 basic type ENUMERATED object single value: Python str, must be a key in _cont %s """ % ASN1Obj_docstring TYPE = TYPE_ENUM TAG = 10 _type = TYPE CONST = [CONST_VAL] def __init__(self, Obj=None): self._init_from_obj(Obj) def _to_asn1(self, val): # to be applied to an internal single value `val' to get # an ASN.1 compliant value return val class OID(ASN1Obj): __doc__ = """ ASN.1 basic type OBJECT IDENTIFIER object single value: Python list of int %s """ % ASN1Obj_docstring TYPE = TYPE_OID TAG = 6 _type = TYPE CONST = [CONST_VAL] def __init__(self, Obj=None): self._init_from_obj(Obj) def _to_asn1(self, val): # to be applied to an internal single value `val' to get # an ASN.1 compliant value return '{%s}' % ' '.join(map(str, val)) class REL_OID(ASN1Obj): __doc__ = """ ASN.1 basic type RELATIVE-OID object single value: Python list of int %s """ % ASN1Obj_docstring TYPE = TYPE_REL_OID TAG = 13 _type = TYPE CONST = [CONST_VAL] def __init__(self, Obj=None): self._init_from_obj(Obj) def _to_asn1(self, val): # to be applied to an internal single value `val' to get # an ASN.1 compliant value return '{%s}' % ' '.join(map(str, val)) class BIT_STR(ASN1Obj): __doc__ = """ ASN.1 basic type BIT STRING object single value: Python list of 2 int 1st int is the unsigned integral value, 2nd is the length in bits when CONST_CONTAINING is set, a Python list of 3 items is used, the 1st and 2nd items are the original value (2 int), the 3rd item is a CHOICE-like single value, i.e. a list of 2 items with a global identifier string and an ASN1Obj single value %s """ % ASN1Obj_docstring REPR_VAL = b'B' # b'B': bstring, b'H': hstring if bit length mutliple of 4 TYPE = TYPE_BIT_STR TAG = 3 _type = TYPE CONST = [CONST_VAL, CONST_SIZE, CONST_CONTAINING, CONST_ENCODE_BY] def __init__(self, Obj=None): self._init_from_obj(Obj) def _to_asn1(self, val): # to be applied to an internal single value `val' to get # an ASN.1 compliant value if self.REPR_VAL == b'H' and not val[1]%4: # hstr hstr = hex(val[0])[2:] if 4*len(hstr) < val[1]: hstr = (val[1]//4 - len(hstr))*'0' + hstr return '\'%s\'H' % hstr else: # bstr bstr = bin(val[0])[2:] if len(bstr) < val[1]: bstr = (val[1] - len(bstr))*'0' + bstr return '\'%s\'B' % bstr class OCT_STR(ASN1Obj): __doc__ = """ ASN.1 basic type OCTET STRING object single value: Python bytes when CONST_CONTAINING is set, a Python list of 2 items is used, the 1st item is the original value (bytes), the 2nd item is a CHOICE-like single value, i.e. a list of 2 items with a global identifier string and an ASN1Obj single value %s """ % ASN1Obj_docstring REPR_VAL = b'B' # b'B': bstring, b'H': hstring TYPE = TYPE_OCT_STR TAG = 4 _type = TYPE CONST = [CONST_VAL, CONST_SIZE, CONST_CONTAINING, CONST_ENCODE_BY] def __init__(self, Obj=None): self._init_from_obj(Obj) def val_ok(self, val): """ returns True if val complies to the content, against any size constraint """ # TODO return True def _to_asn1(self, val): # to be applied to an internal single value `val' to get # an ASN.1 compliant value if isinstance(val, tuple): if self.REPR_VAL == b'H': return '\'%s\'H' % hexlify(val[0]).upper() else: bstr = bin(bytes_to_uint(val[0], len(val[0])))[2:] if len(bstr) < 8*len(val[0]): bstr = (8*len(val[0]) - len(bstr))*'0' + bstr return '\'%s\'B' % bstr else: if self.REPR_VAL == b'H': return '\'%s\'H' % hexlify(val).upper() else: bstr = bin(bytes_to_uint(val, len(val)))[2:] if len(bstr) < 8*len(val): bstr = (8*len(val) - len(bstr))*'0' + bstr return '\'%s\'B' % bstr _String_docstring = """ single value: Python str Attribute specific to *String: _clen: None or int, indicates the number of bits for a character %s """ % ASN1Obj_docstring class _String(ASN1Obj): __doc__ = """ Virtual parent for any ASN.1 *String object %s """ % _String_docstring _clen = None CONST = [CONST_VAL, CONST_SIZE, CONST_ALPHABET, CONST_REGEXP] def __init__(self, Obj): self._init_from_obj(Obj) def _to_asn1(self, val): # to be applied to an internal single value `val' to get # an ASN.1 compliant value return '"%s"' % val.replace('"', '""') class OBJ_DESC(_String): __doc__ = """ ASN.1 basic type OBJECT DESCRIPTOR object %s """ % _String_docstring TYPE = TYPE_OBJ_DESC TAG = 7 _type = TYPE class STR_UTF8(_String): __doc__ = """ ASN.1 basic type UTF8String object %s """ % _String_docstring TYPE = TYPE_STR_UTF8 TAG = 12 _type = TYPE class STR_NUM(_String): __doc__ = """ ASN.1 basic type NumericString object %s """ % _String_docstring TYPE = TYPE_STR_NUM TAG = 18 _type = TYPE class STR_PRINT(_String): __doc__ = """ ASN.1 basic type PrintableString object %s """ % _String_docstring TYPE = TYPE_STR_PRINT TAG = 19 _type = TYPE class STR_TELE(_String): __doc__ = """ ASN.1 basic type TeletexString object %s """ % _String_docstring TYPE = TYPE_STR_TELE TAG = 20 _type = TYPE class STR_T61(_String): __doc__ = """ ASN.1 basic type T61String object %s """ % _String_docstring TYPE = TYPE_STR_T61 TAG = 20 _type = TYPE class STR_VID(_String): __doc__ = """ ASN.1 basic type VideotexString object %s """ % _String_docstring TYPE = TYPE_STR_VID TAG = 21 _type = TYPE class STR_IA5(_String): __doc__ = """ ASN.1 basic type IA5String object %s """ % _String_docstring TYPE = TYPE_STR_IA5 TAG = 22 _type = TYPE _clen = 7 # to be confirmed class STR_GRAPH(_String): __doc__ = """ ASN.1 basic type GraphicString object %s """ % _String_docstring TYPE = TYPE_STR_GRAPH TAG = 25 _type = TYPE class STR_VIS(_String): __doc__ = """ ASN.1 basic type VisibleString object %s """ % _String_docstring TYPE = TYPE_STR_VIS TAG = 26 _type = TYPE class STR_ISO646(_String): __doc__ = """ ASN.1 basic type ISO646String object %s """ % _String_docstring TYPE = TYPE_STR_ISO646 TAG = 26 _type = TYPE class STR_GENE(_String): __doc__ = """ ASN.1 basic type GenericString object %s """ % _String_docstring TYPE = TYPE_STR_GENE TAG = 27 _type = TYPE class STR_UNIV(_String): __doc__ = """ ASN.1 basic type UniversalString object %s """ % _String_docstring TYPE = TYPE_STR_UNIV TAG = 28 _type = TYPE _clen = 32 class STR_BMP(_String): __doc__ = """ ASN.1 basic type BMPString object %s """ % _String_docstring TYPE = TYPE_STR_BMP TAG = 30 _type = TYPE _clen = 16 class _Time(ASN1Obj): __doc__ = """Virtual parent for UTCTime and GeneralizedTime""" CONST = [CONST_VAL] def __init__(self, Obj): self._init_from_obj(Obj) class TIME_UTC(_Time): __doc__ = """ ASN.1 basic type UTCTime object single value: Python 7-tuple of int (AA, MM, DD, HH, MM, [SS,] Z), SS is optional, hence 6th element can be None Z corresponds to the UTC decay %s """ % ASN1Obj_docstring TYPE = TYPE_TIME_UTC TAG = 23 _type = TYPE def _to_asn1(self, val): # to be applied to an internal single value `val' to get # an ASN.1 compliant value ret = '%.2i%.2i%.2i%.2i%.2i' % val[:5] if val[5] is not None: ret += '%.2i' % val[5] if val[6] == 0: ret += 'Z' elif val[6] > 0: ret += '+%.4i' % val[6] else: ret += '%.4i' % val[6] return ret class TIME_GEN(_Time): __doc__ = """ ASN.1 basic type GeneralizedTime object single value: Python 8-tuple of int (AAAA, MM, DD, HH, [MM, [SS, [FFFF,]]], Z), MM, SS and FFFF are optional, hence 5th, 6th and 7th element can be None Z corresponds to the UTC decay and is optional, hence 8th element can be None too %s """ % ASN1Obj_docstring TYPE = TYPE_TIME_GEN TAG = 24 _type = TYPE def _to_asn1(self, val): # to be applied to an internal single value `val' to get # an ASN.1 compliant value ret = '%.4i%.2i%.2i%.2i' % val[:4] if val[4] is not None: ret += '%.2i' % val[4] if val[5] is not None: ret += '%.2i' % val[5] if val[6] is not None: ret += '.%.4i' % val[6] if val[7] == 0: ret += 'Z' elif val[7] > 0: ret += '+%.4i' % val[7] elif val[7] < 0: ret += '%.4i' % val[7] return ret #///////////////////////////////////////\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\# #------------------------------------------------------------------------------# # Python classes for ASN.1 native constructed objects #------------------------------------------------------------------------------# #\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\///////////////////////////////////////# class CHOICE(ASN1Obj): __doc__ = """ ASN.1 constructed type CHOICE object single value: Python list of 2 items 1st item is the identifier of the choice (str), 2nd item is the ASN1Obj single value specific to the chosen object %s """ % ASN1Obj_docstring TYPE = TYPE_CHOICE TAG = None _type = TYPE CONST = [CONST_VAL, CONST_COMPS] def __init__(self, Obj=None): self._init_from_obj(Obj) def _to_asn1(self, val): return '%s: %s' % (val[0], self.get_cont()[val[0]]._to_asn1[val[1]]) class SEQ(ASN1Obj): __doc__ = """ ASN.1 constructed type SEQUENCE object single value: Python ASN1Dict keys are components' identifier (str), values are ASN1Obj single value specific to components object %s """ % ASN1Obj_docstring TYPE = TYPE_SEQ TAG = 16 _type = TYPE CONST = [CONST_VAL, CONST_COMPS] def __init__(self, Obj=None): self._init_from_obj(Obj) def init_cache(self): self._cache = {} if self._cont is not None: for Comp in self._cont.values(): Comp.init_cache() def _to_asn1(self, val): cont = self.get_cont() values = ['%s %s' % (ident, cont[ident]._to_asn1(value)) for \ (ident, value) in val.items()] return '{ %s }' % ', '.join(values) class SEQ_OF(ASN1Obj): __doc__ = """ ASN.1 constructed type SEQUENCE OF object single value: Python list items are ASN1Obj single value specific to the component object %s """ % ASN1Obj_docstring TYPE = TYPE_SEQ_OF TAG = 16 _type = TYPE CONST = [CONST_VAL, CONST_SIZE, CONST_COMP] def __init__(self, Obj): self._init_from_obj(Obj) def init_cache(self): self._cache = {} if self._cont is not None: self._cont.init_cache() def _to_asn1(self, val): cont = self.get_cont() return '{ %s }' % ', '.join([cont._to_asn1(value) for value in val]) class SET(ASN1Obj): __doc__ = """ ASN.1 constructed type SET object single value: Python dict keys are components' identifier (str), values are ASN1Obj single value specific to components object %s """ % ASN1Obj_docstring TYPE = TYPE_SET TAG = 17 _type = TYPE CONST = [CONST_VAL, CONST_COMPS] def __init__(self, Obj=None): self._init_from_obj(Obj) def init_cache(self): self._cache = {} if self._cont is not None: for Comp in self._cont.values(): Comp.init_cache() def _to_asn1(self, val): cont = self.get_cont() values = ['%s %s' % (ident, cont[ident]._to_asn1(value)) for \ (ident, value) in val.items()] return '{ %s }' % ', '.join(values) class SET_OF(ASN1Obj): __doc__ = """ ASN.1 constructed type SET OF object single value: Python list items are ASN1Obj single value specific to the component object %s """ % ASN1Obj_docstring TYPE = TYPE_SET_OF TAG = 17 _type = TYPE CONST = [CONST_VAL, CONST_SIZE, CONST_COMP] def __init__(self, Obj): self._init_from_obj(Obj) def init_cache(self): self._cache = {} if self._cont is not None: self._cont.init_cache() def _to_asn1(self, val): cont = self.get_cont() return '{ %s }' % ', '.join([cont._to_asn1(value) for value in val]) #///////////////////////////////////////\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\# #------------------------------------------------------------------------------# # Python classes for ASN.1 native container objects #------------------------------------------------------------------------------# #\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\///////////////////////////////////////# class OPEN(ASN1Obj): TYPE = TYPE_OPEN TAG = None _type = TYPE CONST = [CONST_VAL, CONST_CONTAINING] def __init__(self, Obj=None): self._init_from_obj(Obj) class ANY(OPEN): TYPE = TYPE_ANY TAG = None _type = TYPE CONST = [CONST_VAL, CONST_CONTAINING] class EXT(ASN1Obj): """ ASN.1 context switching type EXPTERNAL object associated type: [UNIVERSAL 8] IMPLICIT SEQUENCE { identification [0] EXPLICIT CHOICE { syntaxes [0] SEQUENCE { abstract [0] OBJECT IDENTIFIER, transfer [1] OBJECT IDENTIFIER }, syntax [1] OBJECT IDENTIFIER, presentation-context-id [2] INTEGER, context-negotiation [3] SEQUENCE { presentation-context-id [0] INTEGER, transfer-syntax [1] OBJECT IDENTIFIER }, transfer-syntax [4] OBJECT IDENTIFIER, fixed [5] NULL }, data-value-descriptor [1] ObjectDescriptor OPTIONAL, data-value [2] OCTET STRING } (WITH COMPONENTS { ..., identification (WITH COMPONENTS { ..., syntaxes ABSENT, transfer-syntax ABSENT, fixed ABSENT }) }) """ TYPE = TYPE_EXT TAG = 8 _type = TYPE CONST = [CONST_VAL, CONST_COMPS] def __init__(self, Obj=None): self._init_from_obj(Obj) class EMB_PDV(ASN1Obj): """ ASN.1 context switching type EMBEDDED PDV object associated type: [UNIVERSAL 8] IMPLICIT SEQUENCE { identification [0] EXPLICIT CHOICE { syntaxes [0] SEQUENCE { abstract [0] OBJECT IDENTIFIER, transfer [1] OBJECT IDENTIFIER }, syntax [1] OBJECT IDENTIFIER, presentation-context-id [2] INTEGER, context-negotiation [3] SEQUENCE { presentation-context-id [0] INTEGER, transfer-syntax [1] OBJECT IDENTIFIER }, transfer-syntax [4] OBJECT IDENTIFIER, fixed [5] NULL }, data-value-descriptor [1] ObjectDescriptor OPTIONAL, data-value [2] OCTET STRING } (WITH COMPONENTS { ..., data-value-descriptor ABSENT }) """ TYPE = TYPE_EMB_PDV TAG = 11 _type = TYPE CONST = [CONST_VAL, CONST_COMPS] def __init__(self, Obj=None): self._init_from_obj(Obj) class CHAR_STR(ASN1Obj): TYPE = TYPE_CHAR_STR TAG = 29 _type = TYPE CONST = [CONST_VAL, CONST_SIZE, CONST_COMPS] def __init__(self, Obj=None): self._init_from_obj(Obj) #///////////////////////////////////////\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\# #------------------------------------------------------------------------------# # Python classes for ASN.1 native CLASS objects #------------------------------------------------------------------------------# #\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\///////////////////////////////////////# class CLASS(ASN1Obj): __doc__ = """ ASN.1 CLASS object type single value: Python ASN1Dict keys are fields' identifier (str), values are ASN1Obj single value, values set or type, specific to each field %s """ % ASN1Obj_docstring KW = ('name', 'mode', 'param', 'tag', 'type', 'typeref', 'cont', 'ext', 'const', 'val', 'ref', 'parent', 'flag', 'group', 'msg', 'syntax') TYPE = TYPE_CLASS TAG = None _type = TYPE CONST = [CONST_VAL] def __init__(self, Obj): self._init_from_obj(Obj) def init_cache(self): self._cache = {} if self._cont is not None: for Field in self._cont.values(): Field.init_cache() def __call__(self, *args): # calling CLASS enables to get lists of values or values corresponding # to identifiers if len(args) == 0: return self._val if len(args) >= 1: name = args[0] if len(args) >= 2: val = args[1] else: val = None # if name not in self.get_cont(): raise(ASN1ObjErr('{0}: invalid identifier, {1}'\ .format(self.fullname(), name))) # # TODO: enable caching all those values' lists # if self._mode == MODE_VALUE: if val is None: # return the value for the given identifier return self._val[name] elif val == self._val[name]: # returns the list of fields' value associated return self._val else: return None # elif self._mode == MODE_SET: if val is None: values = [] # return all the values for the given identifier if self._val['root']: values.extend([v[name] for v in self._val['root']]) if self._val['ext']: values.extend([v[name] for v in self._val['ext']]) return values else: if self._val['root']: for v in self._val['root']: if v[name] == val: return v if self._val['ext']: for v in self._val['ext']: if v[name] == val: return v return None def _to_asn1(self, val): return None #///////////////////////////////////////\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\# #------------------------------------------------------------------------------# # Python classes for predefined ASN.1 objects #------------------------------------------------------------------------------# #\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\///////////////////////////////////////# class INST_OF(SEQ): TYPE = TYPE_INSTOF TAG = 8 _type = TYPE def __init__(self, Obj): self._init_from_obj(Obj) # TODO: # resolve _typeref # verifies it is a TYPE-IDENTIFIER # expand its content into sequential components assert( self._typeref is not None ) #tr = self.get_typeref() #tr.get_refchain() #///////////////////////////////////////\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\# #------------------------------------------------------------------------------# # Python dict for ASN.1 object look-up #------------------------------------------------------------------------------# #\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\///////////////////////////////////////# ASN1ObjLUT = { TYPE_NULL : NULL, TYPE_BOOL : BOOL, TYPE_INT : INT, TYPE_REAL : REAL, TYPE_ENUM : ENUM, TYPE_BIT_STR : BIT_STR, TYPE_OCT_STR : OCT_STR, TYPE_OID : OID, TYPE_REL_OID : REL_OID, TYPE_STR_IA5 : STR_IA5, TYPE_STR_PRINT : STR_PRINT, TYPE_STR_NUM : STR_NUM, TYPE_STR_VIS : STR_VIS, TYPE_STR_BMP : STR_BMP, TYPE_STR_UTF8 : STR_UTF8, TYPE_STR_ISO646 : STR_ISO646, TYPE_STR_TELE : STR_TELE, TYPE_STR_VID : STR_VID, TYPE_STR_GRAPH : STR_GRAPH, TYPE_STR_T61 : STR_T61, TYPE_STR_GENE : STR_GENE, TYPE_STR_UNIV : STR_UNIV, TYPE_OBJ_DESC : OBJ_DESC, TYPE_TIME_GEN : TIME_GEN, TYPE_TIME_UTC : TIME_UTC, TYPE_CHOICE : CHOICE, TYPE_SEQ : SEQ, TYPE_SEQ_OF : SEQ_OF, TYPE_SET : SET, TYPE_SET_OF : SET_OF, TYPE_OPEN : OPEN, TYPE_ANY : ANY, TYPE_EXT : EXT, TYPE_EMB_PDV : EMB_PDV, TYPE_CHAR_STR : CHAR_STR, TYPE_CLASS : CLASS, TYPE_TYPEIDENT : CLASS, TYPE_ABSSYNT : CLASS, TYPE_INSTOF : INST_OF }