Skip to content

Commit

Permalink
Add SET ... WITH COMPONENTS ... ASN.1 construct support (#171)
Browse files Browse the repository at this point in the history
Added `WithComponentsConstraint` along with related
`ComponentPresentConstraint` and `ComponentAbsentConstraint` classes
to be used with `Sequence`/`Set` types representing
`SET ... WITH COMPONENTS ...` like ASN.1 constructs.
  • Loading branch information
etingof authored Aug 27, 2019
1 parent 7214dca commit d0b7f2e
Show file tree
Hide file tree
Showing 9 changed files with 393 additions and 19 deletions.
4 changes: 4 additions & 0 deletions CHANGES.rst
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,10 @@ Revision 0.4.7, released XX-09-2019
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
-----------------------------------
Expand Down
2 changes: 1 addition & 1 deletion docs/source/pyasn1/type/base/constructedasn1type.rst
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,4 @@
------------

.. autoclass:: pyasn1.type.base.ConstructedAsn1Type(tagSet=TagSet(), subtypeSpec=ConstraintsIntersection(), componentType=None)
:members: isSameTypeWith, isSuperTypeOf, tagSet, effectiveTagSet, tagMap, subtypeSpec, isInconsistent
:members: isSameTypeWith, isSuperTypeOf, tagSet, effectiveTagSet, tagMap, subtypeSpec
1 change: 1 addition & 0 deletions docs/source/pyasn1/type/constraint/contents.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
16 changes: 16 additions & 0 deletions docs/source/pyasn1/type/constraint/withcomponents.rst
Original file line number Diff line number Diff line change
@@ -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:
18 changes: 0 additions & 18 deletions pyasn1/type/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -677,24 +677,6 @@ class takes on instantiation except that all parameters

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')

Expand Down
133 changes: 133 additions & 0 deletions pyasn1/type/constraint.py
Original file line number Diff line number Diff line change
Expand Up @@ -342,6 +342,139 @@ def _testValue(self, value, idx):
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.
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"""
Expand Down
73 changes: 73 additions & 0 deletions pyasn1/type/univ.py
Original file line number Diff line number Diff line change
Expand Up @@ -2042,6 +2042,41 @@ def isValue(self):

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__
Expand Down Expand Up @@ -2637,6 +2672,44 @@ def isValue(self):

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.
Expand Down
63 changes: 63 additions & 0 deletions tests/type/test_constraint.py
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,69 @@ def testBadVal(self):
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)
Expand Down
Loading

0 comments on commit d0b7f2e

Please sign in to comment.