summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--README.md36
-rw-r--r--pyasn1/codec/python/decoder.py7
-rw-r--r--pyasn1/codec/python/encoder.py26
-rw-r--r--test/codec/python/__init__.py1
-rw-r--r--test/codec/python/suite.py33
-rw-r--r--test/codec/python/test_decoder.py116
-rw-r--r--test/codec/python/test_encoder.py131
-rw-r--r--test/codec/suite.py5
8 files changed, 337 insertions, 18 deletions
diff --git a/README.md b/README.md
index 9589d29..17b62bc 100644
--- a/README.md
+++ b/README.md
@@ -17,7 +17,7 @@ Features
--------
* Generic implementation of ASN.1 types (X.208)
-* Fully standard compliant BER/CER/DER codecs
+* Standards compliant BER/CER/DER codecs
* 100% Python, works with Python 2.4 up to Python 3.6
* MT-safe
* Contributed ASN.1 compiler [Asn1ate](https://github.com/kimgr/asn1ate)
@@ -100,6 +100,7 @@ Part of the power of ASN.1 comes from its serialization features. You
can serialize your data structure and send it over the network.
```python
+>>> from pyasn1.codec.der.encoder import encode
>>> substrate = encode(record)
>>> hexdump(substrate)
00000: 30 07 02 01 7B 80 02 01 41
@@ -110,6 +111,7 @@ network or read from a file, into a Python object which you can
introspect, modify, encode and send back.
```python
+>>> from pyasn1.codec.der.decoder import decode
>>> received_record, rest_of_substrate = decode(substrate, asn1Spec=Record())
>>>
>>> for field in received_record:
@@ -126,11 +128,35 @@ True
00000: 30 06 02 01 7B 80 01 7B
```
+The pyasn1 classes struggle to emulate their Python prototypes (e.g. int,
+list, dict etc.). But ASN.1 types exhibit more complicated behaviour.
+To make life easier for a Pythonista, they can turn their pyasn1
+classes into Python built-ins:
+
+```python
+>>> from pyasn1.codec.python.encoder import encode
+>>> encode(record)
+{'id': 123, 'room': 321, 'house': 0}
+```
+
+Or vice-versa -- you can initialize an ASN.1 structure from a tree of
+Python objects:
+
+```python
+>>> from pyasn1.codec.python.decoder import decode
+>>> record = decode({'id': 123, 'room': 321, 'house': 0}, asn1Spec=Record())
+>>> print(record.prettyPrint())
+Record:
+ id=123
+ room=321
+>>>
+```
+
With ASN.1 design, serialization codecs are decoupled from data objects,
so you could turn every single ASN.1 object into many different
-serialized forms. As of this moment, pyasn1 supports BER, DER and CER
-formats. The extremely compact PER encoding is expected to be introduced
-in the upcoming pyasn1 release.
+serialized forms. As of this moment, pyasn1 supports BER, DER, CER and
+Python built-ins codecs. The extremely compact PER encoding is expected
+to be introduced in the upcoming pyasn1 release.
More information on pyasn1 APIs can be found in the
[documentation](http://pyasn1.sourceforge.net),
@@ -149,7 +175,7 @@ You could `pip install pyasn1` or download it from [PyPI](https://pypi.python.or
If something does not work as expected,
[open an issue](https://github.com/etingof/pyasn1/issues) at GitHub or
-post your question [to Stack Overflow](http://stackoverflow.com/questions/ask)
+post your question [on Stack Overflow](http://stackoverflow.com/questions/ask)
or try browsing pyasn1
[mailing list archives](https://sourceforge.net/p/pyasn1/mailman/pyasn1-users/).
diff --git a/pyasn1/codec/python/decoder.py b/pyasn1/codec/python/decoder.py
index 53f47cf..aef79b4 100644
--- a/pyasn1/codec/python/decoder.py
+++ b/pyasn1/codec/python/decoder.py
@@ -105,7 +105,9 @@ class Decoder(object):
def __call__(self, substrate, asn1Spec):
if debug.logger & debug.flagDecoder:
+ debug.scope.push(type(substrate).__name__)
debug.logger('decoder called at scope %s, working with type %s' % (debug.scope, type(substrate).__name__))
+
if asn1Spec is None or not isinstance(asn1Spec, base.Asn1Item):
raise error.PyAsn1Error('asn1Spec is not valid (should be an instance of an ASN.1 Item, not %s)' % asn1Spec.__class__.__name__)
@@ -117,13 +119,12 @@ class Decoder(object):
raise error.PyAsn1Error('Unknown ASN.1 tag %s' % asn1Spec.tagSet)
if debug.logger & debug.flagDecoder:
- debug.logger('calling decoder %s on Python type %s' % (valueDecoder, type(substrate).__name__))
- debug.scope.push(type(substrate).__name__)
+ debug.logger('calling decoder %s on Python type %s <%s>' % (type(valueDecoder).__name__, type(substrate).__name__, repr(substrate)))
value = valueDecoder(substrate, asn1Spec, self)
if debug.logger & debug.flagDecoder:
- debug.logger('decoder %s produced ASN.1 type %s' % (valueDecoder, type(value).__name__))
+ debug.logger('decoder %s produced ASN.1 type %s <%s>' % (type(valueDecoder).__name__, type(value).__name__, repr(value)))
debug.scope.pop()
return value
diff --git a/pyasn1/codec/python/encoder.py b/pyasn1/codec/python/encoder.py
index 9113271..887df78 100644
--- a/pyasn1/codec/python/encoder.py
+++ b/pyasn1/codec/python/encoder.py
@@ -8,7 +8,7 @@ try:
from collections import OrderedDict
except ImportError:
- from ordereddict import OrderedDict
+ OrderedDict = dict
from pyasn1.type import base, univ, char, useful
from pyasn1 import debug, error
@@ -155,8 +155,11 @@ class Encoder(object):
def __call__(self, value):
if not isinstance(value, base.Asn1Item):
raise error.PyAsn1Error('value is not valid (should be an instance of an ASN.1 Item)')
- debug.logger & debug.flagEncoder and debug.logger(
- 'encoder called for type %s, value:\n%s' % (value.prettyPrintType(), value.prettyPrint()))
+
+ if debug.logger & debug.flagEncoder:
+ debug.scope.push(type(value).__name__)
+ debug.logger('encoder called for type %s <%s>' % (type(value).__name__, value.prettyPrint()))
+
tagSet = value.getTagSet()
if len(tagSet) > 1:
concreteEncoder = explicitlyTaggedItemEncoder
@@ -171,11 +174,15 @@ class Encoder(object):
concreteEncoder = self.__tagMap[tagSet]
else:
raise error.PyAsn1Error('No encoder for %s' % (value,))
- debug.logger & debug.flagEncoder and debug.logger(
- 'using value codec %s chosen by %s' % (concreteEncoder.__class__.__name__, tagSet))
+
+ debug.logger & debug.flagEncoder and debug.logger('using value codec %s chosen by %s' % (type(concreteEncoder).__name__, tagSet))
+
substrate = concreteEncoder.encode(self, value)
- debug.logger & debug.flagEncoder and debug.logger(
- 'encoder produced: %s\nencoder completed' % repr(substrate))
+
+ if debug.logger & debug.flagEncoder:
+ debug.logger('encoder %s produced: %s' % (type(concreteEncoder).__name__, repr(substrate)))
+ debug.scope.pop()
+
return substrate
@@ -185,8 +192,9 @@ class Encoder(object):
#: walks all its components recursively and produces a Python built-in type or a tree
#: of those.
#:
-#: One important exception is that instead of :py:class:`dict`, the :py:class:`OrderedDict`
-#: is produced to preserve ordering of the components in ASN.1 SEQUENCE.
+#: One exception is that instead of :py:class:`dict`, the :py:class:`OrderedDict`
+#: can be produced (whenever available) to preserve ordering of the components
+#: in ASN.1 SEQUENCE.
#:
#: Parameters
#: ----------
diff --git a/test/codec/python/__init__.py b/test/codec/python/__init__.py
new file mode 100644
index 0000000..8c3066b
--- /dev/null
+++ b/test/codec/python/__init__.py
@@ -0,0 +1 @@
+# This file is necessary to make this directory a package.
diff --git a/test/codec/python/suite.py b/test/codec/python/suite.py
new file mode 100644
index 0000000..57ce67d
--- /dev/null
+++ b/test/codec/python/suite.py
@@ -0,0 +1,33 @@
+#
+# This file is part of pyasn1 software.
+#
+# Copyright (c) 2005-2017, Ilya Etingof <etingof@gmail.com>
+# License: http://pyasn1.sf.net/license.html
+#
+from sys import path, version_info
+from os.path import sep
+
+path.insert(1, path[0] + sep + 'python')
+import test_encoder, test_decoder
+
+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/test/codec/python/test_decoder.py b/test/codec/python/test_decoder.py
new file mode 100644
index 0000000..af79bd3
--- /dev/null
+++ b/test/codec/python/test_decoder.py
@@ -0,0 +1,116 @@
+#
+# This file is part of pyasn1 software.
+#
+# Copyright (c) 2005-2017, Ilya Etingof <etingof@gmail.com>
+# License: http://pyasn1.sf.net/license.html
+#
+from pyasn1.type import tag, namedtype, univ, char
+from pyasn1.codec.python 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 BadAsn1SpecTestCase(unittest.TestCase):
+ def testBadSpec(self):
+ try:
+ decoder.decode('', asn1Spec='not an Asn1Item')
+ except PyAsn1Error:
+ pass
+ else:
+ assert 0, 'Invalid asn1Spec accepted'
+
+
+class IntegerDecoderTestCase(unittest.TestCase):
+ def testPosInt(self):
+ assert decoder.decode(12, asn1Spec=univ.Integer()) == univ.Integer(12)
+
+ def testNegInt(self):
+ assert decoder.decode(-12, asn1Spec=univ.Integer()) == univ.Integer(-12)
+
+
+class BooleanDecoderTestCase(unittest.TestCase):
+ def testTrue(self):
+ assert decoder.decode(True, asn1Spec=univ.Boolean()) == univ.Boolean(True)
+
+ def testTrueNeg(self):
+ assert decoder.decode(False, asn1Spec=univ.Boolean()) == univ.Boolean(False)
+
+
+class BitStringDecoderTestCase(unittest.TestCase):
+ def testSimple(self):
+ assert decoder.decode('11111111', asn1Spec=univ.BitString()) == univ.BitString(hexValue='ff')
+
+
+class OctetStringDecoderTestCase(unittest.TestCase):
+ def testSimple(self):
+ assert decoder.decode('Quick brown fox', asn1Spec=univ.OctetString()) == univ.OctetString('Quick brown fox')
+
+
+class NullDecoderTestCase(unittest.TestCase):
+ def testNull(self):
+ assert decoder.decode(None, asn1Spec=univ.Null()) == univ.Null()
+
+
+class ObjectIdentifierDecoderTestCase(unittest.TestCase):
+ def testOne(self):
+ assert decoder.decode('1.3.6.11', asn1Spec=univ.ObjectIdentifier()) == univ.ObjectIdentifier('1.3.6.11')
+
+
+class RealDecoderTestCase(unittest.TestCase):
+ def testSimple(self):
+ assert decoder.decode(1.33, asn1Spec=univ.Real()) == univ.Real(1.33)
+
+
+class SequenceDecoderTestCase(unittest.TestCase):
+ def setUp(self):
+ self.s = univ.Sequence(
+ componentType=namedtype.NamedTypes(
+ namedtype.NamedType('place-holder', univ.Null()),
+ namedtype.NamedType('first-name', univ.OctetString()),
+ namedtype.NamedType('age', univ.Integer(33))
+ )
+ )
+
+ def testSimple(self):
+ s = self.s.clone()
+ s[0] = univ.Null()
+ s[1] = univ.OctetString('xx')
+ s[2] = univ.Integer(33)
+ assert decoder.decode({'place-holder': None, 'first-name': 'xx', 'age': 33}, asn1Spec=self.s) == s
+
+
+class ChoiceDecoderTestCase(unittest.TestCase):
+ def setUp(self):
+ self.s = univ.Choice(
+ componentType=namedtype.NamedTypes(
+ namedtype.NamedType('place-holder', univ.Null()),
+ namedtype.NamedType('first-name', univ.OctetString()),
+ namedtype.NamedType('age', univ.Integer(33))
+ )
+ )
+
+ def testSimple(self):
+ s = self.s.clone()
+ s[1] = univ.OctetString('xx')
+ assert decoder.decode({'first-name': 'xx'}, asn1Spec=self.s) == s
+
+
+class AnyDecoderTestCase(unittest.TestCase):
+ def setUp(self):
+ self.s = univ.Any()
+
+ def testSimple(self):
+ assert decoder.decode('fox', asn1Spec=univ.Any()) == univ.Any('fox')
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/test/codec/python/test_encoder.py b/test/codec/python/test_encoder.py
new file mode 100644
index 0000000..202248c
--- /dev/null
+++ b/test/codec/python/test_encoder.py
@@ -0,0 +1,131 @@
+#
+# This file is part of pyasn1 software.
+#
+# Copyright (c) 2005-2017, Ilya Etingof <etingof@gmail.com>
+# License: http://pyasn1.sf.net/license.html
+#
+from pyasn1.type import tag, namedtype, univ, char
+from pyasn1.codec.python import encoder
+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 BadAsn1SpecTestCase(unittest.TestCase):
+ def testBadValueType(self):
+ try:
+ encoder.encode('not an Asn1Item')
+ except PyAsn1Error:
+ pass
+ else:
+ assert 0, 'Invalid value type accepted'
+
+
+class IntegerEncoderTestCase(unittest.TestCase):
+ def testPosInt(self):
+ assert encoder.encode(univ.Integer(12)) == 12
+
+ def testNegInt(self):
+ assert encoder.encode(univ.Integer(-12)) == -12
+
+
+class BooleanEncoderTestCase(unittest.TestCase):
+ def testTrue(self):
+ assert encoder.encode(univ.Boolean(1)) is True
+
+ def testFalse(self):
+ assert encoder.encode(univ.Boolean(0)) is False
+
+
+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 testValue(self):
+ assert encoder.encode(self.b) == '101010011000101'
+
+
+class OctetStringEncoderTestCase(unittest.TestCase):
+ def setUp(self):
+ self.o = univ.OctetString('Quick brown fox')
+
+ def testValue(self):
+ assert encoder.encode(self.o) == 'Quick brown fox'
+
+
+class NullEncoderTestCase(unittest.TestCase):
+ def testNull(self):
+ assert encoder.encode(univ.Null('')) is None
+
+
+class ObjectIdentifierEncoderTestCase(unittest.TestCase):
+ def testOne(self):
+ assert encoder.encode(univ.ObjectIdentifier((1, 3, 6, 0, 12345))) == '1.3.6.0.12345'
+
+
+class RealEncoderTestCase(unittest.TestCase):
+ def testChar(self):
+ assert encoder.encode(univ.Real((123, 10, 11))) == 1.23e+13
+
+ def testPlusInf(self):
+ assert encoder.encode(univ.Real('inf')) == float('inf')
+
+ def testMinusInf(self):
+ assert encoder.encode(univ.Real('-inf')) == float('-inf')
+
+
+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 testSimple(self):
+ s = self.s.clone()
+ s[0] = univ.Null('')
+ s[1] = 'abc'
+ s[2] = 123
+ assert encoder.encode(s) == {'place-holder': None, 'first-name': 'abc', 'age': 123}
+
+
+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 False, 'encoded unset choice'
+
+ def testFilled(self):
+ self.s.setComponentByPosition(0, univ.Null(''))
+ assert encoder.encode(self.s) == {'place-holder': None}
+
+
+class AnyEncoderTestCase(unittest.TestCase):
+ def setUp(self):
+ self.s = univ.Any(encoder.encode(univ.OctetString('fox')))
+
+ def testSimple(self):
+ assert encoder.encode(self.s) == 'fox'
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/test/codec/suite.py b/test/codec/suite.py
index fcdf328..344e430 100644
--- a/test/codec/suite.py
+++ b/test/codec/suite.py
@@ -16,6 +16,9 @@ import cer.suite
path.insert(1, path[0] + sep + 'codec' + sep + 'der')
import der.suite
+path.insert(1, path[0] + sep + 'codec' + sep + 'python')
+import python.suite
+
if version_info[0:2] < (2, 7) or \
version_info[0:2] in ((3, 0), (3, 1)):
try:
@@ -26,7 +29,7 @@ else:
import unittest
suite = unittest.TestSuite()
-for m in (ber.suite, cer.suite, der.suite):
+for m in (ber.suite, cer.suite, der.suite, python.suite):
suite.addTest(getattr(m, 'suite'))