From 5f8de423f190bbb79a62f804151bc24824fa32d8 Mon Sep 17 00:00:00 2001 From: "Matt A. Tobin" Date: Fri, 2 Feb 2018 04:16:08 -0500 Subject: Add m-esr52 at 52.6.0 --- python/pyasn1/CHANGES | 278 +++ python/pyasn1/LICENSE | 24 + python/pyasn1/MANIFEST.in | 3 + python/pyasn1/PKG-INFO | 26 + python/pyasn1/README | 68 + python/pyasn1/THANKS | 4 + python/pyasn1/TODO | 36 + python/pyasn1/doc/codecs.html | 503 ++++++ python/pyasn1/doc/constraints.html | 436 +++++ python/pyasn1/doc/constructed.html | 377 ++++ python/pyasn1/doc/intro.html | 156 ++ python/pyasn1/doc/pyasn1-tutorial.html | 2405 ++++++++++++++++++++++++++ python/pyasn1/doc/scalar.html | 794 +++++++++ python/pyasn1/doc/tagging.html | 233 +++ python/pyasn1/pyasn1/__init__.py | 8 + python/pyasn1/pyasn1/codec/__init__.py | 1 + python/pyasn1/pyasn1/codec/ber/__init__.py | 1 + python/pyasn1/pyasn1/codec/ber/decoder.py | 808 +++++++++ python/pyasn1/pyasn1/codec/ber/encoder.py | 353 ++++ python/pyasn1/pyasn1/codec/ber/eoo.py | 8 + python/pyasn1/pyasn1/codec/cer/__init__.py | 1 + python/pyasn1/pyasn1/codec/cer/decoder.py | 35 + python/pyasn1/pyasn1/codec/cer/encoder.py | 87 + python/pyasn1/pyasn1/codec/der/__init__.py | 1 + python/pyasn1/pyasn1/codec/der/decoder.py | 9 + python/pyasn1/pyasn1/codec/der/encoder.py | 28 + python/pyasn1/pyasn1/compat/__init__.py | 1 + python/pyasn1/pyasn1/compat/octets.py | 20 + python/pyasn1/pyasn1/debug.py | 65 + python/pyasn1/pyasn1/error.py | 3 + python/pyasn1/pyasn1/type/__init__.py | 1 + python/pyasn1/pyasn1/type/base.py | 249 +++ python/pyasn1/pyasn1/type/char.py | 61 + python/pyasn1/pyasn1/type/constraint.py | 200 +++ python/pyasn1/pyasn1/type/error.py | 3 + python/pyasn1/pyasn1/type/namedtype.py | 132 ++ python/pyasn1/pyasn1/type/namedval.py | 46 + python/pyasn1/pyasn1/type/tag.py | 122 ++ python/pyasn1/pyasn1/type/tagmap.py | 52 + python/pyasn1/pyasn1/type/univ.py | 1042 +++++++++++ python/pyasn1/pyasn1/type/useful.py | 12 + python/pyasn1/setup.cfg | 5 + python/pyasn1/setup.py | 115 ++ python/pyasn1/test/__init__.py | 1 + python/pyasn1/test/codec/__init__.py | 1 + python/pyasn1/test/codec/ber/__init__.py | 1 + python/pyasn1/test/codec/ber/suite.py | 22 + python/pyasn1/test/codec/ber/test_decoder.py | 535 ++++++ python/pyasn1/test/codec/ber/test_encoder.py | 338 ++++ python/pyasn1/test/codec/cer/__init__.py | 1 + python/pyasn1/test/codec/cer/suite.py | 22 + python/pyasn1/test/codec/cer/test_decoder.py | 31 + python/pyasn1/test/codec/cer/test_encoder.py | 107 ++ python/pyasn1/test/codec/der/__init__.py | 1 + python/pyasn1/test/codec/der/suite.py | 22 + python/pyasn1/test/codec/der/test_decoder.py | 20 + python/pyasn1/test/codec/der/test_encoder.py | 44 + python/pyasn1/test/codec/suite.py | 29 + python/pyasn1/test/suite.py | 26 + python/pyasn1/test/type/__init__.py | 1 + python/pyasn1/test/type/suite.py | 20 + python/pyasn1/test/type/test_constraint.py | 280 +++ python/pyasn1/test/type/test_namedtype.py | 87 + python/pyasn1/test/type/test_tag.py | 107 ++ python/pyasn1/test/type/test_univ.py | 479 +++++ 65 files changed, 10987 insertions(+) create mode 100644 python/pyasn1/CHANGES create mode 100644 python/pyasn1/LICENSE create mode 100644 python/pyasn1/MANIFEST.in create mode 100644 python/pyasn1/PKG-INFO create mode 100644 python/pyasn1/README create mode 100644 python/pyasn1/THANKS create mode 100644 python/pyasn1/TODO create mode 100644 python/pyasn1/doc/codecs.html create mode 100644 python/pyasn1/doc/constraints.html create mode 100644 python/pyasn1/doc/constructed.html create mode 100644 python/pyasn1/doc/intro.html create mode 100644 python/pyasn1/doc/pyasn1-tutorial.html create mode 100644 python/pyasn1/doc/scalar.html create mode 100644 python/pyasn1/doc/tagging.html create mode 100644 python/pyasn1/pyasn1/__init__.py create mode 100644 python/pyasn1/pyasn1/codec/__init__.py create mode 100644 python/pyasn1/pyasn1/codec/ber/__init__.py create mode 100644 python/pyasn1/pyasn1/codec/ber/decoder.py create mode 100644 python/pyasn1/pyasn1/codec/ber/encoder.py create mode 100644 python/pyasn1/pyasn1/codec/ber/eoo.py create mode 100644 python/pyasn1/pyasn1/codec/cer/__init__.py create mode 100644 python/pyasn1/pyasn1/codec/cer/decoder.py create mode 100644 python/pyasn1/pyasn1/codec/cer/encoder.py create mode 100644 python/pyasn1/pyasn1/codec/der/__init__.py create mode 100644 python/pyasn1/pyasn1/codec/der/decoder.py create mode 100644 python/pyasn1/pyasn1/codec/der/encoder.py create mode 100644 python/pyasn1/pyasn1/compat/__init__.py create mode 100644 python/pyasn1/pyasn1/compat/octets.py create mode 100644 python/pyasn1/pyasn1/debug.py create mode 100644 python/pyasn1/pyasn1/error.py create mode 100644 python/pyasn1/pyasn1/type/__init__.py create mode 100644 python/pyasn1/pyasn1/type/base.py create mode 100644 python/pyasn1/pyasn1/type/char.py create mode 100644 python/pyasn1/pyasn1/type/constraint.py create mode 100644 python/pyasn1/pyasn1/type/error.py create mode 100644 python/pyasn1/pyasn1/type/namedtype.py create mode 100644 python/pyasn1/pyasn1/type/namedval.py create mode 100644 python/pyasn1/pyasn1/type/tag.py create mode 100644 python/pyasn1/pyasn1/type/tagmap.py create mode 100644 python/pyasn1/pyasn1/type/univ.py create mode 100644 python/pyasn1/pyasn1/type/useful.py create mode 100644 python/pyasn1/setup.cfg create mode 100644 python/pyasn1/setup.py create mode 100644 python/pyasn1/test/__init__.py create mode 100644 python/pyasn1/test/codec/__init__.py create mode 100644 python/pyasn1/test/codec/ber/__init__.py create mode 100644 python/pyasn1/test/codec/ber/suite.py create mode 100644 python/pyasn1/test/codec/ber/test_decoder.py create mode 100644 python/pyasn1/test/codec/ber/test_encoder.py create mode 100644 python/pyasn1/test/codec/cer/__init__.py create mode 100644 python/pyasn1/test/codec/cer/suite.py create mode 100644 python/pyasn1/test/codec/cer/test_decoder.py create mode 100644 python/pyasn1/test/codec/cer/test_encoder.py create mode 100644 python/pyasn1/test/codec/der/__init__.py create mode 100644 python/pyasn1/test/codec/der/suite.py create mode 100644 python/pyasn1/test/codec/der/test_decoder.py create mode 100644 python/pyasn1/test/codec/der/test_encoder.py create mode 100644 python/pyasn1/test/codec/suite.py create mode 100644 python/pyasn1/test/suite.py create mode 100644 python/pyasn1/test/type/__init__.py create mode 100644 python/pyasn1/test/type/suite.py create mode 100644 python/pyasn1/test/type/test_constraint.py create mode 100644 python/pyasn1/test/type/test_namedtype.py create mode 100644 python/pyasn1/test/type/test_tag.py create mode 100644 python/pyasn1/test/type/test_univ.py (limited to 'python/pyasn1') diff --git a/python/pyasn1/CHANGES b/python/pyasn1/CHANGES new file mode 100644 index 000000000..561dedd88 --- /dev/null +++ b/python/pyasn1/CHANGES @@ -0,0 +1,278 @@ +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/python/pyasn1/LICENSE b/python/pyasn1/LICENSE new file mode 100644 index 000000000..fac589b8c --- /dev/null +++ b/python/pyasn1/LICENSE @@ -0,0 +1,24 @@ +Copyright (c) 2005-2013, Ilya Etingof +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. diff --git a/python/pyasn1/MANIFEST.in b/python/pyasn1/MANIFEST.in new file mode 100644 index 000000000..e8b3d36ce --- /dev/null +++ b/python/pyasn1/MANIFEST.in @@ -0,0 +1,3 @@ +include CHANGES README LICENSE THANKS TODO +recursive-include test *.py +recursive-include doc *.html diff --git a/python/pyasn1/PKG-INFO b/python/pyasn1/PKG-INFO new file mode 100644 index 000000000..5de78eceb --- /dev/null +++ b/python/pyasn1/PKG-INFO @@ -0,0 +1,26 @@ +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/python/pyasn1/README b/python/pyasn1/README new file mode 100644 index 000000000..ffa3b57e5 --- /dev/null +++ b/python/pyasn1/README @@ -0,0 +1,68 @@ + +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/python/pyasn1/THANKS b/python/pyasn1/THANKS new file mode 100644 index 000000000..4de1713c0 --- /dev/null +++ b/python/pyasn1/THANKS @@ -0,0 +1,4 @@ +Denis S. Otkidach +Gregory Golberg +Bud P. Bruegger +Jacek Konieczny diff --git a/python/pyasn1/TODO b/python/pyasn1/TODO new file mode 100644 index 000000000..0ee211c2a --- /dev/null +++ b/python/pyasn1/TODO @@ -0,0 +1,36 @@ +* 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/python/pyasn1/doc/codecs.html b/python/pyasn1/doc/codecs.html new file mode 100644 index 000000000..9c2c36ed6 --- /dev/null +++ b/python/pyasn1/doc/codecs.html @@ -0,0 +1,503 @@ + + +PyASN1 codecs + + + + +
+ + + + +
+

+2. PyASN1 Codecs +

+ +

+In ASN.1 context, +codec +is a program that transforms between concrete data structures and a stream +of octets, suitable for transmission over the wire. This serialized form of +data is sometimes called substrate or essence. +

+ +

+In pyasn1 implementation, substrate takes shape of Python 3 bytes or +Python 2 string objects. +

+ +

+One of the properties of a codec is its ability to cope with incomplete +data and/or substrate what implies codec to be stateful. In other words, +when decoder runs out of substrate and data item being recovered is still +incomplete, stateful codec would suspend and complete data item recovery +whenever the rest of substrate becomes available. Similarly, stateful encoder +would encode data items in multiple steps waiting for source data to +arrive. Codec restartability is especially important when application deals +with large volumes of data and/or runs on low RAM. For an interesting +discussion on codecs options and design choices, refer to +Apache ASN.1 project +. +

+ +

+As of this writing, codecs implemented in pyasn1 are all stateless, mostly +to keep the code simple. +

+ +

+The pyasn1 package currently supports +BER codec and +its variations -- +CER and +DER. +More ASN.1 codecs are planned for implementation in the future. +

+ + +

+2.1 Encoders +

+ +

+Encoder is used for transforming pyasn1 value objects into substrate. Only +pyasn1 value objects could be serialized, attempts to process pyasn1 type +objects will cause encoder failure. +

+ +

+The following code will create a pyasn1 Integer object and serialize it with +BER encoder: +

+ +
+
+>>> from pyasn1.type import univ
+>>> from pyasn1.codec.ber import encoder
+>>> encoder.encode(univ.Integer(123456))
+b'\x02\x03\x01\xe2@'
+>>>
+
+
+ +

+BER standard also defines a so-called indefinite length encoding form +which makes large data items processing more memory efficient. It is mostly +useful when encoder does not have the whole value all at once and the +length of the value can not be determined at the beginning of encoding. +

+ +

+Constructed encoding is another feature of BER closely related to the +indefinite length form. In essence, a large scalar value (such as ASN.1 +character BitString type) could be chopped into smaller chunks by encoder +and transmitted incrementally to limit memory consumption. Unlike indefinite +length case, the length of the whole value must be known in advance when +using constructed, definite length encoding form. +

+ +

+Since pyasn1 codecs are not restartable, pyasn1 encoder may only encode data +item all at once. However, even in this case, generating indefinite length +encoding may help a low-memory receiver, running a restartable decoder, +to process a large data item. +

+ +
+
+>>> from pyasn1.type import univ
+>>> from pyasn1.codec.ber import encoder
+>>> encoder.encode(
+...   univ.OctetString('The quick brown fox jumps over the lazy dog'),
+...   defMode=False,
+...   maxChunkSize=8
+... )
+b'$\x80\x04\x08The quic\x04\x08k brown \x04\x08fox jump\x04\x08s over \
+t\x04\x08he lazy \x04\x03dog\x00\x00'
+>>>
+>>> encoder.encode(
+...   univ.OctetString('The quick brown fox jumps over the lazy dog'),
+...   maxChunkSize=8
+... )
+b'$7\x04\x08The quic\x04\x08k brown \x04\x08fox jump\x04\x08s over \
+t\x04\x08he lazy \x04\x03dog'
+
+
+ +

+The defMode encoder parameter disables definite length encoding mode, +while the optional maxChunkSize parameter specifies desired +substrate chunk size that influences memory requirements at the decoder's end. +

+ +

+To use CER or DER encoders one needs to explicitly import and call them - the +APIs are all compatible. +

+ +
+
+>>> from pyasn1.type import univ
+>>> from pyasn1.codec.ber import encoder as ber_encoder
+>>> from pyasn1.codec.cer import encoder as cer_encoder
+>>> from pyasn1.codec.der import encoder as der_encoder
+>>> ber_encoder.encode(univ.Boolean(True))
+b'\x01\x01\x01'
+>>> cer_encoder.encode(univ.Boolean(True))
+b'\x01\x01\xff'
+>>> der_encoder.encode(univ.Boolean(True))
+b'\x01\x01\xff'
+>>>
+
+
+ + +

+2.2 Decoders +

+ +

+In the process of decoding, pyasn1 value objects are created and linked to +each other, based on the information containted in the substrate. Thus, +the original pyasn1 value object(s) are recovered. +

+ +
+
+>>> from pyasn1.type import univ
+>>> from pyasn1.codec.ber import encoder, decoder
+>>> substrate = encoder.encode(univ.Boolean(True))
+>>> decoder.decode(substrate)
+(Boolean('True(1)'), b'')
+>>>
+
+
+ +

+Commenting on the code snippet above, pyasn1 decoder accepts substrate +as an argument and returns a tuple of pyasn1 value object (possibly +a top-level one in case of constructed object) and unprocessed part +of input substrate. +

+ +

+All pyasn1 decoders can handle both definite and indefinite length +encoding modes automatically, explicit switching into one mode +to another is not required. +

+ +
+
+>>> from pyasn1.type import univ
+>>> from pyasn1.codec.ber import encoder, decoder
+>>> substrate = encoder.encode(
+...   univ.OctetString('The quick brown fox jumps over the lazy dog'),
+...   defMode=False,
+...   maxChunkSize=8
+... )
+>>> decoder.decode(substrate)
+(OctetString(b'The quick brown fox jumps over the lazy dog'), b'')
+>>>
+
+
+ +

+Speaking of BER/CER/DER encoding, in many situations substrate may not contain +all necessary information needed for complete and accurate ASN.1 values +recovery. The most obvious cases include implicitly tagged ASN.1 types +and constrained types. +

+ +

+As discussed earlier in this handbook, when an ASN.1 type is implicitly +tagged, previous outermost tag is lost and never appears in substrate. +If it is the base tag that gets lost, decoder is unable to pick type-specific +value decoder at its table of built-in types, and therefore recover +the value part, based only on the information contained in substrate. The +approach taken by pyasn1 decoder is to use a prototype pyasn1 type object (or +a set of them) to guide the decoding process by matching [possibly +incomplete] tags recovered from substrate with those found in prototype pyasn1 +type objects (also called pyasn1 specification object further in this paper). +

+ +
+
+>>> from pyasn1.codec.ber import decoder
+>>> decoder.decode(b'\x02\x01\x0c', asn1Spec=univ.Integer())
+Integer(12), b''
+>>>
+
+
+ +

+Decoder would neither modify pyasn1 specification object nor use +its current values (if it's a pyasn1 value object), but rather use it as +a hint for choosing proper decoder and as a pattern for creating new objects: +

+ +
+
+>>> from pyasn1.type import univ, tag
+>>> from pyasn1.codec.ber import encoder, decoder
+>>> i = univ.Integer(12345).subtype(
+...   implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 40)
+... )
+>>> substrate = encoder.encode(i)
+>>> substrate
+b'\x9f(\x0209'
+>>> decoder.decode(substrate)
+Traceback (most recent call last):
+...
+pyasn1.error.PyAsn1Error: 
+   TagSet(Tag(tagClass=128, tagFormat=0, tagId=40)) not in asn1Spec
+>>> decoder.decode(substrate, asn1Spec=i)
+(Integer(12345), b'')
+>>>
+
+
+ +

+Notice in the example above, that an attempt to run decoder without passing +pyasn1 specification object fails because recovered tag does not belong +to any of the built-in types. +

+ +

+Another important feature of guided decoder operation is the use of +values constraints possibly present in pyasn1 specification object. +To explain this, we will decode a random integer object into generic Integer +and the constrained one. +

+ +
+
+>>> from pyasn1.type import univ, constraint
+>>> from pyasn1.codec.ber import encoder, decoder
+>>> class DialDigit(univ.Integer):
+...   subtypeSpec = constraint.ValueRangeConstraint(0,9)
+>>> substrate = encoder.encode(univ.Integer(13))
+>>> decoder.decode(substrate)
+(Integer(13), b'')
+>>> decoder.decode(substrate, asn1Spec=DialDigit())
+Traceback (most recent call last):
+...
+pyasn1.type.error.ValueConstraintError:
+  ValueRangeConstraint(0, 9) failed at: 13
+>>> 
+
+
+ +

+Similarily to encoders, to use CER or DER decoders application has to +explicitly import and call them - all APIs are compatible. +

+ +
+
+>>> from pyasn1.type import univ
+>>> from pyasn1.codec.ber import encoder as ber_encoder
+>>> substrate = ber_encoder.encode(univ.OctetString('http://pyasn1.sf.net'))
+>>>
+>>> from pyasn1.codec.ber import decoder as ber_decoder
+>>> from pyasn1.codec.cer import decoder as cer_decoder
+>>> from pyasn1.codec.der import decoder as der_decoder
+>>> 
+>>> ber_decoder.decode(substrate)
+(OctetString(b'http://pyasn1.sf.net'), b'')
+>>> cer_decoder.decode(substrate)
+(OctetString(b'http://pyasn1.sf.net'), b'')
+>>> der_decoder.decode(substrate)
+(OctetString(b'http://pyasn1.sf.net'), b'')
+>>> 
+
+
+ + +

+2.2.1 Decoding untagged types +

+ +

+It has already been mentioned, that ASN.1 has two "special case" types: +CHOICE and ANY. They are different from other types in part of +tagging - unless these two are additionally tagged, neither of them will +have their own tag. Therefore these types become invisible in substrate +and can not be recovered without passing pyasn1 specification object to +decoder. +

+ +

+To explain the issue, we will first prepare a Choice object to deal with: +

+ +
+
+>>> from pyasn1.type import univ, namedtype
+>>> class CodeOrMessage(univ.Choice):
+...   componentType = namedtype.NamedTypes(
+...     namedtype.NamedType('code', univ.Integer()),
+...     namedtype.NamedType('message', univ.OctetString())
+...   )
+>>>
+>>> codeOrMessage = CodeOrMessage()
+>>> codeOrMessage.setComponentByName('message', 'my string value')
+>>> print(codeOrMessage.prettyPrint())
+CodeOrMessage:
+ message=b'my string value'
+>>>
+
+
+ +

+Let's now encode this Choice object and then decode its substrate +with and without pyasn1 specification object: +

+ +
+
+>>> from pyasn1.codec.ber import encoder, decoder
+>>> substrate = encoder.encode(codeOrMessage)
+>>> substrate
+b'\x04\x0fmy string value'
+>>> encoder.encode(univ.OctetString('my string value'))
+b'\x04\x0fmy string value'
+>>>
+>>> decoder.decode(substrate)
+(OctetString(b'my string value'), b'')
+>>> codeOrMessage, substrate = decoder.decode(substrate, asn1Spec=CodeOrMessage())
+>>> print(codeOrMessage.prettyPrint())
+CodeOrMessage:
+ message=b'my string value'
+>>>
+
+
+ +

+First thing to notice in the listing above is that the substrate produced +for our Choice value object is equivalent to the substrate for an OctetString +object initialized to the same value. In other words, any information about +the Choice component is absent in encoding. +

+ +

+Sure enough, that kind of substrate will decode into an OctetString object, +unless original Choice type object is passed to decoder to guide the decoding +process. +

+ +

+Similarily untagged ANY type behaves differently on decoding phase - when +decoder bumps into an Any object in pyasn1 specification, it stops decoding +and puts all the substrate into a new Any value object in form of an octet +string. Concerned application could then re-run decoder with an additional, +more exact pyasn1 specification object to recover the contents of Any +object. +

+ +

+As it was mentioned elsewhere in this paper, Any type allows for incomplete +or changing ASN.1 specification to be handled gracefully by decoder and +applications. +

+ +

+To illustrate the working of Any type, we'll have to make the stage +by encoding a pyasn1 object and then putting its substrate into an any +object. +

+ +
+
+>>> from pyasn1.type import univ
+>>> from pyasn1.codec.ber import encoder, decoder
+>>> innerSubstrate = encoder.encode(univ.Integer(1234))
+>>> innerSubstrate
+b'\x02\x02\x04\xd2'
+>>> any = univ.Any(innerSubstrate)
+>>> any
+Any(b'\x02\x02\x04\xd2')
+>>> substrate = encoder.encode(any)
+>>> substrate
+b'\x02\x02\x04\xd2'
+>>>
+
+
+ +

+As with Choice type encoding, there is no traces of Any type in substrate. +Obviously, the substrate we are dealing with, will decode into the inner +[Integer] component, unless pyasn1 specification is given to guide the +decoder. Continuing previous code: +

+ +
+
+>>> from pyasn1.type import univ
+>>> from pyasn1.codec.ber import encoder, decoder
+
+>>> decoder.decode(substrate)
+(Integer(1234), b'')
+>>> any, substrate = decoder.decode(substrate, asn1Spec=univ.Any())
+>>> any
+Any(b'\x02\x02\x04\xd2')
+>>> decoder.decode(str(any))
+(Integer(1234), b'')
+>>>
+
+
+ +

+Both CHOICE and ANY types are widely used in practice. Reader is welcome to +take a look at + +ASN.1 specifications of X.509 applications for more information. +

+ + +

+2.2.2 Ignoring unknown types +

+ +

+When dealing with a loosely specified ASN.1 structure, the receiving +end may not be aware of some types present in the substrate. It may be +convenient then to turn decoder into a recovery mode. Whilst there, decoder +will not bail out when hit an unknown tag but rather treat it as an Any +type. +

+ +
+
+>>> from pyasn1.type import univ, tag
+>>> from pyasn1.codec.ber import encoder, decoder
+>>> taggedInt = univ.Integer(12345).subtype(
+...   implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 40)
+... )
+>>> substrate = encoder.encode(taggedInt)
+>>> decoder.decode(substrate)
+Traceback (most recent call last):
+...
+pyasn1.error.PyAsn1Error: TagSet(Tag(tagClass=128, tagFormat=0, tagId=40)) not in asn1Spec
+>>>
+>>> decoder.decode.defaultErrorState = decoder.stDumpRawValue
+>>> decoder.decode(substrate)
+(Any(b'\x9f(\x0209'), '')
+>>>
+
+
+ +

+It's also possible to configure a custom decoder, to handle unknown tags +found in substrate. This can be done by means of defaultRawDecoder +attribute holding a reference to type decoder object. Refer to the source +for API details. +

+ +
+ +
+
+ + diff --git a/python/pyasn1/doc/constraints.html b/python/pyasn1/doc/constraints.html new file mode 100644 index 000000000..53da1addf --- /dev/null +++ b/python/pyasn1/doc/constraints.html @@ -0,0 +1,436 @@ + + +PyASN1 subtype constraints + + + + +
+ + + + +
+ +

+1.4 PyASN1 subtype constraints +

+ +

+Most ASN.1 types can correspond to an infinite set of values. To adapt to +particular application's data model and needs, ASN.1 provides a mechanism +for limiting the infinite set to values, that make sense in particular case. +

+ +

+Imposing value constraints on an ASN.1 type can also be seen as creating +a subtype from its base type. +

+ +

+In pyasn1, constraints take shape of immutable objects capable +of evaluating given value against constraint-specific requirements. +Constraint object is a property of pyasn1 type. Like TagSet property, +associated with every pyasn1 type, constraints can never be modified +in place. The only way to modify pyasn1 type constraint is to associate +new constraint object to a new pyasn1 type object. +

+ +

+A handful of different flavors of constraints are defined in ASN.1. +We will discuss them one by one in the following chapters and also explain +how to combine and apply them to types. +

+ + +

+1.4.1 Single value constraint +

+ +

+This kind of constraint allows for limiting type to a finite, specified set +of values. +

+ +
+
+DialButton ::= OCTET STRING (
+  "0" | "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9"
+)
+
+
+ +

+Its pyasn1 implementation would look like: +

+ +
+
+>>> from pyasn1.type import constraint
+>>> c = constraint.SingleValueConstraint(
+  '0','1','2','3','4','5','6','7','8','9'
+)
+>>> c
+SingleValueConstraint(0, 1, 2, 3, 4, 5, 6, 7, 8, 9)
+>>> c('0')
+>>> c('A')
+Traceback (most recent call last):
+...
+pyasn1.type.error.ValueConstraintError: 
+  SingleValueConstraint(0, 1, 2, 3, 4, 5, 6, 7, 8, 9) failed at: A
+>>> 
+
+
+ +

+As can be seen in the snippet above, if a value violates the constraint, an +exception will be thrown. A constrainted pyasn1 type object holds a +reference to a constraint object (or their combination, as will be explained +later) and calls it for value verification. +

+ +
+
+>>> from pyasn1.type import univ, constraint
+>>> class DialButton(univ.OctetString):
+...   subtypeSpec = constraint.SingleValueConstraint(
+...       '0','1','2','3','4','5','6','7','8','9'
+...   )
+>>> DialButton('0')
+DialButton(b'0')
+>>> DialButton('A')
+Traceback (most recent call last):
+...
+pyasn1.type.error.ValueConstraintError:
+  SingleValueConstraint(0, 1, 2, 3, 4, 5, 6, 7, 8, 9) failed at: A
+>>> 
+
+
+ +

+Constrained pyasn1 value object can never hold a violating value. +

+ + +

+1.4.2 Value range constraint +

+ +

+A pair of values, compliant to a type to be constrained, denote low and upper +bounds of allowed range of values of a type. +

+ +
+
+Teenagers ::= INTEGER (13..19)
+
+
+ +

+And in pyasn1 terms: +

+ +
+
+>>> from pyasn1.type import univ, constraint
+>>> class Teenagers(univ.Integer):
+...   subtypeSpec = constraint.ValueRangeConstraint(13, 19)
+>>> Teenagers(14)
+Teenagers(14)
+>>> Teenagers(20)
+Traceback (most recent call last):
+...
+pyasn1.type.error.ValueConstraintError:
+  ValueRangeConstraint(13, 19) failed at: 20
+>>> 
+
+
+ +

+Value range constraint usually applies numeric types. +

+ + +

+1.4.3 Size constraint +

+ +

+It is sometimes convenient to set or limit the allowed size of a data item +to be sent from one application to another to manage bandwidth and memory +consumption issues. Size constraint specifies the lower and upper bounds +of the size of a valid value. +

+ +
+
+TwoBits ::= BIT STRING (SIZE (2))
+
+
+ +

+Express the same grammar in pyasn1: +

+ +
+
+>>> from pyasn1.type import univ, constraint
+>>> class TwoBits(univ.BitString):
+...   subtypeSpec = constraint.ValueSizeConstraint(2, 2)
+>>> TwoBits((1,1))
+TwoBits("'11'B")
+>>> TwoBits((1,1,0))
+Traceback (most recent call last):
+...
+pyasn1.type.error.ValueConstraintError:
+  ValueSizeConstraint(2, 2) failed at: (1, 1, 0)
+>>> 
+
+
+ +

+Size constraint can be applied to potentially massive values - bit or octet +strings, SEQUENCE OF/SET OF values. +

+ + +

+1.4.4 Alphabet constraint +

+ +

+The permitted alphabet constraint is similar to Single value constraint +but constraint applies to individual characters of a value. +

+ +
+
+MorseCode ::= PrintableString (FROM ("."|"-"|" "))
+
+
+ +

+And in pyasn1: +

+ +
+
+>>> from pyasn1.type import char, constraint
+>>> class MorseCode(char.PrintableString):
+...   subtypeSpec = constraint.PermittedAlphabetConstraint(".", "-", " ")
+>>> MorseCode("...---...")
+MorseCode('...---...')
+>>> MorseCode("?")
+Traceback (most recent call last):
+...
+pyasn1.type.error.ValueConstraintError:
+  PermittedAlphabetConstraint(".", "-", " ") failed at: "?"
+>>> 
+
+
+ +

+Current implementation does not handle ranges of characters in constraint +(FROM "A".."Z" syntax), one has to list the whole set in a range. +

+ + +

+1.4.5 Constraint combinations +

+ +

+Up to this moment, we used a single constraint per ASN.1 type. The standard, +however, allows for combining multiple individual constraints into +intersections, unions and exclusions. +

+ +

+In pyasn1 data model, all of these methods of constraint combinations are +implemented as constraint-like objects holding individual constraint (or +combination) objects. Like terminal constraint objects, combination objects +are capable to perform value verification at its set of enclosed constraints +according to the logic of particular combination. +

+ +

+Constraints intersection verification succeeds only if a value is +compliant to each constraint in a set. To begin with, the following +specification will constitute a valid telephone number: +

+ +
+
+PhoneNumber ::= NumericString (FROM ("0".."9")) (SIZE 11)
+
+
+ +

+Constraint intersection object serves the logic above: +

+ +
+
+>>> from pyasn1.type import char, constraint
+>>> class PhoneNumber(char.NumericString):
+...   subtypeSpec = constraint.ConstraintsIntersection(
+...     constraint.PermittedAlphabetConstraint('0','1','2','3','4','5','6','7','8','9'),
+...     constraint.ValueSizeConstraint(11, 11)
+...   )
+>>> PhoneNumber('79039343212')
+PhoneNumber('79039343212')
+>>> PhoneNumber('?9039343212')
+Traceback (most recent call last):
+...
+pyasn1.type.error.ValueConstraintError:
+  ConstraintsIntersection(
+    PermittedAlphabetConstraint('0','1','2','3','4','5','6','7','8','9'),
+      ValueSizeConstraint(11, 11)) failed at: 
+   PermittedAlphabetConstraint('0','1','2','3','4','5','6','7','8','9') failed at: "?039343212"
+>>> PhoneNumber('9343212')
+Traceback (most recent call last):
+...
+pyasn1.type.error.ValueConstraintError:
+  ConstraintsIntersection(
+    PermittedAlphabetConstraint('0','1','2','3','4','5','6','7','8','9'),
+      ValueSizeConstraint(11, 11)) failed at:
+  ValueSizeConstraint(10, 10) failed at: "9343212"
+>>>
+
+
+ +

+Union of constraints works by making sure that a value is compliant +to any of the constraint in a set. For instance: +

+ +
+
+CapitalOrSmall ::= IA5String (FROM ('A','B','C') | FROM ('a','b','c'))
+
+
+ +

+It's important to note, that a value must fully comply to any single +constraint in a set. In the specification above, a value of all small or +all capital letters is compliant, but a mix of small&capitals is not. +Here's its pyasn1 analogue: +

+ +
+
+>>> from pyasn1.type import char, constraint
+>>> class CapitalOrSmall(char.IA5String):
+...   subtypeSpec = constraint.ConstraintsUnion(
+...     constraint.PermittedAlphabetConstraint('A','B','C'),
+...     constraint.PermittedAlphabetConstraint('a','b','c')
+...   )
+>>> CapitalOrSmall('ABBA')
+CapitalOrSmall('ABBA')
+>>> CapitalOrSmall('abba')
+CapitalOrSmall('abba')
+>>> CapitalOrSmall('Abba')
+Traceback (most recent call last):
+...
+pyasn1.type.error.ValueConstraintError:
+  ConstraintsUnion(PermittedAlphabetConstraint('A', 'B', 'C'),
+    PermittedAlphabetConstraint('a', 'b', 'c')) failed at: failed for "Abba"
+>>>
+
+
+ +

+Finally, the exclusion constraint simply negates the logic of value +verification at a constraint. In the following example, any integer value +is allowed in a type but not zero. +

+ +
+
+NoZero ::= INTEGER (ALL EXCEPT 0)
+
+
+ +

+In pyasn1 the above definition would read: +

+ +
+
+>>> from pyasn1.type import univ, constraint
+>>> class NoZero(univ.Integer):
+...   subtypeSpec = constraint.ConstraintsExclusion(
+...     constraint.SingleValueConstraint(0)
+...   )
+>>> NoZero(1)
+NoZero(1)
+>>> NoZero(0)
+Traceback (most recent call last):
+...
+pyasn1.type.error.ValueConstraintError:
+  ConstraintsExclusion(SingleValueConstraint(0)) failed at: 0
+>>>
+
+
+ +

+The depth of such a constraints tree, built with constraint combination objects +at its nodes, has not explicit limit. Value verification is performed in a +recursive manner till a definite solution is found. +

+ + +

+1.5 Types relationships +

+ +

+In the course of data processing in an application, it is sometimes +convenient to figure out the type relationships between pyasn1 type or +value objects. Formally, two things influence pyasn1 types relationship: +tag set and subtype constraints. One pyasn1 type is considered +to be a derivative of another if their TagSet and Constraint objects are +a derivation of one another. +

+ +

+The following example illustrates the concept (we use the same tagset but +different constraints for simplicity): +

+ +
+
+>>> from pyasn1.type import univ, constraint
+>>> i1 = univ.Integer(subtypeSpec=constraint.ValueRangeConstraint(3,8))
+>>> i2 = univ.Integer(subtypeSpec=constraint.ConstraintsIntersection(
+...    constraint.ValueRangeConstraint(3,8),
+...    constraint.ValueRangeConstraint(4,7)
+... ) )
+>>> i1.isSameTypeWith(i2)
+False
+>>> i1.isSuperTypeOf(i2)
+True
+>>> i1.isSuperTypeOf(i1)
+True
+>>> i2.isSuperTypeOf(i1)
+False
+>>>
+
+
+ +

+As can be seen in the above code snippet, there are two methods of any pyasn1 +type/value object that test types for their relationship: +isSameTypeWith() and isSuperTypeOf(). The former is +self-descriptive while the latter yields true if the argument appears +to be a pyasn1 object which has tagset and constraints derived from those +of the object being called. +

+ +
+ +
+
+ + diff --git a/python/pyasn1/doc/constructed.html b/python/pyasn1/doc/constructed.html new file mode 100644 index 000000000..88de75075 --- /dev/null +++ b/python/pyasn1/doc/constructed.html @@ -0,0 +1,377 @@ + + +PyASN1 Constructed types + + + + +
+ + + + +
+ +

+1.3 PyASN1 Constructed types +

+ +

+Besides scalar types, ASN.1 specifies so-called constructed ones - these +are capable of holding one or more values of other types, both scalar +and constructed. +

+ +

+In pyasn1 implementation, constructed ASN.1 types behave like +Python sequences, and also support additional component addressing methods, +specific to particular constructed type. +

+ + +

+1.3.1 Sequence and Set types +

+ +

+The Sequence and Set types have many similar properties: +

