]> Cypherpunks.ru repositories - pyderasn.git/blobdiff - pyderasn.py
Valid DER SET ordering
[pyderasn.git] / pyderasn.py
index 58583b18d0083bb56df58116e8f588533e9c3186..f1be6473130142969088c58038efc402a5995a10 100755 (executable)
@@ -352,7 +352,6 @@ Let's parse that output, human::
  (and its derivatives), ``SET``, ``SET OF``, ``UTCTime``, ``GeneralizedTime``
  could be BERed.
 
-
 .. _definedby:
 
 DEFINED BY
@@ -550,12 +549,12 @@ _______
 Integer
 _______
 .. autoclass:: pyderasn.Integer
-   :members: __init__
+   :members: __init__, named
 
 BitString
 _________
 .. autoclass:: pyderasn.BitString
-   :members: __init__
+   :members: __init__, bit_len, named
 
 OctetString
 ___________
@@ -587,7 +586,7 @@ _____________
 PrintableString
 _______________
 .. autoclass:: pyderasn.PrintableString
-   :members: __init__
+   :members: __init__, allow_asterisk, allow_ampersand
 
 UTCTime
 _______
@@ -597,6 +596,7 @@ _______
 GeneralizedTime
 _______________
 .. autoclass:: pyderasn.GeneralizedTime
+   :members: __init__, todatetime
 
 Special types
 -------------
@@ -604,7 +604,7 @@ Special types
 Choice
 ______
 .. autoclass:: pyderasn.Choice
-   :members: __init__
+   :members: __init__, choice, value
 
 PrimitiveTypes
 ______________
@@ -780,6 +780,7 @@ from copy import copy
 from datetime import datetime
 from datetime import timedelta
 from math import ceil
+from operator import attrgetter
 from string import ascii_letters
 from string import digits
 from sys import version_info
@@ -807,7 +808,7 @@ except ImportError:  # pragma: no cover
     def colored(what, *args, **kwargs):
         return what
 
