1#
2#   ASN.1 subtype constraints classes.
3#
4#   Constraints are relatively rare, but every ASN1 object
5#   is doing checks all the time for whether they have any
6#   constraints and whether they are applicable to the object.
7#
8#   What we're going to do is define objects/functions that
9#   can be called unconditionally if they are present, and that
10#   are simply not present if there are no constraints.
11#
12#   Original concept and code by Mike C. Fletcher.
13#
14import sys
15from pyasn1.type import error
16
17class AbstractConstraint:
18    """Abstract base-class for constraint objects
19
20       Constraints should be stored in a simple sequence in the
21       namespace of their client Asn1Item sub-classes.
22    """
23    def __init__(self, *values):
24        self._valueMap = {}
25        self._setValues(values)
26        self.__hashedValues = None
27    def __call__(self, value, idx=None):
28        try:
29            self._testValue(value, idx)
30        except error.ValueConstraintError:
31            raise error.ValueConstraintError(
32               '%s failed at: \"%s\"' % (self, sys.exc_info()[1])
33            )
34    def __repr__(self):
35        return '%s(%s)' % (
36            self.__class__.__name__,
37            ', '.join([repr(x) for x in self._values])
38        )
39    def __eq__(self, other):
40        return self is other and True or self._values == other
41    def __ne__(self, other): return self._values != other
42    def __lt__(self, other): return self._values < other
43    def __le__(self, other): return self._values <= other
44    def __gt__(self, other): return self._values > other
45    def __ge__(self, other): return self._values >= other
46    if sys.version_info[0] <= 2:
47        def __nonzero__(self): return bool(self._values)
48    else:
49        def __bool__(self): return bool(self._values)
50
51    def __hash__(self):
52        if self.__hashedValues is None:
53            self.__hashedValues = hash((self.__class__.__name__, self._values))
54        return self.__hashedValues
55
56    def _setValues(self, values): self._values = values
57    def _testValue(self, value, idx):
58        raise error.ValueConstraintError(value)
59
60    # Constraints derivation logic
61    def getValueMap(self): return self._valueMap
62    def isSuperTypeOf(self, otherConstraint):
63        return self in otherConstraint.getValueMap() or \
64               otherConstraint is self or otherConstraint == self
65    def isSubTypeOf(self, otherConstraint):
66        return otherConstraint in self._valueMap or \
67               otherConstraint is self or otherConstraint == self
68
69class SingleValueConstraint(AbstractConstraint):
70    """Value must be part of defined values constraint"""
71    def _testValue(self, value, idx):
72        # XXX index vals for performance?
73        if value not in self._values:
74            raise error.ValueConstraintError(value)
75
76class ContainedSubtypeConstraint(AbstractConstraint):
77    """Value must satisfy all of defined set of constraints"""
78    def _testValue(self, value, idx):
79        for c in self._values:
80            c(value, idx)
81
82class ValueRangeConstraint(AbstractConstraint):
83    """Value must be within start and stop values (inclusive)"""
84    def _testValue(self, value, idx):
85        if value < self.start or value > self.stop:
86            raise error.ValueConstraintError(value)
87
88    def _setValues(self, values):
89        if len(values) != 2:
90            raise error.PyAsn1Error(
91                '%s: bad constraint values' % (self.__class__.__name__,)
92                )
93        self.start, self.stop = values
94        if self.start > self.stop:
95            raise error.PyAsn1Error(
96                '%s: screwed constraint values (start > stop): %s > %s' % (
97                    self.__class__.__name__,
98                    self.start, self.stop
99                )
100            )
101        AbstractConstraint._setValues(self, values)
102
103class ValueSizeConstraint(ValueRangeConstraint):
104    """len(value) must be within start and stop values (inclusive)"""
105    def _testValue(self, value, idx):
106        l = len(value)
107        if l < self.start or l > self.stop:
108            raise error.ValueConstraintError(value)
109
110class PermittedAlphabetConstraint(SingleValueConstraint):
111    def _setValues(self, values):
112        self._values = ()
113        for v in values:
114            self._values = self._values + tuple(v)
115
116    def _testValue(self, value, idx):
117        for v in value:
118            if v not in self._values:
119                raise error.ValueConstraintError(value)
120
121# This is a bit kludgy, meaning two op modes within a single constraing
122class InnerTypeConstraint(AbstractConstraint):
123    """Value must satisfy type and presense constraints"""
124    def _testValue(self, value, idx):
125        if self.__singleTypeConstraint:
126            self.__singleTypeConstraint(value)
127        elif self.__multipleTypeConstraint:
128            if idx not in self.__multipleTypeConstraint:
129                raise error.ValueConstraintError(value)
130            constraint, status = self.__multipleTypeConstraint[idx]
131            if status == 'ABSENT':   # XXX presense is not checked!
132                raise error.ValueConstraintError(value)
133            constraint(value)
134
135    def _setValues(self, values):
136        self.__multipleTypeConstraint = {}
137        self.__singleTypeConstraint = None
138        for v in values:
139            if isinstance(v, tuple):
140                self.__multipleTypeConstraint[v[0]] = v[1], v[2]
141            else:
142                self.__singleTypeConstraint = v
143        AbstractConstraint._setValues(self, values)
144
145# Boolean ops on constraints
146
147class ConstraintsExclusion(AbstractConstraint):
148    """Value must not fit the single constraint"""
149    def _testValue(self, value, idx):
150        try:
151            self._values[0](value, idx)
152        except error.ValueConstraintError:
153            return
154        else:
155            raise error.ValueConstraintError(value)
156
157    def _setValues(self, values):
158        if len(values) != 1:
159            raise error.PyAsn1Error('Single constraint expected')
160        AbstractConstraint._setValues(self, values)
161
162class AbstractConstraintSet(AbstractConstraint):
163    """Value must not satisfy the single constraint"""
164    def __getitem__(self, idx): return self._values[idx]
165
166    def __add__(self, value): return self.__class__(self, value)
167    def __radd__(self, value): return self.__class__(self, value)
168
169    def __len__(self): return len(self._values)
170
171    # Constraints inclusion in sets
172
173    def _setValues(self, values):
174        self._values = values
175        for v in values:
176            self._valueMap[v] = 1
177            self._valueMap.update(v.getValueMap())
178
179class ConstraintsIntersection(AbstractConstraintSet):
180    """Value must satisfy all constraints"""
181    def _testValue(self, value, idx):
182        for v in self._values:
183            v(value, idx)
184
185class ConstraintsUnion(AbstractConstraintSet):
186    """Value must satisfy at least one constraint"""
187    def _testValue(self, value, idx):
188        for v in self._values:
189            try:
190                v(value, idx)
191            except error.ValueConstraintError:
192                pass
193            else:
194                return
195        raise error.ValueConstraintError(
196            'all of %s failed for \"%s\"' % (self._values, value)
197            )
198
199# XXX
200# add tests for type check
201