diff options
author | Haibo Huang <hhb@google.com> | 2019-09-03 22:38:51 -0700 |
---|---|---|
committer | android-build-merger <android-build-merger@google.com> | 2019-09-03 22:38:51 -0700 |
commit | f89dc2f7d0808ff5f33e5633e8c22745f82c0e80 (patch) | |
tree | 33f05fcb02b39ce15aa202ac28ce3cbf9b405736 | |
parent | 0e2d869e09c1ad06292da0385bb852df17c45a7a (diff) | |
parent | 2d80b35362ff227edda6389e2c07359f57cc6e48 (diff) | |
download | pyasn1-f89dc2f7d0808ff5f33e5633e8c22745f82c0e80.tar.gz |
Upgrade python/pyasn1 to v0.4.7 am: 1fab31ffd7 am: 500c6a6bdb am: 4ac677bf95
am: 2d80b35362
Change-Id: I7eaa8eacd00c07e476422e8797a22190efa1cbfd
-rw-r--r-- | CHANGES.rst | 22 | ||||
-rw-r--r-- | METADATA | 6 | ||||
-rw-r--r-- | docs/source/pyasn1/type/base/constructedasn1type.rst | 2 | ||||
-rw-r--r-- | docs/source/pyasn1/type/constraint/contents.rst | 1 | ||||
-rw-r--r-- | docs/source/pyasn1/type/constraint/withcomponents.rst | 16 | ||||
-rw-r--r-- | docs/source/pyasn1/type/univ/choice.rst | 6 | ||||
-rw-r--r-- | docs/source/pyasn1/type/univ/sequence.rst | 6 | ||||
-rw-r--r-- | docs/source/pyasn1/type/univ/sequenceof.rst | 6 | ||||
-rw-r--r-- | docs/source/pyasn1/type/univ/set.rst | 6 | ||||
-rw-r--r-- | docs/source/pyasn1/type/univ/setof.rst | 6 | ||||
-rw-r--r-- | pyasn1/__init__.py | 2 | ||||
-rw-r--r-- | pyasn1/codec/ber/decoder.py | 8 | ||||
-rw-r--r-- | pyasn1/codec/ber/encoder.py | 8 | ||||
-rw-r--r-- | pyasn1/codec/cer/encoder.py | 4 | ||||
-rw-r--r-- | pyasn1/codec/native/encoder.py | 8 | ||||
-rw-r--r-- | pyasn1/type/base.py | 34 | ||||
-rw-r--r-- | pyasn1/type/char.py | 4 | ||||
-rw-r--r-- | pyasn1/type/constraint.py | 151 | ||||
-rw-r--r-- | pyasn1/type/univ.py | 184 | ||||
-rw-r--r-- | tests/codec/ber/test_decoder.py | 6 | ||||
-rw-r--r-- | tests/codec/ber/test_encoder.py | 12 | ||||
-rw-r--r-- | tests/type/test_constraint.py | 63 | ||||
-rw-r--r-- | tests/type/test_univ.py | 136 |
23 files changed, 562 insertions, 135 deletions
diff --git a/CHANGES.rst b/CHANGES.rst index 0ca5904..139376d 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -1,4 +1,26 @@ +Revision 0.4.7, released 01-09-2019 +----------------------------------- + +- Added `isInconsistent` property to all constructed types. This property + conceptually replaces `verifySizeSpec` method to serve a more general + purpose e.g. ensuring all required fields are in a good shape. By default + this check invokes subtype constraints verification and is run by codecs + on value de/serialisation. +- Deprecate `subtypeSpec` attributes and keyword argument. It is now + recommended to pass `ValueSizeConstraint`, as well as all other constraints, + to `subtypeSpec`. +- Fixed a design bug in a way of how the items assigned to constructed + types are verified. Now if `Asn1Type`-based object is assigned, its + compatibility is verified based on having all tags and constraint + objects as the type in field definition. When a bare Python value is + assigned, then field type object is cloned and initialized with the + bare value (constraints verificaton would run at this moment). +- Added `WithComponentsConstraint` along with related + `ComponentPresentConstraint` and `ComponentAbsentConstraint` classes + to be used with `Sequence`/`Set` types representing + `SET ... WITH COMPONENTS ...` like ASN.1 constructs. + Revision 0.4.6, released 31-07-2019 ----------------------------------- @@ -9,10 +9,10 @@ third_party { type: GIT value: "https://github.com/etingof/pyasn1" } - version: "v0.4.6" + version: "v0.4.7" last_upgrade_date { year: 2019 - month: 7 - day: 31 + month: 9 + day: 3 } } diff --git a/docs/source/pyasn1/type/base/constructedasn1type.rst b/docs/source/pyasn1/type/base/constructedasn1type.rst index 54d828f..8709066 100644 --- a/docs/source/pyasn1/type/base/constructedasn1type.rst +++ b/docs/source/pyasn1/type/base/constructedasn1type.rst @@ -6,5 +6,5 @@ |ASN.1| type ------------ -.. autoclass:: pyasn1.type.base.ConstructedAsn1Type(tagSet=TagSet(), subtypeSpec=ConstraintsIntersection(), sizeSpec=ConstraintsIntersection(), componentType=None) +.. autoclass:: pyasn1.type.base.ConstructedAsn1Type(tagSet=TagSet(), subtypeSpec=ConstraintsIntersection(), componentType=None) :members: isSameTypeWith, isSuperTypeOf, tagSet, effectiveTagSet, tagMap, subtypeSpec diff --git a/docs/source/pyasn1/type/constraint/contents.rst b/docs/source/pyasn1/type/constraint/contents.rst index 8e4db7c..a4e2424 100644 --- a/docs/source/pyasn1/type/constraint/contents.rst +++ b/docs/source/pyasn1/type/constraint/contents.rst @@ -32,6 +32,7 @@ they get attached to ASN.1 type object at a *.subtypeSpec* attribute. /pyasn1/type/constraint/valuerange /pyasn1/type/constraint/valuesize /pyasn1/type/constraint/permittedalphabet + /pyasn1/type/constraint/withcomponents Logic operations on constraints diff --git a/docs/source/pyasn1/type/constraint/withcomponents.rst b/docs/source/pyasn1/type/constraint/withcomponents.rst new file mode 100644 index 0000000..f1556b0 --- /dev/null +++ b/docs/source/pyasn1/type/constraint/withcomponents.rst @@ -0,0 +1,16 @@ + +.. _constrain.WithComponentsConstraint: + +.. |Constraint| replace:: WithComponentsConstraint + +WITH COMPONENTS constraint +-------------------------- + +.. autoclass:: pyasn1.type.constraint.WithComponentsConstraint(*fields) + :members: + +.. autoclass:: pyasn1.type.constraint.ComponentPresentConstraint() + :members: + +.. autoclass:: pyasn1.type.constraint.ComponentAbsentConstraint() + :members: diff --git a/docs/source/pyasn1/type/univ/choice.rst b/docs/source/pyasn1/type/univ/choice.rst index 9731971..323932b 100644 --- a/docs/source/pyasn1/type/univ/choice.rst +++ b/docs/source/pyasn1/type/univ/choice.rst @@ -6,10 +6,10 @@ |ASN.1| type ------------ -.. autoclass:: pyasn1.type.univ.Choice(componentType=None, tagSet=tagSet(), subtypeSpec=ConstraintsIntersection(), sizeSpec=ConstraintsIntersection()) - :members: isValue, isSameTypeWith, isSuperTypeOf, tagSet, effectiveTagSet, tagMap, componentType, subtypeSpec, sizeSpec, +.. autoclass:: pyasn1.type.univ.Choice(componentType=None, tagSet=tagSet(), subtypeSpec=ConstraintsIntersection()) + :members: isValue, isSameTypeWith, isSuperTypeOf, tagSet, effectiveTagSet, tagMap, componentType, subtypeSpec, getComponentByPosition, setComponentByPosition, getComponentByName, setComponentByName, setDefaultComponents, - getComponentByType, setComponentByType, getName, getComponent + getComponentByType, setComponentByType, getName, getComponent, isInconsistent .. note:: diff --git a/docs/source/pyasn1/type/univ/sequence.rst b/docs/source/pyasn1/type/univ/sequence.rst index 0988004..163a3a0 100644 --- a/docs/source/pyasn1/type/univ/sequence.rst +++ b/docs/source/pyasn1/type/univ/sequence.rst @@ -6,10 +6,10 @@ |ASN.1| type ------------ -.. autoclass:: pyasn1.type.univ.Sequence(componentType=NamedTypes(), tagSet=tagSet(), subtypeSpec=ConstraintsIntersection(), sizeSpec=ConstraintsIntersection()) - :members: isValue, isSameTypeWith, isSuperTypeOf, tagSet, effectiveTagSet, tagMap, componentType, subtypeSpec, sizeSpec, getComponentByPosition, +.. autoclass:: pyasn1.type.univ.Sequence(componentType=NamedTypes(), tagSet=tagSet(), subtypeSpec=ConstraintsIntersection()) + :members: isValue, isSameTypeWith, isSuperTypeOf, tagSet, effectiveTagSet, tagMap, componentType, subtypeSpec, getComponentByPosition, setComponentByPosition, getComponentByName, setComponentByName, setDefaultComponents, - clear, reset + clear, reset, isInconsistent .. note:: diff --git a/docs/source/pyasn1/type/univ/sequenceof.rst b/docs/source/pyasn1/type/univ/sequenceof.rst index 6a09b92..dd4f0c5 100644 --- a/docs/source/pyasn1/type/univ/sequenceof.rst +++ b/docs/source/pyasn1/type/univ/sequenceof.rst @@ -6,9 +6,9 @@ |ASN.1| type ------------ -.. autoclass:: pyasn1.type.univ.SequenceOf(componentType=NoValue(), tagSet=TagSet(), subtypeSpec=ConstraintsIntersection(), sizeSpec=ConstraintsIntersection()) - :members: isValue, isSameTypeWith, isSuperTypeOf, tagSet, effectiveTagSet, tagMap, componentType, subtypeSpec, sizeSpec, - getComponentByPosition, setComponentByPosition, clear, reset +.. autoclass:: pyasn1.type.univ.SequenceOf(componentType=NoValue(), tagSet=TagSet(), subtypeSpec=ConstraintsIntersection()) + :members: isValue, isSameTypeWith, isSuperTypeOf, tagSet, effectiveTagSet, tagMap, componentType, subtypeSpec, + getComponentByPosition, setComponentByPosition, clear, reset, isInconsistent .. note:: diff --git a/docs/source/pyasn1/type/univ/set.rst b/docs/source/pyasn1/type/univ/set.rst index 792d2e9..157fed0 100644 --- a/docs/source/pyasn1/type/univ/set.rst +++ b/docs/source/pyasn1/type/univ/set.rst @@ -6,10 +6,10 @@ |ASN.1| type ------------ -.. autoclass:: pyasn1.type.univ.Set(componentType=NamedTypes(), tagSet=TagSet(), subtypeSpec=ConstraintsIntersection(), sizeSpec=ConstraintsIntersection()) - :members: isValue, isSameTypeWith, isSuperTypeOf, tagSet, effectiveTagSet, tagMap, componentType, subtypeSpec, sizeSpec, +.. autoclass:: pyasn1.type.univ.Set(componentType=NamedTypes(), tagSet=TagSet(), subtypeSpec=ConstraintsIntersection()) + :members: isValue, isSameTypeWith, isSuperTypeOf, tagSet, effectiveTagSet, tagMap, componentType, subtypeSpec, getComponentByPosition, setComponentByPosition, getComponentByName, setComponentByName, setDefaultComponents, - getComponentByType, setComponentByType, clear, reset + getComponentByType, setComponentByType, clear, reset, isInconsistent .. note:: diff --git a/docs/source/pyasn1/type/univ/setof.rst b/docs/source/pyasn1/type/univ/setof.rst index 67ef92f..581c20f 100644 --- a/docs/source/pyasn1/type/univ/setof.rst +++ b/docs/source/pyasn1/type/univ/setof.rst @@ -6,9 +6,9 @@ |ASN.1| type ------------ -.. autoclass:: pyasn1.type.univ.SetOf(componentType=NoValue(), tagSet=TagSet(), subtypeSpec=ConstraintsIntersection(), sizeSpec=ConstraintsIntersection()) - :members: isValue, isSameTypeWith, isSuperTypeOf, tagSet, effectiveTagSet, tagMap, componentType, subtypeSpec, sizeSpec, - Â getComponentByPosition, setComponentByPosition, clear, reset +.. autoclass:: pyasn1.type.univ.SetOf(componentType=NoValue(), tagSet=TagSet(), subtypeSpec=ConstraintsIntersection()) + :members: isValue, isSameTypeWith, isSuperTypeOf, tagSet, effectiveTagSet, tagMap, componentType, subtypeSpec, + Â getComponentByPosition, setComponentByPosition, clear, reset, isInconsistent .. note:: diff --git a/pyasn1/__init__.py b/pyasn1/__init__.py index d780df2..92838e0 100644 --- a/pyasn1/__init__.py +++ b/pyasn1/__init__.py @@ -1,7 +1,7 @@ import sys # https://www.python.org/dev/peps/pep-0396/ -__version__ = '0.4.6' +__version__ = '0.4.7' if sys.version_info[:2] < (2, 4): raise RuntimeError('PyASN1 requires Python 2.4 or later') diff --git a/pyasn1/codec/ber/decoder.py b/pyasn1/codec/ber/decoder.py index 3f2d180..5759ab8 100644 --- a/pyasn1/codec/ber/decoder.py +++ b/pyasn1/codec/ber/decoder.py @@ -695,7 +695,9 @@ class UniversalConstructedTypeDecoder(AbstractConstructedDecoder): asn1Object.setComponentByPosition(idx, component) else: - asn1Object.verifySizeSpec() + inconsistency = asn1Object.isInconsistent + if inconsistency: + raise inconsistency else: asn1Object = asn1Spec.clone() @@ -879,7 +881,9 @@ class UniversalConstructedTypeDecoder(AbstractConstructedDecoder): asn1Object.setComponentByPosition(idx, component) else: - asn1Object.verifySizeSpec() + inconsistency = asn1Object.isInconsistent + if inconsistency: + raise inconsistency else: asn1Object = asn1Spec.clone() diff --git a/pyasn1/codec/ber/encoder.py b/pyasn1/codec/ber/encoder.py index a5d5fd3..778aa86 100644 --- a/pyasn1/codec/ber/encoder.py +++ b/pyasn1/codec/ber/encoder.py @@ -537,7 +537,9 @@ class SequenceEncoder(AbstractItemEncoder): if asn1Spec is None: # instance of ASN.1 schema - value.verifySizeSpec() + inconsistency = value.isInconsistent + if inconsistency: + raise inconsistency namedTypes = value.componentType @@ -643,7 +645,9 @@ class SequenceOfEncoder(AbstractItemEncoder): def _encodeComponents(self, value, asn1Spec, encodeFun, **options): if asn1Spec is None: - value.verifySizeSpec() + inconsistency = value.isInconsistent + if inconsistency: + raise inconsistency else: asn1Spec = asn1Spec.componentType diff --git a/pyasn1/codec/cer/encoder.py b/pyasn1/codec/cer/encoder.py index 6a9db97..935b696 100644 --- a/pyasn1/codec/cer/encoder.py +++ b/pyasn1/codec/cer/encoder.py @@ -169,7 +169,9 @@ class SetEncoder(encoder.SequenceEncoder): if asn1Spec is None: # instance of ASN.1 schema - value.verifySizeSpec() + inconsistency = value.isInconsistent + if inconsistency: + raise inconsistency namedTypes = value.componentType diff --git a/pyasn1/codec/native/encoder.py b/pyasn1/codec/native/encoder.py index 4c5908d..4318abd 100644 --- a/pyasn1/codec/native/encoder.py +++ b/pyasn1/codec/native/encoder.py @@ -72,7 +72,9 @@ class SetEncoder(AbstractItemEncoder): protoDict = dict def encode(self, value, encodeFun, **options): - value.verifySizeSpec() + inconsistency = value.isInconsistent + if inconsistency: + raise inconsistency namedTypes = value.componentType substrate = self.protoDict() @@ -90,7 +92,9 @@ class SequenceEncoder(SetEncoder): class SequenceOfEncoder(AbstractItemEncoder): def encode(self, value, encodeFun, **options): - value.verifySizeSpec() + inconsistency = value.isInconsistent + if inconsistency: + raise inconsistency return [encodeFun(x, **options) for x in value] diff --git a/pyasn1/type/base.py b/pyasn1/type/base.py index 21e4041..994f1c9 100644 --- a/pyasn1/type/base.py +++ b/pyasn1/type/base.py @@ -498,17 +498,39 @@ class ConstructedAsn1Type(Asn1Type): strictConstraints = False componentType = None - sizeSpec = None + + # backward compatibility, unused + sizeSpec = constraint.ConstraintsIntersection() def __init__(self, **kwargs): readOnly = { 'componentType': self.componentType, + # backward compatibility, unused 'sizeSpec': self.sizeSpec } + + # backward compatibility: preserve legacy sizeSpec support + kwargs = self._moveSizeSpec(**kwargs) + readOnly.update(kwargs) Asn1Type.__init__(self, **readOnly) + def _moveSizeSpec(self, **kwargs): + # backward compatibility, unused + sizeSpec = kwargs.pop('sizeSpec', self.sizeSpec) + if sizeSpec: + subtypeSpec = kwargs.pop('subtypeSpec', self.subtypeSpec) + if subtypeSpec: + subtypeSpec = sizeSpec + + else: + subtypeSpec += sizeSpec + + kwargs['subtypeSpec'] = subtypeSpec + + return kwargs + def __repr__(self): representation = '%s %s object' % ( self.__class__.__name__, self.isValue and 'value' or 'schema' @@ -655,9 +677,6 @@ class ConstructedAsn1Type(Asn1Type): return clone - def verifySizeSpec(self): - self.sizeSpec(self) - def getComponentByPosition(self, idx): raise error.PyAsn1Error('Method not implemented') @@ -679,5 +698,10 @@ class ConstructedAsn1Type(Asn1Type): def getComponentType(self): return self.componentType -# Backward compatibility + # backward compatibility, unused + def verifySizeSpec(self): + self.subtypeSpec(self) + + + # Backward compatibility AbstractConstructedAsn1Item = ConstructedAsn1Type diff --git a/pyasn1/type/char.py b/pyasn1/type/char.py index 3f8c444..06074da 100644 --- a/pyasn1/type/char.py +++ b/pyasn1/type/char.py @@ -39,7 +39,9 @@ class AbstractCharacterString(univ.OctetString): Object representing non-default ASN.1 tag(s) subtypeSpec: :py:class:`~pyasn1.type.constraint.ConstraintsIntersection` - Object representing non-default ASN.1 subtype constraint(s) + Object representing non-default ASN.1 subtype constraint(s). Constraints + verification for |ASN.1| type occurs automatically on object + instantiation. encoding: :py:class:`str` Unicode codec ID to encode/decode :class:`unicode` (Python 2) or diff --git a/pyasn1/type/constraint.py b/pyasn1/type/constraint.py index 807c827..b66627d 100644 --- a/pyasn1/type/constraint.py +++ b/pyasn1/type/constraint.py @@ -342,6 +342,151 @@ class PermittedAlphabetConstraint(SingleValueConstraint): raise error.ValueConstraintError(value) +class ComponentPresentConstraint(AbstractConstraint): + """Create a ComponentPresentConstraint object. + + The ComponentPresentConstraint is only satisfied when the value + is not `None`. + + The ComponentPresentConstraint object is typically used with + `WithComponentsConstraint`. + + Examples + -------- + .. code-block:: python + + present = ComponentPresentConstraint() + + # this will succeed + present('whatever') + + # this will raise ValueConstraintError + present(None) + """ + def _setValues(self, values): + self._values = ('<must be present>',) + + if values: + raise error.PyAsn1Error('No arguments expected') + + def _testValue(self, value, idx): + if value is None: + raise error.ValueConstraintError( + 'Component is not present:') + + +class ComponentAbsentConstraint(AbstractConstraint): + """Create a ComponentAbsentConstraint object. + + The ComponentAbsentConstraint is only satisfied when the value + is `None`. + + The ComponentAbsentConstraint object is typically used with + `WithComponentsConstraint`. + + Examples + -------- + .. code-block:: python + + absent = ComponentAbsentConstraint() + + # this will succeed + absent(None) + + # this will raise ValueConstraintError + absent('whatever') + """ + def _setValues(self, values): + self._values = ('<must be absent>',) + + if values: + raise error.PyAsn1Error('No arguments expected') + + def _testValue(self, value, idx): + if value is not None: + raise error.ValueConstraintError( + 'Component is not absent: %r' % value) + + +class WithComponentsConstraint(AbstractConstraint): + """Create a WithComponentsConstraint object. + + The `WithComponentsConstraint` satisfies any mapping object that has + constrained fields present or absent, what is indicated by + `ComponentPresentConstraint` and `ComponentAbsentConstraint` + objects respectively. + + The `WithComponentsConstraint` object is typically applied + to :class:`~pyasn1.type.univ.Set` or + :class:`~pyasn1.type.univ.Sequence` types. + + Parameters + ---------- + *fields: :class:`tuple` + Zero or more tuples of (`field`, `constraint`) indicating constrained + fields. + + Notes + ----- + On top of the primary use of `WithComponentsConstraint` (ensuring presence + or absence of particular components of a :class:`~pyasn1.type.univ.Set` or + :class:`~pyasn1.type.univ.Sequence`), it is also possible to pass any other + constraint objects or their combinations. In case of scalar fields, these + constraints will be verified in addition to the constraints belonging to + scalar components themselves. However, formally, these additional + constraints do not change the type of these ASN.1 objects. + + Examples + -------- + + .. code-block:: python + + class Item(Sequence): # Set is similar + ''' + ASN.1 specification: + + Item ::= SEQUENCE { + id INTEGER OPTIONAL, + name OCTET STRING OPTIONAL + } WITH COMPONENTS id PRESENT, name ABSENT | id ABSENT, name PRESENT + ''' + componentType = NamedTypes( + OptionalNamedType('id', Integer()), + OptionalNamedType('name', OctetString()) + ) + withComponents = ConstraintsUnion( + WithComponentsConstraint( + ('id', ComponentPresentConstraint()), + ('name', ComponentAbsentConstraint()) + ), + WithComponentsConstraint( + ('id', ComponentAbsentConstraint()), + ('name', ComponentPresentConstraint()) + ) + ) + + item = Item() + + # This will succeed + item['id'] = 1 + + # This will succeed + item.reset() + item['name'] = 'John' + + # This will fail (on encoding) + item.reset() + descr['id'] = 1 + descr['name'] = 'John' + """ + def _testValue(self, value, idx): + for field, constraint in self._values: + constraint(value.get(field)) + + def _setValues(self, values): + AbstractConstraint._setValues(self, values) + + # This is a bit kludgy, meaning two op modes within a single constraint class InnerTypeConstraint(AbstractConstraint): """Value must satisfy the type and presence constraints""" @@ -501,8 +646,8 @@ class ConstraintsIntersection(AbstractConstraintSet): class ConstraintsUnion(AbstractConstraintSet): """Create a ConstraintsUnion logic operator object. - The ConstraintsUnion logic operator only succeeds if - *at least a single* operand succeeds. + The ConstraintsUnion logic operator succeeds if + *at least* a single operand succeeds. The ConstraintsUnion object can be applied to any constraint and logic operator objects. @@ -526,7 +671,7 @@ class ConstraintsUnion(AbstractConstraintSet): CapitalOrSmall ::= IA5String (FROM ("A".."Z") | FROM ("a".."z")) ''' - subtypeSpec = ConstraintsIntersection( + subtypeSpec = ConstraintsUnion( PermittedAlphabetConstraint('A', 'Z'), PermittedAlphabetConstraint('a', 'z') ) diff --git a/pyasn1/type/univ.py b/pyasn1/type/univ.py index b39c533..aa688b2 100644 --- a/pyasn1/type/univ.py +++ b/pyasn1/type/univ.py @@ -47,7 +47,9 @@ class Integer(base.SimpleAsn1Type): Object representing non-default ASN.1 tag(s) subtypeSpec: :py:class:`~pyasn1.type.constraint.ConstraintsIntersection` - Object representing non-default ASN.1 subtype constraint(s) + Object representing non-default ASN.1 subtype constraint(s). Constraints + verification for |ASN.1| type occurs automatically on object + instantiation. namedValues: :py:class:`~pyasn1.type.namedval.NamedValues` Object representing non-default symbolic aliases for numbers @@ -293,7 +295,9 @@ class Boolean(Integer): Object representing non-default ASN.1 tag(s) subtypeSpec: :py:class:`~pyasn1.type.constraint.ConstraintsIntersection` - Object representing non-default ASN.1 subtype constraint(s) + Object representing non-default ASN.1 subtype constraint(s).Constraints + verification for |ASN.1| type occurs automatically on object + instantiation. namedValues: :py:class:`~pyasn1.type.namedval.NamedValues` Object representing non-default symbolic aliases for numbers @@ -377,7 +381,9 @@ class BitString(base.SimpleAsn1Type): Object representing non-default ASN.1 tag(s) subtypeSpec: :py:class:`~pyasn1.type.constraint.ConstraintsIntersection` - Object representing non-default ASN.1 subtype constraint(s) + Object representing non-default ASN.1 subtype constraint(s). Constraints + verification for |ASN.1| type occurs automatically on object + instantiation. namedValues: :py:class:`~pyasn1.type.namedval.NamedValues` Object representing non-default symbolic aliases for numbers @@ -747,7 +753,9 @@ class OctetString(base.SimpleAsn1Type): Object representing non-default ASN.1 tag(s) subtypeSpec: :py:class:`~pyasn1.type.constraint.ConstraintsIntersection` - Object representing non-default ASN.1 subtype constraint(s) + Object representing non-default ASN.1 subtype constraint(s). Constraints + verification for |ASN.1| type occurs automatically on object + instantiation. encoding: :py:class:`str` Unicode codec ID to encode/decode :class:`unicode` (Python 2) or @@ -1130,7 +1138,9 @@ class ObjectIdentifier(base.SimpleAsn1Type): Object representing non-default ASN.1 tag(s) subtypeSpec: :py:class:`~pyasn1.type.constraint.ConstraintsIntersection` - Object representing non-default ASN.1 subtype constraint(s) + Object representing non-default ASN.1 subtype constraint(s). Constraints + verification for |ASN.1| type occurs automatically on object + instantiation. Raises ------ @@ -1268,7 +1278,9 @@ class Real(base.SimpleAsn1Type): Object representing non-default ASN.1 tag(s) subtypeSpec: :py:class:`~pyasn1.type.constraint.ConstraintsIntersection` - Object representing non-default ASN.1 subtype constraint(s) + Object representing non-default ASN.1 subtype constraint(s). Constraints + verification for |ASN.1| type occurs automatically on object + instantiation. Raises ------ @@ -1552,7 +1564,9 @@ class Enumerated(Integer): Object representing non-default ASN.1 tag(s) subtypeSpec: :py:class:`~pyasn1.type.constraint.ConstraintsIntersection` - Object representing non-default ASN.1 subtype constraint(s) + Object representing non-default ASN.1 subtype constraint(s). Constraints + verification for |ASN.1| type occurs automatically on object + instantiation. namedValues: :py:class:`~pyasn1.type.namedval.NamedValues` Object representing non-default symbolic aliases for numbers @@ -1620,10 +1634,9 @@ class SequenceOfAndSetOfBase(base.ConstructedAsn1Type): Object representing non-default ASN.1 tag(s) subtypeSpec: :py:class:`~pyasn1.type.constraint.ConstraintsIntersection` - Object representing non-default ASN.1 subtype constraint(s) - - sizeSpec: :py:class:`~pyasn1.type.constraint.ConstraintsIntersection` - Object representing collection size constraint + Object representing non-default ASN.1 subtype constraint(s). Constraints + verification for |ASN.1| type can only occur on explicit + `.isInconsistent` call. Examples -------- @@ -1645,7 +1658,7 @@ class SequenceOfAndSetOfBase(base.ConstructedAsn1Type): # support positional params for backward compatibility if args: for key, value in zip(('componentType', 'tagSet', - 'subtypeSpec', 'sizeSpec'), args): + 'subtypeSpec'), args): if key in kwargs: raise error.PyAsn1Error('Conflicting positional and keyword params!') kwargs['componentType'] = value @@ -1921,7 +1934,8 @@ class SequenceOfAndSetOfBase(base.ConstructedAsn1Type): componentType.isSameTypeWith or componentType.isSuperTypeOf) - if not subtypeChecker(value, matchTags, matchConstraints): + if not subtypeChecker(value, verifyConstraints and matchTags, + verifyConstraints and matchConstraints): # TODO: we should wrap componentType with UnnamedType to carry # additional properties associated with componentType if componentType.typeId != Any.typeId: @@ -1929,21 +1943,6 @@ class SequenceOfAndSetOfBase(base.ConstructedAsn1Type): 'Component value is tag-incompatible: %r vs ' '%r' % (value, componentType)) - else: - if not componentType.isSuperTypeOf( - value, matchTags, matchConstraints): - raise error.PyAsn1Error( - 'Component value is tag-incompatible: ' - '%r vs %r' % (value, componentType)) - - if verifyConstraints and value.isValue: - try: - self.subtypeSpec(value, idx) - - except error.PyAsn1Error: - exType, exValue, exTb = sys.exc_info() - raise exType('%s at %s' % (exValue, self.__class__.__name__)) - componentValues[idx] = value self._componentValues = componentValues @@ -2043,6 +2042,41 @@ class SequenceOfAndSetOfBase(base.ConstructedAsn1Type): return True + @property + def isInconsistent(self): + """Run necessary checks to ensure |ASN.1| object consistency. + + Default action is to verify |ASN.1| object against constraints imposed + by `subtypeSpec`. + + Raises + ------ + :py:class:`~pyasn1.error.PyAsn1tError` on any inconsistencies found + """ + if self.componentType is noValue or not self.subtypeSpec: + return False + + if self._componentValues is noValue: + return True + + mapping = {} + + for idx, value in self._componentValues.items(): + # Absent fields are not in the mapping + if value is noValue: + continue + + mapping[idx] = value + + try: + # Represent SequenceOf/SetOf as a bare dict to constraints chain + self.subtypeSpec(mapping) + + except error.PyAsn1Error: + exc = sys.exc_info()[1] + return exc + + return False class SequenceOf(SequenceOfAndSetOfBase): __doc__ = SequenceOfAndSetOfBase.__doc__ @@ -2063,10 +2097,6 @@ class SequenceOf(SequenceOfAndSetOfBase): #: imposing constraints on |ASN.1| type initialization values. subtypeSpec = constraint.ConstraintsIntersection() - #: Default :py:class:`~pyasn1.type.constraint.ConstraintsIntersection` - #: object imposing size constraint on |ASN.1| objects - sizeSpec = constraint.ConstraintsIntersection() - # Disambiguation ASN.1 types identification typeId = SequenceOfAndSetOfBase.getTypeId() @@ -2090,10 +2120,6 @@ class SetOf(SequenceOfAndSetOfBase): #: imposing constraints on |ASN.1| type initialization values. subtypeSpec = constraint.ConstraintsIntersection() - #: Default :py:class:`~pyasn1.type.constraint.ConstraintsIntersection` - #: object imposing size constraint on |ASN.1| objects - sizeSpec = constraint.ConstraintsIntersection() - # Disambiguation ASN.1 types identification typeId = SequenceOfAndSetOfBase.getTypeId() @@ -2113,10 +2139,9 @@ class SequenceAndSetBase(base.ConstructedAsn1Type): Object representing non-default ASN.1 tag(s) subtypeSpec: :py:class:`~pyasn1.type.constraint.ConstraintsIntersection` - Object representing non-default ASN.1 subtype constraint(s) - - sizeSpec: :py:class:`~pyasn1.type.constraint.ConstraintsIntersection` - Object representing collection size constraint + Object representing non-default ASN.1 subtype constraint(s). Constraints + verification for |ASN.1| type can only occur on explicit + `.isInconsistent` call. Examples -------- @@ -2562,25 +2587,19 @@ class SequenceAndSetBase(base.ConstructedAsn1Type): else: raise error.PyAsn1Error('%s undefined component type' % componentType.__class__.__name__) - elif (matchTags or matchConstraints) and componentTypeLen: + elif ((verifyConstraints or matchTags or matchConstraints) and + componentTypeLen): subComponentType = componentType.getTypeByPosition(idx) if subComponentType is not noValue: subtypeChecker = (self.strictConstraints and subComponentType.isSameTypeWith or subComponentType.isSuperTypeOf) - if not subtypeChecker(value, matchTags, matchConstraints): + if not subtypeChecker(value, verifyConstraints and matchTags, + verifyConstraints and matchConstraints): if not componentType[idx].openType: raise error.PyAsn1Error('Component value is tag-incompatible: %r vs %r' % (value, componentType)) - if verifyConstraints and value.isValue: - try: - self.subtypeSpec(value, idx) - - except error.PyAsn1Error: - exType, exValue, exTb = sys.exc_info() - raise exType('%s at %s' % (exValue, self.__class__.__name__)) - if componentTypeLen or idx in self._dynamicNames: componentValues[idx] = value @@ -2653,6 +2672,44 @@ class SequenceAndSetBase(base.ConstructedAsn1Type): return True + @property + def isInconsistent(self): + """Run necessary checks to ensure |ASN.1| object consistency. + + Default action is to verify |ASN.1| object against constraints imposed + by `subtypeSpec`. + + Raises + ------ + :py:class:`~pyasn1.error.PyAsn1tError` on any inconsistencies found + """ + if self.componentType is noValue or not self.subtypeSpec: + return False + + if self._componentValues is noValue: + return True + + mapping = {} + + for idx, value in enumerate(self._componentValues): + # Absent fields are not in the mapping + if value is noValue: + continue + + name = self.componentType.getNameByPosition(idx) + + mapping[name] = value + + try: + # Represent Sequence/Set as a bare dict to constraints chain + self.subtypeSpec(mapping) + + except error.PyAsn1Error: + exc = sys.exc_info()[1] + return exc + + return False + def prettyPrint(self, scope=0): """Return an object representation string. @@ -2717,10 +2774,6 @@ class Sequence(SequenceAndSetBase): #: imposing constraints on |ASN.1| type initialization values. subtypeSpec = constraint.ConstraintsIntersection() - #: Default :py:class:`~pyasn1.type.constraint.ConstraintsIntersection` - #: object imposing constraints on |ASN.1| objects - sizeSpec = constraint.ConstraintsIntersection() - #: Default collection of ASN.1 types of component (e.g. :py:class:`~pyasn1.type.namedtype.NamedType`) #: object imposing size constraint on |ASN.1| objects componentType = namedtype.NamedTypes() @@ -2760,10 +2813,6 @@ class Set(SequenceAndSetBase): #: imposing constraints on |ASN.1| type initialization values. subtypeSpec = constraint.ConstraintsIntersection() - #: Default :py:class:`~pyasn1.type.constraint.ConstraintsIntersection` - #: object imposing constraints on |ASN.1| objects - sizeSpec = constraint.ConstraintsIntersection() - # Disambiguation ASN.1 types identification typeId = SequenceAndSetBase.getTypeId() @@ -2884,10 +2933,9 @@ class Choice(Set): Object representing non-default ASN.1 tag(s) subtypeSpec: :py:class:`~pyasn1.type.constraint.ConstraintsIntersection` - Object representing non-default ASN.1 subtype constraint(s) - - sizeSpec: :py:class:`~pyasn1.type.constraint.ConstraintsIntersection` - Object representing collection size constraint + Object representing non-default ASN.1 subtype constraint(s). Constraints + verification for |ASN.1| type can only occur on explicit + `.isInconsistent` call. Examples -------- @@ -2927,11 +2975,7 @@ class Choice(Set): #: Set (on class, not on instance) or return a #: :py:class:`~pyasn1.type.constraint.ConstraintsIntersection` object #: imposing constraints on |ASN.1| type initialization values. - subtypeSpec = constraint.ConstraintsIntersection() - - #: Default :py:class:`~pyasn1.type.constraint.ConstraintsIntersection` - #: object imposing size constraint on |ASN.1| objects - sizeSpec = constraint.ConstraintsIntersection( + subtypeSpec = constraint.ConstraintsIntersection( constraint.ValueSizeConstraint(1, 1) ) @@ -3004,7 +3048,7 @@ class Choice(Set): if self._currentIdx is not None: yield self.componentType[self._currentIdx].getName(), self[self._currentIdx] - def verifySizeSpec(self): + def checkConsistency(self): if self._currentIdx is None: raise error.PyAsn1Error('Component not chosen') @@ -3197,7 +3241,9 @@ class Any(OctetString): Object representing non-default ASN.1 tag(s) subtypeSpec: :py:class:`~pyasn1.type.constraint.ConstraintsIntersection` - Object representing non-default ASN.1 subtype constraint(s) + Object representing non-default ASN.1 subtype constraint(s). Constraints + verification for |ASN.1| type occurs automatically on object + instantiation. encoding: :py:class:`str` Unicode codec ID to encode/decode :class:`unicode` (Python 2) or diff --git a/tests/codec/ber/test_decoder.py b/tests/codec/ber/test_decoder.py index 089f0f3..e3b74df 100644 --- a/tests/codec/ber/test_decoder.py +++ b/tests/codec/ber/test_decoder.py @@ -486,17 +486,17 @@ class RealDecoderTestCase(BaseTestCase): if sys.version_info[0:2] > (2, 5): class UniversalStringDecoderTestCase(BaseTestCase): def testDecoder(self): - assert decoder.decode(ints2octs((28, 12, 0, 0, 0, 97, 0, 0, 0, 98, 0, 0, 0, 99))) == (char.UniversalString(sys.version_info[0] == 3 and 'abc' or unicode('abc')), null) + assert decoder.decode(ints2octs((28, 12, 0, 0, 0, 97, 0, 0, 0, 98, 0, 0, 0, 99))) == (char.UniversalString(sys.version_info[0] >= 3 and 'abc' or unicode('abc')), null) class BMPStringDecoderTestCase(BaseTestCase): def testDecoder(self): - assert decoder.decode(ints2octs((30, 6, 0, 97, 0, 98, 0, 99))) == (char.BMPString(sys.version_info[0] == 3 and 'abc' or unicode('abc')), null) + assert decoder.decode(ints2octs((30, 6, 0, 97, 0, 98, 0, 99))) == (char.BMPString(sys.version_info[0] >= 3 and 'abc' or unicode('abc')), null) class UTF8StringDecoderTestCase(BaseTestCase): def testDecoder(self): - assert decoder.decode(ints2octs((12, 3, 97, 98, 99))) == (char.UTF8String(sys.version_info[0] == 3 and 'abc' or unicode('abc')), null) + assert decoder.decode(ints2octs((12, 3, 97, 98, 99))) == (char.UTF8String(sys.version_info[0] >= 3 and 'abc' or unicode('abc')), null) class SequenceOfDecoderTestCase(BaseTestCase): diff --git a/tests/codec/ber/test_encoder.py b/tests/codec/ber/test_encoder.py index 38d75c0..df82e7b 100644 --- a/tests/codec/ber/test_encoder.py +++ b/tests/codec/ber/test_encoder.py @@ -436,40 +436,40 @@ class RealEncoderWithSchemaTestCase(BaseTestCase): if sys.version_info[0:2] > (2, 5): class UniversalStringEncoderTestCase(BaseTestCase): def testEncoding(self): - assert encoder.encode(char.UniversalString(sys.version_info[0] == 3 and 'abc' or unicode('abc'))) == ints2octs( + assert encoder.encode(char.UniversalString(sys.version_info[0] >= 3 and 'abc' or unicode('abc'))) == ints2octs( (28, 12, 0, 0, 0, 97, 0, 0, 0, 98, 0, 0, 0, 99)), 'Incorrect encoding' class UniversalStringEncoderWithSchemaTestCase(BaseTestCase): def testEncoding(self): assert encoder.encode( - sys.version_info[0] == 3 and 'abc' or unicode('abc'), asn1Spec=char.UniversalString() + sys.version_info[0] >= 3 and 'abc' or unicode('abc'), asn1Spec=char.UniversalString() ) == ints2octs((28, 12, 0, 0, 0, 97, 0, 0, 0, 98, 0, 0, 0, 99)), 'Incorrect encoding' class BMPStringEncoderTestCase(BaseTestCase): def testEncoding(self): - assert encoder.encode(char.BMPString(sys.version_info[0] == 3 and 'abc' or unicode('abc'))) == ints2octs( + assert encoder.encode(char.BMPString(sys.version_info[0] >= 3 and 'abc' or unicode('abc'))) == ints2octs( (30, 6, 0, 97, 0, 98, 0, 99)), 'Incorrect encoding' class BMPStringEncoderWithSchemaTestCase(BaseTestCase): def testEncoding(self): assert encoder.encode( - sys.version_info[0] == 3 and 'abc' or unicode('abc'), asn1Spec=char.BMPString() + sys.version_info[0] >= 3 and 'abc' or unicode('abc'), asn1Spec=char.BMPString() ) == ints2octs((30, 6, 0, 97, 0, 98, 0, 99)), 'Incorrect encoding' class UTF8StringEncoderTestCase(BaseTestCase): def testEncoding(self): - assert encoder.encode(char.UTF8String(sys.version_info[0] == 3 and 'abc' or unicode('abc'))) == ints2octs( + assert encoder.encode(char.UTF8String(sys.version_info[0] >= 3 and 'abc' or unicode('abc'))) == ints2octs( (12, 3, 97, 98, 99)), 'Incorrect encoding' class UTF8StringEncoderWithSchemaTestCase(BaseTestCase): def testEncoding(self): assert encoder.encode( - sys.version_info[0] == 3 and 'abc' or unicode('abc'), asn1Spec=char.UTF8String() + sys.version_info[0] >= 3 and 'abc' or unicode('abc'), asn1Spec=char.UTF8String() ) == ints2octs((12, 3, 97, 98, 99)), 'Incorrect encoding' diff --git a/tests/type/test_constraint.py b/tests/type/test_constraint.py index b5276cd..0f49c78 100644 --- a/tests/type/test_constraint.py +++ b/tests/type/test_constraint.py @@ -128,6 +128,69 @@ class PermittedAlphabetConstraintTestCase(SingleValueConstraintTestCase): assert 0, 'constraint check fails' +class WithComponentsConstraintTestCase(BaseTestCase): + + def testGoodVal(self): + c = constraint.WithComponentsConstraint( + ('A', constraint.ComponentPresentConstraint()), + ('B', constraint.ComponentAbsentConstraint())) + + try: + c({'A': 1}) + + except error.ValueConstraintError: + assert 0, 'constraint check fails' + + def testGoodValWithExtraFields(self): + c = constraint.WithComponentsConstraint( + ('A', constraint.ComponentPresentConstraint()), + ('B', constraint.ComponentAbsentConstraint()) + ) + + try: + c({'A': 1, 'C': 2}) + + except error.ValueConstraintError: + assert 0, 'constraint check fails' + + def testEmptyConstraint(self): + c = constraint.WithComponentsConstraint() + + try: + c({'A': 1}) + + except error.ValueConstraintError: + assert 0, 'constraint check fails' + + def testBadVal(self): + c = constraint.WithComponentsConstraint( + ('A', constraint.ComponentPresentConstraint()) + ) + + try: + c({'B': 2}) + + except error.ValueConstraintError: + pass + + else: + assert 0, 'constraint check fails' + + def testBadValExtraFields(self): + c = constraint.WithComponentsConstraint( + ('A', constraint.ComponentPresentConstraint()) + ) + + try: + c({'B': 2, 'C': 3}) + + except error.ValueConstraintError: + pass + + else: + assert 0, 'constraint check fails' + + class ConstraintsIntersectionTestCase(BaseTestCase): def setUp(self): BaseTestCase.setUp(self) diff --git a/tests/type/test_univ.py b/tests/type/test_univ.py index 0092588..9762959 100644 --- a/tests/type/test_univ.py +++ b/tests/type/test_univ.py @@ -992,11 +992,13 @@ class SequenceOf(BaseTestCase): assert self.s1 == self.s2, '__cmp__() fails' def testSubtypeSpec(self): - s = self.s1.clone(subtypeSpec=constraint.ConstraintsUnion( - constraint.SingleValueConstraint(str2octs('abc')) - )) + s = self.s1.clone( + componentType=univ.OctetString().subtype( + subtypeSpec=constraint.SingleValueConstraint(str2octs('abc')))) try: - s.setComponentByPosition(0, univ.OctetString('abc')) + s.setComponentByPosition( + 0, univ.OctetString().subtype( + 'abc', subtypeSpec=constraint.SingleValueConstraint(str2octs('abc')))) except PyAsn1Error: assert 0, 'constraint fails' try: @@ -1006,7 +1008,7 @@ class SequenceOf(BaseTestCase): s.setComponentByPosition(1, univ.OctetString('Abc'), verifyConstraints=False) except PyAsn1Error: - assert 0, 'constraint failes with verifyConstraints=True' + assert 0, 'constraint fails with verifyConstraints=False' else: assert 0, 'constraint fails' @@ -1040,22 +1042,14 @@ class SequenceOf(BaseTestCase): else: pass - def testSizeSpec(self): - s = self.s1.clone(sizeSpec=constraint.ConstraintsUnion( + def testConsistency(self): + s = self.s1.clone(subtypeSpec=constraint.ConstraintsUnion( constraint.ValueSizeConstraint(1, 1) )) s.setComponentByPosition(0, univ.OctetString('abc')) - try: - s.verifySizeSpec() - except PyAsn1Error: - assert 0, 'size spec fails' + assert not s.isInconsistent, 'size spec fails' s.setComponentByPosition(1, univ.OctetString('abc')) - try: - s.verifySizeSpec() - except PyAsn1Error: - pass - else: - assert 0, 'size spec fails' + assert s.isInconsistent, 'size spec fails' def testGetComponentTagMap(self): assert self.s1.componentType.tagMap.presentTypes == { @@ -1065,15 +1059,13 @@ class SequenceOf(BaseTestCase): def testSubtype(self): subtype = self.s1.subtype( implicitTag=tag.Tag(tag.tagClassPrivate, tag.tagFormatSimple, 2), - subtypeSpec=constraint.SingleValueConstraint(1, 3), - sizeSpec=constraint.ValueSizeConstraint(0, 1) + subtypeSpec=constraint.ValueSizeConstraint(0, 1) ) subtype.clear() clone = self.s1.clone( tagSet=tag.TagSet(tag.Tag(tag.tagClassPrivate, tag.tagFormatSimple, 2)), - subtypeSpec=constraint.ConstraintsIntersection(constraint.SingleValueConstraint(1, 3)), - sizeSpec=constraint.ValueSizeConstraint(0, 1) + subtypeSpec=constraint.ValueSizeConstraint(0, 1) ) clone.clear() assert clone == subtype @@ -1257,6 +1249,37 @@ class SequenceOf(BaseTestCase): assert not s.isValue + def testIsInconsistentSizeConstraint(self): + + class SequenceOf(univ.SequenceOf): + componentType = univ.OctetString() + subtypeSpec = constraint.ValueSizeConstraint(0, 1) + + s = SequenceOf() + + assert s.isInconsistent + + s[0] = 'test' + + assert not s.isInconsistent + + s[0] = 'test' + s[1] = 'test' + + assert s.isInconsistent + + s.clear() + + assert not s.isInconsistent + + s.reset() + + assert s.isInconsistent + + s[1] = 'test' + + assert not s.isInconsistent + class SequenceOfPicklingTestCase(unittest.TestCase): @@ -1593,6 +1616,77 @@ class Sequence(BaseTestCase): assert not s.isValue + def testIsInconsistentWithComponentsConstraint(self): + + class Sequence(univ.Sequence): + componentType = namedtype.NamedTypes( + namedtype.OptionalNamedType('name', univ.OctetString()), + namedtype.DefaultedNamedType('age', univ.Integer(65)) + ) + subtypeSpec = constraint.WithComponentsConstraint( + ('name', constraint.ComponentPresentConstraint()), + ('age', constraint.ComponentAbsentConstraint()) + ) + + s = Sequence() + + assert s.isInconsistent + + s[0] = 'test' + + assert not s.isInconsistent + + s[0] = 'test' + s[1] = 23 + + assert s.isInconsistent + + s.clear() + + assert s.isInconsistent + + s.reset() + + assert s.isInconsistent + + s[1] = 23 + + assert s.isInconsistent + + def testIsInconsistentSizeConstraint(self): + + class Sequence(univ.Sequence): + componentType = namedtype.NamedTypes( + namedtype.OptionalNamedType('name', univ.OctetString()), + namedtype.DefaultedNamedType('age', univ.Integer(65)) + ) + subtypeSpec = constraint.ValueSizeConstraint(0, 1) + + s = Sequence() + + assert not s.isInconsistent + + s[0] = 'test' + + assert not s.isInconsistent + + s[0] = 'test' + s[1] = 23 + + assert s.isInconsistent + + s.clear() + + assert not s.isInconsistent + + s.reset() + + assert s.isInconsistent + + s[1] = 23 + + assert not s.isInconsistent + class SequenceWithoutSchema(BaseTestCase): |