]> Cypherpunks.ru repositories - pyderasn.git/commitdiff
Strict SET values ordering check
authorSergey Matveev <stargrave@stargrave.org>
Mon, 10 Sep 2018 19:26:48 +0000 (22:26 +0300)
committerSergey Matveev <stargrave@stargrave.org>
Mon, 10 Sep 2018 19:54:28 +0000 (22:54 +0300)
doc/news.rst
pyderasn.py
tests/test_pyderasn.py

index d0f156f560e6bd21c663c4a859191cb192083cd8..6712fe3d686c59d39978dbbb1642ac86a968f8a4 100644 (file)
@@ -9,6 +9,7 @@ News
 * **Incompatible** change: defaulted values in Sequence/Set are always
   strictly checked, unless ``allow_default_values`` context option is
   set. ``strict_default_existence`` option disappeared
+* Strict Set's values ordering check
 
 .. _release3.14:
 
index a7732964dedcc8befbd31da6f4778cfb1cb23dc6..3dd3ea9c151a3f1ca6f571a7765614e1c5bf5b91 100755 (executable)
@@ -215,6 +215,7 @@ Currently available context options:
 
 * :ref:`allow_default_values <allow_default_values_ctx>`
 * :ref:`allow_expl_oob <allow_expl_oob_ctx>`
+* :ref:`allow_unordered_set <allow_unordered_set_ctx>`
 * :ref:`bered <bered_ctx>`
 * :ref:`defines_by_path <defines_by_path_ctx>`
 
@@ -4708,6 +4709,14 @@ class Set(Sequence):
     """``SET`` structure type
 
     Its usage is identical to :py:class:`pyderasn.Sequence`.
+
+    .. _allow_unordered_set_ctx:
+
+    DER prohibits unordered values encoding and will raise an error
+    during decode. If If :ref:`bered <bered_ctx>` context option is set,
+    then no error will occure. Also you can disable strict values
+    ordering check by setting ``"allow_unordered_set": True``
+    :ref:`context <ctx>` option.
     """
     __slots__ = ()
     tag_default = tag_encode(form=TagFormConstructed, num=17)
@@ -4771,6 +4780,8 @@ class Set(Sequence):
         values = {}
         bered = 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])
         specs_items = self.specs.items
         while len(v) > 0:
             if lenindef and v[:EOC_LEN].tobytes() == EOC:
@@ -4803,9 +4814,16 @@ class Set(Sequence):
                 ctx=ctx,
             )
             value_len = value.fulllen
-            sub_offset += value_len
-            vlen += value_len
-            v = v_tail
+            if value_prev.tobytes() > v[:value_len].tobytes():
+                if ctx_bered or ctx_allow_unordered_set:
+                    bered = True
+                else:
+                    raise DecodeError(
+                        "unordered " + self.asn1_type_name,
+                        klass=self.__class__,
+                        decode_path=sub_decode_path,
+                        offset=sub_offset,
+                    )
             if spec.default is None or value != spec.default:
                 pass
             elif ctx_bered or ctx_allow_default_values:
@@ -4818,6 +4836,10 @@ class Set(Sequence):
                     offset=sub_offset,
                 )
             values[name] = value
+            value_prev = v[:value_len]
+            sub_offset += value_len
+            vlen += value_len
+            v = v_tail
         obj = self.__class__(
             schema=self.specs,
             impl=self.tag,
@@ -4826,8 +4848,6 @@ class Set(Sequence):
             optional=self.optional,
             _decoded=(offset, llen, vlen + (EOC_LEN if lenindef else 0)),
         )
-        obj._value = values
-        obj.bered = bered
         if lenindef:
             if v[:EOC_LEN].tobytes() != EOC:
                 raise DecodeError(
@@ -4838,6 +4858,7 @@ class Set(Sequence):
                 )
             tail = v[EOC_LEN:]
             obj.lenindef = True
+        obj._value = values
         if not obj.ready:
             raise DecodeError(
                 "not all values are ready",
@@ -4845,6 +4866,7 @@ class Set(Sequence):
                 decode_path=decode_path,
                 offset=offset,
             )
+        obj.bered = bered
         return obj, tail
 
 
index c8321bb0d25116a2ea4320c2ff187bed8df54499..797d9b02ed8ed73b281fdc4283409bf6e066c247 100644 (file)
@@ -4972,6 +4972,35 @@ class TestSet(SeqMixing, CommonMixin, TestCase):
             b"".join(sorted([seq[name].encode() for name, _ in Seq.schema])),
         )
 
+    @settings(max_examples=LONG_TEST_MAX_EXAMPLES)
+    @given(data_strategy())
+    def test_unsorted(self, d):
+        tags = [
+            tag_encode(tag) for tag in
+            d.draw(sets(integers(min_value=1), min_size=2, max_size=5))
+        ]
+        tags = d.draw(permutations(tags))
+        assume(tags != sorted(tags))
+        encoded = b"".join(OctetString(t, impl=t).encode() for t in tags)
+        seq_encoded = b"".join((
+            Set.tag_default,
+            len_encode(len(encoded)),
+            encoded,
+        ))
+
+        class Seq(Set):
+            schema = [(str(i), OctetString(impl=t)) for i, t in enumerate(tags)]
+        seq = Seq()
+        with assertRaisesRegex(self, DecodeError, "unordered SET"):
+            seq.decode(seq_encoded)
+        for ctx in ({"bered": True}, {"allow_unordered_set": True}):
+            seq_decoded, _ = Seq().decode(seq_encoded, ctx=ctx)
+            self.assertTrue(seq_decoded.bered)
+            self.assertSequenceEqual(
+                [bytes(seq_decoded[str(i)]) for i, t in enumerate(tags)],
+                [t for t in tags],
+            )
+
 
 @composite
 def seqof_values_strategy(draw, schema=None, do_expl=False):