]> Cypherpunks.ru repositories - pyderasn.git/blobdiff - pyderasn.py
Various additions to documentation
[pyderasn.git] / pyderasn.py
index b1f029cdd89e00ec6d99f5400e9bb79ba3f7a2f3..4ae1eb0814182f065fa045d1c89b7a9f6c11e9ba 100755 (executable)
@@ -298,6 +298,17 @@ SetOf
 _____
 .. autoclass:: pyderasn.SetOf
    :members: __init__
 _____
 .. autoclass:: pyderasn.SetOf
    :members: __init__
+
+Various
+-------
+
+.. autofunction:: pyderasn.hexenc
+.. autofunction:: pyderasn.hexdec
+.. autofunction:: pyderasn.tag_encode
+.. autofunction:: pyderasn.tag_decode
+.. autofunction:: pyderasn.tag_ctxp
+.. autofunction:: pyderasn.tag_ctxc
+.. autoclass:: pyderasn.Obj
 """
 
 from codecs import getdecoder
 """
 
 from codecs import getdecoder
@@ -392,6 +403,16 @@ TagClassReprs = {
 
 class DecodeError(Exception):
     def __init__(self, msg="", klass=None, decode_path=(), offset=0):
 
 class DecodeError(Exception):
     def __init__(self, msg="", klass=None, decode_path=(), offset=0):
+        """
+        :param str msg: reason of decode failing
+        :param klass: optional exact DecodeError inherited class (like
+                      :py:exc:`NotEnoughData`, :py:exc:`TagMismatch`,
+                      :py:exc:`InvalidLength`)
+        :param decode_path: tuple of strings. It contains human
+                            readable names of the fields through which
+                            decoding process has passed
+        :param int offset: binary offset where failure happened
+        """
         super(DecodeError, self).__init__()
         self.msg = msg
         self.klass = klass
         super(DecodeError, self).__init__()
         self.msg = msg
         self.klass = klass
@@ -496,10 +517,14 @@ _hexencoder = getencoder("hex")
 
 
 def hexdec(data):
 
 
 def hexdec(data):
+    """Binary data to hexadecimal string convert
+    """
     return _hexdecoder(data)[0]
 
 
 def hexenc(data):
     return _hexdecoder(data)[0]
 
 
 def hexenc(data):
+    """Hexadecimal string to binary data convert
+    """
     return _hexencoder(data)[0].decode("ascii")
 
 
     return _hexencoder(data)[0].decode("ascii")
 
 
@@ -523,6 +548,16 @@ def zero_ended_encode(num):
 
 
 def tag_encode(num, klass=TagClassUniversal, form=TagFormPrimitive):
 
 
 def tag_encode(num, klass=TagClassUniversal, form=TagFormPrimitive):
+    """Encode tag to binary form
+
+    :param int num: tag's number
+    :param int klass: tag's class (:py:data:`pyderasn.TagClassUniversal`,
+                      :py:data:`pyderasn.TagClassContext`,
+                      :py:data:`pyderasn.TagClassApplication`,
+                      :py:data:`pyderasn.TagClassPrivate`)
+    :param int form: tag's form (:py:data:`pyderasn.TagFormPrimitive`,
+                     :py:data:`pyderasn.TagFormConstructed`)
+    """
     if num < 31:
         # [XX|X|.....]
         return int2byte(klass | form | num)
     if num < 31:
         # [XX|X|.....]
         return int2byte(klass | form | num)
@@ -531,8 +566,14 @@ def tag_encode(num, klass=TagClassUniversal, form=TagFormPrimitive):
 
 
 def tag_decode(tag):
 
 
 def tag_decode(tag):
-    """
-    assume that data is validated
+    """Decode tag from binary form
+
+    .. warning::
+
+       No validation is performed, assuming that it has already passed.
+
+    It returns tuple with three integers, as
+    :py:func:`pyderasn.tag_encode` accepts.
     """
     first_octet = byte2int(tag)
     klass = first_octet & 0xC0
     """
     first_octet = byte2int(tag)
     klass = first_octet & 0xC0
@@ -547,10 +588,14 @@ def tag_decode(tag):
 
 
 def tag_ctxp(num):
 
 
 def tag_ctxp(num):
+    """Create CONTEXT PRIMITIVE tag
+    """
     return tag_encode(num=num, klass=TagClassContext, form=TagFormPrimitive)
 
 
 def tag_ctxc(num):
     return tag_encode(num=num, klass=TagClassContext, form=TagFormPrimitive)
 
 
 def tag_ctxc(num):
+    """Create CONTEXT CONSTRUCTED tag
+    """
     return tag_encode(num=num, klass=TagClassContext, form=TagFormConstructed)
 
 
     return tag_encode(num=num, klass=TagClassContext, form=TagFormConstructed)
 
 
@@ -612,6 +657,9 @@ def len_decode(data):
 
 class Obj(object):
     """Common ASN.1 object class
 
 class Obj(object):
     """Common ASN.1 object class
+
+    All ASN.1 types are inherited from it. It has metaclass that
+    automatically adds ``__slots__`` to all inherited classes.
     """
     __slots__ = (
         "tag",
     """
     __slots__ = (
         "tag",
@@ -649,6 +697,8 @@ class Obj(object):
 
     @property
     def ready(self):  # pragma: no cover
 
     @property
     def ready(self):  # pragma: no cover
+        """Is object ready to be encoded?
+        """
         raise NotImplementedError()
 
     def _assert_ready(self):
         raise NotImplementedError()
 
     def _assert_ready(self):
@@ -657,9 +707,13 @@ class Obj(object):
 
     @property
     def decoded(self):
 
     @property
     def decoded(self):
+        """Is object decoded?
+        """
         return self.llen > 0
 
     def copy(self):  # pragma: no cover
         return self.llen > 0
 
     def copy(self):  # pragma: no cover
+        """Make a copy of object, safe to be mutated
+        """
         raise NotImplementedError()
 
     @property
         raise NotImplementedError()
 
     @property
@@ -686,6 +740,14 @@ class Obj(object):
         return b"".join((self._expl, len_encode(len(raw)), raw))
 
     def decode(self, data, offset=0, leavemm=False, decode_path=()):
         return b"".join((self._expl, len_encode(len(raw)), raw))
 
     def decode(self, data, offset=0, leavemm=False, decode_path=()):
+        """Decode the data
+
+        :param data: either binary or memoryview
+        :param int offset: initial data's offset
+        :param bool leavemm: do we need to leave memoryview of remaining
+                    data as is, or convert it to bytes otherwise
+        :returns: (Obj, remaining data)
+        """
         tlv = memoryview(data)
         if self._expl is None:
             obj, tail = self._decode(
         tlv = memoryview(data)
         if self._expl is None:
             obj, tail = self._decode(
@@ -888,6 +950,15 @@ def pp_console_blob(pp):
 
 
 def pprint(obj, oids=None, big_blobs=False):
 
 
 def pprint(obj, oids=None, big_blobs=False):
+    """Pretty print object
+
+    :param Obj obj: object you want to pretty print
+    :param oids: ``OID <-> humand readable string`` dictionary. When OID
+                 from it is met, then its humand readable form is printed
+    :param big_blobs: if large binary objects are met (like OctetString
+                      values), do we need to print them too, on separate
+                      lines
+    """
     def _pprint_pps(pps):
         for pp in pps:
             if hasattr(pp, "_fields"):
     def _pprint_pps(pps):
         for pp in pps:
             if hasattr(pp, "_fields"):
@@ -921,10 +992,6 @@ class Boolean(Obj):
     True
     >>> bool(b)
     True
     True
     >>> bool(b)
     True
-    >>> Boolean(optional=True)
-    BOOLEAN OPTIONAL
-    >>> Boolean(impl=tag_ctxp(1), default=False)
-    [1] BOOLEAN False OPTIONAL DEFAULT
     """
     __slots__ = ()
     tag_default = tag_encode(1)
     """
     __slots__ = ()
     tag_default = tag_encode(1)
@@ -1119,10 +1186,6 @@ class Integer(Obj):
     True
     >>> int(b)
     -123
     True
     >>> int(b)
     -123
-    >>> Integer(optional=True)
-    INTEGER OPTIONAL
-    >>> Integer(impl=tag_ctxp(1), default=123)
-    [1] INTEGER 123 OPTIONAL DEFAULT
 
     >>> Integer(2, bounds=(1, 3))
     INTEGER 2
 
     >>> Integer(2, bounds=(1, 3))
     INTEGER 2
@@ -3275,6 +3338,86 @@ class Any(Obj):
 ########################################################################
 
 class Sequence(Obj):
 ########################################################################
 
 class Sequence(Obj):
+    """``SEQUENCE`` structure type
+
+    You have to make specification of sequence::
+
+        class Extension(Sequence):
+            __slots__ = ()
+            schema = (
+                ("extnID", ObjectIdentifier()),
+                ("critical", Boolean(default=False)),
+                ("extnValue", OctetString()),
+            )
+
+    Then, you can work with it as with dictionary.
+
+    >>> ext = Extension()
+    >>> Extension().specs
+    OrderedDict([
+        ('extnID', OBJECT IDENTIFIER),
+        ('critical', BOOLEAN False OPTIONAL DEFAULT),
+        ('extnValue', OCTET STRING),
+    ])
+    >>> ext["extnID"] = "1.2.3"
+    Traceback (most recent call last):
+    pyderasn.InvalidValueType: invalid value type, expected: <class 'pyderasn.ObjectIdentifier'>
+    >>> ext["extnID"] = ObjectIdentifier("1.2.3")
+
+    You can know if sequence is ready to be encoded:
+
+    >>> ext.ready
+    False
+    >>> ext.encode()
+    Traceback (most recent call last):
+    pyderasn.ObjNotReady: object is not ready: extnValue
+    >>> ext["extnValue"] = OctetString(b"foobar")
+    >>> ext.ready
+    True
+
+    Value you want to assign, must have the same **type** as in
+    corresponding specification, but it can have different tags,
+    optional/default attributes -- they will be taken from specification
+    automatically::
+
+        class TBSCertificate(Sequence):
+            schema = (
+                ("version", Version(expl=tag_ctxc(0), default="v1")),
+            [...]
+
+    >>> tbs = TBSCertificate()
+    >>> tbs["version"] = Version("v2") # no need to explicitly add ``expl``
+
+    You can know if value exists/set in the sequence and take its value:
+
+    >>> "extnID" in ext, "extnValue" in ext, "critical" in ext
+    (True, True, False)
+    >>> ext["extnID"]
+    OBJECT IDENTIFIER 1.2.3
+
+    But pay attention that if value has default, then it won't be (not
+    in) in the sequence (because ``DEFAULT`` must not be encoded in
+    DER), but you can read its value:
+
+    >>> "critical" in ext, ext["critical"]
+    (False, BOOLEAN False)
+    >>> ext["critical"] = Boolean(True)
+    >>> "critical" in ext, ext["critical"]
+    (True, BOOLEAN True)
+
+    All defaulted values are always optional.
+
+    .. warning::
+
+       When decoded DER contains defaulted value inside, then
+       technically this is not valid DER encoding. But we allow
+       and pass it. Of course reencoding of that kind of DER will
+       result in different binary representation (validly without
+       defaulted value inside).
+
+    Two sequences are equal if they have equal specification (schema),
+    implicit/explicit tagging and the same values.
+    """
     __slots__ = ("specs",)
     tag_default = tag_encode(form=TagFormConstructed, num=16)
     asn1_type_name = "SEQUENCE"
     __slots__ = ("specs",)
     tag_default = tag_encode(form=TagFormConstructed, num=16)
     asn1_type_name = "SEQUENCE"
@@ -3518,6 +3661,10 @@ class Sequence(Obj):
 
 
 class Set(Sequence):
 
 
 class Set(Sequence):
+    """``SET`` structure type
+
+    Its usage is identical to :py:class:`pyderasn.Sequence`.
+    """
     __slots__ = ()
     tag_default = tag_encode(form=TagFormConstructed, num=17)
     asn1_type_name = "SET"
     __slots__ = ()
     tag_default = tag_encode(form=TagFormConstructed, num=17)
     asn1_type_name = "SET"
@@ -3601,6 +3748,35 @@ class Set(Sequence):
 
 
 class SequenceOf(Obj):
 
 
 class SequenceOf(Obj):
+    """``SEQUENCE OF`` sequence type
+
+    For that kind of type you must specify the object it will carry on
+    (bounds are for example here, not required)::
+
+        class Ints(SequenceOf):
+            schema = Integer()
+            bounds = (0, 2)
+
+    >>> ints = Ints()
+    >>> ints.append(Integer(123))
+    >>> ints.append(Integer(234))
+    >>> ints
+    Ints SEQUENCE OF[INTEGER 123, INTEGER 234]
+    >>> [int(i) for i in ints]
+    [123, 234]
+    >>> ints.append(Integer(345))
+    Traceback (most recent call last):
+    pyderasn.BoundsError: unsatisfied bounds: 0 <= 3 <= 2
+    >>> ints[1]
+    INTEGER 234
+    >>> ints[1] = Integer(345)
+    >>> ints
+    Ints SEQUENCE OF[INTEGER 123, INTEGER 345]
+
+    Also you can initialize sequence with preinitialized values:
+
+    >>> ints = Ints([Integer(123), Integer(234)])
+    """
     __slots__ = ("spec", "_bound_min", "_bound_max")
     tag_default = tag_encode(form=TagFormConstructed, num=16)
     asn1_type_name = "SEQUENCE OF"
     __slots__ = ("spec", "_bound_min", "_bound_max")
     tag_default = tag_encode(form=TagFormConstructed, num=16)
     asn1_type_name = "SEQUENCE OF"
@@ -3841,6 +4017,10 @@ class SequenceOf(Obj):
 
 
 class SetOf(SequenceOf):
 
 
 class SetOf(SequenceOf):
+    """``SET OF`` sequence type
+
+    Its usage is identical to :py:class:`pyderasn.SequenceOf`.
+    """
     __slots__ = ()
     tag_default = tag_encode(form=TagFormConstructed, num=17)
     asn1_type_name = "SET OF"
     __slots__ = ()
     tag_default = tag_encode(form=TagFormConstructed, num=17)
     asn1_type_name = "SET OF"
@@ -3884,7 +4064,7 @@ def main():  # pragma: no cover
     parser.add_argument(
         "DERFile",
         type=argparse.FileType("rb"),
     parser.add_argument(
         "DERFile",
         type=argparse.FileType("rb"),
-        help="Python path to schema definition to use",
+        help="Path to DER file you want to decode",
     )
     args = parser.parse_args()
     der = memoryview(args.DERFile.read())
     )
     args = parser.parse_args()
     der = memoryview(args.DERFile.read())