summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--CHANGES.rst5
-rw-r--r--doc/source/docs/api-reference.rst1
-rw-r--r--doc/source/docs/type/opentype/contents.rst8
-rw-r--r--doc/source/docs/type/opentype/opentype.rst14
-rw-r--r--pyasn1/codec/ber/decoder.py80
-rw-r--r--pyasn1/codec/ber/encoder.py16
-rw-r--r--pyasn1/codec/cer/encoder.py17
-rw-r--r--pyasn1/debug.py1
-rw-r--r--pyasn1/type/namedtype.py26
-rw-r--r--pyasn1/type/opentype.py76
-rw-r--r--tests/codec/ber/test_decoder.py53
11 files changed, 281 insertions, 16 deletions
diff --git a/CHANGES.rst b/CHANGES.rst
index a1d9799..3e65b8f 100644
--- a/CHANGES.rst
+++ b/CHANGES.rst
@@ -1,4 +1,9 @@
+Revision 0.4.1, released XX-09-2017
+-----------------------------------
+
+- ANY DEFINED BY clause support implemented
+
Revision 0.3.5, released 16-09-2017
-----------------------------------
diff --git a/doc/source/docs/api-reference.rst b/doc/source/docs/api-reference.rst
index 0f98527..d80c45a 100644
--- a/doc/source/docs/api-reference.rst
+++ b/doc/source/docs/api-reference.rst
@@ -16,6 +16,7 @@ ASN.1 types
/docs/type/useful/contents
/docs/type/tag/contents
/docs/type/namedtype/contents
+ /docs/type/opentype/contents
/docs/type/namedval/contents
Transformation codecs
diff --git a/doc/source/docs/type/opentype/contents.rst b/doc/source/docs/type/opentype/contents.rst
new file mode 100644
index 0000000..78f5a78
--- /dev/null
+++ b/doc/source/docs/type/opentype/contents.rst
@@ -0,0 +1,8 @@
+
+Untyped fields of constructed types
+-----------------------------------
+
+.. toctree::
+ :maxdepth: 2
+
+ /docs/type/opentype/opentype
diff --git a/doc/source/docs/type/opentype/opentype.rst b/doc/source/docs/type/opentype/opentype.rst
new file mode 100644
index 0000000..2660bd9
--- /dev/null
+++ b/doc/source/docs/type/opentype/opentype.rst
@@ -0,0 +1,14 @@
+
+.. |OpenType| replace:: OpenType
+
+|OpenType|
+-----------
+
+.. autoclass:: pyasn1.type.opentype.OpenType
+ :members:
+
+ .. note::
+
+ The |OpenType| class models an untyped field of a constructed ASN.1
+ type. In ASN.1 syntax it is usually represented by the
+ `ANY DEFINED BY` clause.
diff --git a/pyasn1/codec/ber/decoder.py b/pyasn1/codec/ber/decoder.py
index ee3064f..ddd9a75 100644
--- a/pyasn1/codec/ber/decoder.py
+++ b/pyasn1/codec/ber/decoder.py
@@ -514,6 +514,43 @@ class UniversalConstructedTypeDecoder(AbstractConstructedDecoder):
if namedTypes:
if not namedTypes.requiredComponents.issubset(seenIndices):
raise error.PyAsn1Error('ASN.1 object %s has uninitialized components' % asn1Object.__class__.__name__)
+
+ if namedTypes.hasOpenTypes:
+
+ openTypes = options.get('openTypes', {})
+
+ if openTypes or options.get('decodeOpenTypes', False):
+
+ for idx, namedType in enumerate(namedTypes.namedTypes):
+ if not namedType.openType:
+ continue
+
+ governingValue = asn1Object.getComponentByName(
+ namedType.openType.name
+ )
+
+ try:
+ asn1Spec = openTypes[governingValue]
+
+ except KeyError:
+
+ try:
+ asn1Spec = namedType.openType[governingValue]
+
+ except KeyError:
+ continue
+
+ component, rest = decodeFun(
+ asn1Object.getComponentByPosition(idx).asOctets(),
+ asn1Spec=asn1Spec
+ )
+
+ asn1Object.setComponentByPosition(
+ idx, component,
+ matchTags=False,
+ matchConstraints=False
+ )
+
else:
asn1Object.verifySizeSpec()
@@ -529,8 +566,6 @@ class UniversalConstructedTypeDecoder(AbstractConstructedDecoder):
)
idx += 1
- asn1Object.verifySizeSpec()
-
return asn1Object, tail
def indefLenValueDecoder(self, substrate, asn1Spec,
@@ -611,6 +646,44 @@ class UniversalConstructedTypeDecoder(AbstractConstructedDecoder):
if namedTypes:
if not namedTypes.requiredComponents.issubset(seenIndices):
raise error.PyAsn1Error('ASN.1 object %s has uninitialized components' % asn1Object.__class__.__name__)
+
+ if namedTypes.hasOpenTypes:
+
+ openTypes = options.get('openTypes', None)
+
+ if openTypes or options.get('decodeOpenTypes', False):
+
+ for idx, namedType in enumerate(namedTypes.namedTypes):
+ if not namedType.openType:
+ continue
+
+ governingValue = asn1Object.getComponentByName(
+ namedType.openType.name
+ )
+
+ try:
+ asn1Spec = openTypes[governingValue]
+
+ except KeyError:
+
+ try:
+ asn1Spec = namedType.openType[governingValue]
+
+ except KeyError:
+ continue
+
+ component, rest = decodeFun(
+ asn1Object.getComponentByPosition(idx).asOctets(),
+ asn1Spec=asn1Spec, allowEoo=True
+ )
+
+ if component is not eoo.endOfOctets:
+ asn1Object.setComponentByPosition(
+ idx, component,
+ matchTags=False,
+ matchConstraints=False
+ )
+
else:
asn1Object.verifySizeSpec()
@@ -631,7 +704,6 @@ class UniversalConstructedTypeDecoder(AbstractConstructedDecoder):
raise error.SubstrateUnderrunError(
'No EOO seen before substrate ends'
)
- asn1Object.verifySizeSpec()
return asn1Object, substrate
@@ -1159,8 +1231,10 @@ class Decoder(object):
self, substrateFun,
**options
)
+
if logger:
logger('codec %s yields type %s, value:\n%s\n...remaining substrate is: %s' % (concreteDecoder.__class__.__name__, value.__class__.__name__, isinstance(value, base.Asn1Item) and value.prettyPrint() or value, substrate and debug.hexdump(substrate) or '<none>'))
+
state = stStop
break
if state is stTryAsExplicitTag:
diff --git a/pyasn1/codec/ber/encoder.py b/pyasn1/codec/ber/encoder.py
index 3ac2ef8..2c5ba14 100644
--- a/pyasn1/codec/ber/encoder.py
+++ b/pyasn1/codec/ber/encoder.py
@@ -361,11 +361,21 @@ class SequenceEncoder(AbstractItemEncoder):
while idx > 0:
idx -= 1
if namedTypes:
- if namedTypes[idx].isOptional and not value[idx].isValue:
+ namedType = namedTypes[idx]
+ if namedType.isOptional and not value[idx].isValue:
continue
- if namedTypes[idx].isDefaulted and value[idx] == namedTypes[idx].asn1Object:
+ if namedType.isDefaulted and value[idx] == namedType.asn1Object:
continue
- substrate = encodeFun(value[idx], **options) + substrate
+
+ chunk = encodeFun(value[idx], **options)
+
+ # wrap open type blob if needed
+ if namedTypes and namedType.openType:
+ asn1Spec = namedType.asn1Object
+ if asn1Spec.tagSet and not asn1Spec.isSameTypeWith(value[idx]):
+ chunk = encodeFun(asn1Spec.clone(chunk), **options)
+
+ substrate = chunk + substrate
return substrate, True, True
diff --git a/pyasn1/codec/cer/encoder.py b/pyasn1/codec/cer/encoder.py
index 4700de0..40e7eba 100644
--- a/pyasn1/codec/cer/encoder.py
+++ b/pyasn1/codec/cer/encoder.py
@@ -135,14 +135,23 @@ class SequenceEncoder(encoder.SequenceEncoder):
while idx > 0:
idx -= 1
if namedTypes:
- if namedTypes[idx].isOptional and not value[idx].isValue:
+ namedType = namedTypes[idx]
+ if namedType.isOptional and not value[idx].isValue:
continue
- if namedTypes[idx].isDefaulted and value[idx] == namedTypes[idx].asn1Object:
+ if namedType.isDefaulted and value[idx] == namedType.asn1Object:
continue
- options.update(ifNotEmpty=namedTypes and namedTypes[idx].isOptional)
+ options.update(ifNotEmpty=namedTypes and namedType.isOptional)
- substrate = encodeFun(value[idx], **options) + substrate
+ chunk = encodeFun(value[idx], **options)
+
+ # wrap open type blob if needed
+ if namedTypes and namedType.openType:
+ asn1Spec = namedType.asn1Object
+ if asn1Spec.tagSet and not asn1Spec.isSameTypeWith(value[idx]):
+ chunk = encodeFun(asn1Spec.clone(chunk), **options)
+
+ substrate = chunk + substrate
return substrate, True, True
diff --git a/pyasn1/debug.py b/pyasn1/debug.py
index 24ac5ce..874a03d 100644
--- a/pyasn1/debug.py
+++ b/pyasn1/debug.py
@@ -17,6 +17,7 @@ flagDecoder = 0x0002
flagAll = 0xffff
flagMap = {
+ 'none': flagNone,
'encoder': flagEncoder,
'decoder': flagDecoder,
'all': flagAll
diff --git a/pyasn1/type/namedtype.py b/pyasn1/type/namedtype.py
index 7a51f18..edcf3ce 100644
--- a/pyasn1/type/namedtype.py
+++ b/pyasn1/type/namedtype.py
@@ -10,6 +10,12 @@ from pyasn1 import error
__all__ = ['NamedType', 'OptionalNamedType', 'DefaultedNamedType', 'NamedTypes']
+try:
+ any
+
+except AttributeError:
+ any = lambda x: bool(filter(bool, x))
+
class NamedType(object):
"""Create named field object for a constructed ASN.1 type.
@@ -30,10 +36,11 @@ class NamedType(object):
isOptional = False
isDefaulted = False
- def __init__(self, name, asn1Object):
+ def __init__(self, name, asn1Object, openType=None):
self.__name = name
self.__type = asn1Object
self.__nameAndType = name, asn1Object
+ self.__openType = openType
def __repr__(self):
return '%s(%r, %r)' % (self.__class__.__name__, self.__name, self.__type)
@@ -73,6 +80,10 @@ class NamedType(object):
def asn1Object(self):
return self.__type
+ @property
+ def openType(self):
+ return self.__openType
+
# Backward compatibility
def getName(self):
@@ -115,8 +126,11 @@ class NamedTypes(object):
self.__ambiguousTypes = 'terminal' not in kwargs and self.__computeAmbiguousTypes() or {}
self.__uniqueTagMap = self.__computeTagMaps(unique=True)
self.__nonUniqueTagMap = self.__computeTagMaps(unique=False)
- self.__hasOptionalOrDefault = bool([True for namedType in self.__namedTypes
- if namedType.isDefaulted or namedType.isOptional])
+ self.__hasOptionalOrDefault = any([True for namedType in self.__namedTypes
+ if namedType.isDefaulted or namedType.isOptional])
+ self.__hasOpenTypes = any([True for namedType in self.__namedTypes
+ if namedType.openType])
+
self.__requiredComponents = frozenset(
[idx for idx, nt in enumerate(self.__namedTypes) if not nt.isOptional and not nt.isDefaulted]
)
@@ -503,8 +517,12 @@ class NamedTypes(object):
return self.__hasOptionalOrDefault
@property
+ def hasOpenTypes(self):
+ return self.__hasOpenTypes
+
+ @property
def namedTypes(self):
- return iter(self.__namedTypes)
+ return tuple(self.__namedTypes)
@property
def requiredComponents(self):
diff --git a/pyasn1/type/opentype.py b/pyasn1/type/opentype.py
new file mode 100644
index 0000000..76abe7b
--- /dev/null
+++ b/pyasn1/type/opentype.py
@@ -0,0 +1,76 @@
+#
+# This file is part of pyasn1 software.
+#
+# Copyright (c) 2005-2017, Ilya Etingof <etingof@gmail.com>
+# License: http://pyasn1.sf.net/license.html
+#
+
+__all__ = ['OpenType']
+
+
+class OpenType(object):
+ """Create ASN.1 type map indexed by a value
+
+ The *DefinedBy* object models the ASN.1 *DEFINED BY* clause which maps
+ values to ASN.1 types in the context of the ASN.1 SEQUENCE/SET type.
+
+ OpenType objects are duck-type a read-only Python :class:`dict` objects,
+ however the passed `typeMap` is stored by reference.
+
+ Parameters
+ ----------
+ name: :py:class:`str`
+ Field name
+
+ typeMap: :py:class:`dict`:
+ A map of value->ASN.1 type. It's stored by reference and can be
+ mutated later to register new mappings.
+
+ Examples
+ --------
+
+ .. code-block::
+
+ openType = OpenType(
+ 'id',
+ {1: Integer(),
+ 2: OctetString()}
+ )
+ Sequence(
+ componentType=NamedTypes(
+ NamedType('id', Integer()),
+ NamedType('blob', Any(), openType=openType)
+ )
+ )
+ """
+
+ def __init__(self, name, typeMap=None):
+ self.__name = name
+ if typeMap is None:
+ self.__typeMap = {}
+ else:
+ self.__typeMap = typeMap
+
+ @property
+ def name(self):
+ return self.__name
+
+ # Python dict protocol
+
+ def values(self):
+ return self.__typeMap.values()
+
+ def keys(self):
+ return self.__typeMap.keys()
+
+ def items(self):
+ return self.__typeMap.items()
+
+ def __contains__(self, key):
+ return key in self.__typeMap
+
+ def __getitem__(self, key):
+ return self.__typeMap[key]
+
+ def __iter__(self):
+ return iter(self.__typeMap)
diff --git a/tests/codec/ber/test_decoder.py b/tests/codec/ber/test_decoder.py
index 5ec3a5f..cd7262e 100644
--- a/tests/codec/ber/test_decoder.py
+++ b/tests/codec/ber/test_decoder.py
@@ -12,7 +12,7 @@ except ImportError:
from tests.base import BaseTestCase
-from pyasn1.type import tag, namedtype, univ, char
+from pyasn1.type import tag, namedtype, opentype, univ, char
from pyasn1.codec.ber import decoder, eoo
from pyasn1.compat.octets import ints2octs, str2octs, null
from pyasn1.error import PyAsn1Error
@@ -728,7 +728,7 @@ class SequenceDecoderWithSchemaTestCase(BaseTestCase):
def testDefMode(self):
self.__init()
assert decoder.decode(
- ints2octs((48, 128, 5, 0, 0, 0)), asn1Spec=self.s
+ ints2octs((48, 2, 5, 0)), asn1Spec=self.s
) == (self.s, null)
def testIndefMode(self):
@@ -831,6 +831,55 @@ class SequenceDecoderWithSchemaTestCase(BaseTestCase):
) == (self.s, null)
+class SequenceDecoderWithIntegerOpenTypesTestCase(BaseTestCase):
+ def setUp(self):
+ openType = opentype.OpenType(
+ 'id',
+ {1: univ.Integer(),
+ 2: univ.OctetString()}
+ )
+ self.s = univ.Sequence(
+ componentType=namedtype.NamedTypes(
+ namedtype.NamedType('id', univ.Integer()),
+ namedtype.NamedType('blob', univ.Any(), openType=openType)
+ )
+ )
+
+ def testDecodeOpenTypesChoiceOne(self):
+ s, r = decoder.decode(
+ ints2octs((48, 6, 2, 1, 1, 2, 1, 12)), asn1Spec=self.s,
+ decodeOpenTypes=True
+ )
+ assert not r
+ assert s[0] == 1
+ assert s[1] == 12
+
+ def testDecodeOpenTypesChoiceTwo(self):
+ s, r = decoder.decode(
+ ints2octs((48, 16, 2, 1, 2, 4, 11, 113, 117, 105, 99, 107, 32, 98, 114, 111, 119, 110)), asn1Spec=self.s,
+ decodeOpenTypes = True
+ )
+ assert not r
+ assert s[0] == 2
+ assert s[1] == univ.OctetString('quick brown')
+
+ def testDontDecodeOpenTypesChoiceOne(self):
+ s, r = decoder.decode(
+ ints2octs((48, 6, 2, 1, 1, 2, 1, 12)), asn1Spec=self.s
+ )
+ assert not r
+ assert s[0] == 1
+ assert s[1] == ints2octs((2, 1, 12))
+
+ def testDontDecodeOpenTypesChoiceTwo(self):
+ s, r = decoder.decode(
+ ints2octs((48, 16, 2, 1, 2, 4, 11, 113, 117, 105, 99, 107, 32, 98, 114, 111, 119, 110)), asn1Spec=self.s
+ )
+ assert not r
+ assert s[0] == 2
+ assert s[1] == ints2octs((4, 11, 113, 117, 105, 99, 107, 32, 98, 114, 111, 119, 110))
+
+
class SetDecoderTestCase(BaseTestCase):
def setUp(self):
BaseTestCase.setUp(self)