1#
2# This file is part of pyasn1 software.
3#
4# Copyright (c) 2005-2020, Ilya Etingof <etingof@gmail.com>
5# License: https://pyasn1.readthedocs.io/en/latest/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