7082 lines
276 KiB
Python
7082 lines
276 KiB
Python
# -*- 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
|
|
}
|
|
|