From d0b7f2ec8677eec8f9aa31103a66f5cab18e9308 Mon Sep 17 00:00:00 2001 From: Ilya Etingof Date: Tue, 27 Aug 2019 10:17:42 +0200 Subject: Add `SET ... WITH COMPONENTS ...` ASN.1 construct support (#171) Added `WithComponentsConstraint` along with related `ComponentPresentConstraint` and `ComponentAbsentConstraint` classes to be used with `Sequence`/`Set` types representing `SET ... WITH COMPONENTS ...` like ASN.1 constructs. --- pyasn1/type/base.py | 18 ------- pyasn1/type/constraint.py | 133 ++++++++++++++++++++++++++++++++++++++++++++++ pyasn1/type/univ.py | 73 +++++++++++++++++++++++++ 3 files changed, 206 insertions(+), 18 deletions(-) (limited to 'pyasn1') diff --git a/pyasn1/type/base.py b/pyasn1/type/base.py index 834b76e..994f1c9 100644 --- a/pyasn1/type/base.py +++ b/pyasn1/type/base.py @@ -677,24 +677,6 @@ class ConstructedAsn1Type(Asn1Type): return clone - @property - def isInconsistent(self): - """Run necessary checks to ensure 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 - """ - try: - self.subtypeSpec(self) - - except error.PyAsn1Error: - exc = sys.exc_info()[1] - return exc - def getComponentByPosition(self, idx): raise error.PyAsn1Error('Method not implemented') diff --git a/pyasn1/type/constraint.py b/pyasn1/type/constraint.py index 75db38a..b8aa0af 100644 --- a/pyasn1/type/constraint.py +++ b/pyasn1/type/constraint.py @@ -342,6 +342,139 @@ 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 = ('',) + + 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 = ('',) + + 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. + + 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 = ConstraintsIntersection( + WithComponentsConstraint( + ('id', ComponentPresentConstraint()), + ('name', ComponentAbsentConstraint()) + ), + WithComponentsConstraint( + ('id', ComponentAbsentConstraint()), + ('name', ComponentPresentConstraint()) + ) + ) + + item = Item() + + # This will succeed + item['id'] = 1 + + # This will succeed + item['name'] = 'John' + + # This will fail on encoding + 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""" diff --git a/pyasn1/type/univ.py b/pyasn1/type/univ.py index fbf8ed5..aa688b2 100644 --- a/pyasn1/type/univ.py +++ b/pyasn1/type/univ.py @@ -2042,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__ @@ -2637,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. -- cgit v1.2.1