| # |
| # ASN.1 subtype constraints classes. |
| # |
| # Constraints are relatively rare, but every ASN1 object |
| # is doing checks all the time for whether they have any |
| # constraints and whether they are applicable to the object. |
| # |
| # What we're going to do is define objects/functions that |
| # can be called unconditionally if they are present, and that |
| # are simply not present if there are no constraints. |
| # |
| # Original concept and code by Mike C. Fletcher. |
| # |
| import sys |
| from pyasn1.type import error |
| |
| class AbstractConstraint: |
| """Abstract base-class for constraint objects |
| |
| Constraints should be stored in a simple sequence in the |
| namespace of their client Asn1Item sub-classes. |
| """ |
| def __init__(self, *values): |
| self._valueMap = {} |
| self._setValues(values) |
| self.__hashedValues = None |
| def __call__(self, value, idx=None): |
| try: |
| self._testValue(value, idx) |
| except error.ValueConstraintError: |
| raise error.ValueConstraintError( |
| '%s failed at: \"%s\"' % (self, sys.exc_info()[1]) |
| ) |
| def __repr__(self): |
| return '%s(%s)' % ( |
| self.__class__.__name__, |
| ', '.join([repr(x) for x in self._values]) |
| ) |
| def __eq__(self, other): |
| return self is other and True or self._values == other |
| def __ne__(self, other): return self._values != other |
| def __lt__(self, other): return self._values < other |
| def __le__(self, other): return self._values <= other |
| def __gt__(self, other): return self._values > other |
| def __ge__(self, other): return self._values >= other |
| if sys.version_info[0] <= 2: |
| def __nonzero__(self): return bool(self._values) |
| else: |
| def __bool__(self): return bool(self._values) |
| |
| def __hash__(self): |
| if self.__hashedValues is None: |
| self.__hashedValues = hash((self.__class__.__name__, self._values)) |
| return self.__hashedValues |
| |
| def _setValues(self, values): self._values = values |
| def _testValue(self, value, idx): |
| raise error.ValueConstraintError(value) |
| |
| # Constraints derivation logic |
| def getValueMap(self): return self._valueMap |
| def isSuperTypeOf(self, otherConstraint): |
| return self in otherConstraint.getValueMap() or \ |
| otherConstraint is self or otherConstraint == self |
| def isSubTypeOf(self, otherConstraint): |
| return otherConstraint in self._valueMap or \ |
| otherConstraint is self or otherConstraint == self |
| |
| class SingleValueConstraint(AbstractConstraint): |
| """Value must be part of defined values constraint""" |
| def _testValue(self, value, idx): |
| # XXX index vals for performance? |
| if value not in self._values: |
| raise error.ValueConstraintError(value) |
| |
| class ContainedSubtypeConstraint(AbstractConstraint): |
| """Value must satisfy all of defined set of constraints""" |
| def _testValue(self, value, idx): |
| for c in self._values: |
| c(value, idx) |
| |
| class ValueRangeConstraint(AbstractConstraint): |
| """Value must be within start and stop values (inclusive)""" |
| def _testValue(self, value, idx): |
| if value < self.start or value > self.stop: |
| raise error.ValueConstraintError(value) |
| |
| def _setValues(self, values): |
| if len(values) != 2: |
| raise error.PyAsn1Error( |
| '%s: bad constraint values' % (self.__class__.__name__,) |
| ) |
| self.start, self.stop = values |
| if self.start > self.stop: |
| raise error.PyAsn1Error( |
| '%s: screwed constraint values (start > stop): %s > %s' % ( |
| self.__class__.__name__, |
| self.start, self.stop |
| ) |
| ) |
| AbstractConstraint._setValues(self, values) |
| |
| class ValueSizeConstraint(ValueRangeConstraint): |
| """len(value) must be within start and stop values (inclusive)""" |
| def _testValue(self, value, idx): |
| l = len(value) |
| if l < self.start or l > self.stop: |
| raise error.ValueConstraintError(value) |
| |
| class PermittedAlphabetConstraint(SingleValueConstraint): |
| def _setValues(self, values): |
| self._values = () |
| for v in values: |
| self._values = self._values + tuple(v) |
| |
| def _testValue(self, value, idx): |
| for v in value: |
| if v not in self._values: |
| raise error.ValueConstraintError(value) |
| |
| # This is a bit kludgy, meaning two op modes within a single constraing |
| class InnerTypeConstraint(AbstractConstraint): |
| """Value must satisfy type and presense constraints""" |
| def _testValue(self, value, idx): |
| if self.__singleTypeConstraint: |
| self.__singleTypeConstraint(value) |
| elif self.__multipleTypeConstraint: |
| if idx not in self.__multipleTypeConstraint: |
| raise error.ValueConstraintError(value) |
| constraint, status = self.__multipleTypeConstraint[idx] |
| if status == 'ABSENT': # XXX presense is not checked! |
| raise error.ValueConstraintError(value) |
| constraint(value) |
| |
| def _setValues(self, values): |
| self.__multipleTypeConstraint = {} |
| self.__singleTypeConstraint = None |
| for v in values: |
| if isinstance(v, tuple): |
| self.__multipleTypeConstraint[v[0]] = v[1], v[2] |
| else: |
| self.__singleTypeConstraint = v |
| AbstractConstraint._setValues(self, values) |
| |
| # Boolean ops on constraints |
| |
| class ConstraintsExclusion(AbstractConstraint): |
| """Value must not fit the single constraint""" |
| def _testValue(self, value, idx): |
| try: |
| self._values[0](value, idx) |
| except error.ValueConstraintError: |
| return |
| else: |
| raise error.ValueConstraintError(value) |
| |
| def _setValues(self, values): |
| if len(values) != 1: |
| raise error.PyAsn1Error('Single constraint expected') |
| AbstractConstraint._setValues(self, values) |
| |
| class AbstractConstraintSet(AbstractConstraint): |
| """Value must not satisfy the single constraint""" |
| def __getitem__(self, idx): return self._values[idx] |
| |
| def __add__(self, value): return self.__class__(self, value) |
| def __radd__(self, value): return self.__class__(self, value) |
| |
| def __len__(self): return len(self._values) |
| |
| # Constraints inclusion in sets |
| |
| def _setValues(self, values): |
| self._values = values |
| for v in values: |
| self._valueMap[v] = 1 |
| self._valueMap.update(v.getValueMap()) |
| |
| class ConstraintsIntersection(AbstractConstraintSet): |
| """Value must satisfy all constraints""" |
| def _testValue(self, value, idx): |
| for v in self._values: |
| v(value, idx) |
| |
| class ConstraintsUnion(AbstractConstraintSet): |
| """Value must satisfy at least one constraint""" |
| def _testValue(self, value, idx): |
| for v in self._values: |
| try: |
| v(value, idx) |
| except error.ValueConstraintError: |
| pass |
| else: |
| return |
| raise error.ValueConstraintError( |
| 'all of %s failed for \"%s\"' % (self._values, value) |
| ) |
| |
| # XXX |
| # add tests for type check |