+
    +
  • they can hold any number of inner components of different types +
  • every component has a human-friendly identifier +
  • any component can have a default value +
  • some components can be absent. +
+ +

+However, Sequence type guarantees the ordering of Sequence value components +to match their declaration order. By contrast, components of the +Set type can be ordered to best suite application's needs. +

+ +
+
+Record ::= SEQUENCE {
+  id        INTEGER,
+  room  [0] INTEGER OPTIONAL,
+  house [1] INTEGER DEFAULT 0
+}
+
+
+ +

+Up to this moment, the only method we used for creating new pyasn1 types +is Python sub-classing. With this method, a new, named Python class is created +what mimics type derivation in ASN.1 grammar. However, ASN.1 also allows for +defining anonymous subtypes (room and house components in the example above). +To support anonymous subtyping in pyasn1, a cloning operation on an existing +pyasn1 type object can be invoked what creates a new instance of original +object with possibly modified properties. +

+ +
+
+>>> from pyasn1.type import univ, namedtype, tag
+>>> class Record(univ.Sequence):
+...   componentType = namedtype.NamedTypes(
+...     namedtype.NamedType('id', univ.Integer()),
+...     namedtype.OptionalNamedType(
+...       'room',
+...       univ.Integer().subtype(
+...         implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 0)
+...       )
+...     ),
+...     namedtype.DefaultedNamedType(
+...       'house', 
+...       univ.Integer(0).subtype(
+...         implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 1)
+...       )
+...     )
+...   )
+>>>
+
+
+ +

+All pyasn1 constructed type classes have a class attribute componentType +that represent default type specification. Its value is a NamedTypes object. +

+ +

+The NamedTypes class instance holds a sequence of NameType, OptionalNamedType +or DefaultedNamedType objects which, in turn, refer to pyasn1 type objects that +represent inner SEQUENCE components specification. +

+ +

+Finally, invocation of a subtype() method of pyasn1 type objects in the code +above returns an implicitly tagged copy of original object. +

+ +

+Once a SEQUENCE or SET type is decleared with pyasn1, it can be instantiated +and initialized (continuing the above code): +

+ +
+
+>>> record = Record()
+>>> record.setComponentByName('id', 123)
+>>> print(record.prettyPrint())
+Record:
+ id=123
+>>> 
+>>> record.setComponentByPosition(1, 321)
+>>> print(record.prettyPrint())
+Record:
+ id=123
+ room=321
+>>>
+>>> record.setDefaultComponents()
+>>> print(record.prettyPrint())
+Record:
+ id=123
+ room=321
+ house=0
+
+
+ +

+Inner components of pyasn1 Sequence/Set objects could be accessed using the +following methods: +

+ +
+
+>>> record.getComponentByName('id')
+Integer(123)
+>>> record.getComponentByPosition(1)
+Integer(321)
+>>> record[2]
+Integer(0)
+>>> for idx in range(len(record)):
+...   print(record.getNameByPosition(idx), record.getComponentByPosition(idx))
+id 123
+room 321
+house 0
+>>>
+
+
+ +

+The Set type share all the properties of Sequence type, and additionally +support by-tag component addressing (as all Set components have distinct +types). +

+ +
+
+>>> from pyasn1.type import univ, namedtype, tag
+>>> class Gamer(univ.Set):
+...   componentType = namedtype.NamedTypes(
+...     namedtype.NamedType('score', univ.Integer()),
+...     namedtype.NamedType('player', univ.OctetString()),
+...     namedtype.NamedType('id', univ.ObjectIdentifier())
+...   )
+>>> gamer = Gamer()
+>>> gamer.setComponentByType(univ.Integer().getTagSet(), 121343)
+>>> gamer.setComponentByType(univ.OctetString().getTagSet(), 'Pascal')
+>>> gamer.setComponentByType(univ.ObjectIdentifier().getTagSet(), (1,3,7,2))
+>>> print(gamer.prettyPrint())
+Gamer:
+ score=121343
+ player=b'Pascal'
+ id=1.3.7.2
+>>>
+
+
+ + +

+1.3.2 SequenceOf and SetOf types +

+ +

+Both, SequenceOf and SetOf types resemble an unlimited size list of components. +All the components must be of the same type. +

+ +
+
+Progression ::= SEQUENCE OF INTEGER
+
+arithmeticProgression Progression ::= { 1, 3, 5, 7 }
+
+
+ +

+SequenceOf and SetOf types are expressed by the very similar pyasn1 type +objects. Their components can only be addressed by position and they +both have a property of automatic resize. +

+ +

+To specify inner component type, the componentType class attribute +should refer to another pyasn1 type object. +

+ +
+
+>>> from pyasn1.type import univ
+>>> class Progression(univ.SequenceOf):
+...   componentType = univ.Integer()
+>>> arithmeticProgression = Progression()
+>>> arithmeticProgression.setComponentByPosition(1, 111)
+>>> print(arithmeticProgression.prettyPrint())
+Progression:
+-empty- 111
+>>> arithmeticProgression.setComponentByPosition(0, 100)
+>>> print(arithmeticProgression.prettyPrint())
+Progression:
+100 111
+>>>
+>>> for idx in range(len(arithmeticProgression)):
+...    arithmeticProgression.getComponentByPosition(idx)
+Integer(100)
+Integer(111)
+>>>
+
+
+ +

+Any scalar or constructed pyasn1 type object can serve as an inner component. +Missing components are prohibited in SequenceOf/SetOf value objects. +

+ + +

+1.3.3 Choice type +

+ +

+Values of ASN.1 CHOICE type can contain only a single value of a type from a +list of possible alternatives. Alternatives must be ASN.1 types with +distinct tags for the whole structure to remain unambiguous. Unlike most +other types, CHOICE is an untagged one, e.g. it has no base tag of its own. +

+ +
+
+CodeOrMessage ::= CHOICE {
+  code    INTEGER,
+  message OCTET STRING
+}
+
+
+ +

+In pyasn1 implementation, Choice object behaves like Set but accepts only +a single inner component at a time. It also offers a few additional methods +specific to its behaviour. +

+ +
+
+>>> from pyasn1.type import univ, namedtype
+>>> class CodeOrMessage(univ.Choice):
+...   componentType = namedtype.NamedTypes(
+...     namedtype.NamedType('code', univ.Integer()),
+...     namedtype.NamedType('message', univ.OctetString())
+...   )
+>>>
+>>> codeOrMessage = CodeOrMessage()
+>>> print(codeOrMessage.prettyPrint())
+CodeOrMessage:
+>>> codeOrMessage.setComponentByName('code', 123)
+>>> print(codeOrMessage.prettyPrint())
+CodeOrMessage:
+ code=123
+>>> codeOrMessage.setComponentByName('message', 'my string value')
+>>> print(codeOrMessage.prettyPrint())
+CodeOrMessage:
+ message=b'my string value'
+>>>
+
+
+ +

+Since there could be only a single inner component value in the pyasn1 Choice +value object, either of the following methods could be used for fetching it +(continuing previous code): +

+ +
+
+>>> codeOrMessage.getName()
+'message'
+>>> codeOrMessage.getComponent()
+OctetString(b'my string value')
+>>>
+
+
+ + +

+1.3.4 Any type +

+ +

+The ASN.1 ANY type is a kind of wildcard or placeholder that matches +any other type without knowing it in advance. Like CHOICE type, ANY +has no base tag. +

+ +
+
+Error ::= SEQUENCE {
+  code      INTEGER,
+  parameter ANY DEFINED BY code
+}
+
+
+ +

+The ANY type is frequently used in specifications, where exact type is not +yet agreed upon between communicating parties or the number of possible +alternatives of a type is infinite. +Sometimes an auxiliary selector is kept around to help parties indicate +the kind of ANY payload in effect ("code" in the example above). +

+ +

+Values of the ANY type contain serialized ASN.1 value(s) in form of +an octet string. Therefore pyasn1 Any value object share the properties of +pyasn1 OctetString object. +

+ +
+
+>>> from pyasn1.type import univ
+>>> someValue = univ.Any(b'\x02\x01\x01')
+>>> someValue
+Any(b'\x02\x01\x01')
+>>> str(someValue)
+'\x02\x01\x01'
+>>> bytes(someValue)
+b'\x02\x01\x01'
+>>>
+
+
+ +

+Receiving application is supposed to explicitly deserialize the content of Any +value object, possibly using auxiliary selector for figuring out its ASN.1 +type to pick appropriate decoder. +

+ +

+There will be some more talk and code snippets covering Any type in the codecs +chapters that follow. +

+ +
+ +
+
+ + diff --git a/python/pyasn1/doc/intro.html b/python/pyasn1/doc/intro.html new file mode 100644 index 000000000..3ff18b6ae --- /dev/null +++ b/python/pyasn1/doc/intro.html @@ -0,0 +1,156 @@ + + +PyASN1 reference manual + + + + +
+ + + + +
+ +

+PyASN1 reference manual +

+ +

+written by Ilya Etingof, 2011-2012 +

+ +

+Free and open-source pyasn1 library makes it easier for programmers and +network engineers to develop, debug and experiment with ASN.1-based protocols +using Python programming language as a tool. +

+ +

+Abstract Syntax Notation One +(ASN.1) +is a set of + +ITU standards concered with provisioning instrumentation for developing +data exchange protocols in a robust, clear and interoperabable way for +various IT systems and applications. Most of the efforts are targeting the +following areas: +

    +
  • Data structures: the standard introduces a collection of basic data types +(similar to integers, bits, strings, arrays and records in a programming +language) that can be used for defining complex, possibly nested data +structures representing domain-specific data units. +
  • Serialization protocols: domain-specific data units expressed in ASN.1 +types could be converted into a series of octets for storage or transmission +over the wire and then recovered back into their structured form on the +receiving end. This process is immune to various hardware and software +related dependencies. +
  • Data description language: could be used to describe particular set of +domain-specific data structures and their relationships. Such a description +could be passed to an ASN.1 compiler for automated generation of program +code that represents ASN.1 data structures in language-native environment +and handles data serialization issues. +
+

+ +

+This tutorial and algorithms, implemented by pyasn1 library, are +largely based on the information read in the book + +ASN.1 - Communication between heterogeneous systems +by Olivier Dubuisson. Another relevant resource is + +A Layman's Guide to a Subset of ASN.1, BER, and DER by Burton S. Kaliski. +It's advised to refer to these books for more in-depth knowledge on the +subject of ASN.1. +

+ +

+As of this writing, pyasn1 library implements most of standard ASN.1 data +structures in a rather detailed and feature-rich manner. Another highly +important capability of the library is its data serialization facilities. +The last component of the standard - ASN.1 compiler is planned for +implementation in the future. +

+ +

+The pyasn1 library was designed to follow the pre-1995 ASN.1 specification +(also known as X.208). Later, post 1995, revision (X.680) introduced +significant changes most of which have not yet been supported by pyasn1. +

+ +

+Table of contents +

+ +

+

+ +

+Although pyasn1 software is almost a decade old and used in many production +environments, it still may have bugs and non-implemented pieces. Anyone +who happens to run into such defect is welcome to complain to +pyasn1 mailing list +or better yet fix the issue and send +me the patch. +

+ +

+Typically, pyasn1 is used for building arbitrary protocol support into +various applications. This involves manual translation of ASN.1 data +structures into their pyasn1 implementations. To save time and effort, +data structures for some of the popular protocols are pre-programmed +and kept for further re-use in form of the + +pyasn1-modules package. For instance, many structures for PKI (X.509, +PKCS#*, CRMF, OCSP), LDAP and SNMP are present. +Applications authors are advised to import and use relevant modules +from that package whenever needed protocol structures are already +there. New protocol modules contributions are welcome. +

+ +

+And finally, the latest pyasn1 package revision is available for free +download from +project home and +also from the +Python package repository. +

+ +
+ +
+
+ + diff --git a/python/pyasn1/doc/pyasn1-tutorial.html b/python/pyasn1/doc/pyasn1-tutorial.html new file mode 100644 index 000000000..2eb82f1e9 --- /dev/null +++ b/python/pyasn1/doc/pyasn1-tutorial.html @@ -0,0 +1,2405 @@ + + +PyASN1 programmer's manual + + + + +
+ + + + +
+ +

+PyASN1 programmer's manual +

+ +

+written by Ilya Etingof, 2011-2012 +

+ +

+Free and open-source pyasn1 library makes it easier for programmers and +network engineers to develop, debug and experiment with ASN.1-based protocols +using Python programming language as a tool. +

+ +

+Abstract Syntax Notation One +(ASN.1) +is a set of + +ITU standards concered with provisioning instrumentation for developing +data exchange protocols in a robust, clear and interoperabable way for +various IT systems and applications. Most of the efforts are targeting the +following areas: +

    +
  • Data structures: the standard introduces a collection of basic data types +(similar to integers, bits, strings, arrays and records in a programming +language) that can be used for defining complex, possibly nested data +structures representing domain-specific data units. +
  • Serialization protocols: domain-specific data units expressed in ASN.1 +types could be converted into a series of octets for storage or transmission +over the wire and then recovered back into their structured form on the +receiving end. This process is immune to various hardware and software +related dependencies. +
  • Data description language: could be used to describe particular set of +domain-specific data structures and their relationships. Such a description +could be passed to an ASN.1 compiler for automated generation of program +code that represents ASN.1 data structures in language-native environment +and handles data serialization issues. +
+

+ +

+This tutorial and algorithms, implemented by pyasn1 library, are +largely based on the information read in the book + +ASN.1 - Communication between heterogeneous systems +by Olivier Dubuisson. Another relevant resource is + +A Layman's Guide to a Subset of ASN.1, BER, and DER by Burton S. Kaliski. +It's advised to refer to these books for more in-depth knowledge on the +subject of ASN.1. +

+ +

+As of this writing, pyasn1 library implements most of standard ASN.1 data +structures in a rather detailed and feature-rich manner. Another highly +important capability of the library is its data serialization facilities. +The last component of the standard - ASN.1 compiler is planned for +implementation in the future. +

+ +

+The pyasn1 library was designed to follow the pre-1995 ASN.1 specification +(also known as X.208). Later, post 1995, revision (X.680) introduced +significant changes most of which have not yet been supported by pyasn1. +

+ +

+Table of contents +

+ +

+

+ + + +

+1. Data model for ASN.1 types +

+ +

+All ASN.1 types could be categorized into two groups: scalar (also called +simple or primitive) and constructed. The first group is populated by +well-known types like Integer or String. Members of constructed group +hold other types (simple or constructed) as their inner components, thus +they are semantically close to a programming language records or lists. +

+ +

+In pyasn1, all ASN.1 types and values are implemented as Python objects. +The same pyasn1 object can represent either ASN.1 type and/or value +depending of the presense of value initializer on object instantiation. +We will further refer to these as pyasn1 type object versus pyasn1 +value object. +

+ +

+Primitive ASN.1 types are implemented as immutable scalar objects. There values +could be used just like corresponding native Python values (integers, +strings/bytes etc) and freely mixed with them in expressions. +

+ +
+
+>>> from pyasn1.type import univ
+>>> asn1IntegerValue = univ.Integer(12)
+>>> asn1IntegerValue - 2
+10
+>>> univ.OctetString('abc') == 'abc'
+True   # Python 2
+>>> univ.OctetString(b'abc') == b'abc'
+True   # Python 3
+
+
+ +

+It would be an error to perform an operation on a pyasn1 type object +as it holds no value to deal with: +

+ +
+
+>>> from pyasn1.type import univ
+>>> asn1IntegerType = univ.Integer()
+>>> asn1IntegerType - 2
+...
+pyasn1.error.PyAsn1Error: No value for __coerce__()
+
+
+ + +

+1.1 Scalar types +

+ +

+In the sub-sections that follow we will explain pyasn1 mapping to those +primitive ASN.1 types. Both, ASN.1 notation and corresponding pyasn1 +syntax will be given in each case. +

+ + +

+1.1.1 Boolean type +

+ +

+This is the simplest type those values could be either True or False. +

+ +
+
+;; type specification
+FunFactorPresent ::= BOOLEAN
+
+;; values declaration and assignment
+pythonFunFactor FunFactorPresent ::= TRUE
+cobolFunFactor FunFactorPresent :: FALSE
+
+
+ +

+And here's pyasn1 version of it: +

+ +
+
+>>> from pyasn1.type import univ
+>>> class FunFactorPresent(univ.Boolean): pass
+... 
+>>> pythonFunFactor = FunFactorPresent(True)
+>>> cobolFunFactor = FunFactorPresent(False)
+>>> pythonFunFactor
+FunFactorPresent('True(1)')
+>>> cobolFunFactor
+FunFactorPresent('False(0)')
+>>> pythonFunFactor == cobolFunFactor
+False
+>>>
+
+
+ + +

+1.1.2 Null type +

+ +

+The NULL type is sometimes used to express the absense of any information. +

+ +
+
+;; type specification
+Vote ::= CHOICE {
+  agreed BOOLEAN,
+  skip NULL
+}
+
+ +;; value declaration and assignment +myVote Vote ::= skip:NULL + + +

+We will explain the CHOICE type later in this paper, meanwhile the NULL +type: +

+ +
+
+>>> from pyasn1.type import univ
+>>> skip = univ.Null()
+>>> skip
+Null('')
+>>>
+
+
+ + +

+1.1.3 Integer type +

+ +

+ASN.1 defines the values of Integer type as negative or positive of whatever +length. This definition plays nicely with Python as the latter places no +limit on Integers. However, some ASN.1 implementations may impose certain +limits of integer value ranges. Keep that in mind when designing new +data structures. +

+ +
+
+;; values specification
+age-of-universe INTEGER ::= 13750000000
+mean-martian-surface-temperature INTEGER ::= -63
+
+
+ +

+A rather strigntforward mapping into pyasn1: +

+ +
+
+>>> from pyasn1.type import univ
+>>> ageOfUniverse = univ.Integer(13750000000)
+>>> ageOfUniverse
+Integer(13750000000)
+>>>
+>>> meanMartianSurfaceTemperature = univ.Integer(-63)
+>>> meanMartianSurfaceTemperature
+Integer(-63)
+>>>
+
+
+ +

+ASN.1 allows to assign human-friendly names to particular values of +an INTEGER type. +

+ +
+
+Temperature ::= INTEGER {
+  freezing(0),
+  boiling(100) 
+}
+
+
+ +

+The Temperature type expressed in pyasn1: +

+ +
+
+>>> from pyasn1.type import univ, namedval
+>>> class Temperature(univ.Integer):
+...   namedValues = namedval.NamedValues(('freezing', 0), ('boiling', 100))
+...
+>>> t = Temperature(0)
+>>> t
+Temperature('freezing(0)')
+>>> t + 1
+Temperature(1)
+>>> t + 100
+Temperature('boiling(100)')
+>>> t = Temperature('boiling')
+>>> t
+Temperature('boiling(100)')
+>>> Temperature('boiling') / 2
+Temperature(50)
+>>> -1 < Temperature('freezing')
+True
+>>> 47 > Temperature('boiling')
+False
+>>>
+
+
+ +

+These values labels have no effect on Integer type operations, any value +still could be assigned to a type (information on value constraints will +follow further in this paper). +

+ + +

+1.1.4 Enumerated type +

+ +

+ASN.1 Enumerated type differs from an Integer type in a number of ways. +Most important is that its instance can only hold a value that belongs +to a set of values specified on type declaration. +

+ +
+
+error-status ::= ENUMERATED {
+  no-error(0),
+  authentication-error(10),
+  authorization-error(20),
+  general-failure(51)
+}
+
+
+ +

+When constructing Enumerated type we will use two pyasn1 features: values +labels (as mentioned above) and value constraint (will be described in +more details later on). +

+ +
+
+>>> from pyasn1.type import univ, namedval, constraint
+>>> class ErrorStatus(univ.Enumerated):
+...   namedValues = namedval.NamedValues(
+...        ('no-error', 0),
+...        ('authentication-error', 10),
+...        ('authorization-error', 20),
+...        ('general-failure', 51)
+...   )
+...   subtypeSpec = univ.Enumerated.subtypeSpec + \
+...                    constraint.SingleValueConstraint(0, 10, 20, 51)
+...
+>>> errorStatus = univ.ErrorStatus('no-error')
+>>> errorStatus
+ErrorStatus('no-error(0)')
+>>> errorStatus == univ.ErrorStatus('general-failure')
+False
+>>> univ.ErrorStatus('non-existing-state')
+Traceback (most recent call last):
+...
+pyasn1.error.PyAsn1Error: Can't coerce non-existing-state into integer
+>>>
+
+
+ +

+Particular integer values associated with Enumerated value states +have no meaning. They should not be used as such or in any kind of +math operation. Those integer values are only used by codecs to +transfer state from one entity to another. +

+ + +

+1.1.5 Real type +

+ +

+Values of the Real type are a three-component tuple of mantissa, base and +exponent. All three are integers. +

+ +
+
+pi ::= REAL { mantissa 314159, base 10, exponent -5 }
+
+
+ +

+Corresponding pyasn1 objects can be initialized with either a three-component +tuple or a Python float. Infinite values could be expressed in a way, +compatible with Python float type. + +

+ +
+
+>>> from pyasn1.type import univ
+>>> pi = univ.Real((314159, 10, -5))
+>>> pi
+Real((314159, 10,-5))
+>>> float(pi)
+3.14159
+>>> pi == univ.Real(3.14159)
+True
+>>> univ.Real('inf')
+Real('inf')
+>>> univ.Real('-inf') == float('-inf')
+True
+>>>
+
+
+ +

+If a Real object is initialized from a Python float or yielded by a math +operation, the base is set to decimal 10 (what affects encoding). +

+ + +

+1.1.6 Bit string type +

+ +

+ASN.1 BIT STRING type holds opaque binary data of an arbitrarily length. +A BIT STRING value could be initialized by either a binary (base 2) or +hex (base 16) value. +

+ +
+
+public-key BIT STRING ::= '1010111011110001010110101101101
+                           1011000101010000010110101100010
+                           0110101010000111101010111111110'B
+
+signature  BIT STRING ::= 'AF01330CD932093392100B39FF00DE0'H
+
+
+ +

+The pyasn1 BitString objects can initialize from native ASN.1 notation +(base 2 or base 16 strings) or from a Python tuple of binary components. +

+ +
+
+>>> from pyasn1.type import univ
+>>> publicKey = univ.BitString(
+...          "'1010111011110001010110101101101"
+...          "1011000101010000010110101100010"
+...          "0110101010000111101010111111110'B"
+)
+>>> publicKey
+BitString("'10101110111100010101101011011011011000101010000010110101100010\
+0110101010000111101010111111110'B")
+>>> signature = univ.BitString(
+...          "'AF01330CD932093392100B39FF00DE0'H"
+... )
+>>> signature
+BitString("'101011110000000100110011000011001101100100110010000010010011001\
+1100100100001000000001011001110011111111100000000110111100000'B")
+>>> fingerprint = univ.BitString(
+...          (1, 0, 1, 1 ,0, 1, 1, 1, 0, 1, 0, 1)
+... )
+>>> fingerprint
+BitString("'101101110101'B")
+>>>
+
+
+ +

+Another BIT STRING initialization method supported by ASN.1 notation +is to specify only 1-th bits along with their human-friendly label +and bit offset relative to the beginning of the bit string. With this +method, all not explicitly mentioned bits are doomed to be zeros. +

+ +
+
+bit-mask  BIT STRING ::= {
+  read-flag(0),
+  write-flag(2),
+  run-flag(4)
+}
+
+
+ +

+To express this in pyasn1, we will employ the named values feature (as with +Enumeration type). +

+ +
+
+>>> from pyasn1.type import univ, namedval
+>>> class BitMask(univ.BitString):
+...   namedValues = namedval.NamedValues(
+...        ('read-flag', 0),
+...        ('write-flag', 2),
+...        ('run-flag', 4)
+... )
+>>> bitMask = BitMask('read-flag,run-flag')
+>>> bitMask
+BitMask("'10001'B")
+>>> tuple(bitMask)
+(1, 0, 0, 0, 1)
+>>> bitMask[4]
+1
+>>>
+
+
+ +

+The BitString objects mimic the properties of Python tuple type in part +of immutable sequence object protocol support. +

+ + +

+1.1.7 OctetString type +

+ +

+The OCTET STRING type is a confusing subject. According to ASN.1 +specification, this type is similar to BIT STRING, the major difference +is that the former operates in 8-bit chunks of data. What is important +to note, is that OCTET STRING was NOT designed to handle text strings - the +standard provides many other types specialized for text content. For that +reason, ASN.1 forbids to initialize OCTET STRING values with "quoted text +strings", only binary or hex initializers, similar to BIT STRING ones, +are allowed. +

+ +
+
+thumbnail OCTET STRING ::= '1000010111101110101111000000111011'B
+thumbnail OCTET STRING ::= 'FA9823C43E43510DE3422'H
+
+
+ +

+However, ASN.1 users (e.g. protocols designers) seem to ignore the original +purpose of the OCTET STRING type - they used it for handling all kinds of +data, including text strings. +

+ +
+
+welcome-message OCTET STRING ::= "Welcome to ASN.1 wilderness!"
+
+
+ +

+In pyasn1, we have taken a liberal approach and allowed both BIT STRING +style and quoted text initializers for the OctetString objects. To avoid +possible collisions, quoted text is the default initialization syntax. +

+ +
+
+>>> from pyasn1.type import univ
+>>> thumbnail = univ.OctetString(
+...    binValue='1000010111101110101111000000111011'
+... )
+>>> thumbnail
+OctetString(hexValue='85eebcec0')
+>>> thumbnail = univ.OctetString(
+...    hexValue='FA9823C43E43510DE3422'
+... )
+>>> thumbnail
+OctetString(hexValue='fa9823c43e4351de34220')
+>>>
+
+
+ +

+Most frequent usage of the OctetString class is to instantiate it with +a text string. +

+ +
+
+>>> from pyasn1.type import univ
+>>> welcomeMessage = univ.OctetString('Welcome to ASN.1 wilderness!')
+>>> welcomeMessage
+OctetString(b'Welcome to ASN.1 wilderness!')
+>>> print('%s' % welcomeMessage)
+Welcome to ASN.1 wilderness!
+>>> welcomeMessage[11:16]
+OctetString(b'ASN.1')
+>>> 
+
+
+ +

+OctetString objects support the immutable sequence object protocol. +In other words, they behave like Python 3 bytes (or Python 2 strings). +

+ +

+When running pyasn1 on Python 3, it's better to use the bytes objects for +OctetString instantiation, as it's more reliable and efficient. +

+ +

+Additionally, OctetString's can also be instantiated with a sequence of +8-bit integers (ASCII codes). +

+ +
+
+>>> univ.OctetString((77, 101, 101, 103, 111))
+OctetString(b'Meego')
+
+
+ +

+It is sometimes convenient to express OctetString instances as 8-bit +characters (Python 3 bytes or Python 2 strings) or 8-bit integers. +

+ +
+
+>>> octetString = univ.OctetString('ABCDEF')
+>>> octetString.asNumbers()
+(65, 66, 67, 68, 69, 70)
+>>> octetString.asOctets()
+b'ABCDEF'
+
+
+ + +

+1.1.8 ObjectIdentifier type +

+ +

+Values of the OBJECT IDENTIFIER type are sequences of integers that could +be used to identify virtually anything in the world. Various ASN.1-based +protocols employ OBJECT IDENTIFIERs for their own identification needs. +

+ +
+
+internet-id OBJECT IDENTIFIER ::= {
+  iso(1) identified-organization(3) dod(6) internet(1)
+}
+
+
+ +

+One of the natural ways to map OBJECT IDENTIFIER type into a Python +one is to use Python tuples of integers. So this approach is taken by +pyasn1. +

+ +
+
+>>> from pyasn1.type import univ
+>>> internetId = univ.ObjectIdentifier((1, 3, 6, 1))
+>>> internetId
+ObjectIdentifier('1.3.6.1')
+>>> internetId[2]
+6
+>>> internetId[1:3]
+ObjectIdentifier('3.6')
+
+
+ +

+A more human-friendly "dotted" notation is also supported. +

+ +
+
+>>> from pyasn1.type import univ
+>>> univ.ObjectIdentifier('1.3.6.1')
+ObjectIdentifier('1.3.6.1')
+
+
+ +

+Symbolic names of the arcs of object identifier, sometimes present in +ASN.1 specifications, are not preserved and used in pyasn1 objects. +

+ +

+The ObjectIdentifier objects mimic the properties of Python tuple type in +part of immutable sequence object protocol support. +

+ + +

+1.1.9 Character string types +

+ +

+ASN.1 standard introduces a diverse set of text-specific types. All of them +were designed to handle various types of characters. Some of these types seem +be obsolete nowdays, as their target technologies are gone. Another issue +to be aware of is that raw OCTET STRING type is sometimes used in practice +by ASN.1 users instead of specialized character string types, despite +explicit prohibition imposed by ASN.1 specification. +

+ +

+The two types are specific to ASN.1 are NumericString and PrintableString. +

+ +
+
+welcome-message ::= PrintableString {
+  "Welcome to ASN.1 text types"
+}
+
+dial-pad-numbers ::= NumericString {
+  "0", "1", "2", "3", "4", "5", "6", "7", "8", "9"
+}
+
+
+ +

+Their pyasn1 implementations are: +

+ +
+
+>>> from pyasn1.type import char
+>>> '%s' % char.PrintableString("Welcome to ASN.1 text types")
+'Welcome to ASN.1 text types'
+>>> dialPadNumbers = char.NumericString(
+      "0" "1" "2" "3" "4" "5" "6" "7" "8" "9"
+)
+>>> dialPadNumbers
+NumericString(b'0123456789')
+>>>
+
+
+ +

+The following types came to ASN.1 from ISO standards on character sets. +

+ +
+
+>>> from pyasn1.type import char
+>>> char.VisibleString("abc")
+VisibleString(b'abc')
+>>> char.IA5String('abc')
+IA5String(b'abc')
+>>> char.TeletexString('abc')
+TeletexString(b'abc')
+>>> char.VideotexString('abc')
+VideotexString(b'abc')
+>>> char.GraphicString('abc')
+GraphicString(b'abc')
+>>> char.GeneralString('abc')
+GeneralString(b'abc')
+>>>
+
+
+ +

+The last three types are relatively recent addition to the family of +character string types: UniversalString, BMPString, UTF8String. +

+ +
+
+>>> from pyasn1.type import char
+>>> char.UniversalString("abc")
+UniversalString(b'abc')
+>>> char.BMPString('abc')
+BMPString(b'abc')
+>>> char.UTF8String('abc')
+UTF8String(b'abc')
+>>> utf8String = char.UTF8String('У попа была собака')
+>>> utf8String
+UTF8String(b'\xd0\xa3 \xd0\xbf\xd0\xbe\xd0\xbf\xd0\xb0 \xd0\xb1\xd1\x8b\xd0\xbb\xd0\xb0 \
+\xd1\x81\xd0\xbe\xd0\xb1\xd0\xb0\xd0\xba\xd0\xb0')
+>>> print(utf8String)
+У попа была собака
+>>>
+
+
+ +

+In pyasn1, all character type objects behave like Python strings. None of +them is currently constrained in terms of valid alphabet so it's up to +the data source to keep an eye on data validation for these types. +

+ + +

+1.1.10 Useful types +

+ +

+There are three so-called useful types defined in the standard: +ObjectDescriptor, GeneralizedTime, UTCTime. They all are subtypes +of GraphicString or VisibleString types therefore useful types are +character string types. +

+ +

+It's advised by the ASN.1 standard to have an instance of ObjectDescriptor +type holding a human-readable description of corresponding instance of +OBJECT IDENTIFIER type. There are no formal linkage between these instances +and provision for ObjectDescriptor uniqueness in the standard. +

+ +
+
+>>> from pyasn1.type import useful
+>>> descrBER = useful.ObjectDescriptor(
+      "Basic encoding of a single ASN.1 type"
+)
+>>> 
+
+
+ +

+GeneralizedTime and UTCTime types are designed to hold a human-readable +timestamp in a universal and unambiguous form. The former provides +more flexibility in notation while the latter is more strict but has +Y2K issues. +

+ +
+
+;; Mar 8 2010 12:00:00 MSK
+moscow-time GeneralizedTime ::= "20110308120000.0"
+;; Mar 8 2010 12:00:00 UTC
+utc-time GeneralizedTime ::= "201103081200Z"
+;; Mar 8 1999 12:00:00 UTC
+utc-time UTCTime ::= "9803081200Z"
+
+
+ +
+
+>>> from pyasn1.type import useful
+>>> moscowTime = useful.GeneralizedTime("20110308120000.0")
+>>> utcTime = useful.UTCTime("9803081200Z")
+>>> 
+
+
+ +

+Despite their intended use, these types possess no special, time-related, +handling in pyasn1. They are just printable strings. +

+ + +

+1.2 Tagging +

+ +

+In order to continue with the Constructed ASN.1 types, we will first have +to introduce the concept of tagging (and its pyasn1 implementation), as +some of the Constructed types rely upon the tagging feature. +

+ +

+When a value is coming into an ASN.1-based system (received from a network +or read from some storage), the receiving entity has to determine the +type of the value to interpret and verify it accordingly. +

+ +

+Historically, the first data serialization protocol introduced in +ASN.1 was BER (Basic Encoding Rules). According to BER, any serialized +value is packed into a triplet of (Type, Length, Value) where Type is a +code that identifies the value (which is called tag in ASN.1), +length is the number of bytes occupied by the value in its serialized form +and value is ASN.1 value in a form suitable for serial transmission or storage. +

+ +

+For that reason almost every ASN.1 type has a tag (which is actually a +BER type) associated with it by default. +

+ +

+An ASN.1 tag could be viewed as a tuple of three numbers: +(Class, Format, Number). While Number identifies a tag, Class component +is used to create scopes for Numbers. Four scopes are currently defined: +UNIVERSAL, context-specific, APPLICATION and PRIVATE. The Format component +is actually a one-bit flag - zero for tags associated with scalar types, +and one for constructed types (will be discussed later on). +

+ +
+
+MyIntegerType ::= [12] INTEGER
+MyOctetString ::= [APPLICATION 0] OCTET STRING
+
+
+ +

+In pyasn1, tags are implemented as immutable, tuple-like objects: +

+ +
+
+>>> from pyasn1.type import tag
+>>> myTag = tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 10)
+>>> myTag
+Tag(tagClass=128, tagFormat=0, tagId=10)
+>>> tuple(myTag)
+(128, 0, 10)
+>>> myTag[2]
+10
+>>> myTag == tag.Tag(tag.tagClassApplication, tag.tagFormatSimple, 10)
+False
+>>>
+
+
+ +

+Default tag, associated with any ASN.1 type, could be extended or replaced +to make new type distinguishable from its ancestor. The standard provides +two modes of tag mangling - IMPLICIT and EXPLICIT. +

+ +

+EXPLICIT mode works by appending new tag to the existing ones thus creating +an ordered set of tags. This set will be considered as a whole for type +identification and encoding purposes. Important property of EXPLICIT tagging +mode is that it preserves base type information in encoding what makes it +possible to completely recover type information from encoding. +

+ +

+When tagging in IMPLICIT mode, the outermost existing tag is dropped and +replaced with a new one. +

+ +
+
+MyIntegerType ::= [12] IMPLICIT INTEGER
+MyOctetString ::= [APPLICATION 0] EXPLICIT OCTET STRING
+
+
+ +

+To model both modes of tagging, a specialized container TagSet object (holding +zero, one or more Tag objects) is used in pyasn1. +

+ +
+
+>>> from pyasn1.type import tag
+>>> tagSet = tag.TagSet(
+...   tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 10), # base tag
+...   tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 10)  # effective tag
+... )
+>>> tagSet
+TagSet(Tag(tagClass=128, tagFormat=0, tagId=10))
+>>> tagSet.getBaseTag()
+Tag(tagClass=128, tagFormat=0, tagId=10)
+>>> tagSet = tagSet.tagExplicitly(
+...    tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 20)
+... )
+>>> tagSet
+TagSet(Tag(tagClass=128, tagFormat=0, tagId=10), 
+       Tag(tagClass=128, tagFormat=32, tagId=20))
+>>> tagSet = tagSet.tagExplicitly(
+...    tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 30)
+... )
+>>> tagSet
+TagSet(Tag(tagClass=128, tagFormat=0, tagId=10), 
+       Tag(tagClass=128, tagFormat=32, tagId=20), 
+       Tag(tagClass=128, tagFormat=32, tagId=30))
+>>> tagSet = tagSet.tagImplicitly(
+...    tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 40)
+... )
+>>> tagSet
+TagSet(Tag(tagClass=128, tagFormat=0, tagId=10),
+       Tag(tagClass=128, tagFormat=32, tagId=20),
+       Tag(tagClass=128, tagFormat=32, tagId=40))
+>>> 
+
+
+ +

