diff options
Diffstat (limited to 'python/pyasn1/doc/pyasn1-tutorial.html')
-rw-r--r-- | python/pyasn1/doc/pyasn1-tutorial.html | 2405 |
1 files changed, 2405 insertions, 0 deletions
diff --git a/python/pyasn1/doc/pyasn1-tutorial.html b/python/pyasn1/doc/pyasn1-tutorial.html new file mode 100644 index 000000000..2eb82f1e9 --- /dev/null +++ b/python/pyasn1/doc/pyasn1-tutorial.html @@ -0,0 +1,2405 @@ +<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> |