diff options
author | Ilya Etingof <etingof@gmail.com> | 2017-10-22 15:56:51 +0200 |
---|---|---|
committer | GitHub <noreply@github.com> | 2017-10-22 15:56:51 +0200 |
commit | 9653bbb8b2eb9f1e6a6920d8239bd55b72f9550e (patch) | |
tree | de4527642fea0d264f367a577b0a526efe1d4416 | |
parent | 262793a79cad797601fa855eec1e1020e1a534cb (diff) | |
download | pyasn1-git-9653bbb8b2eb9f1e6a6920d8239bd55b72f9550e.tar.gz |
docs on constraints added (#97)
-rw-r--r-- | docs/source/pyasn1/contents.rst | 23 | ||||
-rw-r--r-- | docs/source/pyasn1/type/char/contents.rst | 4 | ||||
-rw-r--r-- | docs/source/pyasn1/type/constraint/constraintsexclusion.rst | 10 | ||||
-rw-r--r-- | docs/source/pyasn1/type/constraint/constraintsintersection.rst | 10 | ||||
-rw-r--r-- | docs/source/pyasn1/type/constraint/constraintsunion.rst | 10 | ||||
-rw-r--r-- | docs/source/pyasn1/type/constraint/containedsubtype.rst | 10 | ||||
-rw-r--r-- | docs/source/pyasn1/type/constraint/contents.rst | 67 | ||||
-rw-r--r-- | docs/source/pyasn1/type/constraint/permittedalphabet.rst | 10 | ||||
-rw-r--r-- | docs/source/pyasn1/type/constraint/singlevalue.rst | 10 | ||||
-rw-r--r-- | docs/source/pyasn1/type/constraint/valuerange.rst | 10 | ||||
-rw-r--r-- | docs/source/pyasn1/type/constraint/valuesize.rst | 10 | ||||
-rw-r--r-- | pyasn1/type/constraint.py | 335 |
12 files changed, 482 insertions, 27 deletions
diff --git a/docs/source/pyasn1/contents.rst b/docs/source/pyasn1/contents.rst index 0795830..1dc9e58 100644 --- a/docs/source/pyasn1/contents.rst +++ b/docs/source/pyasn1/contents.rst @@ -6,12 +6,13 @@ ASN.1 types The ASN.1 data description `language <https://www.itu.int/rec/dologin_pub.asp?lang=e&id=T-REC-X.208-198811-W!!PDF-E&type=items>`_ -defines a handful of built-in data types. ASN.1 types exhibit different semantics -(e.g. number vs string) and can be distinguished from each other by +defines a handful of built-in data types. ASN.1 types exhibit different +semantics (e.g. number vs string) and can be distinguished from each other by :ref:`tags <type.tag>`. Subtypes can be created on top of base ASN.1 types by adding/overriding the -tags and/or imposing additional *constraints* on accepted values. +:ref:`tags <type.tag>` and/or imposing additional +:ref:`constraints <type.constraint>` on accepted values. ASN.1 types in pyasn1 are Python objects. One or more ASN.1 types comprise a *schema* describing data structures of unbounded complexity. @@ -95,7 +96,8 @@ New ASN.1 types can be created on top of existing ASN.1 types with the *subtype()* method. Desired properties of the new type get merged with the corresponding properties of the old type. Main use-case for *.subtype()* is to assemble new ASN.1 types by :ref:`tagging <type.tag>` -or applying additional constraints to accepted type's values. +or applying additional :ref:`constraints <type.constraint>` to accepted +type's values. .. code-block:: python @@ -116,7 +118,20 @@ or applying additional constraints to accepted type's values. /pyasn1/type/univ/contents /pyasn1/type/char/contents /pyasn1/type/useful/contents + +ASN.1 type harness +++++++++++++++++++ + +The identification and behaviour of ASN.1 types is determined by +:ref:`tags <type.tag>` and :ref:`constraints <type.constraint>`. +The inner structure of *constructed* ASN.1 types is defined by +its :ref:`fields <type.namedtype>` specification. + +.. toctree:: + :maxdepth: 2 + /pyasn1/type/tag/contents + /pyasn1/type/constraint/contents /pyasn1/type/namedtype/contents /pyasn1/type/opentype/contents /pyasn1/type/namedval/contents diff --git a/docs/source/pyasn1/type/char/contents.rst b/docs/source/pyasn1/type/char/contents.rst index 87e3f22..e73c959 100644 --- a/docs/source/pyasn1/type/char/contents.rst +++ b/docs/source/pyasn1/type/char/contents.rst @@ -9,8 +9,8 @@ of text types. Most of these types come from the past trying to capture the fragments of long-forgotten technologies. These *character* types are all scalars. They are similar to -:ref:`univ.OctetString` except that they all operate on text, -not bytes. +:ref:`OctetString <univ.OctetString>` except that they all operate on +text, not bytes. .. toctree:: :maxdepth: 2 diff --git a/docs/source/pyasn1/type/constraint/constraintsexclusion.rst b/docs/source/pyasn1/type/constraint/constraintsexclusion.rst new file mode 100644 index 0000000..ad5cf8a --- /dev/null +++ b/docs/source/pyasn1/type/constraint/constraintsexclusion.rst @@ -0,0 +1,10 @@ + +.. _constrain.ConstraintsExclusion: + +.. |Constraint| replace:: ConstraintsExclusion + +Constraints exclusion +--------------------- + +.. autoclass:: pyasn1.type.constraint.ConstraintsExclusion(constraint) + :members: diff --git a/docs/source/pyasn1/type/constraint/constraintsintersection.rst b/docs/source/pyasn1/type/constraint/constraintsintersection.rst new file mode 100644 index 0000000..15465b8 --- /dev/null +++ b/docs/source/pyasn1/type/constraint/constraintsintersection.rst @@ -0,0 +1,10 @@ + +.. _constrain.ConstraintsIntersection: + +.. |Constraint| replace:: ConstraintsIntersection + +Constraints intersection +------------------------ + +.. autoclass:: pyasn1.type.constraint.ConstraintsIntersection(*constraints) + :members: diff --git a/docs/source/pyasn1/type/constraint/constraintsunion.rst b/docs/source/pyasn1/type/constraint/constraintsunion.rst new file mode 100644 index 0000000..d42f3d9 --- /dev/null +++ b/docs/source/pyasn1/type/constraint/constraintsunion.rst @@ -0,0 +1,10 @@ + +.. _constrain.ConstraintsUnion: + +.. |Constraint| replace:: ConstraintsUnion + +Constraints union +----------------- + +.. autoclass:: pyasn1.type.constraint.ConstraintsUnion(*constraints) + :members: diff --git a/docs/source/pyasn1/type/constraint/containedsubtype.rst b/docs/source/pyasn1/type/constraint/containedsubtype.rst new file mode 100644 index 0000000..39fc3a1 --- /dev/null +++ b/docs/source/pyasn1/type/constraint/containedsubtype.rst @@ -0,0 +1,10 @@ + +.. _constrain.ContainedSubtypeConstraint: + +.. |Constraint| replace:: ContainedSubtypeConstraint + +Contained subtype constraint +---------------------------- + +.. autoclass:: pyasn1.type.constraint.ContainedSubtypeConstraint + :members: diff --git a/docs/source/pyasn1/type/constraint/contents.rst b/docs/source/pyasn1/type/constraint/contents.rst new file mode 100644 index 0000000..8e4db7c --- /dev/null +++ b/docs/source/pyasn1/type/constraint/contents.rst @@ -0,0 +1,67 @@ + +.. _type.constraint: + +Constraints +----------- + +ASN.1 standard has a built-in way of limiting the set of values +a type can possibly have. Imposing value constraints on an ASN.1 +type, together with :ref:`tagging <type.tag>`, is a way of creating +a more specialized subtype of an ASN.1 type. + +The pyasn1 implementation represents all flavors of constraints, +as well as their combinations, as immutable Python objects. Ultimately, +they get attached to ASN.1 type object at a *.subtypeSpec* attribute. + +.. code-block:: python + + class Age(Integer): + """ + ASN.1 specification: + + Age ::= INTEGER (0..120) + """ + subtypeSpec = ValueRangeConstraint(0, 120) + + +.. toctree:: + :maxdepth: 2 + + /pyasn1/type/constraint/singlevalue + /pyasn1/type/constraint/containedsubtype + /pyasn1/type/constraint/valuerange + /pyasn1/type/constraint/valuesize + /pyasn1/type/constraint/permittedalphabet + + +Logic operations on constraints ++++++++++++++++++++++++++++++++ + +Sometimes multiple constraints are applied on an ASN.1 type. To capture +this situation, individual constraint objects can be glued together +by the logic operator objects. + +The logic operators are Python objects that exhibit similar behaviour +as the constraint objects do with the only difference that logic operators +are instantiated on the constraint and logic operator objects, not on the +bare values. + +.. code-block:: python + + class PhoneNumber(NumericString): + """ + ASN.1 specification: + + PhoneNumber ::= + NumericString (FROM ("0".."9")) (SIZE (10)) + """ + subtypeSpec = ConstraintsIntersection( + ValueRangeConstraint('0', '9'), ValueSizeConstraint(10) + ) + +.. toctree:: + :maxdepth: 2 + + /pyasn1/type/constraint/constraintsintersection + /pyasn1/type/constraint/constraintsunion + /pyasn1/type/constraint/constraintsexclusion diff --git a/docs/source/pyasn1/type/constraint/permittedalphabet.rst b/docs/source/pyasn1/type/constraint/permittedalphabet.rst new file mode 100644 index 0000000..86d08c4 --- /dev/null +++ b/docs/source/pyasn1/type/constraint/permittedalphabet.rst @@ -0,0 +1,10 @@ + +.. _constrain.PermittedAlphabetConstraint: + +.. |Constraint| replace:: PermittedAlphabetConstraint + +Permitted alphabet constraint +----------------------------- + +.. autoclass:: pyasn1.type.constraint.PermittedAlphabetConstraint(*alphabet) + :members: diff --git a/docs/source/pyasn1/type/constraint/singlevalue.rst b/docs/source/pyasn1/type/constraint/singlevalue.rst new file mode 100644 index 0000000..26f2424 --- /dev/null +++ b/docs/source/pyasn1/type/constraint/singlevalue.rst @@ -0,0 +1,10 @@ + +.. _constrain.SingleValueConstraint: + +.. |Constraint| replace:: SingleValueConstraint + +Single value constraint +----------------------- + +.. autoclass:: pyasn1.type.constraint.SingleValueConstraint + :members: diff --git a/docs/source/pyasn1/type/constraint/valuerange.rst b/docs/source/pyasn1/type/constraint/valuerange.rst new file mode 100644 index 0000000..a3a91ac --- /dev/null +++ b/docs/source/pyasn1/type/constraint/valuerange.rst @@ -0,0 +1,10 @@ + +.. _constrain.ValueRangeConstraint: + +.. |Constraint| replace:: ValueRangeConstraint + +Value range constraint +---------------------- + +.. autoclass:: pyasn1.type.constraint.ValueRangeConstraint(start, end) + :members: diff --git a/docs/source/pyasn1/type/constraint/valuesize.rst b/docs/source/pyasn1/type/constraint/valuesize.rst new file mode 100644 index 0000000..62444df --- /dev/null +++ b/docs/source/pyasn1/type/constraint/valuesize.rst @@ -0,0 +1,10 @@ + +.. _constrain.ValueSizeConstraint: + +.. |Constraint| replace:: ValueSizeConstraint + +Value size constraint +---------------------- + +.. autoclass:: pyasn1.type.constraint.ValueSizeConstraint(minimum, maximum) + :members: diff --git a/pyasn1/type/constraint.py b/pyasn1/type/constraint.py index 35bb0e2..c4f4238 100644 --- a/pyasn1/type/constraint.py +++ b/pyasn1/type/constraint.py @@ -15,12 +15,6 @@ __all__ = ['SingleValueConstraint', 'ContainedSubtypeConstraint', 'ValueRangeCon class AbstractConstraint(object): - """Abstract base-class for constraint objects - - Constraints should be stored in a simple sequence in the - namespace of their client Asn1Item sub-classes in cases - when ASN.1 constraint is define. - """ def __init__(self, *values): self._valueMap = set() @@ -96,9 +90,39 @@ class AbstractConstraint(object): otherConstraint == self or otherConstraint in self._valueMap) + class SingleValueConstraint(AbstractConstraint): - """Value must be part of defined values constraint""" + """Create a SingleValueConstraint object. + + The SingleValueConstraint satisfies any value that + is present in the set of permitted values. + + The SingleValueConstraint object can be applied to + any ASN.1 type. + + Parameters + ---------- + \*values: :class:`int` + Full set of values permitted by this constraint object. + + Example + ------- + .. code-block:: python + + class DivisorOfSix(Integer): + ''' + ASN.1 specification: + + Divisor-Of-6 ::= INTEGER (1 | 2 | 3 | 6) + ''' + subtypeSpec = SingleValueConstraint(1, 2, 3, 6) + + # this will succeed + divisor_of_six = DivisorOfSix(1) + # this will raise ValueConstraintError + divisor_of_six = DivisorOfSix(7) + """ def _setValues(self, values): self._values = values self._set = set(values) @@ -109,16 +133,85 @@ class SingleValueConstraint(AbstractConstraint): class ContainedSubtypeConstraint(AbstractConstraint): - """Value must satisfy all of defined set of constraints""" + """Create a ContainedSubtypeConstraint object. + + The ContainedSubtypeConstraint satisfies any value that + is present in the set of permitted values and also + satisfies included constraints. + + The ContainedSubtypeConstraint object can be applied to + any ASN.1 type. + + Parameters + ---------- + \*values: + Full set of values and constraint objects permitted + by this constraint object. + + Example + ------- + .. code-block:: python + + class DivisorOfEighteen(Integer): + ''' + ASN.1 specification: + + Divisors-of-18 ::= INTEGER (INCLUDES Divisors-of-6 | 9 | 18) + ''' + subtypeSpec = ContainedSubtypeConstraint( + SingleValueConstraint(1, 2, 3, 6), 9, 18 + ) + + # this will succeed + divisor_of_eighteen = DivisorOfEighteen(9) + # this will raise ValueConstraintError + divisor_of_eighteen = DivisorOfEighteen(10) + """ def _testValue(self, value, idx): - for c in self._values: - c(value, idx) + for constraint in self._values: + if isinstance(constraint, AbstractConstraint): + constraint(value, idx) + elif value not in self._set: + raise error.ValueConstraintError(value) class ValueRangeConstraint(AbstractConstraint): - """Value must be within start and stop values (inclusive)""" + """Create a ValueRangeConstraint object. + + The ValueRangeConstraint satisfies any value that + falls in the range of permitted values. + + The ValueRangeConstraint object can only be applied + to :class:`~pyasn1.type.univ.Integer` and + :class:`~pyasn1.type.univ.Real` types. + + Parameters + ---------- + start: :class:`int` + Minimum permitted value in the range (inclusive) + + end: :class:`int` + Maximum permitted value in the range (inclusive) + + Example + ------- + .. code-block:: python + + class TeenAgeYears(Integer): + ''' + ASN.1 specification: + TeenAgeYears ::= INTEGER (13 .. 19) + ''' + subtypeSpec = ValueRangeConstraint(13, 19) + + # this will succeed + teen_year = TeenAgeYears(18) + + # this will raise ValueConstraintError + teen_year = TeenAgeYears(20) + """ def _testValue(self, value, idx): if value < self.start or value > self.stop: raise error.ValueConstraintError(value) @@ -140,8 +233,59 @@ class ValueRangeConstraint(AbstractConstraint): class ValueSizeConstraint(ValueRangeConstraint): - """len(value) must be within start and stop values (inclusive)""" - + """Create a ValueSizeConstraint object. + + The ValueSizeConstraint satisfies any value for + as long as its size falls within the range of + permitted sizes. + + The ValueSizeConstraint object can be applied + to :class:`~pyasn1.type.univ.BitString`, + :class:`~pyasn1.type.univ.OctetString` (including + all :ref:`character ASN.1 types <type.char>`), + :class:`~pyasn1.type.univ.SequenceOf` + and :class:`~pyasn1.type.univ.SetOf` types. + + Parameters + ---------- + minimum: :class:`int` + Minimum permitted size of the value (inclusive) + + maximum: :class:`int` + Maximum permitted size of the value (inclusive) + + Example + ------- + .. code-block:: python + + class BaseballTeamRoster(SetOf): + ''' + ASN.1 specification: + + BaseballTeamRoster ::= SET SIZE (1..25) OF PlayerNames + ''' + componentType = PlayerNames() + subtypeSpec = ValueSizeConstraint(1, 25) + + # this will succeed + team = BaseballTeamRoster() + team.extend(['Jan', 'Matej']) + encode(team) + + # this will raise ValueConstraintError + team = BaseballTeamRoster() + team.extend(['Jan'] * 26) + encode(team) + + Note + ---- + Whenever ValueSizeConstraint is applied to mutable types + (e.g. :class:`~pyasn1.type.univ.SequenceOf`, + :class:`~pyasn1.type.univ.SetOf`), constraint + validation only happens at the serialisation phase rather + than schema instantiation phase (as it is with immutable + types). + """ def _testValue(self, value, idx): valueSize = len(value) if valueSize < self.start or valueSize > self.stop: @@ -149,6 +293,40 @@ class ValueSizeConstraint(ValueRangeConstraint): class PermittedAlphabetConstraint(SingleValueConstraint): + """Create a PermittedAlphabetConstraint object. + + The PermittedAlphabetConstraint satisfies any character + string for as long as all its characters are present in + the set of permitted characters. + + The PermittedAlphabetConstraint object can only be applied + to the :ref:`character ASN.1 types <type.char>` such as + :class:`~pyasn1.type.char.IA5String`. + + Parameters + ---------- + \*alphabet: :class:`str` + Full set of characters permitted by this constraint object. + + Example + ------- + .. code-block:: python + + class BooleanValue(IA5String): + ''' + ASN.1 specification: + + BooleanValue ::= IA5String (FROM ('T' | 'F')) + ''' + subtypeSpec = PermittedAlphabetConstraint('T', 'F') + + # this will succeed + truth = BooleanValue('T') + truth = BooleanValue('TF') + + # this will raise ValueConstraintError + garbage = BooleanValue('TAF') + """ def _setValues(self, values): self._values = values self._set = set(values) @@ -160,7 +338,7 @@ class PermittedAlphabetConstraint(SingleValueConstraint): # This is a bit kludgy, meaning two op modes within a single constraint class InnerTypeConstraint(AbstractConstraint): - """Value must satisfy type and presense constraints""" + """Value must satisfy the type and presence constraints""" def _testValue(self, value, idx): if self.__singleTypeConstraint: @@ -184,11 +362,50 @@ class InnerTypeConstraint(AbstractConstraint): AbstractConstraint._setValues(self, values) -# Boolean ops on constraints +# Logic operations on constraints class ConstraintsExclusion(AbstractConstraint): - """Value must not fit the single constraint""" + """Create a ConstraintsExclusion logic operator object. + + The ConstraintsExclusion logic operator succeeds when the + value does *not* satisfy the operand constraint. + + The ConstraintsExclusion object can be applied to + any constraint and logic operator object. + + Parameters + ---------- + constraint: + Constraint or logic operator object. + + Example + ------- + .. code-block:: python + + class Lipogramme(IA5STRING): + ''' + ASN.1 specification: + Lipogramme ::= + IA5String (FROM (ALL EXCEPT ("e"|"E"))) + ''' + subtypeSpec = ConstraintsExclusion( + PermittedAlphabetConstraint('e', 'E') + ) + + # this will succeed + lipogramme = Lipogramme('A work of fiction?') + + # this will raise ValueConstraintError + lipogramme = Lipogramme('Eel') + + Warning + ------- + The above example involving PermittedAlphabetConstraint might + not work due to the way how PermittedAlphabetConstraint works. + The other constraints might work with ConstraintsExclusion + though. + """ def _testValue(self, value, idx): try: self._values[0](value, idx) @@ -200,11 +417,11 @@ class ConstraintsExclusion(AbstractConstraint): def _setValues(self, values): if len(values) != 1: raise error.PyAsn1Error('Single constraint expected') + AbstractConstraint._setValues(self, values) class AbstractConstraintSet(AbstractConstraint): - """Value must not satisfy the single constraint""" def __getitem__(self, idx): return self._values[idx] @@ -232,16 +449,88 @@ class AbstractConstraintSet(AbstractConstraint): class ConstraintsIntersection(AbstractConstraintSet): - """Value must satisfy all constraints""" + """Create a ConstraintsIntersection logic operator object. + + The ConstraintsIntersection logic operator only succeeds + if *all* its operands succeed. + + The ConstraintsIntersection object can be applied to + any constraint and logic operator objects. + + The ConstraintsIntersection object duck-types the immutable + container object like Python :py:class:`tuple`. + Parameters + ---------- + \*constraints: + Constraint or logic operator objects. + + Example + ------- + .. code-block:: python + + class CapitalAndSmall(IA5String): + ''' + ASN.1 specification: + + CapitalAndSmall ::= + IA5String (FROM ("A".."Z"|"a".."z")) + ''' + subtypeSpec = ConstraintsIntersection( + PermittedAlphabetConstraint('A', 'Z'), + PermittedAlphabetConstraint('a', 'z') + ) + + # this will succeed + capital_and_small = CapitalAndSmall('Hello') + + # this will raise ValueConstraintError + capital_and_small = CapitalAndSmall('hello') + """ def _testValue(self, value, idx): for constraint in self._values: constraint(value, idx) class ConstraintsUnion(AbstractConstraintSet): - """Value must satisfy at least one constraint""" + """Create a ConstraintsUnion logic operator object. + + The ConstraintsUnion logic operator only succeeds if + *at least a single* operand succeeds. + + The ConstraintsUnion object can be applied to + any constraint and logic operator objects. + The ConstraintsUnion object duck-types the immutable + container object like Python :py:class:`tuple`. + + Parameters + ---------- + \*constraints: + Constraint or logic operator objects. + + Example + ------- + .. code-block:: python + + class CapitalOrSmall(IA5String): + ''' + ASN.1 specification: + + CapitalOrSmall ::= + IA5String (FROM ("A".."Z") | FROM ("a".."z")) + ''' + subtypeSpec = ConstraintsIntersection( + PermittedAlphabetConstraint('A', 'Z'), + PermittedAlphabetConstraint('a', 'z') + ) + + # this will succeed + capital_or_small = CapitalAndSmall('Hello') + + # this will raise ValueConstraintError + capital_or_small = CapitalOrSmall('hello!') + """ def _testValue(self, value, idx): for constraint in self._values: try: @@ -250,9 +539,13 @@ class ConstraintsUnion(AbstractConstraintSet): pass else: return + raise error.ValueConstraintError( - 'all of %s failed for \"%s\"' % (self._values, value) + 'all of %s failed for "%s"' % (self._values, value) ) -# XXX +# TODO: +# refactor InnerTypeConstraint # add tests for type check +# implement other constraint types +# make constraint validation easy to skip |