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' % (self.__class__.__name__)
41
42        if self._values:
43            representation += ', consts %s' % ', '.join(
44                [repr(x) for x in self._values])
45
46        return '<%s>' % representation
47
48    def __eq__(self, other):
49        return self is other and True or self._values == other
50
51    def __ne__(self, other):
52        return self._values != other
53
54    def __lt__(self, other):
55        return self._values < other
56
57    def __le__(self, other):
58        return self._values <= other
59
60    def __gt__(self, other):
61        return self._values > other
62
63    def __ge__(self, other):
64        return self._values >= other
65
66    if sys.version_info[0] <= 2:
67        def __nonzero__(self):
68            return self._values and True or False
69    else:
70        def __bool__(self):
71            return self._values and True or False
72
73    def __hash__(self):
74        return self.__hash
75
76    def _setValues(self, values):
77        self._values = values
78
79    def _testValue(self, value, idx):
80        raise error.ValueConstraintError(value)
81
82    # Constraints derivation logic
83    def getValueMap(self):
84        return self._valueMap
85
86    def isSuperTypeOf(self, otherConstraint):
87        # TODO: fix possible comparison of set vs scalars here
88        return (otherConstraint is self or
89                not self._values or
90                otherConstraint == self or
91                self in otherConstraint.getValueMap())
92
93    def isSubTypeOf(self, otherConstraint):
94        return (otherConstraint is self or
95                not self or
96                otherConstraint == self or
97                otherConstraint in self._valueMap)
98
99
100class SingleValueConstraint(AbstractConstraint):
101    """Create a SingleValueConstraint object.
102
103    The SingleValueConstraint satisfies any value that
104    is present in the set of permitted values.
105
106    The SingleValueConstraint object can be applied to
107    any ASN.1 type.
108
109    Parameters
110    ----------
111    *values: :class:`int`
112        Full set of values permitted by this constraint object.
113
114    Examples
115    --------
116    .. code-block:: python
117
118        class DivisorOfSix(Integer):
119            '''
120            ASN.1 specification:
121
122            Divisor-Of-6 ::= INTEGER (1 | 2 | 3 | 6)
123            '''
124            subtypeSpec = SingleValueConstraint(1, 2, 3, 6)
125
126        # this will succeed
127        divisor_of_six = DivisorOfSix(1)
128
129        # this will raise ValueConstraintError
130        divisor_of_six = DivisorOfSix(7)
131    """
132    def _setValues(self, values):
133        self._values = values
134        self._set = set(values)
135
136    def _testValue(self, value, idx):
137        if value not in self._set:
138            raise error.ValueConstraintError(value)
139
140
141class ContainedSubtypeConstraint(AbstractConstraint):
142    """Create a ContainedSubtypeConstraint object.
143
144    The ContainedSubtypeConstraint satisfies any value that
145    is present in the set of permitted values and also
146    satisfies included constraints.
147
148    The ContainedSubtypeConstraint object can be applied to
149    any ASN.1 type.
150
151    Parameters
152    ----------
153    *values:
154        Full set of values and constraint objects permitted
155        by this constraint object.
156
157    Examples
158    --------
159    .. code-block:: python
160
161        class DivisorOfEighteen(Integer):
162            '''
163            ASN.1 specification:
164
165            Divisors-of-18 ::= INTEGER (INCLUDES Divisors-of-6 | 9 | 18)
166            '''
167            subtypeSpec = ContainedSubtypeConstraint(
168                SingleValueConstraint(1, 2, 3, 6), 9, 18
169            )
170
171        # this will succeed
172        divisor_of_eighteen = DivisorOfEighteen(9)
173
174        # this will raise ValueConstraintError
175        divisor_of_eighteen = DivisorOfEighteen(10)
176    """
177    def _testValue(self, value, idx):
178        for constraint in self._values:
179            if isinstance(constraint, AbstractConstraint):
180                constraint(value, idx)
181            elif value not in self._set:
182                raise error.ValueConstraintError(value)
183
184
185class ValueRangeConstraint(AbstractConstraint):
186    """Create a ValueRangeConstraint object.
187
188    The ValueRangeConstraint satisfies any value that
189    falls in the range of permitted values.
190
191    The ValueRangeConstraint object can only be applied
192    to :class:`~pyasn1.type.univ.Integer` and
193    :class:`~pyasn1.type.univ.Real` types.
194
195    Parameters
196    ----------
197    start: :class:`int`
198        Minimum permitted value in the range (inclusive)
199
200    end: :class:`int`
201        Maximum permitted value in the range (inclusive)
202
203    Examples
204    --------
205    .. code-block:: python
206
207        class TeenAgeYears(Integer):
208            '''
209            ASN.1 specification:
210
211            TeenAgeYears ::= INTEGER (13 .. 19)
212            '''
213            subtypeSpec = ValueRangeConstraint(13, 19)
214
215        # this will succeed
216        teen_year = TeenAgeYears(18)
217
218        # this will raise ValueConstraintError
219        teen_year = TeenAgeYears(20)
220    """
221    def _testValue(self, value, idx):
222        if value < self.start or value > self.stop:
223            raise error.ValueConstraintError(value)
224
225    def _setValues(self, values):
226        if len(values) != 2:
227            raise error.PyAsn1Error(
228                '%s: bad constraint values' % (self.__class__.__name__,)
229            )
230        self.start, self.stop = values
231        if self.start > self.stop:
232            raise error.PyAsn1Error(
233                '%s: screwed constraint values (start > stop): %s > %s' % (
234                    self.__class__.__name__,
235                    self.start, self.stop
236                )
237            )
238        AbstractConstraint._setValues(self, values)
239
240
241class ValueSizeConstraint(ValueRangeConstraint):
242    """Create a ValueSizeConstraint object.
243
244    The ValueSizeConstraint satisfies any value for
245    as long as its size falls within the range of
246    permitted sizes.
247
248    The ValueSizeConstraint object can be applied
249    to :class:`~pyasn1.type.univ.BitString`,
250    :class:`~pyasn1.type.univ.OctetString` (including
251    all :ref:`character ASN.1 types <type.char>`),
252    :class:`~pyasn1.type.univ.SequenceOf`
253    and :class:`~pyasn1.type.univ.SetOf` types.
254
255    Parameters
256    ----------
257    minimum: :class:`int`
258        Minimum permitted size of the value (inclusive)
259
260    maximum: :class:`int`
261        Maximum permitted size of the value (inclusive)
262
263    Examples
264    --------
265    .. code-block:: python
266
267        class BaseballTeamRoster(SetOf):
268            '''
269            ASN.1 specification:
270
271            BaseballTeamRoster ::= SET SIZE (1..25) OF PlayerNames
272            '''
273            componentType = PlayerNames()
274            subtypeSpec = ValueSizeConstraint(1, 25)
275
276        # this will succeed
277        team = BaseballTeamRoster()
278        team.extend(['Jan', 'Matej'])
279        encode(team)
280
281        # this will raise ValueConstraintError
282        team = BaseballTeamRoster()
283        team.extend(['Jan'] * 26)
284        encode(team)
285
286    Note
287    ----
288    Whenever ValueSizeConstraint is applied to mutable types
289    (e.g. :class:`~pyasn1.type.univ.SequenceOf`,
290    :class:`~pyasn1.type.univ.SetOf`), constraint
291    validation only happens at the serialisation phase rather
292    than schema instantiation phase (as it is with immutable
293    types).
294    """
295    def _testValue(self, value, idx):
296        valueSize = len(value)
297        if valueSize < self.start or valueSize > self.stop:
298            raise error.ValueConstraintError(value)
299
300
301class PermittedAlphabetConstraint(SingleValueConstraint):
302    """Create a PermittedAlphabetConstraint object.
303
304    The PermittedAlphabetConstraint satisfies any character
305    string for as long as all its characters are present in
306    the set of permitted characters.
307
308    The PermittedAlphabetConstraint object can only be applied
309    to the :ref:`character ASN.1 types <type.char>` such as
310    :class:`~pyasn1.type.char.IA5String`.
311
312    Parameters
313    ----------
314    *alphabet: :class:`str`
315        Full set of characters permitted by this constraint object.
316
317    Examples
318    --------
319    .. code-block:: python
320
321        class BooleanValue(IA5String):
322            '''
323            ASN.1 specification:
324
325            BooleanValue ::= IA5String (FROM ('T' | 'F'))
326            '''
327            subtypeSpec = PermittedAlphabetConstraint('T', 'F')
328
329        # this will succeed
330        truth = BooleanValue('T')
331        truth = BooleanValue('TF')
332
333        # this will raise ValueConstraintError
334        garbage = BooleanValue('TAF')
335    """
336    def _setValues(self, values):
337        self._values = values
338        self._set = set(values)
339
340    def _testValue(self, value, idx):
341        if not self._set.issuperset(value):
342            raise error.ValueConstraintError(value)
343
344
345class ComponentPresentConstraint(AbstractConstraint):
346    """Create a ComponentPresentConstraint object.
347
348    The ComponentPresentConstraint is only satisfied when the value
349    is not `None`.
350
351    The ComponentPresentConstraint object is typically used with
352    `WithComponentsConstraint`.
353
354    Examples
355    --------
356    .. code-block:: python
357
358        present = ComponentPresentConstraint()
359
360        # this will succeed
361        present('whatever')
362
363        # this will raise ValueConstraintError
364        present(None)
365    """
366    def _setValues(self, values):
367        self._values = ('<must be present>',)
368
369        if values:
370            raise error.PyAsn1Error('No arguments expected')
371
372    def _testValue(self, value, idx):
373        if value is None:
374            raise error.ValueConstraintError(
375                'Component is not present:')
376
377
378class ComponentAbsentConstraint(AbstractConstraint):
379    """Create a ComponentAbsentConstraint object.
380
381    The ComponentAbsentConstraint is only satisfied when the value
382    is `None`.
383
384    The ComponentAbsentConstraint object is typically used with
385    `WithComponentsConstraint`.
386
387    Examples
388    --------
389    .. code-block:: python
390
391        absent = ComponentAbsentConstraint()
392
393        # this will succeed
394        absent(None)
395
396        # this will raise ValueConstraintError
397        absent('whatever')
398    """
399    def _setValues(self, values):
400        self._values = ('<must be absent>',)
401
402        if values:
403            raise error.PyAsn1Error('No arguments expected')
404
405    def _testValue(self, value, idx):
406        if value is not None:
407            raise error.ValueConstraintError(
408                'Component is not absent: %r' % value)
409
410
411class WithComponentsConstraint(AbstractConstraint):
412    """Create a WithComponentsConstraint object.
413
414    The `WithComponentsConstraint` satisfies any mapping object that has
415    constrained fields present or absent, what is indicated by
416    `ComponentPresentConstraint` and `ComponentAbsentConstraint`
417    objects respectively.
418
419    The `WithComponentsConstraint` object is typically applied
420    to  :class:`~pyasn1.type.univ.Set` or
421    :class:`~pyasn1.type.univ.Sequence` types.
422
423    Parameters
424    ----------
425    *fields: :class:`tuple`
426        Zero or more tuples of (`field`, `constraint`) indicating constrained
427        fields.
428
429    Notes
430    -----
431    On top of the primary use of `WithComponentsConstraint` (ensuring presence
432    or absence of particular components of a :class:`~pyasn1.type.univ.Set` or
433    :class:`~pyasn1.type.univ.Sequence`), it is also possible to pass any other
434    constraint objects or their combinations. In case of scalar fields, these
435    constraints will be verified in addition to the constraints belonging to
436    scalar components themselves. However, formally, these additional
437    constraints do not change the type of these ASN.1 objects.
438
439    Examples
440    --------
441
442    .. code-block:: python
443
444        class Item(Sequence):  #  Set is similar
445            '''
446            ASN.1 specification:
447
448            Item ::= SEQUENCE {
449                id    INTEGER OPTIONAL,
450                name  OCTET STRING OPTIONAL
451            } WITH COMPONENTS id PRESENT, name ABSENT | id ABSENT, name PRESENT
452            '''
453            componentType = NamedTypes(
454                OptionalNamedType('id', Integer()),
455                OptionalNamedType('name', OctetString())
456            )
457            withComponents = ConstraintsUnion(
458                WithComponentsConstraint(
459                    ('id', ComponentPresentConstraint()),
460                    ('name', ComponentAbsentConstraint())
461                ),
462                WithComponentsConstraint(
463                    ('id', ComponentAbsentConstraint()),
464                    ('name', ComponentPresentConstraint())
465                )
466            )
467
468        item = Item()
469
470        # This will succeed
471        item['id'] = 1
472
473        # This will succeed
474        item.reset()
475        item['name'] = 'John'
476
477        # This will fail (on encoding)
478        item.reset()
479        descr['id'] = 1
480        descr['name'] = 'John'
481    """
482    def _testValue(self, value, idx):
483        for field, constraint in self._values:
484            constraint(value.get(field))
485
486    def _setValues(self, values):
487        AbstractConstraint._setValues(self, values)
488
489
490# This is a bit kludgy, meaning two op modes within a single constraint
491class InnerTypeConstraint(AbstractConstraint):
492    """Value must satisfy the type and presence constraints"""
493
494    def _testValue(self, value, idx):
495        if self.__singleTypeConstraint:
496            self.__singleTypeConstraint(value)
497        elif self.__multipleTypeConstraint:
498            if idx not in self.__multipleTypeConstraint:
499                raise error.ValueConstraintError(value)
500            constraint, status = self.__multipleTypeConstraint[idx]
501            if status == 'ABSENT':  # XXX presence is not checked!
502                raise error.ValueConstraintError(value)
503            constraint(value)
504
505    def _setValues(self, values):
506        self.__multipleTypeConstraint = {}
507        self.__singleTypeConstraint = None
508        for v in values:
509            if isinstance(v, tuple):
510                self.__multipleTypeConstraint[v[0]] = v[1], v[2]
511            else:
512                self.__singleTypeConstraint = v
513        AbstractConstraint._setValues(self, values)
514
515
516# Logic operations on constraints
517
518class ConstraintsExclusion(AbstractConstraint):
519    """Create a ConstraintsExclusion logic operator object.
520
521    The ConstraintsExclusion logic operator succeeds when the
522    value does *not* satisfy the operand constraint.
523
524    The ConstraintsExclusion object can be applied to
525    any constraint and logic operator object.
526
527    Parameters
528    ----------
529    constraint:
530        Constraint or logic operator object.
531
532    Examples
533    --------
534    .. code-block:: python
535
536        class Lipogramme(IA5STRING):
537            '''
538            ASN.1 specification:
539
540            Lipogramme ::=
541                IA5String (FROM (ALL EXCEPT ("e"|"E")))
542            '''
543            subtypeSpec = ConstraintsExclusion(
544                PermittedAlphabetConstraint('e', 'E')
545            )
546
547        # this will succeed
548        lipogramme = Lipogramme('A work of fiction?')
549
550        # this will raise ValueConstraintError
551        lipogramme = Lipogramme('Eel')
552
553    Warning
554    -------
555    The above example involving PermittedAlphabetConstraint might
556    not work due to the way how PermittedAlphabetConstraint works.
557    The other constraints might work with ConstraintsExclusion
558    though.
559    """
560    def _testValue(self, value, idx):
561        try:
562            self._values[0](value, idx)
563        except error.ValueConstraintError:
564            return
565        else:
566            raise error.ValueConstraintError(value)
567
568    def _setValues(self, values):
569        if len(values) != 1:
570            raise error.PyAsn1Error('Single constraint expected')
571
572        AbstractConstraint._setValues(self, values)
573
574
575class AbstractConstraintSet(AbstractConstraint):
576
577    def __getitem__(self, idx):
578        return self._values[idx]
579
580    def __iter__(self):
581        return iter(self._values)
582
583    def __add__(self, value):
584        return self.__class__(*(self._values + (value,)))
585
586    def __radd__(self, value):
587        return self.__class__(*((value,) + self._values))
588
589    def __len__(self):
590        return len(self._values)
591
592    # Constraints inclusion in sets
593
594    def _setValues(self, values):
595        self._values = values
596        for constraint in values:
597            if constraint:
598                self._valueMap.add(constraint)
599                self._valueMap.update(constraint.getValueMap())
600
601
602class ConstraintsIntersection(AbstractConstraintSet):
603    """Create a ConstraintsIntersection logic operator object.
604
605    The ConstraintsIntersection logic operator only succeeds
606    if *all* its operands succeed.
607
608    The ConstraintsIntersection object can be applied to
609    any constraint and logic operator objects.
610
611    The ConstraintsIntersection object duck-types the immutable
612    container object like Python :py:class:`tuple`.
613
614    Parameters
615    ----------
616    *constraints:
617        Constraint or logic operator objects.
618
619    Examples
620    --------
621    .. code-block:: python
622
623        class CapitalAndSmall(IA5String):
624            '''
625            ASN.1 specification:
626
627            CapitalAndSmall ::=
628                IA5String (FROM ("A".."Z"|"a".."z"))
629            '''
630            subtypeSpec = ConstraintsIntersection(
631                PermittedAlphabetConstraint('A', 'Z'),
632                PermittedAlphabetConstraint('a', 'z')
633            )
634
635        # this will succeed
636        capital_and_small = CapitalAndSmall('Hello')
637
638        # this will raise ValueConstraintError
639        capital_and_small = CapitalAndSmall('hello')
640    """
641    def _testValue(self, value, idx):
642        for constraint in self._values:
643            constraint(value, idx)
644
645
646class ConstraintsUnion(AbstractConstraintSet):
647    """Create a ConstraintsUnion logic operator object.
648
649    The ConstraintsUnion logic operator succeeds if
650    *at least* a single operand succeeds.
651
652    The ConstraintsUnion object can be applied to
653    any constraint and logic operator objects.
654
655    The ConstraintsUnion object duck-types the immutable
656    container object like Python :py:class:`tuple`.
657
658    Parameters
659    ----------
660    *constraints:
661        Constraint or logic operator objects.
662
663    Examples
664    --------
665    .. code-block:: python
666
667        class CapitalOrSmall(IA5String):
668            '''
669            ASN.1 specification:
670
671            CapitalOrSmall ::=
672                IA5String (FROM ("A".."Z") | FROM ("a".."z"))
673            '''
674            subtypeSpec = ConstraintsUnion(
675                PermittedAlphabetConstraint('A', 'Z'),
676                PermittedAlphabetConstraint('a', 'z')
677            )
678
679        # this will succeed
680        capital_or_small = CapitalAndSmall('Hello')
681
682        # this will raise ValueConstraintError
683        capital_or_small = CapitalOrSmall('hello!')
684    """
685    def _testValue(self, value, idx):
686        for constraint in self._values:
687            try:
688                constraint(value, idx)
689            except error.ValueConstraintError:
690                pass
691            else:
692                return
693
694        raise error.ValueConstraintError(
695            'all of %s failed for "%s"' % (self._values, value)
696        )
697
698# TODO:
699# refactor InnerTypeConstraint
700# add tests for type check
701# implement other constraint types
702# make constraint validation easy to skip
703