diff options
Diffstat (limited to 'python/pyasn1')
65 files changed, 10987 insertions, 0 deletions
diff --git a/python/pyasn1/CHANGES b/python/pyasn1/CHANGES new file mode 100644 index 0000000000..561dedd882 --- /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 0000000000..fac589b8cd --- /dev/null +++ b/python/pyasn1/LICENSE @@ -0,0 +1,24 @@ +Copyright (c) 2005-2013, Ilya Etingof <ilya@glas.net> +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 0000000000..e8b3d36ce0 --- /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 0000000000..5de78eceb0 --- /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 <ilya@glas.net> +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 0000000000..ffa3b57e5a --- /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 0000000000..4de1713c03 --- /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 0000000000..0ee211c2a4 --- /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 0000000000..9c2c36ed6f --- /dev/null +++ b/python/pyasn1/doc/codecs.html @@ -0,0 +1,503 @@ +<html> +<title> +PyASN1 codecs +</title> +<head> +</head> +<body> +<center> +<table width=60%> +<tr> +<td> +<h3> +2. PyASN1 Codecs +</h3> + +<p> +In ASN.1 context, +<a href=http://en.wikipedia.org/wiki/Codec>codec</a> +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 <i>substrate</i> or <i>essence</i>. +</p> + +<p> +In pyasn1 implementation, substrate takes shape of Python 3 bytes or +Python 2 string objects. +</p> + +<p> +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 +<a href=http://directory.apache.org/subprojects/asn1/>Apache ASN.1 project</a> +. +</p> + +<p> +As of this writing, codecs implemented in pyasn1 are all stateless, mostly +to keep the code simple. +</p> + +<p> +The pyasn1 package currently supports +<a href=http://en.wikipedia.org/wiki/Basic_encoding_rules>BER</a> codec and +its variations -- +<a href=http://en.wikipedia.org/wiki/Canonical_encoding_rules>CER</a> and +<a href=http://en.wikipedia.org/wiki/Distinguished_encoding_rules>DER</a>. +More ASN.1 codecs are planned for implementation in the future. +</p> + +<a name="2.1"></a> +<h4> +2.1 Encoders +</h4> + +<p> +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. +</p> + +<p> +The following code will create a pyasn1 Integer object and serialize it with +BER encoder: +</p> + +<table bgcolor="lightgray" border=0 width=100%><TR><TD> +<pre> +>>> from pyasn1.type import univ +>>> from pyasn1.codec.ber import encoder +>>> encoder.encode(univ.Integer(123456)) +b'\x02\x03\x01\xe2@' +>>> +</pre> +</td></tr></table> + +<p> +BER standard also defines a so-called <i>indefinite length</i> 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. +</p> + +<p> +<i>Constructed encoding</i> 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. +</p> + +<p> +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. +</p> + +<table bgcolor="lightgray" border=0 width=100%><TR><TD> +<pre> +>>> 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' +</pre> +</td></tr></table> + +<p> +The <b>defMode</b> encoder parameter disables definite length encoding mode, +while the optional <b>maxChunkSize</b> parameter specifies desired +substrate chunk size that influences memory requirements at the decoder's end. +</p> + +<p> +To use CER or DER encoders one needs to explicitly import and call them - the +APIs are all compatible. +</p> + +<table bgcolor="lightgray" border=0 width=100%><TR><TD> +<pre> +>>> 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' +>>> +</pre> +</td></tr></table> + +<a name="2.2"></a> +<h4> +2.2 Decoders +</h4> + +<p> +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. +</p> + +<table bgcolor="lightgray" border=0 width=100%><TR><TD> +<pre> +>>> 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'') +>>> +</pre> +</td></tr></table> + +<p> +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. +</p> + +<p> +All pyasn1 decoders can handle both definite and indefinite length +encoding modes automatically, explicit switching into one mode +to another is not required. +</p> + +<table bgcolor="lightgray" border=0 width=100%><TR><TD> +<pre> +>>> 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'') +>>> +</pre> +</td></tr></table> + +<p> +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. +</p> + +<p> +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 <i>guide</i> 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). +</p> + +<table bgcolor="lightgray" border=0 width=100%><TR><TD> +<pre> +>>> from pyasn1.codec.ber import decoder +>>> decoder.decode(b'\x02\x01\x0c', asn1Spec=univ.Integer()) +Integer(12), b'' +>>> +</pre> +</td></tr></table> + +<p> +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: +</p> + +<table bgcolor="lightgray" border=0 width=100%><TR><TD> +<pre> +>>> 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'') +>>> +</pre> +</td></tr></table> + +<p> +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. +</p> + +<p> +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. +</p> + +<table bgcolor="lightgray" border=0 width=100%><TR><TD> +<pre> +>>> 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 +>>> +</pre> +</td></tr></table> + +<p> +Similarily to encoders, to use CER or DER decoders application has to +explicitly import and call them - all APIs are compatible. +</p> + +<table bgcolor="lightgray" border=0 width=100%><TR><TD> +<pre> +>>> 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'') +>>> +</pre> +</td></tr></table> + +<a name="2.2.1"></a> +<h4> +2.2.1 Decoding untagged types +</h4> + +<p> +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. +</p> + +<p> +To explain the issue, we will first prepare a Choice object to deal with: +</p> + +<table bgcolor="lightgray" border=0 width=100%><TR><TD> +<pre> +>>> 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' +>>> +</pre> +</td></tr></table> + +<p> +Let's now encode this Choice object and then decode its substrate +with and without pyasn1 specification object: +</p> + +<table bgcolor="lightgray" border=0 width=100%><TR><TD> +<pre> +>>> 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' +>>> +</pre> +</td></tr></table> + +<p> +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. +</p> + +<p> +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. +</p> + +<p> +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. +</p> + +<p> +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. +</p> + +<p> +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. +</p> + +<table bgcolor="lightgray" border=0 width=100%><TR><TD> +<pre> +>>> 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' +>>> +</pre> +</td></tr></table> + +<p> +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: +</p> + +<table bgcolor="lightgray" border=0 width=100%><TR><TD> +<pre> +>>> 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'') +>>> +</pre> +</td></tr></table> + +<p> +Both CHOICE and ANY types are widely used in practice. Reader is welcome to +take a look at +<a href=http://www.cs.auckland.ac.nz/~pgut001/pubs/x509guide.txt> +ASN.1 specifications of X.509 applications</a> for more information. +</p> + +<a name="2.2.2"></a> +<h4> +2.2.2 Ignoring unknown types +</h4> + +<p> +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. +</p> + +<table bgcolor="lightgray" border=0 width=100%><TR><TD> +<pre> +>>> 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'), '') +>>> +</pre> +</td></tr></table> + +<p> +It's also possible to configure a custom decoder, to handle unknown tags +found in substrate. This can be done by means of <b>defaultRawDecoder</b> +attribute holding a reference to type decoder object. Refer to the source +for API details. +</p> + +<hr> + +</td> +</tr> +</table> +</center> +</body> +</html> diff --git a/python/pyasn1/doc/constraints.html b/python/pyasn1/doc/constraints.html new file mode 100644 index 0000000000..53da1addff --- /dev/null +++ b/python/pyasn1/doc/constraints.html @@ -0,0 +1,436 @@ +<html> +<title> +PyASN1 subtype constraints +</title> +<head> +</head> +<body> +<center> +<table width=60%> +<tr> +<td> + +<h4> +1.4 PyASN1 subtype constraints +</h4> + +<p> +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. +</p> + +<p> +Imposing value constraints on an ASN.1 type can also be seen as creating +a subtype from its base type. +</p> + +<p> +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. +</p> + +<p> +A handful of different flavors of <i>constraints</i> 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. +</p> + +<a name="1.4.1"></a> +<h4> +1.4.1 Single value constraint +</h4> + +<p> +This kind of constraint allows for limiting type to a finite, specified set +of values. +</p> + +<table bgcolor="lightgray" border=0 width=100%><TR><TD> +<pre> +DialButton ::= OCTET STRING ( + "0" | "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9" +) +</pre> +</td></tr></table> + +<p> +Its pyasn1 implementation would look like: +</p> + +<table bgcolor="lightgray" border=0 width=100%><TR><TD> +<pre> +>>> 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 +>>> +</pre> +</td></tr></table> + +<p> +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. +</p> + +<table bgcolor="lightgray" border=0 width=100%><TR><TD> +<pre> +>>> 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 +>>> +</pre> +</td></tr></table> + +<p> +Constrained pyasn1 value object can never hold a violating value. +</p> + +<a name="1.4.2"></a> +<h4> +1.4.2 Value range constraint +</h4> + +<p> +A pair of values, compliant to a type to be constrained, denote low and upper +bounds of allowed range of values of a type. +</p> + +<table bgcolor="lightgray" border=0 width=100%><TR><TD> +<pre> +Teenagers ::= INTEGER (13..19) +</pre> +</td></tr></table> + +<p> +And in pyasn1 terms: +</p> + +<table bgcolor="lightgray" border=0 width=100%><TR><TD> +<pre> +>>> 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 +>>> +</pre> +</td></tr></table> + +<p> +Value range constraint usually applies numeric types. +</p> + +<a name="1.4.3"></a> +<h4> +1.4.3 Size constraint +</h4> + +<p> +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. +</p> + +<table bgcolor="lightgray" border=0 width=100%><TR><TD> +<pre> +TwoBits ::= BIT STRING (SIZE (2)) +</pre> +</td></tr></table> + +<p> +Express the same grammar in pyasn1: +</p> + +<table bgcolor="lightgray" border=0 width=100%><TR><TD> +<pre> +>>> 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) +>>> +</pre> +</td></tr></table> + +<p> +Size constraint can be applied to potentially massive values - bit or octet +strings, SEQUENCE OF/SET OF values. +</p> + +<a name="1.4.4"></a> +<h4> +1.4.4 Alphabet constraint +</h4> + +<p> +The permitted alphabet constraint is similar to Single value constraint +but constraint applies to individual characters of a value. +</p> + +<table bgcolor="lightgray" border=0 width=100%><TR><TD> +<pre> +MorseCode ::= PrintableString (FROM ("."|"-"|" ")) +</pre> +</td></tr></table> + +<p> +And in pyasn1: +</p> + +<table bgcolor="lightgray" border=0 width=100%><TR><TD> +<pre> +>>> 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: "?" +>>> +</pre> +</td></tr></table> + +<p> +Current implementation does not handle ranges of characters in constraint +(FROM "A".."Z" syntax), one has to list the whole set in a range. +</p> + +<a name="1.4.5"></a> +<h4> +1.4.5 Constraint combinations +</h4> + +<p> +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. +</p> + +<p> +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. +</p> + +<p> +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: +</p> + +<table bgcolor="lightgray" border=0 width=100%><TR><TD> +<pre> +PhoneNumber ::= NumericString (FROM ("0".."9")) (SIZE 11) +</pre> +</td></tr></table> + +<p> +Constraint intersection object serves the logic above: +</p> + +<table bgcolor="lightgray" border=0 width=100%><TR><TD> +<pre> +>>> 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" +>>> +</pre> +</td></tr></table> + +<p> +Union of constraints works by making sure that a value is compliant +to any of the constraint in a set. For instance: +</p> + +<table bgcolor="lightgray" border=0 width=100%><TR><TD> +<pre> +CapitalOrSmall ::= IA5String (FROM ('A','B','C') | FROM ('a','b','c')) +</pre> +</td></tr></table> + +<p> +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: +</p> + +<table bgcolor="lightgray" border=0 width=100%><TR><TD> +<pre> +>>> 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" +>>> +</pre> +</td></tr></table> + +<p> +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. +</p> + +<table bgcolor="lightgray" border=0 width=100%><TR><TD> +<pre> +NoZero ::= INTEGER (ALL EXCEPT 0) +</pre> +</td></tr></table> + +<p> +In pyasn1 the above definition would read: +</p> + +<table bgcolor="lightgray" border=0 width=100%><TR><TD> +<pre> +>>> 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 +>>> +</pre> +</td></tr></table> + +<p> +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. +</p> + +<a name="1.5"></a> +<h4> +1.5 Types relationships +</h4> + +<p> +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: +<i>tag set</i> and <i>subtype constraints</i>. One pyasn1 type is considered +to be a derivative of another if their TagSet and Constraint objects are +a derivation of one another. +</p> + +<p> +The following example illustrates the concept (we use the same tagset but +different constraints for simplicity): +</p> + +<table bgcolor="lightgray" border=0 width=100%><TR><TD> +<pre> +>>> 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 +>>> +</pre> +</td></tr></table> + +<p> +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: +<b>isSameTypeWith</b>() and <b>isSuperTypeOf</b>(). 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. +</p> + +<hr> + +</td> +</tr> +</table> +</center> +</body> +</html> diff --git a/python/pyasn1/doc/constructed.html b/python/pyasn1/doc/constructed.html new file mode 100644 index 0000000000..88de750758 --- /dev/null +++ b/python/pyasn1/doc/constructed.html @@ -0,0 +1,377 @@ +<html> +<title> +PyASN1 Constructed types +</title> +<head> +</head> +<body> +<center> +<table width=60%> +<tr> +<td> + +<h4> +1.3 PyASN1 Constructed types +</h4> + +<p> +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. +</p> + +<p> +In pyasn1 implementation, constructed ASN.1 types behave like +Python sequences, and also support additional component addressing methods, +specific to particular constructed type. +</p> + +<a name="1.3.1"></a> +<h4> +1.3.1 Sequence and Set types +</h4> + +<p> +The Sequence and Set types have many similar properties: +</p> +<ul> +<li>they can hold any number of inner components of different types +<li>every component has a human-friendly identifier +<li>any component can have a default value +<li>some components can be absent. +</ul> + +<p> +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. +<p> + +<table bgcolor="lightgray" border=0 width=100%><TR><TD> +<pre> +Record ::= SEQUENCE { + id INTEGER, + room [0] INTEGER OPTIONAL, + house [1] INTEGER DEFAULT 0 +} +</pre> +</td></tr></table> + +<p> +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. +</p> + +<table bgcolor="lightgray" border=0 width=100%><TR><TD> +<pre> +>>> 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) +... ) +... ) +... ) +>>> +</pre> +</td></tr></table> + +<p> +All pyasn1 constructed type classes have a class attribute <b>componentType</b> +that represent default type specification. Its value is a NamedTypes object. +</p> + +<p> +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. +</p> + +<p> +Finally, invocation of a subtype() method of pyasn1 type objects in the code +above returns an implicitly tagged copy of original object. +</p> + +<p> +Once a SEQUENCE or SET type is decleared with pyasn1, it can be instantiated +and initialized (continuing the above code): +</p> + +<table bgcolor="lightgray" border=0 width=100%><TR><TD> +<pre> +>>> 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 +</pre> +</td></tr></table> + +<p> +Inner components of pyasn1 Sequence/Set objects could be accessed using the +following methods: +</p> + +<table bgcolor="lightgray" border=0 width=100%><TR><TD> +<pre> +>>> 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 +>>> +</pre> +</td></tr></table> + +<p> +The Set type share all the properties of Sequence type, and additionally +support by-tag component addressing (as all Set components have distinct +types). +</p> + +<table bgcolor="lightgray" border=0 width=100%><TR><TD> +<pre> +>>> 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 +>>> +</pre> +</td></tr></table> + +<a name="1.3.2"></a> +<h4> +1.3.2 SequenceOf and SetOf types +</h4> + +<p> +Both, SequenceOf and SetOf types resemble an unlimited size list of components. +All the components must be of the same type. +</p> + +<table bgcolor="lightgray" border=0 width=100%><TR><TD> +<pre> +Progression ::= SEQUENCE OF INTEGER + +arithmeticProgression Progression ::= { 1, 3, 5, 7 } +</pre> +</td></tr></table> + +<p> +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. +</p> + +<p> +To specify inner component type, the <b>componentType</b> class attribute +should refer to another pyasn1 type object. +</p> + +<table bgcolor="lightgray" border=0 width=100%><TR><TD> +<pre> +>>> 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) +>>> +</pre> +</td></tr></table> + +<p> +Any scalar or constructed pyasn1 type object can serve as an inner component. +Missing components are prohibited in SequenceOf/SetOf value objects. +</p> + +<a name="1.3.3"></a> +<h4> +1.3.3 Choice type +</h4> + +<p> +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. +</p> + +<table bgcolor="lightgray" border=0 width=100%><TR><TD> +<pre> +CodeOrMessage ::= CHOICE { + code INTEGER, + message OCTET STRING +} +</pre> +</td></tr></table> + +<p> +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. +</p> + +<table bgcolor="lightgray" border=0 width=100%><TR><TD> +<pre> +>>> 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' +>>> +</pre> +</td></tr></table> + +<p> +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): +</p> + +<table bgcolor="lightgray" border=0 width=100%><TR><TD> +<pre> +>>> codeOrMessage.getName() +'message' +>>> codeOrMessage.getComponent() +OctetString(b'my string value') +>>> +</pre> +</td></tr></table> + +<a name="1.3.4"></a> +<h4> +1.3.4 Any type +</h4> + +<p> +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. +</p> + +<table bgcolor="lightgray" border=0 width=100%><TR><TD> +<pre> +Error ::= SEQUENCE { + code INTEGER, + parameter ANY DEFINED BY code +} +</pre> +</td></tr></table> + +<p> +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). +</p> + +<p> +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. +</p> + +<table bgcolor="lightgray" border=0 width=100%><TR><TD> +<pre> +>>> 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' +>>> +</pre> +</td></tr></table> + +<p> +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. +</p> + +<p> +There will be some more talk and code snippets covering Any type in the codecs +chapters that follow. +</p> + +<hr> + +</td> +</tr> +</table> +</center> +</body> +</html> diff --git a/python/pyasn1/doc/intro.html b/python/pyasn1/doc/intro.html new file mode 100644 index 0000000000..3ff18b6ae5 --- /dev/null +++ b/python/pyasn1/doc/intro.html @@ -0,0 +1,156 @@ +<html> +<title> +PyASN1 reference manual +</title> +<head> +</head> +<body> +<center> +<table width=60%> +<tr> +<td> + +<h3> +PyASN1 reference manual +</h3> + +<p align=right> +<i>written by <a href=mailto:ilya@glas.net>Ilya Etingof</a>, 2011-2012</i> +</p> + +<p> +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. +</p> + +<p> +Abstract Syntax Notation One +(<a href=http://en.wikipedia.org/wiki/Abstract_Syntax_Notation_1x>ASN.1</a>) +is a set of +<a href=http://www.itu.int/ITU-T/studygroups/com17/languages/X.680-X.693-0207w.zip> +ITU standards</a> 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: +<ul> +<li>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. +<li>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. +<li>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. +</ul> +</p> + +<p> +This tutorial and algorithms, implemented by pyasn1 library, are +largely based on the information read in the book +<a href="http://www.oss.com/asn1/dubuisson.html"> +ASN.1 - Communication between heterogeneous systems</a> +by Olivier Dubuisson. Another relevant resource is +<a href=ftp://ftp.rsasecurity.com/pub/pkcs/ascii/layman.asc> +A Layman's Guide to a Subset of ASN.1, BER, and DER</a> by Burton S. Kaliski. +It's advised to refer to these books for more in-depth knowledge on the +subject of ASN.1. +</p> + +<p> +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. +</p> + +</p> +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. +</p> + +<h3> +Table of contents +</h3> + +<p> +<ul> +<li><a href="scalar.html">1. Data model for ASN.1 types</a> +<li><a href="scalar.html#1.1">1.1 Scalar types</a> +<li><a href="scalar.html#1.1.1">1.1.1 Boolean type</a> +<li><a href="scalar.html#1.1.2">1.1.2 Null type</a> +<li><a href="scalar.html#1.1.3">1.1.3 Integer type</a> +<li><a href="scalar.html#1.1.4">1.1.4 Enumerated type</a> +<li><a href="scalar.html#1.1.5">1.1.5 Real type</a> +<li><a href="scalar.html#1.1.6">1.1.6 Bit string type</a> +<li><a href="scalar.html#1.1.7">1.1.7 OctetString type</a> +<li><a href="scalar.html#1.1.8">1.1.8 ObjectIdentifier type</a> +<li><a href="scalar.html#1.1.9">1.1.9 Character string types</a> +<li><a href="scalar.html#1.1.10">1.1.10 Useful types</a> +<li><a href="tagging.html">1.2 Tagging</a> +<li><a href="constructed.html">1.3 Constructed types</a> +<li><a href="constructed.html#1.3.1">1.3.1 Sequence and Set types</a> +<li><a href="constructed.html#1.3.2">1.3.2 SequenceOf and SetOf types</a> +<li><a href="constructed.html#1.3.3">1.3.3 Choice type</a> +<li><a href="constructed.html#1.3.4">1.3.4 Any type</a> +<li><a href="constraints.html">1.4 Subtype constraints</a> +<li><a href="constraints.html#1.4.1">1.4.1 Single value constraint</a> +<li><a href="constraints.html#1.4.2">1.4.2 Value range constraint</a> +<li><a href="constraints.html#1.4.3">1.4.3 Size constraint</a> +<li><a href="constraints.html#1.4.4">1.4.4 Alphabet constraint</a> +<li><a href="constraints.html#1.4.5">1.4.5 Constraint combinations</a> +<li><a href="constraints.html#1.5">1.5 Types relationships</a> +<li><a href="codecs.html">2. Codecs</a> +<li><a href="codecs.html#2.1">2.1 Encoders</a> +<li><a href="codecs.html#2.2">2.2 Decoders</a> +<li><a href="codecs.html#2.2.1">2.2.1 Decoding untagged types</a> +<li><a href="codecs.html#2.2.2">2.2.2 Ignoring unknown types</a> +</ul> + +<p> +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 +<a href=mailto:pyasn1-users@lists.sourceforge.net>pyasn1 mailing list</a> +or better yet fix the issue and send +<a href=mailto:ilya@glas.net>me</a> the patch. +</p> + +<p> +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 +<a href=http://sourceforge.net/projects/pyasn1/files/pyasn1-modules/> +pyasn1-modules package</a>. 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. +</p> + +<p> +And finally, the latest pyasn1 package revision is available for free +download from +<a href=http://sourceforge.net/projects/pyasn1/>project home</a> and +also from the +<a href=http://pypi.python.org/pypi>Python package repository</a>. +</p> + +<hr> + +</td> +</tr> +</table> +</center> +</body> +</html> diff --git a/python/pyasn1/doc/pyasn1-tutorial.html b/python/pyasn1/doc/pyasn1-tutorial.html new file mode 100644 index 0000000000..2eb82f1e93 --- /dev/null +++ b/python/pyasn1/doc/pyasn1-tutorial.html @@ -0,0 +1,2405 @@ +<html> +<title> +PyASN1 programmer's manual +</title> +<head> +</head> +<body> +<center> +<table width=60%> +<tr> +<td> + +<h3> +PyASN1 programmer's manual +</h3> + +<p align=right> +<i>written by <a href=mailto:ilya@glas.net>Ilya Etingof</a>, 2011-2012</i> +</p> + +<p> +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. +</p> + +<p> +Abstract Syntax Notation One +(<a href=http://en.wikipedia.org/wiki/Abstract_Syntax_Notation_1x>ASN.1</a>) +is a set of +<a href=http://www.itu.int/ITU-T/studygroups/com17/languages/X.680-X.693-0207w.zip> +ITU standards</a> 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: +<ul> +<li>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. +<li>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. +<li>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. +</ul> +</p> + +<p> +This tutorial and algorithms, implemented by pyasn1 library, are +largely based on the information read in the book +<a href="http://www.oss.com/asn1/dubuisson.html"> +ASN.1 - Communication between heterogeneous systems</a> +by Olivier Dubuisson. Another relevant resource is +<a href=ftp://ftp.rsasecurity.com/pub/pkcs/ascii/layman.asc> +A Layman's Guide to a Subset of ASN.1, BER, and DER</a> by Burton S. Kaliski. +It's advised to refer to these books for more in-depth knowledge on the +subject of ASN.1. +</p> + +<p> +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. +</p> + +</p> +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. +</p> + +<h3> +Table of contents +</h3> + +<p> +<ul> +<li><a href="#1">1. Data model for ASN.1 types</a> +<li><a href="#1.1">1.1 Scalar types</a> +<li><a href="#1.1.1">1.1.1 Boolean type</a> +<li><a href="#1.1.2">1.1.2 Null type</a> +<li><a href="#1.1.3">1.1.3 Integer type</a> +<li><a href="#1.1.4">1.1.4 Enumerated type</a> +<li><a href="#1.1.5">1.1.5 Real type</a> +<li><a href="#1.1.6">1.1.6 Bit string type</a> +<li><a href="#1.1.7">1.1.7 OctetString type</a> +<li><a href="#1.1.8">1.1.8 ObjectIdentifier type</a> +<li><a href="#1.1.9">1.1.9 Character string types</a> +<li><a href="#1.1.10">1.1.10 Useful types</a> +<li><a href="#1.2">1.2 Tagging</a> +<li><a href="#1.3">1.3 Constructed types</a> +<li><a href="#1.3.1">1.3.1 Sequence and Set types</a> +<li><a href="#1.3.2">1.3.2 SequenceOf and SetOf types</a> +<li><a href="#1.3.3">1.3.3 Choice type</a> +<li><a href="#1.3.4">1.3.4 Any type</a> +<li><a href="#1.4">1.4 Subtype constraints</a> +<li><a href="#1.4.1">1.4.1 Single value constraint</a> +<li><a href="#1.4.2">1.4.2 Value range constraint</a> +<li><a href="#1.4.3">1.4.3 Size constraint</a> +<li><a href="#1.4.4">1.4.4 Alphabet constraint</a> +<li><a href="#1.4.5">1.4.5 Constraint combinations</a> +<li><a href="#1.5">1.5 Types relationships</a> +<li><a href="#2">2. Codecs</a> +<li><a href="#2.1">2.1 Encoders</a> +<li><a href="#2.2">2.2 Decoders</a> +<li><a href="#2.2.1">2.2.1 Decoding untagged types</a> +<li><a href="#2.2.2">2.2.2 Ignoring unknown types</a> +<li><a href="#3">3. Feedback and getting help</a> +</ul> + + +<a name="1"></a> +<h3> +1. Data model for ASN.1 types +</h3> + +<p> +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. +</p> + +<p> +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 <i>pyasn1 type object</i> versus <i>pyasn1 +value object</i>. +</p> + +<p> +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. +</p> + +<table bgcolor="lightgray" border=0 width=100%><TR><TD> +<pre> +>>> 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 +</pre> +</td></tr></table> + +<p> +It would be an error to perform an operation on a pyasn1 type object +as it holds no value to deal with: +</p> + +<table bgcolor="lightgray" border=0 width=100%><TR><TD> +<pre> +>>> from pyasn1.type import univ +>>> asn1IntegerType = univ.Integer() +>>> asn1IntegerType - 2 +... +pyasn1.error.PyAsn1Error: No value for __coerce__() +</pre> +</td></tr></table> + +<a name="1.1"></a> +<h4> +1.1 Scalar types +</h4> + +<p> +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. +</p> + +<a name="1.1.1"></a> +<h4> +1.1.1 Boolean type +</h4> + +<p> +This is the simplest type those values could be either True or False. +</p> + +<table bgcolor="lightgray" border=0 width=100%><TR><TD> +<pre> +;; type specification +FunFactorPresent ::= BOOLEAN + +;; values declaration and assignment +pythonFunFactor FunFactorPresent ::= TRUE +cobolFunFactor FunFactorPresent :: FALSE +</pre> +</td></tr></table> + +<p> +And here's pyasn1 version of it: +</p> + +<table bgcolor="lightgray" border=0 width=100%><TR><TD> +<pre> +>>> 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 +>>> +</pre> +</td></tr></table> + +<a name="1.1.2"></a> +<h4> +1.1.2 Null type +</h4> + +<p> +The NULL type is sometimes used to express the absense of any information. +</p> + +<table bgcolor="lightgray" border=0 width=100%><TR><TD> +<pre> +;; type specification +Vote ::= CHOICE { + agreed BOOLEAN, + skip NULL +} +</td></tr></table> + +;; value declaration and assignment +myVote Vote ::= skip:NULL +</pre> + +<p> +We will explain the CHOICE type later in this paper, meanwhile the NULL +type: +</p> + +<table bgcolor="lightgray" border=0 width=100%><TR><TD> +<pre> +>>> from pyasn1.type import univ +>>> skip = univ.Null() +>>> skip +Null('') +>>> +</pre> +</td></tr></table> + +<a name="1.1.3"></a> +<h4> +1.1.3 Integer type +</h4> + +<p> +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. +</p> + +<table bgcolor="lightgray" border=0 width=100%><TR><TD> +<pre> +;; values specification +age-of-universe INTEGER ::= 13750000000 +mean-martian-surface-temperature INTEGER ::= -63 +</pre> +</td></tr></table> + +<p> +A rather strigntforward mapping into pyasn1: +</p> + +<table bgcolor="lightgray" border=0 width=100%><TR><TD> +<pre> +>>> from pyasn1.type import univ +>>> ageOfUniverse = univ.Integer(13750000000) +>>> ageOfUniverse +Integer(13750000000) +>>> +>>> meanMartianSurfaceTemperature = univ.Integer(-63) +>>> meanMartianSurfaceTemperature +Integer(-63) +>>> +</pre> +</td></tr></table> + +<p> +ASN.1 allows to assign human-friendly names to particular values of +an INTEGER type. +</p> + +<table bgcolor="lightgray" border=0 width=100%><TR><TD> +<pre> +Temperature ::= INTEGER { + freezing(0), + boiling(100) +} +</pre> +</td></tr></table> + +<p> +The Temperature type expressed in pyasn1: +</p> + +<table bgcolor="lightgray" border=0 width=100%><TR><TD> +<pre> +>>> 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 +>>> +</pre> +</td></tr></table> + +<p> +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). +</p> + +<a name="1.1.4"></a> +<h4> +1.1.4 Enumerated type +</h4> + +<p> +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. +</p> + +<table bgcolor="lightgray" border=0 width=100%><TR><TD> +<pre> +error-status ::= ENUMERATED { + no-error(0), + authentication-error(10), + authorization-error(20), + general-failure(51) +} +</pre> +</td></tr></table> + +<p> +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). +</p> + +<table bgcolor="lightgray" border=0 width=100%><TR><TD> +<pre> +>>> 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 +>>> +</pre> +</td></tr></table> + +<p> +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. +</p> + +<a name="1.1.5"></a> +<h4> +1.1.5 Real type +</h4> + +<p> +Values of the Real type are a three-component tuple of mantissa, base and +exponent. All three are integers. +</p> + +<table bgcolor="lightgray" border=0 width=100%><TR><TD> +<pre> +pi ::= REAL { mantissa 314159, base 10, exponent -5 } +</pre> +</td></tr></table> + +<p> +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. + +</p> + +<table bgcolor="lightgray" border=0 width=100%><TR><TD> +<pre> +>>> 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 +>>> +</pre> +</td></tr></table> + +<p> +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). +</p> + +<a name="1.1.6"></a> +<h4> +1.1.6 Bit string type +</h4> + +<p> +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. +</p> + +<table bgcolor="lightgray" border=0 width=100%><TR><TD> +<pre> +public-key BIT STRING ::= '1010111011110001010110101101101 + 1011000101010000010110101100010 + 0110101010000111101010111111110'B + +signature BIT STRING ::= 'AF01330CD932093392100B39FF00DE0'H +</pre> +</td></tr></table> + +<p> +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. +</p> + +<table bgcolor="lightgray" border=0 width=100%><TR><TD> +<pre> +>>> 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") +>>> +</pre> +</td></tr></table> + +<p> +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. +</p> + +<table bgcolor="lightgray" border=0 width=100%><TR><TD> +<pre> +bit-mask BIT STRING ::= { + read-flag(0), + write-flag(2), + run-flag(4) +} +</pre> +</td></tr></table> + +<p> +To express this in pyasn1, we will employ the named values feature (as with +Enumeration type). +</p> + +<table bgcolor="lightgray" border=0 width=100%><TR><TD> +<pre> +>>> 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 +>>> +</pre> +</td></tr></table> + +<p> +The BitString objects mimic the properties of Python tuple type in part +of immutable sequence object protocol support. +</p> + +<a name="1.1.7"></a> +<h4> +1.1.7 OctetString type +</h4> + +<p> +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. +</p> + +<table bgcolor="lightgray" border=0 width=100%><TR><TD> +<pre> +thumbnail OCTET STRING ::= '1000010111101110101111000000111011'B +thumbnail OCTET STRING ::= 'FA9823C43E43510DE3422'H +</pre> +</td></tr></table> + +<p> +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. +</p> + +<table bgcolor="lightgray" border=0 width=100%><TR><TD> +<pre> +welcome-message OCTET STRING ::= "Welcome to ASN.1 wilderness!" +</pre> +</td></tr></table> + +<p> +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. +</p> + +<table bgcolor="lightgray" border=0 width=100%><TR><TD> +<pre> +>>> from pyasn1.type import univ +>>> thumbnail = univ.OctetString( +... binValue='1000010111101110101111000000111011' +... ) +>>> thumbnail +OctetString(hexValue='85eebcec0') +>>> thumbnail = univ.OctetString( +... hexValue='FA9823C43E43510DE3422' +... ) +>>> thumbnail +OctetString(hexValue='fa9823c43e4351de34220') +>>> +</pre> +</td></tr></table> + +<p> +Most frequent usage of the OctetString class is to instantiate it with +a text string. +</p> + +<table bgcolor="lightgray" border=0 width=100%><TR><TD> +<pre> +>>> 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') +>>> +</pre> +</td></tr></table> + +<p> +OctetString objects support the immutable sequence object protocol. +In other words, they behave like Python 3 bytes (or Python 2 strings). +</p> + +<p> +When running pyasn1 on Python 3, it's better to use the bytes objects for +OctetString instantiation, as it's more reliable and efficient. +</p> + +<p> +Additionally, OctetString's can also be instantiated with a sequence of +8-bit integers (ASCII codes). +</p> + +<table bgcolor="lightgray" border=0 width=100%><TR><TD> +<pre> +>>> univ.OctetString((77, 101, 101, 103, 111)) +OctetString(b'Meego') +</pre> +</td></tr></table> + +<p> +It is sometimes convenient to express OctetString instances as 8-bit +characters (Python 3 bytes or Python 2 strings) or 8-bit integers. +</p> + +<table bgcolor="lightgray" border=0 width=100%><TR><TD> +<pre> +>>> octetString = univ.OctetString('ABCDEF') +>>> octetString.asNumbers() +(65, 66, 67, 68, 69, 70) +>>> octetString.asOctets() +b'ABCDEF' +</pre> +</td></tr></table> + +<a name="1.1.8"></a> +<h4> +1.1.8 ObjectIdentifier type +</h4> + +<p> +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. +</p> + +<table bgcolor="lightgray" border=0 width=100%><TR><TD> +<pre> +internet-id OBJECT IDENTIFIER ::= { + iso(1) identified-organization(3) dod(6) internet(1) +} +</pre> +</td></tr></table> + +<p> +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. +</p> + +<table bgcolor="lightgray" border=0 width=100%><TR><TD> +<pre> +>>> 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') +</pre> +</td></tr></table> + +<p> +A more human-friendly "dotted" notation is also supported. +</p> + +<table bgcolor="lightgray" border=0 width=100%><TR><TD> +<pre> +>>> from pyasn1.type import univ +>>> univ.ObjectIdentifier('1.3.6.1') +ObjectIdentifier('1.3.6.1') +</pre> +</td></tr></table> + +<p> +Symbolic names of the arcs of object identifier, sometimes present in +ASN.1 specifications, are not preserved and used in pyasn1 objects. +</p> + +<p> +The ObjectIdentifier objects mimic the properties of Python tuple type in +part of immutable sequence object protocol support. +</p> + +<a name="1.1.9"></a> +<h4> +1.1.9 Character string types +</h4> + +<p> +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. +</p> + +<p> +The two types are specific to ASN.1 are NumericString and PrintableString. +</p> + +<table bgcolor="lightgray" border=0 width=100%><TR><TD> +<pre> +welcome-message ::= PrintableString { + "Welcome to ASN.1 text types" +} + +dial-pad-numbers ::= NumericString { + "0", "1", "2", "3", "4", "5", "6", "7", "8", "9" +} +</pre> +</td></tr></table> + +<p> +Their pyasn1 implementations are: +</p> + +<table bgcolor="lightgray" border=0 width=100%><TR><TD> +<pre> +>>> 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') +>>> +</pre> +</td></tr></table> + +<p> +The following types came to ASN.1 from ISO standards on character sets. +</p> + +<table bgcolor="lightgray" border=0 width=100%><TR><TD> +<pre> +>>> 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') +>>> +</pre> +</td></tr></table> + +<p> +The last three types are relatively recent addition to the family of +character string types: UniversalString, BMPString, UTF8String. +</p> + +<table bgcolor="lightgray" border=0 width=100%><TR><TD> +<pre> +>>> 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) +У попа была собака +>>> +</pre> +</td></tr></table> + +<p> +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. +</p> + +<a name="1.1.10"></a> +<h4> +1.1.10 Useful types +</h4> + +<p> +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. +</p> + +<p> +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. +</p> + +<table bgcolor="lightgray" border=0 width=100%><TR><TD> +<pre> +>>> from pyasn1.type import useful +>>> descrBER = useful.ObjectDescriptor( + "Basic encoding of a single ASN.1 type" +) +>>> +</pre> +</td></tr></table> + +<p> +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. +</p> + +<table bgcolor="lightgray" border=0 width=100%><TR><TD> +<pre> +;; 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" +</pre> +</td></tr></table> + +<table bgcolor="lightgray" border=0 width=100%><TR><TD> +<pre> +>>> from pyasn1.type import useful +>>> moscowTime = useful.GeneralizedTime("20110308120000.0") +>>> utcTime = useful.UTCTime("9803081200Z") +>>> +</pre> +</td></tr></table> + +<p> +Despite their intended use, these types possess no special, time-related, +handling in pyasn1. They are just printable strings. +</p> + +<a name="1.2"></a> +<h4> +1.2 Tagging +</h4> + +<p> +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. +</p> + +<p> +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. +</p> + +<p> +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 <i>tag</i> 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. +</p> + +<p> +For that reason almost every ASN.1 type has a tag (which is actually a +BER type) associated with it by default. +</p> + +<p> +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). +</p> + +<table bgcolor="lightgray" border=0 width=100%><TR><TD> +<pre> +MyIntegerType ::= [12] INTEGER +MyOctetString ::= [APPLICATION 0] OCTET STRING +</pre> +</td></tr></table> + +<p> +In pyasn1, tags are implemented as immutable, tuple-like objects: +</p> + +<table bgcolor="lightgray" border=0 width=100%><TR><TD> +<pre> +>>> 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 +>>> +</pre> +</td></tr></table> + +<p> +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. +</p> + +<p> +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. +</p> + +<p> +When tagging in IMPLICIT mode, the outermost existing tag is dropped and +replaced with a new one. +</p> + +<table bgcolor="lightgray" border=0 width=100%><TR><TD> +<pre> +MyIntegerType ::= [12] IMPLICIT INTEGER +MyOctetString ::= [APPLICATION 0] EXPLICIT OCTET STRING +</pre> +</td></tr></table> + +<p> +To model both modes of tagging, a specialized container TagSet object (holding +zero, one or more Tag objects) is used in pyasn1. +</p> + +<table bgcolor="lightgray" border=0 width=100%><TR><TD> +<pre> +>>> 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)) +>>> +</pre> +</td></tr></table> + +<p> +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. +</p> + +<p> +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. +</p> + +<table bgcolor="lightgray" border=0 width=100%><TR><TD> +<pre> +>>> 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 +>>> +</pre> +</td></tr></table> + +<p> +We will complete this discussion on tagging with a real-world example. The +following ASN.1 tagged type: +</p> + +<table bgcolor="lightgray" border=0 width=100%><TR><TD> +<pre> +MyIntegerType ::= [12] EXPLICIT INTEGER +</pre> +</td></tr></table> + +<p> +could be expressed in pyasn1 like this: +</p> + +<table bgcolor="lightgray" border=0 width=100%><TR><TD> +<pre> +>>> 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)) +>>> +</pre> +</td></tr></table> + +<p> +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. +</p> + +<p> +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. +</p> + + +<a name="1.3"></a> +<h4> +1.3 Constructed types +</h4> + +<p> +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. +</p> + +<p> +In pyasn1 implementation, constructed ASN.1 types behave like +Python sequences, and also support additional component addressing methods, +specific to particular constructed type. +</p> + +<a name="1.3.1"></a> +<h4> +1.3.1 Sequence and Set types +</h4> + +<p> +The Sequence and Set types have many similar properties: +</p> +<ul> +<li>they can hold any number of inner components of different types +<li>every component has a human-friendly identifier +<li>any component can have a default value +<li>some components can be absent. +</ul> + +<p> +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. +<p> + +<table bgcolor="lightgray" border=0 width=100%><TR><TD> +<pre> +Record ::= SEQUENCE { + id INTEGER, + room [0] INTEGER OPTIONAL, + house [1] INTEGER DEFAULT 0 +} +</pre> +</td></tr></table> + +<p> +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. +</p> + +<table bgcolor="lightgray" border=0 width=100%><TR><TD> +<pre> +>>> 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)) +... ) +... ) +>>> +</pre> +</td></tr></table> + +<p> +All pyasn1 constructed type classes have a class attribute <b>componentType</b> +that represent default type specification. Its value is a NamedTypes object. +</p> + +<p> +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. +</p> + +<p> +Finally, invocation of a subtype() method of pyasn1 type objects in the code +above returns an implicitly tagged copy of original object. +</p> + +<p> +Once a SEQUENCE or SET type is decleared with pyasn1, it can be instantiated +and initialized (continuing the above code): +</p> + +<table bgcolor="lightgray" border=0 width=100%><TR><TD> +<pre> +>>> 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 +</pre> +</td></tr></table> + +<p> +Inner components of pyasn1 Sequence/Set objects could be accessed using the +following methods: +</p> + +<table bgcolor="lightgray" border=0 width=100%><TR><TD> +<pre> +>>> 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 +>>> +</pre> +</td></tr></table> + +<p> +The Set type share all the properties of Sequence type, and additionally +support by-tag component addressing (as all Set components have distinct +types). +</p> + +<table bgcolor="lightgray" border=0 width=100%><TR><TD> +<pre> +>>> 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 +>>> +</pre> +</td></tr></table> + +<a name="1.3.2"></a> +<h4> +1.3.2 SequenceOf and SetOf types +</h4> + +<p> +Both, SequenceOf and SetOf types resemble an unlimited size list of components. +All the components must be of the same type. +</p> + +<table bgcolor="lightgray" border=0 width=100%><TR><TD> +<pre> +Progression ::= SEQUENCE OF INTEGER + +arithmeticProgression Progression ::= { 1, 3, 5, 7 } +</pre> +</td></tr></table> + +<p> +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. +</p> + +<p> +To specify inner component type, the <b>componentType</b> class attribute +should refer to another pyasn1 type object. +</p> + +<table bgcolor="lightgray" border=0 width=100%><TR><TD> +<pre> +>>> 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) +>>> +</pre> +</td></tr></table> + +<p> +Any scalar or constructed pyasn1 type object can serve as an inner component. +Missing components are prohibited in SequenceOf/SetOf value objects. +</p> + +<a name="1.3.3"></a> +<h4> +1.3.3 Choice type +</h4> + +<p> +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. +</p> + +<table bgcolor="lightgray" border=0 width=100%><TR><TD> +<pre> +CodeOrMessage ::= CHOICE { + code INTEGER, + message OCTET STRING +} +</pre> +</td></tr></table> + +<p> +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. +</p> + +<table bgcolor="lightgray" border=0 width=100%><TR><TD> +<pre> +>>> 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' +>>> +</pre> +</td></tr></table> + +<p> +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): +</p> + +<table bgcolor="lightgray" border=0 width=100%><TR><TD> +<pre> +>>> codeOrMessage.getName() +'message' +>>> codeOrMessage.getComponent() +OctetString(b'my string value') +>>> +</pre> +</td></tr></table> + +<a name="1.3.4"></a> +<h4> +1.3.4 Any type +</h4> + +<p> +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. +</p> + +<table bgcolor="lightgray" border=0 width=100%><TR><TD> +<pre> +Error ::= SEQUENCE { + code INTEGER, + parameter ANY DEFINED BY code +} +</pre> +</td></tr></table> + +<p> +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). +</p> + +<p> +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. +</p> + +<table bgcolor="lightgray" border=0 width=100%><TR><TD> +<pre> +>>> 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' +>>> +</pre> +</td></tr></table> + +<p> +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. +</p> + +<p> +There will be some more talk and code snippets covering Any type in the codecs +chapters that follow. +</p> + +<a name="1.4"></a> +<h4> +1.4 Subtype constraints +</h4> + +<p> +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. +</p> + +<p> +Imposing value constraints on an ASN.1 type can also be seen as creating +a subtype from its base type. +</p> + +<p> +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. +</p> + +<p> +A handful of different flavors of <i>constraints</i> 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. +</p> + +<a name="1.4.1"></a> +<h4> +1.4.1 Single value constraint +</h4> + +<p> +This kind of constraint allows for limiting type to a finite, specified set +of values. +</p> + +<table bgcolor="lightgray" border=0 width=100%><TR><TD> +<pre> +DialButton ::= OCTET STRING ( + "0" | "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9" +) +</pre> +</td></tr></table> + +<p> +Its pyasn1 implementation would look like: +</p> + +<table bgcolor="lightgray" border=0 width=100%><TR><TD> +<pre> +>>> 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 +>>> +</pre> +</td></tr></table> + +<p> +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. +</p> + +<table bgcolor="lightgray" border=0 width=100%><TR><TD> +<pre> +>>> 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 +>>> +</pre> +</td></tr></table> + +<p> +Constrained pyasn1 value object can never hold a violating value. +</p> + +<a name="1.4.2"></a> +<h4> +1.4.2 Value range constraint +</h4> + +<p> +A pair of values, compliant to a type to be constrained, denote low and upper +bounds of allowed range of values of a type. +</p> + +<table bgcolor="lightgray" border=0 width=100%><TR><TD> +<pre> +Teenagers ::= INTEGER (13..19) +</pre> +</td></tr></table> + +<p> +And in pyasn1 terms: +</p> + +<table bgcolor="lightgray" border=0 width=100%><TR><TD> +<pre> +>>> 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 +>>> +</pre> +</td></tr></table> + +<p> +Value range constraint usually applies numeric types. +</p> + +<a name="1.4.3"></a> +<h4> +1.4.3 Size constraint +</h4> + +<p> +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. +</p> + +<table bgcolor="lightgray" border=0 width=100%><TR><TD> +<pre> +TwoBits ::= BIT STRING (SIZE (2)) +</pre> +</td></tr></table> + +<p> +Express the same grammar in pyasn1: +</p> + +<table bgcolor="lightgray" border=0 width=100%><TR><TD> +<pre> +>>> 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) +>>> +</pre> +</td></tr></table> + +<p> +Size constraint can be applied to potentially massive values - bit or octet +strings, SEQUENCE OF/SET OF values. +</p> + +<a name="1.4.4"></a> +<h4> +1.4.4 Alphabet constraint +</h4> + +<p> +The permitted alphabet constraint is similar to Single value constraint +but constraint applies to individual characters of a value. +</p> + +<table bgcolor="lightgray" border=0 width=100%><TR><TD> +<pre> +MorseCode ::= PrintableString (FROM ("."|"-"|" ")) +</pre> +</td></tr></table> + +<p> +And in pyasn1: +</p> + +<table bgcolor="lightgray" border=0 width=100%><TR><TD> +<pre> +>>> 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: "?" +>>> +</pre> +</td></tr></table> + +<p> +Current implementation does not handle ranges of characters in constraint +(FROM "A".."Z" syntax), one has to list the whole set in a range. +</p> + +<a name="1.4.5"></a> +<h4> +1.4.5 Constraint combinations +</h4> + +<p> +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. +</p> + +<p> +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. +</p> + +<p> +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: +</p> + +<table bgcolor="lightgray" border=0 width=100%><TR><TD> +<pre> +PhoneNumber ::= NumericString (FROM ("0".."9")) (SIZE 11) +</pre> +</td></tr></table> + +<p> +Constraint intersection object serves the logic above: +</p> + +<table bgcolor="lightgray" border=0 width=100%><TR><TD> +<pre> +>>> 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" +>>> +</pre> +</td></tr></table> + +<p> +Union of constraints works by making sure that a value is compliant +to any of the constraint in a set. For instance: +</p> + +<table bgcolor="lightgray" border=0 width=100%><TR><TD> +<pre> +CapitalOrSmall ::= IA5String (FROM ('A','B','C') | FROM ('a','b','c')) +</pre> +</td></tr></table> + +<p> +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: +</p> + +<table bgcolor="lightgray" border=0 width=100%><TR><TD> +<pre> +>>> 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" +>>> +</pre> +</td></tr></table> + +<p> +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. +</p> + +<table bgcolor="lightgray" border=0 width=100%><TR><TD> +<pre> +NoZero ::= INTEGER (ALL EXCEPT 0) +</pre> +</td></tr></table> + +<p> +In pyasn1 the above definition would read: +</p> + +<table bgcolor="lightgray" border=0 width=100%><TR><TD> +<pre> +>>> 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 +>>> +</pre> +</td></tr></table> + +<p> +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. +</p> + +<a name="1.5"></a> +<h4> +1.5 Types relationships +</h4> + +<p> +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: +<i>tag set</i> and <i>subtype constraints</i>. One pyasn1 type is considered +to be a derivative of another if their TagSet and Constraint objects are +a derivation of one another. +</p> + +<p> +The following example illustrates the concept (we use the same tagset but +different constraints for simplicity): +</p> + +<table bgcolor="lightgray" border=0 width=100%><TR><TD> +<pre> +>>> 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 +>>> +</pre> +</td></tr></table> + +<p> +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: +<b>isSameTypeWith</b>() and <b>isSuperTypeOf</b>(). 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. +</p> + +<a name="2"></a> +<h3> +2. Codecs +</h3> + +<p> +In ASN.1 context, +<a href=http://en.wikipedia.org/wiki/Codec>codec</a> +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 <i>substrate</i> or <i>essence</i>. +</p> + +<p> +In pyasn1 implementation, substrate takes shape of Python 3 bytes or +Python 2 string objects. +</p> + +<p> +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 +<a href=http://directory.apache.org/subprojects/asn1/>Apache ASN.1 project</a> +. +</p> + +<p> +As of this writing, codecs implemented in pyasn1 are all stateless, mostly +to keep the code simple. +</p> + +<p> +The pyasn1 package currently supports +<a href=http://en.wikipedia.org/wiki/Basic_encoding_rules>BER</a> codec and +its variations -- +<a href=http://en.wikipedia.org/wiki/Canonical_encoding_rules>CER</a> and +<a href=http://en.wikipedia.org/wiki/Distinguished_encoding_rules>DER</a>. +More ASN.1 codecs are planned for implementation in the future. +</p> + +<a name="2.1"></a> +<h4> +2.1 Encoders +</h4> + +<p> +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. +</p> + +<p> +The following code will create a pyasn1 Integer object and serialize it with +BER encoder: +</p> + +<table bgcolor="lightgray" border=0 width=100%><TR><TD> +<pre> +>>> from pyasn1.type import univ +>>> from pyasn1.codec.ber import encoder +>>> encoder.encode(univ.Integer(123456)) +b'\x02\x03\x01\xe2@' +>>> +</pre> +</td></tr></table> + +<p> +BER standard also defines a so-called <i>indefinite length</i> 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. +</p> + +<p> +<i>Constructed encoding</i> 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. +</p> + +<p> +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. +</p> + +<table bgcolor="lightgray" border=0 width=100%><TR><TD> +<pre> +>>> 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' +</pre> +</td></tr></table> + +<p> +The <b>defMode</b> encoder parameter disables definite length encoding mode, +while the optional <b>maxChunkSize</b> parameter specifies desired +substrate chunk size that influences memory requirements at the decoder's end. +</p> + +<p> +To use CER or DER encoders one needs to explicitly import and call them - the +APIs are all compatible. +</p> + +<table bgcolor="lightgray" border=0 width=100%><TR><TD> +<pre> +>>> 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' +>>> +</pre> +</td></tr></table> + +<a name="2.2"></a> +<h4> +2.2 Decoders +</h4> + +<p> +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. +</p> + +<table bgcolor="lightgray" border=0 width=100%><TR><TD> +<pre> +>>> 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'') +>>> +</pre> +</td></tr></table> + +<p> +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. +</p> + +<p> +All pyasn1 decoders can handle both definite and indefinite length +encoding modes automatically, explicit switching into one mode +to another is not required. +</p> + +<table bgcolor="lightgray" border=0 width=100%><TR><TD> +<pre> +>>> 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'') +>>> +</pre> +</td></tr></table> + +<p> +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. +</p> + +<p> +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 <i>guide</i> 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). +</p> + +<table bgcolor="lightgray" border=0 width=100%><TR><TD> +<pre> +>>> from pyasn1.codec.ber import decoder +>>> decoder.decode(b'\x02\x01\x0c', asn1Spec=univ.Integer()) +Integer(12), b'' +>>> +</pre> +</td></tr></table> + +<p> +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: +</p> + +<table bgcolor="lightgray" border=0 width=100%><TR><TD> +<pre> +>>> 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'') +>>> +</pre> +</td></tr></table> + +<p> +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. +</p> + +<p> +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. +</p> + +<table bgcolor="lightgray" border=0 width=100%><TR><TD> +<pre> +>>> 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 +>>> +</pre> +</td></tr></table> + +<p> +Similarily to encoders, to use CER or DER decoders application has to +explicitly import and call them - all APIs are compatible. +</p> + +<table bgcolor="lightgray" border=0 width=100%><TR><TD> +<pre> +>>> 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'') +>>> +</pre> +</td></tr></table> + +<a name="2.2.1"></a> +<h4> +2.2.1 Decoding untagged types +</h4> + +<p> +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. +</p> + +<p> +To explain the issue, we will first prepare a Choice object to deal with: +</p> + +<table bgcolor="lightgray" border=0 width=100%><TR><TD> +<pre> +>>> 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' +>>> +</pre> +</td></tr></table> + +<p> +Let's now encode this Choice object and then decode its substrate +with and without pyasn1 specification object: +</p> + +<table bgcolor="lightgray" border=0 width=100%><TR><TD> +<pre> +>>> 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' +>>> +</pre> +</td></tr></table> + +<p> +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. +</p> + +<p> +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. +</p> + +<p> +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. +</p> + +<p> +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. +</p> + +<p> +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. +</p> + +<table bgcolor="lightgray" border=0 width=100%><TR><TD> +<pre> +>>> 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' +>>> +</pre> +</td></tr></table> + +<p> +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: +</p> + +<table bgcolor="lightgray" border=0 width=100%><TR><TD> +<pre> +>>> 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'') +>>> +</pre> +</td></tr></table> + +<p> +Both CHOICE and ANY types are widely used in practice. Reader is welcome to +take a look at +<a href=http://www.cs.auckland.ac.nz/~pgut001/pubs/x509guide.txt> +ASN.1 specifications of X.509 applications</a> for more information. +</p> + +<a name="2.2.2"></a> +<h4> +2.2.2 Ignoring unknown types +</h4> + +<p> +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. +</p> + +<table bgcolor="lightgray" border=0 width=100%><TR><TD> +<pre> +>>> 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'), '') +>>> +</pre> +</td></tr></table> + +<p> +It's also possible to configure a custom decoder, to handle unknown tags +found in substrate. This can be done by means of <b>defaultRawDecoder</b> +attribute holding a reference to type decoder object. Refer to the source +for API details. +</p> + +<a name="3"></a> +<h3> +3. Feedback and getting help +</h3> + +<p> +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 +<a href=mailto:pyasn1-users@lists.sourceforge.net>pyasn1 mailing list</a> +or better yet fix the issue and send +<a href=mailto:ilya@glas.net>me</a> the patch. +</p> + +<p> +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 +<a href=http://sourceforge.net/projects/pyasn1/files/pyasn1-modules/> +pyasn1-modules package</a>. 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. +</p> + +<p> +And finally, the latest pyasn1 package revision is available for free +download from +<a href=http://sourceforge.net/projects/pyasn1/>project home</a> and +also from the +<a href=http://pypi.python.org/pypi>Python package repository</a>. +</p> + +<hr> + +</td> +</tr> +</table> +</center> +</body> +</html> diff --git a/python/pyasn1/doc/scalar.html b/python/pyasn1/doc/scalar.html new file mode 100644 index 0000000000..e5ccefe60e --- /dev/null +++ b/python/pyasn1/doc/scalar.html @@ -0,0 +1,794 @@ +<html> +<title> +PyASN1 data model and scalar types +</title> +<head> +</head> +<body> +<center> +<table width=60%> +<tr> +<td> + +<h3> +1. Data model for ASN.1 types +</h3> + +<p> +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. +</p> + +<p> +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 <i>pyasn1 type object</i> versus <i>pyasn1 +value object</i>. +</p> + +<p> +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. +</p> + +<table bgcolor="lightgray" border=0 width=100%><TR><TD> +<pre> +>>> 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 +</pre> +</td></tr></table> + +<p> +It would be an error to perform an operation on a pyasn1 type object +as it holds no value to deal with: +</p> + +<table bgcolor="lightgray" border=0 width=100%><TR><TD> +<pre> +>>> from pyasn1.type import univ +>>> asn1IntegerType = univ.Integer() +>>> asn1IntegerType - 2 +... +pyasn1.error.PyAsn1Error: No value for __coerce__() +</pre> +</td></tr></table> + +<a name="1.1"></a> +<h4> +1.1 Scalar types +</h4> + +<p> +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. +</p> + +<a name="1.1.1"></a> +<h4> +1.1.1 Boolean type +</h4> + +<p> +This is the simplest type those values could be either True or False. +</p> + +<table bgcolor="lightgray" border=0 width=100%><TR><TD> +<pre> +;; type specification +FunFactorPresent ::= BOOLEAN + +;; values declaration and assignment +pythonFunFactor FunFactorPresent ::= TRUE +cobolFunFactor FunFactorPresent :: FALSE +</pre> +</td></tr></table> + +<p> +And here's pyasn1 version of it: +</p> + +<table bgcolor="lightgray" border=0 width=100%><TR><TD> +<pre> +>>> 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 +>>> +</pre> +</td></tr></table> + +<a name="1.1.2"></a> +<h4> +1.1.2 Null type +</h4> + +<p> +The NULL type is sometimes used to express the absense of any information. +</p> + +<table bgcolor="lightgray" border=0 width=100%><TR><TD> +<pre> +;; type specification +Vote ::= CHOICE { + agreed BOOLEAN, + skip NULL +} +</td></tr></table> + +;; value declaration and assignment +myVote Vote ::= skip:NULL +</pre> + +<p> +We will explain the CHOICE type later in this paper, meanwhile the NULL +type: +</p> + +<table bgcolor="lightgray" border=0 width=100%><TR><TD> +<pre> +>>> from pyasn1.type import univ +>>> skip = univ.Null() +>>> skip +Null('') +>>> +</pre> +</td></tr></table> + +<a name="1.1.3"></a> +<h4> +1.1.3 Integer type +</h4> + +<p> +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. +</p> + +<table bgcolor="lightgray" border=0 width=100%><TR><TD> +<pre> +;; values specification +age-of-universe INTEGER ::= 13750000000 +mean-martian-surface-temperature INTEGER ::= -63 +</pre> +</td></tr></table> + +<p> +A rather strigntforward mapping into pyasn1: +</p> + +<table bgcolor="lightgray" border=0 width=100%><TR><TD> +<pre> +>>> from pyasn1.type import univ +>>> ageOfUniverse = univ.Integer(13750000000) +>>> ageOfUniverse +Integer(13750000000) +>>> +>>> meanMartianSurfaceTemperature = univ.Integer(-63) +>>> meanMartianSurfaceTemperature +Integer(-63) +>>> +</pre> +</td></tr></table> + +<p> +ASN.1 allows to assign human-friendly names to particular values of +an INTEGER type. +</p> + +<table bgcolor="lightgray" border=0 width=100%><TR><TD> +<pre> +Temperature ::= INTEGER { + freezing(0), + boiling(100) +} +</pre> +</td></tr></table> + +<p> +The Temperature type expressed in pyasn1: +</p> + +<table bgcolor="lightgray" border=0 width=100%><TR><TD> +<pre> +>>> 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 +>>> +</pre> +</td></tr></table> + +<p> +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). +</p> + +<a name="1.1.4"></a> +<h4> +1.1.4 Enumerated type +</h4> + +<p> +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. +</p> + +<table bgcolor="lightgray" border=0 width=100%><TR><TD> +<pre> +error-status ::= ENUMERATED { + no-error(0), + authentication-error(10), + authorization-error(20), + general-failure(51) +} +</pre> +</td></tr></table> + +<p> +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). +</p> + +<table bgcolor="lightgray" border=0 width=100%><TR><TD> +<pre> +>>> 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 +>>> +</pre> +</td></tr></table> + +<p> +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. +</p> + +<a name="1.1.5"></a> +<h4> +1.1.5 Real type +</h4> + +<p> +Values of the Real type are a three-component tuple of mantissa, base and +exponent. All three are integers. +</p> + +<table bgcolor="lightgray" border=0 width=100%><TR><TD> +<pre> +pi ::= REAL { mantissa 314159, base 10, exponent -5 } +</pre> +</td></tr></table> + +<p> +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. + +</p> + +<table bgcolor="lightgray" border=0 width=100%><TR><TD> +<pre> +>>> 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 +>>> +</pre> +</td></tr></table> + +<p> +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). +</p> + +<a name="1.1.6"></a> +<h4> +1.1.6 Bit string type +</h4> + +<p> +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. +</p> + +<table bgcolor="lightgray" border=0 width=100%><TR><TD> +<pre> +public-key BIT STRING ::= '1010111011110001010110101101101 + 1011000101010000010110101100010 + 0110101010000111101010111111110'B + +signature BIT STRING ::= 'AF01330CD932093392100B39FF00DE0'H +</pre> +</td></tr></table> + +<p> +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. +</p> + +<table bgcolor="lightgray" border=0 width=100%><TR><TD> +<pre> +>>> 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") +>>> +</pre> +</td></tr></table> + +<p> +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. +</p> + +<table bgcolor="lightgray" border=0 width=100%><TR><TD> +<pre> +bit-mask BIT STRING ::= { + read-flag(0), + write-flag(2), + run-flag(4) +} +</pre> +</td></tr></table> + +<p> +To express this in pyasn1, we will employ the named values feature (as with +Enumeration type). +</p> + +<table bgcolor="lightgray" border=0 width=100%><TR><TD> +<pre> +>>> 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 +>>> +</pre> +</td></tr></table> + +<p> +The BitString objects mimic the properties of Python tuple type in part +of immutable sequence object protocol support. +</p> + +<a name="1.1.7"></a> +<h4> +1.1.7 OctetString type +</h4> + +<p> +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. +</p> + +<table bgcolor="lightgray" border=0 width=100%><TR><TD> +<pre> +thumbnail OCTET STRING ::= '1000010111101110101111000000111011'B +thumbnail OCTET STRING ::= 'FA9823C43E43510DE3422'H +</pre> +</td></tr></table> + +<p> +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. +</p> + +<table bgcolor="lightgray" border=0 width=100%><TR><TD> +<pre> +welcome-message OCTET STRING ::= "Welcome to ASN.1 wilderness!" +</pre> +</td></tr></table> + +<p> +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. +</p> + +<table bgcolor="lightgray" border=0 width=100%><TR><TD> +<pre> +>>> from pyasn1.type import univ +>>> thumbnail = univ.OctetString( +... binValue='1000010111101110101111000000111011' +... ) +>>> thumbnail +OctetString(hexValue='85eebcec0') +>>> thumbnail = univ.OctetString( +... hexValue='FA9823C43E43510DE3422' +... ) +>>> thumbnail +OctetString(hexValue='fa9823c43e4351de34220') +>>> +</pre> +</td></tr></table> + +<p> +Most frequent usage of the OctetString class is to instantiate it with +a text string. +</p> + +<table bgcolor="lightgray" border=0 width=100%><TR><TD> +<pre> +>>> 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') +>>> +</pre> +</td></tr></table> + +<p> +OctetString objects support the immutable sequence object protocol. +In other words, they behave like Python 3 bytes (or Python 2 strings). +</p> + +<p> +When running pyasn1 on Python 3, it's better to use the bytes objects for +OctetString instantiation, as it's more reliable and efficient. +</p> + +<p> +Additionally, OctetString's can also be instantiated with a sequence of +8-bit integers (ASCII codes). +</p> + +<table bgcolor="lightgray" border=0 width=100%><TR><TD> +<pre> +>>> univ.OctetString((77, 101, 101, 103, 111)) +OctetString(b'Meego') +</pre> +</td></tr></table> + +<p> +It is sometimes convenient to express OctetString instances as 8-bit +characters (Python 3 bytes or Python 2 strings) or 8-bit integers. +</p> + +<table bgcolor="lightgray" border=0 width=100%><TR><TD> +<pre> +>>> octetString = univ.OctetString('ABCDEF') +>>> octetString.asNumbers() +(65, 66, 67, 68, 69, 70) +>>> octetString.asOctets() +b'ABCDEF' +</pre> +</td></tr></table> + +<a name="1.1.8"></a> +<h4> +1.1.8 ObjectIdentifier type +</h4> + +<p> +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. +</p> + +<table bgcolor="lightgray" border=0 width=100%><TR><TD> +<pre> +internet-id OBJECT IDENTIFIER ::= { + iso(1) identified-organization(3) dod(6) internet(1) +} +</pre> +</td></tr></table> + +<p> +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. +</p> + +<table bgcolor="lightgray" border=0 width=100%><TR><TD> +<pre> +>>> 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') +</pre> +</td></tr></table> + +<p> +A more human-friendly "dotted" notation is also supported. +</p> + +<table bgcolor="lightgray" border=0 width=100%><TR><TD> +<pre> +>>> from pyasn1.type import univ +>>> univ.ObjectIdentifier('1.3.6.1') +ObjectIdentifier('1.3.6.1') +</pre> +</td></tr></table> + +<p> +Symbolic names of the arcs of object identifier, sometimes present in +ASN.1 specifications, are not preserved and used in pyasn1 objects. +</p> + +<p> +The ObjectIdentifier objects mimic the properties of Python tuple type in +part of immutable sequence object protocol support. +</p> + +<a name="1.1.9"></a> +<h4> +1.1.9 Character string types +</h4> + +<p> +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. +</p> + +<p> +The two types are specific to ASN.1 are NumericString and PrintableString. +</p> + +<table bgcolor="lightgray" border=0 width=100%><TR><TD> +<pre> +welcome-message ::= PrintableString { + "Welcome to ASN.1 text types" +} + +dial-pad-numbers ::= NumericString { + "0", "1", "2", "3", "4", "5", "6", "7", "8", "9" +} +</pre> +</td></tr></table> + +<p> +Their pyasn1 implementations are: +</p> + +<table bgcolor="lightgray" border=0 width=100%><TR><TD> +<pre> +>>> 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') +>>> +</pre> +</td></tr></table> + +<p> +The following types came to ASN.1 from ISO standards on character sets. +</p> + +<table bgcolor="lightgray" border=0 width=100%><TR><TD> +<pre> +>>> 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') +>>> +</pre> +</td></tr></table> + +<p> +The last three types are relatively recent addition to the family of +character string types: UniversalString, BMPString, UTF8String. +</p> + +<table bgcolor="lightgray" border=0 width=100%><TR><TD> +<pre> +>>> 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) +У попа была собака +>>> +</pre> +</td></tr></table> + +<p> +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. +</p> + +<a name="1.1.10"></a> +<h4> +1.1.10 Useful types +</h4> + +<p> +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. +</p> + +<p> +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. +</p> + +<table bgcolor="lightgray" border=0 width=100%><TR><TD> +<pre> +>>> from pyasn1.type import useful +>>> descrBER = useful.ObjectDescriptor( + "Basic encoding of a single ASN.1 type" +) +>>> +</pre> +</td></tr></table> + +<p> +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. +</p> + +<table bgcolor="lightgray" border=0 width=100%><TR><TD> +<pre> +;; 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" +</pre> +</td></tr></table> + +<table bgcolor="lightgray" border=0 width=100%><TR><TD> +<pre> +>>> from pyasn1.type import useful +>>> moscowTime = useful.GeneralizedTime("20110308120000.0") +>>> utcTime = useful.UTCTime("9803081200Z") +>>> +</pre> +</td></tr></table> + +<p> +Despite their intended use, these types possess no special, time-related, +handling in pyasn1. They are just printable strings. +</p> + +<hr> + +</td> +</tr> +</table> +</center> +</body> +</html> diff --git a/python/pyasn1/doc/tagging.html b/python/pyasn1/doc/tagging.html new file mode 100644 index 0000000000..187f1180d2 --- /dev/null +++ b/python/pyasn1/doc/tagging.html @@ -0,0 +1,233 @@ +<html> +<title> +Tagging in PyASN1 +</title> +<head> +</head> +<body> +<center> +<table width=60%> +<tr> +<td> +<a name="1.2"></a> +<h4> +1.2 Tagging in PyASN1 +</h4> + +<p> +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. +</p> + +<p> +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. +</p> + +<p> +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 <i>tag</i> 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. +</p> + +<p> +For that reason almost every ASN.1 type has a tag (which is actually a +BER type) associated with it by default. +</p> + +<p> +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). +</p> + +<table bgcolor="lightgray" border=0 width=100%><TR><TD> +<pre> +MyIntegerType ::= [12] INTEGER +MyOctetString ::= [APPLICATION 0] OCTET STRING +</pre> +</td></tr></table> + +<p> +In pyasn1, tags are implemented as immutable, tuple-like objects: +</p> + +<table bgcolor="lightgray" border=0 width=100%><TR><TD> +<pre> +>>> 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 +>>> +</pre> +</td></tr></table> + +<p> +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. +</p> + +<p> +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. +</p> + +<p> +When tagging in IMPLICIT mode, the outermost existing tag is dropped and +replaced with a new one. +</p> + +<table bgcolor="lightgray" border=0 width=100%><TR><TD> +<pre> +MyIntegerType ::= [12] IMPLICIT INTEGER +MyOctetString ::= [APPLICATION 0] EXPLICIT OCTET STRING +</pre> +</td></tr></table> + +<p> +To model both modes of tagging, a specialized container TagSet object (holding +zero, one or more Tag objects) is used in pyasn1. +</p> + +<table bgcolor="lightgray" border=0 width=100%><TR><TD> +<pre> +>>> 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)) +>>> +</pre> +</td></tr></table> + +<p> +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. +</p> + +<p> +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. +</p> + +<table bgcolor="lightgray" border=0 width=100%><TR><TD> +<pre> +>>> 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 +>>> +</pre> +</td></tr></table> + +<p> +We will complete this discussion on tagging with a real-world example. The +following ASN.1 tagged type: +</p> + +<table bgcolor="lightgray" border=0 width=100%><TR><TD> +<pre> +MyIntegerType ::= [12] EXPLICIT INTEGER +</pre> +</td></tr></table> + +<p> +could be expressed in pyasn1 like this: +</p> + +<table bgcolor="lightgray" border=0 width=100%><TR><TD> +<pre> +>>> 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)) +>>> +</pre> +</td></tr></table> + +<p> +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. +</p> + +<p> +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. +</p> + +<hr> + +</td> +</tr> +</table> +</center> +</body> +</html> diff --git a/python/pyasn1/pyasn1/__init__.py b/python/pyasn1/pyasn1/__init__.py new file mode 100644 index 0000000000..88aff79c84 --- /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 0000000000..8c3066b2e6 --- /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 0000000000..8c3066b2e6 --- /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 0000000000..be0cf49074 --- /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 "<none>", 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 '<none>' 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 "<none>", 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 "<none>", 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 '<none>')) + 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 0000000000..173949d0b6 --- /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 0000000000..379be19965 --- /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 0000000000..8c3066b2e6 --- /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 0000000000..9fd37c1347 --- /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 0000000000..4c05130af9 --- /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 0000000000..8c3066b2e6 --- /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 0000000000..604abec2bc --- /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 0000000000..4e5faefad4 --- /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 0000000000..8c3066b2e6 --- /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 0000000000..f7f2a29bf5 --- /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 0000000000..c27cb1d446 --- /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 0000000000..716406ff63 --- /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 0000000000..8c3066b2e6 --- /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 0000000000..40873719ca --- /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 '<no value>' + 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 0000000000..ae112f8bd3 --- /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 0000000000..66873937d8 --- /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 0000000000..3e68484472 --- /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 0000000000..48967a5fe2 --- /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 0000000000..d0fea7cc7c --- /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 0000000000..1144907fa1 --- /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 0000000000..7cec3a10e4 --- /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 0000000000..9cd16f8a2a --- /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 + '<empty>' + 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 + '<no-name>' + 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 0000000000..a7139c22ce --- /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 0000000000..861a9f5542 --- /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 0000000000..194f0c8ca8 --- /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 <ilya@glas.net>', + '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 0000000000..8c3066b2e6 --- /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 0000000000..8c3066b2e6 --- /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 0000000000..8c3066b2e6 --- /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 0000000000..796c526b4b --- /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 0000000000..36999e84d4 --- /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 0000000000..bfb3f618c7 --- /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 0000000000..8c3066b2e6 --- /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 0000000000..49d682918b --- /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 0000000000..7195b72e09 --- /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 0000000000..a4f80aa20e --- /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 0000000000..8c3066b2e6 --- /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 0000000000..7af83bf94f --- /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 0000000000..5c9a1948b9 --- /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 0000000000..787da7bec3 --- /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 0000000000..93ff063818 --- /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 0000000000..b4d80e864a --- /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 0000000000..8c3066b2e6 --- /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 0000000000..bc4b48685f --- /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 0000000000..3457c0fc37 --- /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 0000000000..3a4f305994 --- /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 0000000000..78146dca2f --- /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 0000000000..3eedcf26a6 --- /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() |