-__version__ = "6.3"
+__version__ = "7.0"
 
 __all__ = (
     "Any",
@@ -1189,6 +1190,7 @@ class AutoAddSlots(type):
 BasicState = namedtuple("BasicState", (
     "version",
     "tag",
+    "tag_order",
     "expl",
     "default",
     "optional",
@@ -1210,6 +1212,7 @@ class Obj(object):
     """
     __slots__ = (
         "tag",
+        "_tag_order",
         "_value",
         "_expl",
         "default",
@@ -1234,6 +1237,13 @@ class Obj(object):
         self._expl = getattr(self, "expl", None) if expl is None else expl
         if self.tag != self.tag_default and self._expl is not None:
             raise ValueError("implicit and explicit tags can not be set simultaneously")
+        if self.tag is None:
+            self._tag_order = None
+        else:
+            tag_class, _, tag_num = tag_decode(
+                self.tag if self._expl is None else self._expl
+            )
+            self._tag_order = (tag_class, tag_num)
         if default is not None:
             optional = True
         self.optional = optional
@@ -1274,6 +1284,7 @@ class Obj(object):
         if state.version != __version__:
             raise ValueError("data is pickled by different PyDERASN version")
         self.tag = state.tag
+        self._tag_order = state.tag_order
         self._expl = state.expl
         self.default = state.default
         self.optional = state.optional
@@ -1284,6 +1295,12 @@ class Obj(object):
         self.lenindef = state.lenindef
         self.ber_encoded = state.ber_encoded
 
+    @property
+    def tag_order(self):
+        """Tag's (class, number) used for DER/CER sorting
+        """
+        return self._tag_order
+
     @property
     def tlen(self):
         """See :ref:`decoding`
@@ -1937,6 +1954,7 @@ class Boolean(Obj):
         return BooleanState(
             __version__,
             self.tag,
+            self._tag_order,
             self._expl,
             self.default,
             self.optional,
@@ -2206,6 +2224,7 @@ class Integer(Obj):
         return IntegerState(
             __version__,
             self.tag,
+            self._tag_order,
             self._expl,
             self.default,
             self.optional,
@@ -2256,6 +2275,8 @@ class Integer(Obj):
 
     @property
     def named(self):
+        """Return named representation (if exists) of the value
+        """
         for name, value in iteritems(self.specs):
             if value == self._value:
                 return name
@@ -2604,6 +2625,7 @@ class BitString(Obj):
         return BitStringState(
             __version__,
             self.tag,
+            self._tag_order,
             self._expl,
             self.default,
             self.optional,
@@ -2633,6 +2655,8 @@ class BitString(Obj):
 
     @property
     def bit_len(self):
+        """Returns number of bits in the string
+        """
         self._assert_ready()
         return self._value[0]
 
@@ -2653,6 +2677,10 @@ class BitString(Obj):
 
     @property
     def named(self):
+        """Named representation (if exists) of the bits
+
+        :returns: [str(name), ...]
+        """
         return [name for name, bit in iteritems(self.specs) if self[bit]]
 
     def __call__(
@@ -3033,6 +3061,7 @@ class OctetString(Obj):
         return OctetStringState(
             __version__,
             self.tag,
+            self._tag_order,
             self._expl,
             self.default,
             self.optional,
@@ -3338,6 +3367,7 @@ class Null(Obj):
         return NullState(
             __version__,
             self.tag,
+            self._tag_order,
             self._expl,
             self.default,
             self.optional,
@@ -3553,6 +3583,7 @@ class ObjectIdentifier(Obj):
         return ObjectIdentifierState(
             __version__,
             self.tag,
+            self._tag_order,
             self._expl,
             self.default,
             self.optional,
@@ -4654,6 +4685,9 @@ class Choice(Obj):
             self.default = default_obj
             if value is None:
                 self._value = copy(default_obj._value)
+        if self._expl is not None:
+            tag_class, _, tag_num = tag_decode(self._expl)
+            self._tag_order = (tag_class, tag_num)
 
     def _value_sanitize(self, value):
         if (value.__class__ == tuple) and len(value) == 2:
@@ -4683,6 +4717,7 @@ class Choice(Obj):
         return ChoiceState(
             __version__,
             self.tag,
+            self._tag_order,
             self._expl,
             self.default,
             self.optional,
@@ -4728,14 +4763,23 @@ class Choice(Obj):
 
     @property
     def choice(self):
+        """Name of the choice
+        """
         self._assert_ready()
         return self._value[0]
 
     @property
     def value(self):
+        """Value of underlying choice
+        """
         self._assert_ready()
         return self._value[1]
 
+    @property
+    def tag_order(self):
+        self._assert_ready()
+        return self._value[1].tag_order if self._tag_order is None else self._tag_order
+
     def __getitem__(self, key):
         if key not in self.specs:
             raise ObjUnknown(key)
@@ -4904,17 +4948,31 @@ class Any(Obj):
         """
         :param value: set the value. Either any kind of pyderasn's
                       **ready** object, or bytes. Pay attention that
-                      **no** validation is performed is raw binary value
-                      is valid TLV
+                      **no** validation is performed if raw binary value
+                      is valid TLV, except just tag decoding
         :param bytes expl: override default tag with ``EXPLICIT`` one
         :param bool optional: is object ``OPTIONAL`` in sequence
         """
         super(Any, self).__init__(None, expl, None, optional, _decoded)
-        self._value = None if value is None else self._value_sanitize(value)
+        if value is None:
+            self._value = None
+        else:
+            value = self._value_sanitize(value)
+            self._value = value
+            if self._expl is None:
+                if value.__class__ == binary_type:
+                    tag_class, _, tag_num = tag_decode(tag_strip(value)[0])
+                else:
+                    tag_class, tag_num = value.tag_order
+            else:
+                tag_class, _, tag_num = tag_decode(self._expl)
+            self._tag_order = (tag_class, tag_num)
         self.defined = None
 
     def _value_sanitize(self, value):
         if value.__class__ == binary_type:
+            if len(value) == 0:
+                raise ValueError("Any value can not be empty")
             return value
         if isinstance(value, self.__class__):
             return value._value
@@ -4926,6 +4984,11 @@ class Any(Obj):
     def ready(self):
         return self._value is not None
 
+    @property
+    def tag_order(self):
+        self._assert_ready()
+        return self._tag_order
+
     @property
     def bered(self):
         if self.expl_lenindef or self.lenindef:
@@ -4938,6 +5001,7 @@ class Any(Obj):
         return AnyState(
             __version__,
             self.tag,
+            self._tag_order,
             self._expl,
             None,
             self.optional,
@@ -5294,6 +5358,7 @@ class Sequence(Obj):
         return SequenceState(
             __version__,
             self.tag,
+            self._tag_order,
             self._expl,
             self.default,
             self.optional,
@@ -5623,14 +5688,12 @@ class Set(Sequence):
     asn1_type_name = "SET"
 
     def _encode(self):
-        raws = [v.encode() for v in self._values_for_encoding()]
-        raws.sort()
-        v = b"".join(raws)
+        v = b"".join(value.encode() for value in sorted(
+            self._values_for_encoding(),
+            key=attrgetter("tag_order"),
+        ))
         return b"".join((self.tag, len_encode(len(v)), v))
 
-    def _specs_items(self):
-        return iteritems(self.specs)
-
     def _decode(self, tlv, offset, decode_path, ctx, tag_only):
         try:
             t, tlen, lv = tag_strip(tlv)
@@ -5684,12 +5747,13 @@ class Set(Sequence):
         ber_encoded = False
         ctx_allow_default_values = ctx.get("allow_default_values", False)
         ctx_allow_unordered_set = ctx.get("allow_unordered_set", False)
-        value_prev = memoryview(v[:0])
+        tag_order_prev = (0, 0)
+        _specs_items = copy(self.specs)
 
         while len(v) > 0:
             if lenindef and v[:EOC_LEN].tobytes() == EOC:
                 break
-            for name, spec in self._specs_items():
+            for name, spec in iteritems(_specs_items):
                 sub_decode_path = decode_path + (name,)
                 try:
                     spec.decode(
@@ -5718,8 +5782,9 @@ class Set(Sequence):
                 ctx=ctx,
                 _ctx_immutable=False,
             )
+            value_tag_order = value.tag_order
             value_len = value.fulllen
-            if value_prev.tobytes() > v[:value_len].tobytes():
+            if tag_order_prev >= value_tag_order:
                 if ctx_bered or ctx_allow_unordered_set:
                     ber_encoded = True
                 else:
@@ -5741,10 +5806,12 @@ class Set(Sequence):
                     offset=sub_offset,
                 )
             values[name] = value
-            value_prev = v[:value_len]
+            del _specs_items[name]
+            tag_order_prev = value_tag_order
             sub_offset += value_len
             vlen += value_len
             v = v_tail
+
         obj = self.__class__(
             schema=self.specs,
             impl=self.tag,
@@ -5882,6 +5949,7 @@ class SequenceOf(Obj):
         return SequenceOfState(
             __version__,
             self.tag,
+            self._tag_order,
             self._expl,
             self.default,
             self.optional,
@@ -6135,9 +6203,7 @@ class SetOf(SequenceOf):
     asn1_type_name = "SET OF"
 
     def _encode(self):
-        raws = [v.encode() for v in self._values_for_encoding()]
-        raws.sort()
-        v = b"".join(raws)
+        v = b"".join(sorted(v.encode() for v in self._values_for_encoding()))
         return b"".join((self.tag, len_encode(len(v)), v))
 
     def _decode(self, tlv, offset, decode_path, ctx, tag_only):