964 lines
33 KiB
Python
964 lines
33 KiB
Python
# -*- coding: UTF-8 -*-
|
|
#/**
|
|
# * Software Name : pycrate
|
|
# * Version : 0.4
|
|
# *
|
|
# * Copyright 2017. 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/setobj.py
|
|
# * Created : 2017-01-31
|
|
# * Authors : Benoit Michau
|
|
# *--------------------------------------------------------
|
|
#*/
|
|
|
|
from functools import reduce
|
|
|
|
from .utils import *
|
|
from .err import *
|
|
from .refobj import ASN1Ref
|
|
|
|
#------------------------------------------------------------------------------#
|
|
# range for integers, reals and some character strings
|
|
#------------------------------------------------------------------------------#
|
|
|
|
class ASN1Range(object):
|
|
"""
|
|
Special class to handle range of values for ASN.1 types that support
|
|
ordering their values (e.g. INTEGER and REAL)
|
|
|
|
Init args:
|
|
type : str (TYPE_*), type of the ASN.1 object handling the value range
|
|
lb : single value according to the type, lower bound of the range
|
|
lb_incl: bool, indicate if the lower bound is part of the range
|
|
ub : single value according to the type, upper bound of the range
|
|
ub_incl: bool, indicate if the upper bound is part of the range
|
|
|
|
When type is TYPE_INT or TYPE_STR_*, lb_incl and ub_incl shall always be True
|
|
"""
|
|
|
|
_TYPE_STR = (TYPE_STR_IA5, TYPE_STR_PRINT, TYPE_STR_VIS)
|
|
_TYPE = (TYPE_INT, TYPE_REAL) + _TYPE_STR
|
|
|
|
# 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 = ['const', 0, 'root', 0, 'ub']
|
|
def __getitem__(self, kw):
|
|
if kw in self.KW:
|
|
return getattr(self, kw)
|
|
else:
|
|
return object.__getitem__(self, kw)
|
|
|
|
def __setitem__(self, kw, arg):
|
|
if kw in self.KW:
|
|
return setattr(self, kw, arg)
|
|
else:
|
|
return object.__setitem__(self, kw, arg)
|
|
|
|
def copy(self):
|
|
"""
|
|
returns an equal but independent copy of self
|
|
"""
|
|
if isinstance(self.lb, ASN1Ref):
|
|
lb = self.lb.copy()
|
|
else:
|
|
lb = self.lb
|
|
if isinstance(self.ub, ASN1Ref):
|
|
ub = self.ub.copy()
|
|
else:
|
|
ub = self.ub
|
|
if isinstance(self, (ASN1RangeInt, ASN1RangeStr)):
|
|
return self.__class__(self.lb, self.ub)
|
|
elif isinstance(self, ASN1RangeReal):
|
|
return self.__class__(self.lb, self.ub, self.lb_incl, self.ub_incl)
|
|
|
|
def expand(self):
|
|
raise(ASN1Err('{0!r}: unable to expand this type of range'.format(self)))
|
|
|
|
|
|
class ASN1RangeInt(ASN1Range):
|
|
|
|
KW = ('lb', 'ub')
|
|
|
|
_EXP_MAX = 2**18 # max range that can be expanded
|
|
|
|
def __init__(self, lb=None, ub=None):
|
|
self.lb = lb
|
|
self.ub = ub
|
|
|
|
def _safechk(self):
|
|
if not isinstance(self.lb, integer_types + (NoneType, )) or \
|
|
not isinstance(self.ub, integer_types + (NoneType, )) or \
|
|
(self.ub is not None and self.lb is not None and self.ub < self.lb):
|
|
raise(ASN1Err('{0!r}: invalid bounds'.format(self)))
|
|
|
|
def __repr__(self):
|
|
return 'ASN1RangeInt({0!r}..{1!r})'.format(self.lb, self.ub)
|
|
|
|
def __lt__(self, other):
|
|
if not isinstance(other, self.__class__):
|
|
raise(ASN1Err('{0!r}: unable to compare with {1!r}'.format(self, other)))
|
|
if self.lb is None:
|
|
if other.lb is None:
|
|
if self.ub is None:
|
|
return False
|
|
elif other.ub is None:
|
|
return True
|
|
else:
|
|
return self.ub < other.ub
|
|
else:
|
|
return True
|
|
elif other.lb is None:
|
|
return False
|
|
elif self.lb == other.lb:
|
|
if self.ub is None:
|
|
return False
|
|
elif other.ub is None:
|
|
return True
|
|
else:
|
|
return self.ub < other.ub
|
|
else:
|
|
return self.lb < other.lb
|
|
|
|
def __contains__(self, item):
|
|
if not isinstance(item, integer_types):
|
|
return None
|
|
elif self.lb is None:
|
|
if self.ub is None:
|
|
return True
|
|
else:
|
|
return item <= self.ub
|
|
elif self.ub is None:
|
|
return self.lb <= item
|
|
else:
|
|
return self.lb <= item <= self.ub
|
|
|
|
def expand(self):
|
|
"""
|
|
returns a list of integers
|
|
"""
|
|
if self.lb is None or self.ub is None:
|
|
raise(ASN1Err('{0!r}: unable to expand infinite range'.format(self)))
|
|
elif self.ub - self.lb < self._EXP_MAX:
|
|
return list(range(self.lb, 1+self.ub))
|
|
else:
|
|
raise(ASN1Err('{0!r}: range too large for expansion'.format(self)))
|
|
|
|
def intersect(self, ra):
|
|
"""
|
|
returns a single ASN1RangeInt which is the intersection of self and `ra'
|
|
in case they intersect, None otherwise
|
|
"""
|
|
if not isinstance(ra, ASN1RangeInt):
|
|
return None
|
|
#
|
|
# disjoint sets:
|
|
if self.ub is not None and ra.lb is not None and self.ub < ra.lb:
|
|
return None
|
|
elif ra.ub is not None and self.lb is not None and ra.ub < self.lb:
|
|
return None
|
|
#
|
|
# intersecting sets:
|
|
elif ra.lb in self:
|
|
if ra.ub in self:
|
|
return ASN1RangeInt(ra.lb, ra.ub)
|
|
else:
|
|
return ASN1RangeInt(ra.lb, self.ub)
|
|
elif ra.ub in self:
|
|
return ASN1RangeInt(self.lb, ra.ub)
|
|
else:
|
|
return ASN1RangeInt(self.lb, self.ub)
|
|
|
|
def unite(self, ra):
|
|
"""
|
|
returns a single ASN1RangeInt which is the union of self and `ra'
|
|
in case they intersect, None otherwise
|
|
"""
|
|
if not isinstance(ra, ASN1RangeInt):
|
|
return None
|
|
#
|
|
# disjoint sets:
|
|
if self.ub is not None and ra.lb is not None and self.ub < ra.lb:
|
|
return None
|
|
elif ra.ub is not None and self.lb is not None and ra.ub < self.lb:
|
|
return None
|
|
#
|
|
# intersecting sets:
|
|
if self.lb is None or ra.lb is None:
|
|
lb = None
|
|
else:
|
|
lb = min(self.lb, ra.lb)
|
|
if self.ub is None or ra.ub is None:
|
|
ub = None
|
|
else:
|
|
ub = max(self.ub, ra.ub)
|
|
return ASN1RangeInt(lb, ub)
|
|
|
|
def diff(self, ra):
|
|
"""
|
|
returns a 2-tuple of ASN1RangeStr or None, which are the exclusive
|
|
parts of each self and `ra' ranges
|
|
"""
|
|
if not isinstance(ra, ASN1RangeInt):
|
|
return self, ra
|
|
#
|
|
# disjoint sets:
|
|
if self.ub is not None and ra.lb is not None and self.ub < ra.lb:
|
|
return self, ra
|
|
elif ra.ub is not None and self.lb is not None and ra.ub < self.lb:
|
|
return ra, self
|
|
#
|
|
# intersecting sets:
|
|
# lower set
|
|
if self.lb == ra.lb:
|
|
lset = None
|
|
else:
|
|
lset = ASN1RangeStr(min(self.lb, ra.lb), max(self.lb, ra.lb) - 1)
|
|
# upper set
|
|
if self.lb == ra.lb:
|
|
lset = None
|
|
else:
|
|
if None in (self.lb, ra.lb):
|
|
lset = ASN1RangeInt(None, max(self.lb, ra.lb) - 1)
|
|
else:
|
|
lset = ASN1RangeInt(min(self.lb, ra.lb), max(self.lb, ra.lb) - 1)
|
|
if self.ub == ra.ub:
|
|
uset = None
|
|
else:
|
|
if None in (self.ub, ra.ub):
|
|
uset = ASN1RangeInt(min(self.ub, ra.ub) + 1, None)
|
|
else:
|
|
uset = ASN1RangeInt(min(self.ub, ra.ub) + 1, max(self.ub, ra.ub))
|
|
return lset, uset
|
|
|
|
|
|
class ASN1RangeStr(ASN1Range):
|
|
|
|
KW = ('lb', 'ub')
|
|
|
|
def __init__(self, lb=chr(0), ub=chr(0xff)):
|
|
self.lb = lb
|
|
self.ub = ub
|
|
|
|
# Python makes character string orderable ! This helps...
|
|
def _safechk(self):
|
|
if not isinstance(self.lb, str_types) or \
|
|
not isinstance(self.ub, str_types) or \
|
|
len(self.lb) != 1 or len(self.ub) != 1 or self.ub < self.lb:
|
|
raise(ASN1Err('{0!r}!: invalid bounds'.format(self)))
|
|
|
|
def __repr__(self):
|
|
return 'ASN1RangeStr(\'{0}\'..\'{1}\')'.format(self.lb, self.ub)
|
|
|
|
def __lt__(self, other):
|
|
if not isinstance(other, self.__class__):
|
|
raise(ASN1Err('{0!r}: unable to compare with {1!r}'.format(self, other)))
|
|
if self.lb == other.lb:
|
|
return self.ub < other.ub
|
|
else:
|
|
return self.lb < other.lb
|
|
|
|
def __contains__(self, item):
|
|
if not isinstance(item, str_types) or len(item) > 1:
|
|
return False
|
|
else:
|
|
return self.lb <= item <= self.ub
|
|
|
|
def expand(self):
|
|
"""
|
|
returns a list of characters
|
|
"""
|
|
return list(map(chr, range(ord(self.lb), 1+ord(self.ub))))
|
|
|
|
def intersect(self, ra):
|
|
"""
|
|
returns a single ASN1RangeStr which is the intersection of self and `ra'
|
|
in case they intersect, None otherwise
|
|
"""
|
|
if not isinstance(ra, ASN1RangeStr):
|
|
return None
|
|
#
|
|
lb, ub, ralb, raub = ord(self.lb), ord(self.ub), ord(ra.lb), ord(ra.ub)
|
|
# disjoint sets:
|
|
if ub < ralb or raub < lb:
|
|
return None
|
|
#
|
|
# intersecting sets:
|
|
else:
|
|
return ASN1RangeStr(chr(max(lb, ralb)), chr(min(ub, raub)))
|
|
|
|
def unite(self, ra):
|
|
"""
|
|
returns a single ASN1RangeInt which is the union of self and `ra'
|
|
in case they intersect, None otherwise
|
|
"""
|
|
if not isinstance(ra, ASN1RangeStr):
|
|
return None
|
|
#
|
|
lb, ub, ralb, raub = ord(self.lb), ord(self.ub), ord(ra.lb), ord(ra.ub)
|
|
# disjoint sets:
|
|
if ub < ralb or raub < lb:
|
|
return None
|
|
#
|
|
# intersecting sets:
|
|
else:
|
|
return ASN1RangeStr(chr(min(lb, ralb)), chr(max(ub, raub)))
|
|
|
|
def diff(self, ra):
|
|
"""
|
|
returns a 2-tuple of ASN1RangeStr or None, which are the exclusive
|
|
parts of each self and `ra' ranges
|
|
"""
|
|
if not isinstance(ra, ASN1RangeStr):
|
|
return self, ra
|
|
#
|
|
lb, ub, ralb, raub = ord(self.lb), ord(self.ub), ord(ra.lb), ord(ra.ub)
|
|
# disjoint sets:
|
|
if ub < ralb:
|
|
return self, ra
|
|
elif raub < lb:
|
|
return ra, self
|
|
#
|
|
# intersecting sets:
|
|
# lower set
|
|
if self.lb == ra.lb:
|
|
lset = None
|
|
else:
|
|
lset = ASN1RangeStr(min(self.lb, ra.lb), max(self.lb, ra.lb) - 1)
|
|
# upper set
|
|
if self.ub == ra.ub:
|
|
uset = None
|
|
else:
|
|
uset = ASN1RangeStr(min(self.ub, ra.ub) + 1, max(self.ub, ra.ub))
|
|
return lset, uset
|
|
|
|
|
|
MINUS_INF = (-1, None, None)
|
|
PLUS_INF = ( 1, None, None)
|
|
NAN = ( 0, None, None)
|
|
|
|
def real_to_float(realtuple):
|
|
# TODO: Python float default precision is quite bad
|
|
# it would be nice to use the Decimal module to set the floating point
|
|
# precision as required
|
|
# or even better, to use a module dedicated to arbitrary precision floating
|
|
# point operation
|
|
# TODO: moreover, for very large exponent, it will raises an OverflowError
|
|
# as the integral exponentiation will fail
|
|
return float(realtuple[0]*(realtuple[1]**realtuple[2]))
|
|
|
|
def real_lowest(rt1, rt2):
|
|
if rt1 == MINUS_INF or rt2 == MINUS_INF:
|
|
return MINUS_INF
|
|
elif rt1 == PLUS_INF:
|
|
return rt2
|
|
elif rt2 == PLUS_INF:
|
|
return rt1
|
|
#
|
|
rt1f, rt2f = real_to_float(rt1), real_to_float(rt2)
|
|
if rt1f <= rt2f:
|
|
return rt1
|
|
else:
|
|
return rt2
|
|
|
|
def real_highest(rt1, rt2):
|
|
if rt1 == PLUS_INF or rt2 == PLUS_INF:
|
|
return PLUS_INF
|
|
elif rt1 == MINUS_INF:
|
|
return rt2
|
|
elif rt2 == MINUS_INF:
|
|
return rt1
|
|
#
|
|
rt1f, rt2f = real_to_float(rt1), real_to_float(rt2)
|
|
if rt1f <= rt2f:
|
|
return rt2
|
|
else:
|
|
return rt1
|
|
|
|
class ASN1RangeReal(ASN1Range):
|
|
|
|
KW = ('lb', 'ub', 'lb_incl', 'ub_incl')
|
|
|
|
def __init__(self, lb=MINUS_INF, ub=PLUS_INF, lb_incl=True, ub_incl=True):
|
|
self.lb = lb
|
|
self.lb_incl = lb_incl
|
|
self.ub = ub
|
|
self.ub_incl = ub_incl
|
|
|
|
def _safechk(self):
|
|
if not isinstance(self.lb, tuple) or not isinstance(self.ub, tuple) or \
|
|
len(self.lb) != 3 or len(self.ub) != 3 or \
|
|
not isinstance(self.lb[0], integer_types) or \
|
|
not isinstance(self.ub[0], integer_types) or \
|
|
not all([isinstance(b, integer_types + (NoneType, )) for b in \
|
|
(self.lb[1], self.lb[2], self.ub[1], self.ub[2])]):
|
|
raise(ASN1Err('{0!r}: invalid bounds'.format(self.__class__.__name__)))
|
|
elif self.lb in (NAN, PLUS_INF) or self.ub in (NAN, MINUS_INF):
|
|
raise(ASN1Err('{0!r}: invalid inifinite bound'.format(self)))
|
|
elif self.lb != MINUS_INF and self.ub != PLUS_INF:
|
|
lb, ub = real_to_float(self.lb), real_to_float(self.ub)
|
|
if ub < lb:
|
|
raise(ASN1Err('{0!r}: invalid bounds'.format(self)))
|
|
elif lb == ub and not (self.lb_incl and self.ub_incl):
|
|
raise(ASN1Err('{0!r}: invalid bounds'.format(self)))
|
|
|
|
def __repr__(self):
|
|
if self.lb == MINUS_INF:
|
|
lb = 'MINUS-INFINITY'
|
|
elif self.lb[1] == 10:
|
|
lb = '{0!r}e{1!r}'.format(self.lb[0], self.lb[2])
|
|
else:
|
|
lb = '{0!r}*{1!r}**{2!r}'.format(self.lb[0], self.lb[1], self.lb[2])
|
|
if not self.lb_incl:
|
|
lb = lb + '<'
|
|
if self.ub == PLUS_INF:
|
|
ub = 'PLUS-INFINITY'
|
|
elif self.ub[1] == 10:
|
|
ub = '{0!r}e{1!r}'.format(self.ub[0], self.ub[2])
|
|
else:
|
|
ub = '{0!r}*{1!r}**{2!r}'.format(self.lb[0], self.lb[1], self.lb[2])
|
|
if not self.ub_incl:
|
|
ub = '<' + ub
|
|
return 'ASN1RangeReal({0}..{1})'.format(lb, ub)
|
|
|
|
def __lt__(self, other):
|
|
if not isinstance(other, self.__class__):
|
|
raise(ASN1Err('{0!r}: unable to compare with {1!r}'.format(self, other)))
|
|
if self.lb == MINUS_INF:
|
|
if other.lb == MINUS_INF:
|
|
if self.ub == PLUS_INF:
|
|
return False
|
|
elif other.ub == PLUS_INF:
|
|
return True
|
|
else:
|
|
return real_to_float(self.ub) < real_to_float(other.ub)
|
|
else:
|
|
return True
|
|
elif other.lb == MINUS_INF:
|
|
return False
|
|
elif self.lb == other.lb or real_to_float(self.lb) == real_to_float(self.ub):
|
|
if self.ub == PLUS_INF:
|
|
return False
|
|
elif other.ub == PLUS_INF:
|
|
return True
|
|
else:
|
|
return real_to_float(self.ub) < real_to_float(other.ub)
|
|
else:
|
|
return real_to_float(self.lb) < real_to_float(other.lb)
|
|
|
|
def __contains__(self, item):
|
|
if not isinstance(item, tuple) or len(item) != 3:
|
|
return False
|
|
elif not all([isinstance(i, integer_types) for i in item]) or \
|
|
item not in (MINUS_INF, PLUS_INF):
|
|
return False
|
|
#
|
|
if item == MINUS_INF and self.lb == MINUS_INF:
|
|
return self.lb_incl
|
|
elif item == PLUS_INF and self.ub == PLUS_INF:
|
|
return self.ub_incl
|
|
elif real_lowest(self.lb, item) == item:
|
|
return False
|
|
elif real_highest(self.ub, item) == item:
|
|
return False
|
|
else:
|
|
return True
|
|
|
|
def intersect(self, ra):
|
|
"""
|
|
returns a single ASN1RangeReal which is the intersection of self and `ra'
|
|
in case they intersect, None otherwise
|
|
"""
|
|
if not isinstance(ra, ASN1RangeReal):
|
|
return None
|
|
#
|
|
if self.lb != MINUS_INF:
|
|
slb = real_to_float(self.lb)
|
|
if self.ub != PLUS_INF:
|
|
sub = real_to_float(self.ub)
|
|
if ra.lb != MINUS_INF:
|
|
ralb = real_to_float(ra.lb)
|
|
if ra.ub != PLUS_INF:
|
|
raub = real_to_float(ra.ub)
|
|
#
|
|
# disjoint sets:
|
|
if self.ub is not PLUS_INF and ra.lb is not MINUS_INF and sub < ralb:
|
|
return None
|
|
elif ra.ub is not PLUS_INF and self.lb is not MINUS_INF and raub < slb:
|
|
return None
|
|
#
|
|
# intersecting sets:
|
|
# lower bound
|
|
if MINUS_INF == self.lb == ra.lb:
|
|
lb, lb_incl = MINUS_INF, self.lb_incl & ra.lb_incl
|
|
elif MINUS_INF not in (self.lb, ra.lb) and slb == ralb:
|
|
lb, lb_incl = self.lb, self.lb_incl & ra.lb_incl
|
|
else:
|
|
lb = real_highest(self.lb, ra.lb)
|
|
if lb == self.lb:
|
|
lb_incl = self.lb_incl
|
|
else:
|
|
lb_incl = ra.lb_incl
|
|
# upper bound
|
|
if PLUS_INF == self.ub == ra.ub:
|
|
ub, ub_incl = PLUS_INF, self.ub_incl & ra.ub_incl
|
|
elif PLUS_INF not in (self.ub, ra.ub) and sub == raub:
|
|
ub, ub_incl = self.ub, self.ub_incl & ra.ub_incl
|
|
else:
|
|
ub = real_lowest(self.ub, ra.ub)
|
|
if ub == self.ub:
|
|
ub_incl = self.ub_incl
|
|
else:
|
|
ub_incl = ra.ub_incl
|
|
return ASN1RangeReal(lb, ub, lb_incl, ub_incl)
|
|
|
|
def unite(self, ra):
|
|
"""
|
|
returns a single ASN1RangeReal which is the union of self and `ra'
|
|
in case they intersect, None otherwise
|
|
"""
|
|
if not isinstance(ra, ASN1RangeReal):
|
|
return None
|
|
#
|
|
if self.lb != MINUS_INF:
|
|
slb = real_to_float(self.lb)
|
|
if self.ub != PLUS_INF:
|
|
sub = real_to_float(self.ub)
|
|
if ra.lb != MINUS_INF:
|
|
ralb = real_to_float(ra.lb)
|
|
if ra.ub != PLUS_INF:
|
|
raub = real_to_float(ra.ub)
|
|
#
|
|
# disjoint sets:
|
|
if self.ub is not PLUS_INF and ra.lb is not MINUS_INF and sub < ralb:
|
|
return None
|
|
elif ra.ub is not PLUS_INF and self.lb is not MINUS_INF and raub < slb:
|
|
return None
|
|
#
|
|
# intersecting sets:
|
|
# lower bound
|
|
if MINUS_INF == self.lb == ra.lb:
|
|
lb, lb_incl = MINUS_INF, self.lb_incl | ra.lb_incl
|
|
elif MINUS_INF not in (self.lb, ra.lb) and slb == ralb:
|
|
lb, lb_incl = self.lb, self.lb_incl | ra.lb_incl
|
|
else:
|
|
lb = real_lowest(self.lb, ra.lb)
|
|
if lb == self.lb:
|
|
lb_incl = self.lb_incl
|
|
else:
|
|
lb_incl = ra.lb_incl
|
|
# upper bound
|
|
if PLUS_INF == self.ub == ra.ub:
|
|
ub, ub_incl = PLUS_INF, self.ub_incl | ra.ub_incl
|
|
elif PLUS_INF not in (self.ub, ra.ub) and sub == raub:
|
|
ub, ub_incl = self.ub, self.ub_incl | ra.ub_incl
|
|
else:
|
|
ub = real_highest(self.ub, ra.ub)
|
|
if ub == self.ub:
|
|
ub_incl = self.ub_incl
|
|
else:
|
|
ub_incl = ra.ub_incl
|
|
return ASN1RangeReal(lb, ub, lb_incl, ub_incl)
|
|
|
|
def diff(self, ra):
|
|
"""
|
|
returns a 2-tuple of ASN1RangeReal or None, which are the exclusive
|
|
parts of each self and `ra' ranges
|
|
"""
|
|
if not isinstance(ra, ASN1RangeReal):
|
|
return self, ra
|
|
#
|
|
if self.lb != MINUS_INF:
|
|
slb = real_to_float(self.lb)
|
|
if self.ub != PLUS_INF:
|
|
sub = real_to_float(self.ub)
|
|
if ra.lb != MINUS_INF:
|
|
ralb = real_to_float(ra.lb)
|
|
if ra.ub != PLUS_INF:
|
|
raub = real_to_float(ra.ub)
|
|
#
|
|
# disjoint sets:
|
|
if self.ub is not PLUS_INF and ra.lb is not MINUS_INF and sub < ralb:
|
|
return self, ra
|
|
elif ra.ub is not PLUS_INF and self.lb is not MINUS_INF and raub < slb:
|
|
return ra, self
|
|
#
|
|
# intersecting sets
|
|
# lower set
|
|
if MINUS_INF == self.lb == ra.lb or \
|
|
MINUS_INF not in (self.lb, ra.lb) and slb == ralb:
|
|
# no lower set
|
|
lset = None
|
|
else:
|
|
lset_lb = real_lowest(self.lb, ra.lb)
|
|
if lset_lb == self.lb:
|
|
lset_lb_incl = self.lb_incl
|
|
lset_ub = ra.lb
|
|
lset_ub_incl = not ra.lb_incl
|
|
else:
|
|
lset_lb_incl = ra.lb_incl
|
|
lset_ub = self.lb
|
|
lset_ub_incl = not self.lb_incl
|
|
lset = ASN1RangeReal(lset_lb, lset_ub, lset_lb_incl, lset_ub_incl)
|
|
# upper set
|
|
if PLUS_INF == self.ub == ra.ub or \
|
|
PLUS_INF not in (self.ub, ra.ub) and sub == raub:
|
|
uset = None
|
|
else:
|
|
uset_ub = real_highest(self.ub, ra.ub)
|
|
if uset_ub == self.ub:
|
|
uset_ub_incl = self.ub_incl
|
|
uset_lb = ra.ub
|
|
uset_lb_incl = not ra.ub_incl
|
|
else:
|
|
uset_ub_incl = ra.ub_incl
|
|
uset_lb = self.ub
|
|
uset_lb_incl = self.ub_incl
|
|
uset = ASN1RangeReal(uset_lb, uset_ub, uset_lb_incl, uset_ub_incl)
|
|
#
|
|
return lset, uset
|
|
|
|
|
|
def reduce_rangelist(rl=[]):
|
|
"""
|
|
reduces a list of ranges by reuniting intersecting ones
|
|
"""
|
|
# reduced list, to be returned
|
|
red = []
|
|
#
|
|
for r in rl:
|
|
if r is None:
|
|
pass
|
|
else:
|
|
# check in case this range can get united within some previous one(s)
|
|
u, united = None, []
|
|
for rr in red:
|
|
u = rr.unite(r)
|
|
if u is not None:
|
|
united.append(red.index(rr))
|
|
# r is growing...
|
|
r = u
|
|
# remove from red all ranges united with r
|
|
for i in united[::-1]:
|
|
del red[i]
|
|
red.append(r)
|
|
return red
|
|
|
|
|
|
#------------------------------------------------------------------------------#
|
|
# set of ASN.1 values or range of values
|
|
#------------------------------------------------------------------------------#
|
|
# WNG: in case of TYPE_REAL values, they are managerd in their 3-tuple format
|
|
# hence test for inclusion may fail
|
|
|
|
class ASN1Set(object):
|
|
"""
|
|
Class to handle a set of (range of) values for any ASN.1 types
|
|
|
|
_rr : list with all individual values in the root set
|
|
_rv : list with all ranges of values in the root set
|
|
_ev : None (if not extendable) or list with all individual values in the
|
|
extension set
|
|
_er : list with all ranges of values in the extension set
|
|
|
|
root : ordered list with all individual and ranges of values in the root set
|
|
ext : ordered list with all individual and ranges of values in the
|
|
extension set
|
|
"""
|
|
|
|
_CONTAIN_WEXT = False # use extension to test for containment
|
|
|
|
def __init__(self, rv=[], rr=[], ev=None, er=[], name=b''):
|
|
self._rr = reduce_rangelist(rr)
|
|
self._rv = []
|
|
sort_root, sort_ext = True, True
|
|
for v in rv:
|
|
if isinstance(v, dict):
|
|
# dictionnary of heterogeneous values:
|
|
# not comparable neither sortable
|
|
self._rv.append(v)
|
|
sort_root = False
|
|
elif not any([v in _rr for _rr in self._rr]):
|
|
self._rv.append(v)
|
|
if ev is not None:
|
|
self._er = reduce_rangelist(er)
|
|
self._ev = []
|
|
for v in ev:
|
|
if isinstance(v, dict):
|
|
# dictionnary of heterogeneous values:
|
|
# not comparable neither sortable
|
|
self._ev.append(v)
|
|
sort_ext = False
|
|
elif not any([v in _er for _er in self._er]):
|
|
self._ev.append(v)
|
|
else:
|
|
self._er = []
|
|
self._ev = None
|
|
self._init(sort_root, sort_ext)
|
|
|
|
def _init(self, sort_root, sort_ext):
|
|
"""
|
|
creates the `root' and `ext' attributes which lists all values and
|
|
ranges of their domain in order
|
|
"""
|
|
if sort_root:
|
|
if self._rv:
|
|
try:
|
|
self._rv.sort()
|
|
except Exception:
|
|
pass
|
|
if self._rr:
|
|
try:
|
|
self._rr.sort()
|
|
except Exception:
|
|
pass
|
|
if sort_ext:
|
|
if self._ev:
|
|
try:
|
|
self._ev.sort()
|
|
except Exception:
|
|
pass
|
|
if self._er:
|
|
try:
|
|
self._er.sort()
|
|
except Exception:
|
|
pass
|
|
#
|
|
self.root = []
|
|
if self._rv and isinstance(self._rv[0], str_types) or \
|
|
self._rr and isinstance(self._rr[0].lb, str_types):
|
|
# ASN1RangeStr are systematically expanded within ASN1Set
|
|
for rr in self._rr:
|
|
self.root.extend(rr.expand())
|
|
for rv in self._rv:
|
|
if rv not in self.root:
|
|
self.root.append(rv)
|
|
self.root.sort()
|
|
else:
|
|
rv, rr = self._rv, [_rr.lb for _rr in self._rr]
|
|
rv_off = 0
|
|
if rr and rr[0] is None:
|
|
# if None (MIN / MINUS-INFINITY) is in the ranges as lb,
|
|
# it is always at index 0 because of reduce_rangelist()
|
|
self.root.append(self._rr[0])
|
|
rr_off = 1
|
|
else:
|
|
rr_off = 0
|
|
while rv_off < len(rv) and rr_off < len(rr):
|
|
if rv[rv_off] < rr[rr_off]:
|
|
self.root.append(self._rv[rv_off])
|
|
rv_off += 1
|
|
else:
|
|
self.root.append(self._rr[rr_off])
|
|
rr_off += 1
|
|
if rv_off < len(rv):
|
|
self.root.extend( self._rv[rv_off:] )
|
|
elif rr_off < len(rr):
|
|
self.root.extend( self._rr[rr_off:] )
|
|
#
|
|
if self._ev is not None:
|
|
self.ext = []
|
|
if self._ev and isinstance(self._ev[0], str_types) or \
|
|
self._er and isinstance(self._er[0].lb, str_types):
|
|
# ASN1RangeStr are systematically expanded within ASN1Set
|
|
for er in self._er:
|
|
self.ext.extend(er.expand())
|
|
for ev in self._ev:
|
|
if ev not in self.ext:
|
|
self.ext.append(ev)
|
|
self.ext.sort()
|
|
else:
|
|
ev, er = self._ev, [_er.lb for _er in self._er]
|
|
ev_off = 0
|
|
if er and er[0] is None:
|
|
self.ext.append(self._er[0])
|
|
er_off = 1
|
|
else:
|
|
er_off = 0
|
|
while ev_off < len(ev) and er_off < len(er):
|
|
if ev[ev_off] < er[er_off]:
|
|
self.ext.append(self._ev[ev_off])
|
|
ev_off += 1
|
|
else:
|
|
self.ext.append(self._er[er_off])
|
|
er_off += 1
|
|
if ev_off < len(ev):
|
|
self.ext.extend( self._ev[ev_off:] )
|
|
elif er_off < len(er):
|
|
self.ext.extend( self._er[er_off:] )
|
|
else:
|
|
self.ext = None
|
|
|
|
def _set_root_bnd(self):
|
|
"""
|
|
sets the dynamic in bits required to express all root values
|
|
(including ranges), only supporting ASN1Set of integers or character
|
|
alphabets
|
|
"""
|
|
if not self.root:
|
|
# this should never happen
|
|
assert()
|
|
else:
|
|
self.lb, self.ub = self.__get_root_bnd('lb'), self.__get_root_bnd('ub')
|
|
if None in (self.lb, self.ub):
|
|
self.rdyn = None
|
|
self.ra = None
|
|
elif isinstance(self.lb, integer_types):
|
|
self.rdyn = (self.ub - self.lb).bit_length()
|
|
self.ra = 1 + self.ub - self.lb
|
|
else:
|
|
# character strings
|
|
# contrary to integers, the range and dynamic is computed over
|
|
# the set of characters, not the difference between bounds
|
|
self.ra = len(self.root)
|
|
self.rdyn = (self.ra - 1).bit_length()
|
|
|
|
def __get_root_bnd(self, bnd='lb'):
|
|
bnd_ind = {'lb':0, 'ub':-1}[bnd]
|
|
if isinstance(self.root[bnd_ind], (ASN1RangeInt, ASN1RangeStr)):
|
|
return getattr(self.root[bnd_ind], bnd)
|
|
elif isinstance(self.root[bnd_ind], integer_types):
|
|
return self.root[bnd_ind]
|
|
elif isinstance(self.root[bnd_ind], str_types) and len(self.root[bnd_ind]) == 1:
|
|
return self.root[bnd_ind]
|
|
else:
|
|
return None
|
|
|
|
def __repr__(self):
|
|
root = '[' + ', '.join([repr(r) for r in self.root]) + ']'
|
|
if self.ext is None:
|
|
ext = 'None'
|
|
else:
|
|
ext = '[' + ', '.join([repr(e) for e in self.ext]) + ']'
|
|
return 'ASN1Set(root={0}, ext={1})'.format(root, ext)
|
|
|
|
def is_empty(self):
|
|
return self._rv == [] and self._rr == []
|
|
|
|
def is_ext(self):
|
|
return self._er is not None
|
|
|
|
def __contains__(self, v):
|
|
if self._CONTAIN_WEXT:
|
|
return self.in_root(v) or self.in_ext(v)
|
|
else:
|
|
return self.in_root(v)
|
|
|
|
def in_root(self, v):
|
|
for r in self._rr:
|
|
if v in r:
|
|
return True
|
|
if v in self._rv:
|
|
return True
|
|
return False
|
|
|
|
def in_ext(self, v):
|
|
# WNG: for complex constraint, this may return True,
|
|
# even if in_root() returns also True
|
|
if self._ev is None:
|
|
return False
|
|
for r in self._er:
|
|
if v in r:
|
|
return True
|
|
if v in self._ev:
|
|
return True
|
|
return False
|
|
|
|
def intersect(self, S):
|
|
"""
|
|
returns an ASN1Set which root part is the intersection or the root parts
|
|
of self and S, and ext part contains all remaining defined values if self
|
|
and S are extensible
|
|
"""
|
|
ret_root, ret_root_r, = [], []
|
|
# 1) check if ret is extensible
|
|
if self.is_ext() and S.is_ext():
|
|
ret_ext = []
|
|
else:
|
|
ret_ext = None
|
|
# 2) get the intersection of root ranges
|
|
if self._rr and S._rr:
|
|
for r in S._rr:
|
|
# list of intersection with all root ranges of self
|
|
inter = [r.intersect(sr) for sr in self._rr]
|
|
for r in inter:
|
|
if r is not None:
|
|
if r.lb is not None and r.lb == r.ub:
|
|
# range with a single value
|
|
ret_root.append(r.lb)
|
|
else:
|
|
ret_root_r.append(r)
|
|
ret = ASN1Set(rv=ret_root, rr=ret_root_r, ev=ret_ext)
|
|
# 3) check the root individual values
|
|
for v in self._rv:
|
|
if S.in_root(v) and not ret.in_root(v):
|
|
ret._rv.append(v)
|
|
for v in S.root:
|
|
if self.in_root(v) and not ret.in_root(v):
|
|
ret._rv.append(v)
|
|
# 4) build ret extension
|
|
if ret_ext is not None:
|
|
# 4.1) gather both self and S root and extension ranges and remove
|
|
# ret._rv and ret._rr parts of it to put in ret._er
|
|
union = reduce_rangelist(self._rr + S._rr + self._er + S._er)
|
|
ret._er = union
|
|
# TODO: doing holes in ext_r ...
|
|
# ret.ext_r = union - (ret.root_r + ret.root)
|
|
#
|
|
# 4.2) gather both self and S extension individual values
|
|
for v in self._ev:
|
|
if not ret.in_root(v) and not ret.in_ext(v):
|
|
ret._ev.append(v)
|
|
for v in S._ev:
|
|
if not ret.in_root(v) and not ret.in_ext(v):
|
|
ret._ev.append(v)
|
|
# 4.3) add self and S root values not intersecting
|
|
for v in self.root:
|
|
if not ret.in_root(v) and not ret.in_ext(v):
|
|
ret._ev.append(v)
|
|
for v in S.root:
|
|
if not ret.in_root(v) and not ret.in_ext(v):
|
|
ret._ev.append(v)
|
|
ret._init()
|
|
return ret
|
|
|
|
def get_root_dyn(self):
|
|
"""
|
|
returns the dynamic required to express all root values (including ranges)
|
|
"""
|
|
num = len(self._rv)
|
|
for rr in self._rr:
|
|
# ranges, only possible for finite ASN1RangeInt and ASN1RangeStr
|
|
if isinstance(rr, (ASN1RangeInt, ASN1RangeStr)) and \
|
|
rr.lb is not None and rr.ub is not None:
|
|
num += rr.ub - rr.lb
|
|
else:
|
|
return None
|
|
return num.bit_length()
|
|
|
|
def getv(self):
|
|
"""
|
|
returns the list of individual values in the root and ext part, omitting
|
|
any values in ranges
|
|
"""
|
|
ret = self._rv[:]
|
|
if self._ev is not None:
|
|
ret.extend( self._ev )
|
|
return ret
|
|
|