+As a side note: the "base tag" concept (accessible through the getBaseTag() +method) is specific to pyasn1 -- the base tag is used to identify the original +ASN.1 type of an object in question. Base tag is never occurs in encoding +and is mostly used internally by pyasn1 for choosing type-specific data +processing algorithms. The "effective tag" is the one that always appears in +encoding and is used on tagSets comparation. +

+ +

+Any two TagSet objects could be compared to see if one is a derivative +of the other. Figuring this out is also useful in cases when a type-specific +data processing algorithms are to be chosen. +

+ +
+
+>>> from pyasn1.type import tag
+>>> tagSet1 = tag.TagSet(
+...   tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 10) # base tag
+...   tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 10) # effective tag
+... )
+>>> tagSet2 = tagSet1.tagExplicitly(
+...    tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 20)
+... )
+>>> tagSet1.isSuperTagSetOf(tagSet2)
+True
+>>> tagSet2.isSuperTagSetOf(tagSet1)
+False
+>>> 
+
+
+ +

+We will complete this discussion on tagging with a real-world example. The +following ASN.1 tagged type: +

+ +
+
+MyIntegerType ::= [12] EXPLICIT INTEGER
+
+
+ +

+could be expressed in pyasn1 like this: +

+ +
+
+>>> from pyasn1.type import univ, tag
+>>> class MyIntegerType(univ.Integer):
+...   tagSet = univ.Integer.tagSet.tagExplicitly(
+...        tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 12)
+...        )
+>>> myInteger = MyIntegerType(12345)
+>>> myInteger.getTagSet()
+TagSet(Tag(tagClass=0, tagFormat=0, tagId=2), 
+       Tag(tagClass=128, tagFormat=32, tagId=12))
+>>>
+
+
+ +

+Referring to the above code, the tagSet class attribute is a property of any +pyasn1 type object that assigns default tagSet to a pyasn1 value object. This +default tagSet specification can be ignored and effectively replaced by some +other tagSet value passed on object instantiation. +

+ +

+It's important to understand that the tag set property of pyasn1 type/value +object can never be modifed in place. In other words, a pyasn1 type/value +object can never change its tags. The only way is to create a new pyasn1 +type/value object and associate different tag set with it. +

+ + + +

+1.3 Constructed types +

+ +

+Besides scalar types, ASN.1 specifies so-called constructed ones - these +are capable of holding one or more values of other types, both scalar +and constructed. +

+ +

+In pyasn1 implementation, constructed ASN.1 types behave like +Python sequences, and also support additional component addressing methods, +specific to particular constructed type. +

+ + +

+1.3.1 Sequence and Set types +

+ +

+The Sequence and Set types have many similar properties: +

+
    +
  • they can hold any number of inner components of different types +
  • every component has a human-friendly identifier +
  • any component can have a default value +
  • some components can be absent. +
+ +

+However, Sequence type guarantees the ordering of Sequence value components +to match their declaration order. By contrast, components of the +Set type can be ordered to best suite application's needs. +

+ +
+
+Record ::= SEQUENCE {
+  id        INTEGER,
+  room  [0] INTEGER OPTIONAL,
+  house [1] INTEGER DEFAULT 0
+}
+
+
+ +

+Up to this moment, the only method we used for creating new pyasn1 types +is Python sub-classing. With this method, a new, named Python class is created +what mimics type derivation in ASN.1 grammar. However, ASN.1 also allows for +defining anonymous subtypes (room and house components in the example above). +To support anonymous subtyping in pyasn1, a cloning operation on an existing +pyasn1 type object can be invoked what creates a new instance of original +object with possibly modified properties. +

+ +
+
+>>> from pyasn1.type import univ, namedtype, tag
+>>> class Record(univ.Sequence):
+...   componentType = namedtype.NamedTypes(
+...     namedtype.NamedType('id', univ.Integer()),
+...     namedtype.OptionalNamedType(
+...       'room',
+...       univ.Integer().subtype(implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 0))
+...     ),
+...     namedtype.DefaultedNamedType(
+...       'house', 
+...       univ.Integer(0).subtype(implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 1))
+...     )
+...   )
+>>>
+
+
+ +

+All pyasn1 constructed type classes have a class attribute componentType +that represent default type specification. Its value is a NamedTypes object. +

+ +

+The NamedTypes class instance holds a sequence of NameType, OptionalNamedType +or DefaultedNamedType objects which, in turn, refer to pyasn1 type objects that +represent inner SEQUENCE components specification. +

+ +

+Finally, invocation of a subtype() method of pyasn1 type objects in the code +above returns an implicitly tagged copy of original object. +

+ +

+Once a SEQUENCE or SET type is decleared with pyasn1, it can be instantiated +and initialized (continuing the above code): +

+ +
+
+>>> record = Record()
+>>> record.setComponentByName('id', 123)
+>>> print(record.prettyPrint())
+Record:
+ id=123
+>>> 
+>>> record.setComponentByPosition(1, 321)
+>>> print(record.prettyPrint())
+Record:
+ id=123
+ room=321
+>>>
+>>> record.setDefaultComponents()
+>>> print(record.prettyPrint())
+Record:
+ id=123
+ room=321
+ house=0
+
+
+ +

+Inner components of pyasn1 Sequence/Set objects could be accessed using the +following methods: +

+ +
+
+>>> record.getComponentByName('id')
+Integer(123)
+>>> record.getComponentByPosition(1)
+Integer(321)
+>>> record[2]
+Integer(0)
+>>> for idx in range(len(record)):
+...   print(record.getNameByPosition(idx), record.getComponentByPosition(idx))
+id 123
+room 321
+house 0
+>>>
+
+
+ +

+The Set type share all the properties of Sequence type, and additionally +support by-tag component addressing (as all Set components have distinct +types). +

+ +
+
+>>> from pyasn1.type import univ, namedtype, tag
+>>> class Gamer(univ.Set):
+...   componentType = namedtype.NamedTypes(
+...     namedtype.NamedType('score', univ.Integer()),
+...     namedtype.NamedType('player', univ.OctetString()),
+...     namedtype.NamedType('id', univ.ObjectIdentifier())
+...   )
+>>> gamer = Gamer()
+>>> gamer.setComponentByType(univ.Integer().getTagSet(), 121343)
+>>> gamer.setComponentByType(univ.OctetString().getTagSet(), 'Pascal')
+>>> gamer.setComponentByType(univ.ObjectIdentifier().getTagSet(), (1,3,7,2))
+>>> print(gamer.prettyPrint())
+Gamer:
+ score=121343
+ player=b'Pascal'
+ id=1.3.7.2
+>>>
+
+
+ + +

+1.3.2 SequenceOf and SetOf types +

+ +

+Both, SequenceOf and SetOf types resemble an unlimited size list of components. +All the components must be of the same type. +

+ +
+
+Progression ::= SEQUENCE OF INTEGER
+
+arithmeticProgression Progression ::= { 1, 3, 5, 7 }
+
+
+ +

+SequenceOf and SetOf types are expressed by the very similar pyasn1 type +objects. Their components can only be addressed by position and they +both have a property of automatic resize. +

+ +

+To specify inner component type, the componentType class attribute +should refer to another pyasn1 type object. +

+ +
+
+>>> from pyasn1.type import univ
+>>> class Progression(univ.SequenceOf):
+...   componentType = univ.Integer()
+>>> arithmeticProgression = Progression()
+>>> arithmeticProgression.setComponentByPosition(1, 111)
+>>> print(arithmeticProgression.prettyPrint())
+Progression:
+-empty- 111
+>>> arithmeticProgression.setComponentByPosition(0, 100)
+>>> print(arithmeticProgression.prettyPrint())
+Progression:
+100 111
+>>>
+>>> for idx in range(len(arithmeticProgression)):
+...    arithmeticProgression.getComponentByPosition(idx)
+Integer(100)
+Integer(111)
+>>>
+
+
+ +

+Any scalar or constructed pyasn1 type object can serve as an inner component. +Missing components are prohibited in SequenceOf/SetOf value objects. +

+ + +

+1.3.3 Choice type +

+ +

+Values of ASN.1 CHOICE type can contain only a single value of a type from a +list of possible alternatives. Alternatives must be ASN.1 types with +distinct tags for the whole structure to remain unambiguous. Unlike most +other types, CHOICE is an untagged one, e.g. it has no base tag of its own. +

+ +
+
+CodeOrMessage ::= CHOICE {
+  code    INTEGER,
+  message OCTET STRING
+}
+
+
+ +

+In pyasn1 implementation, Choice object behaves like Set but accepts only +a single inner component at a time. It also offers a few additional methods +specific to its behaviour. +

+ +
+
+>>> from pyasn1.type import univ, namedtype
+>>> class CodeOrMessage(univ.Choice):
+...   componentType = namedtype.NamedTypes(
+...     namedtype.NamedType('code', univ.Integer()),
+...     namedtype.NamedType('message', univ.OctetString())
+...   )
+>>>
+>>> codeOrMessage = CodeOrMessage()
+>>> print(codeOrMessage.prettyPrint())
+CodeOrMessage:
+>>> codeOrMessage.setComponentByName('code', 123)
+>>> print(codeOrMessage.prettyPrint())
+CodeOrMessage:
+ code=123
+>>> codeOrMessage.setComponentByName('message', 'my string value')
+>>> print(codeOrMessage.prettyPrint())
+CodeOrMessage:
+ message=b'my string value'
+>>>
+
+
+ +

+Since there could be only a single inner component value in the pyasn1 Choice +value object, either of the following methods could be used for fetching it +(continuing previous code): +

+ +
+
+>>> codeOrMessage.getName()
+'message'
+>>> codeOrMessage.getComponent()
+OctetString(b'my string value')
+>>>
+
+
+ + +

+1.3.4 Any type +

+ +

+The ASN.1 ANY type is a kind of wildcard or placeholder that matches +any other type without knowing it in advance. Like CHOICE type, ANY +has no base tag. +

+ +
+
+Error ::= SEQUENCE {
+  code      INTEGER,
+  parameter ANY DEFINED BY code
+}
+
+
+ +

+The ANY type is frequently used in specifications, where exact type is not +yet agreed upon between communicating parties or the number of possible +alternatives of a type is infinite. +Sometimes an auxiliary selector is kept around to help parties indicate +the kind of ANY payload in effect ("code" in the example above). +

+ +

+Values of the ANY type contain serialized ASN.1 value(s) in form of +an octet string. Therefore pyasn1 Any value object share the properties of +pyasn1 OctetString object. +

+ +
+
+>>> from pyasn1.type import univ
+>>> someValue = univ.Any(b'\x02\x01\x01')
+>>> someValue
+Any(b'\x02\x01\x01')
+>>> str(someValue)
+'\x02\x01\x01'
+>>> bytes(someValue)
+b'\x02\x01\x01'
+>>>
+
+
+ +

+Receiving application is supposed to explicitly deserialize the content of Any +value object, possibly using auxiliary selector for figuring out its ASN.1 +type to pick appropriate decoder. +

+ +

+There will be some more talk and code snippets covering Any type in the codecs +chapters that follow. +

+ + +

+1.4 Subtype constraints +

+ +

+Most ASN.1 types can correspond to an infinite set of values. To adapt to +particular application's data model and needs, ASN.1 provides a mechanism +for limiting the infinite set to values, that make sense in particular case. +

+ +

+Imposing value constraints on an ASN.1 type can also be seen as creating +a subtype from its base type. +

+ +

+In pyasn1, constraints take shape of immutable objects capable +of evaluating given value against constraint-specific requirements. +Constraint object is a property of pyasn1 type. Like TagSet property, +associated with every pyasn1 type, constraints can never be modified +in place. The only way to modify pyasn1 type constraint is to associate +new constraint object to a new pyasn1 type object. +

+ +

+A handful of different flavors of constraints are defined in ASN.1. +We will discuss them one by one in the following chapters and also explain +how to combine and apply them to types. +

+ + +

+1.4.1 Single value constraint +

+ +

+This kind of constraint allows for limiting type to a finite, specified set +of values. +

+ +
+
+DialButton ::= OCTET STRING (
+  "0" | "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9"
+)
+
+
+ +

+Its pyasn1 implementation would look like: +

+ +
+
+>>> from pyasn1.type import constraint
+>>> c = constraint.SingleValueConstraint(
+  '0','1','2','3','4','5','6','7','8','9'
+)
+>>> c
+SingleValueConstraint(0, 1, 2, 3, 4, 5, 6, 7, 8, 9)
+>>> c('0')
+>>> c('A')
+Traceback (most recent call last):
+...
+pyasn1.type.error.ValueConstraintError: 
+  SingleValueConstraint(0, 1, 2, 3, 4, 5, 6, 7, 8, 9) failed at: A
+>>> 
+
+
+ +

+As can be seen in the snippet above, if a value violates the constraint, an +exception will be thrown. A constrainted pyasn1 type object holds a +reference to a constraint object (or their combination, as will be explained +later) and calls it for value verification. +

+ +
+
+>>> from pyasn1.type import univ, constraint
+>>> class DialButton(univ.OctetString):
+...   subtypeSpec = constraint.SingleValueConstraint(
+...       '0','1','2','3','4','5','6','7','8','9'
+...   )
+>>> DialButton('0')
+DialButton(b'0')
+>>> DialButton('A')
+Traceback (most recent call last):
+...
+pyasn1.type.error.ValueConstraintError:
+  SingleValueConstraint(0, 1, 2, 3, 4, 5, 6, 7, 8, 9) failed at: A
+>>> 
+
+
+ +

+Constrained pyasn1 value object can never hold a violating value. +

+ + +

+1.4.2 Value range constraint +

+ +

+A pair of values, compliant to a type to be constrained, denote low and upper +bounds of allowed range of values of a type. +

+ +
+
+Teenagers ::= INTEGER (13..19)
+
+
+ +

+And in pyasn1 terms: +

+ +
+
+>>> from pyasn1.type import univ, constraint
+>>> class Teenagers(univ.Integer):
+...   subtypeSpec = constraint.ValueRangeConstraint(13, 19)
+>>> Teenagers(14)
+Teenagers(14)
+>>> Teenagers(20)
+Traceback (most recent call last):
+...
+pyasn1.type.error.ValueConstraintError:
+  ValueRangeConstraint(13, 19) failed at: 20
+>>> 
+
+
+ +

+Value range constraint usually applies numeric types. +

+ + +

+1.4.3 Size constraint +

+ +

+It is sometimes convenient to set or limit the allowed size of a data item +to be sent from one application to another to manage bandwidth and memory +consumption issues. Size constraint specifies the lower and upper bounds +of the size of a valid value. +

+ +
+
+TwoBits ::= BIT STRING (SIZE (2))
+
+
+ +

+Express the same grammar in pyasn1: +

+ +
+
+>>> from pyasn1.type import univ, constraint
+>>> class TwoBits(univ.BitString):
+...   subtypeSpec = constraint.ValueSizeConstraint(2, 2)
+>>> TwoBits((1,1))
+TwoBits("'11'B")
+>>> TwoBits((1,1,0))
+Traceback (most recent call last):
+...
+pyasn1.type.error.ValueConstraintError:
+  ValueSizeConstraint(2, 2) failed at: (1, 1, 0)
+>>> 
+
+
+ +

+Size constraint can be applied to potentially massive values - bit or octet +strings, SEQUENCE OF/SET OF values. +

+ + +

+1.4.4 Alphabet constraint +

+ +

+The permitted alphabet constraint is similar to Single value constraint +but constraint applies to individual characters of a value. +

+ +
+
+MorseCode ::= PrintableString (FROM ("."|"-"|" "))
+
+
+ +

+And in pyasn1: +

+ +
+
+>>> from pyasn1.type import char, constraint
+>>> class MorseCode(char.PrintableString):
+...   subtypeSpec = constraint.PermittedAlphabetConstraint(".", "-", " ")
+>>> MorseCode("...---...")
+MorseCode('...---...')
+>>> MorseCode("?")
+Traceback (most recent call last):
+...
+pyasn1.type.error.ValueConstraintError:
+  PermittedAlphabetConstraint(".", "-", " ") failed at: "?"
+>>> 
+
+
+ +

+Current implementation does not handle ranges of characters in constraint +(FROM "A".."Z" syntax), one has to list the whole set in a range. +

+ + +

+1.4.5 Constraint combinations +

+ +

+Up to this moment, we used a single constraint per ASN.1 type. The standard, +however, allows for combining multiple individual constraints into +intersections, unions and exclusions. +

+ +

+In pyasn1 data model, all of these methods of constraint combinations are +implemented as constraint-like objects holding individual constraint (or +combination) objects. Like terminal constraint objects, combination objects +are capable to perform value verification at its set of enclosed constraints +according to the logic of particular combination. +

+ +

+Constraints intersection verification succeeds only if a value is +compliant to each constraint in a set. To begin with, the following +specification will constitute a valid telephone number: +

+ +
+
+PhoneNumber ::= NumericString (FROM ("0".."9")) (SIZE 11)
+
+
+ +

+Constraint intersection object serves the logic above: +

+ +
+
+>>> from pyasn1.type import char, constraint
+>>> class PhoneNumber(char.NumericString):
+...   subtypeSpec = constraint.ConstraintsIntersection(
+...     constraint.PermittedAlphabetConstraint('0','1','2','3','4','5','6','7','8','9'),
+...     constraint.ValueSizeConstraint(11, 11)
+...   )
+>>> PhoneNumber('79039343212')
+PhoneNumber('79039343212')
+>>> PhoneNumber('?9039343212')
+Traceback (most recent call last):
+...
+pyasn1.type.error.ValueConstraintError:
+  ConstraintsIntersection(
+    PermittedAlphabetConstraint('0','1','2','3','4','5','6','7','8','9'),
+      ValueSizeConstraint(11, 11)) failed at: 
+   PermittedAlphabetConstraint('0','1','2','3','4','5','6','7','8','9') failed at: "?039343212"
+>>> PhoneNumber('9343212')
+Traceback (most recent call last):
+...
+pyasn1.type.error.ValueConstraintError:
+  ConstraintsIntersection(
+    PermittedAlphabetConstraint('0','1','2','3','4','5','6','7','8','9'),
+      ValueSizeConstraint(11, 11)) failed at:
+  ValueSizeConstraint(10, 10) failed at: "9343212"
+>>>
+
+
+ +

+Union of constraints works by making sure that a value is compliant +to any of the constraint in a set. For instance: +

+ +
+
+CapitalOrSmall ::= IA5String (FROM ('A','B','C') | FROM ('a','b','c'))
+
+
+ +

+It's important to note, that a value must fully comply to any single +constraint in a set. In the specification above, a value of all small or +all capital letters is compliant, but a mix of small&capitals is not. +Here's its pyasn1 analogue: +

+ +
+
+>>> from pyasn1.type import char, constraint
+>>> class CapitalOrSmall(char.IA5String):
+...   subtypeSpec = constraint.ConstraintsUnion(
+...     constraint.PermittedAlphabetConstraint('A','B','C'),
+...     constraint.PermittedAlphabetConstraint('a','b','c')
+...   )
+>>> CapitalOrSmall('ABBA')
+CapitalOrSmall('ABBA')
+>>> CapitalOrSmall('abba')
+CapitalOrSmall('abba')
+>>> CapitalOrSmall('Abba')
+Traceback (most recent call last):
+...
+pyasn1.type.error.ValueConstraintError:
+  ConstraintsUnion(PermittedAlphabetConstraint('A', 'B', 'C'),
+    PermittedAlphabetConstraint('a', 'b', 'c')) failed at: failed for "Abba"
+>>>
+
+
+ +

+Finally, the exclusion constraint simply negates the logic of value +verification at a constraint. In the following example, any integer value +is allowed in a type but not zero. +

+ +
+
+NoZero ::= INTEGER (ALL EXCEPT 0)
+
+
+ +

+In pyasn1 the above definition would read: +

+ +
+
+>>> from pyasn1.type import univ, constraint
+>>> class NoZero(univ.Integer):
+...   subtypeSpec = constraint.ConstraintsExclusion(
+...     constraint.SingleValueConstraint(0)
+...   )
+>>> NoZero(1)
+NoZero(1)
+>>> NoZero(0)
+Traceback (most recent call last):
+...
+pyasn1.type.error.ValueConstraintError:
+  ConstraintsExclusion(SingleValueConstraint(0)) failed at: 0
+>>>
+
+
+ +

+The depth of such a constraints tree, built with constraint combination objects +at its nodes, has not explicit limit. Value verification is performed in a +recursive manner till a definite solution is found. +

+ + +

+1.5 Types relationships +

+ +

+In the course of data processing in an application, it is sometimes +convenient to figure out the type relationships between pyasn1 type or +value objects. Formally, two things influence pyasn1 types relationship: +tag set and subtype constraints. One pyasn1 type is considered +to be a derivative of another if their TagSet and Constraint objects are +a derivation of one another. +

+ +

+The following example illustrates the concept (we use the same tagset but +different constraints for simplicity): +

+ +
+
+>>> from pyasn1.type import univ, constraint
+>>> i1 = univ.Integer(subtypeSpec=constraint.ValueRangeConstraint(3,8))
+>>> i2 = univ.Integer(subtypeSpec=constraint.ConstraintsIntersection(
+...    constraint.ValueRangeConstraint(3,8),
+...    constraint.ValueRangeConstraint(4,7)
+... ) )
+>>> i1.isSameTypeWith(i2)
+False
+>>> i1.isSuperTypeOf(i2)
+True
+>>> i1.isSuperTypeOf(i1)
+True
+>>> i2.isSuperTypeOf(i1)
+False
+>>>
+
+
+ +

+As can be seen in the above code snippet, there are two methods of any pyasn1 +type/value object that test types for their relationship: +isSameTypeWith() and isSuperTypeOf(). The former is +self-descriptive while the latter yields true if the argument appears +to be a pyasn1 object which has tagset and constraints derived from those +of the object being called. +

+ + +

+2. Codecs +

+ +

+In ASN.1 context, +codec +is a program that transforms between concrete data structures and a stream +of octets, suitable for transmission over the wire. This serialized form of +data is sometimes called substrate or essence. +

+ +

+In pyasn1 implementation, substrate takes shape of Python 3 bytes or +Python 2 string objects. +

+ +

+One of the properties of a codec is its ability to cope with incomplete +data and/or substrate what implies codec to be stateful. In other words, +when decoder runs out of substrate and data item being recovered is still +incomplete, stateful codec would suspend and complete data item recovery +whenever the rest of substrate becomes available. Similarly, stateful encoder +would encode data items in multiple steps waiting for source data to +arrive. Codec restartability is especially important when application deals +with large volumes of data and/or runs on low RAM. For an interesting +discussion on codecs options and design choices, refer to +Apache ASN.1 project +. +

+ +

+As of this writing, codecs implemented in pyasn1 are all stateless, mostly +to keep the code simple. +

+ +

+The pyasn1 package currently supports +BER codec and +its variations -- +CER and +DER. +More ASN.1 codecs are planned for implementation in the future. +

+ + +

+2.1 Encoders +

+ +

+Encoder is used for transforming pyasn1 value objects into substrate. Only +pyasn1 value objects could be serialized, attempts to process pyasn1 type +objects will cause encoder failure. +

+ +

+The following code will create a pyasn1 Integer object and serialize it with +BER encoder: +

+ +
+
+>>> from pyasn1.type import univ
+>>> from pyasn1.codec.ber import encoder
+>>> encoder.encode(univ.Integer(123456))
+b'\x02\x03\x01\xe2@'
+>>>
+
+
+ +

+BER standard also defines a so-called indefinite length encoding form +which makes large data items processing more memory efficient. It is mostly +useful when encoder does not have the whole value all at once and the +length of the value can not be determined at the beginning of encoding. +

+ +

+Constructed encoding is another feature of BER closely related to the +indefinite length form. In essence, a large scalar value (such as ASN.1 +character BitString type) could be chopped into smaller chunks by encoder +and transmitted incrementally to limit memory consumption. Unlike indefinite +length case, the length of the whole value must be known in advance when +using constructed, definite length encoding form. +

+ +

+Since pyasn1 codecs are not restartable, pyasn1 encoder may only encode data +item all at once. However, even in this case, generating indefinite length +encoding may help a low-memory receiver, running a restartable decoder, +to process a large data item. +

+ +
+
+>>> from pyasn1.type import univ
+>>> from pyasn1.codec.ber import encoder
+>>> encoder.encode(
+...   univ.OctetString('The quick brown fox jumps over the lazy dog'),
+...   defMode=False,
+...   maxChunkSize=8
+... )
+b'$\x80\x04\x08The quic\x04\x08k brown \x04\x08fox jump\x04\x08s over \
+t\x04\x08he lazy \x04\x03dog\x00\x00'
+>>>
+>>> encoder.encode(
+...   univ.OctetString('The quick brown fox jumps over the lazy dog'),
+...   maxChunkSize=8
+... )
+b'$7\x04\x08The quic\x04\x08k brown \x04\x08fox jump\x04\x08s over \
+t\x04\x08he lazy \x04\x03dog'
+
+
+ +

+The defMode encoder parameter disables definite length encoding mode, +while the optional maxChunkSize parameter specifies desired +substrate chunk size that influences memory requirements at the decoder's end. +

+ +

+To use CER or DER encoders one needs to explicitly import and call them - the +APIs are all compatible. +

+ +
+
+>>> from pyasn1.type import univ
+>>> from pyasn1.codec.ber import encoder as ber_encoder
+>>> from pyasn1.codec.cer import encoder as cer_encoder
+>>> from pyasn1.codec.der import encoder as der_encoder
+>>> ber_encoder.encode(univ.Boolean(True))
+b'\x01\x01\x01'
+>>> cer_encoder.encode(univ.Boolean(True))
+b'\x01\x01\xff'
+>>> der_encoder.encode(univ.Boolean(True))
+b'\x01\x01\xff'
+>>>
+
+
+ + +

+2.2 Decoders +

+ +

+In the process of decoding, pyasn1 value objects are created and linked to +each other, based on the information containted in the substrate. Thus, +the original pyasn1 value object(s) are recovered. +

+ +
+
+>>> from pyasn1.type import univ
+>>> from pyasn1.codec.ber import encoder, decoder
+>>> substrate = encoder.encode(univ.Boolean(True))
+>>> decoder.decode(substrate)
+(Boolean('True(1)'), b'')
+>>>
+
+
+ +

+Commenting on the code snippet above, pyasn1 decoder accepts substrate +as an argument and returns a tuple of pyasn1 value object (possibly +a top-level one in case of constructed object) and unprocessed part +of input substrate. +

+ +

+All pyasn1 decoders can handle both definite and indefinite length +encoding modes automatically, explicit switching into one mode +to another is not required. +

+ +
+
+>>> from pyasn1.type import univ
+>>> from pyasn1.codec.ber import encoder, decoder
+>>> substrate = encoder.encode(
+...   univ.OctetString('The quick brown fox jumps over the lazy dog'),
+...   defMode=False,
+...   maxChunkSize=8
+... )
+>>> decoder.decode(substrate)
+(OctetString(b'The quick brown fox jumps over the lazy dog'), b'')
+>>>
+
+
+ +

+Speaking of BER/CER/DER encoding, in many situations substrate may not contain +all necessary information needed for complete and accurate ASN.1 values +recovery. The most obvious cases include implicitly tagged ASN.1 types +and constrained types. +

+ +

+As discussed earlier in this handbook, when an ASN.1 type is implicitly +tagged, previous outermost tag is lost and never appears in substrate. +If it is the base tag that gets lost, decoder is unable to pick type-specific +value decoder at its table of built-in types, and therefore recover +the value part, based only on the information contained in substrate. The +approach taken by pyasn1 decoder is to use a prototype pyasn1 type object (or +a set of them) to guide the decoding process by matching [possibly +incomplete] tags recovered from substrate with those found in prototype pyasn1 +type objects (also called pyasn1 specification object further in this paper). +

+ +
+
+>>> from pyasn1.codec.ber import decoder
+>>> decoder.decode(b'\x02\x01\x0c', asn1Spec=univ.Integer())
+Integer(12), b''
+>>>
+
+
+ +

+Decoder would neither modify pyasn1 specification object nor use +its current values (if it's a pyasn1 value object), but rather use it as +a hint for choosing proper decoder and as a pattern for creating new objects: +

+ +
+
+>>> from pyasn1.type import univ, tag
+>>> from pyasn1.codec.ber import encoder, decoder
+>>> i = univ.Integer(12345).subtype(
+...   implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 40)
+... )
+>>> substrate = encoder.encode(i)
+>>> substrate
+b'\x9f(\x0209'
+>>> decoder.decode(substrate)
+Traceback (most recent call last):
+...
+pyasn1.error.PyAsn1Error: 
+   TagSet(Tag(tagClass=128, tagFormat=0, tagId=40)) not in asn1Spec
+>>> decoder.decode(substrate, asn1Spec=i)
+(Integer(12345), b'')
+>>>
+
+
+ +

+Notice in the example above, that an attempt to run decoder without passing +pyasn1 specification object fails because recovered tag does not belong +to any of the built-in types. +

+ +

+Another important feature of guided decoder operation is the use of +values constraints possibly present in pyasn1 specification object. +To explain this, we will decode a random integer object into generic Integer +and the constrained one. +

+ +
+
+>>> from pyasn1.type import univ, constraint
+>>> from pyasn1.codec.ber import encoder, decoder
+>>> class DialDigit(univ.Integer):
+...   subtypeSpec = constraint.ValueRangeConstraint(0,9)
+>>> substrate = encoder.encode(univ.Integer(13))
+>>> decoder.decode(substrate)
+(Integer(13), b'')
+>>> decoder.decode(substrate, asn1Spec=DialDigit())
+Traceback (most recent call last):
+...
+pyasn1.type.error.ValueConstraintError:
+  ValueRangeConstraint(0, 9) failed at: 13
+>>> 
+
+
+ +

+Similarily to encoders, to use CER or DER decoders application has to +explicitly import and call them - all APIs are compatible. +

+ +
+
+>>> from pyasn1.type import univ
+>>> from pyasn1.codec.ber import encoder as ber_encoder
+>>> substrate = ber_encoder.encode(univ.OctetString('http://pyasn1.sf.net'))
+>>>
+>>> from pyasn1.codec.ber import decoder as ber_decoder
+>>> from pyasn1.codec.cer import decoder as cer_decoder
+>>> from pyasn1.codec.der import decoder as der_decoder
+>>> 
+>>> ber_decoder.decode(substrate)
+(OctetString(b'http://pyasn1.sf.net'), b'')
+>>> cer_decoder.decode(substrate)
+(OctetString(b'http://pyasn1.sf.net'), b'')
+>>> der_decoder.decode(substrate)
+(OctetString(b'http://pyasn1.sf.net'), b'')
+>>> 
+
+
+ + +

+2.2.1 Decoding untagged types +

+ +

+It has already been mentioned, that ASN.1 has two "special case" types: +CHOICE and ANY. They are different from other types in part of +tagging - unless these two are additionally tagged, neither of them will +have their own tag. Therefore these types become invisible in substrate +and can not be recovered without passing pyasn1 specification object to +decoder. +

+ +

+To explain the issue, we will first prepare a Choice object to deal with: +

+ +
+
+>>> from pyasn1.type import univ, namedtype
+>>> class CodeOrMessage(univ.Choice):
+...   componentType = namedtype.NamedTypes(
+...     namedtype.NamedType('code', univ.Integer()),
+...     namedtype.NamedType('message', univ.OctetString())
+...   )
+>>>
+>>> codeOrMessage = CodeOrMessage()
+>>> codeOrMessage.setComponentByName('message', 'my string value')
+>>> print(codeOrMessage.prettyPrint())
+CodeOrMessage:
+ message=b'my string value'
+>>>
+
+
+ +

+Let's now encode this Choice object and then decode its substrate +with and without pyasn1 specification object: +

+ +
+
+>>> from pyasn1.codec.ber import encoder, decoder
+>>> substrate = encoder.encode(codeOrMessage)
+>>> substrate
+b'\x04\x0fmy string value'
+>>> encoder.encode(univ.OctetString('my string value'))
+b'\x04\x0fmy string value'
+>>>
+>>> decoder.decode(substrate)
+(OctetString(b'my string value'), b'')
+>>> codeOrMessage, substrate = decoder.decode(substrate, asn1Spec=CodeOrMessage())
+>>> print(codeOrMessage.prettyPrint())
+CodeOrMessage:
+ message=b'my string value'
+>>>
+
+
+ +

+First thing to notice in the listing above is that the substrate produced +for our Choice value object is equivalent to the substrate for an OctetString +object initialized to the same value. In other words, any information about +the Choice component is absent in encoding. +

+ +

+Sure enough, that kind of substrate will decode into an OctetString object, +unless original Choice type object is passed to decoder to guide the decoding +process. +

+ +

+Similarily untagged ANY type behaves differently on decoding phase - when +decoder bumps into an Any object in pyasn1 specification, it stops decoding +and puts all the substrate into a new Any value object in form of an octet +string. Concerned application could then re-run decoder with an additional, +more exact pyasn1 specification object to recover the contents of Any +object. +

+ +

+As it was mentioned elsewhere in this paper, Any type allows for incomplete +or changing ASN.1 specification to be handled gracefully by decoder and +applications. +

+ +

+To illustrate the working of Any type, we'll have to make the stage +by encoding a pyasn1 object and then putting its substrate into an any +object. +

+ +
+
+>>> from pyasn1.type import univ
+>>> from pyasn1.codec.ber import encoder, decoder
+>>> innerSubstrate = encoder.encode(univ.Integer(1234))
+>>> innerSubstrate
+b'\x02\x02\x04\xd2'
+>>> any = univ.Any(innerSubstrate)
+>>> any
+Any(b'\x02\x02\x04\xd2')
+>>> substrate = encoder.encode(any)
+>>> substrate
+b'\x02\x02\x04\xd2'
+>>>
+
+
+ +

+As with Choice type encoding, there is no traces of Any type in substrate. +Obviously, the substrate we are dealing with, will decode into the inner +[Integer] component, unless pyasn1 specification is given to guide the +decoder. Continuing previous code: +

+ +
+
+>>> from pyasn1.type import univ
+>>> from pyasn1.codec.ber import encoder, decoder
+
+>>> decoder.decode(substrate)
+(Integer(1234), b'')
+>>> any, substrate = decoder.decode(substrate, asn1Spec=univ.Any())
+>>> any
+Any(b'\x02\x02\x04\xd2')
+>>> decoder.decode(str(any))
+(Integer(1234), b'')
+>>>
+
+
+ +

+Both CHOICE and ANY types are widely used in practice. Reader is welcome to +take a look at + +ASN.1 specifications of X.509 applications for more information. +

+ + +

+2.2.2 Ignoring unknown types +

+ +

+When dealing with a loosely specified ASN.1 structure, the receiving +end may not be aware of some types present in the substrate. It may be +convenient then to turn decoder into a recovery mode. Whilst there, decoder +will not bail out when hit an unknown tag but rather treat it as an Any +type. +

+ +
+
+>>> from pyasn1.type import univ, tag
+>>> from pyasn1.codec.ber import encoder, decoder
+>>> taggedInt = univ.Integer(12345).subtype(
+...   implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 40)
+... )
+>>> substrate = encoder.encode(taggedInt)
+>>> decoder.decode(substrate)
+Traceback (most recent call last):
+...
+pyasn1.error.PyAsn1Error: TagSet(Tag(tagClass=128, tagFormat=0, tagId=40)) not in asn1Spec
+>>>
+>>> decoder.decode.defaultErrorState = decoder.stDumpRawValue
+>>> decoder.decode(substrate)
+(Any(b'\x9f(\x0209'), '')
+>>>
+
+
+ +

+It's also possible to configure a custom decoder, to handle unknown tags +found in substrate. This can be done by means of defaultRawDecoder +attribute holding a reference to type decoder object. Refer to the source +for API details. +

+ + +

+3. Feedback and getting help +

+ +

+Although pyasn1 software is almost a decade old and used in many production +environments, it still may have bugs and non-implemented pieces. Anyone +who happens to run into such defect is welcome to complain to +pyasn1 mailing list +or better yet fix the issue and send +me the patch. +

+ +

