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