1#
2# This file is part of pyasn1 software.
3#
4# Copyright (c) 2005-2019, Ilya Etingof <etingof@gmail.com>
5# License: http://snmplabs.com/pyasn1/license.html
6#
7# Original concept and code by Mike C. Fletcher.
8#
9import sys
10
11from pyasn1.type import error
12
13__all__ = ['SingleValueConstraint', 'ContainedSubtypeConstraint',
14           'ValueRangeConstraint', 'ValueSizeConstraint',
15           'PermittedAlphabetConstraint', 'InnerTypeConstraint',
16           'ConstraintsExclusion', 'ConstraintsIntersection',
17           'ConstraintsUnion']
18
19
20class AbstractConstraint(object):
21
22    def __init__(self, *values):
23        self._valueMap = set()
24        self._setValues(values)
25        self.__hash = hash((self.__class__.__name__, self._values))
26
27    def __call__(self, value, idx=None):
28        if not self._values:
29            return
30
31        try:
32            self._testValue(value, idx)
33
34        except error.ValueConstraintError:
35            raise error.ValueConstraintError(
36                '%s failed at: %r' % (self, sys.exc_info()[1])
37            )
38
39    def __repr__(self):
40        representation = '%s object at 0x%x' % (self.__class__.__name__, id(self))
41
42        if self._values:
43            representation += ' consts %s' % ', '.join([repr(x) for x in self._values])
44
45        return '<%s>' % representation
46
47    def __eq__(self, other):
48        return self is other and True or self._values == other
49
50    def __ne__(self, other):
51        return self._values != other
52
53    def __lt__(self, other):
54        return self._values < other
55
56    def __le__(self, other):
57        return self._values <= other
58
59    def __gt__(self, other):
60        return self._values > other
61
62    def __ge__(self, other):
63        return self._values >= other
64
65    if sys.version_info[0] <= 2:
66        def __nonzero__(self):
67            return self._values and True or False
68    else:
69        def __bool__(self):
70            return self._values and True or False
71
72    def __hash__(self):
73        return self.__hash
74
75    def _setValues(self, values):
76        self._values = values
77
78    def _testValue(self, value, idx):
79        raise error.ValueConstraintError(value)
80
81    # Constraints derivation logic
82    def getValueMap(self):
83        return self._valueMap
84
85    def isSuperTypeOf(self, otherConstraint):
86        # TODO: fix possible comparison of set vs scalars here
87        return (otherConstraint is self or
88                not self._values or
89                otherConstraint == self or
90                self in otherConstraint.getValueMap())
91
92    def isSubTypeOf(self, otherConstraint):
93        return (otherConstraint is self or
94                not self or
95                otherConstraint == self or
96                otherConstraint in self._valueMap)
97
98
99class SingleValueConstraint(AbstractConstraint):
100    """Create a SingleValueConstraint object.
101
102    The SingleValueConstraint satisfies any value that
103    is present in the set of permitted values.
104
105    The SingleValueConstraint object can be applied to
106    any ASN.1 type.
107
108    Parameters
109    ----------
110    \*values: :class:`int`
111        Full set of values permitted by this constraint object.
112
113    Examples
114    --------
115    .. code-block:: python
116
117        class DivisorOfSix(Integer):
118            '''
119            ASN.1 specification:
120
121            Divisor-Of-6 ::= INTEGER (1 | 2 | 3 | 6)
122            '''
123            subtypeSpec = SingleValueConstraint(1, 2, 3, 6)
124
125        # this will succeed
126        divisor_of_six = DivisorOfSix(1)
127
128        # this will raise ValueConstraintError
129        divisor_of_six = DivisorOfSix(7)
130    """
131    def _setValues(self, values):
132        self._values = values
133        self._set = set(values)
134
135    def _testValue(self, value, idx):
136        if value not in self._set:
137            raise error.ValueConstraintError(value)
138
139
140class ContainedSubtypeConstraint(AbstractConstraint):
141    """Create a ContainedSubtypeConstraint object.
142
143    The ContainedSubtypeConstraint satisfies any value that
144    is present in the set of permitted values and also
145    satisfies included constraints.
146
147    The ContainedSubtypeConstraint object can be applied to
148    any ASN.1 type.
149
150    Parameters
151    ----------
152    \*values:
153        Full set of values and constraint objects permitted
154        by this constraint object.
155
156    Examples
157    --------
158    .. code-block:: python
159
160        class DivisorOfEighteen(Integer):
161            '''
162            ASN.1 specification:
163
164            Divisors-of-18 ::= INTEGER (INCLUDES Divisors-of-6 | 9 | 18)
165            '''
166            subtypeSpec = ContainedSubtypeConstraint(
167                SingleValueConstraint(1, 2, 3, 6), 9, 18
168            )
169
170        # this will succeed
171        divisor_of_eighteen = DivisorOfEighteen(9)
172
173        # this will raise ValueConstraintError
174        divisor_of_eighteen = DivisorOfEighteen(10)
175    """
176    def _testValue(self, value, idx):
177        for constraint in self._values:
178            if isinstance(constraint, AbstractConstraint):
179                constraint(value, idx)
180            elif value not in self._set:
181                raise error.ValueConstraintError(value)
182
183
184class ValueRangeConstraint(AbstractConstraint):
185    """Create a ValueRangeConstraint object.
186
187    The ValueRangeConstraint satisfies any value that
188    falls in the range of permitted values.
189
190    The ValueRangeConstraint object can only be applied
191    to :class:`~pyasn1.type.univ.Integer` and
192    :class:`~pyasn1.type.univ.Real` types.
193
194    Parameters
195    ----------
196    start: :class:`int`
197        Minimum permitted value in the range (inclusive)
198
199    end: :class:`int`
200        Maximum permitted value in the range (inclusive)
201
202    Examples
203    --------
204    .. code-block:: python
205
206        class TeenAgeYears(Integer):
207            '''
208            ASN.1 specification:
209
210            TeenAgeYears ::= INTEGER (13 .. 19)
211            '''
212            subtypeSpec = ValueRangeConstraint(13, 19)
213
214        # this will succeed
215        teen_year = TeenAgeYears(18)
216
217        # this will raise ValueConstraintError
218        teen_year = TeenAgeYears(20)
219    """
220    def _testValue(self, value, idx):
221        if value < self.start or value > self.stop:
222            raise error.ValueConstraintError(value)
223
224    def _setValues(self, values):
225        if len(values) != 2:
226            raise error.PyAsn1Error(
227                '%s: bad constraint values' % (self.__class__.__name__,)
228            )
229        self.start, self.stop = values
230        if self.start > self.stop:
231            raise error.PyAsn1Error(
232                '%s: screwed constraint values (start > stop): %s > %s' % (
233                    self.__class__.__name__,
234                    self.start, self.stop
235                )
236            )
237        AbstractConstraint._setValues(self, values)
238
239
240class ValueSizeConstraint(ValueRangeConstraint):
241    """Create a ValueSizeConstraint object.
242
243    The ValueSizeConstraint satisfies any value for
244    as long as its size falls within the range of
245    permitted sizes.
246
247    The ValueSizeConstraint object can be applied
248    to :class:`~pyasn1.type.univ.BitString`,
249    :class:`~pyasn1.type.univ.OctetString` (including
250    all :ref:`character ASN.1 types <type.char>`),
251    :class:`~pyasn1.type.univ.SequenceOf`
252    and :class:`~pyasn1.type.univ.SetOf` types.
253
254    Parameters
255    ----------
256    minimum: :class:`int`
257        Minimum permitted size of the value (inclusive)
258
259    maximum: :class:`int`
260        Maximum permitted size of the value (inclusive)
261
262    Examples
263    --------
264    .. code-block:: python
265
266        class BaseballTeamRoster(SetOf):
267            '''
268            ASN.1 specification:
269
270            BaseballTeamRoster ::= SET SIZE (1..25) OF PlayerNames
271            '''
272            componentType = PlayerNames()
273            subtypeSpec = ValueSizeConstraint(1, 25)
274
275        # this will succeed
276        team = BaseballTeamRoster()
277        team.extend(['Jan', 'Matej'])
278        encode(team)
279
280        # this will raise ValueConstraintError
281        team = BaseballTeamRoster()
282        team.extend(['Jan'] * 26)
283        encode(team)
284
285    Note
286    ----
287    Whenever ValueSizeConstraint is applied to mutable types
288    (e.g. :class:`~pyasn1.type.univ.SequenceOf`,
289    :class:`~pyasn1.type.univ.SetOf`), constraint
290    validation only happens at the serialisation phase rather
291    than schema instantiation phase (as it is with immutable
292    types).
293    """
294    def _testValue(self, value, idx):
295        valueSize = len(value)
296        if valueSize < self.start or valueSize > self.stop:
297            raise error.ValueConstraintError(value)
298
299
300class PermittedAlphabetConstraint(SingleValueConstraint):
301    """Create a PermittedAlphabetConstraint object.
302
303    The PermittedAlphabetConstraint satisfies any character
304    string for as long as all its characters are present in
305    the set of permitted characters.
306
307    The PermittedAlphabetConstraint object can only be applied
308    to the :ref:`character ASN.1 types <type.char>` such as
309    :class:`~pyasn1.type.char.IA5String`.
310
311    Parameters
312    ----------
313    \*alphabet: :class:`str`
314        Full set of characters permitted by this constraint object.
315
316    Examples
317    --------
318    .. code-block:: python
319
320        class BooleanValue(IA5String):
321            '''
322            ASN.1 specification:
323
324            BooleanValue ::= IA5String (FROM ('T' | 'F'))
325            '''
326            subtypeSpec = PermittedAlphabetConstraint('T', 'F')
327
328        # this will succeed
329        truth = BooleanValue('T')
330        truth = BooleanValue('TF')
331
332        # this will raise ValueConstraintError
333        garbage = BooleanValue('TAF')
334    """
335    def _setValues(self, values):
336        self._values = values
337        self._set = set(values)
338
339    def _testValue(self, value, idx):
340        if not self._set.issuperset(value):
341            raise error.ValueConstraintError(value)
342
343
344# This is a bit kludgy, meaning two op modes within a single constraint
345class InnerTypeConstraint(AbstractConstraint):
346    """Value must satisfy the type and presence constraints"""
347
348    def _testValue(self, value, idx):
349        if self.__singleTypeConstraint:
350            self.__singleTypeConstraint(value)
351        elif self.__multipleTypeConstraint:
352            if idx not in self.__multipleTypeConstraint:
353                raise error.ValueConstraintError(value)
354            constraint, status = self.__multipleTypeConstraint[idx]
355            if status == 'ABSENT':  # XXX presence is not checked!
356                raise error.ValueConstraintError(value)
357            constraint(value)
358
359    def _setValues(self, values):
360        self.__multipleTypeConstraint = {}
361        self.__singleTypeConstraint = None
362        for v in values:
363            if isinstance(v, tuple):
364                self.__multipleTypeConstraint[v[0]] = v[1], v[2]
365            else:
366                self.__singleTypeConstraint = v
367        AbstractConstraint._setValues(self, values)
368
369
370# Logic operations on constraints
371
372class ConstraintsExclusion(AbstractConstraint):
373    """Create a ConstraintsExclusion logic operator object.
374
375    The ConstraintsExclusion logic operator succeeds when the
376    value does *not* satisfy the operand constraint.
377
378    The ConstraintsExclusion object can be applied to
379    any constraint and logic operator object.
380
381    Parameters
382    ----------
383    constraint:
384        Constraint or logic operator object.
385
386    Examples
387    --------
388    .. code-block:: python
389
390        class Lipogramme(IA5STRING):
391            '''
392            ASN.1 specification:
393
394            Lipogramme ::=
395                IA5String (FROM (ALL EXCEPT ("e"|"E")))
396            '''
397            subtypeSpec = ConstraintsExclusion(
398                PermittedAlphabetConstraint('e', 'E')
399            )
400
401        # this will succeed
402        lipogramme = Lipogramme('A work of fiction?')
403
404        # this will raise ValueConstraintError
405        lipogramme = Lipogramme('Eel')
406
407    Warning
408    -------
409    The above example involving PermittedAlphabetConstraint might
410    not work due to the way how PermittedAlphabetConstraint works.
411    The other constraints might work with ConstraintsExclusion
412    though.
413    """
414    def _testValue(self, value, idx):
415        try:
416            self._values[0](value, idx)
417        except error.ValueConstraintError:
418            return
419        else:
420            raise error.ValueConstraintError(value)
421
422    def _setValues(self, values):
423        if len(values) != 1:
424            raise error.PyAsn1Error('Single constraint expected')
425
426        AbstractConstraint._setValues(self, values)
427
428
429class AbstractConstraintSet(AbstractConstraint):
430
431    def __getitem__(self, idx):
432        return self._values[idx]
433
434    def __iter__(self):
435        return iter(self._values)
436
437    def __add__(self, value):
438        return self.__class__(*(self._values + (value,)))
439
440    def __radd__(self, value):
441        return self.__class__(*((value,) + self._values))
442
443    def __len__(self):
444        return len(self._values)
445
446    # Constraints inclusion in sets
447
448    def _setValues(self, values):
449        self._values = values
450        for constraint in values:
451            if constraint:
452                self._valueMap.add(constraint)
453                self._valueMap.update(constraint.getValueMap())
454
455
456class ConstraintsIntersection(AbstractConstraintSet):
457    """Create a ConstraintsIntersection logic operator object.
458
459    The ConstraintsIntersection logic operator only succeeds
460    if *all* its operands succeed.
461
462    The ConstraintsIntersection object can be applied to
463    any constraint and logic operator objects.
464
465    The ConstraintsIntersection object duck-types the immutable
466    container object like Python :py:class:`tuple`.
467
468    Parameters
469    ----------
470    \*constraints:
471        Constraint or logic operator objects.
472
473    Examples
474    --------
475    .. code-block:: python
476
477        class CapitalAndSmall(IA5String):
478            '''
479            ASN.1 specification:
480
481            CapitalAndSmall ::=
482                IA5String (FROM ("A".."Z"|"a".."z"))
483            '''
484            subtypeSpec = ConstraintsIntersection(
485                PermittedAlphabetConstraint('A', 'Z'),
486                PermittedAlphabetConstraint('a', 'z')
487            )
488
489        # this will succeed
490        capital_and_small = CapitalAndSmall('Hello')
491
492        # this will raise ValueConstraintError
493        capital_and_small = CapitalAndSmall('hello')
494    """
495    def _testValue(self, value, idx):
496        for constraint in self._values:
497            constraint(value, idx)
498
499
500class ConstraintsUnion(AbstractConstraintSet):
501    """Create a ConstraintsUnion logic operator object.
502
503    The ConstraintsUnion logic operator only succeeds if
504    *at least a single* operand succeeds.
505
506    The ConstraintsUnion object can be applied to
507    any constraint and logic operator objects.
508
509    The ConstraintsUnion object duck-types the immutable
510    container object like Python :py:class:`tuple`.
511
512    Parameters
513    ----------
514    \*constraints:
515        Constraint or logic operator objects.
516
517    Examples
518    --------
519    .. code-block:: python
520
521        class CapitalOrSmall(IA5String):
522            '''
523            ASN.1 specification:
524
525            CapitalOrSmall ::=
526                IA5String (FROM ("A".."Z") | FROM ("a".."z"))
527            '''
528            subtypeSpec = ConstraintsIntersection(
529                PermittedAlphabetConstraint('A', 'Z'),
530                PermittedAlphabetConstraint('a', 'z')
531            )
532
533        # this will succeed
534        capital_or_small = CapitalAndSmall('Hello')
535
536        # this will raise ValueConstraintError
537        capital_or_small = CapitalOrSmall('hello!')
538    """
539    def _testValue(self, value, idx):
540        for constraint in self._values:
541            try:
542                constraint(value, idx)
543            except error.ValueConstraintError:
544                pass
545            else:
546                return
547
548        raise error.ValueConstraintError(
549            'all of %s failed for "%s"' % (self._values, value)
550        )
551
552# TODO:
553# refactor InnerTypeConstraint
554# add tests for type check
555# implement other constraint types
556# make constraint validation easy to skip
557