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