diff options
-rw-r--r-- | CHANGES.rst | 5 | ||||
-rw-r--r-- | doc/source/docs/api-reference.rst | 1 | ||||
-rw-r--r-- | doc/source/docs/type/opentype/contents.rst | 8 | ||||
-rw-r--r-- | doc/source/docs/type/opentype/opentype.rst | 14 | ||||
-rw-r--r-- | pyasn1/codec/ber/decoder.py | 80 | ||||
-rw-r--r-- | pyasn1/codec/ber/encoder.py | 16 | ||||
-rw-r--r-- | pyasn1/codec/cer/encoder.py | 17 | ||||
-rw-r--r-- | pyasn1/debug.py | 1 | ||||
-rw-r--r-- | pyasn1/type/namedtype.py | 26 | ||||
-rw-r--r-- | pyasn1/type/opentype.py | 76 | ||||
-rw-r--r-- | tests/codec/ber/test_decoder.py | 53 |
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) |