diff --git a/src/lib/pyasn1/CHANGES b/src/lib/pyasn1/CHANGES deleted file mode 100644 index 561dedd8..00000000 --- a/src/lib/pyasn1/CHANGES +++ /dev/null @@ -1,278 +0,0 @@ -Revision 0.1.7 --------------- - -- License updated to vanilla BSD 2-Clause to ease package use - (http://opensource.org/licenses/BSD-2-Clause). -- Test suite made discoverable by unittest/unittest2 discovery feature. -- Fix to decoder working on indefinite length substrate -- end-of-octets - marker is now detected by both tag and value. Otherwise zero values may - interfere with end-of-octets marker. -- Fix to decoder to fail in cases where tagFormat indicates inappropriate - format for the type (e.g. BOOLEAN is always PRIMITIVE, SET is always - CONSTRUCTED and OCTET STRING is either of the two) -- Fix to REAL type encoder to force primitive encoding form encoding. -- Fix to CHOICE decoder to handle explicitly tagged, indefinite length - mode encoding -- Fix to REAL type decoder to handle negative REAL values correctly. Test - case added. - -Revision 0.1.6 --------------- - -- The compact (valueless) way of encoding zero INTEGERs introduced in - 0.1.5 seems to fail miserably as the world is filled with broken - BER decoders. So we had to back off the *encoder* for a while. - There's still the IntegerEncoder.supportCompactZero flag which - enables compact encoding form whenever it evaluates to True. -- Report package version on debugging code initialization. - -Revision 0.1.5 --------------- - -- Documentation updated and split into chapters to better match - web-site contents. -- Make prettyPrint() working for non-initialized pyasn1 data objects. It - used to throw an exception. -- Fix to encoder to produce empty-payload INTEGER values for zeros -- Fix to decoder to support empty-payload INTEGER and REAL values -- Fix to unit test suites imports to be able to run each from - their current directory - -Revision 0.1.4 --------------- - -- Built-in codec debugging facility added -- Added some more checks to ObjectIdentifier BER encoder catching - posible 2^8 overflow condition by two leading sub-OIDs -- Implementations overriding the AbstractDecoder.valueDecoder method - changed to return the rest of substrate behind the item being processed - rather than the unprocessed substrate within the item (which is usually - empty). -- Decoder's recursiveFlag feature generalized as a user callback function - which is passed an uninitialized object recovered from substrate and - its uninterpreted payload. -- Catch inappropriate substrate type passed to decoder. -- Expose tagMap/typeMap/Decoder objects at DER decoder to uniform API. -- Obsolete __init__.MajorVersionId replaced with __init__.__version__ - which is now in-sync with distutils. -- Package classifiers updated. -- The __init__.py's made non-empty (rumors are that they may be optimized - out by package managers). -- Bail out gracefully whenever Python version is older than 2.4. -- Fix to Real codec exponent encoding (should be in 2's complement form), - some more test cases added. -- Fix in Boolean truth testing built-in methods -- Fix to substrate underrun error handling at ObjectIdentifier BER decoder -- Fix to BER Boolean decoder that allows other pre-computed - values besides 0 and 1 -- Fix to leading 0x80 octet handling in DER/CER/DER ObjectIdentifier decoder. - See http://www.cosic.esat.kuleuven.be/publications/article-1432.pdf - -Revision 0.1.3 --------------- - -- Include class name into asn1 value constraint violation exception. -- Fix to OctetString.prettyOut() method that looses leading zero when - building hex string. - -Revision 0.1.2 --------------- - -- Fix to __long__() to actually return longs on py2k -- Fix to OctetString.__str__() workings of a non-initialized object. -- Fix to quote initializer of OctetString.__repr__() -- Minor fix towards ObjectIdentifier.prettyIn() reliability -- ObjectIdentifier.__str__() is aliased to prettyPrint() -- Exlicit repr() calls replaced with '%r' - -Revision 0.1.1 --------------- - -- Hex/bin string initializer to OctetString object reworked - (in a backward-incompatible manner) -- Fixed float() infinity compatibility issue (affects 2.5 and earlier) -- Fixed a bug/typo at Boolean CER encoder. -- Major overhawl for Python 2.4 -- 3.2 compatibility: - + get rid of old-style types - + drop string module usage - + switch to rich comparation - + drop explicit long integer type use - + map()/filter() replaced with list comprehension - + apply() replaced with */**args - + switched to use 'key' sort() callback function - + support both __nonzero__() and __bool__() methods - + modified not to use py3k-incompatible exception syntax - + getslice() operator fully replaced with getitem() - + dictionary operations made 2K/3K compatible - + base type for encoding substrate and OctetString-based types - is now 'bytes' when running py3k and 'str' otherwise - + OctetString and derivatives now unicode compliant. - + OctetString now supports two python-neutral getters: asOcts() & asInts() - + print OctetString content in hex whenever it is not printable otherwise - + in test suite, implicit relative import replaced with the absolute one - + in test suite, string constants replaced with numerics - -Revision 0.0.13 ---------------- - -- Fix to base10 normalization function that loops on univ.Real(0) - -Revision 0.0.13b ----------------- - -- ASN.1 Real type is now supported properly. -- Objects of Constructed types now support __setitem__() -- Set/Sequence objects can now be addressed by their field names (string index) - and position (integer index). -- Typo fix to ber.SetDecoder code that prevented guided decoding operation. -- Fix to explicitly tagged items decoding support. -- Fix to OctetString.prettyPrint() to better handle non-printable content. -- Fix to repr() workings of Choice objects. - -Revision 0.0.13a ----------------- - -- Major codec re-design. -- Documentation significantly improved. -- ASN.1 Any type is now supported. -- All example ASN.1 modules moved to separate pyasn1-modules package. -- Fix to initial sub-OID overflow condition detection an encoder. -- BitString initialization value verification improved. -- The Set/Sequence.getNameByPosition() method implemented. -- Fix to proper behaviour of PermittedAlphabetConstraint object. -- Fix to improper Boolean substrate handling at CER/DER decoders. -- Changes towards performance improvement: - + all dict.has_key() & dict.get() invocations replaced with modern syntax - (this breaks compatibility with Python 2.1 and older). - + tag and tagset caches introduced to decoder - + decoder code improved to prevent unnecessary pyasn1 objects creation - + allow disabling components verification when setting components to - structured types, this is used by decoder whilst running in guided mode. - + BER decoder for integer values now looks up a small set of pre-computed - substrate values to save on decoding. - + a few pre-computed values configured to ObjectIdentifier BER encoder. - + ChoiceDecoder split-off SequenceOf one to save on unnecessary checks. - + replace slow hasattr()/getattr() calls with isinstance() introspection. - + track the number of initialized components of Constructed types to save - on default/optional components initialization. - + added a shortcut ObjectIdentifier.asTuple() to be used instead of - __getitem__() in hotspots. - + use Tag.asTuple() and pure integers at tag encoder. - + introduce and use in decoder the baseTagSet attribute of the built-in - ASN.1 types. - -Revision 0.0.12a ----------------- - -- The individual tag/length/value processing methods of - encoder.AbstractItemEncoder renamed (leading underscore stripped) - to promote overloading in cases where partial substrate processing - is required. -- The ocsp.py, ldap.py example scripts added. -- Fix to univ.ObjectIdentifier input value handler to disallow negative - sub-IDs. - -Revision 0.0.11a ----------------- - -- Decoder can now treat values of unknown types as opaque OctetString. -- Fix to Set/SetOf type decoder to handle uninitialized scalar SetOf - components correctly. - -Revision 0.0.10a ----------------- - -- API versioning mechanics retired (pyasn1.v1 -> pyasn1) what makes - it possible to zip-import pyasn1 sources (used by egg and py2exe). - -Revision 0.0.9a ---------------- - -- Allow any non-zero values in Boolean type BER decoder, as it's in - accordnance with the standard. - -Revision 0.0.8a ---------------- - -- Integer.__index__() now supported (for Python 2.5+). -- Fix to empty value encoding in BitString encoder, test case added. -- Fix to SequenceOf decoder that prevents it skipping possible Choice - typed inner component. -- Choice.getName() method added for getting currently set component - name. -- OctetsString.prettyPrint() does a single str() against its value - eliminating an extra quotes. - -Revision 0.0.7a ---------------- - -- Large tags (>31) now supported by codecs. -- Fix to encoder to properly handle explicitly tagged untagged items. -- All possible value lengths (up to 256^126) now supported by encoders. -- Fix to Tag class constructor to prevent negative IDs. - -Revision 0.0.6a ---------------- - -- Make use of setuptools. -- Constraints derivation verification (isSuperTypeOf()/isSubTypeOf()) fixed. -- Fix to constraints comparation logic -- can't cmp() hash values as it - may cause false positives due to hash conflicts. - -Revision 0.0.5a ---------------- - -- Integer BER codec reworked fixing negative values encoding bug. -- clone() and subtype() methods of Constructed ASN.1 classes now - accept optional cloneValueFlag flag which controls original value - inheritance. The default is *not* to inherit original value for - performance reasons (this may affect backward compatibility). - Performance penalty may be huge on deeply nested Constructed objects - re-creation. -- Base ASN.1 types (pyasn1.type.univ.*) do not have default values - anymore. They remain uninitialized acting as ASN.1 types. In - this model, initialized ASN.1 types represent either types with - default value installed or a type instance. -- Decoders' prototypes are now class instances rather than classes. - This is to simplify initial value installation to decoder's - prototype value. -- Bugfix to BitString BER decoder (trailing bits not regarded). -- Bugfix to Constraints use as mapping keys. -- Bugfix to Integer & BitString clone() methods -- Bugix to the way to distinguish Set from SetOf at CER/DER SetOfEncoder -- Adjustments to make it running on Python 1.5. -- In tests, substrate constants converted from hex escaped literals into - octals to overcome indefinite hex width issue occuring in young Python. -- Minor performance optimization of TagSet.isSuperTagSetOf() method -- examples/sshkey.py added - -Revision 0.0.4a ---------------- - -* Asn1ItemBase.prettyPrinter() -> *.prettyPrint() - -Revision 0.0.3a ---------------- - -* Simple ASN1 objects now hash to their Python value and don't - depend upon tag/constraints/etc. -* prettyIn & prettyOut methods of SimplleAsn1Object become public -* many syntax fixes - -Revision 0.0.2a ---------------- - -* ConstraintsIntersection.isSuperTypeOf() and - ConstraintsIntersection.hasConstraint() implemented -* Bugfix to NamedValues initialization code -* +/- operators added to NamedValues objects -* Integer.__abs__() & Integer.subtype() added -* ObjectIdentifier.prettyOut() fixes -* Allow subclass components at SequenceAndSetBase -* AbstractConstraint.__cmp__() dropped -* error.Asn1Error replaced with error.PyAsn1Error - -Revision 0.0.1a ---------------- - -* Initial public alpha release diff --git a/src/lib/pyasn1/LICENSE b/src/lib/pyasn1/LICENSE.rst similarity index 95% rename from src/lib/pyasn1/LICENSE rename to src/lib/pyasn1/LICENSE.rst index fac589b8..02b45c43 100644 --- a/src/lib/pyasn1/LICENSE +++ b/src/lib/pyasn1/LICENSE.rst @@ -1,4 +1,4 @@ -Copyright (c) 2005-2013, Ilya Etingof +Copyright (c) 2005-2017, Ilya Etingof All rights reserved. Redistribution and use in source and binary forms, with or without diff --git a/src/lib/pyasn1/PKG-INFO b/src/lib/pyasn1/PKG-INFO deleted file mode 100644 index 5de78ece..00000000 --- a/src/lib/pyasn1/PKG-INFO +++ /dev/null @@ -1,26 +0,0 @@ -Metadata-Version: 1.0 -Name: pyasn1 -Version: 0.1.7 -Summary: ASN.1 types and codecs -Home-page: http://sourceforge.net/projects/pyasn1/ -Author: Ilya Etingof -Author-email: ilya@glas.net -License: BSD -Description: A pure-Python implementation of ASN.1 types and DER/BER/CER codecs (X.208). -Platform: any -Classifier: Development Status :: 5 - Production/Stable -Classifier: Environment :: Console -Classifier: Intended Audience :: Developers -Classifier: Intended Audience :: Education -Classifier: Intended Audience :: Information Technology -Classifier: Intended Audience :: Science/Research -Classifier: Intended Audience :: System Administrators -Classifier: Intended Audience :: Telecommunications Industry -Classifier: License :: OSI Approved :: BSD License -Classifier: Natural Language :: English -Classifier: Operating System :: OS Independent -Classifier: Programming Language :: Python :: 2 -Classifier: Programming Language :: Python :: 3 -Classifier: Topic :: Communications -Classifier: Topic :: Security :: Cryptography -Classifier: Topic :: Software Development :: Libraries :: Python Modules diff --git a/src/lib/pyasn1/README b/src/lib/pyasn1/README deleted file mode 100644 index ffa3b57e..00000000 --- a/src/lib/pyasn1/README +++ /dev/null @@ -1,68 +0,0 @@ - -ASN.1 library for Python ------------------------- - -This is an implementation of ASN.1 types and codecs in Python programming -language. It has been first written to support particular protocol (SNMP) -but then generalized to be suitable for a wide range of protocols -based on ASN.1 specification. - -FEATURES --------- - -* Generic implementation of ASN.1 types (X.208) -* Fully standard compliant BER/CER/DER codecs -* 100% Python, works with Python 2.4 up to Python 3.3 (beta 1) -* MT-safe - -MISFEATURES ------------ - -* No ASN.1 compiler (by-hand ASN.1 spec compilation into Python code required) -* Codecs are not restartable - -INSTALLATION ------------- - -The pyasn1 package uses setuptools/distutils for installation. Thus do -either: - -$ easy_install pyasn1 - -or - -$ tar zxf pyasn1-0.1.3.tar.gz -$ cd pyasn1-0.1.3 -$ python setup.py install -$ cd test -$ python suite.py # run unit tests - -OPERATION ---------- - -Perhaps a typical use would involve [by-hand] compilation of your ASN.1 -specification into pyasn1-backed Python code at your application. - -For more information on pyasn1 APIs, please, refer to the -doc/pyasn1-tutorial.html file in the distribution. - -Also refer to example modules. Take a look at pyasn1-modules package -- maybe -it already holds something useful to you. - -AVAILABILITY ------------- - -The pyasn1 package is distributed under terms and conditions of BSD-style -license. See LICENSE file in the distribution. Source code is freely -available from: - -http://pyasn1.sf.net - - -FEEDBACK --------- - -Please, send your comments and fixes to mailing lists at project web site. - -=-=-= -mailto: ilya@glas.net diff --git a/src/lib/pyasn1/THANKS b/src/lib/pyasn1/THANKS deleted file mode 100644 index 4de1713c..00000000 --- a/src/lib/pyasn1/THANKS +++ /dev/null @@ -1,4 +0,0 @@ -Denis S. Otkidach -Gregory Golberg -Bud P. Bruegger -Jacek Konieczny diff --git a/src/lib/pyasn1/TODO b/src/lib/pyasn1/TODO deleted file mode 100644 index 0ee211c2..00000000 --- a/src/lib/pyasn1/TODO +++ /dev/null @@ -1,36 +0,0 @@ -* Specialize ASN.1 character and useful types -* Come up with simpler API for deeply nested constructed objects - addressing - -ber.decoder: -* suspend codec on underrun error ? -* class-static components map (in simple type classes) -* present subtypes ? -* component presence check wont work at innertypeconst -* add the rest of ASN1 types/codecs -* type vs value, defaultValue - -ber.encoder: -* Asn1Item.clone() / shallowcopy issue -* large length encoder? -* codec restart -* preserve compatible API whenever stateful codec gets implemented -* restartable vs incremental -* plan: make a stateless univeral decoder, then convert it to restartable - then to incremental - -type.useful: -* may need to implement prettyIn/Out - -type.char: -* may need to implement constraints - -type.univ: -* simpler API to constructed objects: value init, recursive - -type.namedtypes -* type vs tagset name convention - -general: - -* how untagged TagSet should be initialized? diff --git a/src/lib/pyasn1/__init__.py b/src/lib/pyasn1/__init__.py index 88aff79c..091f6c3c 100644 --- a/src/lib/pyasn1/__init__.py +++ b/src/lib/pyasn1/__init__.py @@ -1,8 +1,8 @@ import sys # http://www.python.org/dev/peps/pep-0396/ -__version__ = '0.1.7' +__version__ = '0.2.4' if sys.version_info[:2] < (2, 4): - raise RuntimeError('PyASN1 requires Python 2.4 or later') + raise RuntimeError('PyASN1 requires Python 2.4 or later') diff --git a/src/lib/pyasn1/codec/ber/decoder.py b/src/lib/pyasn1/codec/ber/decoder.py index be0cf490..e100e750 100644 --- a/src/lib/pyasn1/codec/ber/decoder.py +++ b/src/lib/pyasn1/codec/ber/decoder.py @@ -1,56 +1,72 @@ -# BER decoder -from pyasn1.type import tag, base, univ, char, useful, tagmap +# +# This file is part of pyasn1 software. +# +# Copyright (c) 2005-2017, Ilya Etingof +# License: http://pyasn1.sf.net/license.html +# +from pyasn1.type import base, tag, univ, char, useful, tagmap from pyasn1.codec.ber import eoo -from pyasn1.compat.octets import oct2int, octs2ints, isOctetsType +from pyasn1.compat.octets import oct2int, octs2ints, ints2octs, ensureString, null +from pyasn1.compat.integer import from_bytes from pyasn1 import debug, error -class AbstractDecoder: +__all__ = ['decode'] + + +class AbstractDecoder(object): protoComponent = None + def valueDecoder(self, fullSubstrate, substrate, asn1Spec, tagSet, length, state, decodeFun, substrateFun): raise error.PyAsn1Error('Decoder not implemented for %s' % (tagSet,)) def indefLenValueDecoder(self, fullSubstrate, substrate, asn1Spec, tagSet, - length, state, decodeFun, substrateFun): + length, state, decodeFun, substrateFun): raise error.PyAsn1Error('Indefinite length mode decoder not implemented for %s' % (tagSet,)) + class AbstractSimpleDecoder(AbstractDecoder): tagFormats = (tag.tagFormatSimple,) + + @staticmethod + def substrateCollector(asn1Object, substrate, length): + return substrate[:length], substrate[length:] + def _createComponent(self, asn1Spec, tagSet, value=None): - if tagSet[0][1] not in self.tagFormats: - raise error.PyAsn1Error('Invalid tag format %r for %r' % (tagSet[0], self.protoComponent,)) + if tagSet[0].tagFormat not in self.tagFormats: + raise error.PyAsn1Error('Invalid tag format %s for %s' % (tagSet[0], self.protoComponent.prettyPrintType())) if asn1Spec is None: return self.protoComponent.clone(value, tagSet) elif value is None: return asn1Spec else: return asn1Spec.clone(value) - + + class AbstractConstructedDecoder(AbstractDecoder): tagFormats = (tag.tagFormatConstructed,) + + # noinspection PyUnusedLocal def _createComponent(self, asn1Spec, tagSet, value=None): - if tagSet[0][1] not in self.tagFormats: - raise error.PyAsn1Error('Invalid tag format %r for %r' % (tagSet[0], self.protoComponent,)) + if tagSet[0].tagFormat not in self.tagFormats: + raise error.PyAsn1Error('Invalid tag format %s for %s' % (tagSet[0], self.protoComponent.prettyPrintType())) if asn1Spec is None: return self.protoComponent.clone(tagSet) else: return asn1Spec.clone() - -class EndOfOctetsDecoder(AbstractSimpleDecoder): - def valueDecoder(self, fullSubstrate, substrate, asn1Spec, tagSet, - length, state, decodeFun, substrateFun): - return eoo.endOfOctets, substrate[length:] + class ExplicitTagDecoder(AbstractSimpleDecoder): protoComponent = univ.Any('') tagFormats = (tag.tagFormatConstructed,) + def valueDecoder(self, fullSubstrate, substrate, asn1Spec, tagSet, length, state, decodeFun, substrateFun): if substrateFun: return substrateFun( - self._createComponent(asn1Spec, tagSet, ''), - substrate, length - ) + self._createComponent(asn1Spec, tagSet, ''), + substrate, length + ) head, tail = substrate[:length], substrate[length:] value, _ = decodeFun(head, asn1Spec, tagSet, length) return value, tail @@ -59,177 +75,184 @@ class ExplicitTagDecoder(AbstractSimpleDecoder): length, state, decodeFun, substrateFun): if substrateFun: return substrateFun( - self._createComponent(asn1Spec, tagSet, ''), - substrate, length - ) + self._createComponent(asn1Spec, tagSet, ''), + substrate, length + ) value, substrate = decodeFun(substrate, asn1Spec, tagSet, length) - terminator, substrate = decodeFun(substrate) - if eoo.endOfOctets.isSameTypeWith(terminator) and \ - terminator == eoo.endOfOctets: + terminator, substrate = decodeFun(substrate, allowEoo=True) + if terminator is eoo.endOfOctets: return value, substrate else: raise error.PyAsn1Error('Missing end-of-octets terminator') + explicitTagDecoder = ExplicitTagDecoder() + class IntegerDecoder(AbstractSimpleDecoder): protoComponent = univ.Integer(0) - precomputedValues = { - '\x00': 0, - '\x01': 1, - '\x02': 2, - '\x03': 3, - '\x04': 4, - '\x05': 5, - '\x06': 6, - '\x07': 7, - '\x08': 8, - '\x09': 9, - '\xff': -1, - '\xfe': -2, - '\xfd': -3, - '\xfc': -4, - '\xfb': -5 - } - + def valueDecoder(self, fullSubstrate, substrate, asn1Spec, tagSet, length, state, decodeFun, substrateFun): head, tail = substrate[:length], substrate[length:] + if not head: return self._createComponent(asn1Spec, tagSet, 0), tail - if head in self.precomputedValues: - value = self.precomputedValues[head] - else: - firstOctet = oct2int(head[0]) - if firstOctet & 0x80: - value = -1 - else: - value = 0 - for octet in head: - value = value << 8 | oct2int(octet) + + value = from_bytes(head, signed=True) + return self._createComponent(asn1Spec, tagSet, value), tail + class BooleanDecoder(IntegerDecoder): protoComponent = univ.Boolean(0) + def _createComponent(self, asn1Spec, tagSet, value=None): return IntegerDecoder._createComponent(self, asn1Spec, tagSet, value and 1 or 0) + class BitStringDecoder(AbstractSimpleDecoder): protoComponent = univ.BitString(()) tagFormats = (tag.tagFormatSimple, tag.tagFormatConstructed) + supportConstructedForm = True + def valueDecoder(self, fullSubstrate, substrate, asn1Spec, tagSet, length, state, decodeFun, substrateFun): head, tail = substrate[:length], substrate[length:] - if tagSet[0][1] == tag.tagFormatSimple: # XXX what tag to check? + if tagSet[0].tagFormat == tag.tagFormatSimple: # XXX what tag to check? if not head: raise error.PyAsn1Error('Empty substrate') trailingBits = oct2int(head[0]) if trailingBits > 7: raise error.PyAsn1Error( 'Trailing bits overflow %s' % trailingBits - ) + ) head = head[1:] - lsb = p = 0; l = len(head)-1; b = () - while p <= l: - if p == l: - lsb = trailingBits - j = 7 - o = oct2int(head[p]) - while j >= lsb: - b = b + ((o>>j)&0x01,) - j = j - 1 - p = p + 1 - return self._createComponent(asn1Spec, tagSet, b), tail - r = self._createComponent(asn1Spec, tagSet, ()) + value = self.protoComponent.fromOctetString(head, trailingBits) + return self._createComponent(asn1Spec, tagSet, value), tail + + if not self.supportConstructedForm: + raise error.PyAsn1Error('Constructed encoding form prohibited at %s' % self.__class__.__name__) + + bitString = self._createComponent(asn1Spec, tagSet) + if substrateFun: - return substrateFun(r, substrate, length) + return substrateFun(bitString, substrate, length) + while head: - component, head = decodeFun(head) - r = r + component - return r, tail + component, head = decodeFun(head, self.protoComponent) + bitString += component + + return bitString, tail def indefLenValueDecoder(self, fullSubstrate, substrate, asn1Spec, tagSet, length, state, decodeFun, substrateFun): - r = self._createComponent(asn1Spec, tagSet, '') + bitString = self._createComponent(asn1Spec, tagSet) + if substrateFun: - return substrateFun(r, substrate, length) + return substrateFun(bitString, substrate, length) + while substrate: - component, substrate = decodeFun(substrate) - if eoo.endOfOctets.isSameTypeWith(component) and \ - component == eoo.endOfOctets: + component, substrate = decodeFun(substrate, self.protoComponent, allowEoo=True) + if component is eoo.endOfOctets: break - r = r + component + + bitString += component + else: - raise error.SubstrateUnderrunError( - 'No EOO seen before substrate ends' - ) - return r, substrate + raise error.SubstrateUnderrunError('No EOO seen before substrate ends') + + return bitString, substrate + class OctetStringDecoder(AbstractSimpleDecoder): protoComponent = univ.OctetString('') tagFormats = (tag.tagFormatSimple, tag.tagFormatConstructed) + supportConstructedForm = True + def valueDecoder(self, fullSubstrate, substrate, asn1Spec, tagSet, length, state, decodeFun, substrateFun): head, tail = substrate[:length], substrate[length:] - if tagSet[0][1] == tag.tagFormatSimple: # XXX what tag to check? - return self._createComponent(asn1Spec, tagSet, head), tail - r = self._createComponent(asn1Spec, tagSet, '') + if substrateFun: - return substrateFun(r, substrate, length) + return substrateFun(self._createComponent(asn1Spec, tagSet), + substrate, length) + + if tagSet[0].tagFormat == tag.tagFormatSimple: # XXX what tag to check? + return self._createComponent(asn1Spec, tagSet, head), tail + + if not self.supportConstructedForm: + raise error.PyAsn1Error('Constructed encoding form prohibited at %s' % self.__class__.__name__) + + # All inner fragments are of the same type, treat them as octet string + substrateFun = self.substrateCollector + + header = null + while head: - component, head = decodeFun(head) - r = r + component - return r, tail + component, head = decodeFun(head, self.protoComponent, + substrateFun=substrateFun) + header += component + + return self._createComponent(asn1Spec, tagSet, header), tail def indefLenValueDecoder(self, fullSubstrate, substrate, asn1Spec, tagSet, length, state, decodeFun, substrateFun): - r = self._createComponent(asn1Spec, tagSet, '') - if substrateFun: - return substrateFun(r, substrate, length) + if substrateFun and substrateFun is not self.substrateCollector: + asn1Object = self._createComponent(asn1Spec, tagSet) + return substrateFun(asn1Object, substrate, length) + + # All inner fragments are of the same type, treat them as octet string + substrateFun = self.substrateCollector + + header = null + while substrate: - component, substrate = decodeFun(substrate) - if eoo.endOfOctets.isSameTypeWith(component) and \ - component == eoo.endOfOctets: + component, substrate = decodeFun(substrate, + self.protoComponent, + substrateFun=substrateFun, + allowEoo=True) + if component is eoo.endOfOctets: break - r = r + component + header += component else: raise error.SubstrateUnderrunError( 'No EOO seen before substrate ends' - ) - return r, substrate + ) + return self._createComponent(asn1Spec, tagSet, header), substrate + class NullDecoder(AbstractSimpleDecoder): protoComponent = univ.Null('') + def valueDecoder(self, fullSubstrate, substrate, asn1Spec, tagSet, length, state, decodeFun, substrateFun): head, tail = substrate[:length], substrate[length:] - r = self._createComponent(asn1Spec, tagSet) + component = self._createComponent(asn1Spec, tagSet) if head: raise error.PyAsn1Error('Unexpected %d-octet substrate for Null' % length) - return r, tail + return component, tail + class ObjectIdentifierDecoder(AbstractSimpleDecoder): protoComponent = univ.ObjectIdentifier(()) + def valueDecoder(self, fullSubstrate, substrate, asn1Spec, tagSet, length, state, decodeFun, substrateFun): head, tail = substrate[:length], substrate[length:] if not head: raise error.PyAsn1Error('Empty substrate') - # Get the first subid - subId = oct2int(head[0]) - oid = divmod(subId, 40) + head = octs2ints(head) - index = 1 + oid = () + index = 0 substrateLen = len(head) while index < substrateLen: - subId = oct2int(head[index]) - index = index + 1 - if subId == 128: - # ASN.1 spec forbids leading zeros (0x80) in sub-ID OID - # encoding, tolerating it opens a vulnerability. - # See http://www.cosic.esat.kuleuven.be/publications/article-1432.pdf page 7 - raise error.PyAsn1Error('Invalid leading 0x80 in sub-OID') + subId = head[index] + index += 1 + if subId < 128: + oid = oid + (subId,) elif subId > 128: # Construct subid from a number of octets nextSubId = subId @@ -239,44 +262,77 @@ class ObjectIdentifierDecoder(AbstractSimpleDecoder): if index >= substrateLen: raise error.SubstrateUnderrunError( 'Short substrate for sub-OID past %s' % (oid,) - ) - nextSubId = oct2int(head[index]) - index = index + 1 - subId = (subId << 7) + nextSubId - oid = oid + (subId,) + ) + nextSubId = head[index] + index += 1 + oid += ((subId << 7) + nextSubId,) + elif subId == 128: + # ASN.1 spec forbids leading zeros (0x80) in OID + # encoding, tolerating it opens a vulnerability. See + # http://www.cosic.esat.kuleuven.be/publications/article-1432.pdf + # page 7 + raise error.PyAsn1Error('Invalid octet 0x80 in OID encoding') + + # Decode two leading arcs + if 0 <= oid[0] <= 39: + oid = (0,) + oid + elif 40 <= oid[0] <= 79: + oid = (1, oid[0] - 40) + oid[1:] + elif oid[0] >= 80: + oid = (2, oid[0] - 80) + oid[1:] + else: + raise error.PyAsn1Error('Malformed first OID octet: %s' % head[0]) + return self._createComponent(asn1Spec, tagSet, oid), tail + class RealDecoder(AbstractSimpleDecoder): protoComponent = univ.Real() + def valueDecoder(self, fullSubstrate, substrate, asn1Spec, tagSet, length, state, decodeFun, substrateFun): head, tail = substrate[:length], substrate[length:] if not head: return self._createComponent(asn1Spec, tagSet, 0.0), tail - fo = oct2int(head[0]); head = head[1:] - if fo & 0x80: # binary enoding + fo = oct2int(head[0]) + head = head[1:] + if fo & 0x80: # binary encoding + if not head: + raise error.PyAsn1Error("Incomplete floating-point value") n = (fo & 0x03) + 1 if n == 4: n = oct2int(head[0]) + head = head[1:] eo, head = head[:n], head[n:] if not eo or not head: raise error.PyAsn1Error('Real exponent screwed') e = oct2int(eo[0]) & 0x80 and -1 or 0 - while eo: # exponent + while eo: # exponent e <<= 8 e |= oct2int(eo[0]) eo = eo[1:] + b = fo >> 4 & 0x03 # base bits + if b > 2: + raise error.PyAsn1Error('Illegal Real base') + if b == 1: # encbase = 8 + e *= 3 + elif b == 2: # encbase = 16 + e *= 4 p = 0 while head: # value p <<= 8 p |= oct2int(head[0]) head = head[1:] - if fo & 0x40: # sign bit + if fo & 0x40: # sign bit p = -p + sf = fo >> 2 & 0x03 # scale bits + p *= 2 ** sf value = (p, 2, e) elif fo & 0x40: # infinite value value = fo & 0x01 and '-inf' or 'inf' elif fo & 0xc0 == 0: # character encoding + if not head: + raise error.PyAsn1Error("Incomplete floating-point value") try: if fo & 0x3 == 0x1: # NR1 value = (int(head), 10, 0) @@ -287,180 +343,268 @@ class RealDecoder(AbstractSimpleDecoder): else: raise error.SubstrateUnderrunError( 'Unknown NR (tag %s)' % fo - ) + ) except ValueError: raise error.SubstrateUnderrunError( 'Bad character Real syntax' - ) + ) else: raise error.SubstrateUnderrunError( 'Unknown encoding (tag %s)' % fo - ) + ) return self._createComponent(asn1Spec, tagSet, value), tail - -class SequenceDecoder(AbstractConstructedDecoder): + + +class SequenceAndSetDecoderBase(AbstractConstructedDecoder): + protoComponent = None + orderedComponents = False + + def _getComponentTagMap(self, asn1Object, idx): + raise NotImplementedError() + + def _getComponentPositionByType(self, asn1Object, tagSet, idx): + raise NotImplementedError() + + def valueDecoder(self, fullSubstrate, substrate, asn1Spec, tagSet, + length, state, decodeFun, substrateFun): + head, tail = substrate[:length], substrate[length:] + asn1Object = self._createComponent(asn1Spec, tagSet) + if substrateFun: + return substrateFun(asn1Object, substrate, length) + + namedTypes = asn1Object.getComponentType() + + if not self.orderedComponents or not namedTypes or namedTypes.hasOptionalOrDefault: + seenIndices = set() + idx = 0 + while head: + asn1Spec = self._getComponentTagMap(asn1Object, idx) + component, head = decodeFun(head, asn1Spec) + idx = self._getComponentPositionByType( + asn1Object, component.effectiveTagSet, idx + ) + + asn1Object.setComponentByPosition( + idx, component, + verifyConstraints=False, + matchTags=False, matchConstraints=False + ) + seenIndices.add(idx) + idx += 1 + + if namedTypes and not namedTypes.requiredComponents.issubset(seenIndices): + raise error.PyAsn1Error('ASN.1 object %s has uninitialized components' % asn1Object.__class__.__name__) + else: + for idx, asn1Spec in enumerate(namedTypes.values()): + component, head = decodeFun(head, asn1Spec) + asn1Object.setComponentByPosition( + idx, component, + verifyConstraints=False, + matchTags=False, matchConstraints=False + ) + + if not namedTypes: + asn1Object.verifySizeSpec() + + return asn1Object, tail + + def indefLenValueDecoder(self, fullSubstrate, substrate, asn1Spec, tagSet, + length, state, decodeFun, substrateFun): + asn1Object = self._createComponent(asn1Spec, tagSet) + if substrateFun: + return substrateFun(asn1Object, substrate, length) + + namedTypes = asn1Object.getComponentType() + + if not namedTypes or namedTypes.hasOptionalOrDefault: + seenIndices = set() + idx = 0 + while substrate: + asn1Spec = self._getComponentTagMap(asn1Object, idx) + component, substrate = decodeFun(substrate, asn1Spec, allowEoo=True) + if component is eoo.endOfOctets: + break + idx = self._getComponentPositionByType( + asn1Object, component.effectiveTagSet, idx + ) + + asn1Object.setComponentByPosition( + idx, component, + verifyConstraints=False, + matchTags=False, matchConstraints=False + ) + seenIndices.add(idx) + idx += 1 + + else: + raise error.SubstrateUnderrunError( + 'No EOO seen before substrate ends' + ) + + if namedTypes and not namedTypes.requiredComponents.issubset(seenIndices): + raise error.PyAsn1Error('ASN.1 object %s has uninitialized components' % asn1Object.__class__.__name__) + else: + for idx, asn1Spec in enumerate(namedTypes.values()): + component, substrate = decodeFun(substrate, asn1Spec) + + asn1Object.setComponentByPosition( + idx, component, + verifyConstraints=False, + matchTags=False, matchConstraints=False + ) + + component, substrate = decodeFun(substrate, eoo.endOfOctets, allowEoo=True) + if component is not eoo.endOfOctets: + raise error.SubstrateUnderrunError( + 'No EOO seen before substrate ends' + ) + + if not namedTypes: + asn1Object.verifySizeSpec() + + return asn1Object, substrate + +class SequenceDecoder(SequenceAndSetDecoderBase): protoComponent = univ.Sequence() - def _getComponentTagMap(self, r, idx): + orderedComponents = True + + def _getComponentTagMap(self, asn1Object, idx): try: - return r.getComponentTagMapNearPosition(idx) + return asn1Object.getComponentTagMapNearPosition(idx) except error.PyAsn1Error: return - def _getComponentPositionByType(self, r, t, idx): - return r.getComponentPositionNearType(t, idx) - - def valueDecoder(self, fullSubstrate, substrate, asn1Spec, tagSet, - length, state, decodeFun, substrateFun): - head, tail = substrate[:length], substrate[length:] - r = self._createComponent(asn1Spec, tagSet) - idx = 0 - if substrateFun: - return substrateFun(r, substrate, length) - while head: - asn1Spec = self._getComponentTagMap(r, idx) - component, head = decodeFun(head, asn1Spec) - idx = self._getComponentPositionByType( - r, component.getEffectiveTagSet(), idx - ) - r.setComponentByPosition(idx, component, asn1Spec is None) - idx = idx + 1 - r.setDefaultComponents() - r.verifySizeSpec() - return r, tail + def _getComponentPositionByType(self, asn1Object, tagSet, idx): + return asn1Object.getComponentPositionNearType(tagSet, idx) - def indefLenValueDecoder(self, fullSubstrate, substrate, asn1Spec, tagSet, - length, state, decodeFun, substrateFun): - r = self._createComponent(asn1Spec, tagSet) - if substrateFun: - return substrateFun(r, substrate, length) - idx = 0 - while substrate: - asn1Spec = self._getComponentTagMap(r, idx) - component, substrate = decodeFun(substrate, asn1Spec) - if eoo.endOfOctets.isSameTypeWith(component) and \ - component == eoo.endOfOctets: - break - idx = self._getComponentPositionByType( - r, component.getEffectiveTagSet(), idx - ) - r.setComponentByPosition(idx, component, asn1Spec is None) - idx = idx + 1 - else: - raise error.SubstrateUnderrunError( - 'No EOO seen before substrate ends' - ) - r.setDefaultComponents() - r.verifySizeSpec() - return r, substrate class SequenceOfDecoder(AbstractConstructedDecoder): - protoComponent = univ.SequenceOf() + protoComponent = univ.SequenceOf() + def valueDecoder(self, fullSubstrate, substrate, asn1Spec, tagSet, length, state, decodeFun, substrateFun): head, tail = substrate[:length], substrate[length:] - r = self._createComponent(asn1Spec, tagSet) + asn1Object = self._createComponent(asn1Spec, tagSet) if substrateFun: - return substrateFun(r, substrate, length) - asn1Spec = r.getComponentType() + return substrateFun(asn1Object, substrate, length) + asn1Spec = asn1Object.getComponentType() idx = 0 while head: component, head = decodeFun(head, asn1Spec) - r.setComponentByPosition(idx, component, asn1Spec is None) - idx = idx + 1 - r.verifySizeSpec() - return r, tail + asn1Object.setComponentByPosition( + idx, component, + verifyConstraints=False, + matchTags=False, matchConstraints=False + ) + idx += 1 + asn1Object.verifySizeSpec() + return asn1Object, tail def indefLenValueDecoder(self, fullSubstrate, substrate, asn1Spec, tagSet, length, state, decodeFun, substrateFun): - r = self._createComponent(asn1Spec, tagSet) + asn1Object = self._createComponent(asn1Spec, tagSet) if substrateFun: - return substrateFun(r, substrate, length) - asn1Spec = r.getComponentType() + return substrateFun(asn1Object, substrate, length) + asn1Spec = asn1Object.getComponentType() idx = 0 while substrate: - component, substrate = decodeFun(substrate, asn1Spec) - if eoo.endOfOctets.isSameTypeWith(component) and \ - component == eoo.endOfOctets: + component, substrate = decodeFun(substrate, asn1Spec, allowEoo=True) + if component is eoo.endOfOctets: break - r.setComponentByPosition(idx, component, asn1Spec is None) - idx = idx + 1 + asn1Object.setComponentByPosition( + idx, component, + verifyConstraints=False, + matchTags=False, matchConstraints=False + ) + idx += 1 else: raise error.SubstrateUnderrunError( 'No EOO seen before substrate ends' - ) - r.verifySizeSpec() - return r, substrate + ) + asn1Object.verifySizeSpec() + return asn1Object, substrate -class SetDecoder(SequenceDecoder): + +class SetDecoder(SequenceAndSetDecoderBase): protoComponent = univ.Set() - def _getComponentTagMap(self, r, idx): - return r.getComponentTagMap() + orderedComponents = False - def _getComponentPositionByType(self, r, t, idx): - nextIdx = r.getComponentPositionByType(t) + def _getComponentTagMap(self, asn1Object, idx): + return asn1Object.componentTagMap + + def _getComponentPositionByType(self, asn1Object, tagSet, idx): + nextIdx = asn1Object.getComponentPositionByType(tagSet) if nextIdx is None: return idx else: return nextIdx - + + class SetOfDecoder(SequenceOfDecoder): protoComponent = univ.SetOf() - + + class ChoiceDecoder(AbstractConstructedDecoder): protoComponent = univ.Choice() tagFormats = (tag.tagFormatSimple, tag.tagFormatConstructed) + def valueDecoder(self, fullSubstrate, substrate, asn1Spec, tagSet, length, state, decodeFun, substrateFun): head, tail = substrate[:length], substrate[length:] - r = self._createComponent(asn1Spec, tagSet) + asn1Object = self._createComponent(asn1Spec, tagSet) if substrateFun: - return substrateFun(r, substrate, length) - if r.getTagSet() == tagSet: # explicitly tagged Choice + return substrateFun(asn1Object, substrate, length) + if asn1Object.tagSet == tagSet: # explicitly tagged Choice component, head = decodeFun( - head, r.getComponentTagMap() - ) + head, asn1Object.componentTagMap + ) else: component, head = decodeFun( - head, r.getComponentTagMap(), tagSet, length, state - ) - if isinstance(component, univ.Choice): - effectiveTagSet = component.getEffectiveTagSet() - else: - effectiveTagSet = component.getTagSet() - r.setComponentByType(effectiveTagSet, component, 0, asn1Spec is None) - return r, tail + head, asn1Object.componentTagMap, tagSet, length, state + ) + effectiveTagSet = component.effectiveTagSet + asn1Object.setComponentByType( + effectiveTagSet, component, + verifyConstraints=False, + matchTags=False, matchConstraints=False, + innerFlag=False + ) + return asn1Object, tail def indefLenValueDecoder(self, fullSubstrate, substrate, asn1Spec, tagSet, - length, state, decodeFun, substrateFun): - r = self._createComponent(asn1Spec, tagSet) + length, state, decodeFun, substrateFun): + asn1Object = self._createComponent(asn1Spec, tagSet) if substrateFun: - return substrateFun(r, substrate, length) - if r.getTagSet() == tagSet: # explicitly tagged Choice - component, substrate = decodeFun(substrate, r.getComponentTagMap()) - eooMarker, substrate = decodeFun(substrate) # eat up EOO marker - if not eoo.endOfOctets.isSameTypeWith(eooMarker) or \ - eooMarker != eoo.endOfOctets: + return substrateFun(asn1Object, substrate, length) + if asn1Object.tagSet == tagSet: # explicitly tagged Choice + component, substrate = decodeFun(substrate, asn1Object.componentTagMap) + # eat up EOO marker + eooMarker, substrate = decodeFun(substrate, allowEoo=True) + if eooMarker is not eoo.endOfOctets: raise error.PyAsn1Error('No EOO seen before substrate ends') else: - component, substrate= decodeFun( - substrate, r.getComponentTagMap(), tagSet, length, state + component, substrate = decodeFun( + substrate, asn1Object.componentTagMap, tagSet, length, state ) - if isinstance(component, univ.Choice): - effectiveTagSet = component.getEffectiveTagSet() - else: - effectiveTagSet = component.getTagSet() - r.setComponentByType(effectiveTagSet, component, 0, asn1Spec is None) - return r, substrate + effectiveTagSet = component.effectiveTagSet + asn1Object.setComponentByType( + effectiveTagSet, component, + verifyConstraints=False, + matchTags=False, matchConstraints=False, + innerFlag=False + ) + return asn1Object, substrate + class AnyDecoder(AbstractSimpleDecoder): protoComponent = univ.Any() tagFormats = (tag.tagFormatSimple, tag.tagFormatConstructed) + def valueDecoder(self, fullSubstrate, substrate, asn1Spec, tagSet, length, state, decodeFun, substrateFun): - if asn1Spec is None or \ - asn1Spec is not None and tagSet != asn1Spec.getTagSet(): + if asn1Spec is None or asn1Spec is not None and tagSet != asn1Spec.tagSet: # untagged Any container, recover inner header substrate - length = length + len(fullSubstrate) - len(substrate) + length += len(fullSubstrate) - len(substrate) substrate = fullSubstrate if substrateFun: return substrateFun(self._createComponent(asn1Spec, tagSet), @@ -470,64 +614,99 @@ class AnyDecoder(AbstractSimpleDecoder): def indefLenValueDecoder(self, fullSubstrate, substrate, asn1Spec, tagSet, length, state, decodeFun, substrateFun): - if asn1Spec is not None and tagSet == asn1Spec.getTagSet(): + if asn1Spec is not None and tagSet == asn1Spec.tagSet: # tagged Any type -- consume header substrate - header = '' + header = null else: # untagged Any, recover header substrate header = fullSubstrate[:-len(substrate)] - r = self._createComponent(asn1Spec, tagSet, header) - # Any components do not inherit initial tag asn1Spec = self.protoComponent - - if substrateFun: - return substrateFun(r, substrate, length) + + if substrateFun and substrateFun is not self.substrateCollector: + asn1Object = self._createComponent(asn1Spec, tagSet) + return substrateFun(asn1Object, header + substrate, length + len(header)) + + # All inner fragments are of the same type, treat them as octet string + substrateFun = self.substrateCollector + while substrate: - component, substrate = decodeFun(substrate, asn1Spec) - if eoo.endOfOctets.isSameTypeWith(component) and \ - component == eoo.endOfOctets: + component, substrate = decodeFun(substrate, asn1Spec, + substrateFun=substrateFun, + allowEoo=True) + if component is eoo.endOfOctets: break - r = r + component + header += component else: raise error.SubstrateUnderrunError( 'No EOO seen before substrate ends' - ) - return r, substrate + ) + if substrateFun: + return header, substrate + else: + return self._createComponent(asn1Spec, tagSet, header), substrate + # character string types class UTF8StringDecoder(OctetStringDecoder): protoComponent = char.UTF8String() + + class NumericStringDecoder(OctetStringDecoder): protoComponent = char.NumericString() + + class PrintableStringDecoder(OctetStringDecoder): protoComponent = char.PrintableString() + + class TeletexStringDecoder(OctetStringDecoder): protoComponent = char.TeletexString() + + class VideotexStringDecoder(OctetStringDecoder): protoComponent = char.VideotexString() + + class IA5StringDecoder(OctetStringDecoder): protoComponent = char.IA5String() + + class GraphicStringDecoder(OctetStringDecoder): protoComponent = char.GraphicString() + + class VisibleStringDecoder(OctetStringDecoder): protoComponent = char.VisibleString() + + class GeneralStringDecoder(OctetStringDecoder): protoComponent = char.GeneralString() + + class UniversalStringDecoder(OctetStringDecoder): protoComponent = char.UniversalString() + + class BMPStringDecoder(OctetStringDecoder): protoComponent = char.BMPString() + # "useful" types +class ObjectDescriptorDecoder(OctetStringDecoder): + protoComponent = useful.ObjectDescriptor() + + class GeneralizedTimeDecoder(OctetStringDecoder): protoComponent = useful.GeneralizedTime() + + class UTCTimeDecoder(OctetStringDecoder): protoComponent = useful.UTCTime() + tagMap = { - eoo.endOfOctets.tagSet: EndOfOctetsDecoder(), univ.Integer.tagSet: IntegerDecoder(), univ.Boolean.tagSet: BooleanDecoder(), univ.BitString.tagSet: BitStringDecoder(), @@ -537,8 +716,8 @@ tagMap = { univ.Enumerated.tagSet: IntegerDecoder(), univ.Real.tagSet: RealDecoder(), univ.Sequence.tagSet: SequenceDecoder(), # conflicts with SequenceOf - univ.Set.tagSet: SetDecoder(), # conflicts with SetOf - univ.Choice.tagSet: ChoiceDecoder(), # conflicts with Any + univ.Set.tagSet: SetDecoder(), # conflicts with SetOf + univ.Choice.tagSet: ChoiceDecoder(), # conflicts with Any # character string types char.UTF8String.tagSet: UTF8StringDecoder(), char.NumericString.tagSet: NumericStringDecoder(), @@ -552,9 +731,10 @@ tagMap = { char.UniversalString.tagSet: UniversalStringDecoder(), char.BMPString.tagSet: BMPStringDecoder(), # useful types + useful.ObjectDescriptor.tagSet: ObjectDescriptorDecoder(), useful.GeneralizedTime.tagSet: GeneralizedTimeDecoder(), useful.UTCTime.tagSet: UTCTimeDecoder() - } +} # Type-to-codec map for ambiguous ASN.1 types typeMap = { @@ -564,114 +744,146 @@ typeMap = { univ.SequenceOf.typeId: SequenceOfDecoder(), univ.Choice.typeId: ChoiceDecoder(), univ.Any.typeId: AnyDecoder() - } +} -( stDecodeTag, stDecodeLength, stGetValueDecoder, stGetValueDecoderByAsn1Spec, - stGetValueDecoderByTag, stTryAsExplicitTag, stDecodeValue, - stDumpRawValue, stErrorCondition, stStop ) = [x for x in range(10)] +# Put in non-ambiguous types for faster codec lookup +for typeDecoder in tagMap.values(): + typeId = typeDecoder.protoComponent.__class__.typeId + if typeId is not None and typeId not in typeMap: + typeMap[typeId] = typeDecoder -class Decoder: + +(stDecodeTag, stDecodeLength, stGetValueDecoder, stGetValueDecoderByAsn1Spec, + stGetValueDecoderByTag, stTryAsExplicitTag, stDecodeValue, + stDumpRawValue, stErrorCondition, stStop) = [x for x in range(10)] + + +class Decoder(object): defaultErrorState = stErrorCondition -# defaultErrorState = stDumpRawValue + # defaultErrorState = stDumpRawValue defaultRawDecoder = AnyDecoder() + supportIndefLength = True + + # noinspection PyDefaultArgument def __init__(self, tagMap, typeMap={}): self.__tagMap = tagMap self.__typeMap = typeMap - self.__endOfOctetsTagSet = eoo.endOfOctets.getTagSet() # Tag & TagSet objects caches self.__tagCache = {} self.__tagSetCache = {} - + self.__eooSentinel = ints2octs((0, 0)) + def __call__(self, substrate, asn1Spec=None, tagSet=None, - length=None, state=stDecodeTag, recursiveFlag=1, - substrateFun=None): - if debug.logger & debug.flagDecoder: + length=None, state=stDecodeTag, recursiveFlag=True, + substrateFun=None, allowEoo=False): + if debug.logger and debug.logger & debug.flagDecoder: debug.logger('decoder called at scope %s with state %d, working with up to %d octets of substrate: %s' % (debug.scope, state, len(substrate), debug.hexdump(substrate))) + + substrate = ensureString(substrate) + + # Look for end-of-octets sentinel + if allowEoo and self.supportIndefLength: + if substrate.startswith(self.__eooSentinel): + debug.logger and debug.logger & debug.flagDecoder and debug.logger('end-of-octets sentinel found') + return eoo.endOfOctets, substrate[2:] + + value = base.noValue + fullSubstrate = substrate while state != stStop: if state == stDecodeTag: - # Decode tag if not substrate: raise error.SubstrateUnderrunError( 'Short octet stream on tag decoding' - ) - if not isOctetsType(substrate) and \ - not isinstance(substrate, univ.OctetString): - raise error.PyAsn1Error('Bad octet stream type') - + ) + # Decode tag + isShortTag = True firstOctet = substrate[0] substrate = substrate[1:] - if firstOctet in self.__tagCache: + try: lastTag = self.__tagCache[firstOctet] - else: - t = oct2int(firstOctet) - tagClass = t&0xC0 - tagFormat = t&0x20 - tagId = t&0x1F + except KeyError: + integerTag = oct2int(firstOctet) + tagClass = integerTag & 0xC0 + tagFormat = integerTag & 0x20 + tagId = integerTag & 0x1F if tagId == 0x1F: + isShortTag = False + lengthOctetIdx = 0 tagId = 0 - while 1: - if not substrate: - raise error.SubstrateUnderrunError( - 'Short octet stream on long tag decoding' - ) - t = oct2int(substrate[0]) - tagId = tagId << 7 | (t&0x7F) - substrate = substrate[1:] - if not t&0x80: - break + try: + while True: + integerTag = oct2int(substrate[lengthOctetIdx]) + lengthOctetIdx += 1 + tagId <<= 7 + tagId |= (integerTag & 0x7F) + if not integerTag & 0x80: + break + substrate = substrate[lengthOctetIdx:] + except IndexError: + raise error.SubstrateUnderrunError( + 'Short octet stream on long tag decoding' + ) lastTag = tag.Tag( tagClass=tagClass, tagFormat=tagFormat, tagId=tagId - ) - if tagId < 31: + ) + if isShortTag: # cache short tags self.__tagCache[firstOctet] = lastTag if tagSet is None: - if firstOctet in self.__tagSetCache: - tagSet = self.__tagSetCache[firstOctet] - else: - # base tag not recovered - tagSet = tag.TagSet((), lastTag) - if firstOctet in self.__tagCache: + if isShortTag: + try: + tagSet = self.__tagSetCache[firstOctet] + except KeyError: + # base tag not recovered + tagSet = tag.TagSet((), lastTag) self.__tagSetCache[firstOctet] = tagSet + else: + tagSet = tag.TagSet((), lastTag) else: tagSet = lastTag + tagSet state = stDecodeLength - debug.logger and debug.logger & debug.flagDecoder and debug.logger('tag decoded into %r, decoding length' % tagSet) + debug.logger and debug.logger & debug.flagDecoder and debug.logger( + 'tag decoded into %s, decoding length' % tagSet) if state == stDecodeLength: # Decode length if not substrate: - raise error.SubstrateUnderrunError( - 'Short octet stream on length decoding' - ) - firstOctet = oct2int(substrate[0]) - if firstOctet == 128: + raise error.SubstrateUnderrunError( + 'Short octet stream on length decoding' + ) + firstOctet = oct2int(substrate[0]) + if firstOctet < 128: + size = 1 + length = firstOctet + elif firstOctet == 128: size = 1 length = -1 - elif firstOctet < 128: - length, size = firstOctet, 1 else: size = firstOctet & 0x7F # encoded in size bytes - length = 0 - lengthString = substrate[1:size+1] + encodedLength = octs2ints(substrate[1:size + 1]) # missing check on maximum size, which shouldn't be a # problem, we can handle more than is possible - if len(lengthString) != size: + if len(encodedLength) != size: raise error.SubstrateUnderrunError( - '%s<%s at %s' % - (size, len(lengthString), tagSet) - ) - for char in lengthString: - length = (length << 8) | oct2int(char) - size = size + 1 - substrate = substrate[size:] - if length != -1 and len(substrate) < length: - raise error.SubstrateUnderrunError( - '%d-octet short' % (length - len(substrate)) + '%s<%s at %s' % (size, len(encodedLength), tagSet) ) + length = 0 + for lengthOctet in encodedLength: + length <<= 8 + length |= lengthOctet + size += 1 + substrate = substrate[size:] + if length == -1: + if not self.supportIndefLength: + raise error.PyAsn1Error('Indefinite length encoding not supported by this codec') + else: + if len(substrate) < length: + raise error.SubstrateUnderrunError('%d-octet short' % (length - len(substrate))) state = stGetValueDecoder - debug.logger and debug.logger & debug.flagDecoder and debug.logger('value length decoded into %d, payload substrate is: %s' % (length, debug.hexdump(length == -1 and substrate or substrate[:length]))) + debug.logger and debug.logger & debug.flagDecoder and debug.logger( + 'value length decoded into %d, payload substrate is: %s' % (length, debug.hexdump(length == -1 and substrate or substrate[:length])) + ) if state == stGetValueDecoder: if asn1Spec is None: state = stGetValueDecoderByTag @@ -692,19 +904,18 @@ class Decoder: # in an incremental, tag-by-tag fashion (this is the case of # EXPLICIT tag which is most basic). Outermost tag comes first # from the wire. - # + # if state == stGetValueDecoderByTag: - if tagSet in self.__tagMap: + try: concreteDecoder = self.__tagMap[tagSet] - else: + except KeyError: concreteDecoder = None if concreteDecoder: state = stDecodeValue else: - _k = tagSet[:1] - if _k in self.__tagMap: - concreteDecoder = self.__tagMap[_k] - else: + try: + concreteDecoder = self.__tagMap[tagSet[:1]] + except KeyError: concreteDecoder = None if concreteDecoder: state = stDecodeValue @@ -712,96 +923,125 @@ class Decoder: state = stTryAsExplicitTag if debug.logger and debug.logger & debug.flagDecoder: debug.logger('codec %s chosen by a built-in type, decoding %s' % (concreteDecoder and concreteDecoder.__class__.__name__ or "", state == stDecodeValue and 'value' or 'as explicit tag')) - debug.scope.push(concreteDecoder is None and '?' or concreteDecoder.protoComponent.__class__.__name__) + debug.scope.push( + concreteDecoder is None and '?' or concreteDecoder.protoComponent.__class__.__name__) if state == stGetValueDecoderByAsn1Spec: - if isinstance(asn1Spec, (dict, tagmap.TagMap)): - if tagSet in asn1Spec: - __chosenSpec = asn1Spec[tagSet] - else: - __chosenSpec = None + if asn1Spec.__class__ is dict or asn1Spec.__class__ is tagmap.TagMap: + try: + chosenSpec = asn1Spec[tagSet] + except KeyError: + chosenSpec = None if debug.logger and debug.logger & debug.flagDecoder: debug.logger('candidate ASN.1 spec is a map of:') - for t, v in asn1Spec.getPosMap().items(): - debug.logger(' %r -> %s' % (t, v.__class__.__name__)) - if asn1Spec.getNegMap(): + for firstOctet, v in asn1Spec.presentTypes.items(): + debug.logger(' %s -> %s' % (firstOctet, v.__class__.__name__)) + if asn1Spec.skipTypes: debug.logger('but neither of: ') - for i in asn1Spec.getNegMap().items(): - debug.logger(' %r -> %s' % (t, v.__class__.__name__)) - debug.logger('new candidate ASN.1 spec is %s, chosen by %r' % (__chosenSpec is None and '' or __chosenSpec.__class__.__name__, tagSet)) + for firstOctet, v in asn1Spec.skipTypes.items(): + debug.logger(' %s -> %s' % (firstOctet, v.__class__.__name__)) + debug.logger('new candidate ASN.1 spec is %s, chosen by %s' % (chosenSpec is None and '' or chosenSpec.prettyPrintType(), tagSet)) else: - __chosenSpec = asn1Spec - debug.logger and debug.logger & debug.flagDecoder and debug.logger('candidate ASN.1 spec is %s' % asn1Spec.__class__.__name__) - if __chosenSpec is not None and ( - tagSet == __chosenSpec.getTagSet() or \ - tagSet in __chosenSpec.getTagMap() - ): - # use base type for codec lookup to recover untagged types - baseTagSet = __chosenSpec.baseTagSet - if __chosenSpec.typeId is not None and \ - __chosenSpec.typeId in self.__typeMap: - # ambiguous type - concreteDecoder = self.__typeMap[__chosenSpec.typeId] - debug.logger and debug.logger & debug.flagDecoder and debug.logger('value decoder chosen for an ambiguous type by type ID %s' % (__chosenSpec.typeId,)) - elif baseTagSet in self.__tagMap: - # base type or tagged subtype - concreteDecoder = self.__tagMap[baseTagSet] - debug.logger and debug.logger & debug.flagDecoder and debug.logger('value decoder chosen by base %r' % (baseTagSet,)) + if tagSet == asn1Spec.tagSet or tagSet in asn1Spec.tagMap: + chosenSpec = asn1Spec + debug.logger and debug.logger & debug.flagDecoder and debug.logger( + 'candidate ASN.1 spec is %s' % asn1Spec.__class__.__name__) else: - concreteDecoder = None + chosenSpec = None + + if chosenSpec is not None: + try: + # ambiguous type or just faster codec lookup + concreteDecoder = self.__typeMap[chosenSpec.typeId] + debug.logger and debug.logger & debug.flagDecoder and debug.logger( + 'value decoder chosen for an ambiguous type by type ID %s' % (chosenSpec.typeId,)) + except KeyError: + # use base type for codec lookup to recover untagged types + baseTagSet = tag.TagSet(chosenSpec.tagSet.baseTag, chosenSpec.tagSet.baseTag) + try: + # base type or tagged subtype + concreteDecoder = self.__tagMap[baseTagSet] + debug.logger and debug.logger & debug.flagDecoder and debug.logger( + 'value decoder chosen by base %s' % (baseTagSet,)) + except KeyError: + concreteDecoder = None if concreteDecoder: - asn1Spec = __chosenSpec + asn1Spec = chosenSpec state = stDecodeValue else: state = stTryAsExplicitTag - elif tagSet == self.__endOfOctetsTagSet: - concreteDecoder = self.__tagMap[tagSet] - state = stDecodeValue - debug.logger and debug.logger & debug.flagDecoder and debug.logger('end-of-octets found') else: concreteDecoder = None state = stTryAsExplicitTag if debug.logger and debug.logger & debug.flagDecoder: debug.logger('codec %s chosen by ASN.1 spec, decoding %s' % (state == stDecodeValue and concreteDecoder.__class__.__name__ or "", state == stDecodeValue and 'value' or 'as explicit tag')) - debug.scope.push(__chosenSpec is None and '?' or __chosenSpec.__class__.__name__) + debug.scope.push(chosenSpec is None and '?' or chosenSpec.__class__.__name__) if state == stTryAsExplicitTag: - if tagSet and \ - tagSet[0][1] == tag.tagFormatConstructed and \ - tagSet[0][0] != tag.tagClassUniversal: + if tagSet and tagSet[0].tagFormat == tag.tagFormatConstructed and tagSet[0].tagClass != tag.tagClassUniversal: # Assume explicit tagging concreteDecoder = explicitTagDecoder state = stDecodeValue - else: + else: concreteDecoder = None state = self.defaultErrorState debug.logger and debug.logger & debug.flagDecoder and debug.logger('codec %s chosen, decoding %s' % (concreteDecoder and concreteDecoder.__class__.__name__ or "", state == stDecodeValue and 'value' or 'as failure')) if state == stDumpRawValue: concreteDecoder = self.defaultRawDecoder - debug.logger and debug.logger & debug.flagDecoder and debug.logger('codec %s chosen, decoding value' % concreteDecoder.__class__.__name__) + debug.logger and debug.logger & debug.flagDecoder and debug.logger( + 'codec %s chosen, decoding value' % concreteDecoder.__class__.__name__) state = stDecodeValue if state == stDecodeValue: - if recursiveFlag == 0 and not substrateFun: # legacy - substrateFun = lambda a,b,c: (a,b[:c]) + if not recursiveFlag and not substrateFun: # legacy + def substrateFun(a, b, c): + return a, b[:c] if length == -1: # indef length value, substrate = concreteDecoder.indefLenValueDecoder( fullSubstrate, substrate, asn1Spec, tagSet, length, stGetValueDecoder, self, substrateFun - ) + ) else: value, substrate = concreteDecoder.valueDecoder( fullSubstrate, substrate, asn1Spec, tagSet, length, stGetValueDecoder, self, substrateFun - ) + ) state = stStop - debug.logger and debug.logger & debug.flagDecoder and debug.logger('codec %s yields type %s, value:\n%s\n...remaining substrate is: %s' % (concreteDecoder.__class__.__name__, value.__class__.__name__, value.prettyPrint(), substrate and debug.hexdump(substrate) or '')) + debug.logger and debug.logger & debug.flagDecoder and debug.logger( + 'codec %s yields type %s, value:\n%s\n...remaining substrate is: %s' % (concreteDecoder.__class__.__name__, value.__class__.__name__, value.prettyPrint(), substrate and debug.hexdump(substrate) or '')) if state == stErrorCondition: raise error.PyAsn1Error( - '%r not in asn1Spec: %r' % (tagSet, asn1Spec) - ) + '%s not in asn1Spec: %s' % (tagSet, asn1Spec) + ) if debug.logger and debug.logger & debug.flagDecoder: debug.scope.pop() debug.logger('decoder left scope %s, call completed' % debug.scope) return value, substrate - + + +#: Turns BER octet stream into an ASN.1 object. +#: +#: Takes BER octetstream and decode it into an ASN.1 object +#: (e.g. :py:class:`~pyasn1.type.base.PyAsn1Item` derivative) which +#: may be a scalar or an arbitrary nested structure. +#: +#: Parameters +#: ---------- +#: substrate: :py:class:`bytes` (Python 3) or :py:class:`str` (Python 2) +#: BER octetstream +#: +#: asn1Spec: any pyasn1 type object e.g. :py:class:`~pyasn1.type.base.PyAsn1Item` derivative +#: A pyasn1 type object to act as a template guiding the decoder. Depending on the ASN.1 structure +#: being decoded, *asn1Spec* may or may not be required. Most common reason for +#: it to require is that ASN.1 structure is encoded in *IMPLICIT* tagging mode. +#: +#: Returns +#: ------- +#: : :py:class:`tuple` +#: A tuple of pyasn1 object recovered from BER substrate (:py:class:`~pyasn1.type.base.PyAsn1Item` derivative) +#: and the unprocessed trailing portion of the *substrate* (may be empty) +#: +#: Raises +#: ------ +#: : :py:class:`pyasn1.error.PyAsn1Error` +#: On decoding errors decode = Decoder(tagMap, typeMap) # XXX diff --git a/src/lib/pyasn1/codec/ber/encoder.py b/src/lib/pyasn1/codec/ber/encoder.py index 173949d0..2bf2bc7f 100644 --- a/src/lib/pyasn1/codec/ber/encoder.py +++ b/src/lib/pyasn1/codec/ber/encoder.py @@ -1,229 +1,319 @@ -# BER encoder +# +# This file is part of pyasn1 software. +# +# Copyright (c) 2005-2017, Ilya Etingof +# License: http://pyasn1.sf.net/license.html +# from pyasn1.type import base, tag, univ, char, useful from pyasn1.codec.ber import eoo from pyasn1.compat.octets import int2oct, oct2int, ints2octs, null, str2octs +from pyasn1.compat.integer import to_bytes from pyasn1 import debug, error -class Error(Exception): pass +__all__ = ['encode'] -class AbstractItemEncoder: + +class AbstractItemEncoder(object): supportIndefLenMode = 1 - def encodeTag(self, t, isConstructed): - tagClass, tagFormat, tagId = t.asTuple() # this is a hotspot - v = tagClass | tagFormat + + # noinspection PyMethodMayBeStatic + def encodeTag(self, singleTag, isConstructed): + tagClass, tagFormat, tagId = singleTag + encodedTag = tagClass | tagFormat if isConstructed: - v = v|tag.tagFormatConstructed + encodedTag |= tag.tagFormatConstructed if tagId < 31: - return int2oct(v|tagId) + return (encodedTag | tagId,) else: - s = int2oct(tagId&0x7f) - tagId = tagId >> 7 + substrate = (tagId & 0x7f,) + tagId >>= 7 while tagId: - s = int2oct(0x80|(tagId&0x7f)) + s - tagId = tagId >> 7 - return int2oct(v|0x1F) + s + substrate = (0x80 | (tagId & 0x7f),) + substrate + tagId >>= 7 + return (encodedTag | 0x1F,) + substrate def encodeLength(self, length, defMode): if not defMode and self.supportIndefLenMode: - return int2oct(0x80) + return (0x80,) if length < 0x80: - return int2oct(length) + return (length,) else: - substrate = null + substrate = () while length: - substrate = int2oct(length&0xff) + substrate - length = length >> 8 + substrate = (length & 0xff,) + substrate + length >>= 8 substrateLen = len(substrate) if substrateLen > 126: - raise Error('Length octets overflow (%d)' % substrateLen) - return int2oct(0x80 | substrateLen) + substrate + raise error.PyAsn1Error('Length octets overflow (%d)' % substrateLen) + return (0x80 | substrateLen,) + substrate def encodeValue(self, encodeFun, value, defMode, maxChunkSize): - raise Error('Not implemented') + raise error.PyAsn1Error('Not implemented') def _encodeEndOfOctets(self, encodeFun, defMode): if defMode or not self.supportIndefLenMode: return null else: return encodeFun(eoo.endOfOctets, defMode) - + def encode(self, encodeFun, value, defMode, maxChunkSize): - substrate, isConstructed = self.encodeValue( + substrate, isConstructed, isOctets = self.encodeValue( encodeFun, value, defMode, maxChunkSize - ) - tagSet = value.getTagSet() + ) + tagSet = value.tagSet + # tagged value? if tagSet: if not isConstructed: # primitive form implies definite mode - defMode = 1 - return self.encodeTag( - tagSet[-1], isConstructed - ) + self.encodeLength( - len(substrate), defMode - ) + substrate + self._encodeEndOfOctets(encodeFun, defMode) - else: - return substrate # untagged value + defMode = True + header = self.encodeTag(tagSet[-1], isConstructed) + header += self.encodeLength(len(substrate), defMode) + + if isOctets: + substrate = ints2octs(header) + substrate + else: + substrate = ints2octs(header + substrate) + + eoo = self._encodeEndOfOctets(encodeFun, defMode) + if eoo: + substrate += eoo + + return substrate + class EndOfOctetsEncoder(AbstractItemEncoder): def encodeValue(self, encodeFun, value, defMode, maxChunkSize): - return null, 0 + return null, False, True + class ExplicitlyTaggedItemEncoder(AbstractItemEncoder): def encodeValue(self, encodeFun, value, defMode, maxChunkSize): if isinstance(value, base.AbstractConstructedAsn1Item): - value = value.clone(tagSet=value.getTagSet()[:-1], - cloneValueFlag=1) + value = value.clone(tagSet=value.tagSet[:-1], cloneValueFlag=1) else: - value = value.clone(tagSet=value.getTagSet()[:-1]) - return encodeFun(value, defMode, maxChunkSize), 1 + value = value.clone(tagSet=value.tagSet[:-1]) + return encodeFun(value, defMode, maxChunkSize), True, True + explicitlyTaggedItemEncoder = ExplicitlyTaggedItemEncoder() + class BooleanEncoder(AbstractItemEncoder): - supportIndefLenMode = 0 - _true = ints2octs((1,)) - _false = ints2octs((0,)) + supportIndefLenMode = False + def encodeValue(self, encodeFun, value, defMode, maxChunkSize): - return value and self._true or self._false, 0 + return value and (1,) or (0,), False, False + class IntegerEncoder(AbstractItemEncoder): - supportIndefLenMode = 0 + supportIndefLenMode = False supportCompactZero = False + def encodeValue(self, encodeFun, value, defMode, maxChunkSize): - if value == 0: # shortcut for zero value + if value == 0: + # de-facto way to encode zero if self.supportCompactZero: - # this seems to be a correct way for encoding zeros - return null, 0 + return (), False, False else: - # this seems to be a widespread way for encoding zeros - return ints2octs((0,)), 0 - octets = [] - value = int(value) # to save on ops on asn1 type - while 1: - octets.insert(0, value & 0xff) - if value == 0 or value == -1: - break - value = value >> 8 - if value == 0 and octets[0] & 0x80: - octets.insert(0, 0) - while len(octets) > 1 and \ - (octets[0] == 0 and octets[1] & 0x80 == 0 or \ - octets[0] == 0xff and octets[1] & 0x80 != 0): - del octets[0] - return ints2octs(octets), 0 + return (0,), False, False + + return to_bytes(int(value), signed=True), False, True + class BitStringEncoder(AbstractItemEncoder): def encodeValue(self, encodeFun, value, defMode, maxChunkSize): - if not maxChunkSize or len(value) <= maxChunkSize*8: - r = {}; l = len(value); p = 0; j = 7 - while p < l: - i, j = divmod(p, 8) - r[i] = r.get(i,0) | value[p]<<(7-j) - p = p + 1 - keys = list(r); keys.sort() - return int2oct(7-j) + ints2octs([r[k] for k in keys]), 0 + valueLength = len(value) + if valueLength % 8: + alignedValue = value << (8 - valueLength % 8) else: - pos = 0; substrate = null - while 1: - # count in octets - v = value.clone(value[pos*8:pos*8+maxChunkSize*8]) - if not v: - break - substrate = substrate + encodeFun(v, defMode, maxChunkSize) - pos = pos + maxChunkSize - return substrate, 1 + alignedValue = value + + if not maxChunkSize or len(alignedValue) <= maxChunkSize * 8: + substrate = alignedValue.asOctets() + return int2oct(len(substrate) * 8 - valueLength) + substrate, False, True + + stop = 0 + substrate = null + while stop < valueLength: + start = stop + stop = min(start + maxChunkSize * 8, valueLength) + substrate += encodeFun(alignedValue[start:stop], defMode, maxChunkSize) + + return substrate, True, True + class OctetStringEncoder(AbstractItemEncoder): def encodeValue(self, encodeFun, value, defMode, maxChunkSize): if not maxChunkSize or len(value) <= maxChunkSize: - return value.asOctets(), 0 + return value.asOctets(), False, True else: - pos = 0; substrate = null - while 1: - v = value.clone(value[pos:pos+maxChunkSize]) + pos = 0 + substrate = null + while True: + v = value.clone(value[pos:pos + maxChunkSize]) if not v: break - substrate = substrate + encodeFun(v, defMode, maxChunkSize) - pos = pos + maxChunkSize - return substrate, 1 + substrate += encodeFun(v, defMode, maxChunkSize) + pos += maxChunkSize + + return substrate, True, True + class NullEncoder(AbstractItemEncoder): - supportIndefLenMode = 0 + supportIndefLenMode = False + def encodeValue(self, encodeFun, value, defMode, maxChunkSize): - return null, 0 + return null, False, True + class ObjectIdentifierEncoder(AbstractItemEncoder): - supportIndefLenMode = 0 - precomputedValues = { - (1, 3, 6, 1, 2): (43, 6, 1, 2), - (1, 3, 6, 1, 4): (43, 6, 1, 4) - } - def encodeValue(self, encodeFun, value, defMode, maxChunkSize): + supportIndefLenMode = False + + def encodeValue(self, encodeFun, value, defMode, maxChunkSize): oid = value.asTuple() - if oid[:5] in self.precomputedValues: - octets = self.precomputedValues[oid[:5]] - index = 5 - else: - if len(oid) < 2: - raise error.PyAsn1Error('Short OID %s' % (value,)) - # Build the first twos - if oid[0] > 6 or oid[1] > 39 or oid[0] == 6 and oid[1] > 15: - raise error.PyAsn1Error( - 'Initial sub-ID overflow %s in OID %s' % (oid[:2], value) - ) - octets = (oid[0] * 40 + oid[1],) - index = 2 + # Build the first pair + try: + first = oid[0] + second = oid[1] - # Cycle through subids - for subid in oid[index:]: - if subid > -1 and subid < 128: - # Optimize for the common case - octets = octets + (subid & 0x7f,) - elif subid < 0 or subid > 0xFFFFFFFF: - raise error.PyAsn1Error( - 'SubId overflow %s in %s' % (subid, value) - ) + except IndexError: + raise error.PyAsn1Error('Short OID %s' % (value,)) + + if 0 <= second <= 39: + if first == 1: + oid = (second + 40,) + oid[2:] + elif first == 0: + oid = (second,) + oid[2:] + elif first == 2: + oid = (second + 80,) + oid[2:] else: + raise error.PyAsn1Error('Impossible first/second arcs at %s' % (value,)) + elif first == 2: + oid = (second + 80,) + oid[2:] + else: + raise error.PyAsn1Error('Impossible first/second arcs at %s' % (value,)) + + octets = () + + # Cycle through subIds + for subOid in oid: + if 0 <= subOid <= 127: + # Optimize for the common case + octets += (subOid,) + elif subOid > 127: # Pack large Sub-Object IDs - res = (subid & 0x7f,) - subid = subid >> 7 - while subid > 0: - res = (0x80 | (subid & 0x7f),) + res - subid = subid >> 7 + res = (subOid & 0x7f,) + subOid >>= 7 + while subOid: + res = (0x80 | (subOid & 0x7f),) + res + subOid >>= 7 # Add packed Sub-Object ID to resulted Object ID octets += res - - return ints2octs(octets), 0 + else: + raise error.PyAsn1Error('Negative OID arc %s at %s' % (subOid, value)) + + return octets, False, False + class RealEncoder(AbstractItemEncoder): supportIndefLenMode = 0 + binEncBase = 2 # set to None to choose encoding base automatically + + @staticmethod + def _dropFloatingPoint(m, encbase, e): + ms, es = 1, 1 + if m < 0: + ms = -1 # mantissa sign + if e < 0: + es = -1 # exponenta sign + m *= ms + if encbase == 8: + m *= 2 ** (abs(e) % 3 * es) + e = abs(e) // 3 * es + elif encbase == 16: + m *= 2 ** (abs(e) % 4 * es) + e = abs(e) // 4 * es + + while True: + if int(m) != m: + m *= encbase + e -= 1 + continue + break + return ms, int(m), encbase, e + + def _chooseEncBase(self, value): + m, b, e = value + encBase = [2, 8, 16] + if value.binEncBase in encBase: + return self._dropFloatingPoint(m, value.binEncBase, e) + elif self.binEncBase in encBase: + return self._dropFloatingPoint(m, self.binEncBase, e) + # auto choosing base 2/8/16 + mantissa = [m, m, m] + exponenta = [e, e, e] + sign = 1 + encbase = 2 + e = float('inf') + for i in range(3): + (sign, + mantissa[i], + encBase[i], + exponenta[i]) = self._dropFloatingPoint(mantissa[i], encBase[i], exponenta[i]) + if abs(exponenta[i]) < abs(e) or (abs(exponenta[i]) == abs(e) and mantissa[i] < m): + e = exponenta[i] + m = int(mantissa[i]) + encbase = encBase[i] + return sign, m, encbase, e + def encodeValue(self, encodeFun, value, defMode, maxChunkSize): if value.isPlusInfinity(): - return int2oct(0x40), 0 + return (0x40,), False, False if value.isMinusInfinity(): - return int2oct(0x41), 0 + return (0x41,), False, False m, b, e = value if not m: - return null, 0 + return null, False, True if b == 10: - return str2octs('\x03%dE%s%d' % (m, e == 0 and '+' or '', e)), 0 + return str2octs('\x03%dE%s%d' % (m, e == 0 and '+' or '', e)), False, True elif b == 2: - fo = 0x80 # binary enoding - if m < 0: - fo = fo | 0x40 # sign bit - m = -m - while int(m) != m: # drop floating point - m *= 2 - e -= 1 - while m & 0x1 == 0: # mantissa normalization + fo = 0x80 # binary encoding + ms, m, encbase, e = self._chooseEncBase(value) + if ms < 0: # mantissa sign + fo |= 0x40 # sign bit + # exponenta & mantissa normalization + if encbase == 2: + while m & 0x1 == 0: + m >>= 1 + e += 1 + elif encbase == 8: + while m & 0x7 == 0: + m >>= 3 + e += 1 + fo |= 0x10 + else: # encbase = 16 + while m & 0xf == 0: + m >>= 4 + e += 1 + fo |= 0x20 + sf = 0 # scale factor + while m & 0x1 == 0: m >>= 1 - e += 1 + sf += 1 + if sf > 3: + raise error.PyAsn1Error('Scale factor overflow') # bug if raised + fo |= sf << 2 eo = null - while e not in (0, -1): - eo = int2oct(e&0xff) + eo - e >>= 8 - if e == 0 and eo and oct2int(eo[0]) & 0x80: - eo = int2oct(0) + eo + if e == 0 or e == -1: + eo = int2oct(e & 0xff) + else: + while e not in (0, -1): + eo = int2oct(e & 0xff) + eo + e >>= 8 + if e == 0 and eo and oct2int(eo[0]) & 0x80: + eo = int2oct(0) + eo + if e == -1 and eo and not (oct2int(eo[0]) & 0x80): + eo = int2oct(0xff) + eo n = len(eo) if n > 0xff: raise error.PyAsn1Error('Real exponent overflow') @@ -235,51 +325,54 @@ class RealEncoder(AbstractItemEncoder): fo |= 2 else: fo |= 3 - eo = int2oct(n//0xff+1) + eo + eo = int2oct(n & 0xff) + eo po = null while m: - po = int2oct(m&0xff) + po + po = int2oct(m & 0xff) + po m >>= 8 substrate = int2oct(fo) + eo + po - return substrate, 0 + return substrate, False, True else: raise error.PyAsn1Error('Prohibited Real base %s' % b) + class SequenceEncoder(AbstractItemEncoder): def encodeValue(self, encodeFun, value, defMode, maxChunkSize): - value.setDefaultComponents() value.verifySizeSpec() - substrate = null; idx = len(value) + namedTypes = value.getComponentType() + substrate = null + idx = len(value) while idx > 0: - idx = idx - 1 - if value[idx] is None: # Optional component - continue - component = value.getDefaultComponentByPosition(idx) - if component is not None and component == value[idx]: - continue - substrate = encodeFun( - value[idx], defMode, maxChunkSize - ) + substrate - return substrate, 1 + idx -= 1 + if namedTypes: + if namedTypes[idx].isOptional and not value[idx].isValue: + continue + if namedTypes[idx].isDefaulted and value[idx] == namedTypes[idx].asn1Object: + continue + substrate = encodeFun(value[idx], defMode, maxChunkSize) + substrate + return substrate, True, True + class SequenceOfEncoder(AbstractItemEncoder): def encodeValue(self, encodeFun, value, defMode, maxChunkSize): value.verifySizeSpec() - substrate = null; idx = len(value) + substrate = null + idx = len(value) while idx > 0: - idx = idx - 1 - substrate = encodeFun( - value[idx], defMode, maxChunkSize - ) + substrate - return substrate, 1 + idx -= 1 + substrate = encodeFun(value[idx], defMode, maxChunkSize) + substrate + return substrate, True, True + class ChoiceEncoder(AbstractItemEncoder): def encodeValue(self, encodeFun, value, defMode, maxChunkSize): - return encodeFun(value.getComponent(), defMode, maxChunkSize), 1 + return encodeFun(value.getComponent(), defMode, maxChunkSize), True, True + class AnyEncoder(OctetStringEncoder): def encodeValue(self, encodeFun, value, defMode, maxChunkSize): - return value.asOctets(), defMode == 0 + return value.asOctets(), defMode == False, True + tagMap = { eoo.endOfOctets.tagSet: EndOfOctetsEncoder(), @@ -308,46 +401,106 @@ tagMap = { char.UniversalString.tagSet: OctetStringEncoder(), char.BMPString.tagSet: OctetStringEncoder(), # useful types + useful.ObjectDescriptor.tagSet: OctetStringEncoder(), useful.GeneralizedTime.tagSet: OctetStringEncoder(), - useful.UTCTime.tagSet: OctetStringEncoder() - } + useful.UTCTime.tagSet: OctetStringEncoder() +} -# Type-to-codec map for ambiguous ASN.1 types +# Put in ambiguous & non-ambiguous types for faster codec lookup typeMap = { + univ.Boolean.typeId: BooleanEncoder(), + univ.Integer.typeId: IntegerEncoder(), + univ.BitString.typeId: BitStringEncoder(), + univ.OctetString.typeId: OctetStringEncoder(), + univ.Null.typeId: NullEncoder(), + univ.ObjectIdentifier.typeId: ObjectIdentifierEncoder(), + univ.Enumerated.typeId: IntegerEncoder(), + univ.Real.typeId: RealEncoder(), + # Sequence & Set have same tags as SequenceOf & SetOf univ.Set.typeId: SequenceEncoder(), univ.SetOf.typeId: SequenceOfEncoder(), univ.Sequence.typeId: SequenceEncoder(), univ.SequenceOf.typeId: SequenceOfEncoder(), univ.Choice.typeId: ChoiceEncoder(), - univ.Any.typeId: AnyEncoder() - } + univ.Any.typeId: AnyEncoder(), + # character string types + char.UTF8String.typeId: OctetStringEncoder(), + char.NumericString.typeId: OctetStringEncoder(), + char.PrintableString.typeId: OctetStringEncoder(), + char.TeletexString.typeId: OctetStringEncoder(), + char.VideotexString.typeId: OctetStringEncoder(), + char.IA5String.typeId: OctetStringEncoder(), + char.GraphicString.typeId: OctetStringEncoder(), + char.VisibleString.typeId: OctetStringEncoder(), + char.GeneralString.typeId: OctetStringEncoder(), + char.UniversalString.typeId: OctetStringEncoder(), + char.BMPString.typeId: OctetStringEncoder(), + # useful types + useful.ObjectDescriptor.typeId: OctetStringEncoder(), + useful.GeneralizedTime.typeId: OctetStringEncoder(), + useful.UTCTime.typeId: OctetStringEncoder() +} -class Encoder: + +class Encoder(object): + supportIndefLength = True + + # noinspection PyDefaultArgument def __init__(self, tagMap, typeMap={}): self.__tagMap = tagMap self.__typeMap = typeMap - def __call__(self, value, defMode=1, maxChunkSize=0): - debug.logger & debug.flagEncoder and debug.logger('encoder called in %sdef mode, chunk size %s for type %s, value:\n%s' % (not defMode and 'in' or '', maxChunkSize, value.__class__.__name__, value.prettyPrint())) - tagSet = value.getTagSet() + def __call__(self, value, defMode=True, maxChunkSize=0): + if not defMode and not self.supportIndefLength: + raise error.PyAsn1Error('Indefinite length encoding not supported by this codec') + debug.logger & debug.flagEncoder and debug.logger( + 'encoder called in %sdef mode, chunk size %s for type %s, value:\n%s' % ( + not defMode and 'in' or '', maxChunkSize, value.prettyPrintType(), value.prettyPrint())) + tagSet = value.tagSet if len(tagSet) > 1: concreteEncoder = explicitlyTaggedItemEncoder else: - if value.typeId is not None and value.typeId in self.__typeMap: + try: concreteEncoder = self.__typeMap[value.typeId] - elif tagSet in self.__tagMap: - concreteEncoder = self.__tagMap[tagSet] - else: - tagSet = value.baseTagSet - if tagSet in self.__tagMap: - concreteEncoder = self.__tagMap[tagSet] - else: - raise Error('No encoder for %s' % (value,)) - debug.logger & debug.flagEncoder and debug.logger('using value codec %s chosen by %r' % (concreteEncoder.__class__.__name__, tagSet)) + except KeyError: + # use base type for codec lookup to recover untagged types + baseTagSet = tag.TagSet(value.tagSet.baseTag, value.tagSet.baseTag) + try: + concreteEncoder = self.__tagMap[baseTagSet] + except KeyError: + raise error.PyAsn1Error('No encoder for %s' % (value,)) + debug.logger & debug.flagEncoder and debug.logger( + 'using value codec %s chosen by %s' % (concreteEncoder.__class__.__name__, tagSet)) substrate = concreteEncoder.encode( self, value, defMode, maxChunkSize - ) - debug.logger & debug.flagEncoder and debug.logger('built %s octets of substrate: %s\nencoder completed' % (len(substrate), debug.hexdump(substrate))) + ) + debug.logger & debug.flagEncoder and debug.logger( + 'built %s octets of substrate: %s\nencoder completed' % (len(substrate), debug.hexdump(substrate))) return substrate +#: Turns ASN.1 object into BER octet stream. +#: +#: Takes any ASN.1 object (e.g. :py:class:`~pyasn1.type.base.PyAsn1Item` derivative) +#: walks all its components recursively and produces a BER octet stream. +#: +#: Parameters +#: ---------- +# value: any pyasn1 object (e.g. :py:class:`~pyasn1.type.base.PyAsn1Item` derivative) +#: A pyasn1 object to encode +#: +#: defMode: :py:class:`bool` +#: If `False`, produces indefinite length encoding +#: +#: maxChunkSize: :py:class:`int` +#: Maximum chunk size in chunked encoding mode (0 denotes unlimited chunk size) +#: +#: Returns +#: ------- +#: : :py:class:`bytes` (Python 3) or :py:class:`str` (Python 2) +#: Given ASN.1 object encoded into BER octetstream +#: +#: Raises +#: ------ +#: : :py:class:`pyasn1.error.PyAsn1Error` +#: On encoding errors encode = Encoder(tagMap, typeMap) diff --git a/src/lib/pyasn1/codec/ber/eoo.py b/src/lib/pyasn1/codec/ber/eoo.py index 379be199..b02f5cc4 100644 --- a/src/lib/pyasn1/codec/ber/eoo.py +++ b/src/lib/pyasn1/codec/ber/eoo.py @@ -1,8 +1,25 @@ +# +# This file is part of pyasn1 software. +# +# Copyright (c) 2005-2017, Ilya Etingof +# License: http://pyasn1.sf.net/license.html +# from pyasn1.type import base, tag + class EndOfOctets(base.AbstractSimpleAsn1Item): defaultValue = 0 tagSet = tag.initTagSet( tag.Tag(tag.tagClassUniversal, tag.tagFormatSimple, 0x00) - ) + ) + + _instance = None + + def __new__(cls, *args): + if cls._instance is None: + cls._instance = object.__new__(cls, *args) + + return cls._instance + + endOfOctets = EndOfOctets() diff --git a/src/lib/pyasn1/codec/cer/decoder.py b/src/lib/pyasn1/codec/cer/decoder.py index 9fd37c13..bf9cf4af 100644 --- a/src/lib/pyasn1/codec/cer/decoder.py +++ b/src/lib/pyasn1/codec/cer/decoder.py @@ -1,16 +1,25 @@ -# CER decoder +# +# This file is part of pyasn1 software. +# +# Copyright (c) 2005-2017, Ilya Etingof +# License: http://pyasn1.sf.net/license.html +# from pyasn1.type import univ from pyasn1.codec.ber import decoder from pyasn1.compat.octets import oct2int from pyasn1 import error +__all__ = ['decode'] + + class BooleanDecoder(decoder.AbstractSimpleDecoder): protoComponent = univ.Boolean(0) + def valueDecoder(self, fullSubstrate, substrate, asn1Spec, tagSet, length, state, decodeFun, substrateFun): head, tail = substrate[:length], substrate[length:] - if not head: - raise error.PyAsn1Error('Empty substrate') + if not head or length != 1: + raise error.PyAsn1Error('Not single-octet Boolean payload') byte = oct2int(head[0]) # CER/DER specifies encoding of TRUE as 0xFF and FALSE as 0x0, while # BER allows any non-zero value as TRUE; cf. sections 8.2.2. and 11.1 @@ -20,16 +29,59 @@ class BooleanDecoder(decoder.AbstractSimpleDecoder): elif byte == 0x00: value = 0 else: - raise error.PyAsn1Error('Boolean CER violation: %s' % byte) + raise error.PyAsn1Error('Unexpected Boolean payload: %s' % byte) return self._createComponent(asn1Spec, tagSet, value), tail +# TODO: prohibit non-canonical encoding +BitStringDecoder = decoder.BitStringDecoder +OctetStringDecoder = decoder.OctetStringDecoder +RealDecoder = decoder.RealDecoder + tagMap = decoder.tagMap.copy() -tagMap.update({ - univ.Boolean.tagSet: BooleanDecoder() - }) +tagMap.update( + {univ.Boolean.tagSet: BooleanDecoder(), + univ.BitString.tagSet: BitStringDecoder(), + univ.OctetString.tagSet: OctetStringDecoder(), + univ.Real.tagSet: RealDecoder()} +) -typeMap = decoder.typeMap +typeMap = decoder.typeMap.copy() -class Decoder(decoder.Decoder): pass +# Put in non-ambiguous types for faster codec lookup +for typeDecoder in tagMap.values(): + typeId = typeDecoder.protoComponent.__class__.typeId + if typeId is not None and typeId not in typeMap: + typeMap[typeId] = typeDecoder + +class Decoder(decoder.Decoder): + pass + + +#: Turns CER octet stream into an ASN.1 object. +#: +#: Takes CER octetstream and decode it into an ASN.1 object +#: (e.g. :py:class:`~pyasn1.type.base.PyAsn1Item` derivative) which +#: may be a scalar or an arbitrary nested structure. +#: +#: Parameters +#: ---------- +#: substrate: :py:class:`bytes` (Python 3) or :py:class:`str` (Python 2) +#: CER octetstream +#: +#: asn1Spec: any pyasn1 type object e.g. :py:class:`~pyasn1.type.base.PyAsn1Item` derivative +#: A pyasn1 type object to act as a template guiding the decoder. Depending on the ASN.1 structure +#: being decoded, *asn1Spec* may or may not be required. Most common reason for +#: it to require is that ASN.1 structure is encoded in *IMPLICIT* tagging mode. +#: +#: Returns +#: ------- +#: : :py:class:`tuple` +#: A tuple of pyasn1 object recovered from CER substrate (:py:class:`~pyasn1.type.base.PyAsn1Item` derivative) +#: and the unprocessed trailing portion of the *substrate* (may be empty) +#: +#: Raises +#: ------ +#: : :py:class:`pyasn1.error.PyAsn1Error` +#: On decoding errors decode = Decoder(tagMap, decoder.typeMap) diff --git a/src/lib/pyasn1/codec/cer/encoder.py b/src/lib/pyasn1/codec/cer/encoder.py index 4c05130a..e241e43d 100644 --- a/src/lib/pyasn1/codec/cer/encoder.py +++ b/src/lib/pyasn1/codec/cer/encoder.py @@ -1,87 +1,179 @@ -# CER encoder +# +# This file is part of pyasn1 software. +# +# Copyright (c) 2005-2017, Ilya Etingof +# License: http://pyasn1.sf.net/license.html +# from pyasn1.type import univ +from pyasn1.type import useful from pyasn1.codec.ber import encoder -from pyasn1.compat.octets import int2oct, null +from pyasn1.compat.octets import int2oct, str2octs, null +from pyasn1 import error + +__all__ = ['encode'] + class BooleanEncoder(encoder.IntegerEncoder): def encodeValue(self, encodeFun, client, defMode, maxChunkSize): if client == 0: - substrate = int2oct(0) + substrate = (0,) else: - substrate = int2oct(255) - return substrate, 0 + substrate = (255,) + return substrate, False, False + class BitStringEncoder(encoder.BitStringEncoder): def encodeValue(self, encodeFun, client, defMode, maxChunkSize): return encoder.BitStringEncoder.encodeValue( self, encodeFun, client, defMode, 1000 - ) + ) + class OctetStringEncoder(encoder.OctetStringEncoder): def encodeValue(self, encodeFun, client, defMode, maxChunkSize): return encoder.OctetStringEncoder.encodeValue( self, encodeFun, client, defMode, 1000 - ) + ) + + +class RealEncoder(encoder.RealEncoder): + def _chooseEncBase(self, value): + m, b, e = value + return self._dropFloatingPoint(m, b, e) + -# specialized RealEncoder here # specialized GeneralStringEncoder here -# specialized GeneralizedTimeEncoder here -# specialized UTCTimeEncoder here + +class GeneralizedTimeEncoder(OctetStringEncoder): + zchar = str2octs('Z') + pluschar = str2octs('+') + minuschar = str2octs('-') + zero = str2octs('0') + + def encodeValue(self, encodeFun, client, defMode, maxChunkSize): + octets = client.asOctets() + # This breaks too many existing data items + # if '.' not in octets: + # raise error.PyAsn1Error('Format must include fraction of second: %r' % octets) + if len(octets) < 15: + raise error.PyAsn1Error('Bad UTC time length: %r' % octets) + if self.pluschar in octets or self.minuschar in octets: + raise error.PyAsn1Error('Must be UTC time: %r' % octets) + if octets[-1] != self.zchar[0]: + raise error.PyAsn1Error('Missing timezone specifier: %r' % octets) + return encoder.OctetStringEncoder.encodeValue( + self, encodeFun, client, defMode, 1000 + ) + + +class UTCTimeEncoder(encoder.OctetStringEncoder): + zchar = str2octs('Z') + pluschar = str2octs('+') + minuschar = str2octs('-') + + def encodeValue(self, encodeFun, client, defMode, maxChunkSize): + octets = client.asOctets() + if self.pluschar in octets or self.minuschar in octets: + raise error.PyAsn1Error('Must be UTC time: %r' % octets) + if octets and octets[-1] != self.zchar[0]: + client = client.clone(octets + self.zchar) + if len(client) != 13: + raise error.PyAsn1Error('Bad UTC time length: %r' % client) + return encoder.OctetStringEncoder.encodeValue( + self, encodeFun, client, defMode, 1000 + ) + class SetOfEncoder(encoder.SequenceOfEncoder): def encodeValue(self, encodeFun, client, defMode, maxChunkSize): - if isinstance(client, univ.SequenceAndSetBase): - client.setDefaultComponents() client.verifySizeSpec() - substrate = null; idx = len(client) + substrate = null + idx = len(client) # This is certainly a hack but how else do I distinguish SetOf # from Set if they have the same tags&constraints? if isinstance(client, univ.SequenceAndSetBase): # Set + namedTypes = client.getComponentType() comps = [] while idx > 0: - idx = idx - 1 - if client[idx] is None: # Optional component + idx -= 1 + if namedTypes[idx].isOptional and not client[idx].isValue: continue - if client.getDefaultComponentByPosition(idx) == client[idx]: + if namedTypes[idx].isDefaulted and client[idx] == namedTypes[idx].asn1Object: continue comps.append(client[idx]) - comps.sort(key=lambda x: isinstance(x, univ.Choice) and \ - x.getMinTagSet() or x.getTagSet()) + comps.sort(key=lambda x: isinstance(x, univ.Choice) and x.getMinTagSet() or x.tagSet) for c in comps: substrate += encodeFun(c, defMode, maxChunkSize) else: # SetOf compSubs = [] while idx > 0: - idx = idx - 1 + idx -= 1 compSubs.append( encodeFun(client[idx], defMode, maxChunkSize) - ) + ) compSubs.sort() # perhaps padding's not needed substrate = null for compSub in compSubs: substrate += compSub - return substrate, 1 + return substrate, True, True + tagMap = encoder.tagMap.copy() tagMap.update({ univ.Boolean.tagSet: BooleanEncoder(), univ.BitString.tagSet: BitStringEncoder(), univ.OctetString.tagSet: OctetStringEncoder(), + univ.Real.tagSet: RealEncoder(), + useful.GeneralizedTime.tagSet: GeneralizedTimeEncoder(), + useful.UTCTime.tagSet: UTCTimeEncoder(), univ.SetOf().tagSet: SetOfEncoder() # conflcts with Set - }) +}) typeMap = encoder.typeMap.copy() typeMap.update({ + univ.Boolean.typeId: BooleanEncoder(), + univ.BitString.typeId: BitStringEncoder(), + univ.OctetString.typeId: OctetStringEncoder(), + univ.Real.typeId: RealEncoder(), + useful.GeneralizedTime.typeId: GeneralizedTimeEncoder(), + useful.UTCTime.typeId: UTCTimeEncoder(), univ.Set.typeId: SetOfEncoder(), univ.SetOf.typeId: SetOfEncoder() - }) +}) + class Encoder(encoder.Encoder): - def __call__(self, client, defMode=0, maxChunkSize=0): + def __call__(self, client, defMode=False, maxChunkSize=0): return encoder.Encoder.__call__(self, client, defMode, maxChunkSize) + +#: Turns ASN.1 object into CER octet stream. +#: +#: Takes any ASN.1 object (e.g. :py:class:`~pyasn1.type.base.PyAsn1Item` derivative) +#: walks all its components recursively and produces a CER octet stream. +#: +#: Parameters +#: ---------- +# value: any pyasn1 object (e.g. :py:class:`~pyasn1.type.base.PyAsn1Item` derivative) +#: A pyasn1 object to encode +#: +#: defMode: :py:class:`bool` +#: If `False`, produces indefinite length encoding +#: +#: maxChunkSize: :py:class:`int` +#: Maximum chunk size in chunked encoding mode (0 denotes unlimited chunk size) +#: +#: Returns +#: ------- +#: : :py:class:`bytes` (Python 3) or :py:class:`str` (Python 2) +#: Given ASN.1 object encoded into BER octetstream +#: +#: Raises +#: ------ +#: : :py:class:`pyasn1.error.PyAsn1Error` +#: On encoding errors encode = Encoder(tagMap, typeMap) # EncoderFactory queries class instance and builds a map of tags -> encoders diff --git a/src/lib/pyasn1/codec/der/decoder.py b/src/lib/pyasn1/codec/der/decoder.py index 604abec2..24d3cbcb 100644 --- a/src/lib/pyasn1/codec/der/decoder.py +++ b/src/lib/pyasn1/codec/der/decoder.py @@ -1,9 +1,69 @@ -# DER decoder +# +# This file is part of pyasn1 software. +# +# Copyright (c) 2005-2017, Ilya Etingof +# License: http://pyasn1.sf.net/license.html +# from pyasn1.type import univ from pyasn1.codec.cer import decoder -tagMap = decoder.tagMap -typeMap = decoder.typeMap -Decoder = decoder.Decoder +__all__ = ['decode'] + +class BitStringDecoder(decoder.BitStringDecoder): + supportConstructedForm = False + + +class OctetStringDecoder(decoder.OctetStringDecoder): + supportConstructedForm = False + +# TODO: prohibit non-canonical encoding +RealDecoder = decoder.RealDecoder + +tagMap = decoder.tagMap.copy() +tagMap.update( + {univ.BitString.tagSet: BitStringDecoder(), + univ.OctetString.tagSet: OctetStringDecoder(), + univ.Real.tagSet: RealDecoder()} +) + +typeMap = decoder.typeMap.copy() + +# Put in non-ambiguous types for faster codec lookup +for typeDecoder in tagMap.values(): + typeId = typeDecoder.protoComponent.__class__.typeId + if typeId is not None and typeId not in typeMap: + typeMap[typeId] = typeDecoder + + +class Decoder(decoder.Decoder): + supportIndefLength = False + + +#: Turns DER octet stream into an ASN.1 object. +#: +#: Takes DER octetstream and decode it into an ASN.1 object +#: (e.g. :py:class:`~pyasn1.type.base.PyAsn1Item` derivative) which +#: may be a scalar or an arbitrary nested structure. +#: +#: Parameters +#: ---------- +#: substrate: :py:class:`bytes` (Python 3) or :py:class:`str` (Python 2) +#: DER octetstream +#: +#: asn1Spec: any pyasn1 type object e.g. :py:class:`~pyasn1.type.base.PyAsn1Item` derivative +#: A pyasn1 type object to act as a template guiding the decoder. Depending on the ASN.1 structure +#: being decoded, *asn1Spec* may or may not be required. Most common reason for +#: it to require is that ASN.1 structure is encoded in *IMPLICIT* tagging mode. +#: +#: Returns +#: ------- +#: : :py:class:`tuple` +#: A tuple of pyasn1 object recovered from DER substrate (:py:class:`~pyasn1.type.base.PyAsn1Item` derivative) +#: and the unprocessed trailing portion of the *substrate* (may be empty) +#: +#: Raises +#: ------ +#: : :py:class:`pyasn1.error.PyAsn1Error` +#: On decoding errors decode = Decoder(tagMap, typeMap) diff --git a/src/lib/pyasn1/codec/der/encoder.py b/src/lib/pyasn1/codec/der/encoder.py index 4e5faefa..2d615e3f 100644 --- a/src/lib/pyasn1/codec/der/encoder.py +++ b/src/lib/pyasn1/codec/der/encoder.py @@ -1,28 +1,67 @@ -# DER encoder +# +# This file is part of pyasn1 software. +# +# Copyright (c) 2005-2017, Ilya Etingof +# License: http://pyasn1.sf.net/license.html +# from pyasn1.type import univ from pyasn1.codec.cer import encoder +from pyasn1 import error + +__all__ = ['encode'] + class SetOfEncoder(encoder.SetOfEncoder): - def _cmpSetComponents(self, c1, c2): - tagSet1 = isinstance(c1, univ.Choice) and \ - c1.getEffectiveTagSet() or c1.getTagSet() - tagSet2 = isinstance(c2, univ.Choice) and \ - c2.getEffectiveTagSet() or c2.getTagSet() + @staticmethod + def _cmpSetComponents(c1, c2): + tagSet1 = isinstance(c1, univ.Choice) and c1.effectiveTagSet or c1.tagSet + tagSet2 = isinstance(c2, univ.Choice) and c2.effectiveTagSet or c2.tagSet return cmp(tagSet1, tagSet2) + tagMap = encoder.tagMap.copy() tagMap.update({ - # Overload CER encodrs with BER ones (a bit hackerish XXX) + # Overload CER encoders with BER ones (a bit hackerish XXX) univ.BitString.tagSet: encoder.encoder.BitStringEncoder(), univ.OctetString.tagSet: encoder.encoder.OctetStringEncoder(), # Set & SetOf have same tags univ.SetOf().tagSet: SetOfEncoder() - }) +}) + +typeMap = encoder.typeMap.copy() -typeMap = encoder.typeMap class Encoder(encoder.Encoder): - def __call__(self, client, defMode=1, maxChunkSize=0): + supportIndefLength = False + + def __call__(self, client, defMode=True, maxChunkSize=0): + if not defMode or maxChunkSize: + raise error.PyAsn1Error('DER forbids indefinite length mode') return encoder.Encoder.__call__(self, client, defMode, maxChunkSize) - + +#: Turns ASN.1 object into DER octet stream. +#: +#: Takes any ASN.1 object (e.g. :py:class:`~pyasn1.type.base.PyAsn1Item` derivative) +#: walks all its components recursively and produces a DER octet stream. +#: +#: Parameters +#: ---------- +# value: any pyasn1 object (e.g. :py:class:`~pyasn1.type.base.PyAsn1Item` derivative) +#: A pyasn1 object to encode +#: +#: defMode: :py:class:`bool` +#: If `False`, produces indefinite length encoding +#: +#: maxChunkSize: :py:class:`int` +#: Maximum chunk size in chunked encoding mode (0 denotes unlimited chunk size) +#: +#: Returns +#: ------- +#: : :py:class:`bytes` (Python 3) or :py:class:`str` (Python 2) +#: Given ASN.1 object encoded into BER octetstream +#: +#: Raises +#: ------ +#: : :py:class:`pyasn1.error.PyAsn1Error` +#: On encoding errors encode = Encoder(tagMap, typeMap) diff --git a/src/lib/pyasn1/codec/native/__init__.py b/src/lib/pyasn1/codec/native/__init__.py new file mode 100644 index 00000000..8c3066b2 --- /dev/null +++ b/src/lib/pyasn1/codec/native/__init__.py @@ -0,0 +1 @@ +# This file is necessary to make this directory a package. diff --git a/src/lib/pyasn1/codec/native/decoder.py b/src/lib/pyasn1/codec/native/decoder.py new file mode 100644 index 00000000..be75cb86 --- /dev/null +++ b/src/lib/pyasn1/codec/native/decoder.py @@ -0,0 +1,188 @@ +# +# This file is part of pyasn1 software. +# +# Copyright (c) 2005-2017, Ilya Etingof +# License: http://pyasn1.sf.net/license.html +# +from pyasn1.type import base, univ, char, useful, tag +from pyasn1 import debug, error + +__all__ = ['decode'] + + +class AbstractScalarDecoder(object): + def __call__(self, pyObject, asn1Spec, decoderFunc=None): + return asn1Spec.clone(pyObject) + + +class BitStringDecoder(AbstractScalarDecoder): + def __call__(self, pyObject, asn1Spec, decoderFunc=None): + return asn1Spec.clone(univ.BitString.fromBinaryString(pyObject)) + + +class SequenceOrSetDecoder(object): + def __call__(self, pyObject, asn1Spec, decoderFunc): + asn1Value = asn1Spec.clone() + + componentsTypes = asn1Spec.getComponentType() + + for field in asn1Value: + if field in pyObject: + asn1Value[field] = decoderFunc(pyObject[field], componentsTypes[field].asn1Object) + + return asn1Value + + +class SequenceOfOrSetOfDecoder(object): + def __call__(self, pyObject, asn1Spec, decoderFunc): + asn1Value = asn1Spec.clone() + + for pyValue in pyObject: + asn1Value.append(decoderFunc(pyValue, asn1Spec.getComponentType())) + + return asn1Value + + +class ChoiceDecoder(object): + def __call__(self, pyObject, asn1Spec, decoderFunc): + asn1Value = asn1Spec.clone() + + componentsTypes = asn1Spec.getComponentType() + + for field in pyObject: + if field in componentsTypes: + asn1Value[field] = decoderFunc(pyObject[field], componentsTypes[field].asn1Object) + break + + return asn1Value + + +tagMap = { + univ.Integer.tagSet: AbstractScalarDecoder(), + univ.Boolean.tagSet: AbstractScalarDecoder(), + univ.BitString.tagSet: BitStringDecoder(), + univ.OctetString.tagSet: AbstractScalarDecoder(), + univ.Null.tagSet: AbstractScalarDecoder(), + univ.ObjectIdentifier.tagSet: AbstractScalarDecoder(), + univ.Enumerated.tagSet: AbstractScalarDecoder(), + univ.Real.tagSet: AbstractScalarDecoder(), + univ.Sequence.tagSet: SequenceOrSetDecoder(), # conflicts with SequenceOf + univ.Set.tagSet: SequenceOrSetDecoder(), # conflicts with SetOf + univ.Choice.tagSet: ChoiceDecoder(), # conflicts with Any + # character string types + char.UTF8String.tagSet: AbstractScalarDecoder(), + char.NumericString.tagSet: AbstractScalarDecoder(), + char.PrintableString.tagSet: AbstractScalarDecoder(), + char.TeletexString.tagSet: AbstractScalarDecoder(), + char.VideotexString.tagSet: AbstractScalarDecoder(), + char.IA5String.tagSet: AbstractScalarDecoder(), + char.GraphicString.tagSet: AbstractScalarDecoder(), + char.VisibleString.tagSet: AbstractScalarDecoder(), + char.GeneralString.tagSet: AbstractScalarDecoder(), + char.UniversalString.tagSet: AbstractScalarDecoder(), + char.BMPString.tagSet: AbstractScalarDecoder(), + # useful types + useful.ObjectDescriptor.tagSet: AbstractScalarDecoder(), + useful.GeneralizedTime.tagSet: AbstractScalarDecoder(), + useful.UTCTime.tagSet: AbstractScalarDecoder() +} + +# Put in ambiguous & non-ambiguous types for faster codec lookup +typeMap = { + univ.Integer.typeId: AbstractScalarDecoder(), + univ.Boolean.typeId: AbstractScalarDecoder(), + univ.BitString.typeId: BitStringDecoder(), + univ.OctetString.typeId: AbstractScalarDecoder(), + univ.Null.typeId: AbstractScalarDecoder(), + univ.ObjectIdentifier.typeId: AbstractScalarDecoder(), + univ.Enumerated.typeId: AbstractScalarDecoder(), + univ.Real.typeId: AbstractScalarDecoder(), + # ambiguous base types + univ.Set.typeId: SequenceOrSetDecoder(), + univ.SetOf.typeId: SequenceOfOrSetOfDecoder(), + univ.Sequence.typeId: SequenceOrSetDecoder(), + univ.SequenceOf.typeId: SequenceOfOrSetOfDecoder(), + univ.Choice.typeId: ChoiceDecoder(), + univ.Any.typeId: AbstractScalarDecoder(), + # character string types + char.UTF8String.typeId: AbstractScalarDecoder(), + char.NumericString.typeId: AbstractScalarDecoder(), + char.PrintableString.typeId: AbstractScalarDecoder(), + char.TeletexString.typeId: AbstractScalarDecoder(), + char.VideotexString.typeId: AbstractScalarDecoder(), + char.IA5String.typeId: AbstractScalarDecoder(), + char.GraphicString.typeId: AbstractScalarDecoder(), + char.VisibleString.typeId: AbstractScalarDecoder(), + char.GeneralString.typeId: AbstractScalarDecoder(), + char.UniversalString.typeId: AbstractScalarDecoder(), + char.BMPString.typeId: AbstractScalarDecoder(), + # useful types + useful.ObjectDescriptor.typeId: AbstractScalarDecoder(), + useful.GeneralizedTime.typeId: AbstractScalarDecoder(), + useful.UTCTime.typeId: AbstractScalarDecoder() +} + + +class Decoder(object): + + # noinspection PyDefaultArgument + def __init__(self, tagMap, typeMap): + self.__tagMap = tagMap + self.__typeMap = typeMap + + def __call__(self, pyObject, asn1Spec): + if debug.logger & debug.flagDecoder: + debug.scope.push(type(pyObject).__name__) + debug.logger('decoder called at scope %s, working with type %s' % (debug.scope, type(pyObject).__name__)) + + if asn1Spec is None or not isinstance(asn1Spec, base.Asn1Item): + raise error.PyAsn1Error('asn1Spec is not valid (should be an instance of an ASN.1 Item, not %s)' % asn1Spec.__class__.__name__) + + try: + valueDecoder = self.__typeMap[asn1Spec.typeId] + except KeyError: + # use base type for codec lookup to recover untagged types + baseTagSet = tag.TagSet(asn1Spec.tagSet.baseTag, asn1Spec.tagSet.baseTag) + try: + valueDecoder = self.__tagMap[baseTagSet] + except KeyError: + raise error.PyAsn1Error('Unknown ASN.1 tag %s' % asn1Spec.tagSet) + + if debug.logger & debug.flagDecoder: + debug.logger('calling decoder %s on Python type %s <%s>' % (type(valueDecoder).__name__, type(pyObject).__name__, repr(pyObject))) + + value = valueDecoder(pyObject, asn1Spec, self) + + if debug.logger & debug.flagDecoder: + debug.logger('decoder %s produced ASN.1 type %s <%s>' % (type(valueDecoder).__name__, type(value).__name__, repr(value))) + debug.scope.pop() + + return value + + +#: Turns Python objects of built-in types into ASN.1 objects. +#: +#: Takes Python objects of built-in types and turns them into a tree of +#: ASN.1 objects (e.g. :py:class:`~pyasn1.type.base.PyAsn1Item` derivative) which +#: may be a scalar or an arbitrary nested structure. +#: +#: Parameters +#: ---------- +#: pyObject: :py:class:`object` +#: A scalar or nested Python objects +#: +#: asn1Spec: any pyasn1 type object e.g. :py:class:`~pyasn1.type.base.PyAsn1Item` derivative +#: A pyasn1 type object to act as a template guiding the decoder. It is required +#: for successful interpretation of Python objects mapping into their ASN.1 +#: representations. +#: +#: Returns +#: ------- +#: : :py:class:`~pyasn1.type.base.PyAsn1Item` derivative +#: A scalar or constructed pyasn1 object +#: +#: Raises +#: ------ +#: : :py:class:`pyasn1.error.PyAsn1Error` +#: On decoding errors +decode = Decoder(tagMap, typeMap) diff --git a/src/lib/pyasn1/codec/native/encoder.py b/src/lib/pyasn1/codec/native/encoder.py new file mode 100644 index 00000000..afeb8ae0 --- /dev/null +++ b/src/lib/pyasn1/codec/native/encoder.py @@ -0,0 +1,215 @@ +# +# This file is part of pyasn1 software. +# +# Copyright (c) 2005-2017, Ilya Etingof +# License: http://pyasn1.sf.net/license.html +# +try: + from collections import OrderedDict + +except ImportError: + OrderedDict = dict + +from pyasn1.type import base, univ, char, useful +from pyasn1 import debug, error + +__all__ = ['encode'] + + +class AbstractItemEncoder(object): + def encode(self, encodeFun, value): + raise error.PyAsn1Error('Not implemented') + + +class ExplicitlyTaggedItemEncoder(AbstractItemEncoder): + def encode(self, encodeFun, value): + if isinstance(value, base.AbstractConstructedAsn1Item): + value = value.clone(tagSet=value.tagSet[:-1], + cloneValueFlag=1) + else: + value = value.clone(tagSet=value.tagSet[:-1]) + return encodeFun(value) + +explicitlyTaggedItemEncoder = ExplicitlyTaggedItemEncoder() + + +class BooleanEncoder(AbstractItemEncoder): + def encode(self, encodeFun, value): + return bool(value) + + +class IntegerEncoder(AbstractItemEncoder): + def encode(self, encodeFun, value): + return int(value) + + +class BitStringEncoder(AbstractItemEncoder): + def encode(self, encodeFun, value): + return str(value) + + +class OctetStringEncoder(AbstractItemEncoder): + def encode(self, encodeFun, value): + return value.asOctets() + + +class TextStringEncoder(AbstractItemEncoder): + def encode(self, encodeFun, value): + return value.prettyPrint() + + +class NullEncoder(AbstractItemEncoder): + def encode(self, encodeFun, value): + return None + + +class ObjectIdentifierEncoder(AbstractItemEncoder): + def encode(self, encodeFun, value): + return str(value) + + +class RealEncoder(AbstractItemEncoder): + def encode(self, encodeFun, value): + return float(value) + + +class SetEncoder(AbstractItemEncoder): + protoDict = dict + def encode(self, encodeFun, value): + value.verifySizeSpec() + namedTypes = value.getComponentType() + substrate = self.protoDict() + for idx, (key, subValue) in enumerate(value.items()): + if namedTypes[idx].isOptional and not value[idx].isValue: + continue + substrate[key] = encodeFun(subValue) + return substrate + + +class SequenceEncoder(SetEncoder): + protoDict = OrderedDict + + +class SequenceOfEncoder(AbstractItemEncoder): + def encode(self, encodeFun, value): + value.verifySizeSpec() + return [encodeFun(x) for x in value] + + +class ChoiceEncoder(SequenceEncoder): + pass + + +class AnyEncoder(AbstractItemEncoder): + def encode(self, encodeFun, value): + return value.asOctets() + + +tagMap = { + univ.Boolean.tagSet: BooleanEncoder(), + univ.Integer.tagSet: IntegerEncoder(), + univ.BitString.tagSet: BitStringEncoder(), + univ.OctetString.tagSet: OctetStringEncoder(), + univ.Null.tagSet: NullEncoder(), + univ.ObjectIdentifier.tagSet: ObjectIdentifierEncoder(), + univ.Enumerated.tagSet: IntegerEncoder(), + univ.Real.tagSet: RealEncoder(), + # Sequence & Set have same tags as SequenceOf & SetOf + univ.SequenceOf.tagSet: SequenceOfEncoder(), + univ.SetOf.tagSet: SequenceOfEncoder(), + univ.Choice.tagSet: ChoiceEncoder(), + # character string types + char.UTF8String.tagSet: TextStringEncoder(), + char.NumericString.tagSet: TextStringEncoder(), + char.PrintableString.tagSet: TextStringEncoder(), + char.TeletexString.tagSet: TextStringEncoder(), + char.VideotexString.tagSet: TextStringEncoder(), + char.IA5String.tagSet: TextStringEncoder(), + char.GraphicString.tagSet: TextStringEncoder(), + char.VisibleString.tagSet: TextStringEncoder(), + char.GeneralString.tagSet: TextStringEncoder(), + char.UniversalString.tagSet: TextStringEncoder(), + char.BMPString.tagSet: TextStringEncoder(), + # useful types + useful.ObjectDescriptor.tagSet: OctetStringEncoder(), + useful.GeneralizedTime.tagSet: OctetStringEncoder(), + useful.UTCTime.tagSet: OctetStringEncoder() +} + +# Type-to-codec map for ambiguous ASN.1 types +typeMap = { + univ.Set.typeId: SetEncoder(), + univ.SetOf.typeId: SequenceOfEncoder(), + univ.Sequence.typeId: SequenceEncoder(), + univ.SequenceOf.typeId: SequenceOfEncoder(), + univ.Choice.typeId: ChoiceEncoder(), + univ.Any.typeId: AnyEncoder() +} + + +class Encoder(object): + + # noinspection PyDefaultArgument + def __init__(self, tagMap, typeMap={}): + self.__tagMap = tagMap + self.__typeMap = typeMap + + def __call__(self, asn1Value): + if not isinstance(asn1Value, base.Asn1Item): + raise error.PyAsn1Error('value is not valid (should be an instance of an ASN.1 Item)') + + if debug.logger & debug.flagEncoder: + debug.scope.push(type(asn1Value).__name__) + debug.logger('encoder called for type %s <%s>' % (type(asn1Value).__name__, asn1Value.prettyPrint())) + + tagSet = asn1Value.tagSet + if len(tagSet) > 1: + concreteEncoder = explicitlyTaggedItemEncoder + else: + if asn1Value.typeId is not None and asn1Value.typeId in self.__typeMap: + concreteEncoder = self.__typeMap[asn1Value.typeId] + elif tagSet in self.__tagMap: + concreteEncoder = self.__tagMap[tagSet] + else: + tagSet = asn1Value.baseTagSet + if tagSet in self.__tagMap: + concreteEncoder = self.__tagMap[tagSet] + else: + raise error.PyAsn1Error('No encoder for %s' % (asn1Value,)) + + debug.logger & debug.flagEncoder and debug.logger('using value codec %s chosen by %s' % (type(concreteEncoder).__name__, tagSet)) + + pyObject = concreteEncoder.encode(self, asn1Value) + + if debug.logger & debug.flagEncoder: + debug.logger('encoder %s produced: %s' % (type(concreteEncoder).__name__, repr(pyObject))) + debug.scope.pop() + + return pyObject + + +#: Turns ASN.1 object into a Python built-in type object(s). +#: +#: Takes any ASN.1 object (e.g. :py:class:`~pyasn1.type.base.PyAsn1Item` derivative) +#: walks all its components recursively and produces a Python built-in type or a tree +#: of those. +#: +#: One exception is that instead of :py:class:`dict`, the :py:class:`OrderedDict` +#: can be produced (whenever available) to preserve ordering of the components +#: in ASN.1 SEQUENCE. +#: +#: Parameters +#: ---------- +# asn1Value: any pyasn1 object (e.g. :py:class:`~pyasn1.type.base.PyAsn1Item` derivative) +#: pyasn1 object to encode (or a tree of them) +#: +#: Returns +#: ------- +#: : :py:class:`object` +#: Python built-in type instance (or a tree of them) +#: +#: Raises +#: ------ +#: : :py:class:`pyasn1.error.PyAsn1Error` +#: On encoding errors +encode = Encoder(tagMap, typeMap) diff --git a/src/lib/pyasn1/compat/binary.py b/src/lib/pyasn1/compat/binary.py new file mode 100644 index 00000000..65c42c74 --- /dev/null +++ b/src/lib/pyasn1/compat/binary.py @@ -0,0 +1,25 @@ +# +# This file is part of pyasn1 software. +# +# Copyright (c) 2005-2017, Ilya Etingof +# License: http://pyasn1.sf.net/license.html +# +from sys import version_info + +if version_info[0:2] < (2, 6): + def bin(value): + bitstring = [] + + while value: + if value & 1 == 1: + bitstring.append('1') + else: + bitstring.append('0') + + value >>= 1 + + bitstring.reverse() + + return '0b' + ''.join(bitstring) +else: + bin = bin diff --git a/src/lib/pyasn1/compat/integer.py b/src/lib/pyasn1/compat/integer.py new file mode 100644 index 00000000..ae9c7e1d --- /dev/null +++ b/src/lib/pyasn1/compat/integer.py @@ -0,0 +1,96 @@ +# +# This file is part of pyasn1 software. +# +# Copyright (c) 2005-2017, Ilya Etingof +# License: http://pyasn1.sf.net/license.html +# +import sys +if sys.version_info[0:2] < (3, 2): + from binascii import a2b_hex, b2a_hex +from pyasn1.compat.octets import oct2int, null + +if sys.version_info[0:2] < (3, 2): + def from_bytes(octets, signed=False): + value = long(b2a_hex(str(octets)), 16) + + if signed and oct2int(octets[0]) & 0x80: + return value - (1 << len(octets) * 8) + + return value + + def to_bytes(value, signed=False, length=0): + if value < 0: + if signed: + bits = bitLength(value) + + # two's complement form + maxValue = 1 << bits + valueToEncode = (value + maxValue) % maxValue + + else: + raise OverflowError('can\'t convert negative int to unsigned') + elif value == 0 and length == 0: + return null + else: + bits = 0 + valueToEncode = value + + hexValue = hex(valueToEncode)[2:] + if hexValue.endswith('L'): + hexValue = hexValue[:-1] + + if len(hexValue) & 1: + hexValue = '0' + hexValue + + # padding may be needed for two's complement encoding + if value != valueToEncode or length: + hexLength = len(hexValue) * 4 + + padLength = max(length, bits) + + if padLength > hexLength: + hexValue = '00' * ((padLength - hexLength - 1) // 8 + 1) + hexValue + elif length and hexLength - length > 7: + raise OverflowError('int too big to convert') + + firstOctet = int(hexValue[:2], 16) + + if signed: + if firstOctet & 0x80: + if value >= 0: + hexValue = '00' + hexValue + elif value < 0: + hexValue = 'ff' + hexValue + + octets_value = a2b_hex(hexValue) + + return octets_value + + def bitLength(number): + # bits in unsigned number + hexValue = hex(abs(number)) + bits = len(hexValue) - 2 + if hexValue.endswith('L'): + bits -= 1 + if bits & 1: + bits += 1 + bits *= 4 + # TODO: strip lhs zeros + return bits + +else: + + def from_bytes(octets, signed=False): + return int.from_bytes(bytes(octets), 'big', signed=signed) + + def to_bytes(value, signed=False, length=0): + length = max(value.bit_length(), length) + + if signed and length % 8 == 0: + length += 1 + + return value.to_bytes(length // 8 + (length % 8 and 1 or 0), 'big', signed=signed) + + def bitLength(number): + return int(number).bit_length() + diff --git a/src/lib/pyasn1/compat/octets.py b/src/lib/pyasn1/compat/octets.py index f7f2a29b..ec497a68 100644 --- a/src/lib/pyasn1/compat/octets.py +++ b/src/lib/pyasn1/compat/octets.py @@ -1,20 +1,46 @@ +# +# This file is part of pyasn1 software. +# +# Copyright (c) 2005-2017, Ilya Etingof +# License: http://pyasn1.sf.net/license.html +# from sys import version_info if version_info[0] <= 2: int2oct = chr - ints2octs = lambda s: ''.join([ int2oct(x) for x in s ]) + # noinspection PyPep8 + ints2octs = lambda s: ''.join([int2oct(x) for x in s]) null = '' oct2int = ord - octs2ints = lambda s: [ oct2int(x) for x in s ] + # noinspection PyPep8 + octs2ints = lambda s: [oct2int(x) for x in s] + # noinspection PyPep8 str2octs = lambda x: x + # noinspection PyPep8 octs2str = lambda x: x + # noinspection PyPep8 isOctetsType = lambda s: isinstance(s, str) + # noinspection PyPep8 + isStringType = lambda s: isinstance(s, (str, unicode)) + # noinspection PyPep8 + ensureString = str else: ints2octs = bytes + # noinspection PyPep8 int2oct = lambda x: ints2octs((x,)) null = ints2octs() + # noinspection PyPep8 oct2int = lambda x: x - octs2ints = lambda s: [ x for x in s ] - str2octs = lambda x: x.encode() - octs2str = lambda x: x.decode() + # noinspection PyPep8 + octs2ints = lambda x: x + # noinspection PyPep8 + str2octs = lambda x: x.encode('iso-8859-1') + # noinspection PyPep8 + octs2str = lambda x: x.decode('iso-8859-1') + # noinspection PyPep8 isOctetsType = lambda s: isinstance(s, bytes) + # noinspection PyPep8 + isStringType = lambda s: isinstance(s, str) + # noinspection PyPep8 + ensureString = bytes + diff --git a/src/lib/pyasn1/debug.py b/src/lib/pyasn1/debug.py index c27cb1d4..04a9da5c 100644 --- a/src/lib/pyasn1/debug.py +++ b/src/lib/pyasn1/debug.py @@ -1,36 +1,96 @@ -import sys +# +# This file is part of pyasn1 software. +# +# Copyright (c) 2005-2017, Ilya Etingof +# License: http://pyasn1.sf.net/license.html +# +import logging from pyasn1.compat.octets import octs2ints from pyasn1 import error from pyasn1 import __version__ -flagNone = 0x0000 -flagEncoder = 0x0001 -flagDecoder = 0x0002 -flagAll = 0xffff +__all__ = ['Debug', 'setLogger', 'hexdump'] + +flagNone = 0x0000 +flagEncoder = 0x0001 +flagDecoder = 0x0002 +flagAll = 0xffff flagMap = { 'encoder': flagEncoder, 'decoder': flagDecoder, 'all': flagAll - } +} -class Debug: - defaultPrinter = sys.stderr.write - def __init__(self, *flags): + +class Printer(object): + # noinspection PyShadowingNames + def __init__(self, logger=None, handler=None, formatter=None): + if logger is None: + logger = logging.getLogger('pyasn1') + logger.setLevel(logging.DEBUG) + if handler is None: + handler = logging.StreamHandler() + if formatter is None: + formatter = logging.Formatter('%(asctime)s %(name)s: %(message)s') + handler.setFormatter(formatter) + handler.setLevel(logging.DEBUG) + logger.addHandler(handler) + self.__logger = logger + + def __call__(self, msg): + self.__logger.debug(msg) + + def __str__(self): + return '' + + +if hasattr(logging, 'NullHandler'): + NullHandler = logging.NullHandler +else: + # Python 2.6 and older + class NullHandler(logging.Handler): + def emit(self, record): + pass + + +class Debug(object): + defaultPrinter = None + + def __init__(self, *flags, **options): self._flags = flagNone - self._printer = self.defaultPrinter + if options.get('printer') is not None: + self._printer = options.get('printer') + elif self.defaultPrinter is not None: + self._printer = self.defaultPrinter + if 'loggerName' in options: + # route our logs to parent logger + self._printer = Printer( + logger=logging.getLogger(options['loggerName']), + handler=NullHandler() + ) + else: + self._printer = Printer() self('running pyasn1 version %s' % __version__) for f in flags: - if f not in flagMap: - raise error.PyAsn1Error('bad debug flag %s' % (f,)) - self._flags = self._flags | flagMap[f] - self('debug category \'%s\' enabled' % f) - + inverse = f and f[0] in ('!', '~') + if inverse: + f = f[1:] + try: + if inverse: + self._flags &= ~flagMap[f] + else: + self._flags |= flagMap[f] + except KeyError: + raise error.PyAsn1Error('bad debug flag %s' % f) + + self('debug category \'%s\' %s' % (f, inverse and 'disabled' or 'enabled')) + def __str__(self): return 'logger %s, flags %x' % (self._printer, self._flags) - + def __call__(self, msg): - self._printer('DBG: %s\n' % msg) + self._printer(msg) def __and__(self, flag): return self._flags & flag @@ -38,19 +98,23 @@ class Debug: def __rand__(self, flag): return flag & self._flags + logger = 0 + def setLogger(l): global logger logger = l + def hexdump(octets): return ' '.join( - [ '%s%.2X' % (n%16 == 0 and ('\n%.5d: ' % n) or '', x) - for n,x in zip(range(len(octets)), octs2ints(octets)) ] - ) + ['%s%.2X' % (n % 16 == 0 and ('\n%.5d: ' % n) or '', x) + for n, x in zip(range(len(octets)), octs2ints(octets))] + ) -class Scope: + +class Scope(object): def __init__(self): self._list = [] @@ -62,4 +126,5 @@ class Scope: def pop(self): return self._list.pop() + scope = Scope() diff --git a/src/lib/pyasn1/error.py b/src/lib/pyasn1/error.py index 716406ff..85308557 100644 --- a/src/lib/pyasn1/error.py +++ b/src/lib/pyasn1/error.py @@ -1,3 +1,18 @@ -class PyAsn1Error(Exception): pass -class ValueConstraintError(PyAsn1Error): pass -class SubstrateUnderrunError(PyAsn1Error): pass +# +# This file is part of pyasn1 software. +# +# Copyright (c) 2005-2017, Ilya Etingof +# License: http://pyasn1.sf.net/license.html +# + + +class PyAsn1Error(Exception): + pass + + +class ValueConstraintError(PyAsn1Error): + pass + + +class SubstrateUnderrunError(PyAsn1Error): + pass diff --git a/src/lib/pyasn1/type/base.py b/src/lib/pyasn1/type/base.py index 40873719..00c329c2 100644 --- a/src/lib/pyasn1/type/base.py +++ b/src/lib/pyasn1/type/base.py @@ -1,134 +1,402 @@ -# Base classes for ASN.1 types +# +# This file is part of pyasn1 software. +# +# Copyright (c) 2005-2017, Ilya Etingof +# License: http://pyasn1.sf.net/license.html +# import sys -from pyasn1.type import constraint, tagmap +from pyasn1.type import constraint, tagmap, tag from pyasn1 import error -class Asn1Item: pass +__all__ = ['Asn1Item', 'Asn1ItemBase', 'AbstractSimpleAsn1Item', 'AbstractConstructedAsn1Item'] + + +class Asn1Item(object): + @classmethod + def getTypeId(cls, increment=1): + try: + Asn1Item._typeCounter += increment + except AttributeError: + Asn1Item._typeCounter = increment + return Asn1Item._typeCounter + class Asn1ItemBase(Asn1Item): - # Set of tags for this ASN.1 type - tagSet = () - - # A list of constraint.Constraint instances for checking values + #: Set or return a :py:class:`~pyasn1.type.tag.TagSet` object representing + #: ASN.1 tag(s) associated with |ASN.1| type. + tagSet = tag.TagSet() + + #: Default :py:class:`~pyasn1.type.constraint.ConstraintsIntersection` + #: object imposing constraints on initialization values. subtypeSpec = constraint.ConstraintsIntersection() - # Used for ambiguous ASN.1 types identification + # Disambiguation ASN.1 types identification typeId = None - + def __init__(self, tagSet=None, subtypeSpec=None): if tagSet is None: - self._tagSet = self.tagSet + self._tagSet = self.__class__.tagSet else: self._tagSet = tagSet if subtypeSpec is None: - self._subtypeSpec = self.subtypeSpec + self._subtypeSpec = self.__class__.subtypeSpec else: self._subtypeSpec = subtypeSpec - def _verifySubtypeSpec(self, value, idx=None): - try: - self._subtypeSpec(value, idx) - except error.PyAsn1Error: - c, i, t = sys.exc_info() - raise c('%s at %s' % (i, self.__class__.__name__)) - - def getSubtypeSpec(self): return self._subtypeSpec - - def getTagSet(self): return self._tagSet - def getEffectiveTagSet(self): return self._tagSet # used by untagged types - def getTagMap(self): return tagmap.TagMap({self._tagSet: self}) - - def isSameTypeWith(self, other): - return self is other or \ - self._tagSet == other.getTagSet() and \ - self._subtypeSpec == other.getSubtypeSpec() - def isSuperTypeOf(self, other): - """Returns true if argument is a ASN1 subtype of ourselves""" - return self._tagSet.isSuperTagSetOf(other.getTagSet()) and \ - self._subtypeSpec.isSuperTypeOf(other.getSubtypeSpec()) + @property + def effectiveTagSet(self): + """For |ASN.1| type is equivalent to *tagSet* + """ + return self._tagSet # used by untagged types + + @property + def tagMap(self): + """Return a :class:`~pyasn1.type.tagmap.TagMap` object mapping ASN.1 tags to ASN.1 objects within callee object. + """ + try: + return self._tagMap + + except AttributeError: + self._tagMap = tagmap.TagMap({self._tagSet: self}) + return self._tagMap + + def isSameTypeWith(self, other, matchTags=True, matchConstraints=True): + """Examine |ASN.1| type for equality with other ASN.1 type. + + ASN.1 tags (:py:mod:`~pyasn1.type.tag`) and constraints + (:py:mod:`~pyasn1.type.constraint`) are examined when carrying + out ASN.1 types comparison. + + No Python inheritance relationship between PyASN1 objects is considered. + + Parameters + ---------- + other: a pyasn1 type object + Class instance representing ASN.1 type. + + Returns + ------- + : :class:`bool` + :class:`True` if *other* is |ASN.1| type, + :class:`False` otherwise. + """ + return self is other or \ + (not matchTags or + self._tagSet == other.tagSet) and \ + (not matchConstraints or + self._subtypeSpec == other.subtypeSpec) + + def isSuperTypeOf(self, other, matchTags=True, matchConstraints=True): + """Examine |ASN.1| type for subtype relationship with other ASN.1 type. + + ASN.1 tags (:py:mod:`~pyasn1.type.tag`) and constraints + (:py:mod:`~pyasn1.type.constraint`) are examined when carrying + out ASN.1 types comparison. + + No Python inheritance relationship between PyASN1 objects is considered. + + + Parameters + ---------- + other: a pyasn1 type object + Class instance representing ASN.1 type. + + Returns + ------- + : :class:`bool` + :class:`True` if *other* is a subtype of |ASN.1| type, + :class:`False` otherwise. + """ + return (not matchTags or + self._tagSet.isSuperTagSetOf(other.tagSet)) and \ + (not matchConstraints or + (self._subtypeSpec.isSuperTypeOf(other.subtypeSpec))) + + @staticmethod + def isNoValue(*values): + for value in values: + if value is not None and value is not noValue: + return False + return True + + # backward compatibility + + def getTagSet(self): + return self.tagSet + + def getEffectiveTagSet(self): + return self.effectiveTagSet + + def getTagMap(self): + return self.tagMap + + def getSubtypeSpec(self): + return self.subtypeSpec + + +class NoValue(object): + """Create a singleton instance of NoValue class. + + NoValue object can be used as an initializer on PyASN1 type class + instantiation to represent ASN.1 type rather than ASN.1 data value. + + No operations other than type comparison can be performed on + a PyASN1 type object. + """ + skipMethods = ('__getattribute__', '__getattr__', '__setattr__', '__delattr__', + '__class__', '__init__', '__del__', '__new__', '__repr__', + '__qualname__', '__objclass__', 'im_class', '__sizeof__') + + _instance = None + + def __new__(cls): + if cls._instance is None: + def getPlug(name): + def plug(self, *args, **kw): + raise error.PyAsn1Error('Uninitialized ASN.1 value ("%s" attribute looked up)' % name) + return plug + + op_names = [name + for typ in (str, int, list, dict) + for name in dir(typ) + if name not in cls.skipMethods and name.startswith('__') and name.endswith('__') and callable(getattr(typ, name))] + + for name in set(op_names): + setattr(cls, name, getPlug(name)) + + cls._instance = object.__new__(cls) + + return cls._instance -class __NoValue: def __getattr__(self, attr): - raise error.PyAsn1Error('No value for %s()' % attr) - def __getitem__(self, i): - raise error.PyAsn1Error('No value') - -noValue = __NoValue() + if attr in self.skipMethods: + raise AttributeError('attribute %s not present' % attr) + raise error.PyAsn1Error('No value for "%s"' % attr) + + def __repr__(self): + return '%s()' % self.__class__.__name__ + +noValue = NoValue() + # Base class for "simple" ASN.1 objects. These are immutable. -class AbstractSimpleAsn1Item(Asn1ItemBase): +class AbstractSimpleAsn1Item(Asn1ItemBase): + #: Default payload value defaultValue = noValue - def __init__(self, value=None, tagSet=None, subtypeSpec=None): + + def __init__(self, value=noValue, tagSet=None, subtypeSpec=None): Asn1ItemBase.__init__(self, tagSet, subtypeSpec) if value is None or value is noValue: value = self.defaultValue - if value is None or value is noValue: - self.__hashedValue = value = noValue else: value = self.prettyIn(value) - self._verifySubtypeSpec(value) - self.__hashedValue = hash(value) + try: + self._subtypeSpec(value) + + except error.PyAsn1Error: + exType, exValue, exTb = sys.exc_info() + raise exType('%s at %s' % (exValue, self.__class__.__name__)) + + self.__hashedValue = None self._value = value self._len = None - + def __repr__(self): - if self._value is noValue: - return self.__class__.__name__ + '()' - else: - return self.__class__.__name__ + '(%s)' % (self.prettyOut(self._value),) - def __str__(self): return str(self._value) + representation = [] + if self._value is not self.defaultValue: + representation.append(self.prettyOut(self._value)) + if self._tagSet is not self.__class__.tagSet: + representation.append('tagSet=%r' % (self._tagSet,)) + if self._subtypeSpec is not self.subtypeSpec: + representation.append('subtypeSpec=%r' % (self._subtypeSpec,)) + return '%s(%s)' % (self.__class__.__name__, ', '.join(representation)) + + def __str__(self): + return str(self._value) + def __eq__(self, other): return self is other and True or self._value == other - def __ne__(self, other): return self._value != other - def __lt__(self, other): return self._value < other - def __le__(self, other): return self._value <= other - def __gt__(self, other): return self._value > other - def __ge__(self, other): return self._value >= other + + def __ne__(self, other): + return self._value != other + + def __lt__(self, other): + return self._value < other + + def __le__(self, other): + return self._value <= other + + def __gt__(self, other): + return self._value > other + + def __ge__(self, other): + return self._value >= other + if sys.version_info[0] <= 2: - def __nonzero__(self): return bool(self._value) + def __nonzero__(self): + return self._value and True or False else: - def __bool__(self): return bool(self._value) - def __hash__(self): return self.__hashedValue + def __bool__(self): + return self._value and True or False - def clone(self, value=None, tagSet=None, subtypeSpec=None): - if value is None and tagSet is None and subtypeSpec is None: + def __hash__(self): + if self.__hashedValue is None: + self.__hashedValue = hash(self._value) + return self.__hashedValue + + @property + def isValue(self): + """Indicate if |ASN.1| object represents ASN.1 type or ASN.1 value. + + The PyASN1 type objects can only participate in types comparison + and serve as a blueprint for serialization codecs to resolve + ambiguous types. + + The PyASN1 value objects can additionally participate in most + of built-in Python operations. + + Returns + ------- + : :class:`bool` + :class:`True` if object represents ASN.1 value and type, + :class:`False` if object represents just ASN.1 type. + + """ + return self._value is not noValue + + def clone(self, value=noValue, tagSet=None, subtypeSpec=None): + """Create a copy of a |ASN.1| type or object. + + Any parameters to the *clone()* method will replace corresponding + properties of the |ASN.1| object. + + Parameters + ---------- + value: :class:`tuple`, :class:`str` or |ASN.1| object + Initialization value to pass to new ASN.1 object instead of + inheriting one from the caller. + + tagSet: :py:class:`~pyasn1.type.tag.TagSet` + Object representing ASN.1 tag(s) to use in new object instead of inheriting from the caller + + subtypeSpec: :py:class:`~pyasn1.type.constraint.ConstraintsIntersection` + Object representing ASN.1 subtype constraint(s) to use in new object instead of inheriting from the caller + + Returns + ------- + : + new instance of |ASN.1| type/value + """ + isModified = False + + if value is None or value is noValue: + value = self._value + else: + isModified = True + if tagSet is None or tagSet is noValue: + tagSet = self._tagSet + else: + isModified = True + if subtypeSpec is None or subtypeSpec is noValue: + subtypeSpec = self._subtypeSpec + else: + isModified = True + + if isModified: + return self.__class__(value, tagSet, subtypeSpec) + else: return self - if value is None: - value = self._value - if tagSet is None: - tagSet = self._tagSet - if subtypeSpec is None: - subtypeSpec = self._subtypeSpec - return self.__class__(value, tagSet, subtypeSpec) - def subtype(self, value=None, implicitTag=None, explicitTag=None, + def subtype(self, value=noValue, implicitTag=None, explicitTag=None, subtypeSpec=None): - if value is None: + """Create a copy of a |ASN.1| type or object. + + Any parameters to the *subtype()* method will be added to the corresponding + properties of the |ASN.1| object. + + Parameters + ---------- + value: :class:`tuple`, :class:`str` or |ASN.1| object + Initialization value to pass to new ASN.1 object instead of + inheriting one from the caller. + + implicitTag: :py:class:`~pyasn1.type.tag.Tag` + Implicitly apply given ASN.1 tag object to caller's + :py:class:`~pyasn1.type.tag.TagSet`, then use the result as + new object's ASN.1 tag(s). + + explicitTag: :py:class:`~pyasn1.type.tag.Tag` + Explicitly apply given ASN.1 tag object to caller's + :py:class:`~pyasn1.type.tag.TagSet`, then use the result as + new object's ASN.1 tag(s). + + subtypeSpec: :py:class:`~pyasn1.type.constraint.ConstraintsIntersection` + Add ASN.1 constraints object to one of the caller, then + use the result as new object's ASN.1 constraints. + + Returns + ------- + : + new instance of |ASN.1| type/value + """ + isModified = False + + if value is None or value is noValue: value = self._value - if implicitTag is not None: + else: + isModified = True + if implicitTag is not None and implicitTag is not noValue: tagSet = self._tagSet.tagImplicitly(implicitTag) - elif explicitTag is not None: + isModified = True + elif explicitTag is not None and explicitTag is not noValue: tagSet = self._tagSet.tagExplicitly(explicitTag) + isModified = True else: tagSet = self._tagSet - if subtypeSpec is None: + if subtypeSpec is None or subtypeSpec is noValue: subtypeSpec = self._subtypeSpec else: - subtypeSpec = subtypeSpec + self._subtypeSpec - return self.__class__(value, tagSet, subtypeSpec) + subtypeSpec = self._subtypeSpec + subtypeSpec + isModified = True - def prettyIn(self, value): return value - def prettyOut(self, value): return str(value) + if isModified: + return self.__class__(value, tagSet, subtypeSpec) + else: + return self + + def prettyIn(self, value): + return value + + def prettyOut(self, value): + return str(value) def prettyPrint(self, scope=0): - if self._value is noValue: - return '' - else: + """Provide human-friendly printable object representation. + + Returns + ------- + : :class:`str` + human-friendly type and/or value representation. + """ + if self.isValue: return self.prettyOut(self._value) + else: + return '' # XXX Compatibility stub - def prettyPrinter(self, scope=0): return self.prettyPrint(scope) - + def prettyPrinter(self, scope=0): + return self.prettyPrint(scope) + + # noinspection PyUnusedLocal + def prettyPrintType(self, scope=0): + return '%s -> %s' % (self.tagSet, self.__class__.__name__) + + # backward compatibility + + def hasValue(self): + return self.isValue + + # # Constructed types: # * There are five of them: Sequence, SequenceOf/SetOf, Set and Choice @@ -148,9 +416,29 @@ class AbstractSimpleAsn1Item(Asn1ItemBase): # of types for Sequence/Set/Choice. # +def setupComponent(): + """Returns a sentinel value. + + Indicates to a constructed type to set up its inner component so that it + can be referred to. This is useful in situation when you want to populate + descendants of a constructed type what requires being able to refer to + their parent types along the way. + + Example + ------- + + >>> constructed['record'] = setupComponent() + >>> constructed['record']['scalar'] = 42 + """ + return noValue + + class AbstractConstructedAsn1Item(Asn1ItemBase): - componentType = None - sizeSpec = constraint.ConstraintsIntersection() + + #: If `True`, requires exact component type matching, + #: otherwise subtype relation is only enforced + strictConstraints = False + def __init__(self, componentType=None, tagSet=None, subtypeSpec=None, sizeSpec=None): Asn1ItemBase.__init__(self, tagSet, subtypeSpec) @@ -163,87 +451,167 @@ class AbstractConstructedAsn1Item(Asn1ItemBase): else: self._sizeSpec = sizeSpec self._componentValues = [] - self._componentValuesSet = 0 def __repr__(self): - r = self.__class__.__name__ + '()' - for idx in range(len(self._componentValues)): - if self._componentValues[idx] is None: - continue - r = r + '.setComponentByPosition(%s, %r)' % ( - idx, self._componentValues[idx] - ) - return r + representation = [] + if self._componentType is not self.componentType: + representation.append('componentType=%r' % (self._componentType,)) + if self._tagSet is not self.__class__.tagSet: + representation.append('tagSet=%r' % (self._tagSet,)) + if self._subtypeSpec is not self.subtypeSpec: + representation.append('subtypeSpec=%r' % (self._subtypeSpec,)) + representation = '%s(%s)' % (self.__class__.__name__, ', '.join(representation)) + if self._componentValues: + for idx, component in enumerate(self._componentValues): + if component is None or component is noValue: + continue + representation += '.setComponentByPosition(%d, %s)' % (idx, repr(component)) + return representation def __eq__(self, other): return self is other and True or self._componentValues == other - def __ne__(self, other): return self._componentValues != other - def __lt__(self, other): return self._componentValues < other - def __le__(self, other): return self._componentValues <= other - def __gt__(self, other): return self._componentValues > other - def __ge__(self, other): return self._componentValues >= other + + def __ne__(self, other): + return self._componentValues != other + + def __lt__(self, other): + return self._componentValues < other + + def __le__(self, other): + return self._componentValues <= other + + def __gt__(self, other): + return self._componentValues > other + + def __ge__(self, other): + return self._componentValues >= other + if sys.version_info[0] <= 2: - def __nonzero__(self): return bool(self._componentValues) + def __nonzero__(self): + return self._componentValues and True or False else: - def __bool__(self): return bool(self._componentValues) + def __bool__(self): + return self._componentValues and True or False - def getComponentTagMap(self): - raise error.PyAsn1Error('Method not implemented') + def _cloneComponentValues(self, myClone, cloneValueFlag): + pass - def _cloneComponentValues(self, myClone, cloneValueFlag): pass + def clone(self, tagSet=None, subtypeSpec=None, sizeSpec=None, cloneValueFlag=None): + """Create a copy of a |ASN.1| type or object. - def clone(self, tagSet=None, subtypeSpec=None, sizeSpec=None, - cloneValueFlag=None): + Any parameters to the *clone()* method will replace corresponding + properties of the |ASN.1| object. + + Parameters + ---------- + tagSet: :py:class:`~pyasn1.type.tag.TagSet` + Object representing non-default ASN.1 tag(s) + + subtypeSpec: :py:class:`~pyasn1.type.constraint.ConstraintsIntersection` + Object representing non-default ASN.1 subtype constraint(s) + + sizeSpec: :py:class:`~pyasn1.type.constraint.ConstraintsIntersection` + Object representing non-default ASN.1 size constraint(s) + + Returns + ------- + : + new instance of |ASN.1| type/value + + """ if tagSet is None: tagSet = self._tagSet if subtypeSpec is None: subtypeSpec = self._subtypeSpec if sizeSpec is None: sizeSpec = self._sizeSpec - r = self.__class__(self._componentType, tagSet, subtypeSpec, sizeSpec) + clone = self.__class__(self._componentType, tagSet, subtypeSpec, sizeSpec) if cloneValueFlag: - self._cloneComponentValues(r, cloneValueFlag) - return r + self._cloneComponentValues(clone, cloneValueFlag) + return clone def subtype(self, implicitTag=None, explicitTag=None, subtypeSpec=None, sizeSpec=None, cloneValueFlag=None): - if implicitTag is not None: + """Create a copy of a |ASN.1| type or object. + + Any parameters to the *subtype()* method will be added to the corresponding + properties of the |ASN.1| object. + + Parameters + ---------- + tagSet: :py:class:`~pyasn1.type.tag.TagSet` + Object representing non-default ASN.1 tag(s) + + subtypeSpec: :py:class:`~pyasn1.type.constraint.ConstraintsIntersection` + Object representing non-default ASN.1 subtype constraint(s) + + sizeSpec: :py:class:`~pyasn1.type.constraint.ConstraintsIntersection` + Object representing non-default ASN.1 size constraint(s) + + Returns + ------- + : + new instance of |ASN.1| type/value + + """ + if implicitTag is not None and implicitTag is not noValue: tagSet = self._tagSet.tagImplicitly(implicitTag) - elif explicitTag is not None: + elif explicitTag is not None and explicitTag is not noValue: tagSet = self._tagSet.tagExplicitly(explicitTag) else: tagSet = self._tagSet - if subtypeSpec is None: + if subtypeSpec is None or subtypeSpec is noValue: subtypeSpec = self._subtypeSpec else: - subtypeSpec = subtypeSpec + self._subtypeSpec - if sizeSpec is None: + subtypeSpec = self._subtypeSpec + subtypeSpec + if sizeSpec is None or sizeSpec is noValue: sizeSpec = self._sizeSpec else: - sizeSpec = sizeSpec + self._sizeSpec - r = self.__class__(self._componentType, tagSet, subtypeSpec, sizeSpec) + sizeSpec += self._sizeSpec + clone = self.__class__(self._componentType, tagSet, subtypeSpec, sizeSpec) if cloneValueFlag: - self._cloneComponentValues(r, cloneValueFlag) - return r + self._cloneComponentValues(clone, cloneValueFlag) + return clone - def _verifyComponent(self, idx, value): pass - - def verifySizeSpec(self): self._sizeSpec(self) + def verifySizeSpec(self): + self._sizeSpec(self) def getComponentByPosition(self, idx): raise error.PyAsn1Error('Method not implemented') + def setComponentByPosition(self, idx, value, verifyConstraints=True): raise error.PyAsn1Error('Method not implemented') - def getComponentType(self): return self._componentType + def setComponents(self, *args, **kwargs): + for idx, value in enumerate(args): + self[idx] = value + for k in kwargs: + self[k] = kwargs[k] + return self - def __getitem__(self, idx): return self.getComponentByPosition(idx) - def __setitem__(self, idx, value): self.setComponentByPosition(idx, value) + def getComponentType(self): + return self._componentType + + # backward compatibility -- no-op + def setDefaultComponents(self): + pass + + @property + def componentTagMap(self): + raise error.PyAsn1Error('Method not implemented') + + def __getitem__(self, idx): + return self.getComponentByPosition(idx) + + def __setitem__(self, idx, value): + self.setComponentByPosition(idx, value) + + def __len__(self): + return len(self._componentValues) - def __len__(self): return len(self._componentValues) - def clear(self): self._componentValues = [] - self._componentValuesSet = 0 - def setDefaultComponents(self): pass + # backward compatibility + def getComponentTagMap(self): + return self.componentTagMap \ No newline at end of file diff --git a/src/lib/pyasn1/type/char.py b/src/lib/pyasn1/type/char.py index ae112f8b..039e5366 100644 --- a/src/lib/pyasn1/type/char.py +++ b/src/lib/pyasn1/type/char.py @@ -1,61 +1,378 @@ -# ASN.1 "character string" types +# +# This file is part of pyasn1 software. +# +# Copyright (c) 2005-2017, Ilya Etingof +# License: http://pyasn1.sf.net/license.html +# +import sys from pyasn1.type import univ, tag +from pyasn1 import error -class UTF8String(univ.OctetString): - tagSet = univ.OctetString.tagSet.tagImplicitly( - tag.Tag(tag.tagClassUniversal, tag.tagFormatSimple, 12) - ) - encoding = "utf-8" -class NumericString(univ.OctetString): - tagSet = univ.OctetString.tagSet.tagImplicitly( +__all__ = ['NumericString', 'PrintableString', 'TeletexString', 'T61String', 'VideotexString', + 'IA5String', 'GraphicString', 'VisibleString', 'ISO646String', + 'GeneralString', 'UniversalString', 'BMPString', 'UTF8String'] + +NoValue = univ.NoValue +noValue = univ.noValue + + +class AbstractCharacterString(univ.OctetString): + """Creates |ASN.1| type or 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. + + Parameters + ---------- + 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 serialized + unicode string (note `encoding` parameter) or |ASN.1| class instance. + + tagSet: :py:class:`~pyasn1.type.tag.TagSet` + Object representing non-default ASN.1 tag(s) + + subtypeSpec: :py:class:`~pyasn1.type.constraint.ConstraintsIntersection` + Object representing non-default ASN.1 subtype constraint(s) + + encoding: :py:class:`str` + Unicode codec ID to encode/decode :class:`unicode` (Python 2) or + :class:`str` (Python 3) the payload when |ASN.1| object is used + in octet-stream context. + + Raises + ------ + : :py:class:`pyasn1.error.PyAsn1Error` + On constraint violation or bad initializer. + """ + + if sys.version_info[0] <= 2: + def __str__(self): + try: + return self._value.encode(self._encoding) + except UnicodeEncodeError: + raise error.PyAsn1Error( + 'Can\'t encode string \'%s\' with \'%s\' codec' % (self._value, self._encoding) + ) + + def __unicode__(self): + return unicode(self._value) + + def prettyIn(self, value): + if isinstance(value, unicode): + return value + elif isinstance(value, str): + try: + return value.decode(self._encoding) + except (LookupError, UnicodeDecodeError): + raise error.PyAsn1Error( + 'Can\'t decode string \'%s\' with \'%s\' codec' % (value, self._encoding) + ) + elif isinstance(value, (tuple, list)): + try: + return self.prettyIn(''.join([chr(x) for x in value])) + except ValueError: + raise error.PyAsn1Error( + 'Bad %s initializer \'%s\'' % (self.__class__.__name__, value) + ) + else: + try: + return unicode(value) + except UnicodeDecodeError: + raise error.PyAsn1Error( + 'Can\'t turn object \'%s\' into unicode' % (value,) + ) + + def asOctets(self, padding=True): + return str(self) + + def asNumbers(self, padding=True): + return tuple([ord(x) for x in str(self)]) + + else: + def __str__(self): + return str(self._value) + + def __bytes__(self): + try: + return self._value.encode(self._encoding) + except UnicodeEncodeError: + raise error.PyAsn1Error( + 'Can\'t encode string \'%s\' with \'%s\' codec' % (self._value, self._encoding) + ) + + def prettyIn(self, value): + if isinstance(value, str): + return value + elif isinstance(value, bytes): + try: + return value.decode(self._encoding) + except UnicodeDecodeError: + raise error.PyAsn1Error( + 'Can\'t decode string \'%s\' with \'%s\' codec' % (value, self._encoding) + ) + elif isinstance(value, (tuple, list)): + return self.prettyIn(bytes(value)) + else: + try: + return str(value) + except (UnicodeDecodeError, ValueError): + raise error.PyAsn1Error( + 'Can\'t turn object \'%s\' into unicode' % (value,) + ) + + def asOctets(self, padding=True): + return bytes(self) + + def asNumbers(self, padding=True): + return tuple(bytes(self)) + + def prettyOut(self, value): + return value + + def __reversed__(self): + return reversed(self._value) + + def clone(self, value=noValue, tagSet=None, subtypeSpec=None, + encoding=None, binValue=noValue, hexValue=noValue): + """Creates a copy of a |ASN.1| type or object. + + Any parameters to the *clone()* method will replace corresponding + properties of the |ASN.1| object. + + Parameters + ---------- + 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 serialized + unicode string (note `encoding` parameter) or |ASN.1| class instance. + + tagSet: :py:class:`~pyasn1.type.tag.TagSet` + Object representing non-default ASN.1 tag(s) + + subtypeSpec: :py:class:`~pyasn1.type.constraint.ConstraintsIntersection` + Object representing non-default ASN.1 subtype constraint(s) + + encoding: :py:class:`str` + Unicode codec ID to encode/decode :py:class:`unicode` (Python 2) or + :py:class:`str` (Python 3) the payload when |ASN.1| object is used + in octet-stream context. + + Returns + ------- + : + new instance of |ASN.1| type/value + + """ + return univ.OctetString.clone(self, value, tagSet, subtypeSpec, encoding, binValue, hexValue) + + def subtype(self, value=noValue, implicitTag=None, explicitTag=None, + subtypeSpec=None, encoding=None, binValue=noValue, hexValue=noValue): + """Creates a copy of a |ASN.1| type or object. + + Any parameters to the *subtype()* method will be added to the corresponding + properties of the |ASN.1| object. + + Parameters + ---------- + 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 serialized + unicode string (note `encoding` parameter) or |ASN.1| class instance. + + implicitTag: :py:class:`~pyasn1.type.tag.Tag` + Implicitly apply given ASN.1 tag object to caller's + :py:class:`~pyasn1.type.tag.TagSet`, then use the result as + new object's ASN.1 tag(s). + + explicitTag: :py:class:`~pyasn1.type.tag.Tag` + Explicitly apply given ASN.1 tag object to caller's + :py:class:`~pyasn1.type.tag.TagSet`, then use the result as + new object's ASN.1 tag(s). + + subtypeSpec: :py:class:`~pyasn1.type.constraint.ConstraintsIntersection` + Object representing non-default ASN.1 subtype constraint(s) + + encoding: :py:class:`str` + Unicode codec ID to encode/decode :py:class:`unicode` (Python 2) or + :py:class:`str` (Python 3) the payload when |ASN.1| object is used + in octet-stream context. + + Returns + ------- + : + new instance of |ASN.1| type/value + + """ + return univ.OctetString.subtype(self, value, implicitTag, explicitTag, subtypeSpec, encoding, binValue, hexValue) + + +class NumericString(AbstractCharacterString): + __doc__ = AbstractCharacterString.__doc__ + + #: Set (class attribute) or return (class or instance attribute) a + #: :py:class:`~pyasn1.type.tag.TagSet` object representing ASN.1 tag(s) + #: associated with |ASN.1| type. + tagSet = AbstractCharacterString.tagSet.tagImplicitly( tag.Tag(tag.tagClassUniversal, tag.tagFormatSimple, 18) - ) + ) + encoding = 'us-ascii' -class PrintableString(univ.OctetString): - tagSet = univ.OctetString.tagSet.tagImplicitly( + # Optimization for faster codec lookup + typeId = AbstractCharacterString.getTypeId() + + +class PrintableString(AbstractCharacterString): + __doc__ = AbstractCharacterString.__doc__ + + #: Set (class attribute) or return (class or instance attribute) a + #: :py:class:`~pyasn1.type.tag.TagSet` object representing ASN.1 tag(s) + #: associated with |ASN.1| type. + tagSet = AbstractCharacterString.tagSet.tagImplicitly( tag.Tag(tag.tagClassUniversal, tag.tagFormatSimple, 19) - ) + ) + encoding = 'us-ascii' -class TeletexString(univ.OctetString): - tagSet = univ.OctetString.tagSet.tagImplicitly( + # Optimization for faster codec lookup + typeId = AbstractCharacterString.getTypeId() + + +class TeletexString(AbstractCharacterString): + __doc__ = AbstractCharacterString.__doc__ + + #: Set (class attribute) or return (class or instance attribute) a + #: :py:class:`~pyasn1.type.tag.TagSet` object representing ASN.1 tag(s) + #: associated with |ASN.1| type. + tagSet = AbstractCharacterString.tagSet.tagImplicitly( tag.Tag(tag.tagClassUniversal, tag.tagFormatSimple, 20) - ) - + ) + encoding = 'iso-8859-1' -class VideotexString(univ.OctetString): - tagSet = univ.OctetString.tagSet.tagImplicitly( + +class T61String(TeletexString): + __doc__ = TeletexString.__doc__ + + +class VideotexString(AbstractCharacterString): + __doc__ = AbstractCharacterString.__doc__ + + #: Set (class attribute) or return (class or instance attribute) a + #: :py:class:`~pyasn1.type.tag.TagSet` object representing ASN.1 tag(s) + #: associated with |ASN.1| type. + tagSet = AbstractCharacterString.tagSet.tagImplicitly( tag.Tag(tag.tagClassUniversal, tag.tagFormatSimple, 21) - ) + ) + encoding = 'iso-8859-1' -class IA5String(univ.OctetString): - tagSet = univ.OctetString.tagSet.tagImplicitly( + # Optimization for faster codec lookup + typeId = AbstractCharacterString.getTypeId() + + +class IA5String(AbstractCharacterString): + __doc__ = AbstractCharacterString.__doc__ + + #: Set (class attribute) or return (class or instance attribute) a + #: :py:class:`~pyasn1.type.tag.TagSet` object representing ASN.1 tag(s) + #: associated with |ASN.1| type. + tagSet = AbstractCharacterString.tagSet.tagImplicitly( tag.Tag(tag.tagClassUniversal, tag.tagFormatSimple, 22) - ) + ) + encoding = 'us-ascii' -class GraphicString(univ.OctetString): - tagSet = univ.OctetString.tagSet.tagImplicitly( + # Optimization for faster codec lookup + typeId = AbstractCharacterString.getTypeId() + + +class GraphicString(AbstractCharacterString): + __doc__ = AbstractCharacterString.__doc__ + + #: Set (class attribute) or return (class or instance attribute) a + #: :py:class:`~pyasn1.type.tag.TagSet` object representing ASN.1 tag(s) + #: associated with |ASN.1| type. + tagSet = AbstractCharacterString.tagSet.tagImplicitly( tag.Tag(tag.tagClassUniversal, tag.tagFormatSimple, 25) - ) + ) + encoding = 'iso-8859-1' -class VisibleString(univ.OctetString): - tagSet = univ.OctetString.tagSet.tagImplicitly( + # Optimization for faster codec lookup + typeId = AbstractCharacterString.getTypeId() + + +class VisibleString(AbstractCharacterString): + __doc__ = AbstractCharacterString.__doc__ + + #: Set (class attribute) or return (class or instance attribute) a + #: :py:class:`~pyasn1.type.tag.TagSet` object representing ASN.1 tag(s) + #: associated with |ASN.1| type. + tagSet = AbstractCharacterString.tagSet.tagImplicitly( tag.Tag(tag.tagClassUniversal, tag.tagFormatSimple, 26) - ) + ) + encoding = 'us-ascii' -class GeneralString(univ.OctetString): - tagSet = univ.OctetString.tagSet.tagImplicitly( + # Optimization for faster codec lookup + typeId = AbstractCharacterString.getTypeId() + + +class ISO646String(VisibleString): + __doc__ = VisibleString.__doc__ + + +class GeneralString(AbstractCharacterString): + __doc__ = AbstractCharacterString.__doc__ + + #: Set (class attribute) or return (class or instance attribute) a + #: :py:class:`~pyasn1.type.tag.TagSet` object representing ASN.1 tag(s) + #: associated with |ASN.1| type. + tagSet = AbstractCharacterString.tagSet.tagImplicitly( tag.Tag(tag.tagClassUniversal, tag.tagFormatSimple, 27) - ) + ) + encoding = 'iso-8859-1' -class UniversalString(univ.OctetString): - tagSet = univ.OctetString.tagSet.tagImplicitly( + # Optimization for faster codec lookup + typeId = AbstractCharacterString.getTypeId() + + +class UniversalString(AbstractCharacterString): + __doc__ = AbstractCharacterString.__doc__ + + #: Set (class attribute) or return (class or instance attribute) a + #: :py:class:`~pyasn1.type.tag.TagSet` object representing ASN.1 tag(s) + #: associated with |ASN.1| type. + tagSet = AbstractCharacterString.tagSet.tagImplicitly( tag.Tag(tag.tagClassUniversal, tag.tagFormatSimple, 28) - ) + ) encoding = "utf-32-be" -class BMPString(univ.OctetString): - tagSet = univ.OctetString.tagSet.tagImplicitly( + # Optimization for faster codec lookup + typeId = AbstractCharacterString.getTypeId() + + +class BMPString(AbstractCharacterString): + __doc__ = AbstractCharacterString.__doc__ + + #: Set (class attribute) or return (class or instance attribute) a + #: :py:class:`~pyasn1.type.tag.TagSet` object representing ASN.1 tag(s) + #: associated with |ASN.1| type. + tagSet = AbstractCharacterString.tagSet.tagImplicitly( tag.Tag(tag.tagClassUniversal, tag.tagFormatSimple, 30) - ) + ) encoding = "utf-16-be" + + # Optimization for faster codec lookup + typeId = AbstractCharacterString.getTypeId() + + +class UTF8String(AbstractCharacterString): + __doc__ = AbstractCharacterString.__doc__ + + #: Set (class attribute) or return (class or instance attribute) a + #: :py:class:`~pyasn1.type.tag.TagSet` object representing ASN.1 tag(s) + #: associated with |ASN.1| type. + tagSet = AbstractCharacterString.tagSet.tagImplicitly( + tag.Tag(tag.tagClassUniversal, tag.tagFormatSimple, 12) + ) + encoding = "utf-8" + + # Optimization for faster codec lookup + typeId = AbstractCharacterString.getTypeId() diff --git a/src/lib/pyasn1/type/constraint.py b/src/lib/pyasn1/type/constraint.py index 66873937..7f96c507 100644 --- a/src/lib/pyasn1/type/constraint.py +++ b/src/lib/pyasn1/type/constraint.py @@ -1,86 +1,149 @@ # -# ASN.1 subtype constraints classes. +# This file is part of pyasn1 software. # -# Constraints are relatively rare, but every ASN1 object -# is doing checks all the time for whether they have any -# constraints and whether they are applicable to the object. +# Copyright (c) 2005-2017, Ilya Etingof +# License: http://pyasn1.sf.net/license.html # -# What we're going to do is define objects/functions that -# can be called unconditionally if they are present, and that -# are simply not present if there are no constraints. -# -# Original concept and code by Mike C. Fletcher. +# Original concept and code by Mike C. Fletcher. # import sys from pyasn1.type import error -class AbstractConstraint: +__all__ = ['SingleValueConstraint', 'ContainedSubtypeConstraint', 'ValueRangeConstraint', + 'ValueSizeConstraint', 'PermittedAlphabetConstraint', 'InnerTypeConstraint', + 'ConstraintsExclusion', 'ConstraintsIntersection', 'ConstraintsUnion'] + + +class AbstractConstraint(object): """Abstract base-class for constraint objects Constraints should be stored in a simple sequence in the - namespace of their client Asn1Item sub-classes. + namespace of their client Asn1Item sub-classes in cases + when ASN.1 constraint is define. """ + def __init__(self, *values): - self._valueMap = {} + self._valueMap = set() self._setValues(values) self.__hashedValues = None + def __call__(self, value, idx=None): + if not self._values: + return + try: self._testValue(value, idx) + except error.ValueConstraintError: raise error.ValueConstraintError( - '%s failed at: \"%s\"' % (self, sys.exc_info()[1]) + '%s failed at: %r' % (self, sys.exc_info()[1]) ) + def __repr__(self): return '%s(%s)' % ( self.__class__.__name__, ', '.join([repr(x) for x in self._values]) ) + def __eq__(self, other): return self is other and True or self._values == other - def __ne__(self, other): return self._values != other - def __lt__(self, other): return self._values < other - def __le__(self, other): return self._values <= other - def __gt__(self, other): return self._values > other - def __ge__(self, other): return self._values >= other + + def __ne__(self, other): + return self._values != other + + def __lt__(self, other): + return self._values < other + + def __le__(self, other): + return self._values <= other + + def __gt__(self, other): + return self._values > other + + def __ge__(self, other): + return self._values >= other + if sys.version_info[0] <= 2: - def __nonzero__(self): return bool(self._values) + def __nonzero__(self): + return self._values and True or False else: - def __bool__(self): return bool(self._values) + def __bool__(self): + return self._values and True or False def __hash__(self): if self.__hashedValues is None: self.__hashedValues = hash((self.__class__.__name__, self._values)) return self.__hashedValues - def _setValues(self, values): self._values = values + # descriptor protocol + + def __get__(self, instance, owner): + if instance is None: + return self + + # This is a bit of hack: look up instance attribute first, + # then try class attribute if instance attribute with that + # name is not available. + # The rationale is to have `.subtypeSpec`/`.sizeSpec` readable-writeable + # as a class attribute and read-only as instance attribute. + try: + return instance._subtypeSpec + + except AttributeError: + try: + return instance._sizeSpec + + except AttributeError: + return self + + def __set__(self, instance, value): + raise AttributeError('attribute is read-only') + + def _setValues(self, values): + self._values = values + def _testValue(self, value, idx): raise error.ValueConstraintError(value) # Constraints derivation logic - def getValueMap(self): return self._valueMap + def getValueMap(self): + return self._valueMap + def isSuperTypeOf(self, otherConstraint): - return self in otherConstraint.getValueMap() or \ - otherConstraint is self or otherConstraint == self + return (otherConstraint is self or + not self._values or + otherConstraint == self or + self in otherConstraint.getValueMap()) + def isSubTypeOf(self, otherConstraint): - return otherConstraint in self._valueMap or \ - otherConstraint is self or otherConstraint == self + return (otherConstraint is self or + not self or + otherConstraint == self or + otherConstraint in self._valueMap) class SingleValueConstraint(AbstractConstraint): """Value must be part of defined values constraint""" + + def _setValues(self, values): + self._values = values + self._set = set(values) + def _testValue(self, value, idx): - # XXX index vals for performance? - if value not in self._values: + if value not in self._set: raise error.ValueConstraintError(value) + class ContainedSubtypeConstraint(AbstractConstraint): """Value must satisfy all of defined set of constraints""" + def _testValue(self, value, idx): for c in self._values: c(value, idx) + class ValueRangeConstraint(AbstractConstraint): """Value must be within start and stop values (inclusive)""" + def _testValue(self, value, idx): if value < self.start or value > self.stop: raise error.ValueConstraintError(value) @@ -89,7 +152,7 @@ class ValueRangeConstraint(AbstractConstraint): if len(values) != 2: raise error.PyAsn1Error( '%s: bad constraint values' % (self.__class__.__name__,) - ) + ) self.start, self.stop = values if self.start > self.stop: raise error.PyAsn1Error( @@ -99,28 +162,31 @@ class ValueRangeConstraint(AbstractConstraint): ) ) AbstractConstraint._setValues(self, values) - + + class ValueSizeConstraint(ValueRangeConstraint): """len(value) must be within start and stop values (inclusive)""" + def _testValue(self, value, idx): - l = len(value) - if l < self.start or l > self.stop: + valueSize = len(value) + if valueSize < self.start or valueSize > self.stop: raise error.ValueConstraintError(value) + class PermittedAlphabetConstraint(SingleValueConstraint): def _setValues(self, values): - self._values = () - for v in values: - self._values = self._values + tuple(v) + self._values = values + self._set = set(values) def _testValue(self, value, idx): - for v in value: - if v not in self._values: - raise error.ValueConstraintError(value) + if not self._set.issuperset(value): + raise error.ValueConstraintError(value) -# This is a bit kludgy, meaning two op modes within a single constraing + +# This is a bit kludgy, meaning two op modes within a single constraint class InnerTypeConstraint(AbstractConstraint): """Value must satisfy type and presense constraints""" + def _testValue(self, value, idx): if self.__singleTypeConstraint: self.__singleTypeConstraint(value) @@ -128,7 +194,7 @@ class InnerTypeConstraint(AbstractConstraint): if idx not in self.__multipleTypeConstraint: raise error.ValueConstraintError(value) constraint, status = self.__multipleTypeConstraint[idx] - if status == 'ABSENT': # XXX presense is not checked! + if status == 'ABSENT': # XXX presense is not checked! raise error.ValueConstraintError(value) constraint(value) @@ -142,10 +208,12 @@ class InnerTypeConstraint(AbstractConstraint): self.__singleTypeConstraint = v AbstractConstraint._setValues(self, values) -# Boolean ops on constraints + +# Boolean ops on constraints class ConstraintsExclusion(AbstractConstraint): """Value must not fit the single constraint""" + def _testValue(self, value, idx): try: self._values[0](value, idx) @@ -159,42 +227,57 @@ class ConstraintsExclusion(AbstractConstraint): raise error.PyAsn1Error('Single constraint expected') AbstractConstraint._setValues(self, values) + class AbstractConstraintSet(AbstractConstraint): """Value must not satisfy the single constraint""" - def __getitem__(self, idx): return self._values[idx] - def __add__(self, value): return self.__class__(self, value) - def __radd__(self, value): return self.__class__(self, value) + def __getitem__(self, idx): + return self._values[idx] - def __len__(self): return len(self._values) + def __iter__(self): + return iter(self._values) + + def __add__(self, value): + return self.__class__(*(self._values + (value,))) + + def __radd__(self, value): + return self.__class__(*((value,) + self._values)) + + def __len__(self): + return len(self._values) # Constraints inclusion in sets - + def _setValues(self, values): self._values = values - for v in values: - self._valueMap[v] = 1 - self._valueMap.update(v.getValueMap()) + for constraint in values: + if constraint: + self._valueMap.add(constraint) + self._valueMap.update(constraint.getValueMap()) + class ConstraintsIntersection(AbstractConstraintSet): """Value must satisfy all constraints""" + def _testValue(self, value, idx): - for v in self._values: - v(value, idx) + for constraint in self._values: + constraint(value, idx) + class ConstraintsUnion(AbstractConstraintSet): """Value must satisfy at least one constraint""" + def _testValue(self, value, idx): - for v in self._values: + for constraint in self._values: try: - v(value, idx) + constraint(value, idx) except error.ValueConstraintError: pass else: return raise error.ValueConstraintError( 'all of %s failed for \"%s\"' % (self._values, value) - ) + ) # XXX # add tests for type check diff --git a/src/lib/pyasn1/type/error.py b/src/lib/pyasn1/type/error.py index 3e684844..cbfa276a 100644 --- a/src/lib/pyasn1/type/error.py +++ b/src/lib/pyasn1/type/error.py @@ -1,3 +1,11 @@ +# +# This file is part of pyasn1 software. +# +# Copyright (c) 2005-2017, Ilya Etingof +# License: http://pyasn1.sf.net/license.html +# from pyasn1.error import PyAsn1Error -class ValueConstraintError(PyAsn1Error): pass + +class ValueConstraintError(PyAsn1Error): + pass diff --git a/src/lib/pyasn1/type/namedtype.py b/src/lib/pyasn1/type/namedtype.py index 48967a5f..3f9ae190 100644 --- a/src/lib/pyasn1/type/namedtype.py +++ b/src/lib/pyasn1/type/namedtype.py @@ -1,132 +1,475 @@ -# NamedType specification for constructed types +# +# This file is part of pyasn1 software. +# +# Copyright (c) 2005-2017, Ilya Etingof +# License: http://pyasn1.sf.net/license.html +# import sys from pyasn1.type import tagmap from pyasn1 import error -class NamedType: - isOptional = 0 - isDefaulted = 0 - def __init__(self, name, t): - self.__name = name; self.__type = t - def __repr__(self): return '%s(%s, %s)' % ( - self.__class__.__name__, self.__name, self.__type - ) - def getType(self): return self.__type - def getName(self): return self.__name +__all__ = ['NamedType', 'OptionalNamedType', 'DefaultedNamedType', 'NamedTypes'] + + +class NamedType(object): + """Create named field object for a constructed ASN.1 type. + + The |NamedType| object represents a single name and ASN.1 type of a constructed ASN.1 type. + + |NamedType| objects are immutable and duck-type Python :class:`tuple` objects + holding *name* and *asn1Object* components. + + Parameters + ---------- + name: :py:class:`str` + Field name + + asn1Object: + ASN.1 type object + """ + isOptional = False + isDefaulted = False + + def __init__(self, name, asn1Object): + self.__name = name + self.__type = asn1Object + self.__nameAndType = name, asn1Object + + def __repr__(self): + return '%s(%r, %r)' % (self.__class__.__name__, self.__name, self.__type) + + def __eq__(self, other): + return self.__nameAndType == other + + def __ne__(self, other): + return self.__nameAndType != other + + def __lt__(self, other): + return self.__nameAndType < other + + def __le__(self, other): + return self.__nameAndType <= other + + def __gt__(self, other): + return self.__nameAndType > other + + def __ge__(self, other): + return self.__nameAndType >= other + + def __hash__(self): + return hash(self.__nameAndType) + def __getitem__(self, idx): - if idx == 0: return self.__name - if idx == 1: return self.__type - raise IndexError() + return self.__nameAndType[idx] + + def __iter__(self): + return iter(self.__nameAndType) + + @property + def name(self): + return self.__name + @property + def asn1Object(self): + return self.__type + + # Backward compatibility + + def getName(self): + return self.name + + def getType(self): + return self.asn1Object + + class OptionalNamedType(NamedType): - isOptional = 1 + __doc__ = NamedType.__doc__ + + isOptional = True + + class DefaultedNamedType(NamedType): - isDefaulted = 1 - -class NamedTypes: + __doc__ = NamedType.__doc__ + + isDefaulted = True + + +class NamedTypes(object): + """Create a collection of named fields for a constructed ASN.1 type. + + The NamedTypes object represents a collection of named fields of a constructed ASN.1 type. + + *NamedTypes* objects are immutable and duck-type Python :class:`dict` objects + holding *name* as keys and ASN.1 type object as values. + + Parameters + ---------- + *namedTypes: :class:`~pyasn1.type.namedtype.NamedType` + """ def __init__(self, *namedTypes): self.__namedTypes = namedTypes self.__namedTypesLen = len(self.__namedTypes) self.__minTagSet = None - self.__tagToPosIdx = {}; self.__nameToPosIdx = {} - self.__tagMap = { False: None, True: None } - self.__ambigiousTypes = {} + self.__tagToPosMapImpl = None + self.__nameToPosMapImpl = None + self.__ambigiousTypesImpl = None + self.__tagMap = {} + self.__hasOptionalOrDefault = None + self.__requiredComponents = None def __repr__(self): - r = '%s(' % self.__class__.__name__ - for n in self.__namedTypes: - r = r + '%r, ' % (n,) - return r + ')' - - def __getitem__(self, idx): return self.__namedTypes[idx] + return '%s(%s)' % ( + self.__class__.__name__, ', '.join([repr(x) for x in self.__namedTypes]) + ) + + def __eq__(self, other): + return self.__namedTypes == other + + def __ne__(self, other): + return self.__namedTypes != other + + def __lt__(self, other): + return self.__namedTypes < other + + def __le__(self, other): + return self.__namedTypes <= other + + def __gt__(self, other): + return self.__namedTypes > other + + def __ge__(self, other): + return self.__namedTypes >= other + + def __hash__(self): + return hash(self.__namedTypes) + + def __getitem__(self, idx): + try: + return self.__namedTypes[idx] + + except TypeError: + return self.__namedTypes[self.__nameToPosMap[idx]] + + def __contains__(self, key): + return key in self.__nameToPosMap + + def __iter__(self): + return (x[0] for x in self.__namedTypes) if sys.version_info[0] <= 2: - def __nonzero__(self): return bool(self.__namedTypesLen) + def __nonzero__(self): + return self.__namedTypesLen > 0 else: - def __bool__(self): return bool(self.__namedTypesLen) - def __len__(self): return self.__namedTypesLen - - def getTypeByPosition(self, idx): - if idx < 0 or idx >= self.__namedTypesLen: - raise error.PyAsn1Error('Type position out of range') - else: - return self.__namedTypes[idx].getType() + def __bool__(self): + return self.__namedTypesLen > 0 - def getPositionByType(self, tagSet): - if not self.__tagToPosIdx: - idx = self.__namedTypesLen - while idx > 0: - idx = idx - 1 - tagMap = self.__namedTypes[idx].getType().getTagMap() - for t in tagMap.getPosMap(): - if t in self.__tagToPosIdx: - raise error.PyAsn1Error('Duplicate type %s' % (t,)) - self.__tagToPosIdx[t] = idx + def __len__(self): + return self.__namedTypesLen + + # Python dict protocol + + def values(self): + return (namedType.asn1Object for namedType in self.__namedTypes) + + def keys(self): + return (namedType.name for namedType in self.__namedTypes) + + def items(self): + return ((namedType.name, namedType.asn1Object) for namedType in self.__namedTypes) + + def clone(self): + return self.__class__(*self.__namedTypes) + + @property + def __tagToPosMap(self): + if self.__tagToPosMapImpl is None: + self.__tagToPosMapImpl = {} + for idx, namedType in enumerate(self.__namedTypes): + tagMap = namedType.asn1Object.tagMap + if not tagMap: + continue + for _tagSet in tagMap.presentTypes: + if _tagSet in self.__tagToPosMapImpl: + raise error.PyAsn1Error('Duplicate type %s in %s' % (_tagSet, namedType)) + self.__tagToPosMapImpl[_tagSet] = idx + + return self.__tagToPosMapImpl + + @property + def __nameToPosMap(self): + if self.__nameToPosMapImpl is None: + self.__nameToPosMapImpl = {} + for idx, namedType in enumerate(self.__namedTypes): + if namedType.name in self.__nameToPosMapImpl: + raise error.PyAsn1Error('Duplicate name %s in %s' % (namedType.name, namedType)) + self.__nameToPosMapImpl[namedType.name] = idx + + return self.__nameToPosMapImpl + + @property + def __ambigiousTypes(self): + if self.__ambigiousTypesImpl is None: + self.__ambigiousTypesImpl = {} + ambigiousTypes = () + for idx, namedType in reversed(tuple(enumerate(self.__namedTypes))): + if namedType.isOptional or namedType.isDefaulted: + ambigiousTypes = (namedType,) + ambigiousTypes + else: + ambigiousTypes = (namedType,) + self.__ambigiousTypesImpl[idx] = NamedTypes(*ambigiousTypes) + return self.__ambigiousTypesImpl + + def getTypeByPosition(self, idx): + """Return ASN.1 type object by its position in fields set. + + Parameters + ---------- + idx: :py:class:`int` + Field index + + Returns + ------- + : + ASN.1 type + + Raises + ------ + : :class:`~pyasn1.error.PyAsn1Error` + If given position is out of fields range + """ try: - return self.__tagToPosIdx[tagSet] - except KeyError: - raise error.PyAsn1Error('Type %s not found' % (tagSet,)) - - def getNameByPosition(self, idx): - try: - return self.__namedTypes[idx].getName() + return self.__namedTypes[idx].asn1Object + except IndexError: raise error.PyAsn1Error('Type position out of range') - def getPositionByName(self, name): - if not self.__nameToPosIdx: - idx = self.__namedTypesLen - while idx > 0: - idx = idx - 1 - n = self.__namedTypes[idx].getName() - if n in self.__nameToPosIdx: - raise error.PyAsn1Error('Duplicate name %s' % (n,)) - self.__nameToPosIdx[n] = idx + + def getPositionByType(self, tagSet): + """Return field position by its ASN.1 type. + + Parameters + ---------- + tagSet: :class:`~pysnmp.type.tag.TagSet` + ASN.1 tag set distinguishing one ASN.1 type from others. + + Returns + ------- + : :py:class:`int` + ASN.1 type position in fields set + + Raises + ------ + : :class:`~pyasn1.error.PyAsn1Error` + If *tagSet* is not present or ASN.1 types are not unique within callee *NamedTypes* + """ try: - return self.__nameToPosIdx[name] + return self.__tagToPosMap[tagSet] + + except KeyError: + raise error.PyAsn1Error('Type %s not found' % (tagSet,)) + + def getNameByPosition(self, idx): + """Return field name by its position in fields set. + + Parameters + ---------- + idx: :py:class:`idx` + Field index + + Returns + ------- + : :py:class:`str` + Field name + + Raises + ------ + : :class:`~pyasn1.error.PyAsn1Error` + If given field name is not present in callee *NamedTypes* + """ + try: + return self.__namedTypes[idx].name + + except IndexError: + raise error.PyAsn1Error('Type position out of range') + + def getPositionByName(self, name): + """Return field position by filed name. + + Parameters + ---------- + name: :py:class:`str` + Field name + + Returns + ------- + : :py:class:`int` + Field position in fields set + + Raises + ------ + : :class:`~pyasn1.error.PyAsn1Error` + If *name* is not present or not unique within callee *NamedTypes* + """ + try: + return self.__nameToPosMap[name] + except KeyError: raise error.PyAsn1Error('Name %s not found' % (name,)) - def __buildAmbigiousTagMap(self): - ambigiousTypes = () - idx = self.__namedTypesLen - while idx > 0: - idx = idx - 1 - t = self.__namedTypes[idx] - if t.isOptional or t.isDefaulted: - ambigiousTypes = (t, ) + ambigiousTypes - else: - ambigiousTypes = (t, ) - self.__ambigiousTypes[idx] = NamedTypes(*ambigiousTypes) - def getTagMapNearPosition(self, idx): - if not self.__ambigiousTypes: self.__buildAmbigiousTagMap() + """Return ASN.1 types that are allowed at or past given field position. + + Some ASN.1 serialization allow for skipping optional and defaulted fields. + Some constructed ASN.1 types allow reordering of the fields. When recovering + such objects it may be important to know which types can possibly be + present at any given position in the field sets. + + Parameters + ---------- + idx: :py:class:`int` + Field index + + Returns + ------- + : :class:`~pyasn1.type.tagmap.TagMap` + Map if ASN.1 types allowed at given field position + + Raises + ------ + : :class:`~pyasn1.error.PyAsn1Error` + If given position is out of fields range + """ try: return self.__ambigiousTypes[idx].getTagMap() + except KeyError: raise error.PyAsn1Error('Type position out of range') def getPositionNearType(self, tagSet, idx): - if not self.__ambigiousTypes: self.__buildAmbigiousTagMap() + """Return the closest field position where given ASN.1 type is allowed. + + Some ASN.1 serialization allow for skipping optional and defaulted fields. + Some constructed ASN.1 types allow reordering of the fields. When recovering + such objects it may be important to know at which field position, in field set, + given *tagSet* is allowed at or past *idx* position. + + Parameters + ---------- + tagSet: :class:`~pyasn1.type.tag.TagSet` + ASN.1 type which field position to look up + + idx: :py:class:`int` + Field position at or past which to perform ASN.1 type look up + + Returns + ------- + : :py:class:`int` + Field position in fields set + + Raises + ------ + : :class:`~pyasn1.error.PyAsn1Error` + If *tagSet* is not present or not unique within callee *NamedTypes* + or *idx* is out of fields range + """ try: - return idx+self.__ambigiousTypes[idx].getPositionByType(tagSet) + return idx + self.__ambigiousTypes[idx].getPositionByType(tagSet) + except KeyError: raise error.PyAsn1Error('Type position out of range') - def genMinTagSet(self): + @property + def minTagSet(self): + """Return the minimal TagSet among ASN.1 type in callee *NamedTypes*. + + Some ASN.1 types/serialization protocols require ASN.1 types to be + arranged based on their numerical tag value. The *minTagSet* property + returns that. + + Returns + ------- + : :class:`~pyasn1.type.tagset.TagSet` + Minimal TagSet among ASN.1 types in callee *NamedTypes* + """ if self.__minTagSet is None: - for t in self.__namedTypes: - __type = t.getType() - tagSet = getattr(__type,'getMinTagSet',__type.getTagSet)() + for namedType in self.__namedTypes: + asn1Object = namedType.asn1Object + try: + tagSet = asn1Object.getMinTagSet() + + except AttributeError: + tagSet = asn1Object.tagSet if self.__minTagSet is None or tagSet < self.__minTagSet: self.__minTagSet = tagSet return self.__minTagSet - - def getTagMap(self, uniq=False): - if self.__tagMap[uniq] is None: - tagMap = tagmap.TagMap() - for nt in self.__namedTypes: - tagMap = tagMap.clone( - nt.getType(), nt.getType().getTagMap(), uniq - ) - self.__tagMap[uniq] = tagMap - return self.__tagMap[uniq] + + def getTagMap(self, unique=False): + """Create a *TagMap* object from tags and types recursively. + + Create a new :class:`~pyasn1.type.tagmap.TagMap` object by + combining tags from *TagMap* objects of children types and + associating them with their immediate child type. + + Example + ------- + + .. code-block:: python + + OuterType ::= CHOICE { + innerType INTEGER + } + + Calling *.getTagMap()* on *OuterType* will yield a map like this: + + .. code-block:: python + + Integer.tagSet -> Choice + + Parameters + ---------- + unique: :py:class:`bool` + If `True`, duplicate *TagSet* objects occurring while building + new *TagMap* would cause error. + + Returns + ------- + : :class:`~pyasn1.type.tagmap.TagMap` + New *TagMap* holding *TagSet* object gathered from childen types. + """ + if unique not in self.__tagMap: + presentTypes = {} + skipTypes = {} + defaultType = None + for namedType in self.__namedTypes: + tagMap = namedType.asn1Object.tagMap + for tagSet in tagMap: + if unique and tagSet in presentTypes: + raise error.PyAsn1Error('Non-unique tagSet %s' % (tagSet,)) + presentTypes[tagSet] = namedType.asn1Object + skipTypes.update(tagMap.skipTypes) + + if defaultType is None: + defaultType = tagMap.defaultType + elif tagMap.defaultType is not None: + raise error.PyAsn1Error('Duplicate default ASN.1 type at %s' % (self,)) + + self.__tagMap[unique] = tagmap.TagMap(presentTypes, skipTypes, defaultType) + + return self.__tagMap[unique] + + @property + def hasOptionalOrDefault(self): + if self.__hasOptionalOrDefault is None: + self.__hasOptionalOrDefault = bool([True for namedType in self.__namedTypes if namedType.isDefaulted or namedType.isOptional]) + return self.__hasOptionalOrDefault + + @property + def namedTypes(self): + return iter(self.__namedTypes) + + @property + def requiredComponents(self): + if self.__requiredComponents is None: + self.__requiredComponents = frozenset( + [idx for idx, nt in enumerate(self.__namedTypes) if not nt.isOptional and not nt.isDefaulted] + ) + return self.__requiredComponents diff --git a/src/lib/pyasn1/type/namedval.py b/src/lib/pyasn1/type/namedval.py index d0fea7cc..bcdbf153 100644 --- a/src/lib/pyasn1/type/namedval.py +++ b/src/lib/pyasn1/type/namedval.py @@ -1,12 +1,21 @@ +# +# This file is part of pyasn1 software. +# +# Copyright (c) 2005-2017, Ilya Etingof +# License: http://pyasn1.sf.net/license.html +# # ASN.1 named integers +# from pyasn1 import error -__all__ = [ 'NamedValues' ] +__all__ = ['NamedValues'] -class NamedValues: + +class NamedValues(object): def __init__(self, *namedValues): - self.nameToValIdx = {}; self.valToNameIdx = {} - self.namedValues = () + self.nameToValIdx = {} + self.valToNameIdx = {} + self.namedValues = () automaticVal = 1 for namedValue in namedValues: if isinstance(namedValue, tuple): @@ -21,9 +30,35 @@ class NamedValues: raise error.PyAsn1Error('Duplicate value %s=%s' % (name, val)) self.valToNameIdx[val] = name self.namedValues = self.namedValues + ((name, val),) - automaticVal = automaticVal + 1 - def __str__(self): return str(self.namedValues) - + automaticVal += 1 + + def __repr__(self): + return '%s(%s)' % (self.__class__.__name__, ', '.join([repr(x) for x in self.namedValues])) + + def __str__(self): + return str(self.namedValues) + + def __eq__(self, other): + return tuple(self) == tuple(other) + + def __ne__(self, other): + return tuple(self) != tuple(other) + + def __lt__(self, other): + return tuple(self) < tuple(other) + + def __le__(self, other): + return tuple(self) <= tuple(other) + + def __gt__(self, other): + return tuple(self) > tuple(other) + + def __ge__(self, other): + return tuple(self) >= tuple(other) + + def __hash__(self): + return hash(tuple(self)) + def getName(self, value): if value in self.valToNameIdx: return self.valToNameIdx[value] @@ -31,15 +66,28 @@ class NamedValues: def getValue(self, name): if name in self.nameToValIdx: return self.nameToValIdx[name] - - def __getitem__(self, i): return self.namedValues[i] - def __len__(self): return len(self.namedValues) + + def getValues(self, *names): + try: + return [self.nameToValIdx[name] for name in names] + + except KeyError: + raise error.PyAsn1Error( + 'Unknown bit identifier(s): %s' % (set(names).difference(self.nameToValIdx),) + ) + + def __getitem__(self, i): + return self.namedValues[i] + + def __len__(self): + return len(self.namedValues) def __add__(self, namedValues): return self.__class__(*self.namedValues + namedValues) + def __radd__(self, namedValues): return self.__class__(*namedValues + tuple(self)) - + def clone(self, *namedValues): return self.__class__(*tuple(self) + namedValues) diff --git a/src/lib/pyasn1/type/tag.py b/src/lib/pyasn1/type/tag.py index 1144907f..aaf18572 100644 --- a/src/lib/pyasn1/type/tag.py +++ b/src/lib/pyasn1/type/tag.py @@ -1,122 +1,342 @@ -# ASN.1 types tags -from operator import getitem +# +# This file is part of pyasn1 software. +# +# Copyright (c) 2005-2017, Ilya Etingof +# License: http://pyasn1.sf.net/license.html +# from pyasn1 import error +__all__ = ['tagClassUniversal', 'tagClassApplication', 'tagClassContext', + 'tagClassPrivate', 'tagFormatSimple', 'tagFormatConstructed', + 'tagCategoryImplicit', 'tagCategoryExplicit', 'tagCategoryUntagged', + 'Tag', 'TagSet'] + +#: Identifier for ASN.1 class UNIVERSAL tagClassUniversal = 0x00 + +#: Identifier for ASN.1 class APPLICATION tagClassApplication = 0x40 + +#: Identifier for ASN.1 class context-specific tagClassContext = 0x80 + +#: Identifier for ASN.1 class private tagClassPrivate = 0xC0 +#: Identifier for "simple" ASN.1 structure (e.g. scalar) tagFormatSimple = 0x00 + +#: Identifier for "constructed" ASN.1 structure (e.g. may have inner components) tagFormatConstructed = 0x20 tagCategoryImplicit = 0x01 tagCategoryExplicit = 0x02 tagCategoryUntagged = 0x04 -class Tag: + +class Tag(object): + """Create ASN.1 tag + + Represents ASN.1 tag that can be attached to a ASN.1 type to make + types distinguishable from each other. + + *Tag* objects are immutable and duck-type Python :class:`tuple` objects + holding three integer components of a tag. + + Parameters + ---------- + tagClass: :py:class:`int` + Tag *class* value + + tagFormat: :py:class:`int` + Tag *format* value + + tagId: :py:class:`int` + Tag ID value + """ def __init__(self, tagClass, tagFormat, tagId): if tagId < 0: - raise error.PyAsn1Error( - 'Negative tag ID (%s) not allowed' % (tagId,) - ) - self.__tag = (tagClass, tagFormat, tagId) - self.uniq = (tagClass, tagId) - self.__hashedUniqTag = hash(self.uniq) + raise error.PyAsn1Error('Negative tag ID (%s) not allowed' % tagId) + self.__tagClass = tagClass + self.__tagFormat = tagFormat + self.__tagId = tagId + self.__tagClassId = tagClass, tagId + self.__lazyHash = None + + def __str__(self): + return '[%s:%s:%s]' % (self.__tagClass, self.__tagFormat, self.__tagId) def __repr__(self): return '%s(tagClass=%s, tagFormat=%s, tagId=%s)' % ( - (self.__class__.__name__,) + self.__tag - ) - # These is really a hotspot -- expose public "uniq" attribute to save on - # function calls - def __eq__(self, other): return self.uniq == other.uniq - def __ne__(self, other): return self.uniq != other.uniq - def __lt__(self, other): return self.uniq < other.uniq - def __le__(self, other): return self.uniq <= other.uniq - def __gt__(self, other): return self.uniq > other.uniq - def __ge__(self, other): return self.uniq >= other.uniq - def __hash__(self): return self.__hashedUniqTag - def __getitem__(self, idx): return self.__tag[idx] + (self.__class__.__name__, self.__tagClass, self.__tagFormat, self.__tagId) + ) + + def __eq__(self, other): + return self.__tagClassId == other + + def __ne__(self, other): + return self.__tagClassId != other + + def __lt__(self, other): + return self.__tagClassId < other + + def __le__(self, other): + return self.__tagClassId <= other + + def __gt__(self, other): + return self.__tagClassId > other + + def __ge__(self, other): + return self.__tagClassId >= other + + def __hash__(self): + if self.__lazyHash is None: + self.__lazyHash = hash(self.__tagClassId) + return self.__lazyHash + + def __getitem__(self, idx): + if idx == 0: + return self.__tagClass + elif idx == 1: + return self.__tagFormat + elif idx == 2: + return self.__tagId + else: + raise IndexError() + + def __iter__(self): + yield self.__tagClass + yield self.__tagFormat + yield self.__tagId + def __and__(self, otherTag): - (tagClass, tagFormat, tagId) = otherTag - return self.__class__( - self.__tag&tagClass, self.__tag&tagFormat, self.__tag&tagId - ) + return self.__class__(self.__tagClass & otherTag.tagClass, + self.__tagFormat & otherTag.tagFormat, + self.__tagId & otherTag.tagId) + def __or__(self, otherTag): - (tagClass, tagFormat, tagId) = otherTag - return self.__class__( - self.__tag[0]|tagClass, - self.__tag[1]|tagFormat, - self.__tag[2]|tagId - ) - def asTuple(self): return self.__tag # __getitem__() is slow - -class TagSet: + return self.__class__(self.__tagClass | otherTag.tagClass, + self.__tagFormat | otherTag.tagFormat, + self.__tagId | otherTag.tagId) + + @property + def tagClass(self): + """ASN.1 tag class + + Returns + ------- + : :py:class:`int` + Tag class + """ + return self.__tagClass + + @property + def tagFormat(self): + """ASN.1 tag format + + Returns + ------- + : :py:class:`int` + Tag format + """ + return self.__tagFormat + + @property + def tagId(self): + """ASN.1 tag ID + + Returns + ------- + : :py:class:`int` + Tag ID + """ + return self.__tagId + + +class TagSet(object): + """Create a collection of ASN.1 tags + + Represents a combination of :class:`~pyasn1.type.tag.Tag` objects + that can be attached to a ASN.1 type to make types distinguishable + from each other. + + *TagSet* objects are immutable and duck-type Python :class:`tuple` objects + holding arbitrary number of :class:`~pyasn1.type.tag.Tag` objects. + + Parameters + ---------- + baseTag: :class:`~pyasn1.type.tag.Tag` + Base *Tag* object. This tag survives IMPLICIT tagging. + + *superTags: :class:`~pyasn1.type.tag.Tag` + Additional *Tag* objects taking part in subtyping. + """ def __init__(self, baseTag=(), *superTags): self.__baseTag = baseTag self.__superTags = superTags - self.__hashedSuperTags = hash(superTags) - _uniq = () - for t in superTags: - _uniq = _uniq + t.uniq - self.uniq = _uniq + self.__superTagsSignature = tuple( + [(superTag.tagClass, superTag.tagId) for superTag in superTags] + ) self.__lenOfSuperTags = len(superTags) - + self.__lazyHash = None + + def __str__(self): + return self.__superTags and '+'.join([str(x) for x in self.__superTags]) or '[untagged]' + def __repr__(self): return '%s(%s)' % ( - self.__class__.__name__, - ', '.join([repr(x) for x in self.__superTags]) - ) + self.__class__.__name__, '(), ' + ', '.join([repr(x) for x in self.__superTags]) + ) def __add__(self, superTag): - return self.__class__( - self.__baseTag, *self.__superTags + (superTag,) - ) + return self.__class__(self.__baseTag, *self.__superTags + (superTag,)) + def __radd__(self, superTag): - return self.__class__( - self.__baseTag, *(superTag,) + self.__superTags - ) + return self.__class__(self.__baseTag, *(superTag,) + self.__superTags) + + def __getitem__(self, i): + if i.__class__ is slice: + return self.__class__(self.__baseTag, *self.__superTags[i]) + else: + return self.__superTags[i] + + def __eq__(self, other): + return self.__superTagsSignature == other + + def __ne__(self, other): + return self.__superTagsSignature != other + + def __lt__(self, other): + return self.__superTagsSignature < other + + def __le__(self, other): + return self.__superTagsSignature <= other + + def __gt__(self, other): + return self.__superTagsSignature > other + + def __ge__(self, other): + return self.__superTagsSignature >= other + + def __hash__(self): + if self.__lazyHash is None: + self.__lazyHash = hash(self.__superTags) + return self.__lazyHash + + def __len__(self): + return self.__lenOfSuperTags + + # descriptor protocol + + def __get__(self, instance, owner): + if instance is None: + return self + + # This is a bit of hack: look up instance attribute first, + # then try class attribute if instance attribute with that + # name is not available. + # The rationale is to have `.tagSet` readable-writeable + # as a class attribute and read-only as instance attribute. + try: + return instance._tagSet + + except AttributeError: + return self + + def __set__(self, instance, value): + raise AttributeError('attribute is read-only') + + @property + def baseTag(self): + """Return base ASN.1 tag + + Returns + ------- + : :class:`~pyasn1.type.tag.Tag` + Base tag of this *TagSet* + """ + return self.__baseTag + + @property + def superTags(self): + """Return ASN.1 tags + + Returns + ------- + : :py:class:`tuple` + Tuple of :class:`~pyasn1.type.tag.Tag` objects that this *TagSet* contains + """ + return self.__superTags def tagExplicitly(self, superTag): - tagClass, tagFormat, tagId = superTag - if tagClass == tagClassUniversal: - raise error.PyAsn1Error( - 'Can\'t tag with UNIVERSAL-class tag' - ) - if tagFormat != tagFormatConstructed: - superTag = Tag(tagClass, tagFormatConstructed, tagId) + """Return explicitly tagged *TagSet* + + Create a new *TagSet* representing callee *TagSet* explicitly tagged + with passed tag(s). With explicit tagging mode, new tags are appended + to existing tag(s). + + Parameters + ---------- + superTag: :class:`~pyasn1.type.tag.Tag` + *Tag* object to tag this *TagSet* + + Returns + ------- + : :class:`~pyasn1.type.tag.TagSet` + New *TagSet* object + """ + if superTag.tagClass == tagClassUniversal: + raise error.PyAsn1Error('Can\'t tag with UNIVERSAL class tag') + if superTag.tagFormat != tagFormatConstructed: + superTag = Tag(superTag.tagClass, tagFormatConstructed, superTag.tagId) return self + superTag def tagImplicitly(self, superTag): - tagClass, tagFormat, tagId = superTag + """Return implicitly tagged *TagSet* + + Create a new *TagSet* representing callee *TagSet* implicitly tagged + with passed tag(s). With implicit tagging mode, new tag(s) replace the + last existing tag. + + Parameters + ---------- + superTag: :class:`~pyasn1.type.tag.Tag` + *Tag* object to tag this *TagSet* + + Returns + ------- + : :class:`~pyasn1.type.tag.TagSet` + New *TagSet* object + """ if self.__superTags: - superTag = Tag(tagClass, self.__superTags[-1][1], tagId) + superTag = Tag(superTag.tagClass, self.__superTags[-1].tagFormat, superTag.tagId) return self[:-1] + superTag - def getBaseTag(self): return self.__baseTag - def __getitem__(self, idx): - if isinstance(idx, slice): - return self.__class__( - self.__baseTag, *getitem(self.__superTags, idx) - ) - return self.__superTags[idx] - def __eq__(self, other): return self.uniq == other.uniq - def __ne__(self, other): return self.uniq != other.uniq - def __lt__(self, other): return self.uniq < other.uniq - def __le__(self, other): return self.uniq <= other.uniq - def __gt__(self, other): return self.uniq > other.uniq - def __ge__(self, other): return self.uniq >= other.uniq - def __hash__(self): return self.__hashedSuperTags - def __len__(self): return self.__lenOfSuperTags def isSuperTagSetOf(self, tagSet): + """Test type relationship against given *TagSet* + + The callee is considered to be a supertype of given *TagSet* + tag-wise if all tags in *TagSet* are present in the callee and + they are in the same order. + + Parameters + ---------- + tagSet: :class:`~pyasn1.type.tag.TagSet` + *TagSet* object to evaluate against the callee + + Returns + ------- + : :py:class:`bool` + `True` if callee is a supertype of *tagSet* + """ if len(tagSet) < self.__lenOfSuperTags: - return - idx = self.__lenOfSuperTags - 1 - while idx >= 0: - if self.__superTags[idx] != tagSet[idx]: - return - idx = idx - 1 - return 1 - -def initTagSet(tag): return TagSet(tag, tag) + return False + return self.__superTags == tagSet[:self.__lenOfSuperTags] + + # Backward compatibility + + def getBaseTag(self): + return self.__baseTag + +def initTagSet(tag): + return TagSet(tag, tag) diff --git a/src/lib/pyasn1/type/tagmap.py b/src/lib/pyasn1/type/tagmap.py index 7cec3a10..8527f33d 100644 --- a/src/lib/pyasn1/type/tagmap.py +++ b/src/lib/pyasn1/type/tagmap.py @@ -1,52 +1,102 @@ +# +# This file is part of pyasn1 software. +# +# Copyright (c) 2005-2017, Ilya Etingof +# License: http://pyasn1.sf.net/license.html +# from pyasn1 import error -class TagMap: - def __init__(self, posMap={}, negMap={}, defType=None): - self.__posMap = posMap.copy() - self.__negMap = negMap.copy() - self.__defType = defType - +__all__ = ['TagMap'] + + +class TagMap(object): + """Map *TagSet* objects to ASN.1 types + + Create an object mapping *TagSet* object to ASN.1 type. + + *TagMap* objects are immutable and duck-type read-only Python + :class:`dict` objects holding *TagSet* objects as keys and ASN.1 + type objects as values. + + Parameters + ---------- + presentTypes: :py:class:`dict` + Map of :class:`~pyasn1.type.tag.TagSet` to ASN.1 objects considered + as being unconditionally present in the *TagMap*. + + skipTypes: :py:class:`dict` + A collection of :class:`~pyasn1.type.tag.TagSet` objects considered + as absent in the *TagMap* even when *defaultType* is present. + + defaultType: ASN.1 type object + An ASN.1 type object callee *TagMap* returns for any *TagSet* key not present + in *presentTypes* (unless given key is present in *skipTypes*). + """ + def __init__(self, presentTypes=None, skipTypes=None, defaultType=None): + self.__presentTypes = presentTypes or {} + self.__skipTypes = skipTypes or {} + self.__defaultType = defaultType + def __contains__(self, tagSet): - return tagSet in self.__posMap or \ - self.__defType is not None and tagSet not in self.__negMap + return (tagSet in self.__presentTypes or + self.__defaultType is not None and tagSet not in self.__skipTypes) def __getitem__(self, tagSet): - if tagSet in self.__posMap: - return self.__posMap[tagSet] - elif tagSet in self.__negMap: - raise error.PyAsn1Error('Key in negative map') - elif self.__defType is not None: - return self.__defType - else: - raise KeyError() + try: + return self.__presentTypes[tagSet] + except KeyError: + if self.__defaultType is None: + raise KeyError() + elif tagSet in self.__skipTypes: + raise error.PyAsn1Error('Key in negative map') + else: + return self.__defaultType + + def __iter__(self): + return iter(self.__presentTypes) def __repr__(self): - s = '%r/%r' % (self.__posMap, self.__negMap) - if self.__defType is not None: - s = s + '/%r' % (self.__defType,) + s = self.__class__.__name__ + '(' + if self.__presentTypes: + s += 'presentTypes=%r, ' % (self.__presentTypes,) + if self.__skipTypes: + s += 'skipTypes=%r, ' % (self.__skipTypes,) + if self.__defaultType is not None: + s += 'defaultType=%r' % (self.__defaultType,) + return s + ')' + + def __str__(self): + s = self.__class__.__name__ + ': ' + if self.__presentTypes: + s += 'presentTypes: %s, ' % ', '.join([x.prettyPrintType() for x in self.__presentTypes.values()]) + if self.__skipTypes: + s += 'skipTypes: %s, ' % ', '.join([x.prettyPrintType() for x in self.__skipTypes.values()]) + if self.__defaultType is not None: + s += 'defaultType: %s, ' % self.__defaultType.prettyPrintType() return s - def clone(self, parentType, tagMap, uniq=False): - if self.__defType is not None and tagMap.getDef() is not None: - raise error.PyAsn1Error('Duplicate default value at %s' % (self,)) - if tagMap.getDef() is not None: - defType = tagMap.getDef() - else: - defType = self.__defType - - posMap = self.__posMap.copy() - for k in tagMap.getPosMap(): - if uniq and k in posMap: - raise error.PyAsn1Error('Duplicate positive key %s' % (k,)) - posMap[k] = parentType + @property + def presentTypes(self): + """Return *TagSet* to ASN.1 type map present in callee *TagMap*""" + return self.__presentTypes - negMap = self.__negMap.copy() - negMap.update(tagMap.getNegMap()) - - return self.__class__( - posMap, negMap, defType, - ) + @property + def skipTypes(self): + """Return *TagSet* collection unconditionally absent in callee *TagMap*""" + return self.__skipTypes - def getPosMap(self): return self.__posMap.copy() - def getNegMap(self): return self.__negMap.copy() - def getDef(self): return self.__defType + @property + def defaultType(self): + """Return default ASN.1 type being returned for any missing *TagSet*""" + return self.__defaultType + + # Backward compatibility + + def getPosMap(self): + return self.presentTypes + + def getNegMap(self): + return self.skipTypes + + def getDef(self): + return self.defaultType diff --git a/src/lib/pyasn1/type/univ.py b/src/lib/pyasn1/type/univ.py index 9cd16f8a..1a146e03 100644 --- a/src/lib/pyasn1/type/univ.py +++ b/src/lib/pyasn1/type/univ.py @@ -1,18 +1,70 @@ -# ASN.1 "universal" data types -import operator, sys +# +# This file is part of pyasn1 software. +# +# Copyright (c) 2005-2017, Ilya Etingof +# License: http://pyasn1.sf.net/license.html +# +import sys +import math from pyasn1.type import base, tag, constraint, namedtype, namedval, tagmap from pyasn1.codec.ber import eoo -from pyasn1.compat import octets +from pyasn1.compat import octets, integer, binary from pyasn1 import error +NoValue = base.NoValue +noValue = NoValue() + +__all__ = ['Integer', 'Boolean', 'BitString', 'OctetString', 'Null', + 'ObjectIdentifier', 'Real', 'Enumerated', 'SequenceOfAndSetOfBase', 'SequenceOf', + 'SetOf', 'SequenceAndSetBase', 'Sequence', 'Set', 'Choice', 'Any', + 'NoValue', 'noValue'] + # "Simple" ASN.1 types (yet incomplete) class Integer(base.AbstractSimpleAsn1Item): - tagSet = baseTagSet = tag.initTagSet( + """Create |ASN.1| type or object. + + |ASN.1| objects are immutable and duck-type Python :class:`int` objects. + + Parameters + ---------- + value : :class:`int`, :class:`str` or |ASN.1| object + Python integer or string literal or |ASN.1| class instance. + + tagSet: :py:class:`~pyasn1.type.tag.TagSet` + Object representing non-default ASN.1 tag(s) + + subtypeSpec: :py:class:`~pyasn1.type.constraint.ConstraintsIntersection` + Object representing non-default ASN.1 subtype constraint(s) + + namedValues: :py:class:`~pyasn1.type.namedval.NamedValues` + Object representing non-default symbolic aliases for numbers + + Raises + ------ + : :py:class:`pyasn1.error.PyAsn1Error` + On constraint violation or bad initializer. + """ + #: Set (class attribute) or return (class or instance attribute) a + #: :py:class:`~pyasn1.type.tag.TagSet` object representing ASN.1 tag(s) + #: associated with |ASN.1| type. + tagSet = tag.initTagSet( tag.Tag(tag.tagClassUniversal, tag.tagFormatSimple, 0x02) - ) + ) + + #: Set (class attribute) or return (class or instance attribute) a + #: :py:class:`~pyasn1.type.constraint.ConstraintsIntersection` object + #: imposing constraints on |ASN.1| type initialization values. + subtypeSpec = constraint.ConstraintsIntersection() + + #: Default :py:class:`~pyasn1.type.namedval.NamedValues` object + #: representing symbolic aliases for numbers namedValues = namedval.NamedValues() - def __init__(self, value=None, tagSet=None, subtypeSpec=None, + + # Optimization for faster codec lookup + typeId = base.AbstractSimpleAsn1Item.getTypeId() + + def __init__(self, value=noValue, tagSet=None, subtypeSpec=None, namedValues=None): if namedValues is None: self.__namedValues = self.namedValues @@ -20,490 +72,1386 @@ class Integer(base.AbstractSimpleAsn1Item): self.__namedValues = namedValues base.AbstractSimpleAsn1Item.__init__( self, value, tagSet, subtypeSpec - ) + ) - def __and__(self, value): return self.clone(self._value & value) - def __rand__(self, value): return self.clone(value & self._value) - def __or__(self, value): return self.clone(self._value | value) - def __ror__(self, value): return self.clone(value | self._value) - def __xor__(self, value): return self.clone(self._value ^ value) - def __rxor__(self, value): return self.clone(value ^ self._value) - def __lshift__(self, value): return self.clone(self._value << value) - def __rshift__(self, value): return self.clone(self._value >> value) + def __repr__(self): + if self.__namedValues is not self.namedValues: + return '%s, %r)' % (base.AbstractSimpleAsn1Item.__repr__(self)[:-1], self.__namedValues) + else: + return base.AbstractSimpleAsn1Item.__repr__(self) - def __add__(self, value): return self.clone(self._value + value) - def __radd__(self, value): return self.clone(value + self._value) - def __sub__(self, value): return self.clone(self._value - value) - def __rsub__(self, value): return self.clone(value - self._value) - def __mul__(self, value): return self.clone(self._value * value) - def __rmul__(self, value): return self.clone(value * self._value) - def __mod__(self, value): return self.clone(self._value % value) - def __rmod__(self, value): return self.clone(value % self._value) - def __pow__(self, value, modulo=None): return self.clone(pow(self._value, value, modulo)) - def __rpow__(self, value): return self.clone(pow(value, self._value)) + def __and__(self, value): + return self.clone(self._value & value) + + def __rand__(self, value): + return self.clone(value & self._value) + + def __or__(self, value): + return self.clone(self._value | value) + + def __ror__(self, value): + return self.clone(value | self._value) + + def __xor__(self, value): + return self.clone(self._value ^ value) + + def __rxor__(self, value): + return self.clone(value ^ self._value) + + def __lshift__(self, value): + return self.clone(self._value << value) + + def __rshift__(self, value): + return self.clone(self._value >> value) + + def __add__(self, value): + return self.clone(self._value + value) + + def __radd__(self, value): + return self.clone(value + self._value) + + def __sub__(self, value): + return self.clone(self._value - value) + + def __rsub__(self, value): + return self.clone(value - self._value) + + def __mul__(self, value): + return self.clone(self._value * value) + + def __rmul__(self, value): + return self.clone(value * self._value) + + def __mod__(self, value): + return self.clone(self._value % value) + + def __rmod__(self, value): + return self.clone(value % self._value) + + def __pow__(self, value, modulo=None): + return self.clone(pow(self._value, value, modulo)) + + def __rpow__(self, value): + return self.clone(pow(value, self._value)) + + def __floordiv__(self, value): + return self.clone(self._value // value) + + def __rfloordiv__(self, value): + return self.clone(value // self._value) if sys.version_info[0] <= 2: - def __div__(self, value): return self.clone(self._value // value) - def __rdiv__(self, value): return self.clone(value // self._value) + def __div__(self, value): + if isinstance(value, float): + return Real(self._value / value) + else: + return self.clone(self._value / value) + + def __rdiv__(self, value): + if isinstance(value, float): + return Real(value / self._value) + else: + return self.clone(value / self._value) else: - def __truediv__(self, value): return self.clone(self._value / value) - def __rtruediv__(self, value): return self.clone(value / self._value) - def __divmod__(self, value): return self.clone(self._value // value) - def __rdivmod__(self, value): return self.clone(value // self._value) + def __truediv__(self, value): + return Real(self._value / value) + + def __rtruediv__(self, value): + return Real(value / self._value) + + def __divmod__(self, value): + return self.clone(divmod(self._value, value)) + + def __rdivmod__(self, value): + return self.clone(divmod(value, self._value)) __hash__ = base.AbstractSimpleAsn1Item.__hash__ - def __int__(self): return int(self._value) + def __int__(self): + return int(self._value) + if sys.version_info[0] <= 2: def __long__(self): return long(self._value) - def __float__(self): return float(self._value) - def __abs__(self): return abs(self._value) - def __index__(self): return int(self._value) - def __lt__(self, value): return self._value < value - def __le__(self, value): return self._value <= value - def __eq__(self, value): return self._value == value - def __ne__(self, value): return self._value != value - def __gt__(self, value): return self._value > value - def __ge__(self, value): return self._value >= value + def __float__(self): + return float(self._value) + + def __abs__(self): + return self.clone(abs(self._value)) + + def __index__(self): + return int(self._value) + + def __pos__(self): + return self.clone(+self._value) + + def __neg__(self): + return self.clone(-self._value) + + def __invert__(self): + return self.clone(~self._value) + + def __round__(self, n=0): + r = round(self._value, n) + if n: + return self.clone(r) + else: + return r + + def __floor__(self): + return math.floor(self._value) + + def __ceil__(self): + return math.ceil(self._value) + + if sys.version_info[0:2] > (2, 5): + def __trunc__(self): + return self.clone(math.trunc(self._value)) + + def __lt__(self, value): + return self._value < value + + def __le__(self, value): + return self._value <= value + + def __eq__(self, value): + return self._value == value + + def __ne__(self, value): + return self._value != value + + def __gt__(self, value): + return self._value > value + + def __ge__(self, value): + return self._value >= value def prettyIn(self, value): - if not isinstance(value, str): - try: - return int(value) - except: - raise error.PyAsn1Error( - 'Can\'t coerce %s into integer: %s' % (value, sys.exc_info()[1]) - ) - r = self.__namedValues.getValue(value) - if r is not None: - return r try: return int(value) - except: + + except ValueError: + valueOfName = self.__namedValues.getValue(value) + if valueOfName is not None: + return valueOfName + raise error.PyAsn1Error( - 'Can\'t coerce %s into integer: %s' % (value, sys.exc_info()[1]) - ) + 'Can\'t coerce %r into integer: %s' % (value, sys.exc_info()[1]) + ) def prettyOut(self, value): - r = self.__namedValues.getName(value) - return r is None and str(value) or repr(r) + nameOfValue = self.__namedValues.getName(value) + return nameOfValue is None and str(value) or repr(nameOfValue) - def getNamedValues(self): return self.__namedValues + def getNamedValues(self): + return self.__namedValues - def clone(self, value=None, tagSet=None, subtypeSpec=None, - namedValues=None): - if value is None and tagSet is None and subtypeSpec is None \ - and namedValues is None: - return self - if value is None: + def clone(self, value=noValue, tagSet=None, subtypeSpec=None, namedValues=None): + """Create a copy of a |ASN.1| type or object. + + Any parameters to the *clone()* method will replace corresponding + properties of the |ASN.1| object. + + Parameters + ---------- + value: :class:`int`, :class:`str` or |ASN.1| object + Initialization value to pass to new ASN.1 object instead of + inheriting one from the caller. + + tagSet: :py:class:`~pyasn1.type.tag.TagSet` + Object representing ASN.1 tag(s) to use in new object instead of inheriting from the caller + + subtypeSpec: :py:class:`~pyasn1.type.constraint.ConstraintsIntersection` + Object representing ASN.1 subtype constraint(s) to use in new object instead of inheriting from the caller + + namedValues: :py:class:`~pyasn1.type.namedval.NamedValues` + Object representing symbolic aliases for numbers to use instead of inheriting from caller + + Returns + ------- + : + new instance of |ASN.1| type/value + """ + isModified = False + + if value is None or value is noValue: value = self._value - if tagSet is None: + else: + isModified = True + if tagSet is None or tagSet is noValue: tagSet = self._tagSet - if subtypeSpec is None: + else: + isModified = True + if subtypeSpec is None or subtypeSpec is noValue: subtypeSpec = self._subtypeSpec - if namedValues is None: + else: + isModified = True + if namedValues is None or namedValues is noValue: namedValues = self.__namedValues - return self.__class__(value, tagSet, subtypeSpec, namedValues) + else: + isModified = True - def subtype(self, value=None, implicitTag=None, explicitTag=None, + if isModified: + return self.__class__(value, tagSet, subtypeSpec, namedValues) + else: + return self + + def subtype(self, value=noValue, implicitTag=None, explicitTag=None, subtypeSpec=None, namedValues=None): - if value is None: + """Create a copy of a |ASN.1| type or object. + + Any parameters to the *subtype()* method will be added to the corresponding + properties of the |ASN.1| object. + + Parameters + ---------- + value: :class:`int`, :class:`str` or |ASN.1| object + Initialization value to pass to new ASN.1 object instead of + inheriting one from the caller. + + implicitTag: :py:class:`~pyasn1.type.tag.Tag` + Implicitly apply given ASN.1 tag object to caller's + :py:class:`~pyasn1.type.tag.TagSet`, then use the result as + new object's ASN.1 tag(s). + + explicitTag: :py:class:`~pyasn1.type.tag.Tag` + Explicitly apply given ASN.1 tag object to caller's + :py:class:`~pyasn1.type.tag.TagSet`, then use the result as + new object's ASN.1 tag(s). + + subtypeSpec: :py:class:`~pyasn1.type.constraint.ConstraintsIntersection` + Add ASN.1 constraints object to one of the caller, then + use the result as new object's ASN.1 constraints. + + namedValues: :py:class:`~pyasn1.type.namedval.NamedValues` + Add given object representing symbolic aliases for numbers + to one of the caller, then use the result as new object's + named numbers. + + Returns + ------- + : + new instance of |ASN.1| type/value + """ + isModified = False + + if value is None or value is noValue: value = self._value - if implicitTag is not None: + else: + isModified = True + if implicitTag is not None and implicitTag is not noValue: tagSet = self._tagSet.tagImplicitly(implicitTag) - elif explicitTag is not None: + isModified = True + elif explicitTag is not None and explicitTag is not noValue: tagSet = self._tagSet.tagExplicitly(explicitTag) + isModified = True else: tagSet = self._tagSet - if subtypeSpec is None: + if subtypeSpec is None or subtypeSpec is noValue: subtypeSpec = self._subtypeSpec else: - subtypeSpec = subtypeSpec + self._subtypeSpec - if namedValues is None: + subtypeSpec = self._subtypeSpec + subtypeSpec + isModified = True + if namedValues is None or namedValues is noValue: namedValues = self.__namedValues else: namedValues = namedValues + self.__namedValues - return self.__class__(value, tagSet, subtypeSpec, namedValues) + isModified = True + + if isModified: + return self.__class__(value, tagSet, subtypeSpec, namedValues) + else: + return self + class Boolean(Integer): - tagSet = baseTagSet = tag.initTagSet( + __doc__ = Integer.__doc__ + + #: Set (class attribute) or return (class or instance attribute) a + #: :py:class:`~pyasn1.type.tag.TagSet` object representing ASN.1 tag(s) + #: associated with |ASN.1| type. + tagSet = tag.initTagSet( tag.Tag(tag.tagClassUniversal, tag.tagFormatSimple, 0x01), - ) - subtypeSpec = Integer.subtypeSpec+constraint.SingleValueConstraint(0,1) + ) + + #: Set (class attribute) or return (class or instance attribute) a + #: :py:class:`~pyasn1.type.constraint.ConstraintsIntersection` object + #: imposing constraints on |ASN.1| type initialization values. + subtypeSpec = Integer.subtypeSpec + constraint.SingleValueConstraint(0, 1) + + #: Default :py:class:`~pyasn1.type.namedval.NamedValues` object + #: representing symbolic aliases for numbers namedValues = Integer.namedValues.clone(('False', 0), ('True', 1)) + # Optimization for faster codec lookup + typeId = Integer.getTypeId() + + class BitString(base.AbstractSimpleAsn1Item): - tagSet = baseTagSet = tag.initTagSet( + """Create |ASN.1| type or object. + + |ASN.1| objects are immutable and duck-type both Python :class:`tuple` (as a tuple + of bits) and :class:`int` objects. + + Parameters + ---------- + 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. + + tagSet: :py:class:`~pyasn1.type.tag.TagSet` + Object representing non-default ASN.1 tag(s) + + subtypeSpec: :py:class:`~pyasn1.type.constraint.ConstraintsIntersection` + Object representing non-default ASN.1 subtype constraint(s) + + namedValues: :py:class:`~pyasn1.type.namedval.NamedValues` + Object representing non-default symbolic aliases for numbers + + binValue: :py:class:`str` + Binary string initializer to use instead of the *value*. + Example: '10110011'. + + hexValue: :py:class:`str` + Hexadecimal string initializer to use instead of the *value*. + Example: 'DEADBEEF'. + + Raises + ------ + : :py:class:`pyasn1.error.PyAsn1Error` + On constraint violation or bad initializer. + """ + #: Set (class attribute) or return (class or instance attribute) a + #: :py:class:`~pyasn1.type.tag.TagSet` object representing ASN.1 tag(s) + #: associated with |ASN.1| type. + tagSet = tag.initTagSet( tag.Tag(tag.tagClassUniversal, tag.tagFormatSimple, 0x03) - ) + ) + + #: Set (class attribute) or return (class or instance attribute) a + #: :py:class:`~pyasn1.type.constraint.ConstraintsIntersection` object + #: imposing constraints on |ASN.1| type initialization values. + subtypeSpec = constraint.ConstraintsIntersection() + + #: Default :py:class:`~pyasn1.type.namedval.NamedValues` object + #: representing symbolic aliases for numbers namedValues = namedval.NamedValues() - def __init__(self, value=None, tagSet=None, subtypeSpec=None, - namedValues=None): + + # Optimization for faster codec lookup + typeId = base.AbstractSimpleAsn1Item.getTypeId() + + defaultBinValue = defaultHexValue = noValue + + if sys.version_info[0] < 3: + SizedIntegerBase = long + else: + SizedIntegerBase = int + + class SizedInteger(SizedIntegerBase): + bitLength = leadingZeroBits = None + + def setBitLength(self, bitLength): + self.bitLength = bitLength + self.leadingZeroBits = max(bitLength - integer.bitLength(self), 0) + return self + + def __len__(self): + if self.bitLength is None: + self.setBitLength(integer.bitLength(self)) + + return self.bitLength + + def __init__(self, value=noValue, tagSet=None, subtypeSpec=None, + namedValues=None, binValue=noValue, hexValue=noValue): if namedValues is None: self.__namedValues = self.namedValues else: self.__namedValues = namedValues - base.AbstractSimpleAsn1Item.__init__( - self, value, tagSet, subtypeSpec - ) + if binValue is not noValue: + value = self.fromBinaryString(binValue) + elif hexValue is not noValue: + value = self.fromHexString(hexValue) + elif value is None or value is noValue: + if self.defaultBinValue is not noValue: + value = self.fromBinaryString(self.defaultBinValue) + elif self.defaultHexValue is not noValue: + value = self.fromHexString(self.defaultHexValue) + base.AbstractSimpleAsn1Item.__init__(self, value, tagSet, subtypeSpec) - def clone(self, value=None, tagSet=None, subtypeSpec=None, - namedValues=None): - if value is None and tagSet is None and subtypeSpec is None \ - and namedValues is None: - return self - if value is None: + def clone(self, value=noValue, tagSet=None, subtypeSpec=None, + namedValues=None, binValue=noValue, hexValue=noValue): + """Create a copy of a |ASN.1| type or object. + + Any parameters to the *clone()* method will replace corresponding + properties of the |ASN.1| object. + + Parameters + ---------- + value : :class:`int`, :class:`str` or |ASN.1| object + Initialization value to pass to new ASN.1 object instead of + inheriting one from the caller. + + tagSet: :py:class:`~pyasn1.type.tag.TagSet` + Object representing ASN.1 tag(s) to use in new object instead of inheriting from the caller + + subtypeSpec: :py:class:`~pyasn1.type.constraint.ConstraintsIntersection` + Object representing ASN.1 subtype constraint(s) to use in new object instead of inheriting from the caller + + namedValues: :py:class:`~pyasn1.type.namedval.NamedValues` + Class instance representing BitString type enumerations + + binValue: :py:class:`str` + Binary string initializer to use instead of the *value*. + Example: '10110011'. + + hexValue: :py:class:`str` + Hexadecimal string initializer to use instead of the *value*. + Example: 'DEADBEEF'. + + Returns + ------- + : + new instance of |ASN.1| type/value + """ + isModified = False + + if (value is None or value is noValue) and binValue is noValue and hexValue is noValue: value = self._value - if tagSet is None: + else: + isModified = True + if tagSet is None or tagSet is noValue: tagSet = self._tagSet - if subtypeSpec is None: + else: + isModified = True + if subtypeSpec is None or subtypeSpec is noValue: subtypeSpec = self._subtypeSpec - if namedValues is None: + else: + isModified = True + if namedValues is None or namedValues is noValue: namedValues = self.__namedValues - return self.__class__(value, tagSet, subtypeSpec, namedValues) + else: + isModified = True - def subtype(self, value=None, implicitTag=None, explicitTag=None, - subtypeSpec=None, namedValues=None): - if value is None: + if isModified: + return self.__class__(value, tagSet, subtypeSpec, namedValues, binValue, hexValue) + else: + return self + + def subtype(self, value=noValue, implicitTag=None, explicitTag=None, + subtypeSpec=None, namedValues=None, binValue=noValue, hexValue=noValue): + """Create a copy of a |ASN.1| type or object. + + Any parameters to the *subtype()* method will be added to the corresponding + properties of the |ASN.1| object. + + Parameters + ---------- + value : :class:`int`, :class:`str` or |ASN.1| object + Initialization value to pass to new ASN.1 object instead of + inheriting one from the caller. + + implicitTag: :py:class:`~pyasn1.type.tag.Tag` + Implicitly apply given ASN.1 tag object to caller's + :py:class:`~pyasn1.type.tag.TagSet`, then use the result as + new object's ASN.1 tag(s). + + explicitTag: :py:class:`~pyasn1.type.tag.Tag` + Explicitly apply given ASN.1 tag object to caller's + :py:class:`~pyasn1.type.tag.TagSet`, then use the result as + new object's ASN.1 tag(s). + + subtypeSpec: :py:class:`~pyasn1.type.constraint.ConstraintsIntersection` + Add ASN.1 constraints object to one of the caller, then + use the result as new object's ASN.1 constraints. + + namedValues: :py:class:`~pyasn1.type.namedval.NamedValues` + Add given object representing symbolic aliases for numbers + to one of the caller, then use the result as new object's + named numbers. + + binValue: :py:class:`str` + Binary string initializer to use instead of the *value*. + Example: '10110011'. + + hexValue: :py:class:`str` + Hexadecimal string initializer to use instead of the *value*. + Example: 'DEADBEEF'. + + Returns + ------- + : + new instance of |ASN.1| type/value + """ + isModified = False + + if (value is None or value is noValue) and binValue is noValue and hexValue is noValue: value = self._value - if implicitTag is not None: + else: + isModified = True + if implicitTag is not None and implicitTag is not noValue: tagSet = self._tagSet.tagImplicitly(implicitTag) - elif explicitTag is not None: + isModified = True + elif explicitTag is not None and explicitTag is not noValue: tagSet = self._tagSet.tagExplicitly(explicitTag) + isModified = True else: tagSet = self._tagSet - if subtypeSpec is None: + if subtypeSpec is None or subtypeSpec is noValue: subtypeSpec = self._subtypeSpec else: - subtypeSpec = subtypeSpec + self._subtypeSpec - if namedValues is None: + subtypeSpec = self._subtypeSpec + subtypeSpec + isModified = True + if namedValues is None or namedValues is noValue: namedValues = self.__namedValues else: namedValues = namedValues + self.__namedValues - return self.__class__(value, tagSet, subtypeSpec, namedValues) + isModified = True - def __str__(self): return str(tuple(self)) + if isModified: + return self.__class__(value, tagSet, subtypeSpec, namedValues, binValue, hexValue) + else: + return self + + def __str__(self): + return self.asBinary() + + def __eq__(self, other): + other = self.prettyIn(other) + return self is other or self._value == other and len(self._value) == len(other) + + def __ne__(self, other): + other = self.prettyIn(other) + return self._value != other or len(self._value) != len(other) + + def __lt__(self, other): + other = self.prettyIn(other) + return len(self._value) < len(other) or len(self._value) == len(other) and self._value < other + + def __le__(self, other): + other = self.prettyIn(other) + return len(self._value) <= len(other) or len(self._value) == len(other) and self._value <= other + + def __gt__(self, other): + other = self.prettyIn(other) + return len(self._value) > len(other) or len(self._value) == len(other) and self._value > other + + def __ge__(self, other): + other = self.prettyIn(other) + return len(self._value) >= len(other) or len(self._value) == len(other) and self._value >= other # Immutable sequence object protocol def __len__(self): - if self._len is None: - self._len = len(self._value) - return self._len - def __getitem__(self, i): - if isinstance(i, slice): - return self.clone(operator.getitem(self._value, i)) - else: - return self._value[i] + return len(self._value) - def __add__(self, value): return self.clone(self._value + value) - def __radd__(self, value): return self.clone(value + self._value) - def __mul__(self, value): return self.clone(self._value * value) - def __rmul__(self, value): return self * value + def __getitem__(self, i): + if i.__class__ is slice: + return self.clone([self[x] for x in range(*i.indices(len(self)))]) + else: + length = len(self._value) - 1 + if i > length or i < 0: + raise IndexError('bit index out of range') + return (self._value >> (length - i)) & 1 + + def __iter__(self): + length = len(self._value) + while length: + length -= 1 + yield (self._value >> length) & 1 + + def __reversed__(self): + return reversed(tuple(self)) + + # arithmetic operators + + def __add__(self, value): + value = self.prettyIn(value) + return self.clone(self.SizedInteger(self._value << len(value) | value).setBitLength(len(self._value) + len(value))) + + def __radd__(self, value): + value = self.prettyIn(value) + return self.clone(self.SizedInteger(value << len(self._value) | self._value).setBitLength(len(self._value) + len(value))) + + def __mul__(self, value): + bitString = self._value + while value > 1: + bitString <<= len(self._value) + bitString |= self._value + value -= 1 + return self.clone(bitString) + + def __rmul__(self, value): + return self * value + + def __lshift__(self, count): + return self.clone(self.SizedInteger(self._value << count).setBitLength(len(self._value) + count)) + + def __rshift__(self, count): + return self.clone(self.SizedInteger(self._value >> count).setBitLength(max(0, len(self._value) - count))) + + def __int__(self): + return self._value + + def __float__(self): + return float(self._value) + + if sys.version_info[0] < 3: + def __long__(self): + return self._value + + def asNumbers(self): + """Get |ASN.1| value as a sequence of 8-bit integers. + + If |ASN.1| object length is not a multiple of 8, result + will be left-padded with zeros. + """ + return tuple(octets.octs2ints(self.asOctets())) + + def asOctets(self): + """Get |ASN.1| value as a sequence of octets. + + If |ASN.1| object length is not a multiple of 8, result + will be left-padded with zeros. + """ + return integer.to_bytes(self._value, length=len(self)) + + def asInteger(self): + """Get |ASN.1| value as a single integer value. + """ + return self._value + + def asBinary(self): + """Get |ASN.1| value as a text string of bits. + """ + binString = binary.bin(self._value)[2:] + return '0'*(len(self._value) - len(binString)) + binString + + @classmethod + def fromHexString(cls, value): + try: + return cls.SizedInteger(value, 16).setBitLength(len(value) * 4) + + except ValueError: + raise error.PyAsn1Error('%s.fromHexString() error: %s' % (cls.__name__, sys.exc_info()[1])) + + @classmethod + def fromBinaryString(cls, value): + try: + return cls.SizedInteger(value or '0', 2).setBitLength(len(value)) + + except ValueError: + raise error.PyAsn1Error('%s.fromBinaryString() error: %s' % (cls.__name__, sys.exc_info()[1])) + + @classmethod + def fromOctetString(cls, value, padding=0): + return cls(cls.SizedInteger(integer.from_bytes(value) >> padding).setBitLength(len(value) * 8 - padding)) def prettyIn(self, value): - r = [] - if not value: - return () - elif isinstance(value, str): - if value[0] == '\'': + if octets.isStringType(value): + if not value: + return self.SizedInteger(0).setBitLength(0) + + elif value[0] == '\'': # "'1011'B" -- ASN.1 schema representation (deprecated) if value[-2:] == '\'B': - for v in value[1:-2]: - if v == '0': - r.append(0) - elif v == '1': - r.append(1) - else: - raise error.PyAsn1Error( - 'Non-binary BIT STRING initializer %s' % (v,) - ) - return tuple(r) + return self.fromBinaryString(value[1:-2]) elif value[-2:] == '\'H': - for v in value[1:-2]: - i = 4 - v = int(v, 16) - while i: - i = i - 1 - r.append((v>>i)&0x01) - return tuple(r) + return self.fromHexString(value[1:-2]) else: raise error.PyAsn1Error( 'Bad BIT STRING value notation %s' % (value,) - ) - else: - for i in value.split(','): - j = self.__namedValues.getValue(i) - if j is None: - raise error.PyAsn1Error( - 'Unknown bit identifier \'%s\'' % (i,) - ) - if j >= len(r): - r.extend([0]*(j-len(r)+1)) - r[j] = 1 - return tuple(r) + ) + + elif self.__namedValues and not value.isdigit(): # named bits like 'Urgent, Active' + bitPositions = self.__namedValues.getValues(*[x.strip() for x in value.split(',')]) + + rightmostPosition = max(bitPositions) + + number = 0 + for bitPosition in bitPositions: + number |= 1 << (rightmostPosition - bitPosition) + + return self.SizedInteger(number).setBitLength(rightmostPosition + 1) + + elif value.startswith('0x'): + return self.fromHexString(value[2:]) + + elif value.startswith('0b'): + return self.fromBinaryString(value[2:]) + + else: # assume plain binary string like '1011' + return self.fromBinaryString(value) + elif isinstance(value, (tuple, list)): - r = tuple(value) - for b in r: - if b and b != 1: - raise error.PyAsn1Error( - 'Non-binary BitString initializer \'%s\'' % (r,) - ) - return r - elif isinstance(value, BitString): - return tuple(value) + return self.fromBinaryString(''.join([b and '1' or '0' for b in value])) + + elif isinstance(value, (self.SizedInteger, BitString)): + return self.SizedInteger(value).setBitLength(len(value)) + + elif isinstance(value, intTypes): + return self.SizedInteger(value) + else: raise error.PyAsn1Error( 'Bad BitString initializer type \'%s\'' % (value,) - ) + ) def prettyOut(self, value): - return '\"\'%s\'B\"' % ''.join([str(x) for x in value]) + return '\'%s\'' % str(self) + + +try: + # noinspection PyStatementEffect + all + +except NameError: # Python 2.4 + # noinspection PyShadowingBuiltins + def all(iterable): + for element in iterable: + if not element: + return False + return True + class OctetString(base.AbstractSimpleAsn1Item): - tagSet = baseTagSet = tag.initTagSet( + """Create |ASN.1| type or 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|" serialization. + + Parameters + ---------- + 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 + serialized into octets (note `encoding` parameter) or |ASN.1| object. + + tagSet: :py:class:`~pyasn1.type.tag.TagSet` + Object representing non-default ASN.1 tag(s) + + subtypeSpec: :py:class:`~pyasn1.type.constraint.ConstraintsIntersection` + Object representing non-default ASN.1 subtype constraint(s) + + encoding: :py:class:`str` + Unicode codec ID to encode/decode :class:`unicode` (Python 2) or + :class:`str` (Python 3) the payload when |ASN.1| object is used + in text string context. + + binValue: :py:class:`str` + Binary string initializer to use instead of the *value*. + Example: '10110011'. + + hexValue: :py:class:`str` + Hexadecimal string initializer to use instead of the *value*. + Example: 'DEADBEEF'. + + Raises + ------ + : :py:class:`pyasn1.error.PyAsn1Error` + On constraint violation or bad initializer. + """ + #: Set (class attribute) or return (class or instance attribute) a + #: :py:class:`~pyasn1.type.tag.TagSet` object representing ASN.1 tag(s) + #: associated with |ASN.1| type. + tagSet = tag.initTagSet( tag.Tag(tag.tagClassUniversal, tag.tagFormatSimple, 0x04) - ) - defaultBinValue = defaultHexValue = base.noValue - encoding = 'us-ascii' - def __init__(self, value=None, tagSet=None, subtypeSpec=None, - encoding=None, binValue=None, hexValue=None): + ) + + #: Set (class attribute) or return (class or instance attribute) a + #: :py:class:`~pyasn1.type.constraint.ConstraintsIntersection` object + #: imposing constraints on |ASN.1| type initialization values. + subtypeSpec = constraint.ConstraintsIntersection() + + # Optimization for faster codec lookup + typeId = base.AbstractSimpleAsn1Item.getTypeId() + + defaultBinValue = defaultHexValue = noValue + encoding = 'iso-8859-1' + + def __init__(self, value=noValue, tagSet=None, subtypeSpec=None, + encoding=None, binValue=noValue, hexValue=noValue): if encoding is None: self._encoding = self.encoding else: self._encoding = encoding - if binValue is not None: + if binValue is not noValue: value = self.fromBinaryString(binValue) - if hexValue is not None: + elif hexValue is not noValue: value = self.fromHexString(hexValue) - if value is None or value is base.noValue: - value = self.defaultHexValue - if value is None or value is base.noValue: - value = self.defaultBinValue - self.__intValue = None + elif value is None or value is noValue: + if self.defaultBinValue is not noValue: + value = self.fromBinaryString(self.defaultBinValue) + elif self.defaultHexValue is not noValue: + value = self.fromHexString(self.defaultHexValue) + self.__asNumbersCache = None base.AbstractSimpleAsn1Item.__init__(self, value, tagSet, subtypeSpec) - def clone(self, value=None, tagSet=None, subtypeSpec=None, - encoding=None, binValue=None, hexValue=None): - if value is None and tagSet is None and subtypeSpec is None and \ - encoding is None and binValue is None and hexValue is None: - return self - if value is None and binValue is None and hexValue is None: + def clone(self, value=noValue, tagSet=None, subtypeSpec=None, + encoding=None, binValue=noValue, hexValue=noValue): + """Create a copy of a |ASN.1| type or object. + + Any parameters to the *clone()* method will replace corresponding + properties of the |ASN.1| object. + + Parameters + ---------- + value : :class:`str`, :class:`bytes` or |ASN.1| object + Initialization value to pass to new ASN.1 object instead of + inheriting one from the caller. + + tagSet: :py:class:`~pyasn1.type.tag.TagSet` + Object representing ASN.1 tag(s) to use in new object instead of inheriting from the caller + + subtypeSpec: :py:class:`~pyasn1.type.constraint.ConstraintsIntersection` + Object representing ASN.1 subtype constraint(s) to use in new object instead of inheriting from the caller + + encoding: :py:class:`str` + Unicode codec ID to encode/decode :class:`unicode` (Python 2) + or :class:`str` (Python 3) the payload when |ASN.1| + object is used in string context. + + binValue: :py:class:`str` + Binary string initializer. Example: '10110011'. + + hexValue: :py:class:`str` + Hexadecimal string initializer. Example: 'DEADBEEF'. + + Returns + ------- + : + new instance of |ASN.1| type/value + """ + isModified = False + + if (value is None or value is noValue) and binValue is noValue and hexValue is noValue: value = self._value - if tagSet is None: + else: + isModified = True + if tagSet is None or tagSet is noValue: tagSet = self._tagSet - if subtypeSpec is None: + else: + isModified = True + if subtypeSpec is None or subtypeSpec is noValue: subtypeSpec = self._subtypeSpec - if encoding is None: + else: + isModified = True + if encoding is None or encoding is noValue: encoding = self._encoding - return self.__class__( - value, tagSet, subtypeSpec, encoding, binValue, hexValue - ) - + else: + isModified = True + + if isModified: + return self.__class__(value, tagSet, subtypeSpec, encoding, binValue, hexValue) + else: + return self + + def subtype(self, value=noValue, implicitTag=None, explicitTag=None, + subtypeSpec=None, encoding=None, binValue=noValue, + hexValue=noValue): + """Create a copy of a |ASN.1| type or object. + + Any parameters to the *subtype()* method will be added to the corresponding + properties of the |ASN.1| object. + + Parameters + ---------- + value : :class:`str`, :class:`bytes` or |ASN.1| object + Initialization value to pass to new ASN.1 object instead of + inheriting one from the caller. + + implicitTag: :py:class:`~pyasn1.type.tag.Tag` + Implicitly apply given ASN.1 tag object to |ASN.1| object tag set + :py:class:`~pyasn1.type.tag.TagSet`, then use the result as + new object's ASN.1 tag(s). + + explicitTag: :py:class:`~pyasn1.type.tag.Tag` + Explicitly apply given ASN.1 tag object to |ASN.1| object tag set + :py:class:`~pyasn1.type.tag.TagSet`, then use the result as + new object's ASN.1 tag(s). + + subtypeSpec: :py:class:`~pyasn1.type.constraint.ConstraintsIntersection` + Add ASN.1 constraints object to one of the caller, then + use the result as new object's ASN.1 constraints. + + encoding: :py:class:`str` + Unicode codec ID to encode/decode :class:`unicode` (Python 2) + or :class:`str` (Python 3) the payload when *OctetString* + object is used in string context. + + binValue: :py:class:`str` + Binary string initializer. Example: '10110011'. + + hexValue: :py:class:`str` + Hexadecimal string initializer. Example: 'DEADBEEF'. + + Returns + ------- + : + new instance of |ASN.1| type/value + """ + isModified = False + + if (value is None or value is noValue) and binValue is noValue and hexValue is noValue: + value = self._value + else: + isModified = True + if implicitTag is not None and implicitTag is not noValue: + tagSet = self._tagSet.tagImplicitly(implicitTag) + isModified = True + elif explicitTag is not None and explicitTag is not noValue: + tagSet = self._tagSet.tagExplicitly(explicitTag) + isModified = True + else: + tagSet = self._tagSet + if subtypeSpec is None or subtypeSpec is noValue: + subtypeSpec = self._subtypeSpec + else: + subtypeSpec = self._subtypeSpec + subtypeSpec + isModified = True + if encoding is None or encoding is noValue: + encoding = self._encoding + else: + isModified = True + + if isModified: + return self.__class__(value, tagSet, subtypeSpec, encoding, binValue, hexValue) + else: + return self + if sys.version_info[0] <= 2: def prettyIn(self, value): if isinstance(value, str): return value + elif isinstance(value, unicode): + try: + return value.encode(self._encoding) + except (LookupError, UnicodeEncodeError): + raise error.PyAsn1Error( + 'Can\'t encode string \'%s\' with \'%s\' codec' % (value, self._encoding) + ) elif isinstance(value, (tuple, list)): try: - return ''.join([ chr(x) for x in value ]) + return ''.join([chr(x) for x in value]) except ValueError: raise error.PyAsn1Error( - 'Bad OctetString initializer \'%s\'' % (value,) - ) + 'Bad %s initializer \'%s\'' % (self.__class__.__name__, value) + ) else: return str(value) + + def __str__(self): + return str(self._value) + + def __unicode__(self): + try: + return self._value.decode(self._encoding) + + except UnicodeDecodeError: + raise error.PyAsn1Error( + 'Can\'t decode string \'%s\' with \'%s\' codec' % (self._value, self._encoding) + ) + + def asOctets(self): + return str(self._value) + + def asNumbers(self): + if self.__asNumbersCache is None: + self.__asNumbersCache = tuple([ord(x) for x in self._value]) + return self.__asNumbersCache + else: def prettyIn(self, value): if isinstance(value, bytes): return value - elif isinstance(value, OctetString): - return value.asOctets() - elif isinstance(value, (tuple, list, map)): + elif isinstance(value, str): try: - return bytes(value) - except ValueError: - raise error.PyAsn1Error( - 'Bad OctetString initializer \'%s\'' % (value,) - ) - else: - try: - return str(value).encode(self._encoding) + return value.encode(self._encoding) except UnicodeEncodeError: raise error.PyAsn1Error( 'Can\'t encode string \'%s\' with \'%s\' codec' % (value, self._encoding) - ) - - - def fromBinaryString(self, value): - bitNo = 8; byte = 0; r = () - for v in value: - if bitNo: - bitNo = bitNo - 1 - else: - bitNo = 7 - r = r + (byte,) - byte = 0 - if v == '0': - v = 0 - elif v == '1': - v = 1 - else: - raise error.PyAsn1Error( - 'Non-binary OCTET STRING initializer %s' % (v,) ) - byte = byte | (v << bitNo) - return octets.ints2octs(r + (byte,)) - - def fromHexString(self, value): - r = p = () - for v in value: - if p: - r = r + (int(p+v, 16),) - p = () + 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 + return self.prettyIn(str(value)) + elif isinstance(value, (tuple, list)): + return self.prettyIn(bytes(value)) else: - p = v - if p: - r = r + (int(p+'0', 16),) - return octets.ints2octs(r) + return bytes(value) + + def __str__(self): + 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__) + ) + + def __bytes__(self): + return bytes(self._value) + + def asOctets(self): + return bytes(self._value) + + def asNumbers(self): + if self.__asNumbersCache is None: + self.__asNumbersCache = tuple(self._value) + return self.__asNumbersCache def prettyOut(self, value): if sys.version_info[0] <= 2: - numbers = tuple([ ord(x) for x in value ]) + numbers = tuple((ord(x) for x in value)) else: numbers = tuple(value) - if [ x for x in numbers if x < 32 or x > 126 ]: - return '0x' + ''.join([ '%.2x' % x for x in numbers ]) + for x in numbers: + if x < 32 or x > 126: + return '0x' + ''.join(('%.2x' % x for x in numbers)) else: - return str(value) + return octets.octs2str(value) + + @staticmethod + def fromBinaryString(value): + bitNo = 8 + byte = 0 + r = [] + for v in value: + if bitNo: + bitNo -= 1 + else: + bitNo = 7 + r.append(byte) + byte = 0 + if v in ('0', '1'): + v = int(v) + else: + raise error.PyAsn1Error( + 'Non-binary OCTET STRING initializer %s' % (v,) + ) + byte |= v << bitNo + + r.append(byte) + + return octets.ints2octs(r) + + @staticmethod + def fromHexString(value): + r = [] + p = [] + for v in value: + if p: + r.append(int(p + v, 16)) + p = None + else: + p = v + if p: + r.append(int(p + '0', 16)) + + return octets.ints2octs(r) def __repr__(self): - if self._value is base.noValue: - return self.__class__.__name__ + '()' - if [ x for x in self.asNumbers() if x < 32 or x > 126 ]: - return self.__class__.__name__ + '(hexValue=\'' + ''.join([ '%.2x' % x for x in self.asNumbers() ])+'\')' - else: - return self.__class__.__name__ + '(\'' + self.prettyOut(self._value) + '\')' - - if sys.version_info[0] <= 2: - def __str__(self): return str(self._value) - def __unicode__(self): - return self._value.decode(self._encoding, 'ignore') - def asOctets(self): return self._value - def asNumbers(self): - if self.__intValue is None: - self.__intValue = tuple([ ord(x) for x in self._value ]) - return self.__intValue - else: - def __str__(self): return self._value.decode(self._encoding, 'ignore') - def __bytes__(self): return self._value - def asOctets(self): return self._value - def asNumbers(self): - if self.__intValue is None: - self.__intValue = tuple(self._value) - return self.__intValue - + r = [] + doHex = False + if self._value is not self.defaultValue: + for x in self.asNumbers(): + if x < 32 or x > 126: + doHex = True + break + if not doHex: + r.append('%r' % (self._value,)) + if self._tagSet is not self.__class__.tagSet: + r.append('tagSet=%r' % (self._tagSet,)) + if self._subtypeSpec is not self.subtypeSpec: + r.append('subtypeSpec=%r' % (self._subtypeSpec,)) + if self.encoding is not self._encoding: + r.append('encoding=%r' % (self._encoding,)) + if doHex: + r.append('hexValue=%r' % ''.join(['%.2x' % x for x in self.asNumbers()])) + return '%s(%s)' % (self.__class__.__name__, ', '.join(r)) + # Immutable sequence object protocol - + def __len__(self): if self._len is None: self._len = len(self._value) return self._len + def __getitem__(self, i): - if isinstance(i, slice): - return self.clone(operator.getitem(self._value, i)) + if i.__class__ is slice: + return self.clone(self._value[i]) else: return self._value[i] - def __add__(self, value): return self.clone(self._value + self.prettyIn(value)) - def __radd__(self, value): return self.clone(self.prettyIn(value) + self._value) - def __mul__(self, value): return self.clone(self._value * value) - def __rmul__(self, value): return self * value + def __iter__(self): + return iter(self._value) + + def __contains__(self, value): + return value in self._value + + def __add__(self, value): + return self.clone(self._value + self.prettyIn(value)) + + def __radd__(self, value): + return self.clone(self.prettyIn(value) + self._value) + + def __mul__(self, value): + return self.clone(self._value * value) + + def __rmul__(self, value): + return self * value + + def __int__(self): + return int(self._value) + + def __float__(self): + return float(self._value) + + def __reversed__(self): + return reversed(self._value) + class Null(OctetString): + """Create |ASN.1| type or object. + + |ASN.1| objects are immutable and duck-type Python :class:`str` objects (always empty). + + Parameters + ---------- + value : :class:`str` or :py:class:`~pyasn1.type.univ.Null` object + Python empty string literal or *Null* class instance. + + tagSet: :py:class:`~pyasn1.type.tag.TagSet` + Object representing non-default ASN.1 tag(s) + + Raises + ------ + : :py:class:`pyasn1.error.PyAsn1Error` + On constraint violation or bad initializer. + """ defaultValue = ''.encode() # This is tightly constrained - tagSet = baseTagSet = tag.initTagSet( + + #: Set (class attribute) or return (class or instance attribute) a + #: :py:class:`~pyasn1.type.tag.TagSet` object representing ASN.1 tag(s) + #: associated with |ASN.1| type. + tagSet = tag.initTagSet( tag.Tag(tag.tagClassUniversal, tag.tagFormatSimple, 0x05) - ) - subtypeSpec = OctetString.subtypeSpec+constraint.SingleValueConstraint(''.encode()) - + ) + subtypeSpec = OctetString.subtypeSpec + constraint.SingleValueConstraint(octets.str2octs('')) + + # Optimization for faster codec lookup + typeId = OctetString.getTypeId() + + def clone(self, value=noValue, tagSet=None): + """Create a copy of a |ASN.1| type or object. + + Any parameters to the *clone()* method will replace corresponding + properties of the |ASN.1| object. + + Parameters + ---------- + value: :class:`str` or |ASN.1| object + Initialization value to pass to new ASN.1 object instead of + inheriting one from the caller. + + tagSet: :py:class:`~pyasn1.type.tag.TagSet` + Object representing ASN.1 tag(s) to use in new object instead of inheriting from the caller + + Returns + ------- + : :py:class:`~pyasn1.type.univ.Null` + new instance of NULL type/value + """ + return OctetString.clone(self, value, tagSet) + + def subtype(self, value=noValue, implicitTag=None, explicitTag=None): + """Create a copy of a |ASN.1| type or object. + + Any parameters to the *subtype()* method will be added to the corresponding + properties of the |ASN.1| object. + + Parameters + ---------- + value: :class:`int`, :class:`str` or |ASN.1| object + Initialization value to pass to new ASN.1 object instead of + inheriting one from the caller. + + implicitTag: :py:class:`~pyasn1.type.tag.Tag` + Implicitly apply given ASN.1 tag object to caller's + :py:class:`~pyasn1.type.tag.TagSet`, then use the result as + new object's ASN.1 tag(s). + + explicitTag: :py:class:`~pyasn1.type.tag.Tag` + Explicitly apply given ASN.1 tag object to caller's + :py:class:`~pyasn1.type.tag.TagSet`, then use the result as + new object's ASN.1 tag(s). + + Returns + ------- + : :py:class:`~pyasn1.type.univ.Null` + new instance of NULL type/value + """ + return OctetString.subtype(self, value, implicitTag, explicitTag) + + if sys.version_info[0] <= 2: intTypes = (int, long) else: - intTypes = int + intTypes = (int,) + +numericTypes = intTypes + (float,) + class ObjectIdentifier(base.AbstractSimpleAsn1Item): - tagSet = baseTagSet = tag.initTagSet( - tag.Tag(tag.tagClassUniversal, tag.tagFormatSimple, 0x06) - ) - def __add__(self, other): return self.clone(self._value + other) - def __radd__(self, other): return self.clone(other + self._value) + """Create |ASN.1| type or object. + + |ASN.1| objects are immutable and duck-type Python :class:`tuple` objects (tuple of non-negative integers). + + Parameters + ---------- + value: :class:`tuple`, :class:`str` or |ASN.1| object + Python sequence of :class:`int` or string literal or |ASN.1| object. + + tagSet: :py:class:`~pyasn1.type.tag.TagSet` + Object representing non-default ASN.1 tag(s) + + subtypeSpec: :py:class:`~pyasn1.type.constraint.ConstraintsIntersection` + Object representing non-default ASN.1 subtype constraint(s) + + Raises + ------ + : :py:class:`pyasn1.error.PyAsn1Error` + On constraint violation or bad initializer. + """ + #: Set (class attribute) or return (class or instance attribute) a + #: :py:class:`~pyasn1.type.tag.TagSet` object representing ASN.1 tag(s) + #: associated with |ASN.1| type. + tagSet = tag.initTagSet( + tag.Tag(tag.tagClassUniversal, tag.tagFormatSimple, 0x06) + ) + + #: Set (class attribute) or return (class or instance attribute) a + #: :py:class:`~pyasn1.type.constraint.ConstraintsIntersection` object + #: imposing constraints on |ASN.1| type initialization values. + subtypeSpec = constraint.ConstraintsIntersection() + + # Optimization for faster codec lookup + typeId = base.AbstractSimpleAsn1Item.getTypeId() + + def __add__(self, other): + return self.clone(self._value + other) + + def __radd__(self, other): + return self.clone(other + self._value) + + def asTuple(self): + return self._value - def asTuple(self): return self._value - # Sequence object protocol - + def __len__(self): if self._len is None: self._len = len(self._value) return self._len + def __getitem__(self, i): - if isinstance(i, slice): - return self.clone( - operator.getitem(self._value, i) - ) + if i.__class__ is slice: + return self.clone(self._value[i]) else: return self._value[i] - def __str__(self): return self.prettyPrint() - - def index(self, suboid): return self._value.index(suboid) + def __iter__(self): + return iter(self._value) - def isPrefixOf(self, value): - """Returns true if argument OID resides deeper in the OID tree""" + def __contains__(self, value): + return value in self._value + + def __str__(self): + return self.prettyPrint() + + def __repr__(self): + return '%s(%r)' % (self.__class__.__name__, self.prettyPrint()) + + def index(self, suboid): + return self._value.index(suboid) + + def isPrefixOf(self, other): + """Indicate if this |ASN.1| object is a prefix of other |ASN.1| object. + + Parameters + ---------- + other: |ASN.1| object + |ASN.1| object + + 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. + """ l = len(self) - if l <= len(value): - if self._value[:l] == value[:l]: - return 1 - return 0 + if l <= len(other): + if self._value[:l] == other[:l]: + return True + return False def prettyIn(self, value): - """Dotted -> tuple of numerics OID converter""" - if isinstance(value, tuple): - pass - elif isinstance(value, ObjectIdentifier): - return tuple(value) - elif isinstance(value, str): - r = [] - for element in [ x for x in value.split('.') if x != '' ]: - try: - r.append(int(element, 0)) - except ValueError: - raise error.PyAsn1Error( - 'Malformed Object ID %s at %s: %s' % - (str(value), self.__class__.__name__, sys.exc_info()[1]) - ) - value = tuple(r) - else: + if isinstance(value, ObjectIdentifier): + return tuple(value) + elif octets.isStringType(value): + if '-' in value: + raise error.PyAsn1Error( + 'Malformed Object ID %s at %s: %s' % (value, self.__class__.__name__, sys.exc_info()[1]) + ) try: - value = tuple(value) - except TypeError: + return tuple([int(subOid) for subOid in value.split('.') if subOid]) + except ValueError: raise error.PyAsn1Error( - 'Malformed Object ID %s at %s: %s' % - (str(value), self.__class__.__name__,sys.exc_info()[1]) - ) + 'Malformed Object ID %s at %s: %s' % (value, self.__class__.__name__, sys.exc_info()[1]) + ) + + try: + tupleOfInts = tuple([int(subOid) for subOid in value if subOid >= 0]) + + except (ValueError, TypeError): + raise error.PyAsn1Error( + 'Malformed Object ID %s at %s: %s' % (value, self.__class__.__name__, sys.exc_info()[1]) + ) + + if len(tupleOfInts) == len(value): + return tupleOfInts + + raise error.PyAsn1Error('Malformed Object ID %s at %s' % (value, self.__class__.__name__)) + + def prettyOut(self, value): + return '.'.join([str(x) for x in value]) - for x in value: - if not isinstance(x, intTypes) or x < 0: - raise error.PyAsn1Error( - 'Invalid sub-ID in %s at %s' % (value, self.__class__.__name__) - ) - - return value - def prettyOut(self, value): return '.'.join([ str(x) for x in value ]) - class Real(base.AbstractSimpleAsn1Item): + """Create |ASN.1| type or object. + + |ASN.1| 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. + + Parameters + ---------- + 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. + + tagSet: :py:class:`~pyasn1.type.tag.TagSet` + Object representing non-default ASN.1 tag(s) + + subtypeSpec: :py:class:`~pyasn1.type.constraint.ConstraintsIntersection` + Object representing non-default ASN.1 subtype constraint(s) + + Raises + ------ + : :py:class:`pyasn1.error.PyAsn1Error` + On constraint violation or bad initializer. + + """ + binEncBase = None # binEncBase = 16 is recommended for large numbers + try: _plusInf = float('inf') _minusInf = float('-inf') @@ -513,108 +1461,281 @@ class Real(base.AbstractSimpleAsn1Item): _plusInf = _minusInf = None _inf = () - tagSet = baseTagSet = tag.initTagSet( + #: Set (class attribute) or return (class or instance attribute) a + #: :py:class:`~pyasn1.type.tag.TagSet` object representing ASN.1 tag(s) + #: associated with |ASN.1| type. + tagSet = tag.initTagSet( tag.Tag(tag.tagClassUniversal, tag.tagFormatSimple, 0x09) - ) + ) - def __normalizeBase10(self, value): + #: Set (class attribute) or return (class or instance attribute) a + #: :py:class:`~pyasn1.type.constraint.ConstraintsIntersection` object + #: imposing constraints on |ASN.1| type initialization values. + subtypeSpec = constraint.ConstraintsIntersection() + + # Optimization for faster codec lookup + typeId = base.AbstractSimpleAsn1Item.getTypeId() + + def clone(self, value=noValue, tagSet=None, subtypeSpec=None): + """Create a copy of a |ASN.1| type or object. + + Any parameters to the *clone()* method will replace corresponding + properties of the |ASN.1| object. + + Parameters + ---------- + value: :class:`tuple`, :class:`float` or |ASN.1| object + Initialization value to pass to new ASN.1 object instead of + inheriting one from the caller. + + tagSet: :py:class:`~pyasn1.type.tag.TagSet` + Object representing ASN.1 tag(s) to use in new object instead of inheriting from the caller + + subtypeSpec: :py:class:`~pyasn1.type.constraint.ConstraintsIntersection` + Object representing ASN.1 subtype constraint(s) to use in new object instead of inheriting from the caller + + Returns + ------- + : + new instance of |ASN.1| type/value + """ + return base.AbstractSimpleAsn1Item.clone(self, value, tagSet, subtypeSpec) + + def subtype(self, value=noValue, implicitTag=None, explicitTag=None, + subtypeSpec=None): + """Create a copy of a |ASN.1| type or object. + + Any parameters to the *subtype()* method will be added to the corresponding + properties of the |ASN.1| object. + + Parameters + ---------- + value: :class:`tuple`, :class:`float` or |ASN.1| object + Initialization value to pass to new ASN.1 object instead of + inheriting one from the caller. + + implicitTag: :py:class:`~pyasn1.type.tag.Tag` + Implicitly apply given ASN.1 tag object to caller's + :py:class:`~pyasn1.type.tag.TagSet`, then use the result as + new object's ASN.1 tag(s). + + explicitTag: :py:class:`~pyasn1.type.tag.Tag` + Explicitly apply given ASN.1 tag object to caller's + :py:class:`~pyasn1.type.tag.TagSet`, then use the result as + new object's ASN.1 tag(s). + + subtypeSpec: :py:class:`~pyasn1.type.constraint.ConstraintsIntersection` + Object representing ASN.1 subtype constraint(s) to use in new object instead of inheriting from the caller + + Returns + ------- + : + new instance of |ASN.1| type/value + """ + return base.AbstractSimpleAsn1Item.subtype(self, value, implicitTag, explicitTag) + + @staticmethod + def __normalizeBase10(value): m, b, e = value while m and m % 10 == 0: - m = m / 10 - e = e + 1 + m /= 10 + e += 1 return m, b, e def prettyIn(self, value): if isinstance(value, tuple) and len(value) == 3: - for d in value: - if not isinstance(d, intTypes): - raise error.PyAsn1Error( - 'Lame Real value syntax: %s' % (value,) - ) + if not isinstance(value[0], numericTypes) or \ + not isinstance(value[1], intTypes) or \ + not isinstance(value[2], intTypes): + raise error.PyAsn1Error('Lame Real value syntax: %s' % (value,)) + if isinstance(value[0], float) and \ + self._inf and value[0] in self._inf: + return value[0] if value[1] not in (2, 10): raise error.PyAsn1Error( 'Prohibited base for Real value: %s' % (value[1],) - ) + ) if value[1] == 10: value = self.__normalizeBase10(value) return value elif isinstance(value, intTypes): return self.__normalizeBase10((value, 10, 0)) - elif isinstance(value, float): + elif isinstance(value, float) or octets.isStringType(value): + if octets.isStringType(value): + try: + value = float(value) + except ValueError: + raise error.PyAsn1Error( + 'Bad real value syntax: %s' % (value,) + ) if self._inf and value in self._inf: return value else: e = 0 while int(value) != value: - value = value * 10 - e = e - 1 + value *= 10 + e -= 1 return self.__normalizeBase10((int(value), 10, e)) elif isinstance(value, Real): return tuple(value) - elif isinstance(value, str): # handle infinite literal - try: - return float(value) - except ValueError: - pass raise error.PyAsn1Error( 'Bad real value syntax: %s' % (value,) - ) - + ) + def prettyOut(self, value): if value in self._inf: return '\'%s\'' % value else: return str(value) - def isPlusInfinity(self): return self._value == self._plusInf - def isMinusInfinity(self): return self._value == self._minusInf - def isInfinity(self): return self._value in self._inf - - def __str__(self): return str(float(self)) - - def __add__(self, value): return self.clone(float(self) + value) - def __radd__(self, value): return self + value - def __mul__(self, value): return self.clone(float(self) * value) - def __rmul__(self, value): return self * value - def __sub__(self, value): return self.clone(float(self) - value) - def __rsub__(self, value): return self.clone(value - float(self)) - def __mod__(self, value): return self.clone(float(self) % value) - def __rmod__(self, value): return self.clone(value % float(self)) - def __pow__(self, value, modulo=None): return self.clone(pow(float(self), value, modulo)) - def __rpow__(self, value): return self.clone(pow(value, float(self))) + def prettyPrint(self, scope=0): + if self.isInfinity(): + return self.prettyOut(self._value) + else: + return str(float(self)) + + def isPlusInfinity(self): + """Indicate PLUS-INFINITY object value + + Returns + ------- + : :class:`bool` + :class:`True` if calling object represents plus infinity + or :class:`False` otherwise. + + """ + return self._value == self._plusInf + + def isMinusInfinity(self): + """Indicate MINUS-INFINITY object value + + Returns + ------- + : :class:`bool` + :class:`True` if calling object represents minus infinity + or :class:`False` otherwise. + """ + return self._value == self._minusInf + + def isInfinity(self): + return self._value in self._inf + + def __str__(self): + return str(float(self)) + + def __add__(self, value): + return self.clone(float(self) + value) + + def __radd__(self, value): + return self + value + + def __mul__(self, value): + return self.clone(float(self) * value) + + def __rmul__(self, value): + return self * value + + def __sub__(self, value): + return self.clone(float(self) - value) + + def __rsub__(self, value): + return self.clone(value - float(self)) + + def __mod__(self, value): + return self.clone(float(self) % value) + + def __rmod__(self, value): + return self.clone(value % float(self)) + + def __pow__(self, value, modulo=None): + return self.clone(pow(float(self), value, modulo)) + + def __rpow__(self, value): + return self.clone(pow(value, float(self))) if sys.version_info[0] <= 2: - def __div__(self, value): return self.clone(float(self) / value) - def __rdiv__(self, value): return self.clone(value / float(self)) - else: - def __truediv__(self, value): return self.clone(float(self) / value) - def __rtruediv__(self, value): return self.clone(value / float(self)) - def __divmod__(self, value): return self.clone(float(self) // value) - def __rdivmod__(self, value): return self.clone(value // float(self)) + def __div__(self, value): + return self.clone(float(self) / value) + + def __rdiv__(self, value): + return self.clone(value / float(self)) + else: + def __truediv__(self, value): + return self.clone(float(self) / value) + + def __rtruediv__(self, value): + return self.clone(value / float(self)) + + def __divmod__(self, value): + return self.clone(float(self) // value) + + def __rdivmod__(self, value): + return self.clone(value // float(self)) + + def __int__(self): + return int(float(self)) - def __int__(self): return int(float(self)) if sys.version_info[0] <= 2: def __long__(self): return long(float(self)) + def __float__(self): if self._value in self._inf: return self._value else: return float( self._value[0] * pow(self._value[1], self._value[2]) - ) - def __abs__(self): return abs(float(self)) + ) - def __lt__(self, value): return float(self) < value - def __le__(self, value): return float(self) <= value - def __eq__(self, value): return float(self) == value - def __ne__(self, value): return float(self) != value - def __gt__(self, value): return float(self) > value - def __ge__(self, value): return float(self) >= value + def __abs__(self): + return self.clone(abs(float(self))) + + def __pos__(self): + return self.clone(+float(self)) + + def __neg__(self): + return self.clone(-float(self)) + + def __round__(self, n=0): + r = round(float(self), n) + if n: + return self.clone(r) + else: + return r + + def __floor__(self): + return self.clone(math.floor(float(self))) + + def __ceil__(self): + return self.clone(math.ceil(float(self))) + + if sys.version_info[0:2] > (2, 5): + def __trunc__(self): return self.clone(math.trunc(float(self))) + + def __lt__(self, value): + return float(self) < value + + def __le__(self, value): + return float(self) <= value + + def __eq__(self, value): + return float(self) == value + + def __ne__(self, value): + return float(self) != value + + def __gt__(self, value): + return float(self) > value + + def __ge__(self, value): + return float(self) >= value if sys.version_info[0] <= 2: - def __nonzero__(self): return bool(float(self)) + def __nonzero__(self): + return bool(float(self)) else: - def __bool__(self): return bool(float(self)) + def __bool__(self): + return bool(float(self)) + __hash__ = base.AbstractSimpleAsn1Item.__hash__ def __getitem__(self, idx): @@ -622,273 +1743,780 @@ class Real(base.AbstractSimpleAsn1Item): raise error.PyAsn1Error('Invalid infinite value operation') else: return self._value[idx] - + + class Enumerated(Integer): - tagSet = baseTagSet = tag.initTagSet( + __doc__ = Integer.__doc__ + + #: Set (class attribute) or return (class or instance attribute) a + #: :py:class:`~pyasn1.type.tag.TagSet` object representing ASN.1 tag(s) + #: associated with |ASN.1| type. + tagSet = tag.initTagSet( tag.Tag(tag.tagClassUniversal, tag.tagFormatSimple, 0x0A) - ) + ) + + #: Set (class attribute) or return (class or instance attribute) a + #: :py:class:`~pyasn1.type.constraint.ConstraintsIntersection` object + #: imposing constraints on |ASN.1| type initialization values. + subtypeSpec = constraint.ConstraintsIntersection() + + # Optimization for faster codec lookup + typeId = Integer.getTypeId() + + #: Default :py:class:`~pyasn1.type.namedval.NamedValues` object + #: representing symbolic aliases for numbers + namedValues = namedval.NamedValues() + # "Structured" ASN.1 types -class SetOf(base.AbstractConstructedAsn1Item): - componentType = None - tagSet = baseTagSet = tag.initTagSet( - tag.Tag(tag.tagClassUniversal, tag.tagFormatConstructed, 0x11) - ) - typeId = 1 +class SequenceOfAndSetOfBase(base.AbstractConstructedAsn1Item): + """Create |ASN.1| type. + + |ASN.1| objects are mutable and duck-type Python :class:`list` objects. + + Parameters + ---------- + componentType : :py:class:`~pyasn1.type.base.PyAsn1Item` derivative + A pyasn1 object representing ASN.1 type allowed within |ASN.1| type + + tagSet: :py:class:`~pyasn1.type.tag.TagSet` + Object representing non-default ASN.1 tag(s) + + subtypeSpec: :py:class:`~pyasn1.type.constraint.ConstraintsIntersection` + Object representing non-default ASN.1 subtype constraint(s) + + sizeSpec: :py:class:`~pyasn1.type.constraint.ConstraintsIntersection` + Object representing collection size constraint + """ + + # Python list protocol + + def clear(self): + self._componentValues = [] + + def append(self, value): + self[len(self)] = value + + def count(self, value): + return self._componentValues.count(value) + + def extend(self, values): + for value in values: + self.append(value) + + def index(self, value, start=0, stop=None): + if stop is None: + stop = len(self) + return self._componentValues.index(value, start, stop) + + def reverse(self): + self._componentValues.reverse() + + def sort(self, key=None, reverse=False): + self._componentValues.sort(key=key, reverse=reverse) + + def __iter__(self): + return iter(self._componentValues) def _cloneComponentValues(self, myClone, cloneValueFlag): - idx = 0; l = len(self._componentValues) - while idx < l: - c = self._componentValues[idx] - if c is not None: - if isinstance(c, base.AbstractConstructedAsn1Item): + for idx, componentValue in enumerate(self._componentValues): + if componentValue is not None: + if isinstance(componentValue, base.AbstractConstructedAsn1Item): myClone.setComponentByPosition( - idx, c.clone(cloneValueFlag=cloneValueFlag) - ) + idx, componentValue.clone(cloneValueFlag=cloneValueFlag) + ) else: - myClone.setComponentByPosition(idx, c.clone()) - idx = idx + 1 - - def _verifyComponent(self, idx, value): - if self._componentType is not None and \ - not self._componentType.isSuperTypeOf(value): - raise error.PyAsn1Error('Component type error %s' % (value,)) + myClone.setComponentByPosition(idx, componentValue.clone()) - def getComponentByPosition(self, idx): return self._componentValues[idx] - def setComponentByPosition(self, idx, value=None, verifyConstraints=True): - l = len(self._componentValues) - if idx >= l: - self._componentValues = self._componentValues + (idx-l+1)*[None] - if value is None: - if self._componentValues[idx] is None: - if self._componentType is None: - raise error.PyAsn1Error('Component type not defined') - self._componentValues[idx] = self._componentType.clone() - self._componentValuesSet = self._componentValuesSet + 1 - return self - elif not isinstance(value, base.Asn1Item): - if self._componentType is None: + def getComponentByPosition(self, idx): + """Return |ASN.1| type component value by position. + + Equivalent to Python sequence subscription operation (e.g. `[]`). + + Parameters + ---------- + idx : :class:`int` + Component index (zero-based). Must either refer to an existing + component or to N+1 component (of *componentType is set). In the latter + case a new component type gets instantiated and appended to the |ASN.1| + sequence. + + Returns + ------- + : :py:class:`~pyasn1.type.base.PyAsn1Item` + a pyasn1 object + """ + try: + return self._componentValues[idx] + except IndexError: + self.setComponentByPosition(idx) + return self._componentValues[idx] + + def setComponentByPosition(self, idx, value=noValue, + verifyConstraints=True, + matchTags=True, + matchConstraints=True): + """Assign |ASN.1| type component by position. + + Equivalent to Python sequence item assignment operation (e.g. `[]`) + or list.append() (when idx == len(self)). + + Parameters + ---------- + idx: :class:`int` + Component index (zero-based). Must either refer to existing + component or to N+1 component. In the latter case a new component + type gets instantiated (if *componentType* is set, or given ASN.1 + object is taken otherwise) and appended to the |ASN.1| sequence. + + 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. + + verifyConstraints: :class:`bool` + If `False`, skip constraints validation + + matchTags: :class:`bool` + If `False`, skip component tags matching + + matchConstraints: :class:`bool` + If `False`, skip component constraints matching + + Returns + ------- + self + + Raises + ------ + IndexError: + When idx > len(self) + """ + componentType = self._componentType + + try: + currentValue = self._componentValues[idx] + except IndexError: + currentValue = None + + if len(self._componentValues) < idx: + raise error.PyAsn1Error('Component index out of range') + + if value is None or value is noValue: + if componentType is not None: + value = componentType.clone() + elif currentValue is None: raise error.PyAsn1Error('Component type not defined') - if isinstance(self._componentType, base.AbstractSimpleAsn1Item): - value = self._componentType.clone(value=value) + elif not isinstance(value, base.Asn1Item): + if componentType is not None and isinstance(componentType, base.AbstractSimpleAsn1Item): + value = componentType.clone(value=value) + elif currentValue is not None and isinstance(currentValue, base.AbstractSimpleAsn1Item): + value = currentValue.clone(value=value) else: - raise error.PyAsn1Error('Instance value required') - if verifyConstraints: - if self._componentType is not None: - self._verifyComponent(idx, value) - self._verifySubtypeSpec(value, idx) - if self._componentValues[idx] is None: - self._componentValuesSet = self._componentValuesSet + 1 - self._componentValues[idx] = value + raise error.PyAsn1Error('%s undefined component type' % componentType.__class__.__name__) + 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)) + else: + 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: + self._subtypeSpec(value, idx) + + except error.PyAsn1Error: + exType, exValue, exTb = sys.exc_info() + raise exType('%s at %s' % (exValue, self.__class__.__name__)) + + if currentValue is None: + self._componentValues.append(value) + else: + self._componentValues[idx] = value + return self - def getComponentTagMap(self): + @property + def componentTagMap(self): if self._componentType is not None: - return self._componentType.getTagMap() + return self._componentType.tagMap def prettyPrint(self, scope=0): - scope = scope + 1 - r = self.__class__.__name__ + ':\n' + scope += 1 + representation = self.__class__.__name__ + ':\n' for idx in range(len(self._componentValues)): - r = r + ' '*scope + representation += ' ' * scope if self._componentValues[idx] is None: - r = r + '' + representation += '' else: - r = r + self._componentValues[idx].prettyPrint(scope) - return r + representation += self._componentValues[idx].prettyPrint(scope) + return representation -class SequenceOf(SetOf): - tagSet = baseTagSet = tag.initTagSet( + def prettyPrintType(self, scope=0): + scope += 1 + representation = '%s -> %s {\n' % (self.tagSet, self.__class__.__name__) + if self._componentType is not None: + representation += ' ' * scope + representation += self._componentType.prettyPrintType(scope) + return representation + '\n' + ' ' * (scope - 1) + '}' + + + @property + def isValue(self): + """Indicate if |ASN.1| object components represent ASN.1 type or ASN.1 value. + + The PyASN1 type objects can only participate in types comparison + and serve as a blueprint for serialization codecs to resolve + ambiguous types. + + The PyASN1 value objects can additionally participate in most + of built-in Python operations. + + Returns + ------- + : :class:`bool` + :class:`True` if all |ASN.1| components represent value and type, + :class:`False` if at least one |ASN.1| component represents just ASN.1 type. + """ + if not self._componentValues: + return False + + for componentValue in self._componentValues: + if not componentValue.isValue: + return False + + return True + + +class SequenceOf(SequenceOfAndSetOfBase): + __doc__ = SequenceOfAndSetOfBase.__doc__ + + #: Set (class attribute) or return (class or instance attribute) a + #: :py:class:`~pyasn1.type.tag.TagSet` object representing ASN.1 tag(s) + #: associated with |ASN.1| type. + tagSet = tag.initTagSet( tag.Tag(tag.tagClassUniversal, tag.tagFormatConstructed, 0x10) - ) - typeId = 2 + ) + + #: Default :py:class:`~pyasn1.type.base.PyAsn1Item` derivative + #: object representing ASN.1 type allowed within |ASN.1| type + componentType = None + + #: Set (class attribute) or return (class or instance attribute) a + #: :py:class:`~pyasn1.type.constraint.ConstraintsIntersection` object + #: imposing constraints on |ASN.1| type initialization values. + subtypeSpec = constraint.ConstraintsIntersection() + + #: Default :py:class:`~pyasn1.type.constraint.ConstraintsIntersection` + #: object imposing size constraint on |ASN.1| objects + sizeSpec = constraint.ConstraintsIntersection() + + # Disambiguation ASN.1 types identification + typeId = SequenceOfAndSetOfBase.getTypeId() + + +class SetOf(SequenceOfAndSetOfBase): + __doc__ = SequenceOfAndSetOfBase.__doc__ + + #: Set (class attribute) or return (class or instance attribute) a + #: :py:class:`~pyasn1.type.tag.TagSet` object representing ASN.1 tag(s) + #: associated with |ASN.1| type. + tagSet = tag.initTagSet( + tag.Tag(tag.tagClassUniversal, tag.tagFormatConstructed, 0x11) + ) + + #: Default :py:class:`~pyasn1.type.base.PyAsn1Item` derivative + #: object representing ASN.1 type allowed within |ASN.1| type + componentType = None + + #: Set (class attribute) or return (class or instance attribute) a + #: :py:class:`~pyasn1.type.constraint.ConstraintsIntersection` object + #: imposing constraints on |ASN.1| type initialization values. + subtypeSpec = constraint.ConstraintsIntersection() + + #: Default :py:class:`~pyasn1.type.constraint.ConstraintsIntersection` + #: object imposing size constraint on |ASN.1| objects + sizeSpec = constraint.ConstraintsIntersection() + + # Disambiguation ASN.1 types identification + typeId = SequenceOfAndSetOfBase.getTypeId() + class SequenceAndSetBase(base.AbstractConstructedAsn1Item): + """Create |ASN.1| type. + + |ASN.1| objects are mutable and duck-type Python :class:`dict` objects. + + Parameters + ---------- + componentType: :py:class:`~pyasn1.type.namedtype.NamedType` + Object holding named ASN.1 types allowed within this collection + + tagSet: :py:class:`~pyasn1.type.tag.TagSet` + Object representing non-default ASN.1 tag(s) + + subtypeSpec: :py:class:`~pyasn1.type.constraint.ConstraintsIntersection` + Object representing non-default ASN.1 subtype constraint(s) + + sizeSpec: :py:class:`~pyasn1.type.constraint.ConstraintsIntersection` + Object representing collection size constraint + """ + #: Default :py:class:`~pyasn1.type.namedtype.NamedTypes` + #: object representing named ASN.1 types allowed within |ASN.1| type componentType = namedtype.NamedTypes() + def __init__(self, componentType=None, tagSet=None, subtypeSpec=None, sizeSpec=None): + if componentType is None: + componentType = self.componentType base.AbstractConstructedAsn1Item.__init__( self, componentType, tagSet, subtypeSpec, sizeSpec - ) - if self._componentType is None: - self._componentTypeLen = 0 - else: - self._componentTypeLen = len(self._componentType) + ) + self._componentTypeLen = len(self._componentType) def __getitem__(self, idx): - if isinstance(idx, str): + if octets.isStringType(idx): return self.getComponentByName(idx) else: return base.AbstractConstructedAsn1Item.__getitem__(self, idx) def __setitem__(self, idx, value): - if isinstance(idx, str): + if octets.isStringType(idx): self.setComponentByName(idx, value) else: base.AbstractConstructedAsn1Item.__setitem__(self, idx, value) - - def _cloneComponentValues(self, myClone, cloneValueFlag): - idx = 0; l = len(self._componentValues) - while idx < l: - c = self._componentValues[idx] - if c is not None: - if isinstance(c, base.AbstractConstructedAsn1Item): - myClone.setComponentByPosition( - idx, c.clone(cloneValueFlag=cloneValueFlag) - ) - else: - myClone.setComponentByPosition(idx, c.clone()) - idx = idx + 1 - def _verifyComponent(self, idx, value): - if idx >= self._componentTypeLen: - raise error.PyAsn1Error( - 'Component type error out of range' - ) - t = self._componentType[idx].getType() - if not t.isSuperTypeOf(value): - raise error.PyAsn1Error('Component type error %r vs %r' % (t, value)) + def __contains__(self, key): + return key in self._componentType + + def __iter__(self): + return iter(self._componentType) + + # Python dict protocol + + def values(self): + for idx in range(self._componentTypeLen): + yield self[idx] + + def keys(self): + return iter(self._componentType) + + def items(self): + for idx in range(self._componentTypeLen): + yield self._componentType[idx].getName(), self[idx] + + def update(self, *iterValue, **mappingValue): + for k, v in iterValue: + self[k] = v + for k in mappingValue: + self[k] = mappingValue[k] + + def clear(self): + self._componentValues = [] + + def _cloneComponentValues(self, myClone, cloneValueFlag): + for idx, componentValue in enumerate(self._componentValues): + if componentValue is not None: + if isinstance(componentValue, base.AbstractConstructedAsn1Item): + myClone.setComponentByPosition( + idx, componentValue.clone(cloneValueFlag=cloneValueFlag) + ) + else: + myClone.setComponentByPosition(idx, componentValue.clone()) def getComponentByName(self, name): + """Returns |ASN.1| type component by name. + + Equivalent to Python :class:`dict` subscription operation (e.g. `[]`). + + Parameters + ---------- + name : :class:`str` + |ASN.1| type component name + + Returns + ------- + : :py:class:`~pyasn1.type.base.PyAsn1Item` + Instantiate |ASN.1| component type or return existing component value + """ return self.getComponentByPosition( self._componentType.getPositionByName(name) - ) - def setComponentByName(self, name, value=None, verifyConstraints=True): + ) + + def setComponentByName(self, name, value=noValue, + verifyConstraints=True, + matchTags=True, + matchConstraints=True): + """Assign |ASN.1| type component by name. + + Equivalent to Python :class:`dict` item assignment operation (e.g. `[]`). + + Parameters + ---------- + name: :class:`str` + |ASN.1| type component name + + 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. + + verifyConstraints: :class:`bool` + If `False`, skip constraints validation + + matchTags: :class:`bool` + If `False`, skip component tags matching + + matchConstraints: :class:`bool` + If `False`, skip component constraints matching + + Returns + ------- + self + """ return self.setComponentByPosition( - self._componentType.getPositionByName(name), value, - verifyConstraints - ) + self._componentType.getPositionByName(name), value, verifyConstraints, matchTags, matchConstraints + ) def getComponentByPosition(self, idx): + """Returns |ASN.1| type component by index. + + Equivalent to Python sequence subscription operation (e.g. `[]`). + + Parameters + ---------- + idx : :class:`int` + Component index (zero-based). Must either refer to an existing + component or (if *componentType* is set) new ASN.1 type object gets + instantiated. + + Returns + ------- + : :py:class:`~pyasn1.type.base.PyAsn1Item` + a PyASN1 object + """ try: - return self._componentValues[idx] + componentValue = self._componentValues[idx] except IndexError: - if idx < self._componentTypeLen: - return - raise - def setComponentByPosition(self, idx, value=None, verifyConstraints=True): - l = len(self._componentValues) - if idx >= l: - self._componentValues = self._componentValues + (idx-l+1)*[None] - if value is None: - if self._componentValues[idx] is None: - self._componentValues[idx] = self._componentType.getTypeByPosition(idx).clone() - self._componentValuesSet = self._componentValuesSet + 1 - return self + componentValue = None + + if componentValue is None: + self.setComponentByPosition(idx) + + return self._componentValues[idx] + + def setComponentByPosition(self, idx, value=noValue, + verifyConstraints=True, + matchTags=True, + matchConstraints=True): + """Assign |ASN.1| type component by position. + + Equivalent to Python sequence item assignment operation (e.g. `[]`). + + Parameters + ---------- + idx : :class:`int` + Component index (zero-based). Must either refer to existing + component (if *componentType* is set) or to N+1 component + otherwise. In the latter case a new component of given ASN.1 + type gets instantiated and appended to |ASN.1| sequence. + + 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. + + verifyConstraints : :class:`bool` + If `False`, skip constraints validation + + matchTags: :class:`bool` + If `False`, skip component tags matching + + matchConstraints: :class:`bool` + If `False`, skip component constraints matching + + Returns + ------- + self + """ + componentType = self._componentType + componentTypeLen = self._componentTypeLen + + try: + currentValue = self._componentValues[idx] + except IndexError: + currentValue = None + if componentTypeLen: + if componentTypeLen < idx: + raise IndexError('component index out of range') + self._componentValues = [None] * componentTypeLen + + if value is None or value is noValue: + if componentTypeLen: + value = componentType.getTypeByPosition(idx).clone() + elif currentValue is None: + raise error.PyAsn1Error('Component type not defined') elif not isinstance(value, base.Asn1Item): - t = self._componentType.getTypeByPosition(idx) - if isinstance(t, base.AbstractSimpleAsn1Item): - value = t.clone(value=value) + if componentTypeLen: + subComponentType = componentType.getTypeByPosition(idx) + if isinstance(subComponentType, base.AbstractSimpleAsn1Item): + value = subComponentType.clone(value=value) + else: + raise error.PyAsn1Error('%s can cast only scalar values' % componentType.__class__.__name__) + elif currentValue is not None and isinstance(currentValue, base.AbstractSimpleAsn1Item): + value = currentValue.clone(value=value) else: - raise error.PyAsn1Error('Instance value required') - if verifyConstraints: - if self._componentTypeLen: - self._verifyComponent(idx, value) - self._verifySubtypeSpec(value, idx) - if self._componentValues[idx] is None: - self._componentValuesSet = self._componentValuesSet + 1 - self._componentValues[idx] = value + raise error.PyAsn1Error('%s undefined component type' % componentType.__class__.__name__) + elif (matchTags or matchConstraints) and componentTypeLen: + subComponentType = componentType.getTypeByPosition(idx) + if subComponentType is not None: + if self.strictConstraints: + if not subComponentType.isSameTypeWith(value, matchTags, matchConstraints): + raise error.PyAsn1Error('Component value is tag-incompatible: %r vs %r' % (value, componentType)) + else: + if not subComponentType.isSuperTypeOf(value, matchTags, matchConstraints): + raise error.PyAsn1Error('Component value is tag-incompatible: %r vs %r' % (value, componentType)) + + if verifyConstraints and value.isValue: + try: + self._subtypeSpec(value, idx) + + except error.PyAsn1Error: + exType, exValue, exTb = sys.exc_info() + raise exType('%s at %s' % (exValue, self.__class__.__name__)) + + if componentTypeLen: + self._componentValues[idx] = value + elif len(self._componentValues) == idx: + self._componentValues.append(value) + else: + raise error.PyAsn1Error('Component index out of range') + return self def getNameByPosition(self, idx): if self._componentTypeLen: return self._componentType.getNameByPosition(idx) - def getDefaultComponentByPosition(self, idx): - if self._componentTypeLen and self._componentType[idx].isDefaulted: - return self._componentType[idx].getType() - def getComponentType(self): if self._componentTypeLen: return self._componentType - - def setDefaultComponents(self): - if self._componentTypeLen == self._componentValuesSet: - return - idx = self._componentTypeLen - while idx: - idx = idx - 1 - if self._componentType[idx].isDefaulted: - if self.getComponentByPosition(idx) is None: - self.setComponentByPosition(idx) - elif not self._componentType[idx].isOptional: - if self.getComponentByPosition(idx) is None: - raise error.PyAsn1Error( - 'Uninitialized component #%s at %r' % (idx, self) - ) + + @property + def isValue(self): + """Indicate if |ASN.1| object components represent ASN.1 type or ASN.1 value. + + The PyASN1 type objects can only participate in types comparison + and serve as a blueprint for serialization codecs to resolve + ambiguous types. + + The PyASN1 value objects can additionally participate in most + of built-in Python operations. + + Returns + ------- + : :class:`bool` + :class:`True` if all |ASN.1| components represent value and type, + :class:`False` if at least one |ASN.1| component represents just ASN.1 type. + """ + componentType = self._componentType + + if componentType: + for idx, subComponentType in enumerate(componentType.namedTypes): + if subComponentType.isDefaulted or subComponentType.isOptional: + continue + if not self._componentValues or self._componentValues[idx] is None or not self._componentValues[idx].isValue: + return False + + else: + for componentValue in self._componentValues: + if not componentValue.isValue: + return False + + return True def prettyPrint(self, scope=0): - scope = scope + 1 - r = self.__class__.__name__ + ':\n' + """Return an object representation string. + + Returns + ------- + : :class:`str` + Human-friendly object representation. + """ + scope += 1 + representation = self.__class__.__name__ + ':\n' for idx in range(len(self._componentValues)): if self._componentValues[idx] is not None: - r = r + ' '*scope + representation += ' ' * scope componentType = self.getComponentType() if componentType is None: - r = r + '' + representation += '' else: - r = r + componentType.getNameByPosition(idx) - r = '%s=%s\n' % ( - r, self._componentValues[idx].prettyPrint(scope) - ) - return r + representation += componentType.getNameByPosition(idx) + representation = '%s=%s\n' % ( + representation, self._componentValues[idx].prettyPrint(scope) + ) + return representation + + def prettyPrintType(self, scope=0): + scope += 1 + representation = '%s -> %s {\n' % (self.tagSet, self.__class__.__name__) + for idx in range(len(self.componentType)): + representation += ' ' * scope + representation += '"%s"' % self.componentType.getNameByPosition(idx) + representation = '%s = %s\n' % ( + representation, self._componentType.getTypeByPosition(idx).prettyPrintType(scope) + ) + return representation + '\n' + ' ' * (scope - 1) + '}' + + # backward compatibility -- no-op + def setDefaultComponents(self): + return self + class Sequence(SequenceAndSetBase): - tagSet = baseTagSet = tag.initTagSet( + __doc__ = SequenceAndSetBase.__doc__ + + #: Set (class attribute) or return (class or instance attribute) a + #: :py:class:`~pyasn1.type.tag.TagSet` object representing ASN.1 tag(s) + #: associated with |ASN.1| type. + tagSet = tag.initTagSet( tag.Tag(tag.tagClassUniversal, tag.tagFormatConstructed, 0x10) - ) - typeId = 3 + ) + + #: Set (class attribute) or return (class or instance attribute) a + #: :py:class:`~pyasn1.type.constraint.ConstraintsIntersection` object + #: imposing constraints on |ASN.1| type initialization values. + subtypeSpec = constraint.ConstraintsIntersection() + + #: Default :py:class:`~pyasn1.type.constraint.ConstraintsIntersection` + #: object imposing constraints on |ASN.1| objects + sizeSpec = constraint.ConstraintsIntersection() + + #: Default collection of ASN.1 types of component (e.g. :py:class:`~pyasn1.type.namedtype.NamedType`) + #: object imposing size constraint on |ASN.1| objects + componentType = namedtype.NamedTypes() + + # Disambiguation ASN.1 types identification + typeId = SequenceAndSetBase.getTypeId() def getComponentTagMapNearPosition(self, idx): if self._componentType: return self._componentType.getTagMapNearPosition(idx) - + def getComponentPositionNearType(self, tagSet, idx): if self._componentType: return self._componentType.getPositionNearType(tagSet, idx) else: return idx - -class Set(SequenceAndSetBase): - tagSet = baseTagSet = tag.initTagSet( - tag.Tag(tag.tagClassUniversal, tag.tagFormatConstructed, 0x11) - ) - typeId = 4 - def getComponent(self, innerFlag=0): return self - - def getComponentByType(self, tagSet, innerFlag=0): - c = self.getComponentByPosition( + +class Set(SequenceAndSetBase): + __doc__ = SequenceAndSetBase.__doc__ + + #: Set (class attribute) or return (class or instance attribute) a + #: :py:class:`~pyasn1.type.tag.TagSet` object representing ASN.1 tag(s) + #: associated with |ASN.1| type. + tagSet = tag.initTagSet( + tag.Tag(tag.tagClassUniversal, tag.tagFormatConstructed, 0x11) + ) + + #: Default collection of ASN.1 types of component (e.g. :py:class:`~pyasn1.type.namedtype.NamedType`) + #: object representing ASN.1 type allowed within |ASN.1| type + componentType = namedtype.NamedTypes() + + #: Set (class attribute) or return (class or instance attribute) a + #: :py:class:`~pyasn1.type.constraint.ConstraintsIntersection` object + #: imposing constraints on |ASN.1| type initialization values. + subtypeSpec = constraint.ConstraintsIntersection() + + #: Default :py:class:`~pyasn1.type.constraint.ConstraintsIntersection` + #: object imposing constraints on |ASN.1| objects + sizeSpec = constraint.ConstraintsIntersection() + + # Disambiguation ASN.1 types identification + typeId = SequenceAndSetBase.getTypeId() + + def getComponent(self, innerFlag=False): + return self + + def getComponentByType(self, tagSet, innerFlag=False): + """Returns |ASN.1| type component by ASN.1 tag. + + Parameters + ---------- + tagSet : :py:class:`~pyasn1.type.tag.TagSet` + Object representing ASN.1 tags to identify one of + |ASN.1| object component + + Returns + ------- + : :py:class:`~pyasn1.type.base.PyAsn1Item` + a pyasn1 object + """ + component = self.getComponentByPosition( self._componentType.getPositionByType(tagSet) - ) - if innerFlag and isinstance(c, Set): + ) + if innerFlag and isinstance(component, Set): # get inner component by inner tagSet - return c.getComponent(1) + return component.getComponent(innerFlag=True) else: # get outer component by inner tagSet - return c - - def setComponentByType(self, tagSet, value=None, innerFlag=0, - verifyConstraints=True): + return component + + def setComponentByType(self, tagSet, value=noValue, + verifyConstraints=True, + matchTags=True, + matchConstraints=True, + innerFlag=False): + """Assign |ASN.1| type component by ASN.1 tag. + + Parameters + ---------- + tagSet : :py:class:`~pyasn1.type.tag.TagSet` + Object representing ASN.1 tags to identify one of + |ASN.1| object component + + 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. + + verifyConstraints : :class:`bool` + If `False`, skip constraints validation + + matchTags: :class:`bool` + If `False`, skip component tags matching + + matchConstraints: :class:`bool` + If `False`, skip component constraints matching + + innerFlag: :class:`bool` + If `True`, search for matching *tagSet* recursively. + + Returns + ------- + self + """ idx = self._componentType.getPositionByType(tagSet) - t = self._componentType.getTypeByPosition(idx) + if innerFlag: # set inner component by inner tagSet - if t.getTagSet(): + componentType = self._componentType.getTypeByPosition(idx) + + if componentType.tagSet: return self.setComponentByPosition( - idx, value, verifyConstraints - ) + idx, value, verifyConstraints, matchTags, matchConstraints + ) else: - t = self.setComponentByPosition(idx).getComponentByPosition(idx) - return t.setComponentByType( - tagSet, value, innerFlag, verifyConstraints - ) + componentType = self.getComponentByPosition(idx) + return componentType.setComponentByType( + tagSet, value, verifyConstraints, matchTags, matchConstraints, innerFlag=innerFlag + ) else: # set outer component by inner tagSet return self.setComponentByPosition( - idx, value, verifyConstraints - ) - - def getComponentTagMap(self): + idx, value, verifyConstraints, matchTags, matchConstraints + ) + + @property + def componentTagMap(self): if self._componentType: return self._componentType.getTagMap(True) @@ -896,116 +2524,202 @@ class Set(SequenceAndSetBase): if self._componentType: return self._componentType.getPositionByType(tagSet) + class Choice(Set): - tagSet = baseTagSet = tag.TagSet() # untagged + __doc__ = Set.__doc__ + + #: Set (class attribute) or return (class or instance attribute) a + #: :py:class:`~pyasn1.type.tag.TagSet` object representing ASN.1 tag(s) + #: associated with |ASN.1| type. + tagSet = tag.TagSet() # untagged + + #: Default collection of ASN.1 types of component (e.g. :py:class:`~pyasn1.type.namedtype.NamedType`) + #: object representing ASN.1 type allowed within |ASN.1| type + componentType = namedtype.NamedTypes() + + #: Set (class attribute) or return (class or instance attribute) a + #: :py:class:`~pyasn1.type.constraint.ConstraintsIntersection` object + #: imposing constraints on |ASN.1| type initialization values. + subtypeSpec = constraint.ConstraintsIntersection() + + #: Default :py:class:`~pyasn1.type.constraint.ConstraintsIntersection` + #: object imposing size constraint on |ASN.1| objects sizeSpec = constraint.ConstraintsIntersection( constraint.ValueSizeConstraint(1, 1) - ) - typeId = 5 + ) + + # Disambiguation ASN.1 types identification + typeId = Set.getTypeId() + _currentIdx = None def __eq__(self, other): if self._componentValues: return self._componentValues[self._currentIdx] == other return NotImplemented + def __ne__(self, other): if self._componentValues: return self._componentValues[self._currentIdx] != other return NotImplemented + def __lt__(self, other): if self._componentValues: return self._componentValues[self._currentIdx] < other return NotImplemented + def __le__(self, other): if self._componentValues: return self._componentValues[self._currentIdx] <= other return NotImplemented + def __gt__(self, other): if self._componentValues: return self._componentValues[self._currentIdx] > other return NotImplemented + def __ge__(self, other): if self._componentValues: return self._componentValues[self._currentIdx] >= other return NotImplemented - if sys.version_info[0] <= 2: - def __nonzero__(self): return bool(self._componentValues) - else: - def __bool__(self): return bool(self._componentValues) - def __len__(self): return self._currentIdx is not None and 1 or 0 - + if sys.version_info[0] <= 2: + def __nonzero__(self): + return self._componentValues and True or False + else: + def __bool__(self): + return self._componentValues and True or False + + def __len__(self): + return self._currentIdx is not None and 1 or 0 + + def __contains__(self, key): + if self._currentIdx is None: + return False + return key == self._componentType[self._currentIdx].getName() + + def __iter__(self): + if self._currentIdx is None: + raise StopIteration + yield self._componentType[self._currentIdx].getName() + + # Python dict protocol + + def values(self): + if self._currentIdx is not None: + yield self._componentValues[self._currentIdx] + + def keys(self): + if self._currentIdx is not None: + yield self._componentType[self._currentIdx].getName() + + def items(self): + if self._currentIdx is not None: + yield self._componentType[self._currentIdx].getName(), self[self._currentIdx] + def verifySizeSpec(self): if self._currentIdx is None: raise error.PyAsn1Error('Component not chosen') - else: - self._sizeSpec(' ') def _cloneComponentValues(self, myClone, cloneValueFlag): try: - c = self.getComponent() + component = self.getComponent() except error.PyAsn1Error: pass else: - if isinstance(c, Choice): - tagSet = c.getEffectiveTagSet() + if isinstance(component, Choice): + tagSet = component.effectiveTagSet else: - tagSet = c.getTagSet() - if isinstance(c, base.AbstractConstructedAsn1Item): + tagSet = component.tagSet + if isinstance(component, base.AbstractConstructedAsn1Item): myClone.setComponentByType( - tagSet, c.clone(cloneValueFlag=cloneValueFlag) - ) - else: - myClone.setComponentByType(tagSet, c.clone()) - - def setComponentByPosition(self, idx, value=None, verifyConstraints=True): - l = len(self._componentValues) - if idx >= l: - self._componentValues = self._componentValues + (idx-l+1)*[None] - if self._currentIdx is not None: - self._componentValues[self._currentIdx] = None - if value is None: - if self._componentValues[idx] is None: - self._componentValues[idx] = self._componentType.getTypeByPosition(idx).clone() - self._componentValuesSet = 1 - self._currentIdx = idx - return self - elif not isinstance(value, base.Asn1Item): - value = self._componentType.getTypeByPosition(idx).clone( - value=value + tagSet, component.clone(cloneValueFlag=cloneValueFlag) ) - if verifyConstraints: - if self._componentTypeLen: - self._verifyComponent(idx, value) - self._verifySubtypeSpec(value, idx) - self._componentValues[idx] = value + else: + myClone.setComponentByType(tagSet, component.clone()) + + def getComponentByPosition(self, idx): + __doc__ = Set.__doc__ + + if self._currentIdx is None or self._currentIdx != idx: + return Set.getComponentByPosition(self, idx) + + return self._componentValues[idx] + + def setComponentByPosition(self, idx, value=noValue, + verifyConstraints=True, + matchTags=True, + matchConstraints=True): + """Assign |ASN.1| type component by position. + + Equivalent to Python sequence item assignment operation (e.g. `[]`). + + Parameters + ---------- + idx: :class:`int` + Component index (zero-based). Must either refer to existing + component or to N+1 component. In the latter case a new component + type gets instantiated (if *componentType* is set, or given ASN.1 + object is taken otherwise) and appended to the |ASN.1| sequence. + + 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. Once a new value is + set to *idx* component, previous value is dropped. + + verifyConstraints : :class:`bool` + If `False`, skip constraints validation + + matchTags: :class:`bool` + If `False`, skip component tags matching + + matchConstraints: :class:`bool` + If `False`, skip component constraints matching + + Returns + ------- + self + """ + oldIdx = self._currentIdx + Set.setComponentByPosition(self, idx, value, verifyConstraints, matchTags, matchConstraints) self._currentIdx = idx - self._componentValuesSet = 1 + if oldIdx is not None and oldIdx != idx: + self._componentValues[oldIdx] = None return self def getMinTagSet(self): if self._tagSet: return self._tagSet else: - return self._componentType.genMinTagSet() + return self._componentType.minTagSet - def getEffectiveTagSet(self): + @property + def effectiveTagSet(self): + """Return a :class:`~pyasn1.type.tag.TagSet` object of the currently initialized component or self (if |ASN.1| is tagged).""" if self._tagSet: return self._tagSet else: - c = self.getComponent() - if isinstance(c, Choice): - return c.getEffectiveTagSet() - else: - return c.getTagSet() + component = self.getComponent() + return component.effectiveTagSet - def getTagMap(self): + @property + def tagMap(self): + """"Return a :class:`~pyasn1.type.tagmap.TagMap` object mapping + ASN.1 tags to ASN.1 objects contained within callee. + """ if self._tagSet: - return Set.getTagMap(self) + return Set.tagMap.fget(self) else: - return Set.getComponentTagMap(self) + return Set.componentTagMap.fget(self) def getComponent(self, innerFlag=0): + """Return currently assigned component of the |ASN.1| object. + + Returns + ------- + : :py:class:`~pyasn1.type.base.PyAsn1Item` + a PyASN1 object + """ if self._currentIdx is None: raise error.PyAsn1Error('Component not chosen') else: @@ -1015,7 +2729,14 @@ class Choice(Set): else: return c - def getName(self, innerFlag=0): + def getName(self, innerFlag=False): + """Return the name of currently assigned component of the |ASN.1| object. + + Returns + ------- + : :py:class:`str` + |ASN.1| component name + """ if self._currentIdx is None: raise error.PyAsn1Error('Component not chosen') else: @@ -1025,18 +2746,61 @@ class Choice(Set): return c.getName(innerFlag) return self._componentType.getNameByPosition(self._currentIdx) - def setDefaultComponents(self): pass + @property + def isValue(self): + """Indicate if |ASN.1| component is set and represents ASN.1 type or ASN.1 value. + + The PyASN1 type objects can only participate in types comparison + and serve as a blueprint for serialization codecs to resolve + ambiguous types. + + The PyASN1 value objects can additionally participate in most + of built-in Python operations. + + Returns + ------- + : :class:`bool` + :class:`True` if |ASN.1| component is set and represent value and type, + :class:`False` if |ASN.1| component is not set or it represents just ASN.1 type. + """ + if self._currentIdx is None: + return False + + return self._componentValues[self._currentIdx].isValue + class Any(OctetString): - tagSet = baseTagSet = tag.TagSet() # untagged - typeId = 6 + __doc__ = OctetString.__doc__ - def getTagMap(self): - return tagmap.TagMap( - { self.getTagSet(): self }, - { eoo.endOfOctets.getTagSet(): eoo.endOfOctets }, - self + #: Set (class attribute) or return (class or instance attribute) a + #: :py:class:`~pyasn1.type.tag.TagSet` object representing ASN.1 tag(s) + #: associated with |ASN.1| type. + tagSet = tag.TagSet() # untagged + + #: Set (class attribute) or return (class or instance attribute) a + #: :py:class:`~pyasn1.type.constraint.ConstraintsIntersection` object + #: imposing constraints on |ASN.1| type initialization values. + subtypeSpec = constraint.ConstraintsIntersection() + + # Disambiguation ASN.1 types identification + typeId = OctetString.getTypeId() + + @property + def tagMap(self): + """"Return a :class:`~pyasn1.type.tagmap.TagMap` object mapping + ASN.1 tags to ASN.1 objects contained within callee. + """ + try: + return self._tagMap + + except AttributeError: + self._tagMap = tagmap.TagMap( + {self.tagSet: self}, + {eoo.endOfOctets.tagSet: eoo.endOfOctets}, + self ) + return self._tagMap + # XXX # coercion rules? diff --git a/src/lib/pyasn1/type/useful.py b/src/lib/pyasn1/type/useful.py index a7139c22..0b79a983 100644 --- a/src/lib/pyasn1/type/useful.py +++ b/src/lib/pyasn1/type/useful.py @@ -1,12 +1,39 @@ -# ASN.1 "useful" types -from pyasn1.type import char, tag +# +# This file is part of pyasn1 software. +# +# Copyright (c) 2005-2017, Ilya Etingof +# License: http://pyasn1.sf.net/license.html +# +from pyasn1.type import univ, char, tag + +__all__ = ['ObjectDescriptor', 'GeneralizedTime', 'UTCTime'] + +NoValue = univ.NoValue +noValue = univ.noValue + + +class ObjectDescriptor(char.GraphicString): + __doc__ = char.GraphicString.__doc__ + + #: Default :py:class:`~pyasn1.type.tag.TagSet` object for |ASN.1| objects + tagSet = char.GraphicString.tagSet.tagImplicitly( + tag.Tag(tag.tagClassUniversal, tag.tagFormatSimple, 7) + ) + class GeneralizedTime(char.VisibleString): + __doc__ = char.GraphicString.__doc__ + + #: Default :py:class:`~pyasn1.type.tag.TagSet` object for |ASN.1| objects tagSet = char.VisibleString.tagSet.tagImplicitly( tag.Tag(tag.tagClassUniversal, tag.tagFormatSimple, 24) - ) + ) + class UTCTime(char.VisibleString): + __doc__ = char.GraphicString.__doc__ + + #: Default :py:class:`~pyasn1.type.tag.TagSet` object for |ASN.1| objects tagSet = char.VisibleString.tagSet.tagImplicitly( tag.Tag(tag.tagClassUniversal, tag.tagFormatSimple, 23) - ) + )