+Typically, pyasn1 is used for building arbitrary protocol support into +various applications. This involves manual translation of ASN.1 data +structures into their pyasn1 implementations. To save time and effort, +data structures for some of the popular protocols are pre-programmed +and kept for further re-use in form of the + +pyasn1-modules package. For instance, many structures for PKI (X.509, +PKCS#*, CRMF, OCSP), LDAP and SNMP are present. +Applications authors are advised to import and use relevant modules +from that package whenever needed protocol structures are already +there. New protocol modules contributions are welcome. +

+ +

+And finally, the latest pyasn1 package revision is available for free +download from +project home and +also from the +Python package repository. +

+ +
+ +
+
+ + diff --git a/python/pyasn1/doc/scalar.html b/python/pyasn1/doc/scalar.html new file mode 100644 index 000000000..e5ccefe60 --- /dev/null +++ b/python/pyasn1/doc/scalar.html @@ -0,0 +1,794 @@ + + +PyASN1 data model and scalar types + + + + +
+ + + + +
+ +

+1. Data model for ASN.1 types +

+ +

+All ASN.1 types could be categorized into two groups: scalar (also called +simple or primitive) and constructed. The first group is populated by +well-known types like Integer or String. Members of constructed group +hold other types (simple or constructed) as their inner components, thus +they are semantically close to a programming language records or lists. +

+ +

+In pyasn1, all ASN.1 types and values are implemented as Python objects. +The same pyasn1 object can represent either ASN.1 type and/or value +depending of the presense of value initializer on object instantiation. +We will further refer to these as pyasn1 type object versus pyasn1 +value object. +

+ +

+Primitive ASN.1 types are implemented as immutable scalar objects. There values +could be used just like corresponding native Python values (integers, +strings/bytes etc) and freely mixed with them in expressions. +

+ +
+
+>>> from pyasn1.type import univ
+>>> asn1IntegerValue = univ.Integer(12)
+>>> asn1IntegerValue - 2
+10
+>>> univ.OctetString('abc') == 'abc'
+True   # Python 2
+>>> univ.OctetString(b'abc') == b'abc'
+True   # Python 3
+
+
+ +

+It would be an error to perform an operation on a pyasn1 type object +as it holds no value to deal with: +

+ +
+
+>>> from pyasn1.type import univ
+>>> asn1IntegerType = univ.Integer()
+>>> asn1IntegerType - 2
+...
+pyasn1.error.PyAsn1Error: No value for __coerce__()
+
+
+ + +

+1.1 Scalar types +

+ +

+In the sub-sections that follow we will explain pyasn1 mapping to those +primitive ASN.1 types. Both, ASN.1 notation and corresponding pyasn1 +syntax will be given in each case. +

+ + +

+1.1.1 Boolean type +

+ +

+This is the simplest type those values could be either True or False. +

+ +
+
+;; type specification
+FunFactorPresent ::= BOOLEAN
+
+;; values declaration and assignment
+pythonFunFactor FunFactorPresent ::= TRUE
+cobolFunFactor FunFactorPresent :: FALSE
+
+
+ +

+And here's pyasn1 version of it: +

+ +
+
+>>> from pyasn1.type import univ
+>>> class FunFactorPresent(univ.Boolean): pass
+... 
+>>> pythonFunFactor = FunFactorPresent(True)
+>>> cobolFunFactor = FunFactorPresent(False)
+>>> pythonFunFactor
+FunFactorPresent('True(1)')
+>>> cobolFunFactor
+FunFactorPresent('False(0)')
+>>> pythonFunFactor == cobolFunFactor
+False
+>>>
+
+
+ + +

+1.1.2 Null type +

+ +

+The NULL type is sometimes used to express the absense of any information. +

+ +
+
+;; type specification
+Vote ::= CHOICE {
+  agreed BOOLEAN,
+  skip NULL
+}
+
+ +;; value declaration and assignment +myVote Vote ::= skip:NULL + + +

+We will explain the CHOICE type later in this paper, meanwhile the NULL +type: +

+ +
+
+>>> from pyasn1.type import univ
+>>> skip = univ.Null()
+>>> skip
+Null('')
+>>>
+
+
+ + +

+1.1.3 Integer type +

+ +

+ASN.1 defines the values of Integer type as negative or positive of whatever +length. This definition plays nicely with Python as the latter places no +limit on Integers. However, some ASN.1 implementations may impose certain +limits of integer value ranges. Keep that in mind when designing new +data structures. +

+ +
+
+;; values specification
+age-of-universe INTEGER ::= 13750000000
+mean-martian-surface-temperature INTEGER ::= -63
+
+
+ +

+A rather strigntforward mapping into pyasn1: +

+ +
+
+>>> from pyasn1.type import univ
+>>> ageOfUniverse = univ.Integer(13750000000)
+>>> ageOfUniverse
+Integer(13750000000)
+>>>
+>>> meanMartianSurfaceTemperature = univ.Integer(-63)
+>>> meanMartianSurfaceTemperature
+Integer(-63)
+>>>
+
+
+ +

+ASN.1 allows to assign human-friendly names to particular values of +an INTEGER type. +

+ +
+
+Temperature ::= INTEGER {
+  freezing(0),
+  boiling(100) 
+}
+
+
+ +

+The Temperature type expressed in pyasn1: +

+ +
+
+>>> from pyasn1.type import univ, namedval
+>>> class Temperature(univ.Integer):
+...   namedValues = namedval.NamedValues(('freezing', 0), ('boiling', 100))
+...
+>>> t = Temperature(0)
+>>> t
+Temperature('freezing(0)')
+>>> t + 1
+Temperature(1)
+>>> t + 100
+Temperature('boiling(100)')
+>>> t = Temperature('boiling')
+>>> t
+Temperature('boiling(100)')
+>>> Temperature('boiling') / 2
+Temperature(50)
+>>> -1 < Temperature('freezing')
+True
+>>> 47 > Temperature('boiling')
+False
+>>>
+
+
+ +

+These values labels have no effect on Integer type operations, any value +still could be assigned to a type (information on value constraints will +follow further in this paper). +

+ + +

+1.1.4 Enumerated type +

+ +

+ASN.1 Enumerated type differs from an Integer type in a number of ways. +Most important is that its instance can only hold a value that belongs +to a set of values specified on type declaration. +

+ +
+
+error-status ::= ENUMERATED {
+  no-error(0),
+  authentication-error(10),
+  authorization-error(20),
+  general-failure(51)
+}
+
+
+ +

+When constructing Enumerated type we will use two pyasn1 features: values +labels (as mentioned above) and value constraint (will be described in +more details later on). +

+ +
+
+>>> from pyasn1.type import univ, namedval, constraint
+>>> class ErrorStatus(univ.Enumerated):
+...   namedValues = namedval.NamedValues(
+...        ('no-error', 0),
+...        ('authentication-error', 10),
+...        ('authorization-error', 20),
+...        ('general-failure', 51)
+...   )
+...   subtypeSpec = univ.Enumerated.subtypeSpec + \
+...                    constraint.SingleValueConstraint(0, 10, 20, 51)
+...
+>>> errorStatus = univ.ErrorStatus('no-error')
+>>> errorStatus
+ErrorStatus('no-error(0)')
+>>> errorStatus == univ.ErrorStatus('general-failure')
+False
+>>> univ.ErrorStatus('non-existing-state')
+Traceback (most recent call last):
+...
+pyasn1.error.PyAsn1Error: Can't coerce non-existing-state into integer
+>>>
+
+
+ +

+Particular integer values associated with Enumerated value states +have no meaning. They should not be used as such or in any kind of +math operation. Those integer values are only used by codecs to +transfer state from one entity to another. +

+ + +

+1.1.5 Real type +

+ +

+Values of the Real type are a three-component tuple of mantissa, base and +exponent. All three are integers. +

+ +
+
+pi ::= REAL { mantissa 314159, base 10, exponent -5 }
+
+
+ +

+Corresponding pyasn1 objects can be initialized with either a three-component +tuple or a Python float. Infinite values could be expressed in a way, +compatible with Python float type. + +

+ +
+
+>>> from pyasn1.type import univ
+>>> pi = univ.Real((314159, 10, -5))
+>>> pi
+Real((314159, 10,-5))
+>>> float(pi)
+3.14159
+>>> pi == univ.Real(3.14159)
+True
+>>> univ.Real('inf')
+Real('inf')
+>>> univ.Real('-inf') == float('-inf')
+True
+>>>
+
+
+ +

+If a Real object is initialized from a Python float or yielded by a math +operation, the base is set to decimal 10 (what affects encoding). +

+ + +

+1.1.6 Bit string type +

+ +

+ASN.1 BIT STRING type holds opaque binary data of an arbitrarily length. +A BIT STRING value could be initialized by either a binary (base 2) or +hex (base 16) value. +

+ +
+
+public-key BIT STRING ::= '1010111011110001010110101101101
+                           1011000101010000010110101100010
+                           0110101010000111101010111111110'B
+
+signature  BIT STRING ::= 'AF01330CD932093392100B39FF00DE0'H
+
+
+ +

+The pyasn1 BitString objects can initialize from native ASN.1 notation +(base 2 or base 16 strings) or from a Python tuple of binary components. +

+ +
+
+>>> from pyasn1.type import univ
+>>> publicKey = univ.BitString(
+...          "'1010111011110001010110101101101"
+...          "1011000101010000010110101100010"
+...          "0110101010000111101010111111110'B"
+)
+>>> publicKey
+BitString("'10101110111100010101101011011011011000101010000010110101100010\
+0110101010000111101010111111110'B")
+>>> signature = univ.BitString(
+...          "'AF01330CD932093392100B39FF00DE0'H"
+... )
+>>> signature
+BitString("'101011110000000100110011000011001101100100110010000010010011001\
+1100100100001000000001011001110011111111100000000110111100000'B")
+>>> fingerprint = univ.BitString(
+...          (1, 0, 1, 1 ,0, 1, 1, 1, 0, 1, 0, 1)
+... )
+>>> fingerprint
+BitString("'101101110101'B")
+>>>
+
+
+ +

+Another BIT STRING initialization method supported by ASN.1 notation +is to specify only 1-th bits along with their human-friendly label +and bit offset relative to the beginning of the bit string. With this +method, all not explicitly mentioned bits are doomed to be zeros. +

+ +
+
+bit-mask  BIT STRING ::= {
+  read-flag(0),
+  write-flag(2),
+  run-flag(4)
+}
+
+
+ +

+To express this in pyasn1, we will employ the named values feature (as with +Enumeration type). +

+ +
+
+>>> from pyasn1.type import univ, namedval
+>>> class BitMask(univ.BitString):
+...   namedValues = namedval.NamedValues(
+...        ('read-flag', 0),
+...        ('write-flag', 2),
+...        ('run-flag', 4)
+... )
+>>> bitMask = BitMask('read-flag,run-flag')
+>>> bitMask
+BitMask("'10001'B")
+>>> tuple(bitMask)
+(1, 0, 0, 0, 1)
+>>> bitMask[4]
+1
+>>>
+
+
+ +

+The BitString objects mimic the properties of Python tuple type in part +of immutable sequence object protocol support. +

+ + +

+1.1.7 OctetString type +

+ +

+The OCTET STRING type is a confusing subject. According to ASN.1 +specification, this type is similar to BIT STRING, the major difference +is that the former operates in 8-bit chunks of data. What is important +to note, is that OCTET STRING was NOT designed to handle text strings - the +standard provides many other types specialized for text content. For that +reason, ASN.1 forbids to initialize OCTET STRING values with "quoted text +strings", only binary or hex initializers, similar to BIT STRING ones, +are allowed. +

+ +
+
+thumbnail OCTET STRING ::= '1000010111101110101111000000111011'B
+thumbnail OCTET STRING ::= 'FA9823C43E43510DE3422'H
+
+
+ +

+However, ASN.1 users (e.g. protocols designers) seem to ignore the original +purpose of the OCTET STRING type - they used it for handling all kinds of +data, including text strings. +

+ +
+
+welcome-message OCTET STRING ::= "Welcome to ASN.1 wilderness!"
+
+
+ +

+In pyasn1, we have taken a liberal approach and allowed both BIT STRING +style and quoted text initializers for the OctetString objects. To avoid +possible collisions, quoted text is the default initialization syntax. +

+ +
+
+>>> from pyasn1.type import univ
+>>> thumbnail = univ.OctetString(
+...    binValue='1000010111101110101111000000111011'
+... )
+>>> thumbnail
+OctetString(hexValue='85eebcec0')
+>>> thumbnail = univ.OctetString(
+...    hexValue='FA9823C43E43510DE3422'
+... )
+>>> thumbnail
+OctetString(hexValue='fa9823c43e4351de34220')
+>>>
+
+
+ +

+Most frequent usage of the OctetString class is to instantiate it with +a text string. +

+ +
+
+>>> from pyasn1.type import univ
+>>> welcomeMessage = univ.OctetString('Welcome to ASN.1 wilderness!')
+>>> welcomeMessage
+OctetString(b'Welcome to ASN.1 wilderness!')
+>>> print('%s' % welcomeMessage)
+Welcome to ASN.1 wilderness!
+>>> welcomeMessage[11:16]
+OctetString(b'ASN.1')
+>>> 
+
+
+ +

+OctetString objects support the immutable sequence object protocol. +In other words, they behave like Python 3 bytes (or Python 2 strings). +

+ +

+When running pyasn1 on Python 3, it's better to use the bytes objects for +OctetString instantiation, as it's more reliable and efficient. +

+ +

+Additionally, OctetString's can also be instantiated with a sequence of +8-bit integers (ASCII codes). +

+ +
+
+>>> univ.OctetString((77, 101, 101, 103, 111))
+OctetString(b'Meego')
+
+
+ +

+It is sometimes convenient to express OctetString instances as 8-bit +characters (Python 3 bytes or Python 2 strings) or 8-bit integers. +

+ +
+
+>>> octetString = univ.OctetString('ABCDEF')
+>>> octetString.asNumbers()
+(65, 66, 67, 68, 69, 70)
+>>> octetString.asOctets()
+b'ABCDEF'
+
+
+ + +

+1.1.8 ObjectIdentifier type +

+ +

+Values of the OBJECT IDENTIFIER type are sequences of integers that could +be used to identify virtually anything in the world. Various ASN.1-based +protocols employ OBJECT IDENTIFIERs for their own identification needs. +

+ +
+
+internet-id OBJECT IDENTIFIER ::= {
+  iso(1) identified-organization(3) dod(6) internet(1)
+}
+
+
+ +

+One of the natural ways to map OBJECT IDENTIFIER type into a Python +one is to use Python tuples of integers. So this approach is taken by +pyasn1. +

+ +
+
+>>> from pyasn1.type import univ
+>>> internetId = univ.ObjectIdentifier((1, 3, 6, 1))
+>>> internetId
+ObjectIdentifier('1.3.6.1')
+>>> internetId[2]
+6
+>>> internetId[1:3]
+ObjectIdentifier('3.6')
+
+
+ +

+A more human-friendly "dotted" notation is also supported. +

+ +
+
+>>> from pyasn1.type import univ
+>>> univ.ObjectIdentifier('1.3.6.1')
+ObjectIdentifier('1.3.6.1')
+
+
+ +

+Symbolic names of the arcs of object identifier, sometimes present in +ASN.1 specifications, are not preserved and used in pyasn1 objects. +

+ +

+The ObjectIdentifier objects mimic the properties of Python tuple type in +part of immutable sequence object protocol support. +

+ + +

+1.1.9 Character string types +

+ +

+ASN.1 standard introduces a diverse set of text-specific types. All of them +were designed to handle various types of characters. Some of these types seem +be obsolete nowdays, as their target technologies are gone. Another issue +to be aware of is that raw OCTET STRING type is sometimes used in practice +by ASN.1 users instead of specialized character string types, despite +explicit prohibition imposed by ASN.1 specification. +

+ +

+The two types are specific to ASN.1 are NumericString and PrintableString. +

+ +
+
+welcome-message ::= PrintableString {
+  "Welcome to ASN.1 text types"
+}
+
+dial-pad-numbers ::= NumericString {
+  "0", "1", "2", "3", "4", "5", "6", "7", "8", "9"
+}
+
+
+ +

+Their pyasn1 implementations are: +

+ +
+
+>>> from pyasn1.type import char
+>>> '%s' % char.PrintableString("Welcome to ASN.1 text types")
+'Welcome to ASN.1 text types'
+>>> dialPadNumbers = char.NumericString(
+      "0" "1" "2" "3" "4" "5" "6" "7" "8" "9"
+)
+>>> dialPadNumbers
+NumericString(b'0123456789')
+>>>
+
+
+ +

+The following types came to ASN.1 from ISO standards on character sets. +

+ +
+
+>>> from pyasn1.type import char
+>>> char.VisibleString("abc")
+VisibleString(b'abc')
+>>> char.IA5String('abc')
+IA5String(b'abc')
+>>> char.TeletexString('abc')
+TeletexString(b'abc')
+>>> char.VideotexString('abc')
+VideotexString(b'abc')
+>>> char.GraphicString('abc')
+GraphicString(b'abc')
+>>> char.GeneralString('abc')
+GeneralString(b'abc')
+>>>
+
+
+ +

+The last three types are relatively recent addition to the family of +character string types: UniversalString, BMPString, UTF8String. +

+ +
+
+>>> from pyasn1.type import char
+>>> char.UniversalString("abc")
+UniversalString(b'abc')
+>>> char.BMPString('abc')
+BMPString(b'abc')
+>>> char.UTF8String('abc')
+UTF8String(b'abc')
+>>> utf8String = char.UTF8String('У попа была собака')
+>>> utf8String
+UTF8String(b'\xd0\xa3 \xd0\xbf\xd0\xbe\xd0\xbf\xd0\xb0 \xd0\xb1\xd1\x8b\xd0\xbb\xd0\xb0 \
+\xd1\x81\xd0\xbe\xd0\xb1\xd0\xb0\xd0\xba\xd0\xb0')
+>>> print(utf8String)
+У попа была собака
+>>>
+
+
+ +

+In pyasn1, all character type objects behave like Python strings. None of +them is currently constrained in terms of valid alphabet so it's up to +the data source to keep an eye on data validation for these types. +

+ + +

+1.1.10 Useful types +

+ +

+There are three so-called useful types defined in the standard: +ObjectDescriptor, GeneralizedTime, UTCTime. They all are subtypes +of GraphicString or VisibleString types therefore useful types are +character string types. +

+ +

+It's advised by the ASN.1 standard to have an instance of ObjectDescriptor +type holding a human-readable description of corresponding instance of +OBJECT IDENTIFIER type. There are no formal linkage between these instances +and provision for ObjectDescriptor uniqueness in the standard. +

+ +
+
+>>> from pyasn1.type import useful
+>>> descrBER = useful.ObjectDescriptor(
+      "Basic encoding of a single ASN.1 type"
+)
+>>> 
+
+
+ +

+GeneralizedTime and UTCTime types are designed to hold a human-readable +timestamp in a universal and unambiguous form. The former provides +more flexibility in notation while the latter is more strict but has +Y2K issues. +

+ +
+
+;; Mar 8 2010 12:00:00 MSK
+moscow-time GeneralizedTime ::= "20110308120000.0"
+;; Mar 8 2010 12:00:00 UTC
+utc-time GeneralizedTime ::= "201103081200Z"
+;; Mar 8 1999 12:00:00 UTC
+utc-time UTCTime ::= "9803081200Z"
+
+
+ +
+
+>>> from pyasn1.type import useful
+>>> moscowTime = useful.GeneralizedTime("20110308120000.0")
+>>> utcTime = useful.UTCTime("9803081200Z")
+>>> 
+
+
+ +

+Despite their intended use, these types possess no special, time-related, +handling in pyasn1. They are just printable strings. +

+ +
+ +
+
+ + diff --git a/python/pyasn1/doc/tagging.html b/python/pyasn1/doc/tagging.html new file mode 100644 index 000000000..187f1180d --- /dev/null +++ b/python/pyasn1/doc/tagging.html @@ -0,0 +1,233 @@ + + +Tagging in PyASN1 + + + + +
+ + + + +
+ +

+1.2 Tagging in PyASN1 +

+ +

+In order to continue with the Constructed ASN.1 types, we will first have +to introduce the concept of tagging (and its pyasn1 implementation), as +some of the Constructed types rely upon the tagging feature. +

+ +

+When a value is coming into an ASN.1-based system (received from a network +or read from some storage), the receiving entity has to determine the +type of the value to interpret and verify it accordingly. +

+ +

+Historically, the first data serialization protocol introduced in +ASN.1 was BER (Basic Encoding Rules). According to BER, any serialized +value is packed into a triplet of (Type, Length, Value) where Type is a +code that identifies the value (which is called tag in ASN.1), +length is the number of bytes occupied by the value in its serialized form +and value is ASN.1 value in a form suitable for serial transmission or storage. +

+ +

+For that reason almost every ASN.1 type has a tag (which is actually a +BER type) associated with it by default. +

+ +

+An ASN.1 tag could be viewed as a tuple of three numbers: +(Class, Format, Number). While Number identifies a tag, Class component +is used to create scopes for Numbers. Four scopes are currently defined: +UNIVERSAL, context-specific, APPLICATION and PRIVATE. The Format component +is actually a one-bit flag - zero for tags associated with scalar types, +and one for constructed types (will be discussed later on). +

+ +
+
+MyIntegerType ::= [12] INTEGER
+MyOctetString ::= [APPLICATION 0] OCTET STRING
+
+
+ +

+In pyasn1, tags are implemented as immutable, tuple-like objects: +

+ +
+
+>>> from pyasn1.type import tag
+>>> myTag = tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 10)
+>>> myTag
+Tag(tagClass=128, tagFormat=0, tagId=10)
+>>> tuple(myTag)
+(128, 0, 10)
+>>> myTag[2]
+10
+>>> myTag == tag.Tag(tag.tagClassApplication, tag.tagFormatSimple, 10)
+False
+>>>
+
+
+ +

+Default tag, associated with any ASN.1 type, could be extended or replaced +to make new type distinguishable from its ancestor. The standard provides +two modes of tag mangling - IMPLICIT and EXPLICIT. +

+ +

+EXPLICIT mode works by appending new tag to the existing ones thus creating +an ordered set of tags. This set will be considered as a whole for type +identification and encoding purposes. Important property of EXPLICIT tagging +mode is that it preserves base type information in encoding what makes it +possible to completely recover type information from encoding. +

+ +

+When tagging in IMPLICIT mode, the outermost existing tag is dropped and +replaced with a new one. +

+ +
+
+MyIntegerType ::= [12] IMPLICIT INTEGER
+MyOctetString ::= [APPLICATION 0] EXPLICIT OCTET STRING
+
+
+ +

+To model both modes of tagging, a specialized container TagSet object (holding +zero, one or more Tag objects) is used in pyasn1. +

+ +
+
+>>> from pyasn1.type import tag
+>>> tagSet = tag.TagSet(
+...   # base tag
+...   tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 10),
+...   # effective tag
+...   tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 10)
+... )
+>>> tagSet
+TagSet(Tag(tagClass=128, tagFormat=0, tagId=10))
+>>> tagSet.getBaseTag()
+Tag(tagClass=128, tagFormat=0, tagId=10)
+>>> tagSet = tagSet.tagExplicitly(
+...    tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 20)
+... )
+>>> tagSet
+TagSet(Tag(tagClass=128, tagFormat=0, tagId=10), 
+       Tag(tagClass=128, tagFormat=32, tagId=20))
+>>> tagSet = tagSet.tagExplicitly(
+...    tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 30)
+... )
+>>> tagSet
+TagSet(Tag(tagClass=128, tagFormat=0, tagId=10), 
+       Tag(tagClass=128, tagFormat=32, tagId=20), 
+       Tag(tagClass=128, tagFormat=32, tagId=30))
+>>> tagSet = tagSet.tagImplicitly(
+...    tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 40)
+... )
+>>> tagSet
+TagSet(Tag(tagClass=128, tagFormat=0, tagId=10),
+       Tag(tagClass=128, tagFormat=32, tagId=20),
+       Tag(tagClass=128, tagFormat=32, tagId=40))
+>>> 
+
+
+ +

+As a side note: the "base tag" concept (accessible through the getBaseTag() +method) is specific to pyasn1 -- the base tag is used to identify the original +ASN.1 type of an object in question. Base tag is never occurs in encoding +and is mostly used internally by pyasn1 for choosing type-specific data +processing algorithms. The "effective tag" is the one that always appears in +encoding and is used on tagSets comparation. +

+ +

+Any two TagSet objects could be compared to see if one is a derivative +of the other. Figuring this out is also useful in cases when a type-specific +data processing algorithms are to be chosen. +

+ +
+
+>>> from pyasn1.type import tag
+>>> tagSet1 = tag.TagSet(
+...   # base tag
+...   tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 10)
+...   # effective tag
+...   tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 10)
+... )
+>>> tagSet2 = tagSet1.tagExplicitly(
+...    tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 20)
+... )
+>>> tagSet1.isSuperTagSetOf(tagSet2)
+True
+>>> tagSet2.isSuperTagSetOf(tagSet1)
+False
+>>> 
+
+
+ +

+We will complete this discussion on tagging with a real-world example. The +following ASN.1 tagged type: +

+ +
+
+MyIntegerType ::= [12] EXPLICIT INTEGER
+
+
+ +

+could be expressed in pyasn1 like this: +

+ +
+
+>>> from pyasn1.type import univ, tag
+>>> class MyIntegerType(univ.Integer):
+...   tagSet = univ.Integer.tagSet.tagExplicitly(
+...        tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 12)
+...        )
+>>> myInteger = MyIntegerType(12345)
+>>> myInteger.getTagSet()
+TagSet(Tag(tagClass=0, tagFormat=0, tagId=2), 
+       Tag(tagClass=128, tagFormat=32, tagId=12))
+>>>
+
+
+ +

+Referring to the above code, the tagSet class attribute is a property of any +pyasn1 type object that assigns default tagSet to a pyasn1 value object. This +default tagSet specification can be ignored and effectively replaced by some +other tagSet value passed on object instantiation. +

+ +

+It's important to understand that the tag set property of pyasn1 type/value +object can never be modifed in place. In other words, a pyasn1 type/value +object can never change its tags. The only way is to create a new pyasn1 +type/value object and associate different tag set with it. +

