doc/news.rst | 1 + pyderasn.py | 32 +++++++++++++++++++++++++++----- tests/test_pyderasn.py | 29 +++++++++++++++++++++++++++++ diff --git a/doc/news.rst b/doc/news.rst index 3c4d04452a02c281558556fef70e3d8c7389e4eb4f168c43bcefaab8a94b1953..fe96946b1ff1f768836a0cb672692050cd0c877c4b12a440e65eab978bebb206 100644 --- a/doc/news.rst +++ b/doc/news.rst @@ -9,6 +9,7 @@ * Default value is checked also for Sets, not for Sequences only * **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: diff --git a/pyderasn.py b/pyderasn.py index 0ed59a30a12acc345d9f8edee69d58888caaa23848781ccd4b4598fa169119e1..d5add7b2e62e72cc73134f35f97fe237b0a6863f1af02a9bf3ea9231f1ed183f 100755 --- a/pyderasn.py +++ b/pyderasn.py @@ -215,6 +215,7 @@ Currently available context options: * :ref:`allow_default_values ` * :ref:`allow_expl_oob ` +* :ref:`allow_unordered_set ` * :ref:`bered ` * :ref:`defines_by_path ` @@ -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 ` 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 ` option. """ __slots__ = () tag_default = tag_encode(form=TagFormConstructed, num=17) @@ -4771,6 +4780,8 @@ sub_offset = offset + tlen + llen 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 @@ decode_path=sub_decode_path, 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 @@ decode_path=sub_decode_path, 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 @@ default=self.default, 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 @@ offset=offset, ) 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 @@ klass=self.__class__, decode_path=decode_path, offset=offset, ) + obj.bered = bered return obj, tail diff --git a/tests/test_pyderasn.py b/tests/test_pyderasn.py index 662c2c4ec041ac87e07fc1ee299737a588dfc5a6939730b6195f43c5da7a4454..c81ac44fb014362d6a83b6e2d12885a87fc83472a3616188f65dff94930898aa 100644 --- a/tests/test_pyderasn.py +++ b/tests/test_pyderasn.py @@ -4972,6 +4972,35 @@ seq_encoded[seq_decoded.tlen + seq_decoded.llen:], 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):