# # 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