+ +
+ +
+
+ + diff --git a/python/pyasn1/pyasn1/__init__.py b/python/pyasn1/pyasn1/__init__.py new file mode 100644 index 000000000..88aff79c8 --- /dev/null +++ b/python/pyasn1/pyasn1/__init__.py @@ -0,0 +1,8 @@ +import sys + +# http://www.python.org/dev/peps/pep-0396/ +__version__ = '0.1.7' + +if sys.version_info[:2] < (2, 4): + raise RuntimeError('PyASN1 requires Python 2.4 or later') + diff --git a/python/pyasn1/pyasn1/codec/__init__.py b/python/pyasn1/pyasn1/codec/__init__.py new file mode 100644 index 000000000..8c3066b2e --- /dev/null +++ b/python/pyasn1/pyasn1/codec/__init__.py @@ -0,0 +1 @@ +# This file is necessary to make this directory a package. diff --git a/python/pyasn1/pyasn1/codec/ber/__init__.py b/python/pyasn1/pyasn1/codec/ber/__init__.py new file mode 100644 index 000000000..8c3066b2e --- /dev/null +++ b/python/pyasn1/pyasn1/codec/ber/__init__.py @@ -0,0 +1 @@ +# This file is necessary to make this directory a package. diff --git a/python/pyasn1/pyasn1/codec/ber/decoder.py b/python/pyasn1/pyasn1/codec/ber/decoder.py new file mode 100644 index 000000000..be0cf4907 --- /dev/null +++ b/python/pyasn1/pyasn1/codec/ber/decoder.py @@ -0,0 +1,808 @@ +# BER decoder +from pyasn1.type import tag, base, univ, char, useful, tagmap +from pyasn1.codec.ber import eoo +from pyasn1.compat.octets import oct2int, octs2ints, isOctetsType +from pyasn1 import debug, error + +class AbstractDecoder: + 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): + raise error.PyAsn1Error('Indefinite length mode decoder not implemented for %s' % (tagSet,)) + +class AbstractSimpleDecoder(AbstractDecoder): + tagFormats = (tag.tagFormatSimple,) + 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 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,) + 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 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 + ) + head, tail = substrate[:length], substrate[length:] + value, _ = decodeFun(head, asn1Spec, tagSet, length) + return value, tail + + def indefLenValueDecoder(self, fullSubstrate, substrate, asn1Spec, tagSet, + length, state, decodeFun, substrateFun): + if substrateFun: + return substrateFun( + 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: + 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) + 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) + 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 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, ()) + if substrateFun: + return substrateFun(r, substrate, length) + while head: + component, head = decodeFun(head) + r = r + component + return r, tail + + def indefLenValueDecoder(self, fullSubstrate, substrate, asn1Spec, tagSet, + length, state, decodeFun, substrateFun): + r = self._createComponent(asn1Spec, tagSet, '') + if substrateFun: + return substrateFun(r, substrate, length) + while substrate: + component, substrate = decodeFun(substrate) + if eoo.endOfOctets.isSameTypeWith(component) and \ + component == eoo.endOfOctets: + break + r = r + component + else: + raise error.SubstrateUnderrunError( + 'No EOO seen before substrate ends' + ) + return r, substrate + +class OctetStringDecoder(AbstractSimpleDecoder): + protoComponent = univ.OctetString('') + tagFormats = (tag.tagFormatSimple, tag.tagFormatConstructed) + 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) + while head: + component, head = decodeFun(head) + r = r + component + return r, tail + + def indefLenValueDecoder(self, fullSubstrate, substrate, asn1Spec, tagSet, + length, state, decodeFun, substrateFun): + r = self._createComponent(asn1Spec, tagSet, '') + if substrateFun: + return substrateFun(r, substrate, length) + while substrate: + component, substrate = decodeFun(substrate) + if eoo.endOfOctets.isSameTypeWith(component) and \ + component == eoo.endOfOctets: + break + r = r + component + else: + raise error.SubstrateUnderrunError( + 'No EOO seen before substrate ends' + ) + return r, 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) + if head: + raise error.PyAsn1Error('Unexpected %d-octet substrate for Null' % length) + return r, 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) + + index = 1 + 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') + elif subId > 128: + # Construct subid from a number of octets + nextSubId = subId + subId = 0 + while nextSubId >= 128: + subId = (subId << 7) + (nextSubId & 0x7F) + 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,) + 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 + n = (fo & 0x03) + 1 + if n == 4: + n = oct2int(head[0]) + 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 + e <<= 8 + e |= oct2int(eo[0]) + eo = eo[1:] + p = 0 + while head: # value + p <<= 8 + p |= oct2int(head[0]) + head = head[1:] + if fo & 0x40: # sign bit + p = -p + value = (p, 2, e) + elif fo & 0x40: # infinite value + value = fo & 0x01 and '-inf' or 'inf' + elif fo & 0xc0 == 0: # character encoding + try: + if fo & 0x3 == 0x1: # NR1 + value = (int(head), 10, 0) + elif fo & 0x3 == 0x2: # NR2 + value = float(head) + elif fo & 0x3 == 0x3: # NR3 + value = float(head) + 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): + protoComponent = univ.Sequence() + def _getComponentTagMap(self, r, idx): + try: + return r.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 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() + def valueDecoder(self, fullSubstrate, substrate, asn1Spec, tagSet, + length, state, decodeFun, substrateFun): + head, tail = substrate[:length], substrate[length:] + r = self._createComponent(asn1Spec, tagSet) + if substrateFun: + return substrateFun(r, substrate, length) + asn1Spec = r.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 + + def indefLenValueDecoder(self, fullSubstrate, substrate, asn1Spec, tagSet, + length, state, decodeFun, substrateFun): + r = self._createComponent(asn1Spec, tagSet) + if substrateFun: + return substrateFun(r, substrate, length) + asn1Spec = r.getComponentType() + idx = 0 + while substrate: + component, substrate = decodeFun(substrate, asn1Spec) + if eoo.endOfOctets.isSameTypeWith(component) and \ + component == eoo.endOfOctets: + break + r.setComponentByPosition(idx, component, asn1Spec is None) + idx = idx + 1 + else: + raise error.SubstrateUnderrunError( + 'No EOO seen before substrate ends' + ) + r.verifySizeSpec() + return r, substrate + +class SetDecoder(SequenceDecoder): + protoComponent = univ.Set() + def _getComponentTagMap(self, r, idx): + return r.getComponentTagMap() + + def _getComponentPositionByType(self, r, t, idx): + nextIdx = r.getComponentPositionByType(t) + 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) + if substrateFun: + return substrateFun(r, substrate, length) + if r.getTagSet() == tagSet: # explicitly tagged Choice + component, head = decodeFun( + head, r.getComponentTagMap() + ) + 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 + + def indefLenValueDecoder(self, fullSubstrate, substrate, asn1Spec, tagSet, + length, state, decodeFun, substrateFun): + r = 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: + raise error.PyAsn1Error('No EOO seen before substrate ends') + else: + component, substrate= decodeFun( + substrate, 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, 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(): + # untagged Any container, recover inner header substrate + length = length + len(fullSubstrate) - len(substrate) + substrate = fullSubstrate + if substrateFun: + return substrateFun(self._createComponent(asn1Spec, tagSet), + substrate, length) + head, tail = substrate[:length], substrate[length:] + return self._createComponent(asn1Spec, tagSet, value=head), tail + + def indefLenValueDecoder(self, fullSubstrate, substrate, asn1Spec, tagSet, + length, state, decodeFun, substrateFun): + if asn1Spec is not None and tagSet == asn1Spec.getTagSet(): + # tagged Any type -- consume header substrate + header = '' + 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) + while substrate: + component, substrate = decodeFun(substrate, asn1Spec) + if eoo.endOfOctets.isSameTypeWith(component) and \ + component == eoo.endOfOctets: + break + r = r + component + else: + raise error.SubstrateUnderrunError( + 'No EOO seen before substrate ends' + ) + return r, 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 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(), + univ.OctetString.tagSet: OctetStringDecoder(), + univ.Null.tagSet: NullDecoder(), + univ.ObjectIdentifier.tagSet: ObjectIdentifierDecoder(), + 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 + # character string types + char.UTF8String.tagSet: UTF8StringDecoder(), + char.NumericString.tagSet: NumericStringDecoder(), + char.PrintableString.tagSet: PrintableStringDecoder(), + char.TeletexString.tagSet: TeletexStringDecoder(), + char.VideotexString.tagSet: VideotexStringDecoder(), + char.IA5String.tagSet: IA5StringDecoder(), + char.GraphicString.tagSet: GraphicStringDecoder(), + char.VisibleString.tagSet: VisibleStringDecoder(), + char.GeneralString.tagSet: GeneralStringDecoder(), + char.UniversalString.tagSet: UniversalStringDecoder(), + char.BMPString.tagSet: BMPStringDecoder(), + # useful types + useful.GeneralizedTime.tagSet: GeneralizedTimeDecoder(), + useful.UTCTime.tagSet: UTCTimeDecoder() + } + +# Type-to-codec map for ambiguous ASN.1 types +typeMap = { + univ.Set.typeId: SetDecoder(), + univ.SetOf.typeId: SetOfDecoder(), + univ.Sequence.typeId: SequenceDecoder(), + 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)] + +class Decoder: + defaultErrorState = stErrorCondition +# defaultErrorState = stDumpRawValue + defaultRawDecoder = AnyDecoder() + def __init__(self, tagMap, typeMap={}): + self.__tagMap = tagMap + self.__typeMap = typeMap + self.__endOfOctetsTagSet = eoo.endOfOctets.getTagSet() + # Tag & TagSet objects caches + self.__tagCache = {} + self.__tagSetCache = {} + + def __call__(self, substrate, asn1Spec=None, tagSet=None, + length=None, state=stDecodeTag, recursiveFlag=1, + substrateFun=None): + if 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))) + 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') + + firstOctet = substrate[0] + substrate = substrate[1:] + if firstOctet in self.__tagCache: + lastTag = self.__tagCache[firstOctet] + else: + t = oct2int(firstOctet) + tagClass = t&0xC0 + tagFormat = t&0x20 + tagId = t&0x1F + if tagId == 0x1F: + 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 + lastTag = tag.Tag( + tagClass=tagClass, tagFormat=tagFormat, tagId=tagId + ) + if tagId < 31: + # 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: + self.__tagSetCache[firstOctet] = tagSet + else: + tagSet = lastTag + tagSet + state = stDecodeLength + debug.logger and debug.logger & debug.flagDecoder and debug.logger('tag decoded into %r, 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: + 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] + # missing check on maximum size, which shouldn't be a + # problem, we can handle more than is possible + if len(lengthString) != 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)) + ) + 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]))) + if state == stGetValueDecoder: + if asn1Spec is None: + state = stGetValueDecoderByTag + else: + state = stGetValueDecoderByAsn1Spec + # + # There're two ways of creating subtypes in ASN.1 what influences + # decoder operation. These methods are: + # 1) Either base types used in or no IMPLICIT tagging has been + # applied on subtyping. + # 2) Subtype syntax drops base type information (by means of + # IMPLICIT tagging. + # The first case allows for complete tag recovery from substrate + # while the second one requires original ASN.1 type spec for + # decoding. + # + # In either case a set of tags (tagSet) is coming from substrate + # 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: + concreteDecoder = self.__tagMap[tagSet] + else: + concreteDecoder = None + if concreteDecoder: + state = stDecodeValue + else: + _k = tagSet[:1] + if _k in self.__tagMap: + concreteDecoder = self.__tagMap[_k] + else: + concreteDecoder = None + if concreteDecoder: + state = stDecodeValue + else: + 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__) + if state == stGetValueDecoderByAsn1Spec: + if isinstance(asn1Spec, (dict, tagmap.TagMap)): + if tagSet in asn1Spec: + __chosenSpec = asn1Spec[tagSet] + else: + __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(): + 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)) + 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,)) + else: + concreteDecoder = None + if concreteDecoder: + 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__) + if state == stTryAsExplicitTag: + if tagSet and \ + tagSet[0][1] == tag.tagFormatConstructed and \ + tagSet[0][0] != tag.tagClassUniversal: + # Assume explicit tagging + concreteDecoder = explicitTagDecoder + state = stDecodeValue + 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__) + state = stDecodeValue + if state == stDecodeValue: + if recursiveFlag == 0 and not substrateFun: # legacy + substrateFun = lambda a,b,c: (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 '')) + if state == stErrorCondition: + raise error.PyAsn1Error( + '%r not in asn1Spec: %r' % (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 + +decode = Decoder(tagMap, typeMap) + +# XXX +# non-recursive decoding; return position rather than substrate diff --git a/python/pyasn1/pyasn1/codec/ber/encoder.py b/python/pyasn1/pyasn1/codec/ber/encoder.py new file mode 100644 index 000000000..173949d0b --- /dev/null +++ b/python/pyasn1/pyasn1/codec/ber/encoder.py @@ -0,0 +1,353 @@ +# BER encoder +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 import debug, error + +class Error(Exception): pass + +class AbstractItemEncoder: + supportIndefLenMode = 1 + def encodeTag(self, t, isConstructed): + tagClass, tagFormat, tagId = t.asTuple() # this is a hotspot + v = tagClass | tagFormat + if isConstructed: + v = v|tag.tagFormatConstructed + if tagId < 31: + return int2oct(v|tagId) + else: + s = int2oct(tagId&0x7f) + tagId = tagId >> 7 + while tagId: + s = int2oct(0x80|(tagId&0x7f)) + s + tagId = tagId >> 7 + return int2oct(v|0x1F) + s + + def encodeLength(self, length, defMode): + if not defMode and self.supportIndefLenMode: + return int2oct(0x80) + if length < 0x80: + return int2oct(length) + else: + substrate = null + while length: + substrate = int2oct(length&0xff) + substrate + length = length >> 8 + substrateLen = len(substrate) + if substrateLen > 126: + raise Error('Length octets overflow (%d)' % substrateLen) + return int2oct(0x80 | substrateLen) + substrate + + def encodeValue(self, encodeFun, value, defMode, maxChunkSize): + raise Error('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( + encodeFun, value, defMode, maxChunkSize + ) + tagSet = value.getTagSet() + 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 + +class EndOfOctetsEncoder(AbstractItemEncoder): + def encodeValue(self, encodeFun, value, defMode, maxChunkSize): + return null, 0 + +class ExplicitlyTaggedItemEncoder(AbstractItemEncoder): + def encodeValue(self, encodeFun, value, defMode, maxChunkSize): + if isinstance(value, base.AbstractConstructedAsn1Item): + value = value.clone(tagSet=value.getTagSet()[:-1], + cloneValueFlag=1) + else: + value = value.clone(tagSet=value.getTagSet()[:-1]) + return encodeFun(value, defMode, maxChunkSize), 1 + +explicitlyTaggedItemEncoder = ExplicitlyTaggedItemEncoder() + +class BooleanEncoder(AbstractItemEncoder): + supportIndefLenMode = 0 + _true = ints2octs((1,)) + _false = ints2octs((0,)) + def encodeValue(self, encodeFun, value, defMode, maxChunkSize): + return value and self._true or self._false, 0 + +class IntegerEncoder(AbstractItemEncoder): + supportIndefLenMode = 0 + supportCompactZero = False + def encodeValue(self, encodeFun, value, defMode, maxChunkSize): + if value == 0: # shortcut for zero value + if self.supportCompactZero: + # this seems to be a correct way for encoding zeros + return null, 0 + 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 + +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 + 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 + +class OctetStringEncoder(AbstractItemEncoder): + def encodeValue(self, encodeFun, value, defMode, maxChunkSize): + if not maxChunkSize or len(value) <= maxChunkSize: + return value.asOctets(), 0 + else: + pos = 0; substrate = null + while 1: + v = value.clone(value[pos:pos+maxChunkSize]) + if not v: + break + substrate = substrate + encodeFun(v, defMode, maxChunkSize) + pos = pos + maxChunkSize + return substrate, 1 + +class NullEncoder(AbstractItemEncoder): + supportIndefLenMode = 0 + def encodeValue(self, encodeFun, value, defMode, maxChunkSize): + return null, 0 + +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): + 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 + + # 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) + ) + else: + # Pack large Sub-Object IDs + res = (subid & 0x7f,) + subid = subid >> 7 + while subid > 0: + res = (0x80 | (subid & 0x7f),) + res + subid = subid >> 7 + # Add packed Sub-Object ID to resulted Object ID + octets += res + + return ints2octs(octets), 0 + +class RealEncoder(AbstractItemEncoder): + supportIndefLenMode = 0 + def encodeValue(self, encodeFun, value, defMode, maxChunkSize): + if value.isPlusInfinity(): + return int2oct(0x40), 0 + if value.isMinusInfinity(): + return int2oct(0x41), 0 + m, b, e = value + if not m: + return null, 0 + if b == 10: + return str2octs('\x03%dE%s%d' % (m, e == 0 and '+' or '', e)), 0 + 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 + m >>= 1 + e += 1 + 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 + n = len(eo) + if n > 0xff: + raise error.PyAsn1Error('Real exponent overflow') + if n == 1: + pass + elif n == 2: + fo |= 1 + elif n == 3: + fo |= 2 + else: + fo |= 3 + eo = int2oct(n//0xff+1) + eo + po = null + while m: + po = int2oct(m&0xff) + po + m >>= 8 + substrate = int2oct(fo) + eo + po + return substrate, 0 + 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) + 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 + +class SequenceOfEncoder(AbstractItemEncoder): + def encodeValue(self, encodeFun, value, defMode, maxChunkSize): + value.verifySizeSpec() + substrate = null; idx = len(value) + while idx > 0: + idx = idx - 1 + substrate = encodeFun( + value[idx], defMode, maxChunkSize + ) + substrate + return substrate, 1 + +class ChoiceEncoder(AbstractItemEncoder): + def encodeValue(self, encodeFun, value, defMode, maxChunkSize): + return encodeFun(value.getComponent(), defMode, maxChunkSize), 1 + +class AnyEncoder(OctetStringEncoder): + def encodeValue(self, encodeFun, value, defMode, maxChunkSize): + return value.asOctets(), defMode == 0 + +tagMap = { + eoo.endOfOctets.tagSet: EndOfOctetsEncoder(), + 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: OctetStringEncoder(), + char.NumericString.tagSet: OctetStringEncoder(), + char.PrintableString.tagSet: OctetStringEncoder(), + char.TeletexString.tagSet: OctetStringEncoder(), + char.VideotexString.tagSet: OctetStringEncoder(), + char.IA5String.tagSet: OctetStringEncoder(), + char.GraphicString.tagSet: OctetStringEncoder(), + char.VisibleString.tagSet: OctetStringEncoder(), + char.GeneralString.tagSet: OctetStringEncoder(), + char.UniversalString.tagSet: OctetStringEncoder(), + char.BMPString.tagSet: OctetStringEncoder(), + # useful types + useful.GeneralizedTime.tagSet: OctetStringEncoder(), + useful.UTCTime.tagSet: OctetStringEncoder() + } + +# Type-to-codec map for ambiguous ASN.1 types +typeMap = { + univ.Set.typeId: SequenceEncoder(), + univ.SetOf.typeId: SequenceOfEncoder(), + univ.Sequence.typeId: SequenceEncoder(), + univ.SequenceOf.typeId: SequenceOfEncoder(), + univ.Choice.typeId: ChoiceEncoder(), + univ.Any.typeId: AnyEncoder() + } + +class Encoder: + 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() + if len(tagSet) > 1: + concreteEncoder = explicitlyTaggedItemEncoder + else: + if value.typeId is not None and value.typeId in self.__typeMap: + 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)) + 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))) + return substrate + +encode = Encoder(tagMap, typeMap) diff --git a/python/pyasn1/pyasn1/codec/ber/eoo.py b/python/pyasn1/pyasn1/codec/ber/eoo.py new file mode 100644 index 000000000..379be1996 --- /dev/null +++ b/python/pyasn1/pyasn1/codec/ber/eoo.py @@ -0,0 +1,8 @@ +from pyasn1.type import base, tag + +class EndOfOctets(base.AbstractSimpleAsn1Item): + defaultValue = 0 + tagSet = tag.initTagSet( + tag.Tag(tag.tagClassUniversal, tag.tagFormatSimple, 0x00) + ) +endOfOctets = EndOfOctets() diff --git a/python/pyasn1/pyasn1/codec/cer/__init__.py b/python/pyasn1/pyasn1/codec/cer/__init__.py new file mode 100644 index 000000000..8c3066b2e --- /dev/null +++ b/python/pyasn1/pyasn1/codec/cer/__init__.py @@ -0,0 +1 @@ +# This file is necessary to make this directory a package. diff --git a/python/pyasn1/pyasn1/codec/cer/decoder.py b/python/pyasn1/pyasn1/codec/cer/decoder.py new file mode 100644 index 000000000..9fd37c134 --- /dev/null +++ b/python/pyasn1/pyasn1/codec/cer/decoder.py @@ -0,0 +1,35 @@ +# CER decoder +from pyasn1.type import univ +from pyasn1.codec.ber import decoder +from pyasn1.compat.octets import oct2int +from pyasn1 import error + +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') + 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 + # in http://www.itu.int/ITU-T/studygroups/com17/languages/X.690-0207.pdf + if byte == 0xff: + value = 1 + elif byte == 0x00: + value = 0 + else: + raise error.PyAsn1Error('Boolean CER violation: %s' % byte) + return self._createComponent(asn1Spec, tagSet, value), tail + +tagMap = decoder.tagMap.copy() +tagMap.update({ + univ.Boolean.tagSet: BooleanDecoder() + }) + +typeMap = decoder.typeMap + +class Decoder(decoder.Decoder): pass + +decode = Decoder(tagMap, decoder.typeMap) diff --git a/python/pyasn1/pyasn1/codec/cer/encoder.py b/python/pyasn1/pyasn1/codec/cer/encoder.py new file mode 100644 index 000000000..4c05130af --- /dev/null +++ b/python/pyasn1/pyasn1/codec/cer/encoder.py @@ -0,0 +1,87 @@ +# CER encoder +from pyasn1.type import univ +from pyasn1.codec.ber import encoder +from pyasn1.compat.octets import int2oct, null + +class BooleanEncoder(encoder.IntegerEncoder): + def encodeValue(self, encodeFun, client, defMode, maxChunkSize): + if client == 0: + substrate = int2oct(0) + else: + substrate = int2oct(255) + return substrate, 0 + +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 + ) + +# specialized RealEncoder here +# specialized GeneralStringEncoder here +# specialized GeneralizedTimeEncoder here +# specialized UTCTimeEncoder here + +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) + # 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 + comps = [] + while idx > 0: + idx = idx - 1 + if client[idx] is None: # Optional component + continue + if client.getDefaultComponentByPosition(idx) == client[idx]: + continue + comps.append(client[idx]) + comps.sort(key=lambda x: isinstance(x, univ.Choice) and \ + x.getMinTagSet() or x.getTagSet()) + for c in comps: + substrate += encodeFun(c, defMode, maxChunkSize) + else: + # SetOf + compSubs = [] + while idx > 0: + idx = 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 + +tagMap = encoder.tagMap.copy() +tagMap.update({ + univ.Boolean.tagSet: BooleanEncoder(), + univ.BitString.tagSet: BitStringEncoder(), + univ.OctetString.tagSet: OctetStringEncoder(), + univ.SetOf().tagSet: SetOfEncoder() # conflcts with Set + }) + +typeMap = encoder.typeMap.copy() +typeMap.update({ + univ.Set.typeId: SetOfEncoder(), + univ.SetOf.typeId: SetOfEncoder() + }) + +class Encoder(encoder.Encoder): + def __call__(self, client, defMode=0, maxChunkSize=0): + return encoder.Encoder.__call__(self, client, defMode, maxChunkSize) + +encode = Encoder(tagMap, typeMap) + +# EncoderFactory queries class instance and builds a map of tags -> encoders diff --git a/python/pyasn1/pyasn1/codec/der/__init__.py b/python/pyasn1/pyasn1/codec/der/__init__.py new file mode 100644 index 000000000..8c3066b2e --- /dev/null +++ b/python/pyasn1/pyasn1/codec/der/__init__.py @@ -0,0 +1 @@ +# This file is necessary to make this directory a package. diff --git a/python/pyasn1/pyasn1/codec/der/decoder.py b/python/pyasn1/pyasn1/codec/der/decoder.py new file mode 100644 index 000000000..604abec2b --- /dev/null +++ b/python/pyasn1/pyasn1/codec/der/decoder.py @@ -0,0 +1,9 @@ +# DER decoder +from pyasn1.type import univ +from pyasn1.codec.cer import decoder + +tagMap = decoder.tagMap +typeMap = decoder.typeMap +Decoder = decoder.Decoder + +decode = Decoder(tagMap, typeMap) diff --git a/python/pyasn1/pyasn1/codec/der/encoder.py b/python/pyasn1/pyasn1/codec/der/encoder.py new file mode 100644 index 000000000..4e5faefad --- /dev/null +++ b/python/pyasn1/pyasn1/codec/der/encoder.py @@ -0,0 +1,28 @@ +# DER encoder +from pyasn1.type import univ +from pyasn1.codec.cer import encoder + +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() + return cmp(tagSet1, tagSet2) + +tagMap = encoder.tagMap.copy() +tagMap.update({ + # Overload CER encodrs 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 + +class Encoder(encoder.Encoder): + def __call__(self, client, defMode=1, maxChunkSize=0): + return encoder.Encoder.__call__(self, client, defMode, maxChunkSize) + +encode = Encoder(tagMap, typeMap) diff --git a/python/pyasn1/pyasn1/compat/__init__.py b/python/pyasn1/pyasn1/compat/__init__.py new file mode 100644 index 000000000..8c3066b2e --- /dev/null +++ b/python/pyasn1/pyasn1/compat/__init__.py @@ -0,0 +1 @@ +# This file is necessary to make this directory a package. diff --git a/python/pyasn1/pyasn1/compat/octets.py b/python/pyasn1/pyasn1/compat/octets.py new file mode 100644 index 000000000..f7f2a29bf --- /dev/null +++ b/python/pyasn1/pyasn1/compat/octets.py @@ -0,0 +1,20 @@ +from sys import version_info + +if version_info[0] <= 2: + int2oct = chr + ints2octs = lambda s: ''.join([ int2oct(x) for x in s ]) + null = '' + oct2int = ord + octs2ints = lambda s: [ oct2int(x) for x in s ] + str2octs = lambda x: x + octs2str = lambda x: x + isOctetsType = lambda s: isinstance(s, str) +else: + ints2octs = bytes + int2oct = lambda x: ints2octs((x,)) + null = ints2octs() + oct2int = lambda x: x + octs2ints = lambda s: [ x for x in s ] + str2octs = lambda x: x.encode() + octs2str = lambda x: x.decode() + isOctetsType = lambda s: isinstance(s, bytes) diff --git a/python/pyasn1/pyasn1/debug.py b/python/pyasn1/pyasn1/debug.py new file mode 100644 index 000000000..c27cb1d44 --- /dev/null +++ b/python/pyasn1/pyasn1/debug.py @@ -0,0 +1,65 @@ +import sys +from pyasn1.compat.octets import octs2ints +from pyasn1 import error +from pyasn1 import __version__ + +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): + self._flags = flagNone + self._printer = self.defaultPrinter + 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) + + def __str__(self): + return 'logger %s, flags %x' % (self._printer, self._flags) + + def __call__(self, msg): + self._printer('DBG: %s\n' % msg) + + def __and__(self, flag): + return self._flags & flag + + 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)) ] + ) + +class Scope: + def __init__(self): + self._list = [] + + def __str__(self): return '.'.join(self._list) + + def push(self, token): + self._list.append(token) + + def pop(self): + return self._list.pop() + +scope = Scope() diff --git a/python/pyasn1/pyasn1/error.py b/python/pyasn1/pyasn1/error.py new file mode 100644 index 000000000..716406ff6 --- /dev/null +++ b/python/pyasn1/pyasn1/error.py @@ -0,0 +1,3 @@ +class PyAsn1Error(Exception): pass +class ValueConstraintError(PyAsn1Error): pass +class SubstrateUnderrunError(PyAsn1Error): pass diff --git a/python/pyasn1/pyasn1/type/__init__.py b/python/pyasn1/pyasn1/type/__init__.py new file mode 100644 index 000000000..8c3066b2e --- /dev/null +++ b/python/pyasn1/pyasn1/type/__init__.py @@ -0,0 +1 @@ +# This file is necessary to make this directory a package. diff --git a/python/pyasn1/pyasn1/type/base.py b/python/pyasn1/pyasn1/type/base.py new file mode 100644 index 000000000..40873719c --- /dev/null +++ b/python/pyasn1/pyasn1/type/base.py @@ -0,0 +1,249 @@ +# Base classes for ASN.1 types +import sys +from pyasn1.type import constraint, tagmap +from pyasn1 import error + +class Asn1Item: pass + +class Asn1ItemBase(Asn1Item): + # Set of tags for this ASN.1 type + tagSet = () + + # A list of constraint.Constraint instances for checking values + subtypeSpec = constraint.ConstraintsIntersection() + + # Used for ambiguous ASN.1 types identification + typeId = None + + def __init__(self, tagSet=None, subtypeSpec=None): + if tagSet is None: + self._tagSet = self.tagSet + else: + self._tagSet = tagSet + if subtypeSpec is None: + self._subtypeSpec = self.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()) + +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() + +# Base class for "simple" ASN.1 objects. These are immutable. +class AbstractSimpleAsn1Item(Asn1ItemBase): + defaultValue = noValue + def __init__(self, value=None, 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) + 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) + 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 + if sys.version_info[0] <= 2: + def __nonzero__(self): return bool(self._value) + else: + def __bool__(self): return bool(self._value) + def __hash__(self): return self.__hashedValue + + def clone(self, value=None, tagSet=None, subtypeSpec=None): + if value is None and tagSet is None and subtypeSpec is None: + 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, + subtypeSpec=None): + if value is None: + value = self._value + if implicitTag is not None: + tagSet = self._tagSet.tagImplicitly(implicitTag) + elif explicitTag is not None: + tagSet = self._tagSet.tagExplicitly(explicitTag) + else: + tagSet = self._tagSet + if subtypeSpec is None: + subtypeSpec = self._subtypeSpec + else: + subtypeSpec = subtypeSpec + self._subtypeSpec + return self.__class__(value, tagSet, subtypeSpec) + + 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: + return self.prettyOut(self._value) + + # XXX Compatibility stub + def prettyPrinter(self, scope=0): return self.prettyPrint(scope) + +# +# Constructed types: +# * There are five of them: Sequence, SequenceOf/SetOf, Set and Choice +# * ASN1 types and values are represened by Python class instances +# * Value initialization is made for defaulted components only +# * Primary method of component addressing is by-position. Data model for base +# type is Python sequence. Additional type-specific addressing methods +# may be implemented for particular types. +# * SequenceOf and SetOf types do not implement any additional methods +# * Sequence, Set and Choice types also implement by-identifier addressing +# * Sequence, Set and Choice types also implement by-asn1-type (tag) addressing +# * Sequence and Set types may include optional and defaulted +# components +# * Constructed types hold a reference to component types used for value +# verification and ordering. +# * Component type is a scalar type for SequenceOf/SetOf types and a list +# of types for Sequence/Set/Choice. +# + +class AbstractConstructedAsn1Item(Asn1ItemBase): + componentType = None + sizeSpec = constraint.ConstraintsIntersection() + def __init__(self, componentType=None, tagSet=None, + subtypeSpec=None, sizeSpec=None): + Asn1ItemBase.__init__(self, tagSet, subtypeSpec) + if componentType is None: + self._componentType = self.componentType + else: + self._componentType = componentType + if sizeSpec is None: + self._sizeSpec = self.sizeSpec + 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 + + 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 + if sys.version_info[0] <= 2: + def __nonzero__(self): return bool(self._componentValues) + else: + def __bool__(self): return bool(self._componentValues) + + def getComponentTagMap(self): + raise error.PyAsn1Error('Method not implemented') + + def _cloneComponentValues(self, myClone, cloneValueFlag): pass + + def clone(self, tagSet=None, subtypeSpec=None, sizeSpec=None, + cloneValueFlag=None): + 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) + if cloneValueFlag: + self._cloneComponentValues(r, cloneValueFlag) + return r + + def subtype(self, implicitTag=None, explicitTag=None, subtypeSpec=None, + sizeSpec=None, cloneValueFlag=None): + if implicitTag is not None: + tagSet = self._tagSet.tagImplicitly(implicitTag) + elif explicitTag is not None: + tagSet = self._tagSet.tagExplicitly(explicitTag) + else: + tagSet = self._tagSet + if subtypeSpec is None: + subtypeSpec = self._subtypeSpec + else: + subtypeSpec = subtypeSpec + self._subtypeSpec + if sizeSpec is None: + sizeSpec = self._sizeSpec + else: + sizeSpec = sizeSpec + self._sizeSpec + r = self.__class__(self._componentType, tagSet, subtypeSpec, sizeSpec) + if cloneValueFlag: + self._cloneComponentValues(r, cloneValueFlag) + return r + + def _verifyComponent(self, idx, value): pass + + 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 __getitem__(self, idx): return self.getComponentByPosition(idx) + def __setitem__(self, idx, value): self.setComponentByPosition(idx, value) + + def __len__(self): return len(self._componentValues) + + def clear(self): + self._componentValues = [] + self._componentValuesSet = 0 + + def setDefaultComponents(self): pass diff --git a/python/pyasn1/pyasn1/type/char.py b/python/pyasn1/pyasn1/type/char.py new file mode 100644 index 000000000..ae112f8bd --- /dev/null +++ b/python/pyasn1/pyasn1/type/char.py @@ -0,0 +1,61 @@ +# ASN.1 "character string" types +from pyasn1.type import univ, tag + +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( + tag.Tag(tag.tagClassUniversal, tag.tagFormatSimple, 18) + ) + +class PrintableString(univ.OctetString): + tagSet = univ.OctetString.tagSet.tagImplicitly( + tag.Tag(tag.tagClassUniversal, tag.tagFormatSimple, 19) + ) + +class TeletexString(univ.OctetString): + tagSet = univ.OctetString.tagSet.tagImplicitly( + tag.Tag(tag.tagClassUniversal, tag.tagFormatSimple, 20) + ) + + +class VideotexString(univ.OctetString): + tagSet = univ.OctetString.tagSet.tagImplicitly( + tag.Tag(tag.tagClassUniversal, tag.tagFormatSimple, 21) + ) + +class IA5String(univ.OctetString): + tagSet = univ.OctetString.tagSet.tagImplicitly( + tag.Tag(tag.tagClassUniversal, tag.tagFormatSimple, 22) + ) + +class GraphicString(univ.OctetString): + tagSet = univ.OctetString.tagSet.tagImplicitly( + tag.Tag(tag.tagClassUniversal, tag.tagFormatSimple, 25) + ) + +class VisibleString(univ.OctetString): + tagSet = univ.OctetString.tagSet.tagImplicitly( + tag.Tag(tag.tagClassUniversal, tag.tagFormatSimple, 26) + ) + +class GeneralString(univ.OctetString): + tagSet = univ.OctetString.tagSet.tagImplicitly( + tag.Tag(tag.tagClassUniversal, tag.tagFormatSimple, 27) + ) + +class UniversalString(univ.OctetString): + tagSet = univ.OctetString.tagSet.tagImplicitly( + tag.Tag(tag.tagClassUniversal, tag.tagFormatSimple, 28) + ) + encoding = "utf-32-be" + +class BMPString(univ.OctetString): + tagSet = univ.OctetString.tagSet.tagImplicitly( + tag.Tag(tag.tagClassUniversal, tag.tagFormatSimple, 30) + ) + encoding = "utf-16-be" diff --git a/python/pyasn1/pyasn1/type/constraint.py b/python/pyasn1/pyasn1/type/constraint.py new file mode 100644 index 000000000..66873937d --- /dev/null +++ b/python/pyasn1/pyasn1/type/constraint.py @@ -0,0 +1,200 @@ +# +# ASN.1 subtype constraints classes. +# +# 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. +# +# 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. +# +import sys +from pyasn1.type import error + +class AbstractConstraint: + """Abstract base-class for constraint objects + + Constraints should be stored in a simple sequence in the + namespace of their client Asn1Item sub-classes. + """ + def __init__(self, *values): + self._valueMap = {} + self._setValues(values) + self.__hashedValues = None + def __call__(self, value, idx=None): + try: + self._testValue(value, idx) + except error.ValueConstraintError: + raise error.ValueConstraintError( + '%s failed at: \"%s\"' % (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 + if sys.version_info[0] <= 2: + def __nonzero__(self): return bool(self._values) + else: + def __bool__(self): return bool(self._values) + + 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 + def _testValue(self, value, idx): + raise error.ValueConstraintError(value) + + # Constraints derivation logic + def getValueMap(self): return self._valueMap + def isSuperTypeOf(self, otherConstraint): + return self in otherConstraint.getValueMap() or \ + otherConstraint is self or otherConstraint == self + def isSubTypeOf(self, otherConstraint): + return otherConstraint in self._valueMap or \ + otherConstraint is self or otherConstraint == self + +class SingleValueConstraint(AbstractConstraint): + """Value must be part of defined values constraint""" + def _testValue(self, value, idx): + # XXX index vals for performance? + if value not in self._values: + 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) + + def _setValues(self, values): + 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( + '%s: screwed constraint values (start > stop): %s > %s' % ( + self.__class__.__name__, + self.start, self.stop + ) + ) + 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: + raise error.ValueConstraintError(value) + +class PermittedAlphabetConstraint(SingleValueConstraint): + def _setValues(self, values): + self._values = () + for v in values: + self._values = self._values + tuple(v) + + def _testValue(self, value, idx): + for v in value: + if v not in self._values: + raise error.ValueConstraintError(value) + +# This is a bit kludgy, meaning two op modes within a single constraing +class InnerTypeConstraint(AbstractConstraint): + """Value must satisfy type and presense constraints""" + def _testValue(self, value, idx): + if self.__singleTypeConstraint: + self.__singleTypeConstraint(value) + elif self.__multipleTypeConstraint: + if idx not in self.__multipleTypeConstraint: + raise error.ValueConstraintError(value) + constraint, status = self.__multipleTypeConstraint[idx] + if status == 'ABSENT': # XXX presense is not checked! + raise error.ValueConstraintError(value) + constraint(value) + + def _setValues(self, values): + self.__multipleTypeConstraint = {} + self.__singleTypeConstraint = None + for v in values: + if isinstance(v, tuple): + self.__multipleTypeConstraint[v[0]] = v[1], v[2] + else: + self.__singleTypeConstraint = v + AbstractConstraint._setValues(self, values) + +# 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) + except error.ValueConstraintError: + return + else: + raise error.ValueConstraintError(value) + + def _setValues(self, values): + if len(values) != 1: + 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 __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()) + +class ConstraintsIntersection(AbstractConstraintSet): + """Value must satisfy all constraints""" + def _testValue(self, value, idx): + for v in self._values: + v(value, idx) + +class ConstraintsUnion(AbstractConstraintSet): + """Value must satisfy at least one constraint""" + def _testValue(self, value, idx): + for v in self._values: + try: + v(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/python/pyasn1/pyasn1/type/error.py b/python/pyasn1/pyasn1/type/error.py new file mode 100644 index 000000000..3e6848447 --- /dev/null +++ b/python/pyasn1/pyasn1/type/error.py @@ -0,0 +1,3 @@ +from pyasn1.error import PyAsn1Error + +class ValueConstraintError(PyAsn1Error): pass diff --git a/python/pyasn1/pyasn1/type/namedtype.py b/python/pyasn1/pyasn1/type/namedtype.py new file mode 100644 index 000000000..48967a5fe --- /dev/null +++ b/python/pyasn1/pyasn1/type/namedtype.py @@ -0,0 +1,132 @@ +# NamedType specification for constructed types +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 + def __getitem__(self, idx): + if idx == 0: return self.__name + if idx == 1: return self.__type + raise IndexError() + +class OptionalNamedType(NamedType): + isOptional = 1 +class DefaultedNamedType(NamedType): + isDefaulted = 1 + +class NamedTypes: + 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 = {} + + 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] + + if sys.version_info[0] <= 2: + def __nonzero__(self): return bool(self.__namedTypesLen) + 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 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 + 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() + 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 + try: + return self.__nameToPosIdx[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() + 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() + try: + return idx+self.__ambigiousTypes[idx].getPositionByType(tagSet) + except KeyError: + raise error.PyAsn1Error('Type position out of range') + + def genMinTagSet(self): + if self.__minTagSet is None: + for t in self.__namedTypes: + __type = t.getType() + tagSet = getattr(__type,'getMinTagSet',__type.getTagSet)() + 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] diff --git a/python/pyasn1/pyasn1/type/namedval.py b/python/pyasn1/pyasn1/type/namedval.py new file mode 100644 index 000000000..d0fea7cc7 --- /dev/null +++ b/python/pyasn1/pyasn1/type/namedval.py @@ -0,0 +1,46 @@ +# ASN.1 named integers +from pyasn1 import error + +__all__ = [ 'NamedValues' ] + +class NamedValues: + def __init__(self, *namedValues): + self.nameToValIdx = {}; self.valToNameIdx = {} + self.namedValues = () + automaticVal = 1 + for namedValue in namedValues: + if isinstance(namedValue, tuple): + name, val = namedValue + else: + name = namedValue + val = automaticVal + if name in self.nameToValIdx: + raise error.PyAsn1Error('Duplicate name %s' % (name,)) + self.nameToValIdx[name] = val + if val in self.valToNameIdx: + 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) + + def getName(self, value): + if value in self.valToNameIdx: + return self.valToNameIdx[value] + + 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 __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) + +# XXX clone/subtype? diff --git a/python/pyasn1/pyasn1/type/tag.py b/python/pyasn1/pyasn1/type/tag.py new file mode 100644 index 000000000..1144907fa --- /dev/null +++ b/python/pyasn1/pyasn1/type/tag.py @@ -0,0 +1,122 @@ +# ASN.1 types tags +from operator import getitem +from pyasn1 import error + +tagClassUniversal = 0x00 +tagClassApplication = 0x40 +tagClassContext = 0x80 +tagClassPrivate = 0xC0 + +tagFormatSimple = 0x00 +tagFormatConstructed = 0x20 + +tagCategoryImplicit = 0x01 +tagCategoryExplicit = 0x02 +tagCategoryUntagged = 0x04 + +class Tag: + 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) + + 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] + def __and__(self, otherTag): + (tagClass, tagFormat, tagId) = otherTag + return self.__class__( + self.__tag&tagClass, self.__tag&tagFormat, self.__tag&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: + 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.__lenOfSuperTags = len(superTags) + + def __repr__(self): + return '%s(%s)' % ( + self.__class__.__name__, + ', '.join([repr(x) for x in self.__superTags]) + ) + + def __add__(self, superTag): + return self.__class__( + self.__baseTag, *self.__superTags + (superTag,) + ) + def __radd__(self, superTag): + return self.__class__( + self.__baseTag, *(superTag,) + 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 self + superTag + + def tagImplicitly(self, superTag): + tagClass, tagFormat, tagId = superTag + if self.__superTags: + superTag = Tag(tagClass, self.__superTags[-1][1], 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): + 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) diff --git a/python/pyasn1/pyasn1/type/tagmap.py b/python/pyasn1/pyasn1/type/tagmap.py new file mode 100644 index 000000000..7cec3a10e --- /dev/null +++ b/python/pyasn1/pyasn1/type/tagmap.py @@ -0,0 +1,52 @@ +from pyasn1 import error + +class TagMap: + def __init__(self, posMap={}, negMap={}, defType=None): + self.__posMap = posMap.copy() + self.__negMap = negMap.copy() + self.__defType = defType + + def __contains__(self, tagSet): + return tagSet in self.__posMap or \ + self.__defType is not None and tagSet not in self.__negMap + + 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() + + def __repr__(self): + s = '%r/%r' % (self.__posMap, self.__negMap) + if self.__defType is not None: + s = s + '/%r' % (self.__defType,) + 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 + + negMap = self.__negMap.copy() + negMap.update(tagMap.getNegMap()) + + return self.__class__( + posMap, negMap, defType, + ) + + def getPosMap(self): return self.__posMap.copy() + def getNegMap(self): return self.__negMap.copy() + def getDef(self): return self.__defType diff --git a/python/pyasn1/pyasn1/type/univ.py b/python/pyasn1/pyasn1/type/univ.py new file mode 100644 index 000000000..9cd16f8a2 --- /dev/null +++ b/python/pyasn1/pyasn1/type/univ.py @@ -0,0 +1,1042 @@ +# ASN.1 "universal" data types +import operator, sys +from pyasn1.type import base, tag, constraint, namedtype, namedval, tagmap +from pyasn1.codec.ber import eoo +from pyasn1.compat import octets +from pyasn1 import error + +# "Simple" ASN.1 types (yet incomplete) + +class Integer(base.AbstractSimpleAsn1Item): + tagSet = baseTagSet = tag.initTagSet( + tag.Tag(tag.tagClassUniversal, tag.tagFormatSimple, 0x02) + ) + namedValues = namedval.NamedValues() + def __init__(self, value=None, tagSet=None, subtypeSpec=None, + namedValues=None): + if namedValues is None: + self.__namedValues = self.namedValues + else: + 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 __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)) + + 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) + 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) + + __hash__ = base.AbstractSimpleAsn1Item.__hash__ + + 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 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: + raise error.PyAsn1Error( + 'Can\'t coerce %s 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) + + 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: + value = self._value + if tagSet is None: + tagSet = self._tagSet + if subtypeSpec is None: + subtypeSpec = self._subtypeSpec + if namedValues is None: + namedValues = self.__namedValues + return self.__class__(value, tagSet, subtypeSpec, namedValues) + + def subtype(self, value=None, implicitTag=None, explicitTag=None, + subtypeSpec=None, namedValues=None): + if value is None: + value = self._value + if implicitTag is not None: + tagSet = self._tagSet.tagImplicitly(implicitTag) + elif explicitTag is not None: + tagSet = self._tagSet.tagExplicitly(explicitTag) + else: + tagSet = self._tagSet + if subtypeSpec is None: + subtypeSpec = self._subtypeSpec + else: + subtypeSpec = subtypeSpec + self._subtypeSpec + if namedValues is None: + namedValues = self.__namedValues + else: + namedValues = namedValues + self.__namedValues + return self.__class__(value, tagSet, subtypeSpec, namedValues) + +class Boolean(Integer): + tagSet = baseTagSet = tag.initTagSet( + tag.Tag(tag.tagClassUniversal, tag.tagFormatSimple, 0x01), + ) + subtypeSpec = Integer.subtypeSpec+constraint.SingleValueConstraint(0,1) + namedValues = Integer.namedValues.clone(('False', 0), ('True', 1)) + +class BitString(base.AbstractSimpleAsn1Item): + tagSet = baseTagSet = tag.initTagSet( + tag.Tag(tag.tagClassUniversal, tag.tagFormatSimple, 0x03) + ) + namedValues = namedval.NamedValues() + def __init__(self, value=None, tagSet=None, subtypeSpec=None, + namedValues=None): + if namedValues is None: + self.__namedValues = self.namedValues + else: + self.__namedValues = namedValues + 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: + value = self._value + if tagSet is None: + tagSet = self._tagSet + if subtypeSpec is None: + subtypeSpec = self._subtypeSpec + if namedValues is None: + namedValues = self.__namedValues + return self.__class__(value, tagSet, subtypeSpec, namedValues) + + def subtype(self, value=None, implicitTag=None, explicitTag=None, + subtypeSpec=None, namedValues=None): + if value is None: + value = self._value + if implicitTag is not None: + tagSet = self._tagSet.tagImplicitly(implicitTag) + elif explicitTag is not None: + tagSet = self._tagSet.tagExplicitly(explicitTag) + else: + tagSet = self._tagSet + if subtypeSpec is None: + subtypeSpec = self._subtypeSpec + else: + subtypeSpec = subtypeSpec + self._subtypeSpec + if namedValues is None: + namedValues = self.__namedValues + else: + namedValues = namedValues + self.__namedValues + return self.__class__(value, tagSet, subtypeSpec, namedValues) + + def __str__(self): return str(tuple(self)) + + # 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] + + 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 prettyIn(self, value): + r = [] + if not value: + return () + elif isinstance(value, str): + if value[0] == '\'': + 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) + 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) + 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 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) + else: + raise error.PyAsn1Error( + 'Bad BitString initializer type \'%s\'' % (value,) + ) + + def prettyOut(self, value): + return '\"\'%s\'B\"' % ''.join([str(x) for x in value]) + +class OctetString(base.AbstractSimpleAsn1Item): + tagSet = baseTagSet = 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): + if encoding is None: + self._encoding = self.encoding + else: + self._encoding = encoding + if binValue is not None: + value = self.fromBinaryString(binValue) + if hexValue is not None: + 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 + 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: + value = self._value + if tagSet is None: + tagSet = self._tagSet + if subtypeSpec is None: + subtypeSpec = self._subtypeSpec + if encoding is None: + encoding = self._encoding + return self.__class__( + value, tagSet, subtypeSpec, encoding, binValue, hexValue + ) + + if sys.version_info[0] <= 2: + def prettyIn(self, value): + if isinstance(value, str): + return value + elif isinstance(value, (tuple, list)): + try: + return ''.join([ chr(x) for x in value ]) + except ValueError: + raise error.PyAsn1Error( + 'Bad OctetString initializer \'%s\'' % (value,) + ) + else: + return str(value) + else: + def prettyIn(self, value): + if isinstance(value, bytes): + return value + elif isinstance(value, OctetString): + return value.asOctets() + elif isinstance(value, (tuple, list, map)): + try: + return bytes(value) + except ValueError: + raise error.PyAsn1Error( + 'Bad OctetString initializer \'%s\'' % (value,) + ) + else: + try: + return str(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 = () + else: + p = v + if p: + r = r + (int(p+'0', 16),) + return octets.ints2octs(r) + + def prettyOut(self, value): + if sys.version_info[0] <= 2: + 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 ]) + else: + return str(value) + + 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 + + # 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] + + 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 + +class Null(OctetString): + defaultValue = ''.encode() # This is tightly constrained + tagSet = baseTagSet = tag.initTagSet( + tag.Tag(tag.tagClassUniversal, tag.tagFormatSimple, 0x05) + ) + subtypeSpec = OctetString.subtypeSpec+constraint.SingleValueConstraint(''.encode()) + +if sys.version_info[0] <= 2: + intTypes = (int, long) +else: + intTypes = int + +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) + + 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) + ) + else: + return self._value[i] + + def __str__(self): return self.prettyPrint() + + def index(self, suboid): return self._value.index(suboid) + + def isPrefixOf(self, value): + """Returns true if argument OID resides deeper in the OID tree""" + l = len(self) + if l <= len(value): + if self._value[:l] == value[:l]: + return 1 + return 0 + + 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: + try: + value = tuple(value) + except TypeError: + raise error.PyAsn1Error( + 'Malformed Object ID %s at %s: %s' % + (str(value), self.__class__.__name__,sys.exc_info()[1]) + ) + + 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): + try: + _plusInf = float('inf') + _minusInf = float('-inf') + _inf = (_plusInf, _minusInf) + except ValueError: + # Infinity support is platform and Python dependent + _plusInf = _minusInf = None + _inf = () + + tagSet = baseTagSet = tag.initTagSet( + tag.Tag(tag.tagClassUniversal, tag.tagFormatSimple, 0x09) + ) + + def __normalizeBase10(self, value): + m, b, e = value + while m and m % 10 == 0: + m = m / 10 + e = 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 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): + if self._inf and value in self._inf: + return value + else: + e = 0 + while int(value) != value: + value = value * 10 + e = 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))) + + 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 __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 + + if sys.version_info[0] <= 2: + def __nonzero__(self): return bool(float(self)) + else: + def __bool__(self): return bool(float(self)) + __hash__ = base.AbstractSimpleAsn1Item.__hash__ + + def __getitem__(self, idx): + if self._value in self._inf: + raise error.PyAsn1Error('Invalid infinite value operation') + else: + return self._value[idx] + +class Enumerated(Integer): + tagSet = baseTagSet = tag.initTagSet( + tag.Tag(tag.tagClassUniversal, tag.tagFormatSimple, 0x0A) + ) + +# "Structured" ASN.1 types + +class SetOf(base.AbstractConstructedAsn1Item): + componentType = None + tagSet = baseTagSet = tag.initTagSet( + tag.Tag(tag.tagClassUniversal, tag.tagFormatConstructed, 0x11) + ) + typeId = 1 + + 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 self._componentType is not None and \ + not self._componentType.isSuperTypeOf(value): + raise error.PyAsn1Error('Component type error %s' % (value,)) + + 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: + raise error.PyAsn1Error('Component type not defined') + if isinstance(self._componentType, base.AbstractSimpleAsn1Item): + value = self._componentType.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 + return self + + def getComponentTagMap(self): + if self._componentType is not None: + return self._componentType.getTagMap() + + def prettyPrint(self, scope=0): + scope = scope + 1 + r = self.__class__.__name__ + ':\n' + for idx in range(len(self._componentValues)): + r = r + ' '*scope + if self._componentValues[idx] is None: + r = r + '' + else: + r = r + self._componentValues[idx].prettyPrint(scope) + return r + +class SequenceOf(SetOf): + tagSet = baseTagSet = tag.initTagSet( + tag.Tag(tag.tagClassUniversal, tag.tagFormatConstructed, 0x10) + ) + typeId = 2 + +class SequenceAndSetBase(base.AbstractConstructedAsn1Item): + componentType = namedtype.NamedTypes() + def __init__(self, componentType=None, tagSet=None, + subtypeSpec=None, sizeSpec=None): + base.AbstractConstructedAsn1Item.__init__( + self, componentType, tagSet, subtypeSpec, sizeSpec + ) + if self._componentType is None: + self._componentTypeLen = 0 + else: + self._componentTypeLen = len(self._componentType) + + def __getitem__(self, idx): + if isinstance(idx, str): + return self.getComponentByName(idx) + else: + return base.AbstractConstructedAsn1Item.__getitem__(self, idx) + + def __setitem__(self, idx, value): + if isinstance(idx, str): + 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 getComponentByName(self, name): + return self.getComponentByPosition( + self._componentType.getPositionByName(name) + ) + def setComponentByName(self, name, value=None, verifyConstraints=True): + return self.setComponentByPosition( + self._componentType.getPositionByName(name), value, + verifyConstraints + ) + + def getComponentByPosition(self, idx): + try: + return 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 + elif not isinstance(value, base.Asn1Item): + t = self._componentType.getTypeByPosition(idx) + if isinstance(t, base.AbstractSimpleAsn1Item): + value = t.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 + 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) + ) + + def prettyPrint(self, scope=0): + scope = scope + 1 + r = self.__class__.__name__ + ':\n' + for idx in range(len(self._componentValues)): + if self._componentValues[idx] is not None: + r = r + ' '*scope + componentType = self.getComponentType() + if componentType is None: + r = r + '' + else: + r = r + componentType.getNameByPosition(idx) + r = '%s=%s\n' % ( + r, self._componentValues[idx].prettyPrint(scope) + ) + return r + +class Sequence(SequenceAndSetBase): + tagSet = baseTagSet = tag.initTagSet( + tag.Tag(tag.tagClassUniversal, tag.tagFormatConstructed, 0x10) + ) + typeId = 3 + + 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( + self._componentType.getPositionByType(tagSet) + ) + if innerFlag and isinstance(c, Set): + # get inner component by inner tagSet + return c.getComponent(1) + else: + # get outer component by inner tagSet + return c + + def setComponentByType(self, tagSet, value=None, innerFlag=0, + verifyConstraints=True): + idx = self._componentType.getPositionByType(tagSet) + t = self._componentType.getTypeByPosition(idx) + if innerFlag: # set inner component by inner tagSet + if t.getTagSet(): + return self.setComponentByPosition( + idx, value, verifyConstraints + ) + else: + t = self.setComponentByPosition(idx).getComponentByPosition(idx) + return t.setComponentByType( + tagSet, value, innerFlag, verifyConstraints + ) + else: # set outer component by inner tagSet + return self.setComponentByPosition( + idx, value, verifyConstraints + ) + + def getComponentTagMap(self): + if self._componentType: + return self._componentType.getTagMap(True) + + def getComponentPositionByType(self, tagSet): + if self._componentType: + return self._componentType.getPositionByType(tagSet) + +class Choice(Set): + tagSet = baseTagSet = tag.TagSet() # untagged + sizeSpec = constraint.ConstraintsIntersection( + constraint.ValueSizeConstraint(1, 1) + ) + typeId = 5 + _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 + + 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() + except error.PyAsn1Error: + pass + else: + if isinstance(c, Choice): + tagSet = c.getEffectiveTagSet() + else: + tagSet = c.getTagSet() + if isinstance(c, 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 + ) + if verifyConstraints: + if self._componentTypeLen: + self._verifyComponent(idx, value) + self._verifySubtypeSpec(value, idx) + self._componentValues[idx] = value + self._currentIdx = idx + self._componentValuesSet = 1 + return self + + def getMinTagSet(self): + if self._tagSet: + return self._tagSet + else: + return self._componentType.genMinTagSet() + + def getEffectiveTagSet(self): + if self._tagSet: + return self._tagSet + else: + c = self.getComponent() + if isinstance(c, Choice): + return c.getEffectiveTagSet() + else: + return c.getTagSet() + + def getTagMap(self): + if self._tagSet: + return Set.getTagMap(self) + else: + return Set.getComponentTagMap(self) + + def getComponent(self, innerFlag=0): + if self._currentIdx is None: + raise error.PyAsn1Error('Component not chosen') + else: + c = self._componentValues[self._currentIdx] + if innerFlag and isinstance(c, Choice): + return c.getComponent(innerFlag) + else: + return c + + def getName(self, innerFlag=0): + if self._currentIdx is None: + raise error.PyAsn1Error('Component not chosen') + else: + if innerFlag: + c = self._componentValues[self._currentIdx] + if isinstance(c, Choice): + return c.getName(innerFlag) + return self._componentType.getNameByPosition(self._currentIdx) + + def setDefaultComponents(self): pass + +class Any(OctetString): + tagSet = baseTagSet = tag.TagSet() # untagged + typeId = 6 + + def getTagMap(self): + return tagmap.TagMap( + { self.getTagSet(): self }, + { eoo.endOfOctets.getTagSet(): eoo.endOfOctets }, + self + ) + +# XXX +# coercion rules? diff --git a/python/pyasn1/pyasn1/type/useful.py b/python/pyasn1/pyasn1/type/useful.py new file mode 100644 index 000000000..a7139c22c --- /dev/null +++ b/python/pyasn1/pyasn1/type/useful.py @@ -0,0 +1,12 @@ +# ASN.1 "useful" types +from pyasn1.type import char, tag + +class GeneralizedTime(char.VisibleString): + tagSet = char.VisibleString.tagSet.tagImplicitly( + tag.Tag(tag.tagClassUniversal, tag.tagFormatSimple, 24) + ) + +class UTCTime(char.VisibleString): + tagSet = char.VisibleString.tagSet.tagImplicitly( + tag.Tag(tag.tagClassUniversal, tag.tagFormatSimple, 23) + ) diff --git a/python/pyasn1/setup.cfg b/python/pyasn1/setup.cfg new file mode 100644 index 000000000..861a9f554 --- /dev/null +++ b/python/pyasn1/setup.cfg @@ -0,0 +1,5 @@ +[egg_info] +tag_build = +tag_date = 0 +tag_svn_revision = 0 + diff --git a/python/pyasn1/setup.py b/python/pyasn1/setup.py new file mode 100644 index 000000000..194f0c8ca --- /dev/null +++ b/python/pyasn1/setup.py @@ -0,0 +1,115 @@ +#!/usr/bin/env python +"""ASN.1 types and codecs + + A pure-Python implementation of ASN.1 types and DER/BER/CER codecs (X.208). +""" + +import os +import sys + +classifiers = """\ +Development Status :: 5 - Production/Stable +Environment :: Console +Intended Audience :: Developers +Intended Audience :: Education +Intended Audience :: Information Technology +Intended Audience :: Science/Research +Intended Audience :: System Administrators +Intended Audience :: Telecommunications Industry +License :: OSI Approved :: BSD License +Natural Language :: English +Operating System :: OS Independent +Programming Language :: Python :: 2 +Programming Language :: Python :: 3 +Topic :: Communications +Topic :: Security :: Cryptography +Topic :: Software Development :: Libraries :: Python Modules +""" + +def howto_install_distribute(): + print(""" + Error: You need the distribute Python package! + + It's very easy to install it, just type (as root on Linux): + + wget http://python-distribute.org/distribute_setup.py + python distribute_setup.py + + Then you could make eggs from this package. +""") + +def howto_install_setuptools(): + print(""" + Error: You need setuptools Python package! + + It's very easy to install it, just type (as root on Linux): + + wget http://peak.telecommunity.com/dist/ez_setup.py + python ez_setup.py + + Then you could make eggs from this package. +""") + +try: + from setuptools import setup, Command + params = { + 'zip_safe': True + } +except ImportError: + for arg in sys.argv: + if arg.find('egg') != -1: + if sys.version_info[0] > 2: + howto_install_distribute() + else: + howto_install_setuptools() + sys.exit(1) + from distutils.core import setup, Command + params = {} + +doclines = [ x.strip() for x in __doc__.split('\n') if x ] + +params.update( { + 'name': 'pyasn1', + 'version': open(os.path.join('pyasn1','__init__.py')).read().split('\'')[1], + 'description': doclines[0], + 'long_description': ' '.join(doclines[1:]), + 'maintainer': 'Ilya Etingof ', + 'author': 'Ilya Etingof', + 'author_email': 'ilya@glas.net', + 'url': 'http://sourceforge.net/projects/pyasn1/', + 'platforms': ['any'], + 'classifiers': [ x for x in classifiers.split('\n') if x ], + 'license': 'BSD', + 'packages': [ 'pyasn1', + 'pyasn1.type', + 'pyasn1.compat', + 'pyasn1.codec', + 'pyasn1.codec.ber', + 'pyasn1.codec.cer', + 'pyasn1.codec.der' ] +} ) + +# handle unittest discovery feature +if sys.version_info[0:2] < (2, 7) or \ + sys.version_info[0:2] in ( (3, 0), (3, 1) ): + try: + import unittest2 as unittest + except ImportError: + unittest = None +else: + import unittest + +if unittest: + class PyTest(Command): + user_options = [] + + def initialize_options(self): pass + def finalize_options(self): pass + + def run(self): + suite = unittest.defaultTestLoader.discover('.') + unittest.TextTestRunner(verbosity=2).run(suite) + + params['cmdclass'] = { 'test': PyTest } + +setup(**params) diff --git a/python/pyasn1/test/__init__.py b/python/pyasn1/test/__init__.py new file mode 100644 index 000000000..8c3066b2e --- /dev/null +++ b/python/pyasn1/test/__init__.py @@ -0,0 +1 @@ +# This file is necessary to make this directory a package. diff --git a/python/pyasn1/test/codec/__init__.py b/python/pyasn1/test/codec/__init__.py new file mode 100644 index 000000000..8c3066b2e --- /dev/null +++ b/python/pyasn1/test/codec/__init__.py @@ -0,0 +1 @@ +# This file is necessary to make this directory a package. diff --git a/python/pyasn1/test/codec/ber/__init__.py b/python/pyasn1/test/codec/ber/__init__.py new file mode 100644 index 000000000..8c3066b2e --- /dev/null +++ b/python/pyasn1/test/codec/ber/__init__.py @@ -0,0 +1 @@ +# This file is necessary to make this directory a package. diff --git a/python/pyasn1/test/codec/ber/suite.py b/python/pyasn1/test/codec/ber/suite.py new file mode 100644 index 000000000..796c526b4 --- /dev/null +++ b/python/pyasn1/test/codec/ber/suite.py @@ -0,0 +1,22 @@ +from sys import path, version_info +from os.path import sep +path.insert(1, path[0]+sep+'ber') +import test_encoder, test_decoder +from pyasn1.error import PyAsn1Error +if version_info[0:2] < (2, 7) or \ + version_info[0:2] in ( (3, 0), (3, 1) ): + try: + import unittest2 as unittest + except ImportError: + import unittest +else: + import unittest + +suite = unittest.TestSuite() +loader = unittest.TestLoader() +for m in (test_encoder, test_decoder): + suite.addTest(loader.loadTestsFromModule(m)) + +def runTests(): unittest.TextTestRunner(verbosity=2).run(suite) + +if __name__ == '__main__': runTests() diff --git a/python/pyasn1/test/codec/ber/test_decoder.py b/python/pyasn1/test/codec/ber/test_decoder.py new file mode 100644 index 000000000..36999e84d --- /dev/null +++ b/python/pyasn1/test/codec/ber/test_decoder.py @@ -0,0 +1,535 @@ +from pyasn1.type import tag, namedtype, univ +from pyasn1.codec.ber import decoder +from pyasn1.compat.octets import ints2octs, str2octs, null +from pyasn1.error import PyAsn1Error +from sys import version_info +if version_info[0:2] < (2, 7) or \ + version_info[0:2] in ( (3, 0), (3, 1) ): + try: + import unittest2 as unittest + except ImportError: + import unittest +else: + import unittest + +class LargeTagDecoderTestCase(unittest.TestCase): + def testLargeTag(self): + assert decoder.decode(ints2octs((127, 141, 245, 182, 253, 47, 3, 2, 1, 1))) == (1, null) + +class IntegerDecoderTestCase(unittest.TestCase): + def testPosInt(self): + assert decoder.decode(ints2octs((2, 1, 12))) == (12, null) + def testNegInt(self): + assert decoder.decode(ints2octs((2, 1, 244))) == (-12, null) + def testZero(self): + assert decoder.decode(ints2octs((2, 0))) == (0, null) + def testZeroLong(self): + assert decoder.decode(ints2octs((2, 1, 0))) == (0, null) + def testMinusOne(self): + assert decoder.decode(ints2octs((2, 1, 255))) == (-1, null) + def testPosLong(self): + assert decoder.decode( + ints2octs((2, 9, 0, 255, 255, 255, 255, 255, 255, 255, 255)) + ) == (0xffffffffffffffff, null) + def testNegLong(self): + assert decoder.decode( + ints2octs((2, 9, 255, 0, 0, 0, 0, 0, 0, 0, 1)) + ) == (-0xffffffffffffffff, null) + def testSpec(self): + try: + decoder.decode( + ints2octs((2, 1, 12)), asn1Spec=univ.Null() + ) == (12, null) + except PyAsn1Error: + pass + else: + assert 0, 'wrong asn1Spec worked out' + assert decoder.decode( + ints2octs((2, 1, 12)), asn1Spec=univ.Integer() + ) == (12, null) + def testTagFormat(self): + try: + decoder.decode(ints2octs((34, 1, 12))) + except PyAsn1Error: + pass + else: + assert 0, 'wrong tagFormat worked out' + +class BooleanDecoderTestCase(unittest.TestCase): + def testTrue(self): + assert decoder.decode(ints2octs((1, 1, 1))) == (1, null) + def testTrueNeg(self): + assert decoder.decode(ints2octs((1, 1, 255))) == (1, null) + def testExtraTrue(self): + assert decoder.decode(ints2octs((1, 1, 1, 0, 120, 50, 50))) == (1, ints2octs((0, 120, 50, 50))) + def testFalse(self): + assert decoder.decode(ints2octs((1, 1, 0))) == (0, null) + def testTagFormat(self): + try: + decoder.decode(ints2octs((33, 1, 1))) + except PyAsn1Error: + pass + else: + assert 0, 'wrong tagFormat worked out' + +class BitStringDecoderTestCase(unittest.TestCase): + def testDefMode(self): + assert decoder.decode( + ints2octs((3, 3, 1, 169, 138)) + ) == ((1,0,1,0,1,0,0,1,1,0,0,0,1,0,1), null) + def testIndefMode(self): + assert decoder.decode( + ints2octs((3, 3, 1, 169, 138)) + ) == ((1,0,1,0,1,0,0,1,1,0,0,0,1,0,1), null) + def testDefModeChunked(self): + assert decoder.decode( + ints2octs((35, 8, 3, 2, 0, 169, 3, 2, 1, 138)) + ) == ((1,0,1,0,1,0,0,1,1,0,0,0,1,0,1), null) + def testIndefModeChunked(self): + assert decoder.decode( + ints2octs((35, 128, 3, 2, 0, 169, 3, 2, 1, 138, 0, 0)) + ) == ((1,0,1,0,1,0,0,1,1,0,0,0,1,0,1), null) + def testDefModeChunkedSubst(self): + assert decoder.decode( + ints2octs((35, 8, 3, 2, 0, 169, 3, 2, 1, 138)), + substrateFun=lambda a,b,c: (b,c) + ) == (ints2octs((3, 2, 0, 169, 3, 2, 1, 138)), 8) + def testIndefModeChunkedSubst(self): + assert decoder.decode( + ints2octs((35, 128, 3, 2, 0, 169, 3, 2, 1, 138, 0, 0)), + substrateFun=lambda a,b,c: (b,c) + ) == (ints2octs((3, 2, 0, 169, 3, 2, 1, 138, 0, 0)), -1) + +class OctetStringDecoderTestCase(unittest.TestCase): + def testDefMode(self): + assert decoder.decode( + ints2octs((4, 15, 81, 117, 105, 99, 107, 32, 98, 114, 111, 119, 110, 32, 102, 111, 120)) + ) == (str2octs('Quick brown fox'), null) + def testIndefMode(self): + assert decoder.decode( + ints2octs((36, 128, 4, 15, 81, 117, 105, 99, 107, 32, 98, 114, 111, 119, 110, 32, 102, 111, 120, 0, 0)) + ) == (str2octs('Quick brown fox'), null) + def testDefModeChunked(self): + assert decoder.decode( + ints2octs((36, 23, 4, 4, 81, 117, 105, 99, 4, 4, 107, 32, 98, 114, 4, 4, 111, 119, 110, 32, 4, 3, 102, 111, 120)) + ) == (str2octs('Quick brown fox'), null) + def testIndefModeChunked(self): + assert decoder.decode( + ints2octs((36, 128, 4, 4, 81, 117, 105, 99, 4, 4, 107, 32, 98, 114, 4, 4, 111, 119, 110, 32, 4, 3, 102, 111, 120, 0, 0)) + ) == (str2octs('Quick brown fox'), null) + def testDefModeChunkedSubst(self): + assert decoder.decode( + ints2octs((36, 23, 4, 4, 81, 117, 105, 99, 4, 4, 107, 32, 98, 114, 4, 4, 111, 119, 110, 32, 4, 3, 102, 111, 120)), + substrateFun=lambda a,b,c: (b,c) + ) == (ints2octs((4, 4, 81, 117, 105, 99, 4, 4, 107, 32, 98, 114, 4, 4, 111, 119, 110, 32, 4, 3, 102, 111, 120)), 23) + def testIndefModeChunkedSubst(self): + assert decoder.decode( + ints2octs((36, 128, 4, 4, 81, 117, 105, 99, 4, 4, 107, 32, 98, 114, 4, 4, 111, 119, 110, 32, 4, 3, 102, 111, 120, 0, 0)), + substrateFun=lambda a,b,c: (b,c) + ) == (ints2octs((4, 4, 81, 117, 105, 99, 4, 4, 107, 32, 98, 114, 4, 4, 111, 119, 110, 32, 4, 3, 102, 111, 120, 0, 0)), -1) + +class ExpTaggedOctetStringDecoderTestCase(unittest.TestCase): + def setUp(self): + self.o = univ.OctetString( + 'Quick brown fox', + tagSet=univ.OctetString.tagSet.tagExplicitly( + tag.Tag(tag.tagClassApplication, tag.tagFormatSimple, 5) + )) + + def testDefMode(self): + assert self.o.isSameTypeWith(decoder.decode( + ints2octs((101, 17, 4, 15, 81, 117, 105, 99, 107, 32, 98, 114, 111, 119, 110, 32, 102, 111, 120)) + )[0]) + + def testIndefMode(self): + v, s = decoder.decode(ints2octs((101, 128, 36, 128, 4, 15, 81, 117, 105, 99, 107, 32, 98, 114, 111, 119, 110, 32, 102, 111, 120, 0, 0, 0, 0))) + assert self.o.isSameTypeWith(v) + assert not s + + def testDefModeChunked(self): + v, s = decoder.decode(ints2octs((101, 25, 36, 23, 4, 4, 81, 117, 105, 99, 4, 4, 107, 32, 98, 114, 4, 4, 111, 119, 110, 32, 4, 3, 102, 111, 120))) + assert self.o.isSameTypeWith(v) + assert not s + + def testIndefModeChunked(self): + v, s = decoder.decode(ints2octs((101, 128, 36, 128, 4, 4, 81, 117, 105, 99, 4, 4, 107, 32, 98, 114, 4, 4, 111, 119, 110, 32, 4, 3, 102, 111, 120, 0, 0, 0, 0))) + assert self.o.isSameTypeWith(v) + assert not s + + def testDefModeSubst(self): + assert decoder.decode( + ints2octs((101, 17, 4, 15, 81, 117, 105, 99, 107, 32, 98, 114, 111, 119, 110, 32, 102, 111, 120)), + substrateFun=lambda a,b,c: (b,c) + ) == (ints2octs((4, 15, 81, 117, 105, 99, 107, 32, 98, 114, 111, 119, 110, 32, 102, 111, 120)), 17) + + def testIndefModeSubst(self): + assert decoder.decode( + ints2octs((101, 128, 36, 128, 4, 15, 81, 117, 105, 99, 107, 32, 98, 114, 111, 119, 110, 32, 102, 111, 120, 0, 0, 0, 0)), + substrateFun=lambda a,b,c: (b,c) + ) == (ints2octs((36, 128, 4, 15, 81, 117, 105, 99, 107, 32, 98, 114, 111, 119, 110, 32, 102, 111, 120, 0, 0, 0, 0)), -1) + +class NullDecoderTestCase(unittest.TestCase): + def testNull(self): + assert decoder.decode(ints2octs((5, 0))) == (null, null) + def testTagFormat(self): + try: + decoder.decode(ints2octs((37, 0))) + except PyAsn1Error: + pass + else: + assert 0, 'wrong tagFormat worked out' + +class ObjectIdentifierDecoderTestCase(unittest.TestCase): + def testOID(self): + assert decoder.decode( + ints2octs((6, 6, 43, 6, 0, 191, 255, 126)) + ) == ((1,3,6,0,0xffffe), null) + + def testEdges1(self): + assert decoder.decode( + ints2octs((6, 1, 255)) + ) == ((6,15), null) + + def testEdges2(self): + assert decoder.decode( + ints2octs((6, 1, 239)) + ) == ((5,39), null) + + def testEdges3(self): + assert decoder.decode( + ints2octs((6, 7, 43, 6, 143, 255, 255, 255, 127)) + ) == ((1, 3, 6, 4294967295), null) + + def testNonLeading0x80(self): + assert decoder.decode( + ints2octs((6, 5, 85, 4, 129, 128, 0)), + ) == ((2, 5, 4, 16384), null) + + def testLeading0x80(self): + try: + decoder.decode( + ints2octs((6, 5, 85, 4, 128, 129, 0)) + ) + except PyAsn1Error: + pass + else: + assert 1, 'Leading 0x80 tolarated' + + def testTagFormat(self): + try: + decoder.decode(ints2octs((38, 1, 239))) + except PyAsn1Error: + pass + else: + assert 0, 'wrong tagFormat worked out' + +class RealDecoderTestCase(unittest.TestCase): + def testChar(self): + assert decoder.decode( + ints2octs((9, 7, 3, 49, 50, 51, 69, 49, 49)) + ) == (univ.Real((123, 10, 11)), null) + + def testBin1(self): + assert decoder.decode( + ints2octs((9, 4, 128, 245, 4, 77)) + ) == (univ.Real((1101, 2, -11)), null) + + def testBin2(self): + assert decoder.decode( + ints2octs((9, 4, 128, 11, 4, 77)) + ) == (univ.Real((1101, 2, 11)), null) + + def testBin3(self): + assert decoder.decode( + ints2octs((9, 3, 192, 10, 123)) + ) == (univ.Real((-123, 2, 10)), null) + + + def testPlusInf(self): + assert decoder.decode( + ints2octs((9, 1, 64)) + ) == (univ.Real('inf'), null) + + def testMinusInf(self): + assert decoder.decode( + ints2octs((9, 1, 65)) + ) == (univ.Real('-inf'), null) + + def testEmpty(self): + assert decoder.decode( + ints2octs((9, 0)) + ) == (univ.Real(0.0), null) + + def testTagFormat(self): + try: + decoder.decode(ints2octs((41, 0))) + except PyAsn1Error: + pass + else: + assert 0, 'wrong tagFormat worked out' + +class SequenceDecoderTestCase(unittest.TestCase): + def setUp(self): + self.s = univ.Sequence(componentType=namedtype.NamedTypes( + namedtype.NamedType('place-holder', univ.Null(null)), + namedtype.NamedType('first-name', univ.OctetString(null)), + namedtype.NamedType('age', univ.Integer(33)), + )) + self.s.setComponentByPosition(0, univ.Null(null)) + self.s.setComponentByPosition(1, univ.OctetString('quick brown')) + self.s.setComponentByPosition(2, univ.Integer(1)) + self.s.setDefaultComponents() + + def testWithOptionalAndDefaultedDefMode(self): + assert decoder.decode( + ints2octs((48, 18, 5, 0, 4, 11, 113, 117, 105, 99, 107, 32, 98, 114, 111, 119, 110, 2, 1, 1)) + ) == (self.s, null) + + def testWithOptionalAndDefaultedIndefMode(self): + assert decoder.decode( + ints2octs((48, 128, 5, 0, 36, 128, 4, 11, 113, 117, 105, 99, 107, 32, 98, 114, 111, 119, 110, 0, 0, 2, 1, 1, 0, 0)) + ) == (self.s, null) + + def testWithOptionalAndDefaultedDefModeChunked(self): + assert decoder.decode( + ints2octs((48, 24, 5, 0, 36, 17, 4, 4, 113, 117, 105, 99, 4, 4, 107, 32, 98, 114, 4, 3, 111, 119, 110, 2, 1, 1)) + ) == (self.s, null) + + def testWithOptionalAndDefaultedIndefModeChunked(self): + assert decoder.decode( + ints2octs((48, 128, 5, 0, 36, 128, 4, 4, 113, 117, 105, 99, 4, 4, 107, 32, 98, 114, 4, 3, 111, 119, 110, 0, 0, 2, 1, 1, 0, 0)) + ) == (self.s, null) + + def testWithOptionalAndDefaultedDefModeSubst(self): + assert decoder.decode( + ints2octs((48, 18, 5, 0, 4, 11, 113, 117, 105, 99, 107, 32, 98, 114, 111, 119, 110, 2, 1, 1)), + substrateFun=lambda a,b,c: (b,c) + ) == (ints2octs((5, 0, 4, 11, 113, 117, 105, 99, 107, 32, 98, 114, 111, 119, 110, 2, 1, 1)), 18) + + def testWithOptionalAndDefaultedIndefModeSubst(self): + assert decoder.decode( + ints2octs((48, 128, 5, 0, 36, 128, 4, 11, 113, 117, 105, 99, 107, 32, 98, 114, 111, 119, 110, 0, 0, 2, 1, 1, 0, 0)), + substrateFun=lambda a,b,c: (b,c) + ) == (ints2octs((5, 0, 36, 128, 4, 11, 113, 117, 105, 99, 107, 32, 98, 114, 111, 119, 110, 0, 0, 2, 1, 1, 0, 0)), -1) + + def testTagFormat(self): + try: + decoder.decode( + ints2octs((16, 18, 5, 0, 4, 11, 113, 117, 105, 99, 107, 32, 98, 114, 111, 119, 110, 2, 1, 1)) + ) + except PyAsn1Error: + pass + else: + assert 0, 'wrong tagFormat worked out' + +class GuidedSequenceDecoderTestCase(unittest.TestCase): + def setUp(self): + self.s = univ.Sequence(componentType=namedtype.NamedTypes( + namedtype.NamedType('place-holder', univ.Null(null)), + namedtype.OptionalNamedType('first-name', univ.OctetString(null)), + namedtype.DefaultedNamedType('age', univ.Integer(33)), + )) + + def __init(self): + self.s.clear() + self.s.setComponentByPosition(0, univ.Null(null)) + self.s.setDefaultComponents() + + def __initWithOptional(self): + self.s.clear() + self.s.setComponentByPosition(0, univ.Null(null)) + self.s.setComponentByPosition(1, univ.OctetString('quick brown')) + self.s.setDefaultComponents() + + def __initWithDefaulted(self): + self.s.clear() + self.s.setComponentByPosition(0, univ.Null(null)) + self.s.setComponentByPosition(2, univ.Integer(1)) + self.s.setDefaultComponents() + + def __initWithOptionalAndDefaulted(self): + self.s.clear() + self.s.setComponentByPosition(0, univ.Null(null)) + self.s.setComponentByPosition(1, univ.OctetString('quick brown')) + self.s.setComponentByPosition(2, univ.Integer(1)) + self.s.setDefaultComponents() + + def testDefMode(self): + self.__init() + assert decoder.decode( + ints2octs((48, 128, 5, 0, 0, 0)), asn1Spec=self.s + ) == (self.s, null) + + def testIndefMode(self): + self.__init() + assert decoder.decode( + ints2octs((48, 128, 5, 0, 0, 0)), asn1Spec=self.s + ) == (self.s, null) + + def testDefModeChunked(self): + self.__init() + assert decoder.decode( + ints2octs((48, 2, 5, 0)), asn1Spec=self.s + ) == (self.s, null) + + def testIndefModeChunked(self): + self.__init() + assert decoder.decode( + ints2octs((48, 128, 5, 0, 0, 0)), asn1Spec=self.s + ) == (self.s, null) + + def testWithOptionalDefMode(self): + self.__initWithOptional() + assert decoder.decode( + ints2octs((48, 15, 5, 0, 4, 11, 113, 117, 105, 99, 107, 32, 98, 114, 111, 119, 110)), asn1Spec=self.s + ) == (self.s, null) + + def testWithOptionaIndefMode(self): + self.__initWithOptional() + assert decoder.decode( + ints2octs((48, 128, 5, 0, 36, 128, 4, 11, 113, 117, 105, 99, 107, 32, 98, 114, 111, 119, 110, 0, 0, 0, 0)), + asn1Spec=self.s + ) == (self.s, null) + + def testWithOptionalDefModeChunked(self): + self.__initWithOptional() + assert decoder.decode( + ints2octs((48, 21, 5, 0, 36, 17, 4, 4, 113, 117, 105, 99, 4, 4, 107, 32, 98, 114, 4, 3, 111, 119, 110)), + asn1Spec=self.s + ) == (self.s, null) + + def testWithOptionalIndefModeChunked(self): + self.__initWithOptional() + assert decoder.decode( + ints2octs((48, 128, 5, 0, 36, 128, 4, 4, 113, 117, 105, 99, 4, 4, 107, 32, 98, 114, 4, 3, 111, 119, 110, 0, 0, 0, 0)), + asn1Spec=self.s + ) == (self.s, null) + + def testWithDefaultedDefMode(self): + self.__initWithDefaulted() + assert decoder.decode( + ints2octs((48, 5, 5, 0, 2, 1, 1)), asn1Spec=self.s + ) == (self.s, null) + + def testWithDefaultedIndefMode(self): + self.__initWithDefaulted() + assert decoder.decode( + ints2octs((48, 128, 5, 0, 2, 1, 1, 0, 0)), asn1Spec=self.s + ) == (self.s, null) + + def testWithDefaultedDefModeChunked(self): + self.__initWithDefaulted() + assert decoder.decode( + ints2octs((48, 5, 5, 0, 2, 1, 1)), asn1Spec=self.s + ) == (self.s, null) + + def testWithDefaultedIndefModeChunked(self): + self.__initWithDefaulted() + assert decoder.decode( + ints2octs((48, 128, 5, 0, 2, 1, 1, 0, 0)), asn1Spec=self.s + ) == (self.s, null) + + def testWithOptionalAndDefaultedDefMode(self): + self.__initWithOptionalAndDefaulted() + assert decoder.decode( + ints2octs((48, 18, 5, 0, 4, 11, 113, 117, 105, 99, 107, 32, 98, 114, 111, 119, 110, 2, 1, 1)), asn1Spec=self.s + ) == (self.s, null) + + def testWithOptionalAndDefaultedIndefMode(self): + self.__initWithOptionalAndDefaulted() + assert decoder.decode( + ints2octs((48, 128, 5, 0, 36, 128, 4, 11, 113, 117, 105, 99, 107, 32, 98, 114, 111, 119, 110, 0, 0, 2, 1, 1, 0, 0)), asn1Spec=self.s + ) == (self.s, null) + + def testWithOptionalAndDefaultedDefModeChunked(self): + self.__initWithOptionalAndDefaulted() + assert decoder.decode( + ints2octs((48, 24, 5, 0, 36, 17, 4, 4, 113, 117, 105, 99, 4, 4, 107, 32, 98, 114, 4, 3, 111, 119, 110, 2, 1, 1)), asn1Spec=self.s + ) == (self.s, null) + + def testWithOptionalAndDefaultedIndefModeChunked(self): + self.__initWithOptionalAndDefaulted() + assert decoder.decode( + ints2octs((48, 128, 5, 0, 36, 128, 4, 4, 113, 117, 105, 99, 4, 4, 107, 32, 98, 114, 4, 3, 111, 119, 110, 0, 0, 2, 1, 1, 0, 0)), asn1Spec=self.s + ) == (self.s, null) + +class ChoiceDecoderTestCase(unittest.TestCase): + def setUp(self): + self.s = univ.Choice(componentType=namedtype.NamedTypes( + namedtype.NamedType('place-holder', univ.Null(null)), + namedtype.NamedType('number', univ.Integer(0)), + namedtype.NamedType('string', univ.OctetString()) + )) + + def testBySpec(self): + self.s.setComponentByPosition(0, univ.Null(null)) + assert decoder.decode( + ints2octs((5, 0)), asn1Spec=self.s + ) == (self.s, null) + + def testWithoutSpec(self): + self.s.setComponentByPosition(0, univ.Null(null)) + assert decoder.decode(ints2octs((5, 0))) == (self.s, null) + assert decoder.decode(ints2octs((5, 0))) == (univ.Null(null), null) + + def testUndefLength(self): + self.s.setComponentByPosition(2, univ.OctetString('abcdefgh')) + assert decoder.decode(ints2octs((36, 128, 4, 3, 97, 98, 99, 4, 3, 100, 101, 102, 4, 2, 103, 104, 0, 0)), asn1Spec=self.s) == (self.s, null) + + def testExplicitTag(self): + s = self.s.subtype(explicitTag=tag.Tag(tag.tagClassContext, + tag.tagFormatConstructed, 4)) + s.setComponentByPosition(0, univ.Null(null)) + assert decoder.decode(ints2octs((164, 2, 5, 0)), asn1Spec=s) == (s, null) + + def testExplicitTagUndefLength(self): + s = self.s.subtype(explicitTag=tag.Tag(tag.tagClassContext, + tag.tagFormatConstructed, 4)) + s.setComponentByPosition(0, univ.Null(null)) + assert decoder.decode(ints2octs((164, 128, 5, 0, 0, 0)), asn1Spec=s) == (s, null) + +class AnyDecoderTestCase(unittest.TestCase): + def setUp(self): + self.s = univ.Any() + + def testByUntagged(self): + assert decoder.decode( + ints2octs((4, 3, 102, 111, 120)), asn1Spec=self.s + ) == (univ.Any('\004\003fox'), null) + + def testTaggedEx(self): + s = univ.Any('\004\003fox').subtype(explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 4)) + assert decoder.decode(ints2octs((164, 5, 4, 3, 102, 111, 120)), asn1Spec=s) == (s, null) + + def testTaggedIm(self): + s = univ.Any('\004\003fox').subtype(implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 4)) + assert decoder.decode(ints2octs((132, 5, 4, 3, 102, 111, 120)), asn1Spec=s) == (s, null) + + def testByUntaggedIndefMode(self): + assert decoder.decode( + ints2octs((4, 3, 102, 111, 120)), asn1Spec=self.s + ) == (univ.Any('\004\003fox'), null) + + def testTaggedExIndefMode(self): + s = univ.Any('\004\003fox').subtype(explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 4)) + assert decoder.decode(ints2octs((164, 128, 4, 3, 102, 111, 120, 0, 0)), asn1Spec=s) == (s, null) + + def testTaggedImIndefMode(self): + s = univ.Any('\004\003fox').subtype(implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 4)) + assert decoder.decode(ints2octs((164, 128, 4, 3, 102, 111, 120, 0, 0)), asn1Spec=s) == (s, null) + + def testByUntaggedSubst(self): + assert decoder.decode( + ints2octs((4, 3, 102, 111, 120)), + asn1Spec=self.s, + substrateFun=lambda a,b,c: (b,c) + ) == (ints2octs((4, 3, 102, 111, 120)), 5) + + def testTaggedExSubst(self): + assert decoder.decode( + ints2octs((164, 5, 4, 3, 102, 111, 120)), + asn1Spec=self.s, + substrateFun=lambda a,b,c: (b,c) + ) == (ints2octs((164, 5, 4, 3, 102, 111, 120)), 7) + +if __name__ == '__main__': unittest.main() diff --git a/python/pyasn1/test/codec/ber/test_encoder.py b/python/pyasn1/test/codec/ber/test_encoder.py new file mode 100644 index 000000000..bfb3f618c --- /dev/null +++ b/python/pyasn1/test/codec/ber/test_encoder.py @@ -0,0 +1,338 @@ +from pyasn1.type import tag, namedtype, univ +from pyasn1.codec.ber import encoder +from pyasn1.compat.octets import ints2octs +from pyasn1.error import PyAsn1Error +from sys import version_info +if version_info[0:2] < (2, 7) or \ + version_info[0:2] in ( (3, 0), (3, 1) ): + try: + import unittest2 as unittest + except ImportError: + import unittest +else: + import unittest + +class LargeTagEncoderTestCase(unittest.TestCase): + def setUp(self): + self.o = univ.Integer().subtype( + value=1, explicitTag=tag.Tag(tag.tagClassApplication, tag.tagFormatSimple, 0xdeadbeaf) + ) + def testEncoder(self): + assert encoder.encode(self.o) == ints2octs((127, 141, 245, 182, 253, 47, 3, 2, 1, 1)) + +class IntegerEncoderTestCase(unittest.TestCase): + def testPosInt(self): + assert encoder.encode(univ.Integer(12)) == ints2octs((2, 1, 12)) + + def testNegInt(self): + assert encoder.encode(univ.Integer(-12)) == ints2octs((2, 1, 244)) + + def testZero(self): + assert encoder.encode(univ.Integer(0)) == ints2octs((2, 1, 0)) + + def testCompactZero(self): + encoder.IntegerEncoder.supportCompactZero = True + substrate = encoder.encode(univ.Integer(0)) + encoder.IntegerEncoder.supportCompactZero = False + assert substrate == ints2octs((2, 0)) + + def testMinusOne(self): + assert encoder.encode(univ.Integer(-1)) == ints2octs((2, 1, 255)) + + def testPosLong(self): + assert encoder.encode( + univ.Integer(0xffffffffffffffff) + ) == ints2octs((2, 9, 0, 255, 255, 255, 255, 255, 255, 255, 255)) + + def testNegLong(self): + assert encoder.encode( + univ.Integer(-0xffffffffffffffff) + ) == ints2octs((2, 9, 255, 0, 0, 0, 0, 0, 0, 0, 1)) + +class BooleanEncoderTestCase(unittest.TestCase): + def testTrue(self): + assert encoder.encode(univ.Boolean(1)) == ints2octs((1, 1, 1)) + + def testFalse(self): + assert encoder.encode(univ.Boolean(0)) == ints2octs((1, 1, 0)) + +class BitStringEncoderTestCase(unittest.TestCase): + def setUp(self): + self.b = univ.BitString((1,0,1,0,1,0,0,1,1,0,0,0,1,0,1)) + + def testDefMode(self): + assert encoder.encode(self.b) == ints2octs((3, 3, 1, 169, 138)) + + def testIndefMode(self): + assert encoder.encode( + self.b, defMode=0 + ) == ints2octs((3, 3, 1, 169, 138)) + + def testDefModeChunked(self): + assert encoder.encode( + self.b, maxChunkSize=1 + ) == ints2octs((35, 8, 3, 2, 0, 169, 3, 2, 1, 138)) + + def testIndefModeChunked(self): + assert encoder.encode( + self.b, defMode=0, maxChunkSize=1 + ) == ints2octs((35, 128, 3, 2, 0, 169, 3, 2, 1, 138, 0, 0)) + + def testEmptyValue(self): + assert encoder.encode(univ.BitString(())) == ints2octs((3, 1, 0)) + +class OctetStringEncoderTestCase(unittest.TestCase): + def setUp(self): + self.o = univ.OctetString('Quick brown fox') + + def testDefMode(self): + assert encoder.encode(self.o) == ints2octs((4, 15, 81, 117, 105, 99, 107, 32, 98, 114, 111, 119, 110, 32, 102, 111, 120)) + + def testIndefMode(self): + assert encoder.encode( + self.o, defMode=0 + ) == ints2octs((4, 15, 81, 117, 105, 99, 107, 32, 98, 114, 111, 119, 110, 32, 102, 111, 120)) + + def testDefModeChunked(self): + assert encoder.encode( + self.o, maxChunkSize=4 + ) == ints2octs((36, 23, 4, 4, 81, 117, 105, 99, 4, 4, 107, 32, 98, 114, 4, 4, 111, 119, 110, 32, 4, 3, 102, 111, 120)) + + def testIndefModeChunked(self): + assert encoder.encode( + self.o, defMode=0, maxChunkSize=4 + ) == ints2octs((36, 128, 4, 4, 81, 117, 105, 99, 4, 4, 107, 32, 98, 114, 4, 4, 111, 119, 110, 32, 4, 3, 102, 111, 120, 0, 0)) + +class ExpTaggedOctetStringEncoderTestCase(unittest.TestCase): + def setUp(self): + self.o = univ.OctetString().subtype( + value='Quick brown fox', + explicitTag=tag.Tag(tag.tagClassApplication,tag.tagFormatSimple,5) + ) + def testDefMode(self): + assert encoder.encode(self.o) == ints2octs((101, 17, 4, 15, 81, 117, 105, 99, 107, 32, 98, 114, 111, 119, 110, 32, 102, 111, 120)) + + def testIndefMode(self): + assert encoder.encode( + self.o, defMode=0 + ) == ints2octs((101, 128, 4, 15, 81, 117, 105, 99, 107, 32, 98, 114, 111, 119, 110, 32, 102, 111, 120, 0, 0)) + + def testDefModeChunked(self): + assert encoder.encode( + self.o, defMode=1, maxChunkSize=4 + ) == ints2octs((101, 25, 36, 23, 4, 4, 81, 117, 105, 99, 4, 4, 107, 32, 98, 114, 4, 4, 111, 119, 110, 32, 4, 3, 102, 111, 120)) + + def testIndefModeChunked(self): + assert encoder.encode( + self.o, defMode=0, maxChunkSize=4 + ) == ints2octs((101, 128, 36, 128, 4, 4, 81, 117, 105, 99, 4, 4, 107, 32, 98, 114, 4, 4, 111, 119, 110, 32, 4, 3, 102, 111, 120, 0, 0, 0, 0)) + +class NullEncoderTestCase(unittest.TestCase): + def testNull(self): + assert encoder.encode(univ.Null('')) == ints2octs((5, 0)) + +class ObjectIdentifierEncoderTestCase(unittest.TestCase): + def testNull(self): + assert encoder.encode( + univ.ObjectIdentifier((1,3,6,0,0xffffe)) + ) == ints2octs((6, 6, 43, 6, 0, 191, 255, 126)) + +class RealEncoderTestCase(unittest.TestCase): + def testChar(self): + assert encoder.encode( + univ.Real((123, 10, 11)) + ) == ints2octs((9, 7, 3, 49, 50, 51, 69, 49, 49)) + + def testBin1(self): + assert encoder.encode( + univ.Real((1101, 2, 11)) + ) == ints2octs((9, 4, 128, 11, 4, 77)) + + def testBin2(self): + assert encoder.encode( + univ.Real((1101, 2, -11)) + ) == ints2octs((9, 4, 128, 245, 4, 77)) + + def testPlusInf(self): + assert encoder.encode(univ.Real('inf')) == ints2octs((9, 1, 64)) + + def testMinusInf(self): + assert encoder.encode(univ.Real('-inf')) == ints2octs((9, 1, 65)) + + def testZero(self): + assert encoder.encode(univ.Real(0)) == ints2octs((9, 0)) + +class SequenceEncoderTestCase(unittest.TestCase): + def setUp(self): + self.s = univ.Sequence(componentType=namedtype.NamedTypes( + namedtype.NamedType('place-holder', univ.Null('')), + namedtype.OptionalNamedType('first-name', univ.OctetString('')), + namedtype.DefaultedNamedType('age', univ.Integer(33)), + )) + + def __init(self): + self.s.clear() + self.s.setComponentByPosition(0) + + def __initWithOptional(self): + self.s.clear() + self.s.setComponentByPosition(0) + self.s.setComponentByPosition(1, 'quick brown') + + def __initWithDefaulted(self): + self.s.clear() + self.s.setComponentByPosition(0) + self.s.setComponentByPosition(2, 1) + + def __initWithOptionalAndDefaulted(self): + self.s.clear() + self.s.setComponentByPosition(0, univ.Null('')) + self.s.setComponentByPosition(1, univ.OctetString('quick brown')) + self.s.setComponentByPosition(2, univ.Integer(1)) + + def testDefMode(self): + self.__init() + assert encoder.encode(self.s) == ints2octs((48, 2, 5, 0)) + + def testIndefMode(self): + self.__init() + assert encoder.encode( + self.s, defMode=0 + ) == ints2octs((48, 128, 5, 0, 0, 0)) + + def testDefModeChunked(self): + self.__init() + assert encoder.encode( + self.s, defMode=1, maxChunkSize=4 + ) == ints2octs((48, 2, 5, 0)) + + def testIndefModeChunked(self): + self.__init() + assert encoder.encode( + self.s, defMode=0, maxChunkSize=4 + ) == ints2octs((48, 128, 5, 0, 0, 0)) + + def testWithOptionalDefMode(self): + self.__initWithOptional() + assert encoder.encode(self.s) == ints2octs((48, 15, 5, 0, 4, 11, 113, 117, 105, 99, 107, 32, 98, 114, 111, 119, 110)) + + def testWithOptionalIndefMode(self): + self.__initWithOptional() + assert encoder.encode( + self.s, defMode=0 + ) == ints2octs((48, 128, 5, 0, 4, 11, 113, 117, 105, 99, 107, 32, 98, 114, 111, 119, 110, 0, 0)) + + def testWithOptionalDefModeChunked(self): + self.__initWithOptional() + assert encoder.encode( + self.s, defMode=1, maxChunkSize=4 + ) == ints2octs((48, 21, 5, 0, 36, 17, 4, 4, 113, 117, 105, 99, 4, 4, 107, 32, 98, 114, 4, 3, 111, 119, 110)) + + def testWithOptionalIndefModeChunked(self): + self.__initWithOptional() + assert encoder.encode( + self.s, defMode=0, maxChunkSize=4 + ) == ints2octs((48, 128, 5, 0, 36, 128, 4, 4, 113, 117, 105, 99, 4, 4, 107, 32, 98, 114, 4, 3, 111, 119, 110, 0, 0, 0, 0)) + + def testWithDefaultedDefMode(self): + self.__initWithDefaulted() + assert encoder.encode(self.s) == ints2octs((48, 5, 5, 0, 2, 1, 1)) + + def testWithDefaultedIndefMode(self): + self.__initWithDefaulted() + assert encoder.encode( + self.s, defMode=0 + ) == ints2octs((48, 128, 5, 0, 2, 1, 1, 0, 0)) + + def testWithDefaultedDefModeChunked(self): + self.__initWithDefaulted() + assert encoder.encode( + self.s, defMode=1, maxChunkSize=4 + ) == ints2octs((48, 5, 5, 0, 2, 1, 1)) + + def testWithDefaultedIndefModeChunked(self): + self.__initWithDefaulted() + assert encoder.encode( + self.s, defMode=0, maxChunkSize=4 + ) == ints2octs((48, 128, 5, 0, 2, 1, 1, 0, 0)) + + def testWithOptionalAndDefaultedDefMode(self): + self.__initWithOptionalAndDefaulted() + assert encoder.encode(self.s) == ints2octs((48, 18, 5, 0, 4, 11, 113, 117, 105, 99, 107, 32, 98, 114, 111, 119, 110, 2, 1, 1)) + + def testWithOptionalAndDefaultedIndefMode(self): + self.__initWithOptionalAndDefaulted() + assert encoder.encode( + self.s, defMode=0 + ) == ints2octs((48, 128, 5, 0, 4, 11, 113, 117, 105, 99, 107, 32, 98, 114, 111, 119, 110, 2, 1, 1, 0, 0)) + + def testWithOptionalAndDefaultedDefModeChunked(self): + self.__initWithOptionalAndDefaulted() + assert encoder.encode( + self.s, defMode=1, maxChunkSize=4 + ) == ints2octs((48, 24, 5, 0, 36, 17, 4, 4, 113, 117, 105, 99, 4, 4, 107, 32, 98, 114, 4, 3, 111, 119, 110, 2, 1, 1)) + + def testWithOptionalAndDefaultedIndefModeChunked(self): + self.__initWithOptionalAndDefaulted() + assert encoder.encode( + self.s, defMode=0, maxChunkSize=4 + ) == ints2octs((48, 128, 5, 0, 36, 128, 4, 4, 113, 117, 105, 99, 4, 4, 107, 32, 98, 114, 4, 3, 111, 119, 110, 0, 0, 2, 1, 1, 0, 0)) + +class ChoiceEncoderTestCase(unittest.TestCase): + def setUp(self): + self.s = univ.Choice(componentType=namedtype.NamedTypes( + namedtype.NamedType('place-holder', univ.Null('')), + namedtype.NamedType('number', univ.Integer(0)), + namedtype.NamedType('string', univ.OctetString()) + )) + + def testEmpty(self): + try: + encoder.encode(self.s) + except PyAsn1Error: + pass + else: + assert 0, 'encoded unset choice' + + def testFilled(self): + self.s.setComponentByPosition(0, univ.Null('')) + assert encoder.encode(self.s) == ints2octs((5, 0)) + + def testTagged(self): + s = self.s.subtype( + explicitTag=tag.Tag(tag.tagClassContext,tag.tagFormatConstructed,4) + ) + s.setComponentByPosition(0, univ.Null('')) + assert encoder.encode(s) == ints2octs((164, 2, 5, 0)) + + def testUndefLength(self): + self.s.setComponentByPosition(2, univ.OctetString('abcdefgh')) + assert encoder.encode(self.s, defMode=False, maxChunkSize=3) == ints2octs((36, 128, 4, 3, 97, 98, 99, 4, 3, 100, 101, 102, 4, 2, 103, 104, 0, 0)) + + def testTaggedUndefLength(self): + s = self.s.subtype( + explicitTag=tag.Tag(tag.tagClassContext,tag.tagFormatConstructed,4) + ) + s.setComponentByPosition(2, univ.OctetString('abcdefgh')) + assert encoder.encode(s, defMode=False, maxChunkSize=3) == ints2octs((164, 128, 36, 128, 4, 3, 97, 98, 99, 4, 3, 100, 101, 102, 4, 2, 103, 104, 0, 0, 0, 0)) + +class AnyEncoderTestCase(unittest.TestCase): + def setUp(self): + self.s = univ.Any(encoder.encode(univ.OctetString('fox'))) + + def testUntagged(self): + assert encoder.encode(self.s) == ints2octs((4, 3, 102, 111, 120)) + + def testTaggedEx(self): + s = self.s.subtype( + explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 4) + ) + assert encoder.encode(s) == ints2octs((164, 5, 4, 3, 102, 111, 120)) + + def testTaggedIm(self): + s = self.s.subtype( + implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 4) + ) + assert encoder.encode(s) == ints2octs((132, 5, 4, 3, 102, 111, 120)) + +if __name__ == '__main__': unittest.main() diff --git a/python/pyasn1/test/codec/cer/__init__.py b/python/pyasn1/test/codec/cer/__init__.py new file mode 100644 index 000000000..8c3066b2e --- /dev/null +++ b/python/pyasn1/test/codec/cer/__init__.py @@ -0,0 +1 @@ +# This file is necessary to make this directory a package. diff --git a/python/pyasn1/test/codec/cer/suite.py b/python/pyasn1/test/codec/cer/suite.py new file mode 100644 index 000000000..49d682918 --- /dev/null +++ b/python/pyasn1/test/codec/cer/suite.py @@ -0,0 +1,22 @@ +from sys import path, version_info +from os.path import sep +path.insert(1, path[0]+sep+'cer') +import test_encoder, test_decoder +from pyasn1.error import PyAsn1Error +if version_info[0:2] < (2, 7) or \ + version_info[0:2] in ( (3, 0), (3, 1) ): + try: + import unittest2 as unittest + except ImportError: + import unittest +else: + import unittest + +suite = unittest.TestSuite() +loader = unittest.TestLoader() +for m in (test_encoder, test_decoder): + suite.addTest(loader.loadTestsFromModule(m)) + +def runTests(): unittest.TextTestRunner(verbosity=2).run(suite) + +if __name__ == '__main__': runTests() diff --git a/python/pyasn1/test/codec/cer/test_decoder.py b/python/pyasn1/test/codec/cer/test_decoder.py new file mode 100644 index 000000000..7195b72e0 --- /dev/null +++ b/python/pyasn1/test/codec/cer/test_decoder.py @@ -0,0 +1,31 @@ +from pyasn1.type import univ +from pyasn1.codec.cer import decoder +from pyasn1.compat.octets import ints2octs, str2octs, null +from pyasn1.error import PyAsn1Error +from sys import version_info +if version_info[0:2] < (2, 7) or \ + version_info[0:2] in ( (3, 0), (3, 1) ): + try: + import unittest2 as unittest + except ImportError: + import unittest +else: + import unittest + +class BooleanDecoderTestCase(unittest.TestCase): + def testTrue(self): + assert decoder.decode(ints2octs((1, 1, 255))) == (1, null) + def testFalse(self): + assert decoder.decode(ints2octs((1, 1, 0))) == (0, null) + +class OctetStringDecoderTestCase(unittest.TestCase): + def testShortMode(self): + assert decoder.decode( + ints2octs((4, 15, 81, 117, 105, 99, 107, 32, 98, 114, 111, 119, 110, 32, 102, 111, 120)), + ) == (str2octs('Quick brown fox'), null) + def testLongMode(self): + assert decoder.decode( + ints2octs((36, 128, 4, 130, 3, 232) + (81,)*1000 + (4, 1, 81, 0, 0)) + ) == (str2octs('Q'*1001), null) + +if __name__ == '__main__': unittest.main() diff --git a/python/pyasn1/test/codec/cer/test_encoder.py b/python/pyasn1/test/codec/cer/test_encoder.py new file mode 100644 index 000000000..a4f80aa20 --- /dev/null +++ b/python/pyasn1/test/codec/cer/test_encoder.py @@ -0,0 +1,107 @@ +from pyasn1.type import namedtype, univ +from pyasn1.codec.cer import encoder +from pyasn1.compat.octets import ints2octs +from pyasn1.error import PyAsn1Error +from sys import version_info +if version_info[0:2] < (2, 7) or \ + version_info[0:2] in ( (3, 0), (3, 1) ): + try: + import unittest2 as unittest + except ImportError: + import unittest +else: + import unittest + +class BooleanEncoderTestCase(unittest.TestCase): + def testTrue(self): + assert encoder.encode(univ.Boolean(1)) == ints2octs((1, 1, 255)) + def testFalse(self): + assert encoder.encode(univ.Boolean(0)) == ints2octs((1, 1, 0)) + +class BitStringEncoderTestCase(unittest.TestCase): + def testShortMode(self): + assert encoder.encode( + univ.BitString((1,0)*501) + ) == ints2octs((3, 127, 6) + (170,) * 125 + (128,)) + + def testLongMode(self): + assert encoder.encode( + univ.BitString((1,0)*501) + ) == ints2octs((3, 127, 6) + (170,) * 125 + (128,)) + +class OctetStringEncoderTestCase(unittest.TestCase): + def testShortMode(self): + assert encoder.encode( + univ.OctetString('Quick brown fox') + ) == ints2octs((4, 15, 81, 117, 105, 99, 107, 32, 98, 114, 111, 119, 110, 32, 102, 111, 120)) + def testLongMode(self): + assert encoder.encode( + univ.OctetString('Q'*1001) + ) == ints2octs((36, 128, 4, 130, 3, 232) + (81,)*1000 + (4, 1, 81, 0, 0)) + +class SetEncoderTestCase(unittest.TestCase): + def setUp(self): + self.s = univ.Set(componentType=namedtype.NamedTypes( + namedtype.NamedType('place-holder', univ.Null('')), + namedtype.OptionalNamedType('first-name', univ.OctetString('')), + namedtype.DefaultedNamedType('age', univ.Integer(33)) + )) + + def __init(self): + self.s.clear() + self.s.setComponentByPosition(0) + def __initWithOptional(self): + self.s.clear() + self.s.setComponentByPosition(0) + self.s.setComponentByPosition(1, 'quick brown') + + def __initWithDefaulted(self): + self.s.clear() + self.s.setComponentByPosition(0) + self.s.setComponentByPosition(2, 1) + + def __initWithOptionalAndDefaulted(self): + self.s.clear() + self.s.setComponentByPosition(0, univ.Null('')) + self.s.setComponentByPosition(1, univ.OctetString('quick brown')) + self.s.setComponentByPosition(2, univ.Integer(1)) + + def testIndefMode(self): + self.__init() + assert encoder.encode(self.s) == ints2octs((49, 128, 5, 0, 0, 0)) + + def testWithOptionalIndefMode(self): + self.__initWithOptional() + assert encoder.encode( + self.s + ) == ints2octs((49, 128, 4, 11, 113, 117, 105, 99, 107, 32, 98, 114, 111, 119, 110, 5, 0, 0, 0)) + + def testWithDefaultedIndefMode(self): + self.__initWithDefaulted() + assert encoder.encode( + self.s + ) == ints2octs((49, 128, 2, 1, 1, 5, 0, 0, 0)) + + def testWithOptionalAndDefaultedIndefMode(self): + self.__initWithOptionalAndDefaulted() + assert encoder.encode( + self.s + ) == ints2octs((49, 128, 2, 1, 1, 4, 11, 113, 117, 105, 99, 107, 32, 98, 114, 111, 119, 110, 5, 0, 0, 0)) + +class SetWithChoiceEncoderTestCase(unittest.TestCase): + def setUp(self): + c = univ.Choice(componentType=namedtype.NamedTypes( + namedtype.NamedType('actual', univ.Boolean(0)) + )) + self.s = univ.Set(componentType=namedtype.NamedTypes( + namedtype.NamedType('place-holder', univ.Null('')), + namedtype.NamedType('status', c) + )) + + def testIndefMode(self): + self.s.setComponentByPosition(0) + self.s.setComponentByName('status') + self.s.getComponentByName('status').setComponentByPosition(0, 1) + assert encoder.encode(self.s) == ints2octs((49, 128, 1, 1, 255, 5, 0, 0, 0)) + +if __name__ == '__main__': unittest.main() diff --git a/python/pyasn1/test/codec/der/__init__.py b/python/pyasn1/test/codec/der/__init__.py new file mode 100644 index 000000000..8c3066b2e --- /dev/null +++ b/python/pyasn1/test/codec/der/__init__.py @@ -0,0 +1 @@ +# This file is necessary to make this directory a package. diff --git a/python/pyasn1/test/codec/der/suite.py b/python/pyasn1/test/codec/der/suite.py new file mode 100644 index 000000000..7af83bf94 --- /dev/null +++ b/python/pyasn1/test/codec/der/suite.py @@ -0,0 +1,22 @@ +from sys import path, version_info +from os.path import sep +path.insert(1, path[0]+sep+'der') +import test_encoder, test_decoder +from pyasn1.error import PyAsn1Error +if version_info[0:2] < (2, 7) or \ + version_info[0:2] in ( (3, 0), (3, 1) ): + try: + import unittest2 as unittest + except ImportError: + import unittest +else: + import unittest + +suite = unittest.TestSuite() +loader = unittest.TestLoader() +for m in (test_encoder, test_decoder): + suite.addTest(loader.loadTestsFromModule(m)) + +def runTests(): unittest.TextTestRunner(verbosity=2).run(suite) + +if __name__ == '__main__': runTests() diff --git a/python/pyasn1/test/codec/der/test_decoder.py b/python/pyasn1/test/codec/der/test_decoder.py new file mode 100644 index 000000000..5c9a1948b --- /dev/null +++ b/python/pyasn1/test/codec/der/test_decoder.py @@ -0,0 +1,20 @@ +from pyasn1.type import univ +from pyasn1.codec.der import decoder +from pyasn1.error import PyAsn1Error +from sys import version_info +if version_info[0:2] < (2, 7) or \ + version_info[0:2] in ( (3, 0), (3, 1) ): + try: + import unittest2 as unittest + except ImportError: + import unittest +else: + import unittest + +class OctetStringDecoderTestCase(unittest.TestCase): + def testShortMode(self): + assert decoder.decode( + '\004\017Quick brown fox'.encode() + ) == ('Quick brown fox'.encode(), ''.encode()) + +if __name__ == '__main__': unittest.main() diff --git a/python/pyasn1/test/codec/der/test_encoder.py b/python/pyasn1/test/codec/der/test_encoder.py new file mode 100644 index 000000000..787da7bec --- /dev/null +++ b/python/pyasn1/test/codec/der/test_encoder.py @@ -0,0 +1,44 @@ +from pyasn1.type import namedtype, univ +from pyasn1.codec.der import encoder +from pyasn1.compat.octets import ints2octs +from pyasn1.error import PyAsn1Error +from sys import version_info +if version_info[0:2] < (2, 7) or \ + version_info[0:2] in ( (3, 0), (3, 1) ): + try: + import unittest2 as unittest + except ImportError: + import unittest +else: + import unittest + +class OctetStringEncoderTestCase(unittest.TestCase): + def testShortMode(self): + assert encoder.encode( + univ.OctetString('Quick brown fox') + ) == ints2octs((4, 15, 81, 117, 105, 99, 107, 32, 98, 114, 111, 119, 110, 32, 102, 111, 120)) + +class BitStringEncoderTestCase(unittest.TestCase): + def testShortMode(self): + assert encoder.encode( + univ.BitString((1,)) + ) == ints2octs((3, 2, 7, 128)) + +class SetWithChoiceEncoderTestCase(unittest.TestCase): + def setUp(self): + c = univ.Choice(componentType=namedtype.NamedTypes( + namedtype.NamedType('name', univ.OctetString('')), + namedtype.NamedType('amount', univ.Integer(0)) + )) + self.s = univ.Set(componentType=namedtype.NamedTypes( + namedtype.NamedType('place-holder', univ.Null('')), + namedtype.NamedType('status', c) + )) + + def testDefMode(self): + self.s.setComponentByPosition(0) + self.s.setComponentByName('status') + self.s.getComponentByName('status').setComponentByPosition(0, 'ann') + assert encoder.encode(self.s) == ints2octs((49, 7, 4, 3, 97, 110, 110, 5, 0)) + +if __name__ == '__main__': unittest.main() diff --git a/python/pyasn1/test/codec/suite.py b/python/pyasn1/test/codec/suite.py new file mode 100644 index 000000000..93ff06381 --- /dev/null +++ b/python/pyasn1/test/codec/suite.py @@ -0,0 +1,29 @@ +from sys import path, version_info +from os.path import sep +path.insert(1, path[0]+sep+'codec'+sep+'ber') +import ber.suite +path.insert(1, path[0]+sep+'codec'+sep+'cer') +import cer.suite +path.insert(1, path[0]+sep+'codec'+sep+'der') +import der.suite +from pyasn1.error import PyAsn1Error +if version_info[0:2] < (2, 7) or \ + version_info[0:2] in ( (3, 0), (3, 1) ): + try: + import unittest2 as unittest + except ImportError: + import unittest +else: + import unittest + +suite = unittest.TestSuite() +for m in ( + ber.suite, + cer.suite, + der.suite + ): + suite.addTest(getattr(m, 'suite')) + +def runTests(): unittest.TextTestRunner(verbosity=2).run(suite) + +if __name__ == '__main__': runTests() diff --git a/python/pyasn1/test/suite.py b/python/pyasn1/test/suite.py new file mode 100644 index 000000000..b4d80e864 --- /dev/null +++ b/python/pyasn1/test/suite.py @@ -0,0 +1,26 @@ +from sys import path, version_info +from os.path import sep +path.insert(1, path[0]+sep+'type') +import type.suite +path.insert(1, path[0]+sep+'codec') +import codec.suite +from pyasn1.error import PyAsn1Error +if version_info[0:2] < (2, 7) or \ + version_info[0:2] in ( (3, 0), (3, 1) ): + try: + import unittest2 as unittest + except ImportError: + import unittest +else: + import unittest + +suite = unittest.TestSuite() +for m in ( + type.suite, + codec.suite + ): + suite.addTest(getattr(m, 'suite')) + +def runTests(): unittest.TextTestRunner(verbosity=2).run(suite) + +if __name__ == '__main__': runTests() diff --git a/python/pyasn1/test/type/__init__.py b/python/pyasn1/test/type/__init__.py new file mode 100644 index 000000000..8c3066b2e --- /dev/null +++ b/python/pyasn1/test/type/__init__.py @@ -0,0 +1 @@ +# This file is necessary to make this directory a package. diff --git a/python/pyasn1/test/type/suite.py b/python/pyasn1/test/type/suite.py new file mode 100644 index 000000000..bc4b48685 --- /dev/null +++ b/python/pyasn1/test/type/suite.py @@ -0,0 +1,20 @@ +import test_tag, test_constraint, test_namedtype, test_univ +from pyasn1.error import PyAsn1Error +from sys import version_info +if version_info[0:2] < (2, 7) or \ + version_info[0:2] in ( (3, 0), (3, 1) ): + try: + import unittest2 as unittest + except ImportError: + import unittest +else: + import unittest + +suite = unittest.TestSuite() +loader = unittest.TestLoader() +for m in (test_tag, test_constraint, test_namedtype, test_univ): + suite.addTest(loader.loadTestsFromModule(m)) + +def runTests(): unittest.TextTestRunner(verbosity=2).run(suite) + +if __name__ == '__main__': runTests() diff --git a/python/pyasn1/test/type/test_constraint.py b/python/pyasn1/test/type/test_constraint.py new file mode 100644 index 000000000..3457c0fc3 --- /dev/null +++ b/python/pyasn1/test/type/test_constraint.py @@ -0,0 +1,280 @@ +from pyasn1.type import constraint, error +from pyasn1.error import PyAsn1Error +from sys import version_info +if version_info[0:2] < (2, 7) or \ + version_info[0:2] in ( (3, 0), (3, 1) ): + try: + import unittest2 as unittest + except ImportError: + import unittest +else: + import unittest + +class SingleValueConstraintTestCase(unittest.TestCase): + def setUp(self): + self.c1 = constraint.SingleValueConstraint(1,2) + self.c2 = constraint.SingleValueConstraint(3,4) + + def testCmp(self): assert self.c1 == self.c1, 'comparation fails' + def testHash(self): assert hash(self.c1) != hash(self.c2), 'hash() fails' + def testGoodVal(self): + try: + self.c1(1) + except error.ValueConstraintError: + assert 0, 'constraint check fails' + def testBadVal(self): + try: + self.c1(4) + except error.ValueConstraintError: + pass + else: + assert 0, 'constraint check fails' + +class ContainedSubtypeConstraintTestCase(unittest.TestCase): + def setUp(self): + self.c1 = constraint.ContainedSubtypeConstraint( + constraint.SingleValueConstraint(12) + ) + + def testGoodVal(self): + try: + self.c1(12) + except error.ValueConstraintError: + assert 0, 'constraint check fails' + def testBadVal(self): + try: + self.c1(4) + except error.ValueConstraintError: + pass + else: + assert 0, 'constraint check fails' + +class ValueRangeConstraintTestCase(unittest.TestCase): + def setUp(self): + self.c1 = constraint.ValueRangeConstraint(1,4) + + def testGoodVal(self): + try: + self.c1(1) + except error.ValueConstraintError: + assert 0, 'constraint check fails' + def testBadVal(self): + try: + self.c1(-5) + except error.ValueConstraintError: + pass + else: + assert 0, 'constraint check fails' + +class ValueSizeConstraintTestCase(unittest.TestCase): + def setUp(self): + self.c1 = constraint.ValueSizeConstraint(1,2) + + def testGoodVal(self): + try: + self.c1('a') + except error.ValueConstraintError: + assert 0, 'constraint check fails' + def testBadVal(self): + try: + self.c1('abc') + except error.ValueConstraintError: + pass + else: + assert 0, 'constraint check fails' + +class PermittedAlphabetConstraintTestCase(SingleValueConstraintTestCase): + def setUp(self): + self.c1 = constraint.PermittedAlphabetConstraint('A', 'B', 'C') + self.c2 = constraint.PermittedAlphabetConstraint('DEF') + + def testGoodVal(self): + try: + self.c1('A') + except error.ValueConstraintError: + assert 0, 'constraint check fails' + def testBadVal(self): + try: + self.c1('E') + except error.ValueConstraintError: + pass + else: + assert 0, 'constraint check fails' + +class ConstraintsIntersectionTestCase(unittest.TestCase): + def setUp(self): + self.c1 = constraint.ConstraintsIntersection( + constraint.SingleValueConstraint(4), + constraint.ValueRangeConstraint(2, 4) + ) + + def testCmp1(self): + assert constraint.SingleValueConstraint(4) in self.c1, '__cmp__() fails' + + def testCmp2(self): + assert constraint.SingleValueConstraint(5) not in self.c1, \ + '__cmp__() fails' + + def testCmp3(self): + c = constraint.ConstraintsUnion(constraint.ConstraintsIntersection( + constraint.SingleValueConstraint(4), + constraint.ValueRangeConstraint(2, 4) + )) + assert self.c1 in c, '__cmp__() fails' + def testCmp4(self): + c = constraint.ConstraintsUnion( + constraint.ConstraintsIntersection(constraint.SingleValueConstraint(5)) + ) + assert self.c1 not in c, '__cmp__() fails' + + def testGoodVal(self): + try: + self.c1(4) + except error.ValueConstraintError: + assert 0, 'constraint check fails' + def testBadVal(self): + try: + self.c1(-5) + except error.ValueConstraintError: + pass + else: + assert 0, 'constraint check fails' + +class InnerTypeConstraintTestCase(unittest.TestCase): + def testConst1(self): + c = constraint.InnerTypeConstraint( + constraint.SingleValueConstraint(4) + ) + try: + c(4, 32) + except error.ValueConstraintError: + assert 0, 'constraint check fails' + try: + c(5, 32) + except error.ValueConstraintError: + pass + else: + assert 0, 'constraint check fails' + def testConst2(self): + c = constraint.InnerTypeConstraint( + (0, constraint.SingleValueConstraint(4), 'PRESENT'), + (1, constraint.SingleValueConstraint(4), 'ABSENT') + ) + try: + c(4, 0) + except error.ValueConstraintError: + raise + assert 0, 'constraint check fails' + try: + c(4, 1) + except error.ValueConstraintError: + pass + else: + assert 0, 'constraint check fails' + try: + c(3, 0) + except error.ValueConstraintError: + pass + else: + assert 0, 'constraint check fails' + +# Constraints compositions + +class ConstraintsIntersectionTestCase(unittest.TestCase): + def setUp(self): + self.c1 = constraint.ConstraintsIntersection( + constraint.ValueRangeConstraint(1, 9), + constraint.ValueRangeConstraint(2, 5) + ) + + def testGoodVal(self): + try: + self.c1(3) + except error.ValueConstraintError: + assert 0, 'constraint check fails' + def testBadVal(self): + try: + self.c1(0) + except error.ValueConstraintError: + pass + else: + assert 0, 'constraint check fails' + +class ConstraintsUnionTestCase(unittest.TestCase): + def setUp(self): + self.c1 = constraint.ConstraintsUnion( + constraint.SingleValueConstraint(5), + constraint.ValueRangeConstraint(1, 3) + ) + + def testGoodVal(self): + try: + self.c1(2) + self.c1(5) + except error.ValueConstraintError: + assert 0, 'constraint check fails' + def testBadVal(self): + try: + self.c1(-5) + except error.ValueConstraintError: + pass + else: + assert 0, 'constraint check fails' + +class ConstraintsExclusionTestCase(unittest.TestCase): + def setUp(self): + self.c1 = constraint.ConstraintsExclusion( + constraint.ValueRangeConstraint(2, 4) + ) + + def testGoodVal(self): + try: + self.c1(6) + except error.ValueConstraintError: + assert 0, 'constraint check fails' + def testBadVal(self): + try: + self.c1(2) + except error.ValueConstraintError: + pass + else: + assert 0, 'constraint check fails' + +# Constraints derivations + +class DirectDerivationTestCase(unittest.TestCase): + def setUp(self): + self.c1 = constraint.SingleValueConstraint(5) + self.c2 = constraint.ConstraintsUnion( + self.c1, constraint.ValueRangeConstraint(1, 3) + ) + + def testGoodVal(self): + assert self.c1.isSuperTypeOf(self.c2), 'isSuperTypeOf failed' + assert not self.c1.isSubTypeOf(self.c2) , 'isSubTypeOf failed' + def testBadVal(self): + assert not self.c2.isSuperTypeOf(self.c1) , 'isSuperTypeOf failed' + assert self.c2.isSubTypeOf(self.c1) , 'isSubTypeOf failed' + +class IndirectDerivationTestCase(unittest.TestCase): + def setUp(self): + self.c1 = constraint.ConstraintsIntersection( + constraint.ValueRangeConstraint(1, 30) + ) + self.c2 = constraint.ConstraintsIntersection( + self.c1, constraint.ValueRangeConstraint(1, 20) + ) + self.c2 = constraint.ConstraintsIntersection( + self.c2, constraint.ValueRangeConstraint(1, 10) + ) + + def testGoodVal(self): + assert self.c1.isSuperTypeOf(self.c2), 'isSuperTypeOf failed' + assert not self.c1.isSubTypeOf(self.c2) , 'isSubTypeOf failed' + def testBadVal(self): + assert not self.c2.isSuperTypeOf(self.c1) , 'isSuperTypeOf failed' + assert self.c2.isSubTypeOf(self.c1) , 'isSubTypeOf failed' + +if __name__ == '__main__': unittest.main() + +# how to apply size constriants to constructed types? diff --git a/python/pyasn1/test/type/test_namedtype.py b/python/pyasn1/test/type/test_namedtype.py new file mode 100644 index 000000000..3a4f30599 --- /dev/null +++ b/python/pyasn1/test/type/test_namedtype.py @@ -0,0 +1,87 @@ +from pyasn1.type import namedtype, univ +from pyasn1.error import PyAsn1Error +from sys import version_info +if version_info[0:2] < (2, 7) or \ + version_info[0:2] in ( (3, 0), (3, 1) ): + try: + import unittest2 as unittest + except ImportError: + import unittest +else: + import unittest + +class NamedTypeCaseBase(unittest.TestCase): + def setUp(self): + self.e = namedtype.NamedType('age', univ.Integer()) + def testIter(self): + n, t = self.e + assert n == 'age' or t == univ.Integer(), 'unpack fails' + +class NamedTypesCaseBase(unittest.TestCase): + def setUp(self): + self.e = namedtype.NamedTypes( + namedtype.NamedType('first-name', univ.OctetString('')), + namedtype.OptionalNamedType('age', univ.Integer(0)), + namedtype.NamedType('family-name', univ.OctetString('')) + ) + def testIter(self): + for t in self.e: + break + else: + assert 0, '__getitem__() fails' + + def testGetTypeByPosition(self): + assert self.e.getTypeByPosition(0) == univ.OctetString(''), \ + 'getTypeByPosition() fails' + + def testGetNameByPosition(self): + assert self.e.getNameByPosition(0) == 'first-name', \ + 'getNameByPosition() fails' + + def testGetPositionByName(self): + assert self.e.getPositionByName('first-name') == 0, \ + 'getPositionByName() fails' + + def testGetTypesNearPosition(self): + assert self.e.getTagMapNearPosition(0).getPosMap() == { + univ.OctetString.tagSet: univ.OctetString('') + } + assert self.e.getTagMapNearPosition(1).getPosMap() == { + univ.Integer.tagSet: univ.Integer(0), + univ.OctetString.tagSet: univ.OctetString('') + } + assert self.e.getTagMapNearPosition(2).getPosMap() == { + univ.OctetString.tagSet: univ.OctetString('') + } + + def testGetTagMap(self): + assert self.e.getTagMap().getPosMap() == { + univ.OctetString.tagSet: univ.OctetString(''), + univ.Integer.tagSet: univ.Integer(0) + } + + def testGetTagMapWithDups(self): + try: + self.e.getTagMap(1) + except PyAsn1Error: + pass + else: + assert 0, 'Duped types not noticed' + + def testGetPositionNearType(self): + assert self.e.getPositionNearType(univ.OctetString.tagSet, 0) == 0 + assert self.e.getPositionNearType(univ.Integer.tagSet, 1) == 1 + assert self.e.getPositionNearType(univ.OctetString.tagSet, 2) == 2 + +class OrderedNamedTypesCaseBase(unittest.TestCase): + def setUp(self): + self.e = namedtype.NamedTypes( + namedtype.NamedType('first-name', univ.OctetString('')), + namedtype.NamedType('age', univ.Integer(0)) + ) + + def testGetTypeByPosition(self): + assert self.e.getTypeByPosition(0) == univ.OctetString(''), \ + 'getTypeByPosition() fails' + +if __name__ == '__main__': unittest.main() diff --git a/python/pyasn1/test/type/test_tag.py b/python/pyasn1/test/type/test_tag.py new file mode 100644 index 000000000..78146dca2 --- /dev/null +++ b/python/pyasn1/test/type/test_tag.py @@ -0,0 +1,107 @@ +from pyasn1.type import tag +from pyasn1.error import PyAsn1Error +from sys import version_info +if version_info[0:2] < (2, 7) or \ + version_info[0:2] in ( (3, 0), (3, 1) ): + try: + import unittest2 as unittest + except ImportError: + import unittest +else: + import unittest + +class TagTestCaseBase(unittest.TestCase): + def setUp(self): + self.t1 = tag.Tag(tag.tagClassUniversal, tag.tagFormatSimple, 3) + self.t2 = tag.Tag(tag.tagClassUniversal, tag.tagFormatSimple, 3) + +class TagCmpTestCase(TagTestCaseBase): + def testCmp(self): + assert self.t1 == self.t2, 'tag comparation fails' + + def testHash(self): + assert hash(self.t1) == hash(self.t2), 'tag hash comparation fails' + + def testSequence(self): + assert self.t1[0] == self.t2[0] and \ + self.t1[1] == self.t2[1] and \ + self.t1[2] == self.t2[2], 'tag sequence protocol fails' + +class TagSetTestCaseBase(unittest.TestCase): + def setUp(self): + self.ts1 = tag.initTagSet( + tag.Tag(tag.tagClassUniversal, tag.tagFormatSimple, 12) + ) + self.ts2 = tag.initTagSet( + tag.Tag(tag.tagClassUniversal, tag.tagFormatSimple, 12) + ) + +class TagSetCmpTestCase(TagSetTestCaseBase): + def testCmp(self): + assert self.ts1 == self.ts2, 'tag set comparation fails' + + def testHash(self): + assert hash(self.ts1) == hash(self.ts2), 'tag set hash comp. fails' + + def testLen(self): + assert len(self.ts1) == len(self.ts2), 'tag length comparation fails' + +class TaggingTestSuite(TagSetTestCaseBase): + def testImplicitTag(self): + t = self.ts1.tagImplicitly( + tag.Tag(tag.tagClassApplication, tag.tagFormatSimple, 14) + ) + assert t == tag.TagSet( + tag.Tag(tag.tagClassApplication, tag.tagFormatSimple, 12), + tag.Tag(tag.tagClassApplication, tag.tagFormatSimple, 14) + ), 'implicit tagging went wrong' + + def testExplicitTag(self): + t = self.ts1.tagExplicitly( + tag.Tag(tag.tagClassPrivate, tag.tagFormatSimple, 32) + ) + assert t == tag.TagSet( + tag.Tag(tag.tagClassUniversal, tag.tagFormatSimple, 12), + tag.Tag(tag.tagClassUniversal, tag.tagFormatSimple, 12), + tag.Tag(tag.tagClassPrivate, tag.tagFormatConstructed, 32) + ), 'explicit tagging went wrong' + +class TagSetAddTestSuite(TagSetTestCaseBase): + def testAdd(self): + t = self.ts1 + tag.Tag(tag.tagClassApplication, tag.tagFormatSimple, 2) + assert t == tag.TagSet( + tag.Tag(tag.tagClassUniversal, tag.tagFormatSimple, 12), + tag.Tag(tag.tagClassUniversal, tag.tagFormatSimple, 12), + tag.Tag(tag.tagClassApplication, tag.tagFormatSimple, 2) + ), 'TagSet.__add__() fails' + + def testRadd(self): + t = tag.Tag(tag.tagClassApplication, tag.tagFormatSimple, 2) + self.ts1 + assert t == tag.TagSet( + tag.Tag(tag.tagClassUniversal, tag.tagFormatSimple, 12), + tag.Tag(tag.tagClassApplication, tag.tagFormatSimple, 2), + tag.Tag(tag.tagClassUniversal, tag.tagFormatSimple, 12) + ), 'TagSet.__radd__() fails' + +class SuperTagSetTestCase(TagSetTestCaseBase): + def testSuperTagCheck1(self): + assert self.ts1.isSuperTagSetOf( + tag.TagSet( + tag.Tag(tag.tagClassUniversal, tag.tagFormatSimple, 12), + tag.Tag(tag.tagClassUniversal, tag.tagFormatSimple, 12) + )), 'isSuperTagSetOf() fails' + + def testSuperTagCheck2(self): + assert not self.ts1.isSuperTagSetOf( + tag.TagSet( + tag.Tag(tag.tagClassUniversal, tag.tagFormatSimple, 12), + tag.Tag(tag.tagClassUniversal, tag.tagFormatSimple, 13) + )), 'isSuperTagSetOf() fails' + + def testSuperTagCheck3(self): + assert self.ts1.isSuperTagSetOf( + tag.TagSet((), tag.Tag(tag.tagClassUniversal, + tag.tagFormatSimple, 12)) + ), 'isSuperTagSetOf() fails' + +if __name__ == '__main__': unittest.main() diff --git a/python/pyasn1/test/type/test_univ.py b/python/pyasn1/test/type/test_univ.py new file mode 100644 index 000000000..3eedcf26a --- /dev/null +++ b/python/pyasn1/test/type/test_univ.py @@ -0,0 +1,479 @@ +from pyasn1.type import univ, tag, constraint, namedtype, namedval, error +from pyasn1.compat.octets import str2octs, ints2octs +from pyasn1.error import PyAsn1Error +from sys import version_info +if version_info[0:2] < (2, 7) or \ + version_info[0:2] in ( (3, 0), (3, 1) ): + try: + import unittest2 as unittest + except ImportError: + import unittest +else: + import unittest + +class IntegerTestCase(unittest.TestCase): + def testStr(self): assert str(univ.Integer(1)) in ('1','1L'),'str() fails' + def testAnd(self): assert univ.Integer(1) & 0 == 0, '__and__() fails' + def testOr(self): assert univ.Integer(1) | 0 == 1, '__or__() fails' + def testXor(self): assert univ.Integer(1) ^ 0 == 1, '__xor__() fails' + def testRand(self): assert 0 & univ.Integer(1) == 0, '__rand__() fails' + def testRor(self): assert 0 | univ.Integer(1) == 1, '__ror__() fails' + def testRxor(self): assert 0 ^ univ.Integer(1) == 1, '__rxor__() fails' + def testAdd(self): assert univ.Integer(-4) + 6 == 2, '__add__() fails' + def testRadd(self): assert 4 + univ.Integer(5) == 9, '__radd__() fails' + def testSub(self): assert univ.Integer(3) - 6 == -3, '__sub__() fails' + def testRsub(self): assert 6 - univ.Integer(3) == 3, '__rsub__() fails' + def testMul(self): assert univ.Integer(3) * -3 == -9, '__mul__() fails' + def testRmul(self): assert 2 * univ.Integer(3) == 6, '__rmul__() fails' + def testDiv(self): assert univ.Integer(3) / 2 == 1, '__div__() fails' + def testRdiv(self): assert 6 / univ.Integer(3) == 2, '__rdiv__() fails' + def testMod(self): assert univ.Integer(3) % 2 == 1, '__mod__() fails' + def testRmod(self): assert 4 % univ.Integer(3) == 1, '__rmod__() fails' + def testPow(self): assert univ.Integer(3) ** 2 == 9, '__pow__() fails' + def testRpow(self): assert 2 ** univ.Integer(2) == 4, '__rpow__() fails' + def testLshift(self): assert univ.Integer(1) << 1 == 2, '<< fails' + def testRshift(self): assert univ.Integer(2) >> 1 == 1, '>> fails' + def testInt(self): assert int(univ.Integer(3)) == 3, '__int__() fails' + def testLong(self): assert int(univ.Integer(8)) == 8, '__long__() fails' + def testFloat(self): assert float(univ.Integer(4))==4.0,'__float__() fails' + def testPrettyIn(self): assert univ.Integer('3') == 3, 'prettyIn() fails' + def testTag(self): + assert univ.Integer().getTagSet() == tag.TagSet( + (), + tag.Tag(tag.tagClassUniversal, tag.tagFormatSimple, 0x02) + ) + def testNamedVals(self): + i = univ.Integer( + 'asn1', namedValues=univ.Integer.namedValues.clone(('asn1', 1)) + ) + assert i == 1, 'named val fails' + assert str(i) != 'asn1', 'named val __str__() fails' + +class BooleanTestCase(unittest.TestCase): + def testTruth(self): + assert univ.Boolean(True) and univ.Boolean(1), 'Truth initializer fails' + def testFalse(self): + assert not univ.Boolean(False) and not univ.Boolean(0), 'False initializer fails' + def testStr(self): + assert str(univ.Boolean(1)) in ('1', '1L'), 'str() fails' + def testTag(self): + assert univ.Boolean().getTagSet() == tag.TagSet( + (), + tag.Tag(tag.tagClassUniversal, tag.tagFormatSimple, 0x01) + ) + def testConstraints(self): + try: + univ.Boolean(2) + except error.ValueConstraintError: + pass + else: + assert 0, 'constraint fail' + def testSubtype(self): + assert univ.Integer().subtype( + value=1, + implicitTag=tag.Tag(tag.tagClassPrivate,tag.tagFormatSimple,2), + subtypeSpec=constraint.SingleValueConstraint(1,3) + ) == univ.Integer( + value=1, + tagSet=tag.TagSet(tag.Tag(tag.tagClassPrivate, + tag.tagFormatSimple,2)), + subtypeSpec=constraint.ConstraintsIntersection(constraint.SingleValueConstraint(1,3)) + ) + +class BitStringTestCase(unittest.TestCase): + def setUp(self): + self.b = univ.BitString( + namedValues=namedval.NamedValues(('Active', 0), ('Urgent', 1)) + ) + def testSet(self): + assert self.b.clone('Active') == (1,) + assert self.b.clone("'1010100110001010'B") == (1,0,1,0,1,0,0,1,1,0,0,0,1,0,1,0) + assert self.b.clone("'A98A'H") == (1,0,1,0,1,0,0,1,1,0,0,0,1,0,1,0) + assert self.b.clone((1,0,1)) == (1,0,1) + def testStr(self): + assert str(self.b.clone('Urgent,Active')) == '(1, 1)' + def testRepr(self): + assert repr(self.b.clone('Urgent,Active')) == 'BitString("\'11\'B")' + def testTag(self): + assert univ.BitString().getTagSet() == tag.TagSet( + (), + tag.Tag(tag.tagClassUniversal, tag.tagFormatSimple, 0x03) + ) + def testLen(self): assert len(self.b.clone("'A98A'H")) == 16 + def testIter(self): + assert self.b.clone("'A98A'H")[0] == 1 + assert self.b.clone("'A98A'H")[1] == 0 + assert self.b.clone("'A98A'H")[2] == 1 + +class OctetStringTestCase(unittest.TestCase): + def testInit(self): + assert univ.OctetString(str2octs('abcd')) == str2octs('abcd'), '__init__() fails' + def testBinStr(self): + assert univ.OctetString(binValue="1000010111101110101111000000111011") == ints2octs((133, 238, 188, 14, 192)), 'bin init fails' + def testHexStr(self): + assert univ.OctetString(hexValue="FA9823C43E43510DE3422") == ints2octs((250, 152, 35, 196, 62, 67, 81, 13, 227, 66, 32)), 'hex init fails' + def testTuple(self): + assert univ.OctetString((1,2,3,4,5)) == ints2octs((1,2,3,4,5)), 'tuple init failed' + def testStr(self): + assert str(univ.OctetString('q')) == 'q', '__str__() fails' + def testSeq(self): + assert univ.OctetString('q')[0] == str2octs('q')[0],'__getitem__() fails' + def testAsOctets(self): + assert univ.OctetString('abcd').asOctets() == str2octs('abcd'), 'testAsOctets() fails' + def testAsInts(self): + assert univ.OctetString('abcd').asNumbers() == (97, 98, 99, 100), 'testAsNumbers() fails' + + def testEmpty(self): + try: + str(univ.OctetString()) + except PyAsn1Error: + pass + else: + assert 0, 'empty OctetString() not reported' + + def testAdd(self): + assert univ.OctetString('') + 'q' == str2octs('q'), '__add__() fails' + def testRadd(self): + assert 'b' + univ.OctetString('q') == str2octs('bq'), '__radd__() fails' + def testMul(self): + assert univ.OctetString('a') * 2 == str2octs('aa'), '__mul__() fails' + def testRmul(self): + assert 2 * univ.OctetString('b') == str2octs('bb'), '__rmul__() fails' + def testTag(self): + assert univ.OctetString().getTagSet() == tag.TagSet( + (), + tag.Tag(tag.tagClassUniversal, tag.tagFormatSimple, 0x04) + ) + +class Null(unittest.TestCase): + def testStr(self): assert str(univ.Null('')) == '', 'str() fails' + def testTag(self): + assert univ.Null().getTagSet() == tag.TagSet( + (), + tag.Tag(tag.tagClassUniversal, tag.tagFormatSimple, 0x05) + ) + def testConstraints(self): + try: + univ.Null(2) + except error.ValueConstraintError: + pass + else: + assert 0, 'constraint fail' + +class RealTestCase(unittest.TestCase): + def testStr(self): assert str(univ.Real(1.0)) == '1.0','str() fails' + def testRepr(self): assert repr(univ.Real(-4.1)) == 'Real((-41, 10, -1))','repr() fails' + def testAdd(self): assert univ.Real(-4.1) + 1.4 == -2.7, '__add__() fails' + def testRadd(self): assert 4 + univ.Real(0.5) == 4.5, '__radd__() fails' + def testSub(self): assert univ.Real(3.9) - 1.7 == 2.2, '__sub__() fails' + def testRsub(self): assert 6.1 - univ.Real(0.1) == 6, '__rsub__() fails' + def testMul(self): assert univ.Real(3.0) * -3 == -9, '__mul__() fails' + def testRmul(self): assert 2 * univ.Real(3.0) == 6, '__rmul__() fails' + def testDiv(self): assert univ.Real(3.0) / 2 == 1.5, '__div__() fails' + def testRdiv(self): assert 6 / univ.Real(3.0) == 2, '__rdiv__() fails' + def testMod(self): assert univ.Real(3.0) % 2 == 1, '__mod__() fails' + def testRmod(self): assert 4 % univ.Real(3.0) == 1, '__rmod__() fails' + def testPow(self): assert univ.Real(3.0) ** 2 == 9, '__pow__() fails' + def testRpow(self): assert 2 ** univ.Real(2.0) == 4, '__rpow__() fails' + def testInt(self): assert int(univ.Real(3.0)) == 3, '__int__() fails' + def testLong(self): assert int(univ.Real(8.0)) == 8, '__long__() fails' + def testFloat(self): assert float(univ.Real(4.0))==4.0,'__float__() fails' + def testPrettyIn(self): assert univ.Real((3,10,0)) == 3, 'prettyIn() fails' + # infinite float values + def testStrInf(self): + assert str(univ.Real('inf')) == 'inf','str() fails' + def testReprInf(self): + assert repr(univ.Real('inf')) == 'Real(\'inf\')','repr() fails' + def testAddInf(self): + assert univ.Real('inf') + 1 == float('inf'), '__add__() fails' + def testRaddInf(self): + assert 1 + univ.Real('inf') == float('inf'), '__radd__() fails' + def testIntInf(self): + try: + assert int(univ.Real('inf')) + except OverflowError: + pass + else: + assert 0, '__int__() fails' + def testLongInf(self): + try: + assert int(univ.Real('inf')) + except OverflowError: + pass + else: + assert 0, '__long__() fails' + assert int(univ.Real(8.0)) == 8, '__long__() fails' + def testFloatInf(self): + assert float(univ.Real('-inf')) == float('-inf'),'__float__() fails' + def testPrettyInInf(self): + assert univ.Real(float('inf')) == float('inf'), 'prettyIn() fails' + def testPlusInf(self): + assert univ.Real('inf').isPlusInfinity(), 'isPlusInfinity failed' + def testMinusInf(self): + assert univ.Real('-inf').isMinusInfinity(), 'isMinusInfinity failed' + + def testTag(self): + assert univ.Real().getTagSet() == tag.TagSet( + (), + tag.Tag(tag.tagClassUniversal, tag.tagFormatSimple, 0x09) + ) + +class ObjectIdentifier(unittest.TestCase): + def testStr(self): + assert str(univ.ObjectIdentifier((1,3,6))) == '1.3.6' + def testEq(self): + assert univ.ObjectIdentifier((1,3,6)) == (1,3,6), '__cmp__() fails' + def testAdd(self): + assert univ.ObjectIdentifier((1,3)) + (6,)==(1,3,6),'__add__() fails' + def testRadd(self): + assert (1,) + univ.ObjectIdentifier((3,6))==(1,3,6),'__radd__() fails' + def testLen(self): + assert len(univ.ObjectIdentifier((1,3))) == 2,'__len__() fails' + def testPrefix(self): + o = univ.ObjectIdentifier('1.3.6') + assert o.isPrefixOf((1,3,6)), 'isPrefixOf() fails' + assert o.isPrefixOf((1,3,6,1)), 'isPrefixOf() fails' + assert not o.isPrefixOf((1,3)), 'isPrefixOf() fails' + def testInput(self): + assert univ.ObjectIdentifier('1.3.6')==(1,3,6),'prettyIn() fails' + def testTag(self): + assert univ.ObjectIdentifier().getTagSet() == tag.TagSet( + (), + tag.Tag(tag.tagClassUniversal, tag.tagFormatSimple, 0x06) + ) + +class SequenceOf(unittest.TestCase): + def setUp(self): + self.s1 = univ.SequenceOf( + componentType=univ.OctetString('') + ) + self.s2 = self.s1.clone() + def testTag(self): + assert self.s1.getTagSet() == tag.TagSet( + (), + tag.Tag(tag.tagClassUniversal, tag.tagFormatConstructed, 0x10) + ), 'wrong tagSet' + def testSeq(self): + self.s1.setComponentByPosition(0, univ.OctetString('abc')) + assert self.s1[0] == str2octs('abc'), 'set by idx fails' + self.s1[0] = 'cba' + assert self.s1[0] == str2octs('cba'), 'set by idx fails' + def testCmp(self): + self.s1.clear() + self.s1.setComponentByPosition(0, 'abc') + self.s2.clear() + self.s2.setComponentByPosition(0, univ.OctetString('abc')) + assert self.s1 == self.s2, '__cmp__() fails' + def testSubtypeSpec(self): + s = self.s1.clone(subtypeSpec=constraint.ConstraintsUnion( + constraint.SingleValueConstraint(str2octs('abc')) + )) + try: + s.setComponentByPosition(0, univ.OctetString('abc')) + except: + assert 0, 'constraint fails' + try: + s.setComponentByPosition(1, univ.OctetString('Abc')) + except: + pass + else: + assert 0, 'constraint fails' + def testSizeSpec(self): + s = self.s1.clone(sizeSpec=constraint.ConstraintsUnion( + constraint.ValueSizeConstraint(1,1) + )) + s.setComponentByPosition(0, univ.OctetString('abc')) + try: + s.verifySizeSpec() + except: + assert 0, 'size spec fails' + s.setComponentByPosition(1, univ.OctetString('abc')) + try: + s.verifySizeSpec() + except: + pass + else: + assert 0, 'size spec fails' + def testGetComponentTagMap(self): + assert self.s1.getComponentTagMap().getPosMap() == { + univ.OctetString.tagSet: univ.OctetString('') + } + def testSubtype(self): + self.s1.clear() + assert self.s1.subtype( + implicitTag=tag.Tag(tag.tagClassPrivate,tag.tagFormatSimple,2), + subtypeSpec=constraint.SingleValueConstraint(1,3), + sizeSpec=constraint.ValueSizeConstraint(0,1) + ) == self.s1.clone( + tagSet=tag.TagSet(tag.Tag(tag.tagClassPrivate, + tag.tagFormatSimple,2)), + subtypeSpec=constraint.ConstraintsIntersection(constraint.SingleValueConstraint(1,3)), + sizeSpec=constraint.ValueSizeConstraint(0,1) + ) + def testClone(self): + self.s1.setComponentByPosition(0, univ.OctetString('abc')) + s = self.s1.clone() + assert len(s) == 0 + s = self.s1.clone(cloneValueFlag=1) + assert len(s) == 1 + assert s.getComponentByPosition(0) == self.s1.getComponentByPosition(0) + +class Sequence(unittest.TestCase): + def setUp(self): + self.s1 = univ.Sequence(componentType=namedtype.NamedTypes( + namedtype.NamedType('name', univ.OctetString('')), + namedtype.OptionalNamedType('nick', univ.OctetString('')), + namedtype.DefaultedNamedType('age', univ.Integer(34)) + )) + def testTag(self): + assert self.s1.getTagSet() == tag.TagSet( + (), + tag.Tag(tag.tagClassUniversal, tag.tagFormatConstructed, 0x10) + ), 'wrong tagSet' + def testById(self): + self.s1.setComponentByName('name', univ.OctetString('abc')) + assert self.s1.getComponentByName('name') == str2octs('abc'), 'set by name fails' + def testByKey(self): + self.s1['name'] = 'abc' + assert self.s1['name'] == str2octs('abc'), 'set by key fails' + def testGetNearPosition(self): + assert self.s1.getComponentTagMapNearPosition(1).getPosMap() == { + univ.OctetString.tagSet: univ.OctetString(''), + univ.Integer.tagSet: univ.Integer(34) + } + assert self.s1.getComponentPositionNearType( + univ.OctetString.tagSet, 1 + ) == 1 + def testGetDefaultComponentByPosition(self): + self.s1.clear() + assert self.s1.getDefaultComponentByPosition(0) == None + assert self.s1.getDefaultComponentByPosition(2) == univ.Integer(34) + def testSetDefaultComponents(self): + self.s1.clear() + assert self.s1.getComponentByPosition(2) == None + self.s1.setComponentByPosition(0, univ.OctetString('Ping')) + self.s1.setComponentByPosition(1, univ.OctetString('Pong')) + self.s1.setDefaultComponents() + assert self.s1.getComponentByPosition(2) == 34 + def testClone(self): + self.s1.setComponentByPosition(0, univ.OctetString('abc')) + self.s1.setComponentByPosition(1, univ.OctetString('def')) + self.s1.setComponentByPosition(2, univ.Integer(123)) + s = self.s1.clone() + assert s.getComponentByPosition(0) != self.s1.getComponentByPosition(0) + assert s.getComponentByPosition(1) != self.s1.getComponentByPosition(1) + assert s.getComponentByPosition(2) != self.s1.getComponentByPosition(2) + s = self.s1.clone(cloneValueFlag=1) + assert s.getComponentByPosition(0) == self.s1.getComponentByPosition(0) + assert s.getComponentByPosition(1) == self.s1.getComponentByPosition(1) + assert s.getComponentByPosition(2) == self.s1.getComponentByPosition(2) + +class SetOf(unittest.TestCase): + def setUp(self): + self.s1 = univ.SetOf(componentType=univ.OctetString('')) + def testTag(self): + assert self.s1.getTagSet() == tag.TagSet( + (), + tag.Tag(tag.tagClassUniversal, tag.tagFormatConstructed, 0x11) + ), 'wrong tagSet' + def testSeq(self): + self.s1.setComponentByPosition(0, univ.OctetString('abc')) + assert self.s1[0] == str2octs('abc'), 'set by idx fails' + self.s1.setComponentByPosition(0, self.s1[0].clone('cba')) + assert self.s1[0] == str2octs('cba'), 'set by idx fails' + +class Set(unittest.TestCase): + def setUp(self): + self.s1 = univ.Set(componentType=namedtype.NamedTypes( + namedtype.NamedType('name', univ.OctetString('')), + namedtype.OptionalNamedType('null', univ.Null('')), + namedtype.DefaultedNamedType('age', univ.Integer(34)) + )) + self.s2 = self.s1.clone() + def testTag(self): + assert self.s1.getTagSet() == tag.TagSet( + (), + tag.Tag(tag.tagClassUniversal, tag.tagFormatConstructed, 0x11) + ), 'wrong tagSet' + def testByTypeWithPythonValue(self): + self.s1.setComponentByType(univ.OctetString.tagSet, 'abc') + assert self.s1.getComponentByType( + univ.OctetString.tagSet + ) == str2octs('abc'), 'set by name fails' + def testByTypeWithInstance(self): + self.s1.setComponentByType(univ.OctetString.tagSet, univ.OctetString('abc')) + assert self.s1.getComponentByType( + univ.OctetString.tagSet + ) == str2octs('abc'), 'set by name fails' + def testGetTagMap(self): + assert self.s1.getTagMap().getPosMap() == { + univ.Set.tagSet: univ.Set() + } + def testGetComponentTagMap(self): + assert self.s1.getComponentTagMap().getPosMap() == { + univ.OctetString.tagSet: univ.OctetString(''), + univ.Null.tagSet: univ.Null(''), + univ.Integer.tagSet: univ.Integer(34) + } + def testGetPositionByType(self): + assert self.s1.getComponentPositionByType( + univ.Null().getTagSet() + ) == 1 + +class Choice(unittest.TestCase): + def setUp(self): + innerComp = univ.Choice(componentType=namedtype.NamedTypes( + namedtype.NamedType('count', univ.Integer()), + namedtype.NamedType('flag', univ.Boolean()) + )) + self.s1 = univ.Choice(componentType=namedtype.NamedTypes( + namedtype.NamedType('name', univ.OctetString()), + namedtype.NamedType('sex', innerComp) + )) + def testTag(self): + assert self.s1.getTagSet() == tag.TagSet(), 'wrong tagSet' + def testOuterByTypeWithPythonValue(self): + self.s1.setComponentByType(univ.OctetString.tagSet, 'abc') + assert self.s1.getComponentByType( + univ.OctetString.tagSet + ) == str2octs('abc') + def testOuterByTypeWithInstanceValue(self): + self.s1.setComponentByType( + univ.OctetString.tagSet, univ.OctetString('abc') + ) + assert self.s1.getComponentByType( + univ.OctetString.tagSet + ) == str2octs('abc') + def testInnerByTypeWithPythonValue(self): + self.s1.setComponentByType(univ.Integer.tagSet, 123, 1) + assert self.s1.getComponentByType( + univ.Integer.tagSet, 1 + ) == 123 + def testInnerByTypeWithInstanceValue(self): + self.s1.setComponentByType( + univ.Integer.tagSet, univ.Integer(123), 1 + ) + assert self.s1.getComponentByType( + univ.Integer.tagSet, 1 + ) == 123 + def testCmp(self): + self.s1.setComponentByName('name', univ.OctetString('abc')) + assert self.s1 == str2octs('abc'), '__cmp__() fails' + def testGetComponent(self): + self.s1.setComponentByType(univ.OctetString.tagSet, 'abc') + assert self.s1.getComponent() == str2octs('abc'), 'getComponent() fails' + def testGetName(self): + self.s1.setComponentByType(univ.OctetString.tagSet, 'abc') + assert self.s1.getName() == 'name', 'getName() fails' + def testSetComponentByPosition(self): + self.s1.setComponentByPosition(0, univ.OctetString('Jim')) + assert self.s1 == str2octs('Jim') + def testClone(self): + self.s1.setComponentByPosition(0, univ.OctetString('abc')) + s = self.s1.clone() + assert len(s) == 0 + s = self.s1.clone(cloneValueFlag=1) + assert len(s) == 1 + assert s.getComponentByPosition(0) == self.s1.getComponentByPosition(0) + +if __name__ == '__main__': unittest.main() -- cgit v1.2.3