From 25cf116ef8d11bb0e08454c0f3635c9f4002c2d6 Mon Sep 17 00:00:00 2001 From: Ilya Etingof Date: Sat, 29 Dec 2018 23:43:33 +0100 Subject: Prepare for 0.4.6 --- .travis.yml | 10 ---------- CHANGES.rst | 5 +++++ pyasn1/__init__.py | 2 +- 3 files changed, 6 insertions(+), 11 deletions(-) diff --git a/.travis.yml b/.travis.yml index 3152686..4ca7b33 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,31 +4,24 @@ matrix: include: - os: linux dist: trusty - sudo: false python: '2.6' - os: linux dist: trusty - sudo: false python: '2.7' - os: linux dist: trusty - sudo: false python: '3.2' - os: linux dist: trusty - sudo: false python: '3.3' - os: linux dist: trusty - sudo: false python: '3.4' - os: linux dist: trusty - sudo: false python: '3.5' - os: linux dist: trusty - sudo: false python: '3.6' - os: linux dist: xenial @@ -36,15 +29,12 @@ matrix: python: '3.7' - os: linux dist: trusty - sudo: true python: 'nightly' - os: linux dist: trusty - sudo: false python: 'pypy' - os: linux dist: trusty - sudo: false python: 'pypy3' install: - pip install codecov diff --git a/CHANGES.rst b/CHANGES.rst index c4084f3..dabdb33 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -1,4 +1,9 @@ +Revision 0.4.6, released XX-01-2019 +----------------------------------- + +No changes yet + Revision 0.4.5, released 29-12-2018 ----------------------------------- diff --git a/pyasn1/__init__.py b/pyasn1/__init__.py index 68db4f1..d780df2 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.5' +__version__ = '0.4.6' if sys.version_info[:2] < (2, 4): raise RuntimeError('PyASN1 requires Python 2.4 or later') -- cgit v1.2.3 From b028644dea4244f1cd2513ab0241c8cb9be43324 Mon Sep 17 00:00:00 2001 From: Harrison Date: Thu, 20 Jun 2019 14:43:25 -0400 Subject: Remove invalid escape sequences to get rid of DeprecationWarning in Python3. (#161) --- pyasn1/type/constraint.py | 10 +++++----- pyasn1/type/namedval.py | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/pyasn1/type/constraint.py b/pyasn1/type/constraint.py index 9d8883d..656e203 100644 --- a/pyasn1/type/constraint.py +++ b/pyasn1/type/constraint.py @@ -107,7 +107,7 @@ class SingleValueConstraint(AbstractConstraint): Parameters ---------- - \*values: :class:`int` + *values: :class:`int` Full set of values permitted by this constraint object. Examples @@ -149,7 +149,7 @@ class ContainedSubtypeConstraint(AbstractConstraint): Parameters ---------- - \*values: + *values: Full set of values and constraint objects permitted by this constraint object. @@ -310,7 +310,7 @@ class PermittedAlphabetConstraint(SingleValueConstraint): Parameters ---------- - \*alphabet: :class:`str` + *alphabet: :class:`str` Full set of characters permitted by this constraint object. Examples @@ -467,7 +467,7 @@ class ConstraintsIntersection(AbstractConstraintSet): Parameters ---------- - \*constraints: + *constraints: Constraint or logic operator objects. Examples @@ -511,7 +511,7 @@ class ConstraintsUnion(AbstractConstraintSet): Parameters ---------- - \*constraints: + *constraints: Constraint or logic operator objects. Examples diff --git a/pyasn1/type/namedval.py b/pyasn1/type/namedval.py index 2233aaf..40468d4 100644 --- a/pyasn1/type/namedval.py +++ b/pyasn1/type/namedval.py @@ -23,7 +23,7 @@ class NamedValues(object): Parameters ---------- - \*args: variable number of two-element :py:class:`tuple` + *args: variable number of two-element :py:class:`tuple` name: :py:class:`str` Value label -- cgit v1.2.3 From 66d329acaaf204eff63ae595fd7d6f56cd530c72 Mon Sep 17 00:00:00 2001 From: Ilya Etingof Date: Sun, 23 Jun 2019 19:48:31 +0200 Subject: SequenceOf/SetOf to remain a schema objects (#162) * Add `omitEmptyOptionals` encoder option Added `omitEmptyOptionals` option which is respected by `Sequence` and `Set` encoders. When `omitEmptyOptionals` is set to `True`, empty initialized optional components are not encoded. Default is `False`. * Change `SequenceOf`/`SetOf` behaviour - New elements to `SequenceOf`/`SetOf` objects can now be added at any position - the requirement for the new elements to reside at the end of the existing ones (i.e. s[len(s)] = 123) is removed. - Removed default initializer from `SequenceOf`/`SetOf` types to ensure consistent behaviour with the rest of ASN.1 types. Before this change, `SequenceOf`/`SetOf` instances immediately become value objects behaving like an empty list. With this change, `SequenceOf`/`SetOf` objects remain schema objects unless a component is added or `.clear()` is called. - Added `.reset()` method to all constructed types to turn value object into a schema object. --- CHANGES.rst | 27 ++++++- pyasn1/codec/ber/decoder.py | 4 + pyasn1/codec/ber/encoder.py | 25 ++++-- pyasn1/type/base.py | 31 ++++---- pyasn1/type/univ.py | 171 +++++++++++++++++++++++++++++++--------- tests/codec/ber/test_encoder.py | 2 + tests/codec/cer/test_encoder.py | 2 + tests/type/test_univ.py | 170 +++++++++++++++++++++++++++++++++------ 8 files changed, 346 insertions(+), 86 deletions(-) diff --git a/CHANGES.rst b/CHANGES.rst index dabdb33..19e7bd9 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -1,8 +1,31 @@ -Revision 0.4.6, released XX-01-2019 +Revision 0.4.6, released XX-06-2019 ----------------------------------- -No changes yet +- Added `omitEmptyOptionals` option which is respected by `Sequence` + and `Set` encoders. When `omitEmptyOptionals` is set to `True`, empty + initialized optional components are not encoded. Default is `False`. +- New elements to `SequenceOf`/`SetOf` objects can now be added at any + position - the requirement for the new elements to reside at the end + of the existing ones (i.e. s[len(s)] = 123) is removed. +- Removed default initializer from `SequenceOf`/`SetOf` types to ensure + consistent behaviour with the rest of ASN.1 types. Before this change, + `SequenceOf`/`SetOf` instances immediately become value objects behaving + like an empty list. With this change, `SequenceOf`/`SetOf` objects + remain schema objects unless a component is added or `.clear()` is + called. + This change can potentially cause incompatibilities with existing + pyasn1 objects which assume `SequenceOf`/`SetOf` instances are value + objects right upon instantiation. + The behaviour of `Sequence`/`Set` types depends on the `componentType` + initializer: if on `componentType` is given, the behaviour is the + same as `SequenceOf`/`SetOf` have. IF `componentType` is given, but + neither optional nor defaulted components are present, the created + instance remains schema object, If, however, either optional or + defaulted component isi present, the created instance immediately + becomes a value object. +- Added `.reset()` method to all constructed types to turn value object + into a schema object. Revision 0.4.5, released 29-12-2018 ----------------------------------- diff --git a/pyasn1/codec/ber/decoder.py b/pyasn1/codec/ber/decoder.py index 591bbc4..2e4afbb 100644 --- a/pyasn1/codec/ber/decoder.py +++ b/pyasn1/codec/ber/decoder.py @@ -567,6 +567,7 @@ class UniversalConstructedTypeDecoder(AbstractConstructedDecoder): return asn1Object, tail asn1Object = asn1Spec.clone() + asn1Object.clear() if asn1Spec.typeId in (univ.Sequence.typeId, univ.Set.typeId): @@ -682,6 +683,7 @@ class UniversalConstructedTypeDecoder(AbstractConstructedDecoder): else: asn1Object = asn1Spec.clone() + asn1Object.clear() componentType = asn1Spec.componentType @@ -727,6 +729,7 @@ class UniversalConstructedTypeDecoder(AbstractConstructedDecoder): ) asn1Object = asn1Spec.clone() + asn1Object.clear() if asn1Spec.typeId in (univ.Sequence.typeId, univ.Set.typeId): @@ -847,6 +850,7 @@ class UniversalConstructedTypeDecoder(AbstractConstructedDecoder): else: asn1Object = asn1Spec.clone() + asn1Object.clear() componentType = asn1Spec.componentType diff --git a/pyasn1/codec/ber/encoder.py b/pyasn1/codec/ber/encoder.py index 65b8514..325ed46 100644 --- a/pyasn1/codec/ber/encoder.py +++ b/pyasn1/codec/ber/encoder.py @@ -4,6 +4,8 @@ # Copyright (c) 2005-2019, Ilya Etingof # License: http://snmplabs.com/pyasn1/license.html # +import sys + from pyasn1 import debug from pyasn1 import error from pyasn1.codec.ber import eoo @@ -93,9 +95,15 @@ class AbstractItemEncoder(object): # base tag? if not idx: - substrate, isConstructed, isOctets = self.encodeValue( - value, asn1Spec, encodeFun, **options - ) + try: + substrate, isConstructed, isOctets = self.encodeValue( + value, asn1Spec, encodeFun, **options + ) + + except error.PyAsn1Error: + exc = sys.exc_info() + raise error.PyAsn1Error( + 'Error encoding %r: %s' % (value, exc[1])) if LOG: LOG('encoded %svalue %s into %s' % ( @@ -518,6 +526,13 @@ class SequenceEncoder(AbstractItemEncoder): substrate = null + omitEmptyOptionals = options.get( + 'omitEmptyOptionals', self.omitEmptyOptionals) + + if LOG: + LOG('%sencoding empty OPTIONAL components' % ( + omitEmptyOptionals and 'not ' or '')) + if asn1Spec is None: # instance of ASN.1 schema value.verifySizeSpec() @@ -538,7 +553,7 @@ class SequenceEncoder(AbstractItemEncoder): LOG('not encoding DEFAULT component %r' % (namedType,)) continue - if self.omitEmptyOptionals: + if omitEmptyOptionals: options.update(ifNotEmpty=namedType.isOptional) chunk = encodeFun(component, asn1Spec, **options) @@ -575,7 +590,7 @@ class SequenceEncoder(AbstractItemEncoder): LOG('not encoding DEFAULT component %r' % (namedType,)) continue - if self.omitEmptyOptionals: + if omitEmptyOptionals: options.update(ifNotEmpty=namedType.isOptional) chunk = encodeFun(component, asn1Spec[idx], **options) diff --git a/pyasn1/type/base.py b/pyasn1/type/base.py index 7995118..b231482 100644 --- a/pyasn1/type/base.py +++ b/pyasn1/type/base.py @@ -467,8 +467,6 @@ class AbstractConstructedAsn1Item(Asn1ItemBase): Asn1ItemBase.__init__(self, **readOnly) - self._componentValues = [] - def __repr__(self): representation = '%s %s object at 0x%x' % ( self.__class__.__name__, self.isValue and 'value' or 'schema', id(self) @@ -478,38 +476,40 @@ class AbstractConstructedAsn1Item(Asn1ItemBase): if value is not noValue: representation += ' %s=%r' % (attr, value) - if self.isValue and self._componentValues: - representation += ' payload [%s]' % ', '.join([repr(x) for x in self._componentValues]) + if self.isValue and self.components: + representation += ' payload [%s]' % ', '.join( + [repr(x) for x in self.components]) return '<%s>' % representation def __eq__(self, other): - return self is other and True or self._componentValues == other + return self is other or self.components == other def __ne__(self, other): - return self._componentValues != other + return self.components != other def __lt__(self, other): - return self._componentValues < other + return self.components < other def __le__(self, other): - return self._componentValues <= other + return self.components <= other def __gt__(self, other): - return self._componentValues > other + return self.components > other def __ge__(self, other): - return self._componentValues >= other + return self.components >= other if sys.version_info[0] <= 2: def __nonzero__(self): - return self._componentValues and True or False + return bool(self.components) else: def __bool__(self): - return self._componentValues and True or False + return bool(self.components) - def __len__(self): - return len(self._componentValues) + @property + def components(self): + raise error.PyAsn1Error('Method not implemented') def _cloneComponentValues(self, myClone, cloneValueFlag): pass @@ -631,9 +631,6 @@ class AbstractConstructedAsn1Item(Asn1ItemBase): self[k] = kwargs[k] return self - def clear(self): - self._componentValues = [] - # backward compatibility def setDefaultComponents(self): diff --git a/pyasn1/type/univ.py b/pyasn1/type/univ.py index 7fab69f..4c8c8e4 100644 --- a/pyasn1/type/univ.py +++ b/pyasn1/type/univ.py @@ -1610,6 +1610,8 @@ class SequenceOfAndSetOfBase(base.AbstractConstructedAsn1Item): raise error.PyAsn1Error('Conflicting positional and keyword params!') kwargs['componentType'] = value + self._componentValues = noValue + base.AbstractConstructedAsn1Item.__init__(self, **kwargs) # Python list protocol @@ -1628,24 +1630,33 @@ class SequenceOfAndSetOfBase(base.AbstractConstructedAsn1Item): except error.PyAsn1Error: raise IndexError(sys.exc_info()[1]) - def clear(self): - self._componentValues = [] - def append(self, value): - self[len(self)] = value + if self._componentValues is noValue: + pos = 0 + + else: + pos = len(self._componentValues) + + self[pos] = value def count(self, value): - return self._componentValues.count(value) + return list(self._componentValues.values()).count(value) def extend(self, values): for value in values: self.append(value) + if self._componentValues is noValue: + self._componentValues = {} + def index(self, value, start=0, stop=None): if stop is None: stop = len(self) + + indices, values = zip(*self._componentValues.items()) + try: - return self._componentValues.index(value, start, stop) + return indices[values.index(value, start, stop)] except error.PyAsn1Error: raise ValueError(sys.exc_info()[1]) @@ -1654,13 +1665,22 @@ class SequenceOfAndSetOfBase(base.AbstractConstructedAsn1Item): self._componentValues.reverse() def sort(self, key=None, reverse=False): - self._componentValues.sort(key=key, reverse=reverse) + self._componentValues = dict( + enumerate(sorted(self._componentValues.values(), + key=key, reverse=reverse))) + + def __len__(self): + if not self._componentValues: + return 0 + + return max(self._componentValues) + 1 def __iter__(self): - return iter(self._componentValues) + for idx in range(0, len(self)): + yield self.getComponentByPosition(idx) def _cloneComponentValues(self, myClone, cloneValueFlag): - for idx, componentValue in enumerate(self._componentValues): + for idx, componentValue in self._componentValues.items(): if componentValue is not noValue: if isinstance(componentValue, base.AbstractConstructedAsn1Item): myClone.setComponentByPosition( @@ -1738,7 +1758,7 @@ class SequenceOfAndSetOfBase(base.AbstractConstructedAsn1Item): try: componentValue = self._componentValues[idx] - except IndexError: + except (KeyError, error.PyAsn1Error): if not instantiate: return default @@ -1792,35 +1812,55 @@ class SequenceOfAndSetOfBase(base.AbstractConstructedAsn1Item): IndexError: When idx > len(self) """ + if idx < 0: + raise error.PyAsn1Error( + 'SequenceOf/SetOf index must not be negative') + componentType = self.componentType - try: - currentValue = self._componentValues[idx] - except IndexError: - currentValue = noValue + if self._componentValues is noValue: + componentValues = {} - if len(self._componentValues) < idx: - raise error.PyAsn1Error('Component index out of range') + else: + componentValues = self._componentValues + + currentValue = componentValues.get(idx, noValue) if value is noValue: if componentType is not None: value = componentType.clone() + elif currentValue is noValue: raise error.PyAsn1Error('Component type not defined') + elif not isinstance(value, base.Asn1Item): - if componentType is not None and isinstance(componentType, base.AbstractSimpleAsn1Item): + if (componentType is not None and + isinstance(componentType, base.AbstractSimpleAsn1Item)): value = componentType.clone(value=value) - elif currentValue is not noValue and isinstance(currentValue, base.AbstractSimpleAsn1Item): + + elif (currentValue is not noValue and + isinstance(currentValue, base.AbstractSimpleAsn1Item)): value = currentValue.clone(value=value) + else: - raise error.PyAsn1Error('Non-ASN.1 value %r and undefined component type at %r' % (value, self)) + raise error.PyAsn1Error( + 'Non-ASN.1 value %r and undefined component' + ' type at %r' % (value, self)) + elif componentType is not None: if self.strictConstraints: - if not componentType.isSameTypeWith(value, matchTags, matchConstraints): - raise error.PyAsn1Error('Component value is tag-incompatible: %r vs %r' % (value, componentType)) + if not componentType.isSameTypeWith( + value, matchTags, matchConstraints): + raise error.PyAsn1Error( + '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 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: @@ -1830,10 +1870,9 @@ class SequenceOfAndSetOfBase(base.AbstractConstructedAsn1Item): exType, exValue, exTb = sys.exc_info() raise exType('%s at %s' % (exValue, self.__class__.__name__)) - if currentValue is noValue: - self._componentValues.append(value) - else: - self._componentValues[idx] = value + componentValues[idx] = value + + self._componentValues = componentValues return self @@ -1842,16 +1881,34 @@ class SequenceOfAndSetOfBase(base.AbstractConstructedAsn1Item): if self.componentType is not None: return self.componentType.tagMap + @property + def components(self): + return [self._componentValues[idx] + for idx in sorted(self._componentValues)] + + def clear(self): + self._componentValues = {} + return self + + def reset(self): + self._componentValues = noValue + return self + def prettyPrint(self, scope=0): scope += 1 representation = self.__class__.__name__ + ':\n' - for idx, componentValue in enumerate(self._componentValues): + + if not self.isValue: + return representation + + for idx, componentValue in enumerate(self): representation += ' ' * scope if (componentValue is noValue and self.componentType is not None): representation += '' else: representation += componentValue.prettyPrint(scope) + return representation def prettyPrintType(self, scope=0): @@ -1890,7 +1947,13 @@ class SequenceOfAndSetOfBase(base.AbstractConstructedAsn1Item): The PyASN1 value objects can **additionally** participate in many operations involving regular Python objects (e.g. arithmetic, comprehension etc). """ - for componentValue in self._componentValues: + if self._componentValues is noValue: + return False + + if len(self._componentValues) != len(self): + return False + + for componentValue in self._componentValues.values(): if componentValue is noValue or not componentValue.isValue: return False @@ -2044,6 +2107,10 @@ class SequenceAndSetBase(base.AbstractConstructedAsn1Item): def __init__(self, **kwargs): base.AbstractConstructedAsn1Item.__init__(self, **kwargs) self._componentTypeLen = len(self.componentType) + if self._componentTypeLen: + self._componentValues = [] + else: + self._componentValues = noValue self._dynamicNames = self._componentTypeLen or self.DynamicNames() def __getitem__(self, idx): @@ -2086,6 +2153,9 @@ class SequenceAndSetBase(base.AbstractConstructedAsn1Item): else: return key in self._dynamicNames + def __len__(self): + return len(self._componentValues) + def __iter__(self): return iter(self.componentType or self._dynamicNames) @@ -2114,8 +2184,21 @@ class SequenceAndSetBase(base.AbstractConstructedAsn1Item): def clear(self): self._componentValues = [] self._dynamicNames = self.DynamicNames() + return self + + def reset(self): + self._componentValues = noValue + self._dynamicNames = self.DynamicNames() + return self + + @property + def components(self): + return self._componentValues def _cloneComponentValues(self, myClone, cloneValueFlag): + if self._componentValues is noValue: + return + for idx, componentValue in enumerate(self._componentValues): if componentValue is not noValue: if isinstance(componentValue, base.AbstractConstructedAsn1Item): @@ -2275,7 +2358,11 @@ class SequenceAndSetBase(base.AbstractConstructedAsn1Item): s.getComponentByPosition(0, instantiate=False) """ try: - componentValue = self._componentValues[idx] + if self._componentValues is noValue: + componentValue = noValue + + else: + componentValue = self._componentValues[idx] except IndexError: componentValue = noValue @@ -2334,8 +2421,14 @@ class SequenceAndSetBase(base.AbstractConstructedAsn1Item): componentType = self.componentType componentTypeLen = self._componentTypeLen + if self._componentValues is noValue: + componentValues = [] + + else: + componentValues = self._componentValues + try: - currentValue = self._componentValues[idx] + currentValue = componentValues[idx] except IndexError: currentValue = noValue @@ -2343,7 +2436,7 @@ class SequenceAndSetBase(base.AbstractConstructedAsn1Item): if componentTypeLen < idx: raise error.PyAsn1Error('component index out of range') - self._componentValues = [noValue] * componentTypeLen + componentValues = [noValue] * componentTypeLen if value is noValue: if componentTypeLen: @@ -2389,15 +2482,17 @@ class SequenceAndSetBase(base.AbstractConstructedAsn1Item): raise exType('%s at %s' % (exValue, self.__class__.__name__)) if componentTypeLen or idx in self._dynamicNames: - self._componentValues[idx] = value + componentValues[idx] = value - elif len(self._componentValues) == idx: - self._componentValues.append(value) + elif len(componentValues) == idx: + componentValues.append(value) self._dynamicNames.addField(idx) else: raise error.PyAsn1Error('Component index out of range') + self._componentValues = componentValues + return self @property @@ -2427,6 +2522,9 @@ class SequenceAndSetBase(base.AbstractConstructedAsn1Item): The PyASN1 value objects can **additionally** participate in many operations involving regular Python objects (e.g. arithmetic, comprehension etc). """ + if self._componentValues is noValue: + return False + componentType = self.componentType if componentType: @@ -2497,7 +2595,6 @@ class SequenceAndSetBase(base.AbstractConstructedAsn1Item): if self._componentTypeLen: return self.componentType[idx].name - class Sequence(SequenceAndSetBase): __doc__ = SequenceAndSetBase.__doc__ @@ -2959,7 +3056,7 @@ class Choice(Set): def clear(self): self._currentIdx = None - Set.clear(self) + return Set.clear(self) # compatibility stubs diff --git a/tests/codec/ber/test_encoder.py b/tests/codec/ber/test_encoder.py index 26819bd..da14830 100644 --- a/tests/codec/ber/test_encoder.py +++ b/tests/codec/ber/test_encoder.py @@ -476,6 +476,7 @@ class UTF8StringEncoderWithSchemaTestCase(BaseTestCase): class SequenceOfEncoderTestCase(BaseTestCase): def testEmpty(self): s = univ.SequenceOf() + s.clear() assert encoder.encode(s) == ints2octs((48, 0)) def testDefMode(self): @@ -570,6 +571,7 @@ class SequenceOfEncoderWithComponentsSchemaTestCase(BaseTestCase): class SetOfEncoderTestCase(BaseTestCase): def testEmpty(self): s = univ.SetOf() + s.clear() assert encoder.encode(s) == ints2octs((49, 0)) def testDefMode(self): diff --git a/tests/codec/cer/test_encoder.py b/tests/codec/cer/test_encoder.py index d9d9212..ea8f813 100644 --- a/tests/codec/cer/test_encoder.py +++ b/tests/codec/cer/test_encoder.py @@ -155,6 +155,7 @@ class UTCTimeEncoderTestCase(BaseTestCase): class SequenceOfEncoderTestCase(BaseTestCase): def testEmpty(self): s = univ.SequenceOf() + s.clear() assert encoder.encode(s) == ints2octs((48, 128, 0, 0)) def testDefMode1(self): @@ -219,6 +220,7 @@ class SequenceOfEncoderWithSchemaTestCase(BaseTestCase): class SetOfEncoderTestCase(BaseTestCase): def testEmpty(self): s = univ.SetOf() + s.clear() assert encoder.encode(s) == ints2octs((49, 128, 0, 0)) def testDefMode1(self): diff --git a/tests/type/test_univ.py b/tests/type/test_univ.py index a44f82a..3cd125b 100644 --- a/tests/type/test_univ.py +++ b/tests/type/test_univ.py @@ -1027,21 +1027,25 @@ class SequenceOf(BaseTestCase): } def testSubtype(self): - self.s1.clear() - assert self.s1.subtype( + subtype = self.s1.subtype( implicitTag=tag.Tag(tag.tagClassPrivate, tag.tagFormatSimple, 2), subtypeSpec=constraint.SingleValueConstraint(1, 3), sizeSpec=constraint.ValueSizeConstraint(0, 1) - ) == self.s1.clone( + ) + 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) ) + clone.clear() + assert clone == subtype def testClone(self): self.s1.setComponentByPosition(0, univ.OctetString('abc')) s = self.s1.clone() + s.clear() assert len(s) == 0 s = self.s1.clone(cloneValueFlag=1) assert len(s) == 1 @@ -1056,31 +1060,15 @@ class SequenceOf(BaseTestCase): s.append('xxx') assert s[0] - try: - s[2] - - except IndexError: - pass - - else: - assert False, 'IndexError not raised' - # this is a deviation from standard sequence protocol - assert not s[1] + assert not s[2] def testSetItem(self): s = self.s1.clone() s.append('xxx') - - try: - - s[2] = 'xxx' - - except IndexError: - pass - - else: - assert False, 'IndexError not raised' + s[2] = 'yyy' + assert len(s) == 3 + assert s[1] == str2octs('') def testAppend(self): self.s1.clear() @@ -1132,6 +1120,15 @@ class SequenceOf(BaseTestCase): assert len(s) == 1 assert s == [str2octs('abc')] + def testUntyped(self): + n = univ.SequenceOf() + + assert not n.isValue + + n[0] = univ.OctetString('fox') + + assert n.isValue + def testLegacyInitializer(self): n = univ.SequenceOf( componentType=univ.OctetString() @@ -1174,6 +1171,39 @@ class SequenceOf(BaseTestCase): s.clear() assert s.getComponentByPosition(0, instantiate=False) is univ.noValue + def testClear(self): + + class SequenceOf(univ.SequenceOf): + componentType = univ.OctetString() + + s = SequenceOf() + s.setComponentByPosition(0, 'test') + + assert s.getComponentByPosition(0) == str2octs('test') + assert len(s) == 1 + assert s.isValue + + s.clear() + + assert len(s) == 0 + assert s == [] + assert s.isValue + + def testReset(self): + + class SequenceOf(univ.SequenceOf): + componentType = univ.OctetString() + + s = SequenceOf() + s.setComponentByPosition(0, 'test') + + assert s.getComponentByPosition(0) == str2octs('test') + assert s.isValue + + s.reset() + + assert not s.isValue + class SequenceOfPicklingTestCase(unittest.TestCase): @@ -1441,6 +1471,75 @@ class Sequence(BaseTestCase): s.clear() assert s.getComponentByPosition(1, instantiate=False) is univ.noValue + def testSchemaWithComponents(self): + + class Sequence(univ.Sequence): + componentType = namedtype.NamedTypes( + namedtype.NamedType('name', univ.OctetString()) + ) + + s = Sequence() + + assert not s.isValue + + s[0] = 'test' + + assert s.isValue + + s.clear() + + assert not s.isValue + + s.reset() + + assert not s.isValue + + def testSchemaWithOptionalComponents(self): + + class Sequence(univ.Sequence): + componentType = namedtype.NamedTypes( + namedtype.OptionalNamedType('name', univ.OctetString()) + ) + + s = Sequence() + + assert s.isValue + + s[0] = 'test' + + assert s.isValue + + s.clear() + + assert s.isValue + + s.reset() + + assert not s.isValue + + def testSchemaWithOptionalComponents(self): + + class Sequence(univ.Sequence): + componentType = namedtype.NamedTypes( + namedtype.DefaultedNamedType('name', univ.OctetString('')) + ) + + s = Sequence() + + assert s.isValue + + s[0] = 'test' + + assert s.isValue + + s.clear() + + assert s.isValue + + s.reset() + + assert not s.isValue + class SequenceWithoutSchema(BaseTestCase): @@ -1500,7 +1599,7 @@ class SequenceWithoutSchema(BaseTestCase): assert list(s.items()) == [('field-0', str2octs('abc')), ('field-1', 123)] def testUpdate(self): - s = univ.Sequence() + s = univ.Sequence().clear() assert not s s.setComponentByPosition(0, univ.OctetString('abc')) s.setComponentByPosition(1, univ.Integer(123)) @@ -1522,6 +1621,27 @@ class SequenceWithoutSchema(BaseTestCase): s.clear() assert 'field-0' not in s + def testSchema(self): + + class Sequence(univ.Sequence): + pass + + s = Sequence() + + assert not s.isValue + + s[0] = univ.OctetString('test') + + assert s.isValue + + s.clear() + + assert s.isValue + + s.reset() + + assert not s.isValue + class SequencePicklingTestCase(unittest.TestCase): @@ -1633,7 +1753,7 @@ class Set(BaseTestCase): def testGetTagMap(self): assert self.s1.tagMap.presentTypes == { - univ.Set.tagSet: univ.Set() + univ.Set.tagSet: univ.Set().clear() } def testGetComponentTagMap(self): -- cgit v1.2.3 From adf4c5b1ef11026102dad0bb77b54576e0ebd71c Mon Sep 17 00:00:00 2001 From: Ilya Etingof Date: Fri, 28 Jun 2019 22:48:40 +0200 Subject: Fix `AnyDecoder` to accept `TagMap` as `asn1Spec` (#152) Fixes `AnyDecoder` to accept `TagMap` as `asn1Spec`. The use-case is to make `AnyDecoder` operational when dumping raw value on error condition is enabled --- CHANGES.rst | 2 ++ pyasn1/codec/ber/decoder.py | 28 ++++++++++++++++++++++++---- tests/codec/ber/test_decoder.py | 32 ++++++++++++++++++++++++++++++++ 3 files changed, 58 insertions(+), 4 deletions(-) diff --git a/CHANGES.rst b/CHANGES.rst index 19e7bd9..1bcd5f6 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -26,6 +26,8 @@ Revision 0.4.6, released XX-06-2019 becomes a value object. - Added `.reset()` method to all constructed types to turn value object into a schema object. +- Fixed `AnyDecoder` to accept possible `TagMap` as `asn1Spec` + to make dumping raw value operational Revision 0.4.5, released 29-12-2018 ----------------------------------- diff --git a/pyasn1/codec/ber/decoder.py b/pyasn1/codec/ber/decoder.py index 2e4afbb..8c556fc 100644 --- a/pyasn1/codec/ber/decoder.py +++ b/pyasn1/codec/ber/decoder.py @@ -1016,7 +1016,16 @@ class AnyDecoder(AbstractSimpleDecoder): tagSet=None, length=None, state=None, decodeFun=None, substrateFun=None, **options): - if asn1Spec is None or asn1Spec is not None and tagSet != asn1Spec.tagSet: + if asn1Spec is None: + isUntagged = True + + elif asn1Spec.__class__ is tagmap.TagMap: + isUntagged = tagSet not in asn1Spec.tagMap + + else: + isUntagged = tagSet != asn1Spec.tagSet + + if isUntagged: fullSubstrate = options['fullSubstrate'] # untagged Any container, recover inner header substrate @@ -1038,7 +1047,16 @@ class AnyDecoder(AbstractSimpleDecoder): tagSet=None, length=None, state=None, decodeFun=None, substrateFun=None, **options): - if asn1Spec is not None and tagSet == asn1Spec.tagSet: + if asn1Spec is None: + isTagged = False + + elif asn1Spec.__class__ is tagmap.TagMap: + isTagged = tagSet in asn1Spec.tagMap + + else: + isTagged = tagSet == asn1Spec.tagSet + + if isTagged: # tagged Any type -- consume header substrate header = null @@ -1208,7 +1226,7 @@ for typeDecoder in tagMap.values(): class Decoder(object): defaultErrorState = stErrorCondition - # defaultErrorState = stDumpRawValue + #defaultErrorState = stDumpRawValue defaultRawDecoder = AnyDecoder() supportIndefLength = True @@ -1509,7 +1527,9 @@ class Decoder(object): break if state is stTryAsExplicitTag: - if tagSet and tagSet[0].tagFormat == tag.tagFormatConstructed and tagSet[0].tagClass != tag.tagClassUniversal: + if (tagSet and + tagSet[0].tagFormat == tag.tagFormatConstructed and + tagSet[0].tagClass != tag.tagClassUniversal): # Assume explicit tagging concreteDecoder = explicitTagDecoder state = stDecodeValue diff --git a/tests/codec/ber/test_decoder.py b/tests/codec/ber/test_decoder.py index 8b6d590..bff246e 100644 --- a/tests/codec/ber/test_decoder.py +++ b/tests/codec/ber/test_decoder.py @@ -1429,6 +1429,38 @@ class NonStringDecoderTestCase(BaseTestCase): assert self.s == s +class ErrorOnDecodingTestCase(BaseTestCase): + + def testErrorCondition(self): + decode = decoder.Decoder(decoder.tagMap, decoder.typeMap) + + try: + asn1Object, rest = decode(str2octs('abc')) + + except PyAsn1Error: + exc = sys.exc_info()[1] + assert (isinstance(exc, PyAsn1Error), + 'Unexpected exception raised %r' % (exc,)) + + else: + assert False, 'Unexpected decoder result %r' % (asn1Object,) + + def testRawDump(self): + decode = decoder.Decoder(decoder.tagMap, decoder.typeMap) + + decode.defaultErrorState = decoder.stDumpRawValue + + asn1Object, rest = decode(ints2octs( + (31, 8, 2, 1, 1, 131, 3, 2, 1, 12))) + + assert (isinstance(asn1Object, univ.Any), + 'Unexpected raw dump type %r' % (asn1Object,)) + assert (asn1Object.asNumbers() == (31, 8, 2, 1, 1), + 'Unexpected raw dump value %r' % (asn1Object,)) + assert (rest == ints2octs((131, 3, 2, 1, 12)), + 'Unexpected rest of substrate after raw dump %r' % rest) + + suite = unittest.TestLoader().loadTestsFromModule(sys.modules[__name__]) if __name__ == '__main__': -- cgit v1.2.3 From fe2725f8c80039d9d82af2bd3299abc174417c27 Mon Sep 17 00:00:00 2001 From: Alex Shafer Date: Sat, 29 Jun 2019 00:20:15 -0600 Subject: Add specific exceptions for String issues (#155) This change helps telling unicode-related exceptions from other PyAsn1Error exceptions. --- pyasn1/error.py | 28 ++++++++++++++++++++++++++++ pyasn1/type/char.py | 24 ++++++++++++------------ pyasn1/type/univ.py | 24 ++++++++++++------------ 3 files changed, 52 insertions(+), 24 deletions(-) diff --git a/pyasn1/error.py b/pyasn1/error.py index 7f606bb..fb23f1d 100644 --- a/pyasn1/error.py +++ b/pyasn1/error.py @@ -13,6 +13,34 @@ class PyAsn1Error(Exception): """ +class PyAsn1StringError(PyAsn1Error, UnicodeError): + """Create pyasn1 exception object + + The `PyAsn1StringError` exception is a base class for errors relating to + string encoding/decoding and other related problems + """ + def __init__(self, message, unicode_error=None): + if isinstance(unicode_error, UnicodeError): + UnicodeError.__init__(self, *unicode_error.args) + PyAsn1Error.__init__(self, message) + + +class PyAsn1StringDecodeError(PyAsn1StringError, UnicodeDecodeError): + """Create pyasn1 exception object + + The `PyAsn1StringDecodeError` exception represents a failure to decode + underlying bytes values to a string + """ + + +class PyAsn1StringEncodeError(PyAsn1StringError, UnicodeEncodeError): + """Create pyasn1 exception object + + The `PyAsn1StringEncodeError` exception represents a failure to encode + underlying string value as bytes + """ + + class ValueConstraintError(PyAsn1Error): """Create pyasn1 exception object diff --git a/pyasn1/type/char.py b/pyasn1/type/char.py index 617b98d..03121a0 100644 --- a/pyasn1/type/char.py +++ b/pyasn1/type/char.py @@ -54,9 +54,9 @@ class AbstractCharacterString(univ.OctetString): # `str` is Py2 text representation return self._value.encode(self.encoding) - except UnicodeEncodeError: - raise error.PyAsn1Error( - "Can't encode string '%s' with codec %s" % (self._value, self.encoding) + except UnicodeEncodeError as e: + raise error.PyAsn1StringEncodeError( + "Can't encode string '%s' with codec %s" % (self._value, self.encoding), e ) def __unicode__(self): @@ -75,9 +75,9 @@ class AbstractCharacterString(univ.OctetString): else: return unicode(value) - except (UnicodeDecodeError, LookupError): - raise error.PyAsn1Error( - "Can't decode string '%s' with codec %s" % (value, self.encoding) + except (UnicodeDecodeError, LookupError) as e: + raise error.PyAsn1StringDecodeError( + "Can't decode string '%s' with codec %s" % (value, self.encoding), e ) def asOctets(self, padding=True): @@ -94,9 +94,9 @@ class AbstractCharacterString(univ.OctetString): def __bytes__(self): try: return self._value.encode(self.encoding) - except UnicodeEncodeError: - raise error.PyAsn1Error( - "Can't encode string '%s' with codec %s" % (self._value, self.encoding) + except UnicodeEncodeError as e: + raise error.PyAsn1StringEncodeError( + "Can't encode string '%s' with codec %s" % (self._value, self.encoding), e ) def prettyIn(self, value): @@ -112,9 +112,9 @@ class AbstractCharacterString(univ.OctetString): else: return str(value) - except (UnicodeDecodeError, LookupError): - raise error.PyAsn1Error( - "Can't decode string '%s' with codec %s" % (value, self.encoding) + except (UnicodeDecodeError, LookupError) as e: + raise error.PyAsn1StringDecodeError( + "Can't decode string '%s' with codec %s" % (value, self.encoding), e ) def asOctets(self, padding=True): diff --git a/pyasn1/type/univ.py b/pyasn1/type/univ.py index 4c8c8e4..7d88957 100644 --- a/pyasn1/type/univ.py +++ b/pyasn1/type/univ.py @@ -825,9 +825,9 @@ class OctetString(base.AbstractSimpleAsn1Item): elif isinstance(value, unicode): try: return value.encode(self.encoding) - except (LookupError, UnicodeEncodeError): - raise error.PyAsn1Error( - "Can't encode string '%s' with codec %s" % (value, self.encoding) + except (LookupError, UnicodeEncodeError) as e: + raise error.PyAsn1StringEncodeError( + "Can't encode string '%s' with codec %s" % (value, self.encoding), e ) elif isinstance(value, (tuple, list)): try: @@ -846,9 +846,9 @@ class OctetString(base.AbstractSimpleAsn1Item): try: return self._value.decode(self.encoding) - except UnicodeDecodeError: - raise error.PyAsn1Error( - "Can't decode string '%s' with codec %s" % (self._value, self.encoding) + except UnicodeDecodeError as e: + raise error.PyAsn1StringDecodeError( + "Can't decode string '%s' with codec %s" % (self._value, self.encoding), e ) def asOctets(self): @@ -864,9 +864,9 @@ class OctetString(base.AbstractSimpleAsn1Item): elif isinstance(value, str): try: return value.encode(self.encoding) - except UnicodeEncodeError: - raise error.PyAsn1Error( - "Can't encode string '%s' with '%s' codec" % (value, self.encoding) + except UnicodeEncodeError as e: + raise error.PyAsn1StringEncodeError( + "Can't encode string '%s' with '%s' codec" % (value, self.encoding), e ) elif isinstance(value, OctetString): # a shortcut, bytes() would work the same way return value.asOctets() @@ -881,9 +881,9 @@ class OctetString(base.AbstractSimpleAsn1Item): try: return self._value.decode(self.encoding) - except UnicodeDecodeError: - raise error.PyAsn1Error( - "Can't decode string '%s' with '%s' codec at '%s'" % (self._value, self.encoding, self.__class__.__name__) + except UnicodeDecodeError as e: + raise error.PyAsn1StringDecodeError( + "Can't decode string '%s' with '%s' codec at '%s'" % (self._value, self.encoding, self.__class__.__name__), e ) def __bytes__(self): -- cgit v1.2.3 From 4a9abf7ae867e9ebabc850320d87a7c1230acfad Mon Sep 17 00:00:00 2001 From: Ilya Etingof Date: Sat, 29 Jun 2019 08:50:35 +0200 Subject: Rename pyasn1 unicode exceptions The new exception classes names are `PyAsn1UnicodeDecodeError` and `PyAsn1UnicodeEncodeError`. Also, unit tests added. --- CHANGES.rst | 3 +++ pyasn1/error.py | 40 +++++++++++++++++++++------------------- pyasn1/type/char.py | 32 ++++++++++++++++++++------------ pyasn1/type/univ.py | 27 +++++++++++++++++---------- tests/type/test_univ.py | 29 ++++++++++++++++++++++++++++- 5 files changed, 89 insertions(+), 42 deletions(-) diff --git a/CHANGES.rst b/CHANGES.rst index 1bcd5f6..e979039 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -26,6 +26,9 @@ Revision 0.4.6, released XX-06-2019 becomes a value object. - Added `.reset()` method to all constructed types to turn value object into a schema object. +- Added `PyAsn1UnicodeDecodeError`/`PyAsn1UnicodeDecodeError` exceptions + to help the caller treating unicode errors happening internally + to pyasn1 at the upper layers. - Fixed `AnyDecoder` to accept possible `TagMap` as `asn1Spec` to make dumping raw value operational diff --git a/pyasn1/error.py b/pyasn1/error.py index fb23f1d..cb30033 100644 --- a/pyasn1/error.py +++ b/pyasn1/error.py @@ -13,45 +13,47 @@ class PyAsn1Error(Exception): """ -class PyAsn1StringError(PyAsn1Error, UnicodeError): +class ValueConstraintError(PyAsn1Error): """Create pyasn1 exception object - The `PyAsn1StringError` exception is a base class for errors relating to - string encoding/decoding and other related problems + The `ValueConstraintError` exception indicates an ASN.1 value + constraint violation. """ - def __init__(self, message, unicode_error=None): - if isinstance(unicode_error, UnicodeError): - UnicodeError.__init__(self, *unicode_error.args) - PyAsn1Error.__init__(self, message) -class PyAsn1StringDecodeError(PyAsn1StringError, UnicodeDecodeError): +class SubstrateUnderrunError(PyAsn1Error): """Create pyasn1 exception object - The `PyAsn1StringDecodeError` exception represents a failure to decode - underlying bytes values to a string + The `SubstrateUnderrunError` exception indicates insufficient serialised + data on input of a de-serialization routine. """ -class PyAsn1StringEncodeError(PyAsn1StringError, UnicodeEncodeError): +class PyAsn1UnicodeError(PyAsn1Error, UnicodeError): """Create pyasn1 exception object - The `PyAsn1StringEncodeError` exception represents a failure to encode - underlying string value as bytes + The `PyAsn1UnicodeError` exception is a base class for errors relating to + unicode text de/serialization. """ + def __init__(self, message, unicode_error=None): + if isinstance(unicode_error, UnicodeError): + UnicodeError.__init__(self, *unicode_error.args) + PyAsn1Error.__init__(self, message) -class ValueConstraintError(PyAsn1Error): +class PyAsn1UnicodeDecodeError(PyAsn1UnicodeError, UnicodeDecodeError): """Create pyasn1 exception object - The `ValueConstraintError` exception indicates an ASN.1 value - constraint violation. + The `PyAsn1UnicodeDecodeError` exception represents a failure to + deserialize unicode text. """ -class SubstrateUnderrunError(PyAsn1Error): +class PyAsn1UnicodeEncodeError(PyAsn1UnicodeError, UnicodeEncodeError): """Create pyasn1 exception object - The `SubstrateUnderrunError` exception indicates insufficient serialised - data on input of a de-serialization routine. + The `PyAsn1UnicodeEncodeError` exception represents a failure to + serialize unicode text. """ + + diff --git a/pyasn1/type/char.py b/pyasn1/type/char.py index 03121a0..1b5daed 100644 --- a/pyasn1/type/char.py +++ b/pyasn1/type/char.py @@ -54,9 +54,11 @@ class AbstractCharacterString(univ.OctetString): # `str` is Py2 text representation return self._value.encode(self.encoding) - except UnicodeEncodeError as e: - raise error.PyAsn1StringEncodeError( - "Can't encode string '%s' with codec %s" % (self._value, self.encoding), e + except UnicodeEncodeError: + exc = sys.exc_info()[1] + raise error.PyAsn1UnicodeEncodeError( + "Can't encode string '%s' with codec " + "%s" % (self._value, self.encoding), exc ) def __unicode__(self): @@ -75,9 +77,11 @@ class AbstractCharacterString(univ.OctetString): else: return unicode(value) - except (UnicodeDecodeError, LookupError) as e: - raise error.PyAsn1StringDecodeError( - "Can't decode string '%s' with codec %s" % (value, self.encoding), e + except (UnicodeDecodeError, LookupError) as exc: + exc = sys.exc_info()[1] + raise error.PyAsn1UnicodeDecodeError( + "Can't decode string '%s' with codec " + "%s" % (value, self.encoding), exc ) def asOctets(self, padding=True): @@ -94,9 +98,11 @@ class AbstractCharacterString(univ.OctetString): def __bytes__(self): try: return self._value.encode(self.encoding) - except UnicodeEncodeError as e: - raise error.PyAsn1StringEncodeError( - "Can't encode string '%s' with codec %s" % (self._value, self.encoding), e + except UnicodeEncodeError: + exc = sys.exc_info()[1] + raise error.PyAsn1UnicodeEncodeError( + "Can't encode string '%s' with codec " + "%s" % (self._value, self.encoding), exc ) def prettyIn(self, value): @@ -112,9 +118,11 @@ class AbstractCharacterString(univ.OctetString): else: return str(value) - except (UnicodeDecodeError, LookupError) as e: - raise error.PyAsn1StringDecodeError( - "Can't decode string '%s' with codec %s" % (value, self.encoding), e + except (UnicodeDecodeError, LookupError): + exc = sys.exc_info()[1] + raise error.PyAsn1UnicodeDecodeError( + "Can't decode string '%s' with codec " + "%s" % (value, self.encoding), exc ) def asOctets(self, padding=True): diff --git a/pyasn1/type/univ.py b/pyasn1/type/univ.py index 7d88957..96623e7 100644 --- a/pyasn1/type/univ.py +++ b/pyasn1/type/univ.py @@ -825,9 +825,11 @@ class OctetString(base.AbstractSimpleAsn1Item): elif isinstance(value, unicode): try: return value.encode(self.encoding) - except (LookupError, UnicodeEncodeError) as e: - raise error.PyAsn1StringEncodeError( - "Can't encode string '%s' with codec %s" % (value, self.encoding), e + except (LookupError, UnicodeEncodeError): + exc = sys.exc_info()[1] + raise error.PyAsn1UnicodeEncodeError( + "Can't encode string '%s' with codec " + "%s" % (value, self.encoding), exc ) elif isinstance(value, (tuple, list)): try: @@ -846,9 +848,11 @@ class OctetString(base.AbstractSimpleAsn1Item): try: return self._value.decode(self.encoding) - except UnicodeDecodeError as e: - raise error.PyAsn1StringDecodeError( - "Can't decode string '%s' with codec %s" % (self._value, self.encoding), e + except UnicodeDecodeError: + exc = sys.exc_info()[1] + raise error.PyAsn1UnicodeDecodeError( + "Can't decode string '%s' with codec " + "%s" % (self._value, self.encoding), exc ) def asOctets(self): @@ -865,7 +869,7 @@ class OctetString(base.AbstractSimpleAsn1Item): try: return value.encode(self.encoding) except UnicodeEncodeError as e: - raise error.PyAsn1StringEncodeError( + raise error.PyAsn1UnicodeEncodeError( "Can't encode string '%s' with '%s' codec" % (value, self.encoding), e ) elif isinstance(value, OctetString): # a shortcut, bytes() would work the same way @@ -881,9 +885,12 @@ class OctetString(base.AbstractSimpleAsn1Item): try: return self._value.decode(self.encoding) - except UnicodeDecodeError as e: - raise error.PyAsn1StringDecodeError( - "Can't decode string '%s' with '%s' codec at '%s'" % (self._value, self.encoding, self.__class__.__name__), e + except UnicodeDecodeError: + exc = sys.exc_info()[1] + raise error.PyAsn1UnicodeDecodeError( + "Can't decode string '%s' with '%s' codec at " + "'%s'" % (self._value, self.encoding, + self.__class__.__name__), exc ) def __bytes__(self): diff --git a/tests/type/test_univ.py b/tests/type/test_univ.py index 3cd125b..169b73e 100644 --- a/tests/type/test_univ.py +++ b/tests/type/test_univ.py @@ -22,8 +22,9 @@ from pyasn1.type import constraint from pyasn1.type import namedtype from pyasn1.type import namedval from pyasn1.type import error -from pyasn1.compat.octets import str2octs, ints2octs, octs2ints +from pyasn1.compat.octets import str2octs, ints2octs, octs2ints, octs2str from pyasn1.error import PyAsn1Error +from pyasn1.error import PyAsn1UnicodeEncodeError, PyAsn1UnicodeDecodeError class NoValueTestCase(BaseTestCase): @@ -543,6 +544,32 @@ class OctetStringWithAsciiTestCase(OctetStringWithUnicodeMixIn, BaseTestCase): encoding = 'us-ascii' +class OctetStringUnicodeErrorTestCase(BaseTestCase): + def testEncodeError(self): + text = octs2str(ints2octs((0xff, 0xfe))) + + try: + univ.OctetString(text, encoding='us-ascii') + + except PyAsn1UnicodeEncodeError: + pass + + else: + assert False, 'Unicode encoding error not caught' + + def testDecodeError(self): + serialized = ints2octs((0xff, 0xfe)) + + try: + str(univ.OctetString(serialized, encoding='us-ascii')) + + except PyAsn1UnicodeDecodeError: + pass + + else: + assert False, 'Unicode decoding error not caught' + + class OctetStringWithUtf8TestCase(OctetStringWithUnicodeMixIn, BaseTestCase): initializer = (208, 176, 208, 177, 208, 178) encoding = 'utf-8' -- cgit v1.2.3 From dd6640a921a5de7f2b35d8bb852d6eb52527f0a7 Mon Sep 17 00:00:00 2001 From: Ilya Etingof Date: Sun, 30 Jun 2019 23:49:59 +0200 Subject: Improve CER/DER encoding of GeneralizedTime (#164) - Added support for subseconds CER/DER encoding edge cases in `GeneralizedTime` codec - Fixed 3-digit fractional seconds value CER/DER encoding of `GeneralizedTime` --- CHANGES.rst | 4 +++ pyasn1/codec/cer/encoder.py | 71 +++++++++++++++++++++++++++++------------ tests/codec/cer/test_encoder.py | 24 ++++++++++++-- 3 files changed, 76 insertions(+), 23 deletions(-) diff --git a/CHANGES.rst b/CHANGES.rst index e979039..448e372 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -29,6 +29,10 @@ Revision 0.4.6, released XX-06-2019 - Added `PyAsn1UnicodeDecodeError`/`PyAsn1UnicodeDecodeError` exceptions to help the caller treating unicode errors happening internally to pyasn1 at the upper layers. +- Added support for subseconds CER/DER encoding edge cases in + `GeneralizedTime` codec. +- Fixed 3-digit fractional seconds value CER/DER encoding of + `GeneralizedTime`. - Fixed `AnyDecoder` to accept possible `TagMap` as `asn1Spec` to make dumping raw value operational diff --git a/pyasn1/codec/cer/encoder.py b/pyasn1/codec/cer/encoder.py index 788567f..3fdcc1d 100644 --- a/pyasn1/codec/cer/encoder.py +++ b/pyasn1/codec/cer/encoder.py @@ -31,17 +31,20 @@ class RealEncoder(encoder.RealEncoder): # specialized GeneralStringEncoder here class TimeEncoderMixIn(object): - zchar, = str2octs('Z') - pluschar, = str2octs('+') - minuschar, = str2octs('-') - commachar, = str2octs(',') - minLength = 12 - maxLength = 19 + Z_CHAR = ord('Z') + PLUS_CHAR = ord('+') + MINUS_CHAR = ord('-') + COMMA_CHAR = ord(',') + DOT_CHAR = ord('.') + ZERO_CHAR = ord('0') + + MIN_LENGTH = 12 + MAX_LENGTH = 19 def encodeValue(self, value, asn1Spec, encodeFun, **options): - # Encoding constraints: + # CER encoding constraints: # - minutes are mandatory, seconds are optional - # - sub-seconds must NOT be zero + # - sub-seconds must NOT be zero / no meaningless zeros # - no hanging fraction dot # - time in UTC (Z) # - only dot is allowed for fractions @@ -49,20 +52,46 @@ class TimeEncoderMixIn(object): if asn1Spec is not None: value = asn1Spec.clone(value) - octets = value.asOctets() - - if not self.minLength < len(octets) < self.maxLength: - raise error.PyAsn1Error('Length constraint violated: %r' % value) + numbers = value.asNumbers() - if self.pluschar in octets or self.minuschar in octets: - raise error.PyAsn1Error('Must be UTC time: %r' % octets) + if self.PLUS_CHAR in numbers or self.MINUS_CHAR in numbers: + raise error.PyAsn1Error('Must be UTC time: %r' % value) - if octets[-1] != self.zchar: - raise error.PyAsn1Error('Missing "Z" time zone specifier: %r' % octets) + if numbers[-1] != self.Z_CHAR: + raise error.PyAsn1Error('Missing "Z" time zone specifier: %r' % value) - if self.commachar in octets: + if self.COMMA_CHAR in numbers: raise error.PyAsn1Error('Comma in fractions disallowed: %r' % value) + if self.DOT_CHAR in numbers: + + isModified = False + + numbers = list(numbers) + + searchIndex = min(numbers.index(self.DOT_CHAR) + 4, len(numbers) - 1) + + while numbers[searchIndex] != self.DOT_CHAR: + if numbers[searchIndex] == self.ZERO_CHAR: + del numbers[searchIndex] + isModified = True + + searchIndex -= 1 + + searchIndex += 1 + + if searchIndex < len(numbers): + if numbers[searchIndex] == self.Z_CHAR: + # drop hanging comma + del numbers[searchIndex - 1] + isModified = True + + if isModified: + value = value.clone(numbers) + + if not self.MIN_LENGTH < len(numbers) < self.MAX_LENGTH: + raise error.PyAsn1Error('Length constraint violated: %r' % value) + options.update(maxChunkSize=1000) return encoder.OctetStringEncoder.encodeValue( @@ -71,13 +100,13 @@ class TimeEncoderMixIn(object): class GeneralizedTimeEncoder(TimeEncoderMixIn, encoder.OctetStringEncoder): - minLength = 12 - maxLength = 19 + MIN_LENGTH = 12 + MAX_LENGTH = 20 class UTCTimeEncoder(TimeEncoderMixIn, encoder.OctetStringEncoder): - minLength = 10 - maxLength = 14 + MIN_LENGTH = 10 + MAX_LENGTH = 14 class SetEncoder(encoder.SequenceEncoder): diff --git a/tests/codec/cer/test_encoder.py b/tests/codec/cer/test_encoder.py index ea8f813..130fe17 100644 --- a/tests/codec/cer/test_encoder.py +++ b/tests/codec/cer/test_encoder.py @@ -87,7 +87,7 @@ class GeneralizedTimeEncoderTestCase(BaseTestCase): def testDecimalCommaPoint(self): try: assert encoder.encode( - useful.GeneralizedTime('20150501120112,1Z') + useful.GeneralizedTime('20150501120112,1Z') ) except PyAsn1Error: pass @@ -96,9 +96,29 @@ class GeneralizedTimeEncoderTestCase(BaseTestCase): def testWithSubseconds(self): assert encoder.encode( - useful.GeneralizedTime('20170801120112.59Z') + useful.GeneralizedTime('20170801120112.59Z') ) == ints2octs((24, 18, 50, 48, 49, 55, 48, 56, 48, 49, 49, 50, 48, 49, 49, 50, 46, 53, 57, 90)) + def testWithSubsecondsWithZeros(self): + assert encoder.encode( + useful.GeneralizedTime('20170801120112.099Z') + ) == ints2octs((24, 18, 50, 48, 49, 55, 48, 56, 48, 49, 49, 50, 48, 49, 49, 50, 46, 57, 57, 90)) + + def testWithSubsecondsMax(self): + assert encoder.encode( + useful.GeneralizedTime('20170801120112.999Z') + ) == ints2octs((24, 19, 50, 48, 49, 55, 48, 56, 48, 49, 49, 50, 48, 49, 49, 50, 46, 57, 57, 57, 90)) + + def testWithSubsecondsMin(self): + assert encoder.encode( + useful.GeneralizedTime('20170801120112.000Z') + ) == ints2octs((24, 15, 50, 48, 49, 55, 48, 56, 48, 49, 49, 50, 48, 49, 49, 50, 90)) + + def testWithSubsecondsDanglingDot(self): + assert encoder.encode( + useful.GeneralizedTime('20170801120112.Z') + ) == ints2octs((24, 15, 50, 48, 49, 55, 48, 56, 48, 49, 49, 50, 48, 49, 49, 50, 90)) + def testWithSeconds(self): assert encoder.encode( useful.GeneralizedTime('20170801120112Z') -- cgit v1.2.3 From ba302699d8fc791760829aa9a6b014563eedbf2c Mon Sep 17 00:00:00 2001 From: Ilya Etingof Date: Thu, 4 Jul 2019 08:51:06 +0200 Subject: Add GitHub funding button --- .github/FUNDING.yml | 1 + 1 file changed, 1 insertion(+) create mode 100644 .github/FUNDING.yml diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 0000000..496b19f --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1 @@ +github: etingof -- cgit v1.2.3 From b5e2eebe53736eb96f3baf5c17ae953261e09d6c Mon Sep 17 00:00:00 2001 From: Ilya Etingof Date: Sat, 6 Jul 2019 14:04:53 +0200 Subject: Add `SET|SEQUENCE OF ANY` encoding support (#165) For example: AttributeTypeAndValues ::= SEQUENCE { type OBJECT IDENTIFIER, values SET OF ANY DEFINED BY type } This patch adds support of the above ASN.1 syntax to BER/DER/CER codecs. It appears that to implement this feature properly, `SetOf`/`SequenceOf` pyasn1 types need to have `.componentType` wrapped into something similar to `NamedType` that `Set`/`Sequence` have. That additional layer would then carry the open type meta information. Without it, `Sequence`/`Set` codec needs to signal `SetOf`/`SequenceOf` codec of the open type being processed, which is a slight hack. A other inconvenience is that when `SetOf`/`SequenceOf` deal with an open type component, they should not verify types on component assignment. Without open type property in `SetOf`/`SequenceOf`, the code checks for `Any` component type which is another hack. The above shortcomings should be addressed in the follow up patch. --- CHANGES.rst | 1 + pyasn1/codec/ber/decoder.py | 56 ++++++++++--- pyasn1/codec/ber/encoder.py | 88 ++++++++++++++++----- pyasn1/type/univ.py | 18 +++-- tests/codec/ber/test_decoder.py | 169 ++++++++++++++++++++++++++++++++++++++-- tests/codec/ber/test_encoder.py | 124 +++++++++++++++++++++++++++++ 6 files changed, 410 insertions(+), 46 deletions(-) diff --git a/CHANGES.rst b/CHANGES.rst index 448e372..26d4978 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -2,6 +2,7 @@ Revision 0.4.6, released XX-06-2019 ----------------------------------- +- Added previously missing `SET OF ANY` construct encoding/decoding support. - Added `omitEmptyOptionals` option which is respected by `Sequence` and `Set` encoders. When `omitEmptyOptionals` is set to `True`, empty initialized optional components are not encoded. Default is `False`. diff --git a/pyasn1/codec/ber/decoder.py b/pyasn1/codec/ber/decoder.py index 8c556fc..655af04 100644 --- a/pyasn1/codec/ber/decoder.py +++ b/pyasn1/codec/ber/decoder.py @@ -671,12 +671,28 @@ class UniversalConstructedTypeDecoder(AbstractConstructedDecoder): LOG('resolved open type %r by governing ' 'value %r' % (openType, governingValue)) - component, rest = decodeFun( - asn1Object.getComponentByPosition(idx).asOctets(), - asn1Spec=openType - ) + containerValue = asn1Object.getComponentByPosition(idx) + + if containerValue.typeId in ( + univ.SetOf.typeId, univ.SequenceOf.typeId): + + for pos, containerElement in enumerate( + containerValue): + + component, rest = decodeFun( + containerValue[pos].asOctets(), + asn1Spec=openType + ) - asn1Object.setComponentByPosition(idx, component) + containerValue[pos] = component + + else: + component, rest = decodeFun( + asn1Object.getComponentByPosition(idx).asOctets(), + asn1Spec=openType + ) + + asn1Object.setComponentByPosition(idx, component) else: asn1Object.verifySizeSpec() @@ -799,7 +815,7 @@ class UniversalConstructedTypeDecoder(AbstractConstructedDecoder): if not namedTypes.requiredComponents.issubset(seenIndices): raise error.PyAsn1Error('ASN.1 object %s has uninitialized components' % asn1Object.__class__.__name__) - if namedTypes.hasOpenTypes: + if namedTypes.hasOpenTypes: openTypes = options.get('openTypes', {}) @@ -837,13 +853,29 @@ class UniversalConstructedTypeDecoder(AbstractConstructedDecoder): LOG('resolved open type %r by governing ' 'value %r' % (openType, governingValue)) - component, rest = decodeFun( - asn1Object.getComponentByPosition(idx).asOctets(), - asn1Spec=openType, allowEoo=True - ) + containerValue = asn1Object.getComponentByPosition(idx) - if component is not eoo.endOfOctets: - asn1Object.setComponentByPosition(idx, component) + if containerValue.typeId in ( + univ.SetOf.typeId, univ.SequenceOf.typeId): + + for pos, containerElement in enumerate( + containerValue): + + component, rest = decodeFun( + containerValue[pos].asOctets(), + asn1Spec=openType, allowEoo=True + ) + + containerValue[pos] = component + + else: + component, rest = decodeFun( + asn1Object.getComponentByPosition(idx).asOctets(), + asn1Spec=openType, allowEoo=True + ) + + if component is not eoo.endOfOctets: + asn1Object.setComponentByPosition(idx, component) else: asn1Object.verifySizeSpec() diff --git a/pyasn1/codec/ber/encoder.py b/pyasn1/codec/ber/encoder.py index 325ed46..b17fca8 100644 --- a/pyasn1/codec/ber/encoder.py +++ b/pyasn1/codec/ber/encoder.py @@ -89,6 +89,8 @@ class AbstractItemEncoder(object): defMode = options.get('defMode', True) + substrate = null + for idx, singleTag in enumerate(tagSet.superTags): defModeOverride = defMode @@ -556,18 +558,32 @@ class SequenceEncoder(AbstractItemEncoder): if omitEmptyOptionals: options.update(ifNotEmpty=namedType.isOptional) - chunk = encodeFun(component, asn1Spec, **options) - # wrap open type blob if needed - if namedTypes and namedType.openType: - wrapType = namedType.asn1Object - if wrapType.tagSet and not wrapType.isSameTypeWith(component): - chunk = encodeFun(chunk, wrapType, **options) + if (namedTypes and namedType.openType + and namedType.asn1Object.tagSet): - if LOG: - LOG('wrapped open type with wrap type %r' % (wrapType,)) + if component.typeId in ( + univ.SetOf.typeId, univ.SequenceOf.typeId): + substrate += encodeFun( + component, asn1Spec, + **dict(options, openType=True)) + + else: + chunk = encodeFun(component, asn1Spec, **options) + + wrapType = namedType.asn1Object - substrate += chunk + if wrapType.isSameTypeWith(component): + substrate += chunk + + else: + substrate += encodeFun(chunk, wrapType, **options) + + if LOG: + LOG('wrapped with wrap type %r' % (wrapType,)) + + else: + substrate += encodeFun(component, asn1Spec, **options) else: # bare Python value + ASN.1 schema @@ -593,18 +609,31 @@ class SequenceEncoder(AbstractItemEncoder): if omitEmptyOptionals: options.update(ifNotEmpty=namedType.isOptional) - chunk = encodeFun(component, asn1Spec[idx], **options) - # wrap open type blob if needed - if namedType.openType: - wrapType = namedType.asn1Object - if wrapType.tagSet and not wrapType.isSameTypeWith(component): - chunk = encodeFun(chunk, wrapType, **options) + if namedType.openType and namedType.asn1Object.tagSet: - if LOG: - LOG('wrapped open type with wrap type %r' % (wrapType,)) + if component.typeId in ( + univ.SetOf.typeId, univ.SequenceOf.typeId): + substrate += encodeFun( + component, asn1Spec[idx], + **dict(options, openType=True)) + + else: + chunk = encodeFun(component, asn1Spec[idx], **options) + + wrapType = namedType.asn1Object - substrate += chunk + if wrapType.isSameTypeWith(component): + substrate += chunk + + else: + substrate += encodeFun(chunk, wrapType, **options) + + if LOG: + LOG('wrapped with wrap type %r' % (wrapType,)) + + else: + substrate += encodeFun(component, asn1Spec[idx], **options) return substrate, True, True @@ -613,13 +642,32 @@ class SequenceOfEncoder(AbstractItemEncoder): def encodeValue(self, value, asn1Spec, encodeFun, **options): if asn1Spec is None: value.verifySizeSpec() + + wrapType = value.componentType + else: - asn1Spec = asn1Spec.componentType + asn1Spec = wrapType = asn1Spec.componentType + + openType = options.pop('openType', False) substrate = null for idx, component in enumerate(value): - substrate += encodeFun(value[idx], asn1Spec, **options) + if openType: + # do not use asn1Spec even if given because it's a wrapper + chunk = encodeFun(component, **options) + + if not wrapType.isSameTypeWith(component): + # wrap encoded value with wrapper container (e.g. ANY) + chunk = encodeFun(chunk, wrapType, **options) + + if LOG: + LOG('wrapped with wrap type %r' % (wrapType,)) + + else: + chunk = encodeFun(component, asn1Spec, **options) + + substrate += chunk return substrate, True, True diff --git a/pyasn1/type/univ.py b/pyasn1/type/univ.py index 96623e7..5634f94 100644 --- a/pyasn1/type/univ.py +++ b/pyasn1/type/univ.py @@ -1854,13 +1854,19 @@ class SequenceOfAndSetOfBase(base.AbstractConstructedAsn1Item): 'Non-ASN.1 value %r and undefined component' ' type at %r' % (value, self)) - elif componentType is not None: - if self.strictConstraints: - if not componentType.isSameTypeWith( - value, matchTags, matchConstraints): + elif componentType is not None and (matchTags or matchConstraints): + subtypeChecker = ( + self.strictConstraints and + componentType.isSameTypeWith or + componentType.isSuperTypeOf) + + if not subtypeChecker(value, matchTags, matchConstraints): + # TODO: we should wrap componentType with UnnamedType to carry + # additional properties associated with componentType + if componentType.typeId != Any.typeId: raise error.PyAsn1Error( - 'Component value is tag-incompatible: %r ' - 'vs %r' % (value, componentType)) + 'Component value is tag-incompatible: %r vs ' + '%r' % (value, componentType)) else: if not componentType.isSuperTypeOf( diff --git a/tests/codec/ber/test_decoder.py b/tests/codec/ber/test_decoder.py index bff246e..95f6c91 100644 --- a/tests/codec/ber/test_decoder.py +++ b/tests/codec/ber/test_decoder.py @@ -972,6 +972,159 @@ class SequenceDecoderWithExplicitlyTaggedOpenTypesTestCase(BaseTestCase): assert s[1] == univ.OctetString(hexValue='02010C') +class SequenceDecoderWithUnaggedSetOfOpenTypesTestCase(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.SetOf(componentType=univ.Any()), + openType=openType) + ) + ) + + def testDecodeOpenTypesChoiceOne(self): + s, r = decoder.decode( + ints2octs((48, 8, 2, 1, 1, 49, 3, 2, 1, 12)), asn1Spec=self.s, + decodeOpenTypes=True + ) + assert not r + assert s[0] == 1 + assert s[1][0] == 12 + + def testDecodeOpenTypesChoiceTwo(self): + s, r = decoder.decode( + ints2octs((48, 18, 2, 1, 2, 49, 13, 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][0] == univ.OctetString('quick brown') + + def testDecodeOpenTypesUnknownType(self): + try: + s, r = decoder.decode( + ints2octs((48, 6, 2, 1, 2, 6, 1, 39)), asn1Spec=self.s, + decodeOpenTypes=True + ) + + except PyAsn1Error: + pass + + else: + assert False, 'unknown open type tolerated' + + def testDecodeOpenTypesUnknownId(self): + s, r = decoder.decode( + ints2octs((48, 8, 2, 1, 3, 49, 3, 2, 1, 12)), asn1Spec=self.s, + decodeOpenTypes=True + ) + assert not r + assert s[0] == 3 + assert s[1][0] == univ.OctetString(hexValue='02010c') + + def testDontDecodeOpenTypesChoiceOne(self): + s, r = decoder.decode( + ints2octs((48, 8, 2, 1, 1, 49, 3, 2, 1, 12)), asn1Spec=self.s + ) + assert not r + assert s[0] == 1 + assert s[1][0] == ints2octs((2, 1, 12)) + + def testDontDecodeOpenTypesChoiceTwo(self): + s, r = decoder.decode( + ints2octs((48, 18, 2, 1, 2, 49, 13, 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][0] == ints2octs((4, 11, 113, 117, 105, 99, 107, 32, 98, 114, + 111, 119, 110)) + + +class SequenceDecoderWithImplicitlyTaggedSetOfOpenTypesTestCase(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.SetOf( + componentType=univ.Any().subtype( + implicitTag=tag.Tag( + tag.tagClassContext, tag.tagFormatSimple, 3))), + openType=openType + ) + ) + ) + + def testDecodeOpenTypesChoiceOne(self): + s, r = decoder.decode( + ints2octs((48, 10, 2, 1, 1, 49, 5, 131, 3, 2, 1, 12)), + asn1Spec=self.s, decodeOpenTypes=True + ) + assert not r + assert s[0] == 1 + assert s[1][0] == 12 + + def testDecodeOpenTypesUnknownId(self): + s, r = decoder.decode( + ints2octs((48, 10, 2, 1, 3, 49, 5, 131, 3, 2, 1, 12)), + asn1Spec=self.s, decodeOpenTypes=True + ) + assert not r + assert s[0] == 3 + assert s[1][0] == univ.OctetString(hexValue='02010C') + + +class SequenceDecoderWithExplicitlyTaggedSetOfOpenTypesTestCase(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.SetOf( + componentType=univ.Any().subtype( + explicitTag=tag.Tag( + tag.tagClassContext, tag.tagFormatSimple, 3))), + openType=openType + ) + ) + ) + + def testDecodeOpenTypesChoiceOne(self): + s, r = decoder.decode( + ints2octs((48, 10, 2, 1, 1, 49, 5, 131, 3, 2, 1, 12)), + asn1Spec=self.s, decodeOpenTypes=True + ) + assert not r + assert s[0] == 1 + assert s[1][0] == 12 + + def testDecodeOpenTypesUnknownId(self): + s, r = decoder.decode( + ints2octs( (48, 10, 2, 1, 3, 49, 5, 131, 3, 2, 1, 12)), + asn1Spec=self.s, decodeOpenTypes=True + ) + assert not r + assert s[0] == 3 + assert s[1][0] == univ.OctetString(hexValue='02010C') + + class SetDecoderTestCase(BaseTestCase): def setUp(self): BaseTestCase.setUp(self) @@ -1439,8 +1592,8 @@ class ErrorOnDecodingTestCase(BaseTestCase): except PyAsn1Error: exc = sys.exc_info()[1] - assert (isinstance(exc, PyAsn1Error), - 'Unexpected exception raised %r' % (exc,)) + assert isinstance(exc, PyAsn1Error), ( + 'Unexpected exception raised %r' % (exc,)) else: assert False, 'Unexpected decoder result %r' % (asn1Object,) @@ -1453,12 +1606,12 @@ class ErrorOnDecodingTestCase(BaseTestCase): asn1Object, rest = decode(ints2octs( (31, 8, 2, 1, 1, 131, 3, 2, 1, 12))) - assert (isinstance(asn1Object, univ.Any), - 'Unexpected raw dump type %r' % (asn1Object,)) - assert (asn1Object.asNumbers() == (31, 8, 2, 1, 1), - 'Unexpected raw dump value %r' % (asn1Object,)) - assert (rest == ints2octs((131, 3, 2, 1, 12)), - 'Unexpected rest of substrate after raw dump %r' % rest) + assert isinstance(asn1Object, univ.Any), ( + 'Unexpected raw dump type %r' % (asn1Object,)) + assert asn1Object.asNumbers() == (31, 8, 2, 1, 1), ( + 'Unexpected raw dump value %r' % (asn1Object,)) + assert rest == ints2octs((131, 3, 2, 1, 12)), ( + 'Unexpected rest of substrate after raw dump %r' % rest) suite = unittest.TestLoader().loadTestsFromModule(sys.modules[__name__]) diff --git a/tests/codec/ber/test_encoder.py b/tests/codec/ber/test_encoder.py index da14830..26236af 100644 --- a/tests/codec/ber/test_encoder.py +++ b/tests/codec/ber/test_encoder.py @@ -854,6 +854,130 @@ class SequenceEncoderWithExplicitlyTaggedOpenTypesTestCase(BaseTestCase): ) +class SequenceEncoderWithUntaggedSetOfOpenTypesTestCase(BaseTestCase): + def setUp(self): + BaseTestCase.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.SetOf( + componentType=univ.Any()), openType=openType) + ) + ) + + def testEncodeOpenTypeChoiceOne(self): + self.s.clear() + + self.s[0] = 1 + self.s[1].append(univ.Integer(12)) + + assert encoder.encode(self.s, asn1Spec=self.s) == ints2octs( + (48, 8, 2, 1, 1, 49, 3, 2, 1, 12) + ) + + def testEncodeOpenTypeChoiceTwo(self): + self.s.clear() + + self.s[0] = 2 + self.s[1].append(univ.OctetString('quick brown')) + + assert encoder.encode(self.s, asn1Spec=self.s) == ints2octs( + (48, 18, 2, 1, 2, 49, 13, 4, 11, 113, 117, 105, 99, + 107, 32, 98, 114, 111, 119, 110) + ) + + def testEncodeOpenTypeUnknownId(self): + self.s.clear() + + self.s[0] = 2 + self.s[1].append(univ.ObjectIdentifier('1.3.6')) + + try: + encoder.encode(self.s, asn1Spec=self.s) + + except PyAsn1Error: + assert False, 'incompatible open type tolerated' + + def testEncodeOpenTypeIncompatibleType(self): + self.s.clear() + + self.s[0] = 2 + self.s[1].append(univ.ObjectIdentifier('1.3.6')) + + try: + encoder.encode(self.s, asn1Spec=self.s) + + except PyAsn1Error: + assert False, 'incompatible open type tolerated' + + +class SequenceEncoderWithImplicitlyTaggedSetOfOpenTypesTestCase(BaseTestCase): + def setUp(self): + BaseTestCase.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.SetOf( + componentType=univ.Any().subtype( + implicitTag=tag.Tag( + tag.tagClassContext, tag.tagFormatSimple, 3))), + openType=openType) + ) + ) + + def testEncodeOpenTypeChoiceOne(self): + self.s.clear() + + self.s[0] = 1 + self.s[1].append(univ.Integer(12)) + + assert encoder.encode(self.s, asn1Spec=self.s) == ints2octs( + (48, 10, 2, 1, 1, 49, 5, 131, 3, 2, 1, 12) + ) + + +class SequenceEncoderWithExplicitlyTaggedSetOfOpenTypesTestCase(BaseTestCase): + def setUp(self): + BaseTestCase.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.SetOf( + componentType=univ.Any().subtype( + explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 3))), + openType=openType) + ) + ) + + def testEncodeOpenTypeChoiceOne(self): + self.s.clear() + + self.s[0] = 1 + self.s[1].append(univ.Integer(12)) + + assert encoder.encode(self.s, asn1Spec=self.s) == ints2octs( + (48, 10, 2, 1, 1, 49, 5, 163, 3, 2, 1, 12) + ) + + class SequenceEncoderWithComponentsSchemaTestCase(BaseTestCase): def setUp(self): BaseTestCase.setUp(self) -- cgit v1.2.3 From 954629f0ba38d9ba26744daba36d97b352ebb721 Mon Sep 17 00:00:00 2001 From: Ilya Etingof Date: Sun, 7 Jul 2019 18:49:24 +0200 Subject: Update FUNDING.yml --- .github/FUNDING.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml index 496b19f..5fcc7bb 100644 --- a/.github/FUNDING.yml +++ b/.github/FUNDING.yml @@ -1 +1 @@ -github: etingof +custom: http://snmplabs.com/services.html -- cgit v1.2.3 From cf9b3f5f67501b85296c05456bbd324352177718 Mon Sep 17 00:00:00 2001 From: Ilya Etingof Date: Tue, 9 Jul 2019 00:18:00 +0200 Subject: Add more docs on `OpenType` (#166) This change adds more explanations and examples on ASN.1 ANY DEFINED BY syntax (OpenType). --- docs/source/pyasn1/type/opentype/contents.rst | 94 ++++++++++++++++++++++++--- docs/source/pyasn1/type/opentype/opentype.rst | 7 +- docs/source/pyasn1/type/univ/sequence.rst | 6 +- docs/source/pyasn1/type/univ/sequenceof.rst | 6 +- docs/source/pyasn1/type/univ/set.rst | 6 +- docs/source/pyasn1/type/univ/setof.rst | 6 +- pyasn1/type/base.py | 11 ++-- pyasn1/type/opentype.py | 45 ++++++++++--- 8 files changed, 139 insertions(+), 42 deletions(-) diff --git a/docs/source/pyasn1/type/opentype/contents.rst b/docs/source/pyasn1/type/opentype/contents.rst index 9ae10d0..034870b 100644 --- a/docs/source/pyasn1/type/opentype/contents.rst +++ b/docs/source/pyasn1/type/opentype/contents.rst @@ -1,16 +1,67 @@ .. _type.opentype: -Untyped fields of constructed types ------------------------------------ +Dynamic or open type +-------------------- -To aid data structures flexibility, ASN.1 allows the designer to -leave incomplete field type specification in the -:ref:`Sequence ` and :ref:`Set ` types. +ASN.1 allows data structure designer to leave "holes" in field type +specification of :ref:`Sequence ` or +:ref:`Set ` types. -To figure out field's type at the run time, a type selector field -must accompany the open type field. The open type situation can -be captured by the :ref:`OpenType ` object. +The idea behind that feature is that there can be times, when the +exact field type is not known at the design time, or it is anticipated +that new field types may come up in the future. + +This "hole" type is manifested in the data structure by :ref:`Any ` +type. Technically, the actual type is serialized into an octet stream +and then put into :ref:`Any ` "container", which is in fact an +(untagged, by default) specialization of ASN.1 +:ref:`OctetString ` type. + +.. code-block:: bash + + Algorithm ::= SEQUENCE { + algorithm OBJECT IDENTIFIER, + parameters ANY DEFINED BY algorithm OPTIONAL + } + +On the receiving end, to know how to interpret the open type +serialization, the receiver can rely on the supplied value in +the other field of the data structure. That other field is +semantically linked with the open type field. This link +is expressed in ASN.1 by the *DEFINE BY* clause. + +From ASN.1 perspective, it is not an error if the decoder does +not know a type selector value it receives. In that case pyasn1 decoder +just leaves serialized blob in the open type field. + +.. note:: + + By default, ASN.1 ANY type has no tag. That makes it an + "invisible" in serialization. However, like any other ASN.1 type, + ANY type can be subtyped by :ref:`tagging `. + +Besides scalar open type fields, ASN.1 allows using *SET OF* +or *SEQUENCE OF* containers holding zero or more of *ANY* +scalars. + +.. code-block:: bash + + AttributeTypeAndValues ::= SEQUENCE { + type OBJECT IDENTIFIER, + values SET OF ANY DEFINED BY type + } + +.. note:: + + A single type selector field is used to guide the decoder + of potentially many elements of a *SET OF* or *SEQUENCE OF* container + all at once. That implies that all *ANY* elements must be of the same + type in any given instance of a data structure. + +When expressing ASN.1 type "holes" in pyasn1, the +:ref:`OpenType ` object should be used to establish +a semantic link between type selector field and open type field. .. code-block:: python @@ -24,15 +75,38 @@ be captured by the :ref:`OpenType ` object. """ Algorithm ::= SEQUENCE { algorithm OBJECT IDENTIFIER, - parameters ANY DEFINED BY algorithm OPTIONAL + parameters ANY DEFINED BY algorithm } """ componentType = NamedTypes( NamedType('algorithm', ObjectIdentifier()), - OptionalNamedType('parameters', Any(), + NamedType('parameters', Any(), openType=OpenType('algorithm', algo_map)) ) +Similarly for `SET OF ANY DEFINED BY` or `SEQUENCE OF ANY DEFINED BY` +constructs: + +.. code-block:: python + + algo_map = { + ObjectIdentifier('1.2.840.113549.1.1.1'): rsaEncryption(), + ObjectIdentifier('1.2.840.113549.1.1.2'): md2WithRSAEncryption() + } + + + class Algorithm(Sequence): + """ + Algorithm ::= SEQUENCE { + algorithm OBJECT IDENTIFIER, + parameters SET OF ANY DEFINED BY algorithm + } + """ + componentType = NamedTypes( + NamedType('algorithm', ObjectIdentifier()), + NamedType('parameters', SetOf(componentType=Any()), + openType=OpenType('algorithm', algo_map)) + ) .. toctree:: :maxdepth: 2 diff --git a/docs/source/pyasn1/type/opentype/opentype.rst b/docs/source/pyasn1/type/opentype/opentype.rst index dc2fa47..8ce9303 100644 --- a/docs/source/pyasn1/type/opentype/opentype.rst +++ b/docs/source/pyasn1/type/opentype/opentype.rst @@ -9,9 +9,4 @@ .. 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. Typically used with :ref:`Any ` - type. +More information on open type use can be found :ref:`here `. \ No newline at end of file diff --git a/docs/source/pyasn1/type/univ/sequence.rst b/docs/source/pyasn1/type/univ/sequence.rst index 6d2c6b5..f48f497 100644 --- a/docs/source/pyasn1/type/univ/sequence.rst +++ b/docs/source/pyasn1/type/univ/sequence.rst @@ -6,7 +6,7 @@ |ASN.1| type ------------ -.. autoclass:: pyasn1.type.univ.Sequence(componentType=None, tagSet=tagSet(), subtypeSpec=ConstraintsIntersection(), sizeSpec=ConstraintsIntersection()) +.. autoclass:: pyasn1.type.univ.Sequence(componentType=NamedTypes(), tagSet=tagSet(), subtypeSpec=ConstraintsIntersection(), sizeSpec=ConstraintsIntersection()) :members: isValue, isSameTypeWith, isSuperTypeOf, tagSet, effectiveTagSet, tagMap, componentType, subtypeSpec, sizeSpec, getComponentByPosition, setComponentByPosition, getComponentByName, setComponentByName, setDefaultComponents @@ -15,5 +15,5 @@ The |ASN.1| type models a collection of named ASN.1 components. Ordering of the components **is** preserved upon de/serialisation. - .. automethod:: pyasn1.type.univ.Sequence.clone(componentType=None, tagSet=tagSet(), subtypeSpec=ConstraintsIntersection()) - .. automethod:: pyasn1.type.univ.Sequence.subtype(componentType=None, implicitTag=Tag(), explicitTag=Tag(),subtypeSpec=ConstraintsIntersection()) + .. automethod:: pyasn1.type.univ.Sequence.clone(componentType=NamedTypes(), tagSet=tagSet(), subtypeSpec=ConstraintsIntersection()) + .. automethod:: pyasn1.type.univ.Sequence.subtype(componentType=NamedTypes(), implicitTag=Tag(), explicitTag=Tag(),subtypeSpec=ConstraintsIntersection()) diff --git a/docs/source/pyasn1/type/univ/sequenceof.rst b/docs/source/pyasn1/type/univ/sequenceof.rst index dadef67..ed6a781 100644 --- a/docs/source/pyasn1/type/univ/sequenceof.rst +++ b/docs/source/pyasn1/type/univ/sequenceof.rst @@ -6,7 +6,7 @@ |ASN.1| type ------------ -.. autoclass:: pyasn1.type.univ.SequenceOf(componentType=None, tagSet=TagSet(), subtypeSpec=ConstraintsIntersection(), sizeSpec=ConstraintsIntersection()) +.. 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 @@ -15,5 +15,5 @@ The |ASN.1| type models a collection of elements of a single ASN.1 type. Ordering of the components **is** preserved upon de/serialisation. - .. automethod:: pyasn1.type.univ.SequenceOf.clone(componentType=None, tagSet=TagSet(), subtypeSpec=ConstraintsIntersection()) - .. automethod:: pyasn1.type.univ.SequenceOf.subtype(componentType=None, implicitTag=Tag(), explicitTag=Tag(),subtypeSpec=ConstraintsIntersection()) + .. automethod:: pyasn1.type.univ.SequenceOf.clone(componentType=NoValue(), tagSet=TagSet(), subtypeSpec=ConstraintsIntersection()) + .. automethod:: pyasn1.type.univ.SequenceOf.subtype(componentType=NoValue(), implicitTag=Tag(), explicitTag=Tag(),subtypeSpec=ConstraintsIntersection()) diff --git a/docs/source/pyasn1/type/univ/set.rst b/docs/source/pyasn1/type/univ/set.rst index 5c75938..229525a 100644 --- a/docs/source/pyasn1/type/univ/set.rst +++ b/docs/source/pyasn1/type/univ/set.rst @@ -6,7 +6,7 @@ |ASN.1| type ------------ -.. autoclass:: pyasn1.type.univ.Set(componentType=None, tagSet=TagSet(), subtypeSpec=ConstraintsIntersection(), sizeSpec=ConstraintsIntersection()) +.. autoclass:: pyasn1.type.univ.Set(componentType=NamedTypes(), tagSet=TagSet(), subtypeSpec=ConstraintsIntersection(), sizeSpec=ConstraintsIntersection()) :members: isValue, isSameTypeWith, isSuperTypeOf, tagSet, effectiveTagSet, tagMap, componentType, subtypeSpec, sizeSpec, getComponentByPosition, setComponentByPosition, getComponentByName, setComponentByName, setDefaultComponents, getComponentByType, setComponentByType @@ -16,5 +16,5 @@ The |ASN.1| type models a collection of named ASN.1 components. Ordering of the components **is not** preserved upon de/serialisation. - .. automethod:: pyasn1.type.univ.Set.clone(componentType=None, tagSet=TagSet(), subtypeSpec=ConstraintsIntersection()) - .. automethod:: pyasn1.type.univ.Set.subtype(componentType=None, implicitTag=Tag(), explicitTag=Tag(),subtypeSpec=ConstraintsIntersection()) + .. automethod:: pyasn1.type.univ.Set.clone(componentType=NamedTypes(), tagSet=TagSet(), subtypeSpec=ConstraintsIntersection()) + .. automethod:: pyasn1.type.univ.Set.subtype(componentType=NamedTypes(), implicitTag=Tag(), explicitTag=Tag(),subtypeSpec=ConstraintsIntersection()) diff --git a/docs/source/pyasn1/type/univ/setof.rst b/docs/source/pyasn1/type/univ/setof.rst index 0317f4a..2745677 100644 --- a/docs/source/pyasn1/type/univ/setof.rst +++ b/docs/source/pyasn1/type/univ/setof.rst @@ -6,7 +6,7 @@ |ASN.1| type ------------ -.. autoclass:: pyasn1.type.univ.SetOf(componentType=None, tagSet=TagSet(), subtypeSpec=ConstraintsIntersection(), sizeSpec=ConstraintsIntersection()) +.. 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 @@ -15,5 +15,5 @@ The |ASN.1| type models a collection of elements of a single ASN.1 type. Ordering of the components **is not** preserved upon de/serialisation. - .. automethod:: pyasn1.type.univ.SetOf.clone(componentType=None, tagSet=TagSet(), subtypeSpec=ConstraintsIntersection()) - .. automethod:: pyasn1.type.univ.SetOf.subtype(componentType=None, implicitTag=Tag(), explicitTag=Tag(),subtypeSpec=ConstraintsIntersection()) + .. automethod:: pyasn1.type.univ.SetOf.clone(componentType=NoValue(), tagSet=TagSet(), subtypeSpec=ConstraintsIntersection()) + .. automethod:: pyasn1.type.univ.SetOf.subtype(componentType=NoValue(), implicitTag=Tag(), explicitTag=Tag(),subtypeSpec=ConstraintsIntersection()) diff --git a/pyasn1/type/base.py b/pyasn1/type/base.py index b231482..61ec35e 100644 --- a/pyasn1/type/base.py +++ b/pyasn1/type/base.py @@ -12,7 +12,8 @@ from pyasn1.type import constraint from pyasn1.type import tag from pyasn1.type import tagmap -__all__ = ['Asn1Item', 'Asn1ItemBase', 'AbstractSimpleAsn1Item', 'AbstractConstructedAsn1Item'] +__all__ = ['Asn1Item', 'Asn1ItemBase', 'AbstractSimpleAsn1Item', + 'AbstractConstructedAsn1Item'] class Asn1Item(object): @@ -535,8 +536,7 @@ class AbstractConstructedAsn1Item(Asn1ItemBase): Note ---- Due to the mutable nature of the |ASN.1| object, even if no arguments - are supplied, new |ASN.1| object will always be created as a shallow - copy of `self`. + are supplied, a new |ASN.1| object will be created and returned. """ cloneValueFlag = kwargs.pop('cloneValueFlag', False) @@ -588,9 +588,8 @@ class AbstractConstructedAsn1Item(Asn1ItemBase): Note ---- - Due to the immutable nature of the |ASN.1| object, if no arguments - are supplied, no new |ASN.1| object will be created and `self` will - be returned instead. + Due to the mutable nature of the |ASN.1| object, even if no arguments + are supplied, a new |ASN.1| object will be created and returned. """ initializers = self.readOnly.copy() diff --git a/pyasn1/type/opentype.py b/pyasn1/type/opentype.py index d37a533..29645f0 100644 --- a/pyasn1/type/opentype.py +++ b/pyasn1/type/opentype.py @@ -11,11 +11,22 @@ __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. + The *OpenType* object models an untyped field of a constructed ASN.1 + type. In ASN.1 syntax it is usually represented by the + `ANY DEFINED BY` for scalars or `SET OF ANY DEFINED BY`, + `SEQUENCE OF ANY DEFINED BY` for container types clauses. Typically + used together with :class:`~pyasn1.type.univ.Any` object. + + OpenType objects duck-type a read-only Python :class:`dict` objects, + however the passed `typeMap` is not copied, but stored by reference. + That means the user can manipulate `typeMap` at run time having this + reflected on *OpenType* object behavior. + + 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` for scalars or `SET OF ANY DEFINED BY`, + `SEQUENCE OF ANY DEFINED BY` for container types clauses. Typically + used with :class:`~pyasn1.type.univ.Any` type. Parameters ---------- @@ -28,12 +39,14 @@ class OpenType(object): Examples -------- + + For untyped scalars: + .. code-block:: python openType = OpenType( - 'id', - {1: Integer(), - 2: OctetString()} + 'id', {1: Integer(), + 2: OctetString()} ) Sequence( componentType=NamedTypes( @@ -41,6 +54,22 @@ class OpenType(object): NamedType('blob', Any(), openType=openType) ) ) + + For untyped `SET OF` or `SEQUENCE OF` vectors: + + .. code-block:: python + + openType = OpenType( + 'id', {1: Integer(), + 2: OctetString()} + ) + Sequence( + componentType=NamedTypes( + NamedType('id', Integer()), + NamedType('blob', SetOf(componentType=Any()), + openType=openType) + ) + ) """ def __init__(self, name, typeMap=None): -- cgit v1.2.3 From 7b3f79cac2ce765537baa406762d59eae47de04c Mon Sep 17 00:00:00 2001 From: Ilya Etingof Date: Fri, 12 Jul 2019 22:31:41 +0200 Subject: Add `SequenceOf`/`SetOf` list-like slicing support (#168) --- CHANGES.rst | 3 ++- pyasn1/type/univ.py | 28 +++++++++++++++++++++++++--- tests/type/test_univ.py | 17 +++++++++++++++++ 3 files changed, 44 insertions(+), 4 deletions(-) diff --git a/CHANGES.rst b/CHANGES.rst index 26d4978..f631be5 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -1,5 +1,5 @@ -Revision 0.4.6, released XX-06-2019 +Revision 0.4.6, released XX-07-2019 ----------------------------------- - Added previously missing `SET OF ANY` construct encoding/decoding support. @@ -9,6 +9,7 @@ Revision 0.4.6, released XX-06-2019 - New elements to `SequenceOf`/`SetOf` objects can now be added at any position - the requirement for the new elements to reside at the end of the existing ones (i.e. s[len(s)] = 123) is removed. +- List-like slicing support added to `SequenceOf`/`SetOf` objects. - Removed default initializer from `SequenceOf`/`SetOf` types to ensure consistent behaviour with the rest of ASN.1 types. Before this change, `SequenceOf`/`SetOf` instances immediately become value objects behaving diff --git a/pyasn1/type/univ.py b/pyasn1/type/univ.py index 5634f94..0ba59a7 100644 --- a/pyasn1/type/univ.py +++ b/pyasn1/type/univ.py @@ -1677,7 +1677,7 @@ class SequenceOfAndSetOfBase(base.AbstractConstructedAsn1Item): key=key, reverse=reverse))) def __len__(self): - if not self._componentValues: + if self._componentValues is noValue or not self._componentValues: return 0 return max(self._componentValues) + 1 @@ -1762,6 +1762,17 @@ class SequenceOfAndSetOfBase(base.AbstractConstructedAsn1Item): # returns noValue s.getComponentByPosition(0, instantiate=False) """ + if isinstance(idx, slice): + indices = tuple(range(len(self))) + return [self.getComponentByPosition(subidx, default, instantiate) + for subidx in indices[idx]] + + if idx < 0: + idx = len(self) + idx + if idx < 0: + raise error.PyAsn1Error( + 'SequenceOf/SetOf index is out of range') + try: componentValue = self._componentValues[idx] @@ -1819,9 +1830,20 @@ class SequenceOfAndSetOfBase(base.AbstractConstructedAsn1Item): IndexError: When idx > len(self) """ + if isinstance(idx, slice): + indices = tuple(range(len(self))) + startIdx = indices and indices[idx][0] or 0 + for subIdx, subValue in enumerate(value): + self.setComponentByPosition( + startIdx + subIdx, subValue, verifyConstraints, + matchTags, matchConstraints) + return self + if idx < 0: - raise error.PyAsn1Error( - 'SequenceOf/SetOf index must not be negative') + idx = len(self) + idx + if idx < 0: + raise error.PyAsn1Error( + 'SequenceOf/SetOf index is out of range') componentType = self.componentType diff --git a/tests/type/test_univ.py b/tests/type/test_univ.py index 169b73e..de7cdee 100644 --- a/tests/type/test_univ.py +++ b/tests/type/test_univ.py @@ -1090,6 +1090,13 @@ class SequenceOf(BaseTestCase): # this is a deviation from standard sequence protocol assert not s[2] + def testGetItemSlice(self): + s = self.s1.clone() + s.extend(['xxx', 'yyy', 'zzz']) + assert s[:1] == [str2octs('xxx')] + assert s[-2:] == [str2octs('yyy'), str2octs('zzz')] + assert s[1:2] == [str2octs('yyy')] + def testSetItem(self): s = self.s1.clone() s.append('xxx') @@ -1097,6 +1104,16 @@ class SequenceOf(BaseTestCase): assert len(s) == 3 assert s[1] == str2octs('') + def testSetItemSlice(self): + s = self.s1.clone() + s[:1] = ['xxx'] + assert s == [str2octs('xxx')] + s[-2:] = ['yyy', 'zzz'] + assert s == [str2octs('yyy'), str2octs('zzz')] + s[1:2] = ['yyy'] + assert s == [str2octs('yyy'), str2octs('yyy')] + assert len(s) == 2 + def testAppend(self): self.s1.clear() self.s1.setComponentByPosition(0, univ.OctetString('abc')) -- cgit v1.2.3 From 62efcb94b15ca7fbd5261ca999bf5eca5680de5f Mon Sep 17 00:00:00 2001 From: Ilya Etingof Date: Fri, 12 Jul 2019 22:42:04 +0200 Subject: Fix CER/DER encoders to respect open types (#167) * Fix CER/DER encoders to respect open types Added a bunch of unit tests to CER/DER codecs covering open types. --- pyasn1/codec/ber/encoder.py | 80 +++++------ pyasn1/codec/cer/encoder.py | 76 +++++----- tests/codec/ber/test_decoder.py | 2 +- tests/codec/ber/test_encoder.py | 20 +-- tests/codec/cer/test_decoder.py | 302 ++++++++++++++++++++++++++++++++++++++++ tests/codec/cer/test_encoder.py | 274 ++++++++++++++++++++++++++++++++---- tests/codec/der/test_decoder.py | 294 ++++++++++++++++++++++++++++++++++++++ tests/codec/der/test_encoder.py | 241 ++++++++++++++++++++++++++++++++ 8 files changed, 1165 insertions(+), 124 deletions(-) diff --git a/pyasn1/codec/ber/encoder.py b/pyasn1/codec/ber/encoder.py index b17fca8..fbad7e7 100644 --- a/pyasn1/codec/ber/encoder.py +++ b/pyasn1/codec/ber/encoder.py @@ -559,20 +559,20 @@ class SequenceEncoder(AbstractItemEncoder): options.update(ifNotEmpty=namedType.isOptional) # wrap open type blob if needed - if (namedTypes and namedType.openType - and namedType.asn1Object.tagSet): + if namedTypes and namedType.openType: - if component.typeId in ( - univ.SetOf.typeId, univ.SequenceOf.typeId): - substrate += encodeFun( + wrapType = namedType.asn1Object + + if wrapType.typeId in ( + univ.SetOf.typeId, univ.SequenceOf.typeId): + + substrate += encodeFun( component, asn1Spec, - **dict(options, openType=True)) + **dict(options, wrapType=wrapType.componentType)) else: chunk = encodeFun(component, asn1Spec, **options) - wrapType = namedType.asn1Object - if wrapType.isSameTypeWith(component): substrate += chunk @@ -609,67 +609,69 @@ class SequenceEncoder(AbstractItemEncoder): if omitEmptyOptionals: options.update(ifNotEmpty=namedType.isOptional) + componentSpec = namedType.asn1Object + # wrap open type blob if needed - if namedType.openType and namedType.asn1Object.tagSet: + if namedType.openType: - if component.typeId in ( - univ.SetOf.typeId, univ.SequenceOf.typeId): - substrate += encodeFun( - component, asn1Spec[idx], - **dict(options, openType=True)) + if componentSpec.typeId in ( + univ.SetOf.typeId, univ.SequenceOf.typeId): - else: - chunk = encodeFun(component, asn1Spec[idx], **options) + substrate += encodeFun( + component, componentSpec, + **dict(options, wrapType=componentSpec.componentType)) - wrapType = namedType.asn1Object + else: + chunk = encodeFun(component, componentSpec, **options) - if wrapType.isSameTypeWith(component): + if componentSpec.isSameTypeWith(component): substrate += chunk else: - substrate += encodeFun(chunk, wrapType, **options) + substrate += encodeFun(chunk, componentSpec, **options) if LOG: - LOG('wrapped with wrap type %r' % (wrapType,)) + LOG('wrapped with wrap type %r' % (componentSpec,)) else: - substrate += encodeFun(component, asn1Spec[idx], **options) + substrate += encodeFun(component, componentSpec, **options) return substrate, True, True class SequenceOfEncoder(AbstractItemEncoder): - def encodeValue(self, value, asn1Spec, encodeFun, **options): + def _encodeComponents(self, value, asn1Spec, encodeFun, **options): + if asn1Spec is None: value.verifySizeSpec() - wrapType = value.componentType - else: - asn1Spec = wrapType = asn1Spec.componentType + asn1Spec = asn1Spec.componentType - openType = options.pop('openType', False) + chunks = [] - substrate = null + wrapType = options.pop('wrapType', None) for idx, component in enumerate(value): - if openType: - # do not use asn1Spec even if given because it's a wrapper - chunk = encodeFun(component, **options) + chunk = encodeFun(component, asn1Spec, **options) - if not wrapType.isSameTypeWith(component): - # wrap encoded value with wrapper container (e.g. ANY) - chunk = encodeFun(chunk, wrapType, **options) + if (wrapType is not None and + not wrapType.isSameTypeWith(component)): + # wrap encoded value with wrapper container (e.g. ANY) + chunk = encodeFun(chunk, wrapType, **options) - if LOG: - LOG('wrapped with wrap type %r' % (wrapType,)) + if LOG: + LOG('wrapped with wrap type %r' % (wrapType,)) - else: - chunk = encodeFun(component, asn1Spec, **options) + chunks.append(chunk) - substrate += chunk + return chunks - return substrate, True, True + def encodeValue(self, value, asn1Spec, encodeFun, **options): + chunks = self._encodeComponents( + value, asn1Spec, encodeFun, **options) + + return null.join(chunks), True, True class ChoiceEncoder(AbstractItemEncoder): diff --git a/pyasn1/codec/cer/encoder.py b/pyasn1/codec/cer/encoder.py index 3fdcc1d..d7a1dfe 100644 --- a/pyasn1/codec/cer/encoder.py +++ b/pyasn1/codec/cer/encoder.py @@ -109,6 +109,37 @@ class UTCTimeEncoder(TimeEncoderMixIn, encoder.OctetStringEncoder): MAX_LENGTH = 14 +class SetOfEncoder(encoder.SequenceOfEncoder): + def encodeValue(self, value, asn1Spec, encodeFun, **options): + chunks = self._encodeComponents( + value, asn1Spec, encodeFun, **options) + + # sort by serialised and padded components + if len(chunks) > 1: + zero = str2octs('\x00') + maxLen = max(map(len, chunks)) + paddedChunks = [ + (x.ljust(maxLen, zero), x) for x in chunks + ] + paddedChunks.sort(key=lambda x: x[0]) + + chunks = [x[1] for x in paddedChunks] + + return null.join(chunks), True, True + + +class SequenceOfEncoder(encoder.SequenceOfEncoder): + def encodeValue(self, value, asn1Spec, encodeFun, **options): + + if options.get('ifNotEmpty', False) and not len(value): + return null, True, True + + chunks = self._encodeComponents( + value, asn1Spec, encodeFun, **options) + + return null.join(chunks), True, True + + class SetEncoder(encoder.SequenceEncoder): @staticmethod def _componentSortKey(componentAndType): @@ -197,55 +228,10 @@ class SetEncoder(encoder.SequenceEncoder): return substrate, True, True -class SetOfEncoder(encoder.SequenceOfEncoder): - def encodeValue(self, value, asn1Spec, encodeFun, **options): - if asn1Spec is None: - value.verifySizeSpec() - else: - asn1Spec = asn1Spec.componentType - - components = [encodeFun(x, asn1Spec, **options) - for x in value] - - # sort by serialised and padded components - if len(components) > 1: - zero = str2octs('\x00') - maxLen = max(map(len, components)) - paddedComponents = [ - (x.ljust(maxLen, zero), x) for x in components - ] - paddedComponents.sort(key=lambda x: x[0]) - - components = [x[1] for x in paddedComponents] - - substrate = null.join(components) - - return substrate, True, True - - class SequenceEncoder(encoder.SequenceEncoder): omitEmptyOptionals = True -class SequenceOfEncoder(encoder.SequenceOfEncoder): - def encodeValue(self, value, asn1Spec, encodeFun, **options): - - if options.get('ifNotEmpty', False) and not len(value): - return null, True, True - - if asn1Spec is None: - value.verifySizeSpec() - else: - asn1Spec = asn1Spec.componentType - - substrate = null - - for idx, component in enumerate(value): - substrate += encodeFun(value[idx], asn1Spec, **options) - - return substrate, True, True - - tagMap = encoder.tagMap.copy() tagMap.update({ univ.Boolean.tagSet: BooleanEncoder(), diff --git a/tests/codec/ber/test_decoder.py b/tests/codec/ber/test_decoder.py index 95f6c91..089f0f3 100644 --- a/tests/codec/ber/test_decoder.py +++ b/tests/codec/ber/test_decoder.py @@ -835,7 +835,7 @@ class SequenceDecoderWithSchemaTestCase(BaseTestCase): ) == (self.s, null) -class SequenceDecoderWithUnaggedOpenTypesTestCase(BaseTestCase): +class SequenceDecoderWithUntaggedOpenTypesTestCase(BaseTestCase): def setUp(self): openType = opentype.OpenType( 'id', diff --git a/tests/codec/ber/test_encoder.py b/tests/codec/ber/test_encoder.py index 26236af..38d75c0 100644 --- a/tests/codec/ber/test_encoder.py +++ b/tests/codec/ber/test_encoder.py @@ -762,7 +762,7 @@ class SequenceEncoderWithUntaggedOpenTypesTestCase(BaseTestCase): self.s[1] = univ.Integer(12) assert encoder.encode(self.s, asn1Spec=self.s) == ints2octs( - (48, 6, 2, 1, 1, 2, 1, 12) + (48, 5, 2, 1, 1, 49, 50) ) def testEncodeOpenTypeChoiceTwo(self): @@ -772,7 +772,7 @@ class SequenceEncoderWithUntaggedOpenTypesTestCase(BaseTestCase): self.s[1] = univ.OctetString('quick brown') assert encoder.encode(self.s, asn1Spec=self.s) == ints2octs( - (48, 16, 2, 1, 2, 4, 11, 113, 117, 105, 99, 107, 32, 98, 114, 111, 119, 110) + (48, 14, 2, 1, 2, 113, 117, 105, 99, 107, 32, 98, 114, 111, 119, 110) ) def testEncodeOpenTypeUnknownId(self): @@ -823,7 +823,7 @@ class SequenceEncoderWithImplicitlyTaggedOpenTypesTestCase(BaseTestCase): self.s[1] = univ.Integer(12) assert encoder.encode(self.s, asn1Spec=self.s) == ints2octs( - (48, 8, 2, 1, 1, 131, 3, 2, 1, 12) + (48, 9, 2, 1, 1, 131, 4, 131, 2, 49, 50) ) @@ -850,8 +850,8 @@ class SequenceEncoderWithExplicitlyTaggedOpenTypesTestCase(BaseTestCase): self.s[1] = univ.Integer(12) assert encoder.encode(self.s, asn1Spec=self.s) == ints2octs( - (48, 8, 2, 1, 1, 163, 3, 2, 1, 12) - ) + (48, 9, 2, 1, 1, 163, 4, 163, 2, 49, 50) + ) class SequenceEncoderWithUntaggedSetOfOpenTypesTestCase(BaseTestCase): @@ -878,7 +878,7 @@ class SequenceEncoderWithUntaggedSetOfOpenTypesTestCase(BaseTestCase): self.s[1].append(univ.Integer(12)) assert encoder.encode(self.s, asn1Spec=self.s) == ints2octs( - (48, 8, 2, 1, 1, 49, 3, 2, 1, 12) + (48, 7, 2, 1, 1, 49, 2, 49, 50) ) def testEncodeOpenTypeChoiceTwo(self): @@ -888,8 +888,8 @@ class SequenceEncoderWithUntaggedSetOfOpenTypesTestCase(BaseTestCase): self.s[1].append(univ.OctetString('quick brown')) assert encoder.encode(self.s, asn1Spec=self.s) == ints2octs( - (48, 18, 2, 1, 2, 49, 13, 4, 11, 113, 117, 105, 99, - 107, 32, 98, 114, 111, 119, 110) + (48, 16, 2, 1, 2, 49, 11, 113, 117, 105, 99, 107, 32, 98, 114, + 111, 119, 110) ) def testEncodeOpenTypeUnknownId(self): @@ -944,7 +944,7 @@ class SequenceEncoderWithImplicitlyTaggedSetOfOpenTypesTestCase(BaseTestCase): self.s[1].append(univ.Integer(12)) assert encoder.encode(self.s, asn1Spec=self.s) == ints2octs( - (48, 10, 2, 1, 1, 49, 5, 131, 3, 2, 1, 12) + (48, 11, 2, 1, 1, 49, 6, 131, 4, 131, 2, 49, 50) ) @@ -974,7 +974,7 @@ class SequenceEncoderWithExplicitlyTaggedSetOfOpenTypesTestCase(BaseTestCase): self.s[1].append(univ.Integer(12)) assert encoder.encode(self.s, asn1Spec=self.s) == ints2octs( - (48, 10, 2, 1, 1, 49, 5, 163, 3, 2, 1, 12) + (48, 11, 2, 1, 1, 49, 6, 163, 4, 163, 2, 49, 50) ) diff --git a/tests/codec/cer/test_decoder.py b/tests/codec/cer/test_decoder.py index d4e00ab..bb5ce93 100644 --- a/tests/codec/cer/test_decoder.py +++ b/tests/codec/cer/test_decoder.py @@ -13,6 +13,10 @@ except ImportError: from tests.base import BaseTestCase +from pyasn1.type import tag +from pyasn1.type import namedtype +from pyasn1.type import opentype +from pyasn1.type import univ from pyasn1.codec.cer import decoder from pyasn1.compat.octets import ints2octs, str2octs, null from pyasn1.error import PyAsn1Error @@ -65,6 +69,304 @@ class OctetStringDecoderTestCase(BaseTestCase): # TODO: test failures on short chunked and long unchunked substrate samples +class SequenceDecoderWithUntaggedOpenTypesTestCase(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, 128, 2, 1, 1, 2, 1, 12, 0, 0)), + 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, 128, 2, 1, 2, 4, 11, 113, 117, 105, 99, 107, 32, 98, + 114, 111, 119, 110, 0, 0)), asn1Spec=self.s, + decodeOpenTypes=True + ) + assert not r + assert s[0] == 2 + assert s[1] == univ.OctetString('quick brown') + + def testDecodeOpenTypesUnknownType(self): + try: + s, r = decoder.decode( + ints2octs((48, 128, 6, 1, 1, 2, 1, 12, 0, 0)), asn1Spec=self.s, + decodeOpenTypes=True + ) + + except PyAsn1Error: + pass + + else: + assert False, 'unknown open type tolerated' + + def testDecodeOpenTypesUnknownId(self): + s, r = decoder.decode( + ints2octs((48, 128, 2, 1, 3, 6, 1, 12, 0, 0)), asn1Spec=self.s, + decodeOpenTypes=True + ) + assert not r + assert s[0] == 3 + assert s[1] == univ.OctetString(hexValue='06010c') + + def testDontDecodeOpenTypesChoiceOne(self): + s, r = decoder.decode( + ints2octs((48, 128, 2, 1, 1, 2, 1, 12, 0, 0)), 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, 128, 2, 1, 2, 4, 11, 113, 117, 105, 99, 107, 32, 98, + 114, 111, 119, 110, 0, 0)), 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 SequenceDecoderWithImplicitlyTaggedOpenTypesTestCase(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().subtype(implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 3)), openType=openType + ) + ) + ) + + def testDecodeOpenTypesChoiceOne(self): + s, r = decoder.decode( + ints2octs((48, 128, 2, 1, 1, 163, 128, 2, 1, 12, 0, 0, 0, 0)), + asn1Spec=self.s, decodeOpenTypes=True + ) + assert not r + assert s[0] == 1 + assert s[1] == 12 + + def testDecodeOpenTypesUnknownId(self): + s, r = decoder.decode( + ints2octs((48, 128, 2, 1, 3, 163, 128, 2, 1, 12, 0, 0, 0, 0)), + asn1Spec=self.s, decodeOpenTypes=True + ) + assert not r + assert s[0] == 3 + assert s[1] == univ.OctetString(hexValue='02010C') + + +class SequenceDecoderWithExplicitlyTaggedOpenTypesTestCase(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().subtype(explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 3)), openType=openType + ) + ) + ) + + def testDecodeOpenTypesChoiceOne(self): + s, r = decoder.decode( + ints2octs((48, 128, 2, 1, 1, 163, 128, 2, 1, 12, 0, 0, 0, 0)), + asn1Spec=self.s, decodeOpenTypes=True + ) + assert not r + assert s[0] == 1 + assert s[1] == 12 + + def testDecodeOpenTypesUnknownId(self): + s, r = decoder.decode( + ints2octs((48, 128, 2, 1, 3, 163, 128, 2, 1, 12, 0, 0, 0, 0)), + asn1Spec=self.s, decodeOpenTypes=True + ) + assert not r + assert s[0] == 3 + assert s[1] == univ.OctetString(hexValue='02010C') + + +class SequenceDecoderWithUntaggedSetOfOpenTypesTestCase(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.SetOf(componentType=univ.Any()), + openType=openType) + ) + ) + + def testDecodeOpenTypesChoiceOne(self): + s, r = decoder.decode( + ints2octs((48, 128, 2, 1, 1, 49, 128, 2, 1, 12, 0, 0, 0, 0)), + asn1Spec=self.s, decodeOpenTypes=True + ) + assert not r + assert s[0] == 1 + assert s[1][0] == 12 + + def testDecodeOpenTypesChoiceTwo(self): + s, r = decoder.decode( + ints2octs((48, 128, 2, 1, 2, 49, 128, 4, 11, 113, 117, 105, 99, + 107, 32, 98, 114, 111, 119, 110, 0, 0, 0, 0)), + asn1Spec=self.s, decodeOpenTypes=True + ) + assert not r + assert s[0] == 2 + assert s[1][0] == univ.OctetString('quick brown') + + def testDecodeOpenTypesUnknownType(self): + try: + s, r = decoder.decode( + ints2octs((48, 128, 6, 1, 1, 49, 128, 2, 1, 12, 0, 0, 0, 0)), + asn1Spec=self.s, decodeOpenTypes=True + ) + + except PyAsn1Error: + pass + + else: + assert False, 'unknown open type tolerated' + + def testDecodeOpenTypesUnknownId(self): + s, r = decoder.decode( + ints2octs((48, 128, 2, 1, 3, 49, 128, 2, 1, 12, 0, 0, 0, 0)), + asn1Spec=self.s, decodeOpenTypes=True + ) + assert not r + assert s[0] == 3 + assert s[1][0] == univ.OctetString(hexValue='02010c') + + def testDontDecodeOpenTypesChoiceOne(self): + s, r = decoder.decode( + ints2octs((48, 128, 2, 1, 1, 49, 128, 2, 1, 12, 0, 0, 0, 0)), + asn1Spec=self.s + ) + assert not r + assert s[0] == 1 + assert s[1][0] == ints2octs((2, 1, 12)) + + def testDontDecodeOpenTypesChoiceTwo(self): + s, r = decoder.decode( + ints2octs((48, 128, 2, 1, 2, 49, 128, 4, 11, 113, 117, 105, 99, 107, 32, + 98, 114, 111, 119, 110, 0, 0, 0, 0)), asn1Spec=self.s + ) + assert not r + assert s[0] == 2 + assert s[1][0] == ints2octs((4, 11, 113, 117, 105, 99, 107, 32, 98, 114, + 111, 119, 110)) + + +class SequenceDecoderWithImplicitlyTaggedSetOfOpenTypesTestCase(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.SetOf( + componentType=univ.Any().subtype( + implicitTag=tag.Tag( + tag.tagClassContext, tag.tagFormatSimple, 3))), + openType=openType + ) + ) + ) + + def testDecodeOpenTypesChoiceOne(self): + s, r = decoder.decode( + ints2octs((48, 10, 2, 1, 1, 49, 5, 131, 3, 2, 1, 12)), + asn1Spec=self.s, decodeOpenTypes=True + ) + assert not r + assert s[0] == 1 + assert s[1][0] == 12 + + def testDecodeOpenTypesUnknownId(self): + s, r = decoder.decode( + ints2octs((48, 10, 2, 1, 3, 49, 5, 131, 3, 2, 1, 12)), + asn1Spec=self.s, decodeOpenTypes=True + ) + assert not r + assert s[0] == 3 + assert s[1][0] == univ.OctetString(hexValue='02010C') + + +class SequenceDecoderWithExplicitlyTaggedSetOfOpenTypesTestCase(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.SetOf( + componentType=univ.Any().subtype( + explicitTag=tag.Tag( + tag.tagClassContext, tag.tagFormatSimple, 3))), + openType=openType + ) + ) + ) + + def testDecodeOpenTypesChoiceOne(self): + s, r = decoder.decode( + ints2octs((48, 10, 2, 1, 1, 49, 5, 131, 3, 2, 1, 12)), + asn1Spec=self.s, decodeOpenTypes=True + ) + assert not r + assert s[0] == 1 + assert s[1][0] == 12 + + def testDecodeOpenTypesUnknownId(self): + s, r = decoder.decode( + ints2octs( (48, 10, 2, 1, 3, 49, 5, 131, 3, 2, 1, 12)), + asn1Spec=self.s, decodeOpenTypes=True + ) + assert not r + assert s[0] == 3 + assert s[1][0] == univ.OctetString(hexValue='02010C') + + suite = unittest.TestLoader().loadTestsFromModule(sys.modules[__name__]) if __name__ == '__main__': diff --git a/tests/codec/cer/test_encoder.py b/tests/codec/cer/test_encoder.py index 130fe17..e155571 100644 --- a/tests/codec/cer/test_encoder.py +++ b/tests/codec/cer/test_encoder.py @@ -15,6 +15,7 @@ from tests.base import BaseTestCase from pyasn1.type import tag from pyasn1.type import namedtype +from pyasn1.type import opentype from pyasn1.type import univ from pyasn1.type import useful from pyasn1.codec.cer import encoder @@ -385,7 +386,7 @@ class SetEncoderWithSchemaTestCase(BaseTestCase): ) == ints2octs((49, 128, 2, 1, 1, 4, 11, 113, 117, 105, 99, 107, 32, 98, 114, 111, 119, 110, 5, 0, 0, 0)) -class SetWithChoiceWithSchemaEncoderTestCase(BaseTestCase): +class SetEncoderWithChoiceWithSchemaEncoderTestCase(BaseTestCase): def setUp(self): BaseTestCase.setUp(self) c = univ.Choice(componentType=namedtype.NamedTypes( @@ -403,7 +404,7 @@ class SetWithChoiceWithSchemaEncoderTestCase(BaseTestCase): assert encoder.encode(self.s) == ints2octs((49, 128, 1, 1, 255, 5, 0, 0, 0)) -class SetWithTaggedChoiceEncoderTestCase(BaseTestCase): +class SetEncoderWithTaggedChoiceEncoderTestCase(BaseTestCase): def testWithUntaggedChoice(self): @@ -446,33 +447,6 @@ class SetWithTaggedChoiceEncoderTestCase(BaseTestCase): assert encoder.encode(s) == ints2octs((49, 128, 4, 1, 65, 167, 128, 1, 1, 255, 0, 0, 0, 0)) -class SetEncoderTestCase(BaseTestCase): - def setUp(self): - BaseTestCase.setUp(self) - self.s = univ.Set() - self.s.setComponentByPosition(0, univ.Null('')) - self.s.setComponentByPosition(1, univ.OctetString('quick brown')) - self.s.setComponentByPosition(2, univ.Integer(1)) - - def testIndefMode(self): - assert encoder.encode(self.s) == ints2octs((49, 128, 2, 1, 1, 4, 11, 113, 117, 105, 99, 107, 32, 98, 114, 111, 119, 110, 5, 0, 0, 0)) - - def testWithOptionalIndefMode(self): - assert encoder.encode( - self.s - ) == ints2octs((49, 128, 2, 1, 1, 4, 11, 113, 117, 105, 99, 107, 32, 98, 114, 111, 119, 110, 5, 0, 0, 0)) - - def testWithDefaultedIndefMode(self): - assert encoder.encode( - self.s - ) == ints2octs((49, 128, 2, 1, 1, 4, 11, 113, 117, 105, 99, 107, 32, 98, 114, 111, 119, 110, 5, 0, 0, 0)) - - def testWithOptionalAndDefaultedIndefMode(self): - assert encoder.encode( - self.s - ) == ints2octs((49, 128, 2, 1, 1, 4, 11, 113, 117, 105, 99, 107, 32, 98, 114, 111, 119, 110, 5, 0, 0, 0)) - - class SequenceEncoderTestCase(BaseTestCase): def setUp(self): BaseTestCase.setUp(self) @@ -554,6 +528,248 @@ class SequenceEncoderWithSchemaTestCase(BaseTestCase): ) == ints2octs((48, 128, 5, 0, 4, 11, 113, 117, 105, 99, 107, 32, 98, 114, 111, 119, 110, 2, 1, 1, 0, 0)) +class SequenceEncoderWithUntaggedOpenTypesTestCase(BaseTestCase): + def setUp(self): + BaseTestCase.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 testEncodeOpenTypeChoiceOne(self): + self.s.clear() + + self.s[0] = 1 + self.s[1] = univ.Integer(12) + + assert encoder.encode(self.s, asn1Spec=self.s) == ints2octs( + (48, 128, 2, 1, 1, 49, 50, 0, 0) + ) + + def testEncodeOpenTypeChoiceTwo(self): + self.s.clear() + + self.s[0] = 2 + self.s[1] = univ.OctetString('quick brown') + + assert encoder.encode(self.s, asn1Spec=self.s) == ints2octs( + (48, 128, 2, 1, 2, 113, 117, 105, 99, 107, 32, 98, 114, + 111, 119, 110, 0, 0) + ) + + def testEncodeOpenTypeUnknownId(self): + self.s.clear() + + self.s[0] = 2 + self.s[1] = univ.ObjectIdentifier('1.3.6') + + try: + encoder.encode(self.s, asn1Spec=self.s) + + except PyAsn1Error: + assert False, 'incompatible open type tolerated' + + def testEncodeOpenTypeIncompatibleType(self): + self.s.clear() + + self.s[0] = 2 + self.s[1] = univ.ObjectIdentifier('1.3.6') + + try: + encoder.encode(self.s, asn1Spec=self.s) + + except PyAsn1Error: + assert False, 'incompatible open type tolerated' + + +class SequenceEncoderWithImplicitlyTaggedOpenTypesTestCase(BaseTestCase): + def setUp(self): + BaseTestCase.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().subtype(implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 3)), openType=openType) + ) + ) + + def testEncodeOpenTypeChoiceOne(self): + self.s.clear() + + self.s[0] = 1 + self.s[1] = univ.Integer(12) + + assert encoder.encode(self.s, asn1Spec=self.s) == ints2octs( + (48, 128, 2, 1, 1, 163, 128, 163, 128, 49, 50, 0, 0, 0, 0, 0, 0) + ) + + +class SequenceEncoderWithExplicitlyTaggedOpenTypesTestCase(BaseTestCase): + def setUp(self): + BaseTestCase.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().subtype(explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 3)), openType=openType) + ) + ) + + def testEncodeOpenTypeChoiceOne(self): + self.s.clear() + + self.s[0] = 1 + self.s[1] = univ.Integer(12) + + assert encoder.encode(self.s, asn1Spec=self.s) == ints2octs( + (48, 128, 2, 1, 1, 163, 128, 163, 128, 49, 50, 0, 0, 0, 0, 0, 0) + ) + + +class SequenceEncoderWithUntaggedSetOfOpenTypesTestCase(BaseTestCase): + def setUp(self): + BaseTestCase.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.SetOf( + componentType=univ.Any()), openType=openType) + ) + ) + + def testEncodeOpenTypeChoiceOne(self): + self.s.clear() + + self.s[0] = 1 + self.s[1].append(univ.Integer(12)) + + assert encoder.encode(self.s, asn1Spec=self.s) == ints2octs( + (48, 128, 2, 1, 1, 49, 128, 49, 50, 0, 0, 0, 0) + ) + + def testEncodeOpenTypeChoiceTwo(self): + self.s.clear() + + self.s[0] = 2 + self.s[1].append(univ.OctetString('quick brown')) + + assert encoder.encode(self.s, asn1Spec=self.s) == ints2octs( + (48, 128, 2, 1, 2, 49, 128, 113, 117, 105, 99, 107, 32, 98, 114, + 111, 119, 110, 0, 0, 0, 0) + ) + + def testEncodeOpenTypeUnknownId(self): + self.s.clear() + + self.s[0] = 2 + self.s[1].append(univ.ObjectIdentifier('1.3.6')) + + try: + encoder.encode(self.s, asn1Spec=self.s) + + except PyAsn1Error: + assert False, 'incompatible open type tolerated' + + def testEncodeOpenTypeIncompatibleType(self): + self.s.clear() + + self.s[0] = 2 + self.s[1].append(univ.ObjectIdentifier('1.3.6')) + + try: + encoder.encode(self.s, asn1Spec=self.s) + + except PyAsn1Error: + assert False, 'incompatible open type tolerated' + + +class SequenceEncoderWithImplicitlyTaggedSetOfOpenTypesTestCase(BaseTestCase): + def setUp(self): + BaseTestCase.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.SetOf( + componentType=univ.Any().subtype( + implicitTag=tag.Tag( + tag.tagClassContext, tag.tagFormatSimple, 3))), + openType=openType) + ) + ) + + def testEncodeOpenTypeChoiceOne(self): + self.s.clear() + + self.s[0] = 1 + self.s[1].append(univ.Integer(12)) + + assert encoder.encode(self.s, asn1Spec=self.s) == ints2octs( + (48, 128, 2, 1, 1, 49, 128, 163, 128, 163, 128, 49, 50, 0, 0, + 0, 0, 0, 0, 0, 0) + ) + + +class SequenceEncoderWithExplicitlyTaggedSetOfOpenTypesTestCase(BaseTestCase): + def setUp(self): + BaseTestCase.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.SetOf( + componentType=univ.Any().subtype( + explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 3))), + openType=openType) + ) + ) + + def testEncodeOpenTypeChoiceOne(self): + self.s.clear() + + self.s[0] = 1 + self.s[1].append(univ.Integer(12)) + + assert encoder.encode(self.s, asn1Spec=self.s) == ints2octs( + (48, 128, 2, 1, 1, 49, 128, 163, 128, 163, 128, 49, 50, 0, 0, + 0, 0, 0, 0, 0, 0) + ) + + class NestedOptionalSequenceEncoderTestCase(BaseTestCase): def setUp(self): BaseTestCase.setUp(self) diff --git a/tests/codec/der/test_decoder.py b/tests/codec/der/test_decoder.py index a76c435..51ce296 100644 --- a/tests/codec/der/test_decoder.py +++ b/tests/codec/der/test_decoder.py @@ -14,6 +14,10 @@ except ImportError: from tests.base import BaseTestCase +from pyasn1.type import tag +from pyasn1.type import namedtype +from pyasn1.type import opentype +from pyasn1.type import univ from pyasn1.codec.der import decoder from pyasn1.compat.octets import ints2octs, null from pyasn1.error import PyAsn1Error @@ -73,6 +77,296 @@ class OctetStringDecoderTestCase(BaseTestCase): assert 0, 'chunked encoding tolerated' +class SequenceDecoderWithUntaggedOpenTypesTestCase(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 testDecodeOpenTypesUnknownType(self): + try: + s, r = decoder.decode( + ints2octs((48, 6, 2, 1, 2, 6, 1, 39)), asn1Spec=self.s, + decodeOpenTypes=True + ) + + except PyAsn1Error: + pass + + else: + assert False, 'unknown open type tolerated' + + def testDecodeOpenTypesUnknownId(self): + s, r = decoder.decode( + ints2octs((48, 6, 2, 1, 3, 6, 1, 39)), asn1Spec=self.s, + decodeOpenTypes=True + ) + assert not r + assert s[0] == 3 + assert s[1] == univ.OctetString(hexValue='060127') + + 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 SequenceDecoderWithImplicitlyTaggedOpenTypesTestCase(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().subtype(implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 3)), openType=openType + ) + ) + ) + + def testDecodeOpenTypesChoiceOne(self): + s, r = decoder.decode( + ints2octs((48, 8, 2, 1, 1, 131, 3, 2, 1, 12)), asn1Spec=self.s, decodeOpenTypes=True + ) + assert not r + assert s[0] == 1 + assert s[1] == 12 + + def testDecodeOpenTypesUnknownId(self): + s, r = decoder.decode( + ints2octs((48, 8, 2, 1, 3, 131, 3, 2, 1, 12)), asn1Spec=self.s, decodeOpenTypes=True + ) + assert not r + assert s[0] == 3 + assert s[1] == univ.OctetString(hexValue='02010C') + + +class SequenceDecoderWithExplicitlyTaggedOpenTypesTestCase(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().subtype(explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 3)), openType=openType + ) + ) + ) + + def testDecodeOpenTypesChoiceOne(self): + s, r = decoder.decode( + ints2octs((48, 8, 2, 1, 1, 163, 3, 2, 1, 12)), asn1Spec=self.s, decodeOpenTypes=True + ) + assert not r + assert s[0] == 1 + assert s[1] == 12 + + def testDecodeOpenTypesUnknownId(self): + s, r = decoder.decode( + ints2octs((48, 8, 2, 1, 3, 163, 3, 2, 1, 12)), asn1Spec=self.s, decodeOpenTypes=True + ) + assert not r + assert s[0] == 3 + assert s[1] == univ.OctetString(hexValue='02010C') + + +class SequenceDecoderWithUnaggedSetOfOpenTypesTestCase(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.SetOf(componentType=univ.Any()), + openType=openType) + ) + ) + + def testDecodeOpenTypesChoiceOne(self): + s, r = decoder.decode( + ints2octs((48, 8, 2, 1, 1, 49, 3, 2, 1, 12)), asn1Spec=self.s, + decodeOpenTypes=True + ) + assert not r + assert s[0] == 1 + assert s[1][0] == 12 + + def testDecodeOpenTypesChoiceTwo(self): + s, r = decoder.decode( + ints2octs((48, 18, 2, 1, 2, 49, 13, 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][0] == univ.OctetString('quick brown') + + def testDecodeOpenTypesUnknownType(self): + try: + s, r = decoder.decode( + ints2octs((48, 6, 2, 1, 2, 6, 1, 39)), asn1Spec=self.s, + decodeOpenTypes=True + ) + + except PyAsn1Error: + pass + + else: + assert False, 'unknown open type tolerated' + + def testDecodeOpenTypesUnknownId(self): + s, r = decoder.decode( + ints2octs((48, 8, 2, 1, 3, 49, 3, 2, 1, 12)), asn1Spec=self.s, + decodeOpenTypes=True + ) + assert not r + assert s[0] == 3 + assert s[1][0] == univ.OctetString(hexValue='02010c') + + def testDontDecodeOpenTypesChoiceOne(self): + s, r = decoder.decode( + ints2octs((48, 8, 2, 1, 1, 49, 3, 2, 1, 12)), asn1Spec=self.s + ) + assert not r + assert s[0] == 1 + assert s[1][0] == ints2octs((2, 1, 12)) + + def testDontDecodeOpenTypesChoiceTwo(self): + s, r = decoder.decode( + ints2octs((48, 18, 2, 1, 2, 49, 13, 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][0] == ints2octs((4, 11, 113, 117, 105, 99, 107, 32, 98, 114, + 111, 119, 110)) + + +class SequenceDecoderWithImplicitlyTaggedSetOfOpenTypesTestCase(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.SetOf( + componentType=univ.Any().subtype( + implicitTag=tag.Tag( + tag.tagClassContext, tag.tagFormatSimple, 3))), + openType=openType + ) + ) + ) + + def testDecodeOpenTypesChoiceOne(self): + s, r = decoder.decode( + ints2octs((48, 10, 2, 1, 1, 49, 5, 131, 3, 2, 1, 12)), + asn1Spec=self.s, decodeOpenTypes=True + ) + assert not r + assert s[0] == 1 + assert s[1][0] == 12 + + def testDecodeOpenTypesUnknownId(self): + s, r = decoder.decode( + ints2octs((48, 10, 2, 1, 3, 49, 5, 131, 3, 2, 1, 12)), + asn1Spec=self.s, decodeOpenTypes=True + ) + assert not r + assert s[0] == 3 + assert s[1][0] == univ.OctetString(hexValue='02010C') + + +class SequenceDecoderWithExplicitlyTaggedSetOfOpenTypesTestCase(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.SetOf( + componentType=univ.Any().subtype( + explicitTag=tag.Tag( + tag.tagClassContext, tag.tagFormatSimple, 3))), + openType=openType + ) + ) + ) + + def testDecodeOpenTypesChoiceOne(self): + s, r = decoder.decode( + ints2octs((48, 10, 2, 1, 1, 49, 5, 131, 3, 2, 1, 12)), + asn1Spec=self.s, decodeOpenTypes=True + ) + assert not r + assert s[0] == 1 + assert s[1][0] == 12 + + def testDecodeOpenTypesUnknownId(self): + s, r = decoder.decode( + ints2octs( (48, 10, 2, 1, 3, 49, 5, 131, 3, 2, 1, 12)), + asn1Spec=self.s, decodeOpenTypes=True + ) + assert not r + assert s[0] == 3 + assert s[1][0] == univ.OctetString(hexValue='02010C') + + suite = unittest.TestLoader().loadTestsFromModule(sys.modules[__name__]) if __name__ == '__main__': diff --git a/tests/codec/der/test_encoder.py b/tests/codec/der/test_encoder.py index 75835f2..912e32c 100644 --- a/tests/codec/der/test_encoder.py +++ b/tests/codec/der/test_encoder.py @@ -16,6 +16,7 @@ from tests.base import BaseTestCase from pyasn1.type import tag from pyasn1.type import namedtype +from pyasn1.type import opentype from pyasn1.type import univ from pyasn1.codec.der import encoder from pyasn1.compat.octets import ints2octs @@ -148,6 +149,246 @@ class SetWithTaggedChoiceEncoderTestCase(BaseTestCase): assert encoder.encode(s) == ints2octs((49, 8, 4, 1, 65, 167, 3, 1, 1, 255)) +class SequenceEncoderWithUntaggedOpenTypesTestCase(BaseTestCase): + def setUp(self): + BaseTestCase.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 testEncodeOpenTypeChoiceOne(self): + self.s.clear() + + self.s[0] = 1 + self.s[1] = univ.Integer(12) + + assert encoder.encode(self.s, asn1Spec=self.s) == ints2octs( + (48, 5, 2, 1, 1, 49, 50) + ) + + def testEncodeOpenTypeChoiceTwo(self): + self.s.clear() + + self.s[0] = 2 + self.s[1] = univ.OctetString('quick brown') + + assert encoder.encode(self.s, asn1Spec=self.s) == ints2octs( + (48, 14, 2, 1, 2, 113, 117, 105, 99, 107, 32, + 98, 114, 111, 119, 110) + ) + + def testEncodeOpenTypeUnknownId(self): + self.s.clear() + + self.s[0] = 2 + self.s[1] = univ.ObjectIdentifier('1.3.6') + + try: + encoder.encode(self.s, asn1Spec=self.s) + + except PyAsn1Error: + assert False, 'incompatible open type tolerated' + + def testEncodeOpenTypeIncompatibleType(self): + self.s.clear() + + self.s[0] = 2 + self.s[1] = univ.ObjectIdentifier('1.3.6') + + try: + encoder.encode(self.s, asn1Spec=self.s) + + except PyAsn1Error: + assert False, 'incompatible open type tolerated' + + +class SequenceEncoderWithImplicitlyTaggedOpenTypesTestCase(BaseTestCase): + def setUp(self): + BaseTestCase.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().subtype(implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 3)), openType=openType) + ) + ) + + def testEncodeOpenTypeChoiceOne(self): + self.s.clear() + + self.s[0] = 1 + self.s[1] = univ.Integer(12) + + assert encoder.encode(self.s, asn1Spec=self.s) == ints2octs( + (48, 9, 2, 1, 1, 131, 4, 131, 2, 49, 50) + ) + + +class SequenceEncoderWithExplicitlyTaggedOpenTypesTestCase(BaseTestCase): + def setUp(self): + BaseTestCase.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().subtype(explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 3)), openType=openType) + ) + ) + + def testEncodeOpenTypeChoiceOne(self): + self.s.clear() + + self.s[0] = 1 + self.s[1] = univ.Integer(12) + + assert encoder.encode(self.s, asn1Spec=self.s) == ints2octs( + (48, 9, 2, 1, 1, 163, 4, 163, 2, 49, 50) + ) + + +class SequenceEncoderWithUntaggedSetOfOpenTypesTestCase(BaseTestCase): + def setUp(self): + BaseTestCase.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.SetOf( + componentType=univ.Any()), openType=openType) + ) + ) + + def testEncodeOpenTypeChoiceOne(self): + self.s.clear() + + self.s[0] = 1 + self.s[1].append(univ.Integer(12)) + + assert encoder.encode(self.s, asn1Spec=self.s) == ints2octs( + (48, 7, 2, 1, 1, 49, 2, 49, 50) + ) + + def testEncodeOpenTypeChoiceTwo(self): + self.s.clear() + + self.s[0] = 2 + self.s[1].append(univ.OctetString('quick brown')) + + assert encoder.encode(self.s, asn1Spec=self.s) == ints2octs( + (48, 16, 2, 1, 2, 49, 11, 113, 117, 105, 99, 107, 32, 98, 114, + 111, 119, 110) + ) + + def testEncodeOpenTypeUnknownId(self): + self.s.clear() + + self.s[0] = 2 + self.s[1].append(univ.ObjectIdentifier('1.3.6')) + + try: + encoder.encode(self.s, asn1Spec=self.s) + + except PyAsn1Error: + assert False, 'incompatible open type tolerated' + + def testEncodeOpenTypeIncompatibleType(self): + self.s.clear() + + self.s[0] = 2 + self.s[1].append(univ.ObjectIdentifier('1.3.6')) + + try: + encoder.encode(self.s, asn1Spec=self.s) + + except PyAsn1Error: + assert False, 'incompatible open type tolerated' + + +class SequenceEncoderWithImplicitlyTaggedSetOfOpenTypesTestCase(BaseTestCase): + def setUp(self): + BaseTestCase.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.SetOf( + componentType=univ.Any().subtype( + implicitTag=tag.Tag( + tag.tagClassContext, tag.tagFormatSimple, 3))), + openType=openType) + ) + ) + + def testEncodeOpenTypeChoiceOne(self): + self.s.clear() + + self.s[0] = 1 + self.s[1].append(univ.Integer(12)) + + assert encoder.encode(self.s, asn1Spec=self.s) == ints2octs( + (48, 11, 2, 1, 1, 49, 6, 131, 4, 131, 2, 49, 50) + ) + + +class SequenceEncoderWithExplicitlyTaggedSetOfOpenTypesTestCase(BaseTestCase): + def setUp(self): + BaseTestCase.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.SetOf( + componentType=univ.Any().subtype( + explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 3))), + openType=openType) + ) + ) + + def testEncodeOpenTypeChoiceOne(self): + self.s.clear() + + self.s[0] = 1 + self.s[1].append(univ.Integer(12)) + + assert encoder.encode(self.s, asn1Spec=self.s) == ints2octs( + (48, 11, 2, 1, 1, 49, 6, 163, 4, 163, 2, 49, 50) + ) + + class NestedOptionalSequenceEncoderTestCase(BaseTestCase): def setUp(self): BaseTestCase.setUp(self) -- cgit v1.2.3 From bce62d5e146a83d3c2d003443875d16271e30a61 Mon Sep 17 00:00:00 2001 From: Ilya Etingof Date: Fri, 12 Jul 2019 23:24:08 +0200 Subject: Fix to pass decoder `options` to open type decoder Prior to this fix, recursively encoded open types won't get fully decoded all the way. --- pyasn1/codec/ber/decoder.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/pyasn1/codec/ber/decoder.py b/pyasn1/codec/ber/decoder.py index 655af04..ddd8f83 100644 --- a/pyasn1/codec/ber/decoder.py +++ b/pyasn1/codec/ber/decoder.py @@ -681,7 +681,7 @@ class UniversalConstructedTypeDecoder(AbstractConstructedDecoder): component, rest = decodeFun( containerValue[pos].asOctets(), - asn1Spec=openType + asn1Spec=openType, **options ) containerValue[pos] = component @@ -689,7 +689,7 @@ class UniversalConstructedTypeDecoder(AbstractConstructedDecoder): else: component, rest = decodeFun( asn1Object.getComponentByPosition(idx).asOctets(), - asn1Spec=openType + asn1Spec=openType, **options ) asn1Object.setComponentByPosition(idx, component) @@ -741,7 +741,8 @@ class UniversalConstructedTypeDecoder(AbstractConstructedDecoder): if asn1Spec is None: return self._decodeComponents( - substrate, tagSet=tagSet, decodeFun=decodeFun, allowEoo=True, **options + substrate, tagSet=tagSet, decodeFun=decodeFun, + **dict(options, allowEoo=True) ) asn1Object = asn1Spec.clone() @@ -863,7 +864,7 @@ class UniversalConstructedTypeDecoder(AbstractConstructedDecoder): component, rest = decodeFun( containerValue[pos].asOctets(), - asn1Spec=openType, allowEoo=True + asn1Spec=openType, **dict(options, allowEoo=True) ) containerValue[pos] = component @@ -871,7 +872,7 @@ class UniversalConstructedTypeDecoder(AbstractConstructedDecoder): else: component, rest = decodeFun( asn1Object.getComponentByPosition(idx).asOctets(), - asn1Spec=openType, allowEoo=True + asn1Spec=openType, **dict(options, allowEoo=True) ) if component is not eoo.endOfOctets: -- cgit v1.2.3 From 4b24fcb98a21a6259ab1b71cd46f382401fad508 Mon Sep 17 00:00:00 2001 From: Ilya Etingof Date: Sat, 13 Jul 2019 09:50:54 +0200 Subject: Add docstring for `.reset()` and `.clear()` methods --- docs/source/pyasn1/type/univ/sequence.rst | 3 ++- docs/source/pyasn1/type/univ/sequenceof.rst | 2 +- docs/source/pyasn1/type/univ/set.rst | 2 +- docs/source/pyasn1/type/univ/setof.rst | 2 +- pyasn1/type/univ.py | 26 ++++++++++++++++++++++++++ 5 files changed, 31 insertions(+), 4 deletions(-) diff --git a/docs/source/pyasn1/type/univ/sequence.rst b/docs/source/pyasn1/type/univ/sequence.rst index f48f497..0988004 100644 --- a/docs/source/pyasn1/type/univ/sequence.rst +++ b/docs/source/pyasn1/type/univ/sequence.rst @@ -8,7 +8,8 @@ .. autoclass:: pyasn1.type.univ.Sequence(componentType=NamedTypes(), tagSet=tagSet(), subtypeSpec=ConstraintsIntersection(), sizeSpec=ConstraintsIntersection()) :members: isValue, isSameTypeWith, isSuperTypeOf, tagSet, effectiveTagSet, tagMap, componentType, subtypeSpec, sizeSpec, getComponentByPosition, - setComponentByPosition, getComponentByName, setComponentByName, setDefaultComponents + setComponentByPosition, getComponentByName, setComponentByName, setDefaultComponents, + clear, reset .. note:: diff --git a/docs/source/pyasn1/type/univ/sequenceof.rst b/docs/source/pyasn1/type/univ/sequenceof.rst index ed6a781..6a09b92 100644 --- a/docs/source/pyasn1/type/univ/sequenceof.rst +++ b/docs/source/pyasn1/type/univ/sequenceof.rst @@ -8,7 +8,7 @@ .. 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 + getComponentByPosition, setComponentByPosition, clear, reset .. note:: diff --git a/docs/source/pyasn1/type/univ/set.rst b/docs/source/pyasn1/type/univ/set.rst index 229525a..792d2e9 100644 --- a/docs/source/pyasn1/type/univ/set.rst +++ b/docs/source/pyasn1/type/univ/set.rst @@ -9,7 +9,7 @@ .. autoclass:: pyasn1.type.univ.Set(componentType=NamedTypes(), tagSet=TagSet(), subtypeSpec=ConstraintsIntersection(), sizeSpec=ConstraintsIntersection()) :members: isValue, isSameTypeWith, isSuperTypeOf, tagSet, effectiveTagSet, tagMap, componentType, subtypeSpec, sizeSpec, getComponentByPosition, setComponentByPosition, getComponentByName, setComponentByName, setDefaultComponents, - getComponentByType, setComponentByType + getComponentByType, setComponentByType, clear, reset .. note:: diff --git a/docs/source/pyasn1/type/univ/setof.rst b/docs/source/pyasn1/type/univ/setof.rst index 2745677..67ef92f 100644 --- a/docs/source/pyasn1/type/univ/setof.rst +++ b/docs/source/pyasn1/type/univ/setof.rst @@ -8,7 +8,7 @@ .. 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 +  getComponentByPosition, setComponentByPosition, clear, reset .. note:: diff --git a/pyasn1/type/univ.py b/pyasn1/type/univ.py index 0ba59a7..93114d6 100644 --- a/pyasn1/type/univ.py +++ b/pyasn1/type/univ.py @@ -1922,10 +1922,20 @@ class SequenceOfAndSetOfBase(base.AbstractConstructedAsn1Item): for idx in sorted(self._componentValues)] def clear(self): + """Remove all components and become an empty |ASN.1| value object. + + Has the same effect on |ASN.1| object as it does on :class:`list` + built-in. + """ self._componentValues = {} return self def reset(self): + """Remove all components and become a |ASN.1| schema object. + + See :meth:`isValue` property for more information on the + distinction between value and schema objects. + """ self._componentValues = noValue return self @@ -2217,11 +2227,21 @@ class SequenceAndSetBase(base.AbstractConstructedAsn1Item): self[k] = mappingValue[k] def clear(self): + """Remove all components and become an empty |ASN.1| value object. + + Has the same effect on |ASN.1| object as it does on :class:`dict` + built-in. + """ self._componentValues = [] self._dynamicNames = self.DynamicNames() return self def reset(self): + """Remove all components and become a |ASN.1| schema object. + + See :meth:`isValue` property for more information on the + distinction between value and schema objects. + """ self._componentValues = noValue self._dynamicNames = self.DynamicNames() return self @@ -2556,6 +2576,12 @@ class SequenceAndSetBase(base.AbstractConstructedAsn1Item): The PyASN1 value objects can **additionally** participate in many operations involving regular Python objects (e.g. arithmetic, comprehension etc). + + It is sufficient for |ASN.1| objects to have all non-optional and non-defaulted + components being value objects to be considered as a value objects as a whole. + In other words, even having one or more optional components not turned into + value objects, |ASN.1| object is still considered as a value object. Defaulted + components are normally value objects by default. """ if self._componentValues is noValue: return False -- cgit v1.2.3 From e1500c60c0a15a641d5ee834f792fd0f2167c8b8 Mon Sep 17 00:00:00 2001 From: Ilya Etingof Date: Sat, 13 Jul 2019 11:09:28 +0200 Subject: Add exception classes documentation Also fix references to exception objects in other docstrings. --- docs/source/pyasn1/contents.rst | 13 ++++++++ docs/source/pyasn1/error/contents.rst | 60 +++++++++++++++++++++++++++++++++++ pyasn1/codec/ber/decoder.py | 2 +- pyasn1/codec/ber/encoder.py | 2 +- pyasn1/codec/cer/decoder.py | 2 +- pyasn1/codec/cer/encoder.py | 2 +- pyasn1/codec/der/decoder.py | 2 +- pyasn1/codec/der/encoder.py | 2 +- pyasn1/codec/native/decoder.py | 2 +- pyasn1/codec/native/encoder.py | 2 +- pyasn1/error.py | 32 ++++++++++++++----- pyasn1/type/char.py | 2 +- pyasn1/type/namedtype.py | 12 +++---- pyasn1/type/univ.py | 20 ++++++------ 14 files changed, 122 insertions(+), 33 deletions(-) create mode 100644 docs/source/pyasn1/error/contents.rst diff --git a/docs/source/pyasn1/contents.rst b/docs/source/pyasn1/contents.rst index 474bbff..554ebe9 100644 --- a/docs/source/pyasn1/contents.rst +++ b/docs/source/pyasn1/contents.rst @@ -212,3 +212,16 @@ implemented in pyasn1. /pyasn1/codec/cer/contents /pyasn1/codec/der/contents /pyasn1/codec/native/contents + +Exceptions +---------- + +Operations on PyASN1 schema and value objects might cause errors. These +errors are manifested to the caller in form of Python exceptions. + +The exception hierarchy is as follows (ordered from least specific). + +.. toctree:: + :maxdepth: 2 + + /pyasn1/error/contents diff --git a/docs/source/pyasn1/error/contents.rst b/docs/source/pyasn1/error/contents.rst new file mode 100644 index 0000000..be8a04c --- /dev/null +++ b/docs/source/pyasn1/error/contents.rst @@ -0,0 +1,60 @@ + +.. _error.PyAsn1Error: + +.. |PyAsn1Error| replace:: PyAsn1Error + +|PyAsn1Error| +------------- + +.. autoclass:: pyasn1.error.PyAsn1Error + :members: + +.. _error.ValueConstraintError: + +.. |ValueConstraintError| replace:: ValueConstraintError + +|ValueConstraintError| +---------------------- + +.. autoclass:: pyasn1.error.ValueConstraintError + :members: + +.. _error.SubstrateUnderrunError: + +.. |SubstrateUnderrunError| replace:: SubstrateUnderrunError + +|SubstrateUnderrunError| +------------------------ + +.. autoclass:: pyasn1.error.SubstrateUnderrunError + :members: + +.. _error.PyAsn1UnicodeError: + +.. |PyAsn1UnicodeError| replace:: PyAsn1UnicodeError + +|PyAsn1UnicodeError| +-------------------- + +.. autoclass:: pyasn1.error.PyAsn1UnicodeError + :members: + +.. _error.PyAsn1UnicodeDecodeError: + +.. |PyAsn1UnicodeDecodeError| replace:: PyAsn1UnicodeDecodeError + +|PyAsn1UnicodeDecodeError| +-------------------------- + +.. autoclass:: pyasn1.error.PyAsn1UnicodeDecodeError + :members: + +.. _error.PyAsn1UnicodeEncodeError: + +.. |PyAsn1UnicodeEncodeError| replace:: PyAsn1UnicodeEncodeError + +|PyAsn1UnicodeEncodeError| +-------------------------- + +.. autoclass:: pyasn1.error.PyAsn1UnicodeEncodeError + :members: diff --git a/pyasn1/codec/ber/decoder.py b/pyasn1/codec/ber/decoder.py index ddd8f83..3f2d180 100644 --- a/pyasn1/codec/ber/decoder.py +++ b/pyasn1/codec/ber/decoder.py @@ -1620,7 +1620,7 @@ class Decoder(object): #: #: Raises #: ------ -#: :py:class:`~pyasn1.error.PyAsn1Error` +#: ~pyasn1.error.PyAsn1Error, ~pyasn1.error.SubstrateUnderrunError #: On decoding errors #: #: Examples diff --git a/pyasn1/codec/ber/encoder.py b/pyasn1/codec/ber/encoder.py index fbad7e7..58e71e7 100644 --- a/pyasn1/codec/ber/encoder.py +++ b/pyasn1/codec/ber/encoder.py @@ -861,7 +861,7 @@ class Encoder(object): #: #: Raises #: ------ -#: :py:class:`~pyasn1.error.PyAsn1Error` +#: ~pyasn1.error.PyAsn1Error #: On encoding errors #: #: Examples diff --git a/pyasn1/codec/cer/decoder.py b/pyasn1/codec/cer/decoder.py index 5099e3c..3e86fd0 100644 --- a/pyasn1/codec/cer/decoder.py +++ b/pyasn1/codec/cer/decoder.py @@ -87,7 +87,7 @@ class Decoder(decoder.Decoder): #: #: Raises #: ------ -#: :py:class:`~pyasn1.error.PyAsn1Error` +#: ~pyasn1.error.PyAsn1Error, ~pyasn1.error.SubstrateUnderrunError #: On decoding errors #: #: Examples diff --git a/pyasn1/codec/cer/encoder.py b/pyasn1/codec/cer/encoder.py index d7a1dfe..6a9db97 100644 --- a/pyasn1/codec/cer/encoder.py +++ b/pyasn1/codec/cer/encoder.py @@ -284,7 +284,7 @@ class Encoder(encoder.Encoder): #: #: Raises #: ------ -#: :py:class:`~pyasn1.error.PyAsn1Error` +#: ~pyasn1.error.PyAsn1Error #: On encoding errors #: #: Examples diff --git a/pyasn1/codec/der/decoder.py b/pyasn1/codec/der/decoder.py index 261bab8..1a13fdb 100644 --- a/pyasn1/codec/der/decoder.py +++ b/pyasn1/codec/der/decoder.py @@ -67,7 +67,7 @@ class Decoder(decoder.Decoder): #: #: Raises #: ------ -#: :py:class:`~pyasn1.error.PyAsn1Error` +#: ~pyasn1.error.PyAsn1Error, ~pyasn1.error.SubstrateUnderrunError #: On decoding errors #: #: Examples diff --git a/pyasn1/codec/der/encoder.py b/pyasn1/codec/der/encoder.py index 5e3c571..90e982d 100644 --- a/pyasn1/codec/der/encoder.py +++ b/pyasn1/codec/der/encoder.py @@ -82,7 +82,7 @@ class Encoder(encoder.Encoder): #: #: Raises #: ------ -#: :py:class:`~pyasn1.error.PyAsn1Error` +#: ~pyasn1.error.PyAsn1Error #: On encoding errors #: #: Examples diff --git a/pyasn1/codec/native/decoder.py b/pyasn1/codec/native/decoder.py index 10e2015..104b92e 100644 --- a/pyasn1/codec/native/decoder.py +++ b/pyasn1/codec/native/decoder.py @@ -195,7 +195,7 @@ class Decoder(object): #: #: Raises #: ------ -#: :py:class:`~pyasn1.error.PyAsn1Error` +#: ~pyasn1.error.PyAsn1Error #: On decoding errors #: #: Examples diff --git a/pyasn1/codec/native/encoder.py b/pyasn1/codec/native/encoder.py index 50caa53..4c5908d 100644 --- a/pyasn1/codec/native/encoder.py +++ b/pyasn1/codec/native/encoder.py @@ -235,7 +235,7 @@ class Encoder(object): #: #: Raises #: ------ -#: :py:class:`~pyasn1.error.PyAsn1Error` +#: ~pyasn1.error.PyAsn1Error #: On encoding errors #: #: Examples diff --git a/pyasn1/error.py b/pyasn1/error.py index cb30033..4f48db2 100644 --- a/pyasn1/error.py +++ b/pyasn1/error.py @@ -7,33 +7,41 @@ class PyAsn1Error(Exception): - """Create pyasn1 exception object + """Base pyasn1 exception - The `PyAsn1Error` exception represents generic, usually fatal, error. + `PyAsn1Error` is the base exception class (based on + :class:`Exception`) that represents all possible ASN.1 related + errors. """ class ValueConstraintError(PyAsn1Error): - """Create pyasn1 exception object + """ASN.1 type constraints violation exception The `ValueConstraintError` exception indicates an ASN.1 value constraint violation. + + It might happen on value object instantiation (for scalar types) or on + serialization (for constructed types). """ class SubstrateUnderrunError(PyAsn1Error): - """Create pyasn1 exception object + """ASN.1 data structure deserialization error The `SubstrateUnderrunError` exception indicates insufficient serialised - data on input of a de-serialization routine. + data on input of a de-serialization codec. """ class PyAsn1UnicodeError(PyAsn1Error, UnicodeError): - """Create pyasn1 exception object + """Unicode text processing error The `PyAsn1UnicodeError` exception is a base class for errors relating to unicode text de/serialization. + + Apart from inheriting from :class:`PyAsn1Error`, it also inherits from + :class:`UnicodeError` to help the caller catching unicode-related errors. """ def __init__(self, message, unicode_error=None): if isinstance(unicode_error, UnicodeError): @@ -42,18 +50,26 @@ class PyAsn1UnicodeError(PyAsn1Error, UnicodeError): class PyAsn1UnicodeDecodeError(PyAsn1UnicodeError, UnicodeDecodeError): - """Create pyasn1 exception object + """Unicode text decoding error The `PyAsn1UnicodeDecodeError` exception represents a failure to deserialize unicode text. + + Apart from inheriting from :class:`PyAsn1UnicodeError`, it also inherits + from :class:`UnicodeDecodeError` to help the caller catching unicode-related + errors. """ class PyAsn1UnicodeEncodeError(PyAsn1UnicodeError, UnicodeEncodeError): - """Create pyasn1 exception object + """Unicode text encoding error The `PyAsn1UnicodeEncodeError` exception represents a failure to serialize unicode text. + + Apart from inheriting from :class:`PyAsn1UnicodeError`, it also inherits + from :class:`UnicodeEncodeError` to help the caller catching + unicode-related errors. """ diff --git a/pyasn1/type/char.py b/pyasn1/type/char.py index 1b5daed..ce10599 100644 --- a/pyasn1/type/char.py +++ b/pyasn1/type/char.py @@ -44,7 +44,7 @@ class AbstractCharacterString(univ.OctetString): Raises ------ - :py:class:`~pyasn1.error.PyAsn1Error` + ~pyasn1.error.PyAsn1Error On constraint violation or bad initializer. """ diff --git a/pyasn1/type/namedtype.py b/pyasn1/type/namedtype.py index 71f5f11..936c96c 100644 --- a/pyasn1/type/namedtype.py +++ b/pyasn1/type/namedtype.py @@ -293,7 +293,7 @@ class NamedTypes(object): Raises ------ - : :class:`~pyasn1.error.PyAsn1Error` + ~pyasn1.error.PyAsn1Error If given position is out of fields range """ try: @@ -317,7 +317,7 @@ class NamedTypes(object): Raises ------ - : :class:`~pyasn1.error.PyAsn1Error` + ~pyasn1.error.PyAsn1Error If *tagSet* is not present or ASN.1 types are not unique within callee *NamedTypes* """ try: @@ -341,7 +341,7 @@ class NamedTypes(object): Raises ------ - : :class:`~pyasn1.error.PyAsn1Error` + ~pyasn1.error.PyAsn1Error If given field name is not present in callee *NamedTypes* """ try: @@ -365,7 +365,7 @@ class NamedTypes(object): Raises ------ - : :class:`~pyasn1.error.PyAsn1Error` + ~pyasn1.error.PyAsn1Error If *name* is not present or not unique within callee *NamedTypes* """ try: @@ -394,7 +394,7 @@ class NamedTypes(object): Raises ------ - : :class:`~pyasn1.error.PyAsn1Error` + ~pyasn1.error.PyAsn1Error If given position is out of fields range """ try: @@ -426,7 +426,7 @@ class NamedTypes(object): Raises ------ - : :class:`~pyasn1.error.PyAsn1Error` + ~pyasn1.error.PyAsn1Error If *tagSet* is not present or not unique within callee *NamedTypes* or *idx* is out of fields range """ diff --git a/pyasn1/type/univ.py b/pyasn1/type/univ.py index 93114d6..2a42fc7 100644 --- a/pyasn1/type/univ.py +++ b/pyasn1/type/univ.py @@ -52,7 +52,7 @@ class Integer(base.AbstractSimpleAsn1Item): Raises ------ - :py:class:`~pyasn1.error.PyAsn1Error` + ~pyasn1.error.PyAsn1Error On constraint violation or bad initializer. Examples @@ -296,7 +296,7 @@ class Boolean(Integer): Raises ------ - :py:class:`~pyasn1.error.PyAsn1Error` + ~pyasn1.error.PyAsn1Error On constraint violation or bad initializer. Examples @@ -386,7 +386,7 @@ class BitString(base.AbstractSimpleAsn1Item): Raises ------ - :py:class:`~pyasn1.error.PyAsn1Error` + ~pyasn1.error.PyAsn1Error On constraint violation or bad initializer. Examples @@ -754,7 +754,7 @@ class OctetString(base.AbstractSimpleAsn1Item): Raises ------ - :py:class:`~pyasn1.error.PyAsn1Error` + ~pyasn1.error.PyAsn1Error On constraint violation or bad initializer. Examples @@ -1047,7 +1047,7 @@ class Null(OctetString): Raises ------ - :py:class:`~pyasn1.error.PyAsn1Error` + ~pyasn1.error.PyAsn1Error On constraint violation or bad initializer. Examples @@ -1106,7 +1106,7 @@ class ObjectIdentifier(base.AbstractSimpleAsn1Item): Raises ------ - :py:class:`~pyasn1.error.PyAsn1Error` + ~pyasn1.error.PyAsn1Error On constraint violation or bad initializer. Examples @@ -1242,7 +1242,7 @@ class Real(base.AbstractSimpleAsn1Item): Raises ------ - :py:class:`~pyasn1.error.PyAsn1Error` + ~pyasn1.error.PyAsn1Error On constraint violation or bad initializer. Examples @@ -1527,7 +1527,7 @@ class Enumerated(Integer): Raises ------ - :py:class:`~pyasn1.error.PyAsn1Error` + ~pyasn1.error.PyAsn1Error On constraint violation or bad initializer. Examples @@ -1827,7 +1827,7 @@ class SequenceOfAndSetOfBase(base.AbstractConstructedAsn1Item): Raises ------ - IndexError: + IndexError When idx > len(self) """ if isinstance(idx, slice): @@ -3160,7 +3160,7 @@ class Any(OctetString): Raises ------ - :py:class:`~pyasn1.error.PyAsn1Error` + ~pyasn1.error.PyAsn1Error On constraint violation or bad initializer. Examples -- cgit v1.2.3 From 2d32a37914c479d24b08fd9ba2e2ece43bde8cd1 Mon Sep 17 00:00:00 2001 From: Ilya Etingof Date: Sat, 13 Jul 2019 11:33:27 +0200 Subject: Enable docs build in Travis --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index 4ca7b33..0b7ad76 100644 --- a/.travis.yml +++ b/.travis.yml @@ -42,6 +42,7 @@ install: - pip install -e . script: - PYTHONPATH=.:$PYTHONPATH python tests/__main__.py + - make -C docs html after_success: - PYTHONPATH=.:$PYTHONPATH coverage run --omit=*test* tests/__main__.py - codecov -- cgit v1.2.3 From 5a5e096520d6ee42b99abaa724e16a48c10f261d Mon Sep 17 00:00:00 2001 From: Ilya Etingof Date: Sat, 13 Jul 2019 11:47:45 +0200 Subject: Remove Python `id` from `repr` It seems to clutter representation printout, especially in the rendered docs. --- pyasn1/type/base.py | 19 +++++++++---------- pyasn1/type/constraint.py | 5 +++-- pyasn1/type/namedtype.py | 8 +++++--- pyasn1/type/namedval.py | 3 ++- pyasn1/type/tag.py | 8 +++++--- pyasn1/type/tagmap.py | 8 ++++---- 6 files changed, 28 insertions(+), 23 deletions(-) diff --git a/pyasn1/type/base.py b/pyasn1/type/base.py index 61ec35e..626e638 100644 --- a/pyasn1/type/base.py +++ b/pyasn1/type/base.py @@ -222,7 +222,7 @@ class NoValue(object): raise error.PyAsn1Error('Attempted "%s" operation on ASN.1 schema object' % attr) def __repr__(self): - return '<%s object at 0x%x>' % (self.__class__.__name__, id(self)) + return '<%s object>' % self.__class__.__name__ noValue = NoValue() @@ -249,19 +249,18 @@ class AbstractSimpleAsn1Item(Asn1ItemBase): self._value = value def __repr__(self): - representation = '%s %s object at 0x%x' % ( - self.__class__.__name__, self.isValue and 'value' or 'schema', id(self) - ) + representation = '%s %s object' % ( + self.__class__.__name__, self.isValue and 'value' or 'schema') for attr, value in self.readOnly.items(): if value: - representation += ' %s %s' % (attr, value) + representation += ', %s %s' % (attr, value) if self.isValue: value = self.prettyPrint() if len(value) > 32: value = value[:16] + '...' + value[-16:] - representation += ' payload [%s]' % value + representation += ', payload [%s]' % value return '<%s>' % representation @@ -469,16 +468,16 @@ class AbstractConstructedAsn1Item(Asn1ItemBase): Asn1ItemBase.__init__(self, **readOnly) def __repr__(self): - representation = '%s %s object at 0x%x' % ( - self.__class__.__name__, self.isValue and 'value' or 'schema', id(self) + representation = '%s %s object' % ( + self.__class__.__name__, self.isValue and 'value' or 'schema' ) for attr, value in self.readOnly.items(): if value is not noValue: - representation += ' %s=%r' % (attr, value) + representation += ', %s=%r' % (attr, value) if self.isValue and self.components: - representation += ' payload [%s]' % ', '.join( + representation += ', payload [%s]' % ', '.join( [repr(x) for x in self.components]) return '<%s>' % representation diff --git a/pyasn1/type/constraint.py b/pyasn1/type/constraint.py index 656e203..807c827 100644 --- a/pyasn1/type/constraint.py +++ b/pyasn1/type/constraint.py @@ -37,10 +37,11 @@ class AbstractConstraint(object): ) def __repr__(self): - representation = '%s object at 0x%x' % (self.__class__.__name__, id(self)) + representation = '%s object' % (self.__class__.__name__) if self._values: - representation += ' consts %s' % ', '.join([repr(x) for x in self._values]) + representation += ', consts %s' % ', '.join( + [repr(x) for x in self._values]) return '<%s>' % representation diff --git a/pyasn1/type/namedtype.py b/pyasn1/type/namedtype.py index 936c96c..cbc1429 100644 --- a/pyasn1/type/namedtype.py +++ b/pyasn1/type/namedtype.py @@ -49,9 +49,10 @@ class NamedType(object): representation = '%s=%r' % (self.name, self.asn1Object) if self.openType: - representation += ' openType: %r' % self.openType + representation += ', open type %r' % self.openType - return '<%s object at 0x%x type %s>' % (self.__class__.__name__, id(self), representation) + return '<%s object, type %s>' % ( + self.__class__.__name__, representation) def __eq__(self, other): return self.__nameAndType == other @@ -173,7 +174,8 @@ class NamedTypes(object): def __repr__(self): representation = ', '.join(['%r' % x for x in self.__namedTypes]) - return '<%s object at 0x%x types %s>' % (self.__class__.__name__, id(self), representation) + return '<%s object, types %s>' % ( + self.__class__.__name__, representation) def __eq__(self, other): return self.__namedTypes == other diff --git a/pyasn1/type/namedval.py b/pyasn1/type/namedval.py index 40468d4..4247597 100644 --- a/pyasn1/type/namedval.py +++ b/pyasn1/type/namedval.py @@ -109,7 +109,8 @@ class NamedValues(object): if len(representation) > 64: representation = representation[:32] + '...' + representation[-32:] - return '<%s object 0x%x enums %s>' % (self.__class__.__name__, id(self), representation) + return '<%s object, enums %s>' % ( + self.__class__.__name__, representation) def __eq__(self, other): return dict(self) == other diff --git a/pyasn1/type/tag.py b/pyasn1/type/tag.py index b46f491..dd33e49 100644 --- a/pyasn1/type/tag.py +++ b/pyasn1/type/tag.py @@ -64,8 +64,10 @@ class Tag(object): self.__hash = hash(self.__tagClassId) def __repr__(self): - representation = '[%s:%s:%s]' % (self.__tagClass, self.__tagFormat, self.__tagId) - return '<%s object at 0x%x tag %s>' % (self.__class__.__name__, id(self), representation) + representation = '[%s:%s:%s]' % ( + self.__tagClass, self.__tagFormat, self.__tagId) + return '<%s object, tag %s>' % ( + self.__class__.__name__, representation) def __eq__(self, other): return self.__tagClassId == other @@ -199,7 +201,7 @@ class TagSet(object): else: representation = 'untagged' - return '<%s object at 0x%x %s>' % (self.__class__.__name__, id(self), representation) + return '<%s object, %s>' % (self.__class__.__name__, representation) def __add__(self, superTag): return self.__class__(self.__baseTag, *self.__superTags + (superTag,)) diff --git a/pyasn1/type/tagmap.py b/pyasn1/type/tagmap.py index e53a14d..6f5163b 100644 --- a/pyasn1/type/tagmap.py +++ b/pyasn1/type/tagmap.py @@ -56,16 +56,16 @@ class TagMap(object): return iter(self.__presentTypes) def __repr__(self): - representation = '%s object at 0x%x' % (self.__class__.__name__, id(self)) + representation = '%s object' % self.__class__.__name__ if self.__presentTypes: - representation += ' present %s' % repr(self.__presentTypes) + representation += ', present %s' % repr(self.__presentTypes) if self.__skipTypes: - representation += ' skip %s' % repr(self.__skipTypes) + representation += ', skip %s' % repr(self.__skipTypes) if self.__defaultType is not None: - representation += ' default %s' % repr(self.__defaultType) + representation += ', default %s' % repr(self.__defaultType) return '<%s>' % representation -- cgit v1.2.3 From bc6cc03491480afe90663fed9b56e07537980022 Mon Sep 17 00:00:00 2001 From: Ilya Etingof Date: Sat, 13 Jul 2019 13:14:21 +0200 Subject: Fix Travis docs build on Python 3.4+ --- .travis.yml | 9 ++++++++- devel-requirements.txt | 1 + 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 0b7ad76..cb495de 100644 --- a/.travis.yml +++ b/.travis.yml @@ -42,7 +42,14 @@ install: - pip install -e . script: - PYTHONPATH=.:$PYTHONPATH python tests/__main__.py - - make -C docs html + - if [[ $TRAVIS_PYTHON_VERSION == '2.6' ]]; then (make -C docs html); fi + - if [[ $TRAVIS_PYTHON_VERSION == '2.7' ]]; then (make -C docs html); fi + - if [[ $TRAVIS_PYTHON_VERSION == '3.3' ]]; then (make -C docs html); fi + - if [[ $TRAVIS_PYTHON_VERSION == '3.4' ]]; then (make -C docs html); fi + - if [[ $TRAVIS_PYTHON_VERSION == '3.5' ]]; then (make -C docs html); fi + - if [[ $TRAVIS_PYTHON_VERSION == '3.6' ]]; then (make -C docs html); fi + - if [[ $TRAVIS_PYTHON_VERSION == '3.7' ]]; then (make -C docs html); fi + - if [[ $TRAVIS_PYTHON_VERSION == 'nightly' ]]; then (make -C docs html); fi after_success: - PYTHONPATH=.:$PYTHONPATH coverage run --omit=*test* tests/__main__.py - codecov diff --git a/devel-requirements.txt b/devel-requirements.txt index 45da368..f960024 100644 --- a/devel-requirements.txt +++ b/devel-requirements.txt @@ -1 +1,2 @@ sphinx<1.5; python_version < '3.4' +sphinx; python_version >= '3.4' -- cgit v1.2.3 From 18974828b048383f15cab77e2e3c181a373ace1a Mon Sep 17 00:00:00 2001 From: Ilya Etingof Date: Sat, 13 Jul 2019 14:14:50 +0200 Subject: Fix Python builtins ReST references in docstrings --- pyasn1/codec/ber/encoder.py | 2 +- pyasn1/type/base.py | 21 +++++---- pyasn1/type/tag.py | 2 +- pyasn1/type/univ.py | 112 +++++++++++++++++++++++--------------------- 4 files changed, 72 insertions(+), 65 deletions(-) diff --git a/pyasn1/codec/ber/encoder.py b/pyasn1/codec/ber/encoder.py index 58e71e7..a5d5fd3 100644 --- a/pyasn1/codec/ber/encoder.py +++ b/pyasn1/codec/ber/encoder.py @@ -849,7 +849,7 @@ class Encoder(object): #: Optional ASN.1 schema or value object e.g. :py:class:`~pyasn1.type.base.PyAsn1Item` derivative #: #: defMode: :py:class:`bool` -#: If `False`, produces indefinite length encoding +#: If :obj:`False`, produces indefinite length encoding #: #: maxChunkSize: :py:class:`int` #: Maximum chunk size in chunked encoding mode (0 denotes unlimited chunk size) diff --git a/pyasn1/type/base.py b/pyasn1/type/base.py index 626e638..e1f34f5 100644 --- a/pyasn1/type/base.py +++ b/pyasn1/type/base.py @@ -92,8 +92,8 @@ class Asn1ItemBase(Asn1Item): Returns ------- : :class:`bool` - :class:`True` if *other* is |ASN.1| type, - :class:`False` otherwise. + :obj:`True` if *other* is |ASN.1| type, + :obj:`False` otherwise. """ return (self is other or (not matchTags or self.tagSet == other.tagSet) and @@ -116,8 +116,8 @@ class Asn1ItemBase(Asn1Item): Returns ------- : :class:`bool` - :class:`True` if *other* is a subtype of |ASN.1| type, - :class:`False` otherwise. + :obj:`True` if *other* is a subtype of |ASN.1| type, + :obj:`False` otherwise. """ return (not matchTags or (self.tagSet.isSuperTagSetOf(other.tagSet)) and @@ -296,17 +296,18 @@ class AbstractSimpleAsn1Item(Asn1ItemBase): def isValue(self): """Indicate that |ASN.1| object represents ASN.1 value. - If *isValue* is `False` then this object represents just ASN.1 schema. + If *isValue* is :obj:`False` then this object represents just + ASN.1 schema. - If *isValue* is `True` then, in addition to its ASN.1 schema features, - this object can also be used like a Python built-in object (e.g. `int`, - `str`, `dict` etc.). + If *isValue* is :obj:`True` then, in addition to its ASN.1 schema + features, this object can also be used like a Python built-in object + (e.g. :class:`int`, :class:`str`, :class:`dict` etc.). Returns ------- : :class:`bool` - :class:`False` if object represents just ASN.1 schema. - :class:`True` if object represents ASN.1 schema and can be used as a normal value. + :obj:`False` if object represents just ASN.1 schema. + :obj:`True` if object represents ASN.1 schema and can be used as a normal value. Note ---- diff --git a/pyasn1/type/tag.py b/pyasn1/type/tag.py index dd33e49..b88a734 100644 --- a/pyasn1/type/tag.py +++ b/pyasn1/type/tag.py @@ -320,7 +320,7 @@ class TagSet(object): Returns ------- : :py:class:`bool` - `True` if callee is a supertype of *tagSet* + :obj:`True` if callee is a supertype of *tagSet* """ if len(tagSet) < self.__lenOfSuperTags: return False diff --git a/pyasn1/type/univ.py b/pyasn1/type/univ.py index 2a42fc7..6f914b1 100644 --- a/pyasn1/type/univ.py +++ b/pyasn1/type/univ.py @@ -1040,7 +1040,7 @@ class Null(OctetString): Keyword Args ------------ value: :class:`str` or :py:class:`~pyasn1.type.univ.Null` object - Python empty string literal or any object that evaluates to `False` + Python empty string literal or any object that evaluates to :obj:`False` tagSet: :py:class:`~pyasn1.type.tag.TagSet` Object representing non-default ASN.1 tag(s) @@ -1180,8 +1180,8 @@ class ObjectIdentifier(base.AbstractSimpleAsn1Item): Returns ------- : :class:`bool` - :class:`True` if this |ASN.1| object is a parent (e.g. prefix) of the other |ASN.1| object - or :class:`False` otherwise. + :obj:`True` if this |ASN.1| object is a parent (e.g. prefix) of the other |ASN.1| object + or :obj:`False` otherwise. """ l = len(self) if l <= len(other): @@ -1349,8 +1349,8 @@ class Real(base.AbstractSimpleAsn1Item): Returns ------- : :class:`bool` - :class:`True` if calling object represents plus infinity - or :class:`False` otherwise. + :obj:`True` if calling object represents plus infinity + or :obj:`False` otherwise. """ return self._value == self._plusInf @@ -1362,8 +1362,8 @@ class Real(base.AbstractSimpleAsn1Item): Returns ------- : :class:`bool` - :class:`True` if calling object represents minus infinity - or :class:`False` otherwise. + :obj:`True` if calling object represents minus infinity + or :obj:`False` otherwise. """ return self._value == self._minusInf @@ -1716,8 +1716,8 @@ class SequenceOfAndSetOfBase(base.AbstractConstructedAsn1Item): object instead of the requested component. instantiate: :class:`bool` - If `True` (default), inner component will be automatically instantiated. - If 'False' either existing component or the `noValue` object will be + If :obj:`True` (default), inner component will be automatically instantiated. + If :obj:`False` either existing component or the :class:`NoValue` object will be returned. Returns @@ -1813,13 +1813,13 @@ class SequenceOfAndSetOfBase(base.AbstractConstructedAsn1Item): or ASN.1 value object to assign to |ASN.1| component. verifyConstraints: :class:`bool` - If `False`, skip constraints validation + If :obj:`False`, skip constraints validation matchTags: :class:`bool` - If `False`, skip component tags matching + If :obj:`False`, skip component tags matching matchConstraints: :class:`bool` - If `False`, skip component constraints matching + If :obj:`False`, skip component constraints matching Returns ------- @@ -1969,17 +1969,17 @@ class SequenceOfAndSetOfBase(base.AbstractConstructedAsn1Item): def isValue(self): """Indicate that |ASN.1| object represents ASN.1 value. - If *isValue* is `False` then this object represents just ASN.1 schema. + If *isValue* is :obj:`False` then this object represents just ASN.1 schema. - If *isValue* is `True` then, in addition to its ASN.1 schema features, - this object can also be used like a Python built-in object (e.g. `int`, - `str`, `dict` etc.). + If *isValue* is :obj:`True` then, in addition to its ASN.1 schema features, + this object can also be used like a Python built-in object + (e.g. :class:`int`, :class:`str`, :class:`dict` etc.). Returns ------- : :class:`bool` - :class:`False` if object represents just ASN.1 schema. - :class:`True` if object represents ASN.1 schema and can be used as a normal value. + :obj:`False` if object represents just ASN.1 schema. + :obj:`True` if object represents ASN.1 schema and can be used as a normal value. Note ---- @@ -2280,14 +2280,16 @@ class SequenceAndSetBase(base.AbstractConstructedAsn1Item): object instead of the requested component. instantiate: :class:`bool` - If `True` (default), inner component will be automatically instantiated. - If 'False' either existing component or the `noValue` object will be - returned. + If :obj:`True` (default), inner component will be automatically + instantiated. + If :obj:`False` either existing component or the :class:`NoValue` + object will be returned. Returns ------- : :py:class:`~pyasn1.type.base.PyAsn1Item` - Instantiate |ASN.1| component type or return existing component value + Instantiate |ASN.1| component type or return existing + component value """ if self._componentTypeLen: idx = self.componentType.getPositionByName(name) @@ -2320,13 +2322,13 @@ class SequenceAndSetBase(base.AbstractConstructedAsn1Item): or ASN.1 value object to assign to |ASN.1| component. verifyConstraints: :class:`bool` - If `False`, skip constraints validation + If :obj:`False`, skip constraints validation matchTags: :class:`bool` - If `False`, skip component tags matching + If :obj:`False`, skip component tags matching matchConstraints: :class:`bool` - If `False`, skip component constraints matching + If :obj:`False`, skip component constraints matching Returns ------- @@ -2364,9 +2366,10 @@ class SequenceAndSetBase(base.AbstractConstructedAsn1Item): object instead of the requested component. instantiate: :class:`bool` - If `True` (default), inner component will be automatically instantiated. - If 'False' either existing component or the `noValue` object will be - returned. + If :obj:`True` (default), inner component will be automatically + instantiated. + If :obj:`False` either existing component or the :class:`NoValue` + object will be returned. Returns ------- @@ -2461,13 +2464,13 @@ class SequenceAndSetBase(base.AbstractConstructedAsn1Item): or ASN.1 value object to assign to |ASN.1| component. verifyConstraints : :class:`bool` - If `False`, skip constraints validation + If :obj:`False`, skip constraints validation matchTags: :class:`bool` - If `False`, skip component tags matching + If :obj:`False`, skip component tags matching matchConstraints: :class:`bool` - If `False`, skip component constraints matching + If :obj:`False`, skip component constraints matching Returns ------- @@ -2554,17 +2557,18 @@ class SequenceAndSetBase(base.AbstractConstructedAsn1Item): def isValue(self): """Indicate that |ASN.1| object represents ASN.1 value. - If *isValue* is `False` then this object represents just ASN.1 schema. + If *isValue* is :obj:`False` then this object represents just ASN.1 schema. - If *isValue* is `True` then, in addition to its ASN.1 schema features, - this object can also be used like a Python built-in object (e.g. `int`, - `str`, `dict` etc.). + If *isValue* is :obj:`True` then, in addition to its ASN.1 schema features, + this object can also be used like a Python built-in object (e.g. + :class:`int`, :class:`str`, :class:`dict` etc.). Returns ------- : :class:`bool` - :class:`False` if object represents just ASN.1 schema. - :class:`True` if object represents ASN.1 schema and can be used as a normal value. + :obj:`False` if object represents just ASN.1 schema. + :obj:`True` if object represents ASN.1 schema and can be used as a + normal value. Note ---- @@ -2741,9 +2745,10 @@ class Set(SequenceAndSetBase): object instead of the requested component. instantiate: :class:`bool` - If `True` (default), inner component will be automatically instantiated. - If 'False' either existing component or the `noValue` object will be - returned. + If :obj:`True` (default), inner component will be automatically + instantiated. + If :obj:`False` either existing component or the :class:`noValue` + object will be returned. Returns ------- @@ -2781,16 +2786,16 @@ class Set(SequenceAndSetBase): or ASN.1 value object to assign to |ASN.1| component. verifyConstraints : :class:`bool` - If `False`, skip constraints validation + If :obj:`False`, skip constraints validation matchTags: :class:`bool` - If `False`, skip component tags matching + If :obj:`False`, skip component tags matching matchConstraints: :class:`bool` - If `False`, skip component constraints matching + If :obj:`False`, skip component constraints matching innerFlag: :class:`bool` - If `True`, search for matching *tagSet* recursively. + If :obj:`True`, search for matching *tagSet* recursively. Returns ------- @@ -3009,13 +3014,13 @@ class Choice(Set): set to *idx* component, previous value is dropped. verifyConstraints : :class:`bool` - If `False`, skip constraints validation + If :obj:`False`, skip constraints validation matchTags: :class:`bool` - If `False`, skip component tags matching + If :obj:`False`, skip component tags matching matchConstraints: :class:`bool` - If `False`, skip component constraints matching + If :obj:`False`, skip component constraints matching Returns ------- @@ -3085,17 +3090,18 @@ class Choice(Set): def isValue(self): """Indicate that |ASN.1| object represents ASN.1 value. - If *isValue* is `False` then this object represents just ASN.1 schema. + If *isValue* is :obj:`False` then this object represents just ASN.1 schema. - If *isValue* is `True` then, in addition to its ASN.1 schema features, - this object can also be used like a Python built-in object (e.g. `int`, - `str`, `dict` etc.). + If *isValue* is :obj:`True` then, in addition to its ASN.1 schema features, + this object can also be used like a Python built-in object (e.g. + :class:`int`, :class:`str`, :class:`dict` etc.). Returns ------- : :class:`bool` - :class:`False` if object represents just ASN.1 schema. - :class:`True` if object represents ASN.1 schema and can be used as a normal value. + :obj:`False` if object represents just ASN.1 schema. + :obj:`True` if object represents ASN.1 schema and can be used as a normal + value. Note ---- -- cgit v1.2.3 From 2f7535c697c3ca9f7c930a5ea79293f0b8d4313b Mon Sep 17 00:00:00 2001 From: Ilya Etingof Date: Sat, 13 Jul 2019 18:58:17 +0200 Subject: Document base ASN.1 types Also many fixes here and there to docs and docstrings. --- CHANGES.rst | 2 +- docs/source/pyasn1/contents.rst | 1 + docs/source/pyasn1/type/base/asn1type.rst | 10 ++ .../pyasn1/type/base/constructedasn1type.rst | 10 ++ docs/source/pyasn1/type/base/contents.rst | 20 +++ docs/source/pyasn1/type/base/novalue.rst | 6 + docs/source/pyasn1/type/base/simpleasn1type.rst | 10 ++ docs/source/pyasn1/type/univ/contents.rst | 4 - pyasn1/codec/ber/eoo.py | 2 +- pyasn1/type/base.py | 64 +++++-- pyasn1/type/char.py | 16 +- pyasn1/type/univ.py | 183 ++++++++++++--------- 12 files changed, 231 insertions(+), 97 deletions(-) create mode 100644 docs/source/pyasn1/type/base/asn1type.rst create mode 100644 docs/source/pyasn1/type/base/constructedasn1type.rst create mode 100644 docs/source/pyasn1/type/base/contents.rst create mode 100644 docs/source/pyasn1/type/base/novalue.rst create mode 100644 docs/source/pyasn1/type/base/simpleasn1type.rst diff --git a/CHANGES.rst b/CHANGES.rst index f631be5..cbed81e 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -658,7 +658,7 @@ Revision 0.0.5a Revision 0.0.4a --------------- -* Asn1ItemBase.prettyPrinter() -> \*.prettyPrint() +* Asn1Type.prettyPrinter() -> \*.prettyPrint() Revision 0.0.3a --------------- diff --git a/docs/source/pyasn1/contents.rst b/docs/source/pyasn1/contents.rst index 554ebe9..eaa7835 100644 --- a/docs/source/pyasn1/contents.rst +++ b/docs/source/pyasn1/contents.rst @@ -139,6 +139,7 @@ type's values. .. toctree:: :maxdepth: 2 + /pyasn1/type/base/contents /pyasn1/type/univ/contents /pyasn1/type/char/contents /pyasn1/type/useful/contents diff --git a/docs/source/pyasn1/type/base/asn1type.rst b/docs/source/pyasn1/type/base/asn1type.rst new file mode 100644 index 0000000..94fe04d --- /dev/null +++ b/docs/source/pyasn1/type/base/asn1type.rst @@ -0,0 +1,10 @@ + +.. _base.Asn1Type: + +.. |ASN.1| replace:: Asn1Type + +|ASN.1| type +------------ + +.. autoclass:: pyasn1.type.base.Asn1Type(tagSet=TagSet(), subtypeSpec=ConstraintsIntersection()) + :members: isSameTypeWith, isSuperTypeOf, tagSet, effectiveTagSet, tagMap, subtypeSpec diff --git a/docs/source/pyasn1/type/base/constructedasn1type.rst b/docs/source/pyasn1/type/base/constructedasn1type.rst new file mode 100644 index 0000000..a6ced17 --- /dev/null +++ b/docs/source/pyasn1/type/base/constructedasn1type.rst @@ -0,0 +1,10 @@ + +.. _base.ConstructedAsn1Type: + +.. |ASN.1| replace:: ConstructedAsn1Type + +|ASN.1| type +------------ + +.. autoclass:: pyasn1.type.base.ConstructedAsn1Type(tagSet=TagSet(), subtypeSpec=ConstraintsIntersection(), sizeSpec=ConstraintsIntersection(), componentType=None) + :members: isValue, isSameTypeWith, isSuperTypeOf, tagSet, effectiveTagSet, tagMap, subtypeSpec diff --git a/docs/source/pyasn1/type/base/contents.rst b/docs/source/pyasn1/type/base/contents.rst new file mode 100644 index 0000000..ffb325c --- /dev/null +++ b/docs/source/pyasn1/type/base/contents.rst @@ -0,0 +1,20 @@ + +.. _type.base: + +ASN.1 type system +----------------- + +The ASN.1 language defines a collection of data types such as *INTEGER* +or *SET*. With pyasn1, ASN.1 types are represented by Python classes. +The base classes are described in this part of the documentation. + +User code might not need to use them directly, except for figuring out +if given object belongs to ASN.1 type or not. + +.. toctree:: + :maxdepth: 2 + + /pyasn1/type/base/asn1type + /pyasn1/type/base/simpleasn1type + /pyasn1/type/base/constructedasn1type + /pyasn1/type/base/novalue diff --git a/docs/source/pyasn1/type/base/novalue.rst b/docs/source/pyasn1/type/base/novalue.rst new file mode 100644 index 0000000..6e34792 --- /dev/null +++ b/docs/source/pyasn1/type/base/novalue.rst @@ -0,0 +1,6 @@ +.. _type.base.NoValue: + +NoValue sentinel +---------------- + +.. autoclass:: pyasn1.type.base.NoValue() diff --git a/docs/source/pyasn1/type/base/simpleasn1type.rst b/docs/source/pyasn1/type/base/simpleasn1type.rst new file mode 100644 index 0000000..03a960d --- /dev/null +++ b/docs/source/pyasn1/type/base/simpleasn1type.rst @@ -0,0 +1,10 @@ + +.. _base.SimpleAsn1Type: + +.. |ASN.1| replace:: SimpleAsn1Type + +|ASN.1| type +------------ + +.. autoclass:: pyasn1.type.base.SimpleAsn1Type(value=NoValue(), tagSet=TagSet(), subtypeSpec=ConstraintsIntersection()) + :members: isValue, isSameTypeWith, isSuperTypeOf, tagSet, effectiveTagSet, tagMap, subtypeSpec diff --git a/docs/source/pyasn1/type/univ/contents.rst b/docs/source/pyasn1/type/univ/contents.rst index 2a5ba25..d546e1c 100644 --- a/docs/source/pyasn1/type/univ/contents.rst +++ b/docs/source/pyasn1/type/univ/contents.rst @@ -32,7 +32,3 @@ and constructed. /pyasn1/type/univ/set /pyasn1/type/univ/sequence /pyasn1/type/univ/choice - -.. _univ.noValue: - -.. autoclass:: pyasn1.type.univ.NoValue() diff --git a/pyasn1/codec/ber/eoo.py b/pyasn1/codec/ber/eoo.py index b613b53..48eb859 100644 --- a/pyasn1/codec/ber/eoo.py +++ b/pyasn1/codec/ber/eoo.py @@ -10,7 +10,7 @@ from pyasn1.type import tag __all__ = ['endOfOctets'] -class EndOfOctets(base.AbstractSimpleAsn1Item): +class EndOfOctets(base.SimpleAsn1Type): defaultValue = 0 tagSet = tag.initTagSet( tag.Tag(tag.tagClassUniversal, tag.tagFormatSimple, 0x00) diff --git a/pyasn1/type/base.py b/pyasn1/type/base.py index e1f34f5..21e4041 100644 --- a/pyasn1/type/base.py +++ b/pyasn1/type/base.py @@ -12,8 +12,8 @@ from pyasn1.type import constraint from pyasn1.type import tag from pyasn1.type import tagmap -__all__ = ['Asn1Item', 'Asn1ItemBase', 'AbstractSimpleAsn1Item', - 'AbstractConstructedAsn1Item'] +__all__ = ['Asn1Item', 'Asn1Type', 'SimpleAsn1Type', + 'ConstructedAsn1Type'] class Asn1Item(object): @@ -26,7 +26,17 @@ class Asn1Item(object): return Asn1Item._typeCounter -class Asn1ItemBase(Asn1Item): +class Asn1Type(Asn1Item): + """Base class for all classes representing ASN.1 types. + + In the user code, |ASN.1| class is normally used only for telling + ASN.1 objects from others. + + Note + ---- + For as long as ASN.1 is concerned, a way to compare ASN.1 types + is to use :meth:`isSameTypeWith` and :meth:`isSuperTypeOf` methods. + """ #: Set or return a :py:class:`~pyasn1.type.tag.TagSet` object representing #: ASN.1 tag(s) associated with |ASN.1| type. tagSet = tag.TagSet() @@ -147,9 +157,13 @@ class Asn1ItemBase(Asn1Item): def getSubtypeSpec(self): return self.subtypeSpec + # backward compatibility def hasValue(self): return self.isValue +# Backward compatibility +Asn1ItemBase = Asn1Type + class NoValue(object): """Create a singleton instance of NoValue class. @@ -228,13 +242,25 @@ class NoValue(object): noValue = NoValue() -# Base class for "simple" ASN.1 objects. These are immutable. -class AbstractSimpleAsn1Item(Asn1ItemBase): +class SimpleAsn1Type(Asn1Type): + """Base class for all simple classes representing ASN.1 types. + + ASN.1 distinguishes types by their ability to hold other objects. + Scalar types are known as *simple* in ASN.1. + + In the user code, |ASN.1| class is normally used only for telling + ASN.1 objects from others. + + Note + ---- + For as long as ASN.1 is concerned, a way to compare ASN.1 types + is to use :meth:`isSameTypeWith` and :meth:`isSuperTypeOf` methods. + """ #: Default payload value defaultValue = noValue def __init__(self, value=noValue, **kwargs): - Asn1ItemBase.__init__(self, **kwargs) + Asn1Type.__init__(self, **kwargs) if value is noValue: value = self.defaultValue else: @@ -426,10 +452,12 @@ class AbstractSimpleAsn1Item(Asn1ItemBase): def prettyPrint(self, scope=0): return self.prettyOut(self._value) - # noinspection PyUnusedLocal def prettyPrintType(self, scope=0): return '%s -> %s' % (self.tagSet, self.__class__.__name__) +# Backward compatibility +AbstractSimpleAsn1Item = SimpleAsn1Type + # # Constructed types: # * There are five of them: Sequence, SequenceOf/SetOf, Set and Choice @@ -450,9 +478,22 @@ class AbstractSimpleAsn1Item(Asn1ItemBase): # -class AbstractConstructedAsn1Item(Asn1ItemBase): +class ConstructedAsn1Type(Asn1Type): + """Base class for all constructed classes representing ASN.1 types. + + ASN.1 distinguishes types by their ability to hold other objects. + Those "nesting" types are known as *constructed* in ASN.1. + + In the user code, |ASN.1| class is normally used only for telling + ASN.1 objects from others. - #: If `True`, requires exact component type matching, + Note + ---- + For as long as ASN.1 is concerned, a way to compare ASN.1 types + is to use :meth:`isSameTypeWith` and :meth:`isSuperTypeOf` methods. + """ + + #: If :obj:`True`, requires exact component type matching, #: otherwise subtype relation is only enforced strictConstraints = False @@ -466,7 +507,7 @@ class AbstractConstructedAsn1Item(Asn1ItemBase): } readOnly.update(kwargs) - Asn1ItemBase.__init__(self, **readOnly) + Asn1Type.__init__(self, **readOnly) def __repr__(self): representation = '%s %s object' % ( @@ -637,3 +678,6 @@ class AbstractConstructedAsn1Item(Asn1ItemBase): def getComponentType(self): return self.componentType + +# Backward compatibility +AbstractConstructedAsn1Item = ConstructedAsn1Type diff --git a/pyasn1/type/char.py b/pyasn1/type/char.py index ce10599..9cba4db 100644 --- a/pyasn1/type/char.py +++ b/pyasn1/type/char.py @@ -21,15 +21,19 @@ noValue = univ.noValue class AbstractCharacterString(univ.OctetString): """Creates |ASN.1| schema or value object. - |ASN.1| objects are immutable and duck-type Python 2 :class:`unicode` or Python 3 :class:`str`. - When used in octet-stream context, |ASN.1| type assumes "|encoding|" encoding. + |ASN.1| class is based on :class:`~pyasn1.type.base.SimpleAsn1Type`, + its objects are immutable and duck-type Python 2 :class:`str` or Python 3 + :class:`bytes`. When used in octet-stream context, |ASN.1| type assumes + "|encoding|" encoding. Keyword Args ------------ value: :class:`unicode`, :class:`str`, :class:`bytes` or |ASN.1| object - unicode object (Python 2) or string (Python 3), alternatively string - (Python 2) or bytes (Python 3) representing octet-stream of serialised - unicode string (note `encoding` parameter) or |ASN.1| class instance. + :class:`unicode` object (Python 2) or :class:`str` (Python 3), + alternatively :class:`str` (Python 2) or :class:`bytes` (Python 3) + representing octet-stream of serialised unicode string + (note `encoding` parameter) or |ASN.1| class instance. + If `value` is not given, schema object will be created. tagSet: :py:class:`~pyasn1.type.tag.TagSet` Object representing non-default ASN.1 tag(s) @@ -44,7 +48,7 @@ class AbstractCharacterString(univ.OctetString): Raises ------ - ~pyasn1.error.PyAsn1Error + ~pyasn1.error.ValueConstraintError, ~pyasn1.error.PyAsn1Error On constraint violation or bad initializer. """ diff --git a/pyasn1/type/univ.py b/pyasn1/type/univ.py index 6f914b1..7715727 100644 --- a/pyasn1/type/univ.py +++ b/pyasn1/type/univ.py @@ -31,15 +31,17 @@ __all__ = ['Integer', 'Boolean', 'BitString', 'OctetString', 'Null', # "Simple" ASN.1 types (yet incomplete) -class Integer(base.AbstractSimpleAsn1Item): - """Create |ASN.1| type or object. +class Integer(base.SimpleAsn1Type): + """Create |ASN.1| schema or value object. - |ASN.1| objects are immutable and duck-type Python :class:`int` objects. + |ASN.1| class is based on :class:`~pyasn1.type.base.SimpleAsn1Type`, its + objects are immutable and duck-type Python :class:`int` objects. Keyword Args ------------ value: :class:`int`, :class:`str` or |ASN.1| object - Python integer or string literal or |ASN.1| class instance. + Python :class:`int` or :class:`str` literal or |ASN.1| class + instance. If `value` is not given, schema object will be created. tagSet: :py:class:`~pyasn1.type.tag.TagSet` Object representing non-default ASN.1 tag(s) @@ -52,7 +54,7 @@ class Integer(base.AbstractSimpleAsn1Item): Raises ------ - ~pyasn1.error.PyAsn1Error + ~pyasn1.error.ValueConstraintError, ~pyasn1.error.PyAsn1Error On constraint violation or bad initializer. Examples @@ -94,13 +96,13 @@ class Integer(base.AbstractSimpleAsn1Item): namedValues = namedval.NamedValues() # Optimization for faster codec lookup - typeId = base.AbstractSimpleAsn1Item.getTypeId() + typeId = base.SimpleAsn1Type.getTypeId() def __init__(self, value=noValue, **kwargs): if 'namedValues' not in kwargs: kwargs['namedValues'] = self.namedValues - base.AbstractSimpleAsn1Item.__init__(self, value, **kwargs) + base.SimpleAsn1Type.__init__(self, value, **kwargs) def __and__(self, value): return self.clone(self._value & value) @@ -187,7 +189,7 @@ class Integer(base.AbstractSimpleAsn1Item): def __rdivmod__(self, value): return self.clone(divmod(value, self._value)) - __hash__ = base.AbstractSimpleAsn1Item.__hash__ + __hash__ = base.SimpleAsn1Type.__hash__ def __int__(self): return int(self._value) @@ -276,14 +278,16 @@ class Integer(base.AbstractSimpleAsn1Item): class Boolean(Integer): - """Create |ASN.1| type or object. + """Create |ASN.1| schema or value object. - |ASN.1| objects are immutable and duck-type Python :class:`int` objects. + |ASN.1| class is based on :class:`~pyasn1.type.base.SimpleAsn1Type`, its + objects are immutable and duck-type Python :class:`int` objects. Keyword Args ------------ value: :class:`int`, :class:`str` or |ASN.1| object - Python integer or boolean or string literal or |ASN.1| class instance. + Python :class:`int` or :class:`str` literal or |ASN.1| class + instance. If `value` is not given, schema object will be created. tagSet: :py:class:`~pyasn1.type.tag.TagSet` Object representing non-default ASN.1 tag(s) @@ -296,7 +300,7 @@ class Boolean(Integer): Raises ------ - ~pyasn1.error.PyAsn1Error + ~pyasn1.error.ValueConstraintError, ~pyasn1.error.PyAsn1Error On constraint violation or bad initializer. Examples @@ -355,17 +359,19 @@ class SizedInteger(SizedIntegerBase): return self.bitLength -class BitString(base.AbstractSimpleAsn1Item): +class BitString(base.SimpleAsn1Type): """Create |ASN.1| schema or value object. - |ASN.1| objects are immutable and duck-type both Python :class:`tuple` (as a tuple + |ASN.1| class is based on :class:`~pyasn1.type.base.SimpleAsn1Type`, its + objects are immutable and duck-type both Python :class:`tuple` (as a tuple of bits) and :class:`int` objects. Keyword Args ------------ value: :class:`int`, :class:`str` or |ASN.1| object - Python integer or string literal representing binary or hexadecimal - number or sequence of integer bits or |ASN.1| object. + Python :class:`int` or :class:`str` literal representing binary + or hexadecimal number or sequence of integer bits or |ASN.1| object. + If `value` is not given, schema object will be created. tagSet: :py:class:`~pyasn1.type.tag.TagSet` Object representing non-default ASN.1 tag(s) @@ -386,7 +392,7 @@ class BitString(base.AbstractSimpleAsn1Item): Raises ------ - ~pyasn1.error.PyAsn1Error + ~pyasn1.error.ValueConstraintError, ~pyasn1.error.PyAsn1Error On constraint violation or bad initializer. Examples @@ -432,7 +438,7 @@ class BitString(base.AbstractSimpleAsn1Item): namedValues = namedval.NamedValues() # Optimization for faster codec lookup - typeId = base.AbstractSimpleAsn1Item.getTypeId() + typeId = base.SimpleAsn1Type.getTypeId() defaultBinValue = defaultHexValue = noValue @@ -461,7 +467,7 @@ class BitString(base.AbstractSimpleAsn1Item): if 'namedValues' not in kwargs: kwargs['namedValues'] = self.namedValues - base.AbstractSimpleAsn1Item.__init__(self, value, **kwargs) + base.SimpleAsn1Type.__init__(self, value, **kwargs) def __str__(self): return self.asBinary() @@ -720,18 +726,22 @@ except NameError: # Python 2.4 return True -class OctetString(base.AbstractSimpleAsn1Item): +class OctetString(base.SimpleAsn1Type): """Create |ASN.1| schema or value object. - |ASN.1| objects are immutable and duck-type Python 2 :class:`str` or Python 3 :class:`bytes`. - When used in Unicode context, |ASN.1| type assumes "|encoding|" serialisation. + |ASN.1| class is based on :class:`~pyasn1.type.base.SimpleAsn1Type`, its + objects are immutable and duck-type Python 2 :class:`str` or + Python 3 :class:`bytes`. When used in Unicode context, |ASN.1| type + assumes "|encoding|" serialisation. Keyword Args ------------ - value: :class:`str`, :class:`bytes` or |ASN.1| object - string (Python 2) or bytes (Python 3), alternatively unicode object - (Python 2) or string (Python 3) representing character string to be - serialised into octets (note `encoding` parameter) or |ASN.1| object. + value: :class:`unicode`, :class:`str`, :class:`bytes` or |ASN.1| object + class:`str` (Python 2) or :class:`bytes` (Python 3), alternatively + class:`unicode` object (Python 2) or :class:`str` (Python 3) + representing character string to be serialised into octets + (note `encoding` parameter) or |ASN.1| object. + If `value` is not given, schema object will be created. tagSet: :py:class:`~pyasn1.type.tag.TagSet` Object representing non-default ASN.1 tag(s) @@ -754,7 +764,7 @@ class OctetString(base.AbstractSimpleAsn1Item): Raises ------ - ~pyasn1.error.PyAsn1Error + ~pyasn1.error.ValueConstraintError, ~pyasn1.error.PyAsn1Error On constraint violation or bad initializer. Examples @@ -786,7 +796,7 @@ class OctetString(base.AbstractSimpleAsn1Item): subtypeSpec = constraint.ConstraintsIntersection() # Optimization for faster codec lookup - typeId = base.AbstractSimpleAsn1Item.getTypeId() + typeId = base.SimpleAsn1Type.getTypeId() defaultBinValue = defaultHexValue = noValue encoding = 'iso-8859-1' @@ -816,7 +826,7 @@ class OctetString(base.AbstractSimpleAsn1Item): if 'encoding' not in kwargs: kwargs['encoding'] = self.encoding - base.AbstractSimpleAsn1Item.__init__(self, value, **kwargs) + base.SimpleAsn1Type.__init__(self, value, **kwargs) if sys.version_info[0] <= 2: def prettyIn(self, value): @@ -874,7 +884,7 @@ class OctetString(base.AbstractSimpleAsn1Item): ) elif isinstance(value, OctetString): # a shortcut, bytes() would work the same way return value.asOctets() - elif isinstance(value, base.AbstractSimpleAsn1Item): # this mostly targets Integer objects + elif isinstance(value, base.SimpleAsn1Type): # this mostly targets Integer objects return self.prettyIn(str(value)) elif isinstance(value, (tuple, list)): return self.prettyIn(bytes(value)) @@ -1035,19 +1045,22 @@ class OctetString(base.AbstractSimpleAsn1Item): class Null(OctetString): """Create |ASN.1| schema or value object. - |ASN.1| objects are immutable and duck-type Python :class:`str` objects (always empty). + |ASN.1| class is based on :class:`~pyasn1.type.base.SimpleAsn1Type`, its + objects are immutable and duck-type Python :class:`str` objects + (always empty). Keyword Args ------------ - value: :class:`str` or :py:class:`~pyasn1.type.univ.Null` object - Python empty string literal or any object that evaluates to :obj:`False` + value: :class:`str` or |ASN.1| object + Python empty :class:`str` literal or any object that evaluates to :obj:`False` + If `value` is not given, schema object will be created. tagSet: :py:class:`~pyasn1.type.tag.TagSet` Object representing non-default ASN.1 tag(s) Raises ------ - ~pyasn1.error.PyAsn1Error + ~pyasn1.error.ValueConstraintError, ~pyasn1.error.PyAsn1Error On constraint violation or bad initializer. Examples @@ -1088,15 +1101,18 @@ else: numericTypes = intTypes + (float,) -class ObjectIdentifier(base.AbstractSimpleAsn1Item): +class ObjectIdentifier(base.SimpleAsn1Type): """Create |ASN.1| schema or value object. - |ASN.1| objects are immutable and duck-type Python :class:`tuple` objects (tuple of non-negative integers). + |ASN.1| class is based on :class:`~pyasn1.type.base.SimpleAsn1Type`, its + objects are immutable and duck-type Python :class:`tuple` objects + (tuple of non-negative integers). Keyword Args ------------ value: :class:`tuple`, :class:`str` or |ASN.1| object - Python sequence of :class:`int` or string literal or |ASN.1| object. + Python sequence of :class:`int` or :class:`str` literal or |ASN.1| object. + If `value` is not given, schema object will be created. tagSet: :py:class:`~pyasn1.type.tag.TagSet` Object representing non-default ASN.1 tag(s) @@ -1106,7 +1122,7 @@ class ObjectIdentifier(base.AbstractSimpleAsn1Item): Raises ------ - ~pyasn1.error.PyAsn1Error + ~pyasn1.error.ValueConstraintError, ~pyasn1.error.PyAsn1Error On constraint violation or bad initializer. Examples @@ -1138,7 +1154,7 @@ class ObjectIdentifier(base.AbstractSimpleAsn1Item): subtypeSpec = constraint.ConstraintsIntersection() # Optimization for faster codec lookup - typeId = base.AbstractSimpleAsn1Item.getTypeId() + typeId = base.SimpleAsn1Type.getTypeId() def __add__(self, other): return self.clone(self._value + other) @@ -1221,10 +1237,11 @@ class ObjectIdentifier(base.AbstractSimpleAsn1Item): return '.'.join([str(x) for x in value]) -class Real(base.AbstractSimpleAsn1Item): +class Real(base.SimpleAsn1Type): """Create |ASN.1| schema or value object. - |ASN.1| objects are immutable and duck-type Python :class:`float` objects. + |ASN.1| class is based on :class:`~pyasn1.type.base.SimpleAsn1Type`, its + objects are immutable and duck-type Python :class:`float` objects. Additionally, |ASN.1| objects behave like a :class:`tuple` in which case its elements are mantissa, base and exponent. @@ -1232,7 +1249,8 @@ class Real(base.AbstractSimpleAsn1Item): ------------ value: :class:`tuple`, :class:`float` or |ASN.1| object Python sequence of :class:`int` (representing mantissa, base and - exponent) or float instance or *Real* class instance. + exponent) or :class:`float` instance or |ASN.1| object. + If `value` is not given, schema object will be created. tagSet: :py:class:`~pyasn1.type.tag.TagSet` Object representing non-default ASN.1 tag(s) @@ -1242,7 +1260,7 @@ class Real(base.AbstractSimpleAsn1Item): Raises ------ - ~pyasn1.error.PyAsn1Error + ~pyasn1.error.ValueConstraintError, ~pyasn1.error.PyAsn1Error On constraint violation or bad initializer. Examples @@ -1285,7 +1303,7 @@ class Real(base.AbstractSimpleAsn1Item): subtypeSpec = constraint.ConstraintsIntersection() # Optimization for faster codec lookup - typeId = base.AbstractSimpleAsn1Item.getTypeId() + typeId = base.SimpleAsn1Type.getTypeId() @staticmethod def __normalizeBase10(value): @@ -1486,7 +1504,7 @@ class Real(base.AbstractSimpleAsn1Item): def __bool__(self): return bool(float(self)) - __hash__ = base.AbstractSimpleAsn1Item.__hash__ + __hash__ = base.SimpleAsn1Type.__hash__ def __getitem__(self, idx): if self._value in self._inf: @@ -1507,14 +1525,16 @@ class Real(base.AbstractSimpleAsn1Item): class Enumerated(Integer): - """Create |ASN.1| type or object. + """Create |ASN.1| schema or value object. - |ASN.1| objects are immutable and duck-type Python :class:`int` objects. + |ASN.1| class is based on :class:`~pyasn1.type.base.SimpleAsn1Type`, its + objects are immutable and duck-type Python :class:`int` objects. Keyword Args ------------ value: :class:`int`, :class:`str` or |ASN.1| object - Python integer or string literal or |ASN.1| class instance. + Python :class:`int` or :class:`str` literal or |ASN.1| object. + If `value` is not given, schema object will be created. tagSet: :py:class:`~pyasn1.type.tag.TagSet` Object representing non-default ASN.1 tag(s) @@ -1527,7 +1547,7 @@ class Enumerated(Integer): Raises ------ - ~pyasn1.error.PyAsn1Error + ~pyasn1.error.ValueConstraintError, ~pyasn1.error.PyAsn1Error On constraint violation or bad initializer. Examples @@ -1573,10 +1593,11 @@ class Enumerated(Integer): # "Structured" ASN.1 types -class SequenceOfAndSetOfBase(base.AbstractConstructedAsn1Item): - """Create |ASN.1| type. +class SequenceOfAndSetOfBase(base.ConstructedAsn1Type): + """Create |ASN.1| schema or value object. - |ASN.1| objects are mutable and duck-type Python :class:`list` objects. + |ASN.1| class is based on :class:`~pyasn1.type.base.ConstructedAsn1Type`, + its objects are mutable and duck-type Python :class:`list` objects. Keyword Args ------------ @@ -1619,7 +1640,7 @@ class SequenceOfAndSetOfBase(base.AbstractConstructedAsn1Item): self._componentValues = noValue - base.AbstractConstructedAsn1Item.__init__(self, **kwargs) + base.ConstructedAsn1Type.__init__(self, **kwargs) # Python list protocol @@ -1689,7 +1710,7 @@ class SequenceOfAndSetOfBase(base.AbstractConstructedAsn1Item): def _cloneComponentValues(self, myClone, cloneValueFlag): for idx, componentValue in self._componentValues.items(): if componentValue is not noValue: - if isinstance(componentValue, base.AbstractConstructedAsn1Item): + if isinstance(componentValue, base.ConstructedAsn1Type): myClone.setComponentByPosition( idx, componentValue.clone(cloneValueFlag=cloneValueFlag) ) @@ -1811,6 +1832,7 @@ class SequenceOfAndSetOfBase(base.AbstractConstructedAsn1Item): value: :class:`object` or :py:class:`~pyasn1.type.base.PyAsn1Item` derivative A Python value to initialize |ASN.1| component with (if *componentType* is set) or ASN.1 value object to assign to |ASN.1| component. + If `value` is not given, schema object will be set as a component. verifyConstraints: :class:`bool` If :obj:`False`, skip constraints validation @@ -1827,6 +1849,8 @@ class SequenceOfAndSetOfBase(base.AbstractConstructedAsn1Item): Raises ------ + ~pyasn1.error.ValueConstraintError, ~pyasn1.error.PyAsn1Error + On constraint violation or bad initializer IndexError When idx > len(self) """ @@ -1864,11 +1888,11 @@ class SequenceOfAndSetOfBase(base.AbstractConstructedAsn1Item): elif not isinstance(value, base.Asn1Item): if (componentType is not None and - isinstance(componentType, base.AbstractSimpleAsn1Item)): + isinstance(componentType, base.SimpleAsn1Type)): value = componentType.clone(value=value) elif (currentValue is not noValue and - isinstance(currentValue, base.AbstractSimpleAsn1Item)): + isinstance(currentValue, base.SimpleAsn1Type)): value = currentValue.clone(value=value) else: @@ -2059,10 +2083,11 @@ class SetOf(SequenceOfAndSetOfBase): typeId = SequenceOfAndSetOfBase.getTypeId() -class SequenceAndSetBase(base.AbstractConstructedAsn1Item): - """Create |ASN.1| type. +class SequenceAndSetBase(base.ConstructedAsn1Type): + """Create |ASN.1| schema or value object. - |ASN.1| objects are mutable and duck-type Python :class:`dict` objects. + |ASN.1| class is based on :class:`~pyasn1.type.base.ConstructedAsn1Type`, + its objects are mutable and duck-type Python :class:`dict` objects. Keyword Args ------------ @@ -2150,7 +2175,7 @@ class SequenceAndSetBase(base.AbstractConstructedAsn1Item): def __init__(self, **kwargs): - base.AbstractConstructedAsn1Item.__init__(self, **kwargs) + base.ConstructedAsn1Type.__init__(self, **kwargs) self._componentTypeLen = len(self.componentType) if self._componentTypeLen: self._componentValues = [] @@ -2256,7 +2281,7 @@ class SequenceAndSetBase(base.AbstractConstructedAsn1Item): for idx, componentValue in enumerate(self._componentValues): if componentValue is not noValue: - if isinstance(componentValue, base.AbstractConstructedAsn1Item): + if isinstance(componentValue, base.ConstructedAsn1Type): myClone.setComponentByPosition( idx, componentValue.clone(cloneValueFlag=cloneValueFlag) ) @@ -2320,6 +2345,7 @@ class SequenceAndSetBase(base.AbstractConstructedAsn1Item): value: :class:`object` or :py:class:`~pyasn1.type.base.PyAsn1Item` derivative A Python value to initialize |ASN.1| component with (if *componentType* is set) or ASN.1 value object to assign to |ASN.1| component. + If `value` is not given, schema object will be set as a component. verifyConstraints: :class:`bool` If :obj:`False`, skip constraints validation @@ -2462,6 +2488,7 @@ class SequenceAndSetBase(base.AbstractConstructedAsn1Item): value: :class:`object` or :py:class:`~pyasn1.type.base.PyAsn1Item` derivative A Python value to initialize |ASN.1| component with (if *componentType* is set) or ASN.1 value object to assign to |ASN.1| component. + If `value` is not given, schema object will be set as a component. verifyConstraints : :class:`bool` If :obj:`False`, skip constraints validation @@ -2499,7 +2526,7 @@ class SequenceAndSetBase(base.AbstractConstructedAsn1Item): if value is noValue: if componentTypeLen: value = componentType.getTypeByPosition(idx) - if isinstance(value, base.AbstractConstructedAsn1Item): + if isinstance(value, base.ConstructedAsn1Type): value = value.clone(cloneValueFlag=componentType[idx].isDefaulted) elif currentValue is noValue: @@ -2508,13 +2535,13 @@ class SequenceAndSetBase(base.AbstractConstructedAsn1Item): elif not isinstance(value, base.Asn1Item): if componentTypeLen: subComponentType = componentType.getTypeByPosition(idx) - if isinstance(subComponentType, base.AbstractSimpleAsn1Item): + if isinstance(subComponentType, base.SimpleAsn1Type): value = subComponentType.clone(value=value) else: raise error.PyAsn1Error('%s can cast only scalar values' % componentType.__class__.__name__) - elif currentValue is not noValue and isinstance(currentValue, base.AbstractSimpleAsn1Item): + elif currentValue is not noValue and isinstance(currentValue, base.SimpleAsn1Type): value = currentValue.clone(value=value) else: @@ -2784,6 +2811,7 @@ class Set(SequenceAndSetBase): value: :class:`object` or :py:class:`~pyasn1.type.base.PyAsn1Item` derivative A Python value to initialize |ASN.1| component with (if *componentType* is set) or ASN.1 value object to assign to |ASN.1| component. + If `value` is not given, schema object will be set as a component. verifyConstraints : :class:`bool` If :obj:`False`, skip constraints validation @@ -2827,9 +2855,10 @@ class Set(SequenceAndSetBase): class Choice(Set): - """Create |ASN.1| type. + """Create |ASN.1| schema or value object. - |ASN.1| objects are mutable and duck-type Python :class:`dict` objects. + |ASN.1| class is based on :class:`~pyasn1.type.base.ConstructedAsn1Type`, + its objects are mutable and duck-type Python :class:`list` objects. Keyword Args ------------ @@ -2974,7 +3003,7 @@ class Choice(Set): tagSet = component.effectiveTagSet else: tagSet = component.tagSet - if isinstance(component, base.AbstractConstructedAsn1Item): + if isinstance(component, base.ConstructedAsn1Type): myClone.setComponentByType( tagSet, component.clone(cloneValueFlag=cloneValueFlag) ) @@ -3012,6 +3041,7 @@ class Choice(Set): A Python value to initialize |ASN.1| component with (if *componentType* is set) or ASN.1 value object to assign to |ASN.1| component. Once a new value is set to *idx* component, previous value is dropped. + If `value` is not given, schema object will be set as a component. verifyConstraints : :class:`bool` If :obj:`False`, skip constraints validation @@ -3134,16 +3164,19 @@ class Choice(Set): class Any(OctetString): """Create |ASN.1| schema or value object. - |ASN.1| objects are immutable and duck-type Python 2 :class:`str` or Python 3 - :class:`bytes`. When used in Unicode context, |ASN.1| type assumes "|encoding|" - serialisation. + |ASN.1| class is based on :class:`~pyasn1.type.base.SimpleAsn1Type`, + its objects are immutable and duck-type Python 2 :class:`str` or Python 3 + :class:`bytes`. When used in Unicode context, |ASN.1| type assumes + "|encoding|" serialisation. Keyword Args ------------ - value: :class:`str`, :class:`bytes` or |ASN.1| object - string (Python 2) or bytes (Python 3), alternatively unicode object - (Python 2) or string (Python 3) representing character string to be - serialised into octets (note `encoding` parameter) or |ASN.1| object. + value: :class:`unicode`, :class:`str`, :class:`bytes` or |ASN.1| object + :class:`str` (Python 2) or :class:`bytes` (Python 3), alternatively + :class:`unicode` object (Python 2) or :class:`str` (Python 3) + representing character string to be serialised into octets (note + `encoding` parameter) or |ASN.1| object. + If `value` is not given, schema object will be created. tagSet: :py:class:`~pyasn1.type.tag.TagSet` Object representing non-default ASN.1 tag(s) @@ -3166,7 +3199,7 @@ class Any(OctetString): Raises ------ - ~pyasn1.error.PyAsn1Error + ~pyasn1.error.ValueConstraintError, ~pyasn1.error.PyAsn1Error On constraint violation or bad initializer. Examples -- cgit v1.2.3 From fc10c68fabf184116aaf1915e41a4f5fd21a4a27 Mon Sep 17 00:00:00 2001 From: Ilya Etingof Date: Wed, 31 Jul 2019 21:26:37 +0200 Subject: Remove a couple of `except as` statements to benefit old Pythons --- pyasn1/type/char.py | 2 +- pyasn1/type/univ.py | 16 ++++++++++++++-- 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/pyasn1/type/char.py b/pyasn1/type/char.py index 9cba4db..3f8c444 100644 --- a/pyasn1/type/char.py +++ b/pyasn1/type/char.py @@ -81,7 +81,7 @@ class AbstractCharacterString(univ.OctetString): else: return unicode(value) - except (UnicodeDecodeError, LookupError) as exc: + except (UnicodeDecodeError, LookupError): exc = sys.exc_info()[1] raise error.PyAsn1UnicodeDecodeError( "Can't decode string '%s' with codec " diff --git a/pyasn1/type/univ.py b/pyasn1/type/univ.py index 7715727..86e9855 100644 --- a/pyasn1/type/univ.py +++ b/pyasn1/type/univ.py @@ -832,22 +832,27 @@ class OctetString(base.SimpleAsn1Type): def prettyIn(self, value): if isinstance(value, str): return value + elif isinstance(value, unicode): try: return value.encode(self.encoding) + except (LookupError, UnicodeEncodeError): exc = sys.exc_info()[1] raise error.PyAsn1UnicodeEncodeError( "Can't encode string '%s' with codec " "%s" % (value, self.encoding), exc ) + elif isinstance(value, (tuple, list)): try: return ''.join([chr(x) for x in value]) + except ValueError: raise error.PyAsn1Error( "Bad %s initializer '%s'" % (self.__class__.__name__, value) ) + else: return str(value) @@ -875,19 +880,26 @@ class OctetString(base.SimpleAsn1Type): def prettyIn(self, value): if isinstance(value, bytes): return value + elif isinstance(value, str): try: return value.encode(self.encoding) - except UnicodeEncodeError as e: + + except UnicodeEncodeError: + exc = sys.exc_info()[1] raise error.PyAsn1UnicodeEncodeError( - "Can't encode string '%s' with '%s' codec" % (value, self.encoding), e + "Can't encode string '%s' with '%s' " + "codec" % (value, self.encoding), exc ) elif isinstance(value, OctetString): # a shortcut, bytes() would work the same way return value.asOctets() + elif isinstance(value, base.SimpleAsn1Type): # this mostly targets Integer objects return self.prettyIn(str(value)) + elif isinstance(value, (tuple, list)): return self.prettyIn(bytes(value)) + else: return bytes(value) -- cgit v1.2.3 From fb824beb47f0e58463bb9a4b4b50bd804f30b9f2 Mon Sep 17 00:00:00 2001 From: Ilya Etingof Date: Wed, 31 Jul 2019 21:40:15 +0200 Subject: Fix failing unit tests on Py25 --- pyasn1/type/univ.py | 3 +++ tests/type/test_univ.py | 17 +++++++++++++---- 2 files changed, 16 insertions(+), 4 deletions(-) diff --git a/pyasn1/type/univ.py b/pyasn1/type/univ.py index 86e9855..b39c533 100644 --- a/pyasn1/type/univ.py +++ b/pyasn1/type/univ.py @@ -1695,6 +1695,9 @@ class SequenceOfAndSetOfBase(base.ConstructedAsn1Type): indices, values = zip(*self._componentValues.items()) + # TODO: remove when Py2.5 support is gone + values = list(values) + try: return indices[values.index(value, start, stop)] diff --git a/tests/type/test_univ.py b/tests/type/test_univ.py index de7cdee..0092588 100644 --- a/tests/type/test_univ.py +++ b/tests/type/test_univ.py @@ -150,13 +150,18 @@ class NoValueTestCase(BaseTestCase): try: if hasattr(sys, 'getsizeof'): sys.getsizeof(univ.noValue) - else: + + # TODO: remove when Py2.5 support is gone + elif sys.version_info > (2, 6): raise unittest.SkipTest("no sys.getsizeof() method") except PyAsn1Error: assert False, 'sizeof failed for NoValue object' + except TypeError: - raise unittest.SkipTest("sys.getsizeof() raises TypeError") + # TODO: remove when Py2.5 support is gone + if sys.version_info > (2, 6): + raise unittest.SkipTest("sys.getsizeof() raises TypeError") class IntegerTestCase(BaseTestCase): @@ -554,8 +559,10 @@ class OctetStringUnicodeErrorTestCase(BaseTestCase): except PyAsn1UnicodeEncodeError: pass + # TODO: remove when Py2.5 support is gone else: - assert False, 'Unicode encoding error not caught' + if sys.version_info > (2, 6): + assert False, 'Unicode encoding error not caught' def testDecodeError(self): serialized = ints2octs((0xff, 0xfe)) @@ -566,8 +573,10 @@ class OctetStringUnicodeErrorTestCase(BaseTestCase): except PyAsn1UnicodeDecodeError: pass + # TODO: remove when Py2.5 support is gone else: - assert False, 'Unicode decoding error not caught' + if sys.version_info > (2, 6): + assert False, 'Unicode decoding error not caught' class OctetStringWithUtf8TestCase(OctetStringWithUnicodeMixIn, BaseTestCase): -- cgit v1.2.3 From bb6b6e26fd2072dae8ba6664e2b8b6c5b78ce5a8 Mon Sep 17 00:00:00 2001 From: Ilya Etingof Date: Wed, 31 Jul 2019 21:43:30 +0200 Subject: Release 0.4.6 --- CHANGES.rst | 2 +- docs/source/pyasn1/type/base/constructedasn1type.rst | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGES.rst b/CHANGES.rst index cbed81e..0ca5904 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -1,5 +1,5 @@ -Revision 0.4.6, released XX-07-2019 +Revision 0.4.6, released 31-07-2019 ----------------------------------- - Added previously missing `SET OF ANY` construct encoding/decoding support. diff --git a/docs/source/pyasn1/type/base/constructedasn1type.rst b/docs/source/pyasn1/type/base/constructedasn1type.rst index a6ced17..54d828f 100644 --- a/docs/source/pyasn1/type/base/constructedasn1type.rst +++ b/docs/source/pyasn1/type/base/constructedasn1type.rst @@ -7,4 +7,4 @@ ------------ .. autoclass:: pyasn1.type.base.ConstructedAsn1Type(tagSet=TagSet(), subtypeSpec=ConstraintsIntersection(), sizeSpec=ConstraintsIntersection(), componentType=None) - :members: isValue, isSameTypeWith, isSuperTypeOf, tagSet, effectiveTagSet, tagMap, subtypeSpec + :members: isSameTypeWith, isSuperTypeOf, tagSet, effectiveTagSet, tagMap, subtypeSpec -- cgit v1.2.3